Модуль 3. Инференс: декодирование, температура, контекст
Цель модуля: освоить слой между моделью и пользователем — декодер и контракт общения с моделью. После модуля вы осознанно выставляете temperature/top-p под задачу, понимаете, что такое chat template и почему без него модель «несёт не туда», и знаете, откуда берутся галлюцинации — а значит, готовы выбирать между «положить в контекст» и «положить в веса».
Перед началом: модули 1–2 (токены, attention, «предскажи следующий токен»). Инструменты: песочница сайта, сниппет temperature-topp. Трудоёмкость: ~1–1.5 ч (чтение 40 мин · сниппет и практика 30 мин).
🔥 «Модель стала тупее после релиза» — а модель не менялась
Команда поддержки жалуется: бот стал многословным и «фантазирующим». Никто
модель не трогал. Расследование занимает день: в очередном рефакторинге
дефолтные параметры генерации поменялись местами — temperature подросла с
0.3 до 0.8. Модель та же, веса те же, ответы другие.
Это самая недооценённая истина работы с LLM: модель и ответ — не одно и то же. Между ними стоит декодер — правила выбора токенов из распределения, которое выдаёт модель, — плюс шаблон диалога и управление контекстом. Этот слой не «настройки для гурманов», а часть дизайна продукта: поддержка, экстракция структурированных данных и копирайтинг хотят разного декодера. И только поняв его, можно честно рассуждать, что чинить — промпт, декодер или веса (модуль 4).
📖 3.1. От распределения к токену: greedy, sampling, temperature
На каждом шаге модель выдаёт логиты — баллы для каждого токена словаря. Превратить их в «следующий токен» можно по-разному:
- greedy: взять самый вероятный. Детерминированно, но текст закомпонованный и «мертвенный», а в длинной генерации — зацикливается («очень хорошо, очень хорошо, очень…»).
- sampling: трактовать распределение как вероятности и бросать кубик. Живее, но без регулировки можно вытащить «банан» из хвоста распределения.
Регулятор — temperature. Перед softmax логиты делятся на T: T < 1 заостряет распределение (вероятные токены вероятнее), T > 1 — разглаживает (хвост получает шанс). T → 0 вырождается в greedy. Ключевой факт: T не меняет модель и не «делает её умнее» — она лишь перераспределяет выбор среди тех же кандидатов.
Фильтры хвоста: top-k («рассматриваем только k самых вероятных») и top-p («рассматриваем минимальный набор токенов, чья суммарная вероятность ≥ p»). Отрезание хвоста почти не снижает качество, но убирает катастрофические выборы «банана». Продакшен-дефолты индустрии: T 0.1–0.7, top_p 0.8–0.95; для генерации кода и извлечения данных — T ближе к 0.
Запустите сниппет temperature-topp.py: одни и те же логиты при T 0.2/1/3
дают три разных «характера» ответа, top_p 0.8 отрезает хвост, а пять
выборок с фиксированным seed показывают, что «случайность» декодера
воспроизводима, если зафиксировать seed. Это базовое требование к
evals (модуль 8): нефиксированный декодер делает любой A/B-тест шумом.
Всё это — не учебная абстракция: в рабочем сервисе, который ходит к LLM, запрос выглядит так. Фрагмент боевого контракта (за одним портом — Ollama, OpenAI-совместимые провайдеры, Anthropic — язык один):
gotype Request struct { Model string // "qwen2.5:7b", URI своей модели Messages []Message // system/user/assistant — тот самый чат-контракт MaxTokens int // 0 = лимит провайдера по умолчанию Temperature float64 // 0..1; провайдер может клампить TopP float64 // 0 = дефолт провайдера FrequencyPenalty float64 // штраф за повторы (-2..2) PresencePenalty float64 // штраф за «уже сказанное» Stop []string // строки-остановки }
Разница между этой структурой и сниппетом ниже — только масштаб последствий: за неверной temperature в проде стоят ответы, которые видит пользователь.
📖 3.2. Chat template: договор о разметке диалога
Модуль 2 закончился тем, что модель обучали продолжать текст. Диалоговое поведение в ней появляется после instruction tuning, и обучалась она на размеченных диалогах специальными служебными токенами:
text<|system|> Ты — ассистент поддержки. Отвечай кратко.<|end|> <|user|> Как вернуть деньги за курс?<|end|> <|assistant|>
Роли: system — конституция диалога (тон, правила, границы), читается
моделью как самый сильный контекст; user — реплики пользователя;
assistant — ответы модели (в конце шаблона модель продолжает именно
позицию assistant). Это chat template — у каждого семейства моделей
свой формат служебных токенов, и API провайдера обычно прячет его за
структурой messages[].
Следствия для практики:
- Системный промпт — не «мягкое пожелание», а часть контекста с высоким приоритетом, но не гарант: конфликтные инструкции модель может трактовать по-разному, проверяйте на тестах (модуль 8).
- Few-shot примеры — это 2–5 пар user/assistant прямо в контексте перед реальным вопросом. Дёшево (только токены), мгновенно меняет формат ответов — первая ступень лестницы решений в модуле 4.
- Кривой шаблон = кривые ответы. Если «скормить» диалоговой модели просто склеенный текст без разметки, она отвечает хуже: вы сдвигаете её из распределения, на котором она обучалась. При дообучении (модуль 5) датасет обязан использовать тот же шаблон, что и инференс, — иначе обучили одному, спрашиваете другое.
📖 3.3. Контекстное окно: бюджет, а не вместилище
Модуль 1 дал единицу измерения (токены), модуль 2 — причину лимита (квадратичная цена attention). Теперь инженерная сторона. Контекстное окно — это бюджет на всё сразу: системный промпт + история диалога + few-shot примеры + документы из RAG + черновики + сам ответ. Превысили — придётся усекать, и вопрос «что выбросить» — дизайн-решение:
- усечение истории «оставить последние N реплик» — просто, но теряет обещания из начала диалога;
- резюмирование старых реплик моделью — сохраняет суть, добавляет латентность и цену;
- «вынести наружу» в хранилище и подкладывать по запросу (эмбеддинги, модуль 1) — так работает RAG.
Правило инженера: считайте бюджет токенов для худшего случая. Виджет поддержки с 8k окном, системным промптом на 400 токенов, тремя few-shot на 1500 и документов на 4000 — уже почти без запаса на диалог.
Насколько быстро контекст съедается на самом деле — замер из рабочего сервиса-оркестратора (агентный цикл: модель отвечает «действием», код выполняет его и возвращает результат). Каждая итерация добавляет +125–150 токенов: ответ модели плюс наблюдение инструмента. На задаче из 11 файлов контекст вырос с 602 до 2 142 токенов за 12 итераций — живой график метрики это и показал. Экстраполяция: 40–50 итераций рефакторинга — 7–9k токенов только на историю, и вторая беда важнее первой: старые шаги становятся шумом, качество решений падает. Решение, которое там применили: детерминированное «rolling summary» — история шагов сжимается по коду (без обращения к LLM), а системный промпт и цель не сжимаются никогда. Приём запомните: сжимайте историю, не трогая конституцию диалога.
📖 3.4. Откуда галлюцинации и «незнание»
Финальный кусок фундамента. Веса модели заморожены в момент окончания обучения; всё, что «знает» модель, — статистика данных той давности. Отсюда три честных вывода:
- Ваших фактов в весах нет. Регламент возврата вашей платформы, вчерашняя цена, внутренний API — модели неизвестны, но она обязана продолжить текст уверенно: в этом её работа. Уверенный тон — не признак знания (модуль 2: задача — правдоподобное продолжение, а не истина).
- Галлюцинация — не сбой, а режим. Плохо определённый вопрос + sampling из хвоста → правдоподобная выдумка. Снижается холодным декодером, few-shot и прямым запретом «если не знаешь — так и скажи», но не исчезает: проверка фактов — задача системы, не модели.
- Свестись к нулю можно только фактами. Есть ровно два места, куда кладут факты: контекст (RAG, модуль 4) или веса (дообучение на своих данных, модули 5+). Это и есть главная развилка всего курса.
📖 3.5. Компромиссы
- T низкая vs высокая. Низкая — стабильность, повторяемость, фактология; высокая — разнообразие, креативность, и галлюцинации. Под задачу, не «по вкусу», и фиксировать в конфиге версии сервиса (модуль 12).
- top_p vs top_k. top_p адаптивен (отрезает по вероятностной массе), top_k стабилен (фиксированный список). Обычно достаточно одного; их комбинируют редко.
- seed vs случайность. Seed делает evals воспроизводимыми, но прячет хвостовые режимы; для тестов гоняют несколько seed.
- Длинный контекст vs RAG. Вложить всё в 128k проще архитектурно, но дороже за запрос и хуже по точности («иголка в стоге теряется»); RAG дешевле и точнее на фактах, но требует инфраструктуры. Это же сравнение вернётся в модуле 4 как выбор ступени.
💻 Практика
- Три характера (10 мин). Запустите
temperature-topp.py. По выводу оцените: при какой T «банан» получает заметные проценты? Какая T даёт «поддержку», какая — «копирайтера»? - Сломайте фильтр (10 мин). В сниппете поднимите top_p до 0.99 и опустите до 0.5. Объясните, почему при 0.5 распределение после перенормировки снова похоже на greedy.
- Бюджет виджета (15 мин). Посчитайте токен-бюджет своего гипотетического бота: системный промпт (напишите его, ~200 токенов), 3 few-shot, окно 8k. Сколько реплик диалога помещается? Когда пора резюмировать?
- Декодер-чеклист продакшена (10 мин). Составьте для своей задачи конфиг: temperature, top_p, seed, max_tokens, и объясните каждую строку одной фразой. Этот же конфиг станет частью «версии модели» в модуле 12.
🧩 Quiz "Проверь себя"
✅ Итоги модуля
- Настроить декодер под задачу: поддержка/экстракция vs креатив.
- Показать на сниппете эффект T и top_p и объяснить переносормировку.
- Объяснить роли system/user/assistant и зачем датасету тот же шаблон, что и на инференсе.
- Посчитать токен-бюджет приложения с worst case.
- Назвать два места, куда можно «положить факты», и их цену.
🔗 Мостик к Модулю 4
Фундамент заложен: вы знаете, как текст становится токенами, как attention делает их контекстными, как декодер превращает распределение в ответ и куда деваются факты. Пора к главному инженерному вопросу: у модели «что-то не так» — чинить промптом, подкладывать документы (RAG) или дообучать веса? В модуле 4 построим лестницу решений и научимся выбирать ступень без лишних трат.