Перейти к содержимому
Ступень 44.1 Языковое моделирование

Генерация текста и сэмплирование

Как превратить распределение следующего токена в текст — greedy, температура, top-k, top-p, штрафы за повторение — и почему даже «температура 0» не гарантирует одинаковых ответов.

УРОКstage-4/4-1-language-modeling/03-sampling.mdx1 ч 40 минУрок 3 из 5
После урока вы сможете
  • Реализовать greedy, температуру, top-k и top-p и объяснить, как каждый приём меняет распределение
  • Подобрать параметры сэмплирования под задачу и прочитать рекомендации из model card Qwen3 и DeepSeek-R1
  • Объяснить, откуда берутся повторы, и как работают repetition, presence и frequency penalty
  • Объяснить, почему temperature = 0 на сервере не гарантирует одинаковых ответов

Языковая модель выдаёт не текст, а распределение следующего токена. Как из него выбрать токен, решает стратегия декодированиястратегия декодированияEN decoding strategyПравило, по которому из распределения следующего токена выбирается сам токен (greedy, beam search, сэмплирование с температурой, top-k, top-p)., и от неё зависит поведение модели не меньше, чем от весов. Одна и та же модель при одних настройках зацикливается на «кот смотрит на окне . кот смотрит на окне . …», при других несёт чепуху, а при третьих пишет связный и живой текст.

Эти настройки вы будете встречать постоянно: temperature и top_p есть почти в каждом API (модуль 8.1), top_k — во многих, а также в vLLM и Ollama (ступень 7). В model card моделей Qwen и DeepSeek есть раздел с рекомендуемыми значениями. Сегодня разберём, что они делают с распределением, почему модели зацикливаются и почему «температура 0» не означает «каждый раз одно и то же».

На каждом шаге генерации логиты проходят цепочку преобразований и только потом превращаются в токен:

flowchart LR
A["Логиты z<br/>(V чисел)"] --> B["Штрафы<br/>за повторы"]
B --> C["÷ температура"]
C --> D["top-k"]
D --> E["top-p"]
E --> F["softmax"]
F --> G["Случайный<br/>выбор токена"]
G --> H["Дописать<br/>в контекст"]
Типичный порядок обработки логитов при сэмплировании (так, например, в Hugging Face transformers)

Фильтры не трогают саму модель: они только переписывают логиты. «Выбросить токен» значит поставить его логиту −∞-\infty: после softmax его вероятность станет ровно 0, а остальные перенормируются.

Самая простая стратегия — жадное декодированиежадное декодирование (greedy)EN greedy decodingНа каждом шаге берётся самый вероятный токен (argmax). Детерминировано, но склонно к повторам.: на каждом шаге берём arg⁡max⁡\arg\max. Она детерминирована и кажется разумной: зачем выбирать менее вероятный токен?

Беда в том, что самый вероятный текст не похож на человеческий. Holtzman и соавторы в работе о nucleus sampling (2019) показали, что люди не выбирают каждое следующее слово максимально предсказуемо: в живом тексте то и дело встречаются слова с невысокой вероятностью. А максимизация вероятности даёт скучный текст, который часто зацикливается. Повтор усиливает сам себя: чем больше раз фраза уже встретилась в контексте, тем вероятнее модель её повторит.

Проще всего увидеть это на крошечной модели. Возьмём словесную биграммную модель (урок 2) на нескольких предложениях про кота. У биграммной модели greedy — детерминированная функция последнего слова, поэтому как только слово повторилось, цикл неизбежен:

greedy: кот смотрит на окне . кот смотрит на окне . кот смотрит на окне .

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

С температуройтемпература (сэмплирования)EN temperatureЛогиты делятся на T перед softmax. T < 1 — распределение острее, T > 1 — площе, T → 0 — greedy. вы уже встречались в модуле 1.3, когда считали энтропию. Напомним: температура делит логиты перед softmax:

pi=ezi/T∑jezj/Tp_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}
  • T=1T = 1 — распределение модели как есть.
  • T<1T < 1 — разрыв между логитами растёт, распределение острее, модель «увереннее». При T→0T \to 0 вся вероятность уходит самому вероятному токену — это greedy.
  • T>1T > 1 — распределение площе, у маловероятных токенов появляются шансы. При T→∞T \to \infty — равномерное.

Температура не меняет порядок токенов, только форму распределения. Пример: логиты [2,1][2, 1]. При T=1T = 1 вероятности [0.73,0.27][0.73, 0.27], при T=0.5T = 0.5 логиты становятся [4,2][4, 2] и вероятности — [0.88,0.12][0.88, 0.12].

Посмотрим на два контекста: неопределённый («Мама мыла …» — продолжений много) и уверенный («Столица Китая — …»).

import torch
import torch.nn.functional as F
def top_k_filter(logits, k):
kth = torch.topk(logits, k).values[-1] # k-е по величине значение
# masked_fill(маска, x): там, где маска True, подставить x; остальное оставить как было
return logits.masked_fill(logits < kth, float("-inf"))
def top_p_filter(logits, p):
probs = F.softmax(logits, dim=-1)
# сортируем по убыванию; idx[k] — исходный номер токена, оказавшегося на k-м месте
sorted_probs, idx = torch.sort(probs, descending=True)
cum = torch.cumsum(sorted_probs, dim=-1) # накопленные суммы: [0.46, 0.66, 0.80, …]
drop_sorted = cum - sorted_probs >= p # масса ДО токена уже ≥ p → токен лишний
# маска посчитана в отсортированном порядке; scatter раскладывает её обратно:
# drop[idx[k]] = drop_sorted[k], то есть «отметку» k-го по рангу токена ставим на его исходное место
drop = torch.zeros_like(drop_sorted).scatter(0, idx, drop_sorted)
return logits.masked_fill(drop, float("-inf"))
def show(title, tokens, logits):
"""Печатает таблицу: вероятности токенов при разных температурах и фильтрах."""
print(title)
print(" " + " ".join(f"{t[:6]:>6}" for t in tokens))
rows = [("T = 0.5", F.softmax(logits / 0.5, -1)), ("T = 1", F.softmax(logits, -1)),
("T = 2", F.softmax(logits / 2, -1)), ("top-k=3", F.softmax(top_k_filter(logits, 3), -1)),
("top-p=.9", F.softmax(top_p_filter(logits, 0.9), -1))]
for name, pr in rows:
print(f" " + " ".join(f"{x:6.2f}" for x in pr.tolist()) + f" ← {name}")
show("«Мама мыла …»",
["раму", "посуду", "окно", "пол", "кота", "машину", "руки", "Пекин"],
torch.tensor([3.0, 2.2, 1.8, 1.5, 0.5, 0.3, 0.2, -2.0]))
show("«Столица Китая — …»",
["Пекин", "город", "Шанхай", "один", "Нанкин", "это", "Москва", "кот"],
torch.tensor([6.0, 2.5, 1.5, 1.0, 0.8, 0.5, 0.0, -1.0]))
«Мама мыла …»
раму посуду окно пол кота машину руки Пекин
0.74 0.15 0.07 0.04 0.00 0.00 0.00 0.00 ← T = 0.5
0.46 0.21 0.14 0.10 0.04 0.03 0.03 0.00 ← T = 1
0.28 0.19 0.15 0.13 0.08 0.07 0.07 0.02 ← T = 2
0.57 0.26 0.17 0.00 0.00 0.00 0.00 0.00 ← top-k=3
0.51 0.23 0.15 0.11 0.00 0.00 0.00 0.00 ← top-p=.9
«Столица Китая — …»
Пекин город Шанхай один Нанкин это Москва кот
1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ← T = 0.5
0.94 0.03 0.01 0.01 0.01 0.00 0.00 0.00 ← T = 1
0.63 0.11 0.07 0.05 0.05 0.04 0.03 0.02 ← T = 2
0.96 0.03 0.01 0.00 0.00 0.00 0.00 0.00 ← top-k=3
1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ← top-p=.9

Сравните строки T=2T = 2: в первом контексте высокая температура даёт разнообразие, во втором — 3% на «Москву» и 2% на «кота» в ответе про столицу Китая. Отсюда главная проблема сэмплирования: у распределения есть длинный хвост из тысяч маловероятных токенов. По отдельности каждый почти невозможен, но вместе их масса заметна. Если на каждом шаге хвост даёт хотя бы 5%, то за 100 шагов модель почти наверняка вытянет оттуда что-нибудь странное, а дальше авторегрессия разовьёт ошибку. Хвост надо отрезать.

top-k сэмплированиеtop-k сэмплированиеEN top-k samplingСэмплирование только из k самых вероятных токенов (остальные получают вероятность 0). оставляет kk самых вероятных токенов, остальным ставит −∞-\infty и перенормирует. Идея появилась в работе Fan et al. (2018) о генерации историй. В таблице при k=3k = 3 «Мама мыла …» сэмплирует только из «раму / посуду / окно».

Слабое место — фиксированное kk. В неопределённом контексте хороших продолжений может быть сотня, и k=3k = 3 режет живые варианты («пол» тоже хорош). В уверенном контексте хорошее продолжение одно, а k=3k = 3 всё равно впускает «Шанхай» с 1%. Подходящее kk зависит от контекста, а мы задаём его один раз на всю генерацию.

top-p (ядерное) сэмплированиеtop-p (ядерное) сэмплированиеEN top-p (nucleus) samplingСэмплирование из минимального набора самых вероятных токенов с суммарной вероятностью ≥ p. Holtzman et al., 2019. (Holtzman et al., 2019) делает число кандидатов адаптивным:

  1. Отсортировать токены по убыванию вероятности.
  2. Идти сверху и накапливать вероятность.
  3. Остановиться, как только сумма достигла pp. Взятые токены — «ядро» (nucleus), остальные выбросить.
  4. Перенормировать и сэмплировать из ядра.

Для «Мама мыла …» накопленные суммы: 0.46→0.66→0.80→0.900.46 \to 0.66 \to 0.80 \to 0.90. При p=0.9p = 0.9 в ядро входят 4 токена. Для «Столица Китая — …» уже первый токен даёт 0.94, и ядро состоит из одного «Пекина». Одно и то же pp даёт 4 кандидата там, где модель сомневается, и 1 — там, где она уверена.

Тонкость реализации: токен попадает в ядро, если масса до него ещё меньше pp. Поэтому самый вероятный токен остаётся всегда, даже при крошечном pp, и ядро никогда не бывает пустым. В коде выше это строка cum - sorted_probs >= p. Top-p с температурой на NumPy — частая задача на собеседовании; её ловушки и дополнительные вопросы интервьюера разобраны в уроке 11.3.4.

Фильтры комбинируют: сначала температура, потом top-k, потом top-p. Типичные значения: TT от 0.6 до 1.0, pp от 0.8 до 0.95, kk от 20 до 50.

Подвигайте ползунки: при температуре 0.2 почти вся вероятность уходит одному токену, при 2.0 распределение заметно площе: доля лидера падает с 71% до 44%, и у хвоста появляются шансы. Включите top-k = 2 и посмотрите, как «хвост» исчезает целиком. Потом сравните с top-p = 0.9: число оставшихся токенов зависит от формы распределения. Кнопка «Сэмплировать» показывает, что получится на практике.

Песочница: softmax и сэмплированиеинтерактив
КонтекстКарп прыгает через…
ТокенЛогитВероятность
Нажмите, чтобы вытянуть 20 токенов из этого распределения

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

ШтрафКак меняет логит zjz_j уже встречавшегося токенаГде встречается
штраф за повторениештраф за повторениеEN repetition penaltyПонижение логитов токенов, которые уже встречались в тексте (CTRL, presence/frequency penalty в API).zj/θz_j / \theta, если zj>0z_j > 0; zj⋅θz_j \cdot \theta, если zj<0z_j < 0Hugging Face, vLLM (идея — из CTRL, 2019)
presence penaltyzj−αpresz_j - \alpha_{\text{pres}}, если токен встречался хотя бы разOpenAI-совместимые API, vLLM
frequency penaltyzj−αfreq⋅cjz_j - \alpha_{\text{freq}} \cdot c_j, где cjc_j — сколько раз встречалсяOpenAI-совместимые API, vLLM

Repetition penalty — мультипликативный. Его предложили авторы CTRL: логит уже встречавшегося токена делят на θ\theta, а в статье рекомендуют θ≈1.2\theta \approx 1.2. Но у простого деления есть изъян. Деление положительного числа на θ>1\theta > 1 уменьшает его, а отрицательного — увеличивает (−2/1.2=−1.67-2 / 1.2 = -1.67), и штраф превращается в поощрение. Поэтому в Hugging Face (и так же в vLLM) отрицательные логиты умножают на θ\theta: так штраф всегда понижает логит. Учтите ещё одну деталь: в Hugging Face штраф по умолчанию действует и на токены промпта, а не только на сгенерированные.

Штрафы — грубый инструмент. Модель не отличает вредный повтор от нужного: в коде одна и та же переменная встречается десятки раз, в математике повторяются символы, в пересказе — имена героев. Сильный штраф заставит модель придумывать синонимы там, где они не нужны. Поэтому штрафы держат маленькими или выключают и включают, только когда модель действительно зацикливается.

Почему «температура 0» не всегда детерминирована

Заголовок раздела «Почему «температура 0» не всегда детерминирована»

Многие уверены: поставь temperature=0, и ответ всегда будет одинаковым. API обычно трактуют это как greedy, а arg⁡max⁡\arg\max — детерминированная функция логитов. Но логиты на сервере сами по себе не детерминированы.

В сентябре 2025 года Хорас Хе и коллеги из Thinking Machines Lab описали в блоге компании показательный эксперимент. Модель Qwen3-235B-A22B-Instruct-2507 1000 раз ответила на запрос «Tell me about Richard Feynman» при температуре 0. Получилось 80 разных ответов, самый частый встретился лишь 78 раз. Первые 102 токена совпадали у всех, а на 103-м пути разошлись: после «Feynman was born on May 11, 1918, in» 992 ответа продолжили «Queens, New York», а 8 — «New York City».

Откуда разница? Цепочка такая:

  1. Сложение floatчисло с плавающей точкой (float)EN floating-point numberСпособ хранить дробные числа как «мантисса × 2 в степени». Форматы: FP32 (4 байта), FP16 и BF16 (2 байта), FP8 (1 байт). не ассоциативно: (a+b)+c(a + b) + c может не равняться a+(b+c)a + (b + c) из-за округления.
  2. GPU-ядра (kernels — маленькие программы, которые видеокарта выполняет параллельно на тысячах потоков) суммируют блоками, и разбиение на блоки зависит от размера батчаразмер батчаEN batch sizeЧисло примеров (у LLM — часто число токенов) на один шаг оптимизатора. Больше батч — меньше шум градиента, но дороже шаг.: от того, сколько чужих запросов сервер обрабатывает вместе с вашим. Нагрузка меняется каждую секунду.
  3. Логиты получаются чуть разными. Обычно это ни на что не влияет, но если два кандидата почти равны (как «Queens» и «New» выше), arg⁡max⁡\arg\max переключается.
  4. Дальше работает авторегрессия: один другой токен — и весь последующий текст другой.

Авторы показали, что главный виновник — именно отсутствие инвариантности к батчу в ядрах нормализации, матричного умножения и внимания. Когда они заменили ядра на батч-инвариантные, все 1000 ответов совпали.

Механизм легко воспроизвести на CPU. Посчитаем один и тот же логит — скалярное произведение двух векторов длины 4096 — с разным числом блоков суммирования, как это делают ядра при разных размерах батча:

import numpy as np
print((0.1 + 0.2) + 0.3 == 0.1 + (0.2 + 0.3)) # сложение float не ассоциативно
rng = np.random.default_rng(0)
h = rng.standard_normal(4096).astype(np.float32) # скрытое состояние последнего токена
w = rng.standard_normal(4096).astype(np.float32) # строка LM-головы для токена A (LM-голова — последний
# слой модели: вектор → логиты всех токенов; модуль 4.5)
prod = h * w # логит = сумма этих 4096 произведений
def chunked_sum(v, n_chunks):
"""Так суммирует GPU-ядро: частичные суммы по блокам, затем сумма сумм."""
# np.array_split режет вектор на n_chunks почти равных кусков
parts = [np.sum(c, dtype=np.float32) for c in np.array_split(v, n_chunks)]
return np.float32(sum(parts, np.float32(0)))
logit_b = np.float32(-18.137735) # логит конкурента — токена B
for n in [1, 4, 8, 16, 64]:
a = chunked_sum(prod, n)
print(f"блоков {n:2}: логит A = {a:.6f} → greedy выбирает {'A' if a > logit_b else 'B'}")
False
блоков 1: логит A = -18.137739 → greedy выбирает B
блоков 4: логит A = -18.137739 → greedy выбирает B
блоков 8: логит A = -18.137741 → greedy выбирает B
блоков 16: логит A = -18.137733 → greedy выбирает A
блоков 64: логит A = -18.137737 → greedy выбирает B

Математически это одно и то же число, а в float32 получилось четыре разных значения, и при разбиении на 16 блоков greedy выбирает другой токен. Здесь почти-ничья подобрана специально, но в словаре из 150 тысяч токенов на тысячах шагов генерации такие ситуации неизбежны. В BF16BF16 (bfloat16)EN bfloat16 (brain floating point)16-битный формат: 1 бит знака, 8 бит порядка (как у FP32), 7 бит мантиссы. Диапазон как у FP32, точность — 2–3 значащие цифры. Стандарт обучения LLM., где у числа всего 7 бит мантиссы (значащих цифр, урок 0.0.2) против 23 у FP32, расхождения гораздо больше. Подробно о форматах BF16 и FP8 — в уроке 5.4.1.

Реализуйте на чистом Python softmax с температурой, фильтры top_k_filter и top_p_filter и функцию sample, которая собирает их в один шаг декодирования. Тесты проверят точные значения, крайние случаи (крошечное pp, k≥Vk \geq V) и статистику сэмплов.

Температура, top-k и top-p8 тестов
import math
import random

NEG_INF = float("-inf")


def softmax(logits, temperature=1.0):
    """Устойчивый softmax с температурой: p_i ∝ exp(z_i / T).

    Логит −inf должен давать вероятность ровно 0. Температура T > 0.
    """
    # TODO: ваш код
    ...


def top_k_filter(logits, k):
    """Оставляет k самых больших логитов, остальные заменяет на −inf.

    Возвращает НОВЫЙ список той же длины (исходный не меняйте).
    Если k >= len(logits), возвращает копию без изменений.
    """
    # TODO: ваш код
    ...


def top_p_filter(logits, p):
    """Nucleus (top-p): оставляет минимальный набор самых вероятных токенов,
    суммарная вероятность которого >= p. Остальным логитам — −inf.

    Вероятности считаются через softmax(logits). Хотя бы один токен остаётся всегда.
    Порядок элементов в возвращаемом списке — как во входном.
    """
    # TODO: ваш код
    ...


def sample(logits, rng, temperature=1.0, top_k=None, top_p=None):
    """Один шаг декодирования. Возвращает индекс выбранного токена.

    temperature == 0 → greedy (индекс максимального логита).
    Иначе: логиты / T → top-k (если задан) → top-p (если задан) → softmax → случайный выбор.
    Для случайности используйте только rng (random.Random), например rng.choices(...).
    """
    # TODO: ваш код
    ...


# Кнопка «Запустить»: «Мама мыла …» — раму, посуду, окно, пол, кота, машину, руки, Пекин
logits = [3.0, 2.2, 1.8, 1.5, 0.5, 0.3, 0.2, -2.0]
print("softmax, T=1:  ", softmax(logits))
print("softmax, T=0.5:", softmax(logits, 0.5))
print("top-k=3:       ", top_k_filter(logits, 3))
print("top-p=0.9:     ", top_p_filter(logits, 0.9))

Полный цикл генерации со штрафом за повторение, температурой, top-k и top-p на словесной биграммной модели. Логиты здесь — log⁡P(следующее∣предыдущее)\log P(\text{следующее} \mid \text{предыдущее}) со сглаживанием 0.01, как в уроке 2.

import torch
import torch.nn.functional as F
text = ("кот сидит на окне . кот смотрит на улицу . на улице идёт снег . "
"снег идёт и идёт . кот смотрит на снег и думает о рыбе . "
"рыба живёт в реке . кот любит рыбу и спит на окне .")
words = text.split()
vocab = sorted(set(words)) # словарь — все разные слова
stoi = {w: i for i, w in enumerate(vocab)} # слово → номер
V = len(vocab)
N = torch.zeros(V, V) # счётчики биграмм слов, как в уроке 2
for a, b in zip(words, words[1:]):
N[stoi[a], stoi[b]] += 1
# log(N + 0.01) — сглаженные «log-счётчики»; log_softmax нормирует строку и сразу берёт логарифм,
# так что logits_table[i, j] = log P(слово j | слово i)
logits_table = torch.log_softmax(torch.log(N + 0.01), dim=-1)
def generate(start, steps, temperature=1.0, top_k=None, top_p=None,
repetition_penalty=1.0, seed=1):
g = torch.Generator().manual_seed(seed)
out = [stoi[start]] # номера сгенерированных слов
for _ in range(steps):
logits = logits_table[out[-1]].clone() # логиты по последнему слову; clone — копия, чтобы не испортить таблицу
# 1) штраф за повторение (как в Hugging Face): уже встречавшиеся токены — менее вероятны
if repetition_penalty != 1.0:
seen = torch.tensor(sorted(set(out))) # номера уже встречавшихся слов
s = logits[seen]
# torch.where(условие, a, b): поэлементно берёт a там, где условие True, иначе b
logits[seen] = torch.where(s > 0, s / repetition_penalty, s * repetition_penalty)
if temperature == 0: # 2) greedy
nxt = int(torch.argmax(logits))
else:
logits = logits / temperature # 3) температура
if top_k is not None: # 4) top-k
kth = torch.topk(logits, top_k).values[-1]
logits[logits < kth] = float("-inf")
if top_p is not None: # 5) top-p
probs = F.softmax(logits, -1)
sp, idx = torch.sort(probs, descending=True)
drop = (torch.cumsum(sp, -1) - sp) >= top_p
logits[idx[drop]] = float("-inf") # idx[drop] — исходные номера выброшенных слов
nxt = int(torch.multinomial(F.softmax(logits, -1), 1, generator=g)) # 6) сэмплирование
out.append(nxt)
return " ".join(vocab[i] for i in out)
print("greedy: ", generate("кот", 14, temperature=0))
print("greedy+штраф:", generate("кот", 14, temperature=0, repetition_penalty=2.0))
print("T=1.0: ", generate("кот", 14, temperature=1.0))
print("T=0.7, p=0.9:", generate("кот", 14, temperature=0.7, top_p=0.9))
greedy: кот смотрит на окне . кот смотрит на окне . кот смотрит на окне .
greedy+штраф: кот смотрит на окне . кот любит рыбу и думает о рыбе . кот сидит
T=1.0: кот сидит на снег . рыба живёт в реке в . кот любит улице идёт
T=0.7, p=0.9: кот сидит на снег . рыба живёт в реке . рыба живёт в реке .

Сэмплы зависят от версии PyTorch; greedy-строки — нет. Что видно:

  • greedy зацикливается через 5 слов.
  • Штраф разрывает цикл: «окне» и «смотрит» уже были, и модель уходит в «любит рыбу». Но «кот» и «.» повторяются всё равно — без них текст невозможен. Заметьте: все наши логиты отрицательны (это логарифмы вероятностей), поэтому работает ветка с умножением. Замените torch.where(...) на простое деление s / repetition_penalty, как в исходном CTRL, и greedy+штраф снова выдаст «кот смотрит на окне . кот смотрит на окне …»: деление поднимает отрицательные логиты уже встречавшихся слов.
  • T = 1 без фильтров вытаскивает из сглаженного хвоста бессмыслицу: «в реке в», «любит улице».
  • T = 0.7 и top-p = 0.9 отрезают хвост: склеек вроде «в реке в» больше нет. Но снова появляется повтор, а «сидит на снег» напоминает, что биграммная модель видит только одно предыдущее слово («на снег» встречалось в «смотрит на снег»). Низкая температура и усечение хвоста уменьшают разнообразие — это та же цена, что у greedy, только меньше.

Поэкспериментируйте: найдите настройки, при которых за 30 шагов нет ни бессмыслицы, ни повторов. Попробуйте top_k=2 и сравните с top_p=0.9.

Проверь себя6 вопросов
1 Логиты двух токенов [2,1][2, 1], температура T=0.5T = 0.5. Какова вероятность первого токена? Округлите до двух знаков.
2 Вероятности токенов [0.5,0.3,0.15,0.05][0.5, 0.3, 0.15, 0.05]. Сколько токенов останется после top-p с p=0.75p = 0.75?
3 Какой метод сам подстраивает число кандидатов под уверенность модели?
4 Выберите все верные утверждения.Можно выбрать несколько ответов
5 Уже встречавшийся токен имеет логит −2.0-2.0. Каким он станет после repetition penalty в Hugging Face с θ=1.2\theta = 1.2?
6 Почему при temperature = 0 сервер может давать разные ответы на один и тот же запрос?
  • Стратегия декодирования переписывает логиты: штрафы → температура → top-k → top-p → softmax → сэмплирование.
  • Greedy детерминирован, но даёт скучный текст и зацикливается. Температура меняет форму распределения, не меняя порядка. Top-k режет хвост с фиксированным числом кандидатов, top-p — адаптивно.
  • Штрафы за повторение (мультипликативный repetition penalty по мотивам CTRL, presence, frequency) лечат зацикливание, но мешают нужным повторам — в коде, формулах, именах.
  • Temperature 0 ≠ детерминизм на сервере: от размера батча зависит порядок сложений float, почти-ничьи переключаются, авторегрессия раздувает разницу.
  • Читайте model card: Qwen3 — T=0.6T = 0.6, top-p 0.95, top-k 20 для thinking-режима; DeepSeek-R1 — T=0.6T = 0.6.

Дальше: даём модели контекст длиннее одной буквы — эмбеддинги, скрытый слой и ручной backprop в модели Бенджио на тех же русских именах — Нейросетевая языковая модель: MLP по Бенджио (2003).