Учебник «Большие языковые модели»
Модель обучена. Но на выходе у неё — не текст, а распределение вероятностей по словарю для очередной позиции. Превращение распределений в конкретный текст — декодирование — это отдельный слой инженерии со своими параметрами, компромиссами и патологиями. Именно здесь живут «температура» и «top_p», доступные во многих API, и именно здесь удобнее всего разобраться с природой галлюцинаций.
Генерация устроена как цикл: подать модели контекст → получить распределение следующего токена → выбрать токен → дописать его к контексту → повторить — и так до токена конца сообщения или исчерпания лимита. Каждый новый токен проходит через все слои, но ключи и значения предыдущих позиций обычно берутся из KV-кэша, а не вычисляются заново. Prefill обрабатывает позиции параллельно, decode идёт последовательно и часто хуже загружает ускоритель; поэтому выходные токены у многих провайдеров дороже входных, хотя тарифы зависят от сервиса.
Точнее, модель выдаёт логиты z1, …, zV — по числу на каждый токен словаря, — которые softmax превращает в вероятности. Вся свобода декодирования — в том, как по этим числам выбрать один токен.
Простейшая стратегия — жадная: всегда брать самый вероятный токен (argmax). При неизменных вычислениях она детерминирована, но иногда вырождается в повторы. Попав в цикл («очень, очень, очень…»), жадная генерация не может из него выйти — каждое следующее повторение делает продолжение цикла ещё более вероятным. Кроме того, максимизация вероятности каждого шага по отдельности не максимизирует вероятность текста в целом (это задача поиска по дереву; её приближение — лучевой поиск, beam search, — применяется в машинном переводе, но для открытой генерации даёт скучный, вырожденный текст). Человеческая речь не является цепочкой самых вероятных слов — она постоянно «удивляет», и хорошее декодирование должно оставлять место случайности.
Основной инструмент управления случайностью — температура T: перед softmax логиты делятся на неё,
pi = exp(zi/T) / Σj exp(zj/T)
При T → 0 распределение стягивается к максимуму — получаем жадную генерацию; при T = 1 исходные логиты не масштабируются; при T > 1 распределение уплощается, и редкие токены получают больше шансов. Название — прямая отсылка к распределению Больцмана из статистической физики: «горячая» система охотнее посещает маловероятные состояния.
Практический режим зависит от модели и API. Для извлечения данных и классификации обычно начинают с жадного или низкотемпературного режима; для поиска разных идей пробуют более высокую температуру и несколько выборок. Числовые диапазоны нельзя переносить между моделями без теста, а некоторые reasoning-модели вовсе не дают управлять всеми параметрами сэмплирования. Даже условное T = 0 не гарантирует побитовой воспроизводимости при смене версии модели, конфигурации сервера или порядка вычислений.
Проблема высокой температуры не в случайности как таковой, а в том, что она открывает дорогу совокупности плохих токенов: каждый из тысяч токенов хвоста ничтожно вероятен, но суммарно хвост весит заметно, и одна неудачная выборка пускает генерацию под откос (а авторегрессия ошибку уже не исправит — следующие токены строятся на ней). Решение — обрезать хвост перед выборкой.
Top-k: оставить k самых вероятных токенов, остальным — вероятность ноль, перенормировать. Грубо: k фиксировано, а «естественное» число разумных продолжений меняется от шага к шагу — после «Столица Франции —» разумен один токен, после «Она открыла дверь и» — сотни.
Top-p (nucleus sampling): оставить минимальный набор токенов, суммарная вероятность которых достигает p. Размер набора адаптируется сам: где модель уверена — выживает один токен, где нет — десятки. Min-p — более поздний вариант: отсекать токены с вероятностью меньше заданной доли от вероятности лидера. Сравнительное поведение зависит от распределения и реализации. На практике температура и top-p действуют совместно, и менять оба параметра сразу не стоит — эффекты перепутываются.
Против дословных повторов применяются и прямые штрафы (repetition / frequency / presence penalty), понижающие логиты уже встречавшихся токенов; работают, но грубы — чрезмерный штраф запрещает законные повторения (термины, имена переменных в коде).
Соберём всё вместе — полный конвейер декодирования одного шага:
import numpy as np
def sample_token(logits, temperature=0.8, top_p=0.95, rng=np.random):
"""logits: (V,) -- выход модели для очередной позиции."""
z = logits / max(temperature, 1e-6)
p = np.exp(z - z.max()); p /= p.sum() # softmax
# top-p: оставляем минимальное "ядро" с суммой >= top_p
order = np.argsort(p)[::-1] # по убыванию
csum = np.cumsum(p[order])
cutoff = np.searchsorted(csum, top_p) + 1 # размер ядра
keep = order[:cutoff]
p_keep = p[keep] / p[keep].sum() # перенормировка
return rng.choice(keep, p=p_keep)
# Игрушечное распределение: один фаворит и длинный хвост
V = 1000
logits = np.full(V, -4.0); logits[:5] = [3.0, 2.2, 1.9, 0.5, 0.1]
for t in (0.2, 0.8, 1.5):
tokens = [sample_token(logits, temperature=t) for _ in range(1000)]
print(f"T={t}: доля фаворита {np.mean(np.array(tokens)==0):.2f}, "
f"уникальных токенов {len(set(tokens))}")
Запустив, вы увидите механику воочию: при T = 0,2 фаворит побеждает почти всегда; при T = 1,5 тяжёлый хвост получает около 80% вероятностной массы, поэтому даже top-p = 0,95 оставляет ядро примерно из 940 токенов. Это важное ограничение: top-p сдерживает хвост при умеренной температуре, но при высокой температуре и тяжёлом хвосте ядро само разрастается.
Теперь о главной болезни. Модель завершает фразу «Теорема доказана в работе…» по статистике продолжений, а цель следующего токена не проверяет существование статьи. Распределение не является калиброванной оценкой знания: оно может быть острым и для неверной фамилии. После первого выбранного элемента авторегрессия легко достраивает согласованные год, том и страницы, получая правдоподобную выдуманную ссылку. Случайная выборка может добавить вариативность, но корень галлюцинации — несоответствие между обучением на продолжение текста и требованием фактической истины. Пост-обучение (глава 5) может научить модель чаще выражать неопределённость, но не превращает вероятность токена в проверку факта.
Практические следствия: понижение температуры уменьшает разброс, но не добавляет знаний — уверенная галлюцинация при T = 0 никуда не денется. Контекст и RAG (глава 10) могут снизить риск, а критичные утверждения следует проверять внешними инструментами и первичными источниками; ни одна из этих мер по отдельности не гарантирует истинность.
Отдельный практически важный режим — генерация в жёстком формате: JSON заданной схемы, SQL, вызов функции. Просить формат промптом — ненадёжно; более сильное решение — ограниченное декодирование (constrained decoding): на каждом шаге маскируются все токены, недопустимые по грамматике формата (логиты → −∞), и модель физически не может породить запрещённое грамматикой продолжение. Но контракты API различаются: JSON mode обычно обещает лишь корректный JSON, а режим со схемой — более строгую структуру. Отказ модели, обрыв по лимиту и ошибка API всё равно требуют обработки. Даже при соблюдении схемы валидный JSON может содержать выдуманные или недопустимые по предметной области значения; формат не заменяет бизнес-валидацию.
Упомянем и спекулятивное декодирование: маленькая модель-черновик быстро предлагает несколько токенов вперёд, большая за один проход проверяет их. Совместимые алгоритмы могут сохранить исходное распределение выборки, но выигрыш зависит от доли принятых токенов, оборудования и накладных расходов; его нужно измерять.
Генерация — авторегрессионный цикл над распределениями: температура управляет остротой softmax, top-p/top-k/min-p отрезают хвост, штрафы подавляют повторы; крайние настройки могут давать циклы или бессвязность. Галлюцинации возникают прежде всего потому, что правдоподобное продолжение не равно проверенному факту; температура не добавляет знаний. Ограниченное декодирование может гарантировать синтаксическую структуру в оговорённых условиях, а спекулятивное — ускорить совместимую генерацию. Семантическая проверка остаётся обязанностью приложения.
Осталось разобраться с ресурсом, в котором всё это происходит, — контекстным окном: его устройством, стоимостью и грамотным использованием. Это следующая глава, завершающая вторую часть.
Упражнения
1. В коде из раздела 6.4 отключите top-p (top_p = 1.0) и повторите эксперимент при T = 1,5. Насколько выросло число уникальных токенов? Объясните результат через веса «хвоста» распределения.
2. Реализуйте min-p поверх функции sample_token и сравните с top-p на том же игрушечном распределении при высоких температурах.
3. Один и тот же фактологический вопрос (например, о малоизвестной исторической дате) задайте модели через API десять раз при T = 1,0. Согласованы ли ответы? Что согласованность ответов говорит (и чего не говорит) об их истинности? Проверьте факт по первичному источнику.