Все уроки · 44
Путь 3 · Дорожная карта AI-инженера·Урок 6 из 33

Разговор с моделью: токены, контекст и сэмплинг

Как устроен разговор с LLM: как текст превращается в токены, почему переполняется окно контекста и что на самом деле делают temperature, top-p и top-k.

УровеньСреднийВремя~15 мин

Всё — это токены

Почему это важно? Потому что токены — это единица, в которой вам выставляют счёт, единица, в которой измеряется предел памяти, и причина, по которой «хитрый» промпт порой обходится в десять раз дороже, чем вы рассчитывали. Пятнадцать минут за этим уроком превратят загадочные счета в простую арифметику.

Модель читает токены — фрагменты текста из фиксированного словаря, в английском обычно длиной 3–4 символа. Букв и слов она не видит. Слово «artificial» может оказаться одним токеном или двумя — это зависит от токенизатора (небольшой программы, которая разбивает текст на токены до того, как его увидит модель). Код, неанглийские языки и редкие символы часто разбиваются на токены менее эффективно — а значит, стоят дороже и быстрее заполняют контекст.

Три практических следствия:

  • Платить приходится за токены — и за то, что вы отправляете, и за то, что получаете.
  • Окно контекста — всё, что модель «видит» в одном запросе, её рабочая память, — измеряется в токенах. У современных моделей оно составляет от 8 тыс. (примерно объём этого урока) до миллиона и больше.
  • Между запросами модель ничего не помнит. То, что выглядит как память, — это ваше приложение, которое каждый раз заново отправляет весь разговор. Модель видит ваш промпт как один сплошной поток: системные инструкции, сообщения пользователя, результаты инструментов, её собственные прежние ответы — всё это склеено в токены. Никакой волшебной памяти за пределами этого окна нет.

Окно контекста — это бюджет

Представьте окно контекста как бюджет, из которого тратят все части вашего приложения. Вот реалистичный пример работы агента — нажмите «запуск» и посмотрите, как бюджет заполняется:

Окно контекста ассистента службы поддержки
Каждый запрос заново отправляет всю историю — смотрите, как она копится
~0tokens
/ 8,000
press play to simulate
press play to simulate
0/7

Две вещи, которые инженеры снова и снова узнают на горьком опыте:

  1. Каждое сообщение заново отправляет всё. Разговор из 10 реплик стоит дороже, чем 10 реплик в токенах: вы платите за растущий «треугольник» повторяющейся истории. Поэтому «простой чат-бот» может обходиться дорого.
  2. Длинный контекст — это не бесплатная память. Модели заметно хуже используют информацию, спрятанную в середине огромного контекста («потерянное в середине», lost in the middle). Больше контекста не означает лучших ответов.

Когда окно заполняется, приложения выполняют сжатие контекста (compaction): кратко пересказывают старые реплики и отбрасывают детали, оставляя только важное. Вы поступили бы так же, передавая коллеге длинную переписку с клиентом: вместо 200 сообщений написали бы «клиент заказал дважды, просит вернуть деньги за один заказ». (Отметьте в симуляции выше пункт «симулировать сжатие контекста»: громоздкий результат инструмента исчезнет, а закреплённый системный промпт останется. Закономерность такая: инструкции должны пережить сжатие, а лишний объём — нет.)

Сэмплирование: как выбирается один токен

На выходе модель выдаёт вероятность для каждого токена из своего словаря (полного списка известных ей фрагментов — их десятки тысяч). Что-то должно превратить эти вероятности в один конкретный выбор. Это и есть сэмплирование — бросок взвешенного кубика, — и вы определяете, как именно этот кубик смещён.

Главный параметр — температура (temperature). Прежде чем выбрать токен, модель делит свои «сырые» оценки на температуру. Высокая температура «сглаживает» распределение: маловероятные токены становятся вероятнее — как будто кубик перебалансировали так, что все грани почти равны. Низкая температура, наоборот, «заостряет» его: самый вероятный токен становится почти неизбежным. При температуре 0 модель всегда выбирает единственный самый вероятный токен, поэтому один и тот же вход даёт один и тот же результат.

Поэкспериментируйте:

How temperature changes sampling
prompt›Столица Франции —▍
Париж
78.3%
Лондон
4.2%
это
7.5%
город
5.8%
известный
4.2%

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 сообщений. Что в этом случае происходит на стороне провайдера?

Вам нужно, чтобы один и тот же вход всегда давал один и тот же результат. Что вы задаёте?

Ваше приложение ставит вопрос пользователя первым, а огромную неизменную базу знаний — вторым. В чём практический минус?