microgpt: GPT в 200 строк, разбор по блокам
Построчный разбор microgpt Андрея Карпатого — обучения и генерации GPT на чистом Python без зависимостей: данные, токенизатор, автоград Value, параметры, внимание с KV-кэшем, MLP, Adam, цикл обучения и сэмплирование — фрагментами, с русскими комментариями и проверенными числами.
microgpt — «арт-проект» Андрея Карпатого, как он сам его называет: один файл на 200 строк чистого Python без единой зависимости, который обучает GPT и генерирует ею текст. В файле есть всё: набор данных, токенизатор, движок автоматического дифференцированияавтоматическое дифференцирование (автоград)EN automatic differentiation (autograd)Программа записывает операции в граф и сама считает производные по цепному правилу. Для loss → параметры используют обратный режим (reverse mode) — это и есть backprop., архитектура в духе GPT-2, оптимизатор Adam, цикл обучения и генерация. Строка из шапки файла задаёт тон всему разбору: «This file is the complete algorithm. Everything else is just efficiency» — «Этот файл — весь алгоритм. Всё остальное — лишь эффективность».
Где взять оригинал: gist microgpt.py, та же программа на странице karpathy.ai/microgpt.html, ноутбук в Google Colab и пост «microgpt» в блоге автора с подробным объяснением.
Коротко
Заголовок раздела «Коротко»- Модель — однослойный трансформер шириной 16 с 4 головами внимания и контекстом 16 символов, всего 4192
параметра. Данные — 32 033 английских имени, токены — отдельные буквы плюс служебный токен
BOS, итого словарь из 27 токенов. - Каждое число модели — объект
Value, который помнит, из чего получен, и локальную производную. Вызовloss.backward()— то же, что в PyTorch, только по одному скаляру за раз. - Модель обрабатывает один токен за вызов и хранит ключи и значения прошлых токенов в списках — это KV-кэшKV-кэшEN KV cache, причём прямо во время обучения. Причинная маска не нужна: будущих токенов в кэше просто нет.
- За 1000 шагов (по одному имени на шаг) loss падает примерно с 3,3 — это случайное угадывание из 27 вариантов, , — до примерно 2,37. На MacBook автора это около минуты. Потом модель «галлюцинирует» правдоподобные новые имена.
- Карпатый показывает, что отличает microgpt от ChatGPT: данные, BPE, тензоры и GPU, масштаб, пост-обучение, инженерия инференса. Но, по его словам, если вы понимаете microgpt, вы понимаете алгоритмическую суть.
Как читать вместе с курсом
Заголовок раздела «Как читать вместе с курсом»Нужны две вещи, которые в курсе уже есть: автоград из модуля 3.2
(класс Value и обратный проход) и внимание из модуля
4.4. Лучший порядок:
- Пройдите урок 4.6.1: там та же модель, собранная по шагам и запускаемая в браузере.
- Прочитайте этот разбор с оригиналом рядом. Вы будете узнавать каждую строку, а разница в деталях покажет, какие решения произвольны.
- После урока 4.6.4 «Генерация и KV-кэш» вернитесь к блоку о
функции
gpt: станет ясно, почему кэш в microgpt нужен даже при обучении.
Карта файла
Заголовок раздела «Карта файла»| Строки | Блок | Что делает | Урок курса |
|---|---|---|---|
| 1–12 | шапка, импорты, random.seed(42) | только os, math, random | — |
| 14–21 | данные | скачивает names.txt, перемешивает 32 033 имени | 4.1.2 |
| 23–27 | токенизатор | буквы → номера 0…25, BOS = 26 | 4.2.1 |
| 29–72 | класс Value | автоград: прямой проход и backward() | 3.2 |
| 74–90 | параметры | гиперпараметры, матрицы весов, список params | 4.6.1 |
| 92–106 | кирпичики | linear, softmax, rmsnorm | 4.5.1 |
| 108–144 | функция gpt | эмбеддинги, внимание с KV-кэшем, MLP, логиты | 4.4, 4.6.4 |
| 146–149 | Adam | гиперпараметры и буферы моментов | 4.6.3 |
| 151–184 | цикл обучения | имя → токены → loss → backward → шаг Adam | 4.6.3 |
| 186–200 | генерация | сэмплирование с температурой 0,5 | 4.1.3 |
flowchart LR A["Имя: emma"] --> B["Токены: BOS e m m a BOS"] B --> C["gpt(): по одному токену, KV-кэш растёт"] C --> D["27 логитов → softmax"] D --> E["loss = среднее −log p(следующего токена)"] E --> F["loss.backward(): градиенты всех 4192 Value"] F --> G["Adam: сдвинуть параметры"] G -->|"следующее имя"| A
Блок 1. Шапка и импорты (строки 1–12)
Заголовок раздела «Блок 1. Шапка и импорты (строки 1–12)»Шапка — три строки манифеста: «самый атомарный способ обучить GPT и запустить её на чистом Python без зависимостей», «этот файл — весь алгоритм», «всё остальное — лишь эффективность». Импортируются только три модуля стандартной библиотеки. Комментарии в стиле «Да будет…» проходят через весь файл — например, у фиксированного зерна случайности:
import os # os.path.existsimport math # math.log, math.expimport random # random.seed, random.choices, random.gauss, random.shufflerandom.seed(42) # «Да будет порядок среди хаоса»Фиксированное зерно делает запуск воспроизводимымвоспроизводимостьEN reproducibilityВозможность получить тот же результат, повторно запустив тот же код на тех же данных.: у всех получатся одни и те же начальные веса и тот же порядок имён. Мы проверили: первые значения loss совпадают с теми, что приведены в посте автора (3,3660 и 3,4243).
Блок 2. Данные (строки 14–21)
Заголовок раздела «Блок 2. Данные (строки 14–21)»Если файла input.txt нет, программа скачивает список имён из репозитория makemore (ссылка закреплена на конкретном
коммите). Каждая непустая строка — отдельный «документ», и список перемешивается:
docs = [line.strip() for line in open('input.txt') if line.strip()]random.shuffle(docs)Программа печатает num docs: 32033. Мысль Карпатого из поста: для модели вроде ChatGPT ваш разговор — тоже просто
«документ» необычного вида, а ответ модели — статистическое продолжение документа.
Блок 3. Токенизатор (строки 23–27)
Заголовок раздела «Блок 3. Токенизатор (строки 23–27)»uchars = sorted(set(''.join(docs))) # уникальные символы набора становятся токенами 0..n-1BOS = len(uchars) # номер служебного токена BOS (Beginning of Sequence — «начало последовательности»)vocab_size = len(uchars) + 1 # всего уникальных токенов: +1 — это BOSВ именах встречаются только строчные латинские буквы, поэтому uchars — это a…z, номера 0…25, а BOS = 26. Словарь
— 27 токенов. Сами номера ничего не значат, это просто имена отдельных символов: «вместо 0, 1, 2 могли бы быть
разные эмодзи», как пишет автор. BOS работает как разделитель: при обучении имя оборачивается им с двух сторон,
[BOS, e, m, m, a, BOS]. Первый BOS значит «начинается имя», второй — «имя закончилось». Настоящие модели
используют подсловный BPEBPE, кодирование пар байтовEN Byte Pair Encoding (BPE)Алгоритм токенизации — многократно сливает самую частую пару соседних токенов в новый токен. Изначально (Gage, 1994) — алгоритм сжатия данных. (см. лекцию о токенизаторе), но
принцип тот же.
Блок 4. Автоград: класс Value (строки 29–72)
Заголовок раздела «Блок 4. Автоград: класс Value (строки 29–72)»Это самая математическая часть, и у Карпатого есть на неё отдельная 2,5-часовая лекция о micrograd. Объект Value
хранит одно число и помнит, как оно получено:
class Value: __slots__ = ('data', 'grad', '_children', '_local_grads') # экономия памяти в Python
def __init__(self, data, children=(), local_grads=()): self.data = data # значение узла, считается при прямом проходе self.grad = 0 # производная loss по этому узлу, считается при обратном проходе self._children = children # «дети» узла в графе вычислений self._local_grads = local_grads # локальные производные узла по каждому из детейКаждая операция — «кубик лего»: считает результат и сразу записывает локальные производные. Например, умножение:
def __mul__(self, other): other = other if isinstance(other, Value) else Value(other) return Value(self.data * other.data, (self, other), (other.data, self.data))Для производная по равна , а по — : это и есть кортеж (other.data, self.data). Так же
устроены сложение (производные 1 и 1), степень (), log (), exp () и relu (1, если
, иначе 0). Вычитание, деление и унарный минус Карпатый выражает через уже имеющиеся операции: например,
деление — это умножение на other**-1. Поэтому «настоящих» кубиков всего шесть.
Обратный проход — топологическая сортировкатопологическая сортировкаEN topological sortПорядок узлов ориентированного графа без циклов, в котором каждый узел идёт после всех, от кого зависит. backward() обходит его в обратную сторону. графа и цепное правило:
def backward(self): topo = [] visited = set() def build_topo(v): # обход в глубину: дети попадают в список раньше родителя if v not in visited: visited.add(v) for child in v._children: build_topo(child) topo.append(v) build_topo(self) self.grad = 1 # dL/dL = 1 for v in reversed(topo): # от loss к параметрам for child, local_grad in zip(v._children, v._local_grads): child.grad += local_grad * v.grad # цепное правило; += потому что путей может быть несколькоФормула в последней строке читается так: если узел зависит от ребёнка , то
.
Плюс-равно нужен потому, что одно число может участвовать в графе в нескольких местах, и вклады всех путей
складываются. Пример из поста: , , . Тогда
(два пути через ), а — ровно то, что выдаст .backward() в PyTorch. Смысл
числа 4: если увеличить на 0,001, то вырастет примерно на 0,004.
Блок 5. Параметры (строки 74–90)
Заголовок раздела «Блок 5. Параметры (строки 74–90)»n_layer = 1 # глубина трансформера (число слоёв)n_embd = 16 # ширина сети (размерность эмбеддингов)block_size = 16 # максимальная длина контекста (самое длинное имя — 15 символов)n_head = 4 # число голов вниманияhead_dim = n_embd // n_head # размерность одной головы: 4matrix = lambda nout, nin, std=0.08: [[Value(random.gauss(0, std)) for _ in range(nin)] for _ in range(nout)]matrix создаёт список строк из случайных чисел нормального распределения со стандартным отклонением 0,08, каждое
обёрнуто в Value. Дальше словарь state_dict (слово взято из PyTorch) собирает именованные матрицы: эмбеддинги
токенов wte (27 × 16), позиций wpe (16 × 16), выходную матрицу lm_head (27 × 16) и для каждого слоя — четыре
матрицы внимания attn_wq, attn_wk, attn_wv, attn_wo (16 × 16) и две матрицы MLP mlp_fc1 (64 × 16) и
mlp_fc2 (16 × 64). Все числа затем выпрямляются в один список params, чтобы оптимизатору было что перебирать.
Посчитаем параметры сами: эмбеддинги и выход — ; внимание —
; MLP — . Итого — ровно столько
печатает программа (num params: 4192). Для сравнения: у GPT-2 было 1,6 млрд параметров, у современных LLM — сотни
миллиардов.
Блок 6. Кирпичики: linear, softmax, rmsnorm (строки 92–106)
Заголовок раздела «Блок 6. Кирпичики: linear, softmax, rmsnorm (строки 92–106)»Комментарий перед блоком: архитектура следует GPT-2 «с небольшими отличиями: layernorm → rmsnorm, без смещений, GeLU → ReLU».
def linear(x, w): # умножение матрицы на вектор: по скалярному произведению на строку w return [sum(wi * xi for wi, xi in zip(wo, x)) for wo in w]
def softmax(logits): # логиты -> вероятности (неотрицательные, сумма 1) max_val = max(val.data for val in logits) exps = [(val - max_val).exp() for val in logits] # вычитаем максимум, чтобы exp не переполнился total = sum(exps) return [e / total for e in exps]
def rmsnorm(x): # делим вектор на его среднеквадратичное значение ms = sum(xi * xi for xi in x) / len(x) scale = (ms + 1e-5) ** -0.5 return [xi * scale for xi in x]linear— главный строительный блок нейросети: обучаемое линейное преобразование.- В
softmaxвычитание максимума математически ничего не меняет, но защищает от переполнения вexp. Обратите внимание:max_valвзят как обычное число (.data), а неValue— через него градиент не течёт, и это корректно, потому что результат от сдвига не зависит. rmsnormприводит вектор к единичному среднеквадратичному значению: активации не раздуваются и не затухают от слоя к слою (подробно — урок 4.5.1). Обучаемого множителя, как у RMSNorm в больших моделях, здесь нет.
Блок 7. Модель: функция gpt (строки 108–144)
Заголовок раздела «Блок 7. Модель: функция gpt (строки 108–144)»Модель — функция без состояния: на вход номер токена, номер позиции и кэши ключей и значений прошлых позиций, на выход — 27 логитов для следующего токена.
Эмбеддинги.
tok_emb = state_dict['wte'][token_id] # эмбеддинг токена: строка таблицы pos_emb = state_dict['wpe'][pos_id] # эмбеддинг позиции x = [t + p for t, p in zip(tok_emb, pos_emb)] # «что это за токен» + «где он стоит» x = rmsnorm(x) # примечание автора: не лишнее из-за обратного прохода через остаточную связьСовременные LLM обычно обходятся без таблицы позиций и используют относительные схемы вроде RoPEротационные позиционные эмбеддинги (RoPE)EN rotary position embedding (RoPE)RoPE предложен в Китае (Су Цзяньлинь и соавторы, компания Zhuiyi). Сейчас используется почти во всех LLM. (так сделано, например, в nanochat).
Внимание. Для каждого слоя: запомнить вход для остаточной связи, нормализовать, получить запрос, ключ и значение
(q, k, v — по linear с матрицами слоя), а k и v дописать в кэш слоя: keys[li].append(k). Затем для
каждой из 4 голов:
for h in range(n_head): hs = h * head_dim # начало «своего» куска вектора q_h = q[hs:hs+head_dim] # запрос этой головы k_h = [ki[hs:hs+head_dim] for ki in keys[li]] # ключи всех позиций 0..t v_h = [vi[hs:hs+head_dim] for vi in values[li]] # значения всех позиций 0..t attn_logits = [sum(q_h[j] * k_h[t][j] for j in range(head_dim)) / head_dim**0.5 for t in range(len(k_h))] attn_weights = softmax(attn_logits) # веса внимания по прошлым позициям head_out = [sum(attn_weights[t] * v_h[t][j] for t in range(len(v_h))) for j in range(head_dim)] x_attn.extend(head_out) # склеиваем выходы головЭто ровно формула вниманиямасштабированное скалярное внимание (SDPA)EN scaled dot-product attention (SDPA)softmax(Q Kᵀ / √d_k) V — формула внимания трансформера (Vaswani и др., 2017). В PyTorch — F.scaled_dot_product_attention.
, записанная циклами: скалярное произведение запроса на каждый
ключ, деление на , softmax и взвешенная сумма значений. Выходы голов склеиваются в вектор из 16 чисел,
проходят через attn_wo и прибавляются к сохранённому входу (остаточная связь).
Где причинная маска? Её нет и не нужно: в кэше лежат только позиции , будущее ещё не вычислено. Карпатый подчёркивает: блок внимания — единственное место, где токен на позиции «видит» прошлые токены. Внимание — это механизм общения токенов.
MLP. Опять остаточная связь и нормализация, затем расширение 16 → 64, ReLU и сжатие обратно 64 → 16:
x = linear(x, state_dict[f'layer{li}.mlp_fc1']) # 16 -> 64 x = [xi.relu() for xi in x] x = linear(x, state_dict[f'layer{li}.mlp_fc2']) # 64 -> 16 x = [a + b for a, b in zip(x, x_residual)] # + остатокВ отличие от внимания, MLP работает только с текущей позицией — здесь модель «думает» над тем, что собрало внимание. Трансформер чередует общение (внимание) и вычисление (MLP). Остаточные связи дают градиенту прямой путь через сеть и делают глубокие модели обучаемыми.
Выход. logits = linear(x, state_dict['lm_head']) — 27 чисел, по одному на токен словаря: чем больше, тем
вероятнее, по мнению модели, этот токен следующий.
Блок 8. Adam (строки 146–149)
Заголовок раздела «Блок 8. Adam (строки 146–149)»learning_rate, beta1, beta2, eps_adam = 0.01, 0.85, 0.99, 1e-8m = [0.0] * len(params) # буфер первого момента (скользящее среднее градиентов)v = [0.0] * len(params) # буфер второго момента (скользящее среднее квадратов градиентов)Комментарий автора — «Да будет Adam, благословенный оптимизатор, и его буферы». Обратите внимание на необычные значения: и вместо привычных 0,9 и 0,999 — для короткого обучения на шумных одиночных примерах. О самом AdamAdamEN Adam (adaptive moment estimation)Momentum + RMSProp + поправка смещения. Стандартный оптимизатор нейросетей с 2015 года. — урок 4.6.3.
Блок 9. Цикл обучения (строки 151–184)
Заголовок раздела «Блок 9. Цикл обучения (строки 151–184)»Один шаг — одно имя. Пять действий: взять документ, прогнать вперёд, посчитать loss, прогнать назад, обновить параметры.
doc = docs[step % len(docs)] tokens = [BOS] + [uchars.index(ch) for ch in doc] + [BOS] # emma -> BOS e m m a BOS n = min(block_size, len(tokens) - 1) # сколько предсказаний делаемЗатем для каждой позиции: вызвать gpt, превратить логиты в вероятности и взять минус логарифм вероятности
правильного следующего токена — это кросс-энтропияперекрёстная энтропияEN cross-entropy:
logits = gpt(token_id, pos_id, keys, values) probs = softmax(logits) loss_t = -probs[target_id].log() # насколько модель «удивлена» правильным ответом losses.append(loss_t) loss = (1 / n) * sum(losses) # итоговый средний loss по имени. «Пусть ваш будет низким»Если модель дала правильному токену вероятность 1, loss равен 0. Если почти 0 — loss уходит в бесконечность. Один
вызов loss.backward() проводит градиент через весь граф — softmax, модель, все 4192 параметра.
Обновление Adam с линейным затуханием learning rate:
lr_t = learning_rate * (1 - step / num_steps) # линейное затухание learning rate for i, p in enumerate(params): m[i] = beta1 * m[i] + (1 - beta1) * p.grad # «инерция»: среднее последних градиентов v[i] = beta2 * v[i] + (1 - beta2) * p.grad ** 2 # средний квадрат: свой масштаб шага для каждого параметра m_hat = m[i] / (1 - beta1 ** (step + 1)) # поправка на то, что m и v стартуют с нуля v_hat = v[i] / (1 - beta2 ** (step + 1)) p.data -= lr_t * m_hat / (v_hat ** 0.5 + eps_adam) p.grad = 0 # обнулить градиент перед следующим шагомПоследняя строка важна: градиенты в Value накапливаются через +=, и без обнуления следующий шаг сложил бы их со
старыми. Это та же причина, по которой в PyTorch вызывают optimizer.zero_grad().
Что увидите. Программа печатает номер шага и loss. Начало — около 3,3 (случайное угадывание из 27 вариантов: ), за 1000 шагов loss опускается примерно до 2,37. Один шаг занимает заметное время: каждое сложение — вызов функции Python и новый объект. Тот же расчёт на GPU обрабатывает миллионы чисел параллельно.
Блок 10. Генерация (строки 186–200)
Заголовок раздела «Блок 10. Генерация (строки 186–200)»Параметры заморожены. Начинаем с BOS и повторяем: прогнать токен через gpt, разделить логиты на
температурутемпература (сэмплирования)EN temperatureЛогиты делятся на T перед softmax. T < 1 — распределение острее, T > 1 — площе, T → 0 — greedy., выбрать следующий токен случайно по вероятностям, подать его на вход
снова — пока модель не выдаст BOS («имя закончилось») или не кончится контекст:
temperature = 0.5 # в (0, 1]: «креативность» текста, от низкой к высокой... probs = softmax([l / temperature for l in logits]) token_id = random.choices(range(vocab_size), weights=[p.data for p in probs])[0] if token_id == BOS: breakТемпература 1 — сэмплировать прямо из выученного распределения. Меньше 1 (здесь 0,5) — распределение «острее», модель осторожнее. Близко к 0 — всегда самый вероятный токен (жадное декодирование). Больше 1 — разнообразнее, но бессвязнее. Подробно — урок 4.1.3. В посте автор приводит 20 сгенерированных имён — от вполне настоящих («anna», «lara», «anton») до новых («kamon», «karai», «yeran»).
От microgpt к ChatGPT: что меняется
Заголовок раздела «От microgpt к ChatGPT: что меняется»В разделе поста «Real stuff» («Настоящее») Карпатый проходит те же блоки и перечисляет, что меняется в боевых системах. Алгоритм и общая схема при этом остаются.
| Блок | microgpt | Настоящие LLM |
|---|---|---|
| Данные | 32 тысячи имён | триллионы токенов интернета, кода, книг; дедупликация, фильтрация, смеси |
| Токенизатор | буквы, 27 токенов | BPE, около 100 тысяч токенов |
| Автоград | скаляры Value на Python | тензоры на GPU/TPU, PyTorch, ядра вроде FlashAttention |
| Архитектура | 4192 параметра, 1 слой, ширина 16 | сотни миллиардов параметров, 100+ слоёв, ширина 10 000+; RoPE, GQA, «воротные» активации, MoE |
| Обучение | одно имя за шаг | батчи по миллионам токенов, накопление градиентов, bf16/fp8, тысячи GPU месяцами |
| Оптимизация | Adam, линейное затухание | тщательно подобранные расписания, законы масштабирования (Chinchilla), много пробных запусков |
| Пост-обучение | нет | SFT на диалогах, затем RL |
| Инференс | цикл по токенам | батчинг запросов, страничный KV-кэш (vLLM), спекулятивное декодирование, квантизация |
Каждой строке таблицы в курсе посвящён свой модуль: от 4.2 и 5.4 до 6.1 и 7.1.
Вопросы и ответы из поста (пересказ)
Заголовок раздела «Вопросы и ответы из поста (пересказ)»- «Понимает» ли модель что-нибудь? Механически — никакой магии: большая математическая функция, превращающая токены в распределение вероятностей следующего токена. Называть ли это пониманием — решать вам, но весь механизм лежит в этих 200 строках.
- Почему это работает? Оптимизатор понемногу сдвигает тысячи параметров так, чтобы loss падал. Параметры оседают в значениях, которые ловят статистику данных: имена часто начинаются с согласной, «qu» ходят вместе, три согласные подряд редки. Явных правил модель не учит — только распределение, которое их отражает.
- Как это связано с ChatGPT? Тот же цикл «предсказать токен — выбрать — повторить», только огромный и с пост-обучением для диалога. Системный промпт, ваше сообщение и ответ — просто токены одной последовательности.
- Откуда галлюцинации? Модель выбирает токены по вероятностям и не знает, что истинно. Выдуманное имя «karia» и уверенно сказанная неправда у ChatGPT — одно и то же явление: правдоподобное продолжение, которого нет в реальности.
- Как получить имена лучше? Обучать дольше (
num_steps), увеличить модель (n_embd,n_layer,n_head), взять больше данных — те же ручки, что и в больших моделях. - А если сменить данные? Модель выучит то, что в них есть: названия городов, покемонов, английские слова, короткие стихи. Остальной код менять не нужно.
Что сделать руками
Заголовок раздела «Что сделать руками»- Скачайте
microgpt.pyиз gist и запустите:python microgpt.py. Нужен только Python. Проверьте, что программа печатаетnum docs: 32033,vocab size: 27,num params: 4192, а первые значения loss — 3,3660 и 3,4243. - Для быстрого опыта уменьшите
num_stepsдо 100 и посмотрите на имена. Потом верните 1000 и сравните. - Смените данные: положите в
input.txtрусские имена (по одному в строке, например из урока 4.1.2). Сколько теперь токенов в словаре и параметров в модели? Пересчитайте по формуле из блока 5. - Поставьте
n_layer = 2иn_embd = 32: как изменятся число параметров, время шага и итоговый loss? - Пройдите эволюцию файла слоями «луковицы» в gist
build_microgpt.py: по ревизиям видно, как
таблица биграмм (
train0) превращается в MLP с ручными градиентами, затем получает автоград, внимание с одной головой, многоголовое внимание и, наконец, Adam. - Сделайте задания урока 4.6.1 — там та же модель с проверкой прямо в браузере.
Словарик
Заголовок раздела «Словарик»| EN | RU |
|---|---|
| dependency-free | без зависимостей (только стандартная библиотека Python) |
| autograd, computation graph | автоматическое дифференцирование, граф вычислений |
| local gradient, chain rule | локальная производная, цепное правило |
| topological order | топологический порядок |
| BOS (Beginning of Sequence) | токен начала последовательности |
| state dict | словарь параметров модели |
| token / position embedding | эмбеддинг токена / позиции |
| query, key, value | запрос, ключ, значение |
| KV cache | KV-кэш |
| residual connection | остаточная связь |
| logits | логиты |
| cross-entropy loss | кросс-энтропия |
| first / second moment, bias correction | первый / второй момент, поправка смещения |
| linear learning rate decay | линейное затухание learning rate |
| temperature, greedy decoding | температура, жадное декодирование |
| hallucination | галлюцинация |