Все уроки · 44
Разговор с моделью: токены, контекст и сэмплинг
Как устроен разговор с LLM: как текст превращается в токены, почему переполняется окно контекста и что на самом деле делают temperature, top-p и top-k.
Всё — это токены
Почему это важно? Потому что токены — это единица, в которой вам выставляют счёт, единица, в которой измеряется предел памяти, и причина, по которой «хитрый» промпт порой обходится в десять раз дороже, чем вы рассчитывали. Пятнадцать минут за этим уроком превратят загадочные счета в простую арифметику.
Модель читает токены — фрагменты текста из фиксированного словаря, в английском обычно длиной 3–4 символа. Букв и слов она не видит. Слово «artificial» может оказаться одним токеном или двумя — это зависит от токенизатора (небольшой программы, которая разбивает текст на токены до того, как его увидит модель). Код, неанглийские языки и редкие символы часто разбиваются на токены менее эффективно — а значит, стоят дороже и быстрее заполняют контекст.
Три практических следствия:
- Платить приходится за токены — и за то, что вы отправляете, и за то, что получаете.
- Окно контекста — всё, что модель «видит» в одном запросе, её рабочая память, — измеряется в токенах. У современных моделей оно составляет от 8 тыс. (примерно объём этого урока) до миллиона и больше.
- Между запросами модель ничего не помнит. То, что выглядит как память, — это ваше приложение, которое каждый раз заново отправляет весь разговор. Модель видит ваш промпт как один сплошной поток: системные инструкции, сообщения пользователя, результаты инструментов, её собственные прежние ответы — всё это склеено в токены. Никакой волшебной памяти за пределами этого окна нет.
Окно контекста — это бюджет
Представьте окно контекста как бюджет, из которого тратят все части вашего приложения. Вот реалистичный пример работы агента — нажмите «запуск» и посмотрите, как бюджет заполняется:
Две вещи, которые инженеры снова и снова узнают на горьком опыте:
- Каждое сообщение заново отправляет всё. Разговор из 10 реплик стоит дороже, чем 10 реплик в токенах: вы платите за растущий «треугольник» повторяющейся истории. Поэтому «простой чат-бот» может обходиться дорого.
- Длинный контекст — это не бесплатная память. Модели заметно хуже используют информацию, спрятанную в середине огромного контекста («потерянное в середине», lost in the middle). Больше контекста не означает лучших ответов.
Когда окно заполняется, приложения выполняют сжатие контекста (compaction): кратко пересказывают старые реплики и отбрасывают детали, оставляя только важное. Вы поступили бы так же, передавая коллеге длинную переписку с клиентом: вместо 200 сообщений написали бы «клиент заказал дважды, просит вернуть деньги за один заказ». (Отметьте в симуляции выше пункт «симулировать сжатие контекста»: громоздкий результат инструмента исчезнет, а закреплённый системный промпт останется. Закономерность такая: инструкции должны пережить сжатие, а лишний объём — нет.)
Сэмплирование: как выбирается один токен
На выходе модель выдаёт вероятность для каждого токена из своего словаря (полного списка известных ей фрагментов — их десятки тысяч). Что-то должно превратить эти вероятности в один конкретный выбор. Это и есть сэмплирование — бросок взвешенного кубика, — и вы определяете, как именно этот кубик смещён.
Главный параметр — температура (temperature). Прежде чем выбрать токен, модель делит свои «сырые» оценки на температуру. Высокая температура «сглаживает» распределение: маловероятные токены становятся вероятнее — как будто кубик перебалансировали так, что все грани почти равны. Низкая температура, наоборот, «заостряет» его: самый вероятный токен становится почти неизбежным. При температуре 0 модель всегда выбирает единственный самый вероятный токен, поэтому один и тот же вход даёт один и тот же результат.
Поэкспериментируйте:
low temperature → the sharp token wins almost always (safe, repetitive). high temperature → the distribution flattens, unusual tokens get a real chance (creative, risky).
Выставьте температуру 0,0 и нажмите «выбрать» несколько раз: каждый раз будет «Париж». Теперь поднимите её до 1,5 и снова понажимайте: начнут появляться «Лондон» и «город». Ни одно из значений не «правильное» — это разные инструменты:
- Низкая температура (0,0–0,3) — извлечение данных, классификация, код, всё, где нужен самый вероятный ответ, причём детерминированно (один и тот же вход — один и тот же результат, каждый раз).
- Средняя (0,5–0,8) — обычный чат и письмо: немного разнообразия без бессмыслицы.
- Высокая (от 1,0) — мозговой штурм, творческая генерация — когда вы хотите неожиданностей.
Остальные параметры — коротко:
- top-p (ядерное сэмплирование) — вместо фиксированного числа кандидатов модель оставляет наименьший набор токенов, вероятности которых в сумме дают p (например, 0,9), и выбирает уже из них. Это можно понимать как «отбросить длинный хвост нелепых вариантов». Его часто используют вместе с температурой; многие команды просто задают температуру, а top-p оставляют по умолчанию.
- top-k — модель оставляет только k самых вероятных токенов и игнорирует остальные. Это более грубый вариант top-p.
- штраф за повторения (repetition penalty) — небольшой «толчок», из-за которого уже использованные токены становятся менее вероятными; он уменьшает зацикливание в длинных ответах. Одни API дают им управлять, другие нет.
- max tokens — жёсткий предел длины ответа. Задавайте его всегда: это ваша страховка от неконтролируемого счёта.
При этом творчество в ответах задаёт промпт, а температура лишь добавляет шума: она определяет, как далеко от проторённой дорожки забредёт сэмплер.
Практическое правило
Начинайте с температуры 0,2–0,7 — в зависимости от того, насколько творческая задача. Меняйте top-p и top-k, только если есть конкретная причина: в большинстве API уже заданы разумные значения по умолчанию.
Каждый параметр на этой странице — температура, top-p, max tokens — меняет только продолжение в одном вызове. Сам вызов остаётся прежним: на входе текст, на выходе продолжение. Чат, агенты, инструменты и RAG — это тот же вызов, только с другим текстом на входе. Эта мысль — основа всего курса: Один вызов: как на самом деле работают все ИИ-продукты.
Попробуйте: скопируйте эту конфигурацию
С этой конфигурации я начинаю любой новый проект. Скопируйте её, поменяйте название модели и меняйте по одному числу за раз — никогда по два, иначе вы не поймёте, какое из них помогло.
response = client.chat.completions.create(
model="gpt-4o-mini",
# 0.2 для фактов и извлечения данных, 0.7 для текстов и чата
temperature=0.2,
# жёсткий предел длины ответа — задавайте его всегда
max_tokens=500,
# не трогайте это без причины
# top_p=0.9,
# n=1, # сколько альтернативных ответов сгенерировать
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Your question here."},
],
)Два параметра, которые легко пропустить и которые стоит знать: n=1 означает
«дай мне один ответ» — увеличьте его, чтобы получить несколько вариантов на
выбор (и заплатить за каждый). А seed, где он поддерживается, делает
сэмплирование воспроизводимым: тот же seed, тот же вход — тот же результат; это
удобно, когда вы отлаживаете промпт.
Потоковая передача: почему ответ появляется по словам
Модель генерирует по одному токену, слева направо: она не может начать последнее слово, пока не написала все предыдущие. API умеют отправлять вам каждый токен в тот момент, когда он создан, — это и есть потоковая передача (streaming). Сам ответ не меняется, меняется только доставка. Сравните со скачиванием файла: он остаётся того же размера, есть индикатор прогресса или нет, но с индикатором ожидание кажется гораздо короче.
Вот и весь трюк: пользователь начинает читать уже через несколько сотен миллисекунд, хотя готового блока текста пришлось бы ждать несколько секунд. Для любого интерактивного интерфейса потоковая передача — обязательный минимум.
Кэширование промпта: платим один раз за повторяющийся контекст
Обратите внимание на симуляцию выше: системный промпт и документы отправляются заново с каждым запросом, и каждый раз вы платите полную цену. Многие провайдеры предлагают кэширование промпта (prompt caching): вы помечаете неизменную часть (стабильное начало промпта — системный промпт, приложенные документы, описания инструментов), и провайдер использует уже проделанную работу вместо того, чтобы повторять её. Чтение из кэша обычно стоит малую долю обычной цены входных токенов и сокращает время до первого токена.
Сначала стабильное, потом изменчивое. Если в вашем промпте чередуются меняющееся и неизменное содержимое, кэш не сработает. Поместите большой неизменный блок в начало, а настоящее сообщение пользователя — в конец.
Проверьте себя
Ваш чат-бот с каждым новым сообщением отправляет всю историю из 40 сообщений. Что в этом случае происходит на стороне провайдера?
Вам нужно, чтобы один и тот же вход всегда давал один и тот же результат. Что вы задаёте?
Ваше приложение ставит вопрос пользователя первым, а огромную неизменную базу знаний — вторым. В чём практический минус?