Эмбеддинги и выходная проекция
Что вы узнаете
Заголовок раздела «Что вы узнаете»- Почему токен нельзя подать в сеть как число или one-hot вектор и чем эмбеддинг лучше.
- Что эмбеддинг — это строка матрицы , и почему выборка строки (lookup) — то же самое, что умножение one-hot вектора на матрицу.
- Как устроен градиент по матрице эмбеддингов и почему на шаге обучения меняются только строки встреченных токенов.
- Как выходная проекция (LM head) превращает скрытое состояние в логиты и что такое weight tying.
- Зачем эмбеддинги иногда умножают на и какую долю параметров модели они занимают.
- Как всё это реализовано в
core/token_embeddings.pyи в моделях репозитория.
Предварительные знания
Заголовок раздела «Предварительные знания»- Задача языкового моделирования: модель выдаёт распределение вероятностей следующего токена, loss — cross-entropy.
- Токенизация: текст превращается в последовательность целых чисел , каждое из диапазона .
- Умножение матриц, скалярное произведение, производная сложной функции.
Зачем нужны эмбеддинги
Заголовок раздела «Зачем нужны эмбеддинги»После токенизации текст — это последовательность индексов, например [412, 7, 3051]. Нейросеть работает с векторами действительных чисел, поэтому индекс нужно во что-то превратить. Есть три очевидных способа.
Подать индекс как число. Тогда токен 412 «больше» токена 7 и «близок» к токену 413. Но номера в словаре расставлены токенизатором по частоте слияний BPE, а не по смыслу: соседние номера ничем не похожи. Сеть получила бы ложную порядковую структуру.
One-hot вектор. Токен кодируется вектором длины , где на месте стоит 1, а остальное — нули:
где:
- — индекс токена;
- — размер словаря (у GPT-2 — 50 257, у Gemma — 256 000);
- — вектор-строка длины с единственной единицей на позиции .
Ложного порядка здесь нет, но появляются другие проблемы:
- Размерность. Вектор длины 256 000 на каждый токен — огромный вход, почти целиком из нулей.
- Все токены одинаково непохожи. Скалярное произведение двух разных one-hot векторов равно 0, расстояние между любыми двумя — . «кот» так же далёк от «кошка», как от «синхрофазотрон». Сходство слов нельзя выразить, его придётся выучивать заново в каждом слое.
- Нет обобщения. Что модель узнала про токен «кошка», никак не переносится на «кот»: у них нет общих координат.
Эмбеддинг (embedding) решает все три проблемы: каждому токену сопоставляется обучаемый плотный вектор небольшой длины (от сотен до нескольких тысяч). Похожим токенам обучение даёт похожие векторы, и знание переносится между ними.
Эмбеддинг как строка матрицы
Заголовок раздела «Эмбеддинг как строка матрицы»Определение
Заголовок раздела «Определение»Матрица эмбеддингов хранит по одной строке на каждый токен словаря. Эмбеддинг токена — это -я строка:
где:
- — обучаемая матрица, строк и столбцов (в главе о MoE той же буквой обозначено число экспертов; в этой главе — всегда матрица эмбеддингов);
- — строка с номером (нумерация с 0);
- — размерность модели (
embed_dimв конфиге); тот же размер у скрытого состояния во всех блоках декодера.
Для последовательности эмбеддинги складываются в матрицу
где — вход в первый блок декодера (до добавления позиционной информации, о ней — в следующей главе). В батче добавляется ещё одно измерение: вход [B, T] целых чисел превращается в тензор [B, T, d].
Lookup и умножение на one-hot — одно и то же
Заголовок раздела «Lookup и умножение на one-hot — одно и то же»Возьмём линейный слой без bias, на вход которому подаётся one-hot вектор: . По определению умножения вектора на матрицу -я координата результата
где:
- — -я координата one-hot вектора: 1 при и 0 иначе;
- — элемент матрицы эмбеддингов в строке , столбце ;
- — номер координаты результата.
Все слагаемые, кроме одного, обнуляются, и остаётся ровно строка :
Значит, слой эмбеддингов — это линейный слой, применённый к one-hot входу, просто вычисленный умно. Умножение стоит операций, выборка строки по индексу (lookup) — операций копирования. One-hot вектор при этом даже не создаётся.
Отсюда важное следствие: эмбеддинг не отменяет one-hot кодирование, а сжимает его. Первый линейный слой над one-hot входом всё равно имел бы параметров — это и есть .
Пример. , :
столбцы: 0 1E = строка 0: [ 0.1 0.2 ] строка 1: [ 0.3 -0.1 ] строка 2: [ 0.0 0.5 ]
o(1) = [0, 1, 0]o(1) · E = [0·0.1 + 1·0.3 + 0·0.0, 0·0.2 + 1·(−0.1) + 0·0.5] = [0.3, −0.1] = E[1]Проверка в PyTorch:
import torchfrom llm.core.token_embeddings import TokenEmbeddings
emb = TokenEmbeddings(vocab_size=5, emb_size=3)x = torch.tensor([[1, 3, 1]]) # [B=1, T=3]one_hot = torch.nn.functional.one_hot(x, 5).float() # [1, 3, 5]W = emb._embedding.weight # [5, 3] — матрица Eprint(torch.allclose(one_hot @ W, emb(x))) # TrueГрадиент по матрице эмбеддингов
Заголовок раздела «Градиент по матрице эмбеддингов»Эмбеддинги обучаются вместе со всей моделью обратным распространением ошибки (см. training.md). Какой градиент получает ?
Пусть — loss, а — градиент по эмбеддингу на позиции , который пришёл из первого блока. Эмбеддинг позиции — копия строки , поэтому по цепному правилу
где:
- — строка матрицы для токена ;
- сумма идёт по всем позициям (во всём батче), где стоит токен ;
- — градиент loss по вектору , форма .
Вывод
Запишем эмбеддинг позиции через one-hot: , то есть . Производная одной координаты по одному элементу матрицы:
где — 1, если , и 0 иначе. Loss зависит от только через все , поэтому
Множитель равен 1 только там, где . Остаётся сумма по этим позициям, что и даёт формулу выше для всей строки сразу. В матричном виде: , где — one-hot матрица входа, — градиенты по эмбеддингам.
Что это значит.
- Строки токенов, которых нет в батче, получают нулевой градиент. На шаге обучения меняются только строки встреченных токенов.
- Если токен встретился несколько раз, градиенты складываются.
- Редкие токены обновляются редко, и их эмбеддинги учатся хуже. Это одна из причин, по которой словарь не делают сколь угодно большим.
Пример. Если в примере выше взять всех элементов выхода, то для каждой позиции. Токен 1 стоит на двух позициях, токен 3 — на одной:
out = emb(x) # x = [[1, 3, 1]]out.sum().backward()print(emb._embedding.weight.grad)# tensor([[0., 0., 0.],# [2., 2., 2.], ← токен 1 встретился дважды# [0., 0., 0.],# [1., 1., 1.], ← токен 3 — один раз# [0., 0., 0.]])Тонкость: нулевой градиент ещё не значит, что строка не изменится. Оптимизатор AdamW сдвигает её за счёт weight decay и накопленного момента с прошлых шагов (подробнее — в training.md). Кроме того, при weight tying (ниже) строки получают градиент ещё и от выходной проекции, причём все сразу.
Геометрия эмбеддингов
Заголовок раздела «Геометрия эмбеддингов»Сходство векторов
Заголовок раздела «Сходство векторов»Сравнивать эмбеддинги можно скалярным произведением или косинусом угла между ними:
где:
- — два эмбеддинга;
- — евклидова норма;
- : 1 — векторы сонаправлены, 0 — ортогональны, −1 — противоположны.
Скалярное произведение учитывает и направление, и длину векторов; косинус — только направление. Для one-hot векторов оба равны нулю для любой пары разных токенов — никакой геометрии нет.
Пример (выдуманные трёхмерные векторы):
a = «кот» = [ 1, 2, 0 ]b = «кошка» = [ 1, 1.5, 0.5]c = «машина» = [−1, 0, 2 ]
a·b = 1 + 3 + 0 = 4 ‖a‖ = √5 ≈ 2.236 ‖b‖ = √3.5 ≈ 1.871 cos(a, b) ≈ 0.956a·c = −1 + 0 + 0 = −1 ‖c‖ = √5 ≈ 2.236 cos(a, c) = −0.2b·c = −1 + 0 + 1 = 0 cos(b, c) = 0Дистрибутивная гипотеза и word2vec
Заголовок раздела «Дистрибутивная гипотеза и word2vec»Почему при обучении похожие слова получают похожие векторы? Ответ даёт дистрибутивная гипотеза (distributional hypothesis) лингвистики середины XX века (Harris, 1954; Firth, 1957): слова, которые встречаются в похожих контекстах, имеют похожий смысл. Языковая модель учится предсказывать токен по контексту. Если «кот» и «кошка» стоят в одинаковых окружениях («… мурлычет на диване»), то градиенты сдвигают их эмбеддинги в одну сторону — так проще выдать похожие предсказания.
Нейросетевые языковые модели с обучаемыми векторами слов предложили Bengio et al. (2003). Широкую известность эмбеддинги получили после word2vec (Mikolov et al., 2013, arXiv:1301.3781): две простые модели — CBOW (предсказать слово по соседям) и skip-gram (предсказать соседей по слову) — обучались на миллиардах слов и давали векторы, в которых смысловые отношения становились почти линейными. Классический пример из статьи: вектор, ближайший к , — .
Чем эмбеддинги в трансформере отличаются от word2vec:
- они обучаются совместно со всей моделью (end-to-end), а не отдельно заранее;
- это эмбеддинги токенов (подслов), а не слов: «кошка» может быть одним токеном или несколькими;
- эмбеддинг не зависит от контекста: токен «ключ» получает один и тот же вектор в «ключ от двери» и «горный ключ». Различать значения по контексту — работа следующих слоёв (attention и FFN). Выход последнего блока — уже контекстный вектор.
Выходная проекция (LM head)
Заголовок раздела «Выходная проекция (LM head)»На выходе декодера для каждой позиции есть скрытое состояние (у большинства моделей — после финальной нормализации). Из него нужно получить распределение над словарём. Это делает выходная проекция (output projection, LM head) — линейный слой из в :
где:
- — скрытое состояние позиции после последнего блока (и финальной нормы);
- — матрица проекции; её столбец — «выходной вектор» токена ;
- — bias (есть не во всех моделях);
- — логиты (logits): ненормированные оценки каждого токена словаря.
Для всей последовательности — , , ; в коде выход forward — [B, T, V].
Вероятности получаются softmax:
где — логит токена на позиции . Позиция предсказывает следующий токен (см. language-modeling.md). Модели репозитория возвращают логиты, а softmax применяется снаружи: в функции потерь (cross_entropy принимает логиты) и в генерации.
Интуиция. Без bias логит — скалярное произведение скрытого состояния с выходным вектором токена . Модель «рисует» в вектор, похожий на выходные векторы подходящих следующих токенов, и они получают большие логиты. Выходная проекция — тоже таблица векторов по одному на токен, только транспонированная.
Цена выходной проекции
Заголовок раздела «Цена выходной проекции»Проекция стоит параметров и операций (умножений и сложений) на каждую позицию. У Gemma 2B это M параметров — больше, чем все параметры attention (см. таблицу долей ниже). Softmax тоже считается по всему словарю: на каждую позицию — экспонент.
Weight tying
Заголовок раздела «Weight tying»Во входе и выходе модели есть по таблице «вектор на каждый токен»: строки и столбцы . Weight tying (связывание весов) — использовать одну матрицу для обоих:
где:
- — та же матрица эмбеддингов, что на входе;
- — она же, транспонированная, в роли ;
- bias при этом обычно убирают (так в GPT-1, GPT-2 и Gemma).
Логит токена — скалярное произведение скрытого состояния с его входным эмбеддингом. Модель предсказывает токен, чей эмбеддинг «похож» на текущее состояние.
Идея предложена одновременно в двух работах:
- Press & Wolf (2017), arXiv:1608.05859: выходная матрица языковой модели сама является хорошим эмбеддингом слов; связывание входа и выхода снижает перплексию и заметно уменьшает число параметров.
- Inan, Khosravi, Socher (2017), arXiv:1611.01462: теоретическое обоснование — вход и выход живут в одном пространстве, и если учитывать близость слов в функции потерь, связывание получается естественным следствием.
В трансформере связывание использовано уже в исходной статье (Vaswani et al., 2017, arXiv:1706.03762, разд. 3.4 «Embeddings and Softmax» — со ссылкой на Press & Wolf).
Экономия параметров
Заголовок раздела «Экономия параметров»Без связывания на словарь уходит (вход) (выход с bias) параметров, со связыванием — . Экономия
где слагаемое — отброшенный bias головы (если его и так не было — ).
| Модель | Связывание в оригинале | |||
|---|---|---|---|---|
| GPT-1 | 40 478 | 768 | 31,1M | да (формула (2) статьи: , и код OpenAI) |
| GPT-2 124M | 50 257 | 768 | 38,6M | да |
| Gemma 2B | 256 000 | 2048 | 524,3M | да |
| LLaMA 7B, Mistral 7B, Mixtral 8x7B | 32 000 | 4096 | 131,1M | нет (tie_word_embeddings: false в HF) |
Размеры словарей и — из глав gpt.md, gpt2.md, gemma.md, llama.md.
Градиент при связывании
Заголовок раздела «Градиент при связывании»Матрица одна, поэтому её градиент — сумма двух вкладов: от входа (только строки встреченных токенов, см. выше) и от выхода. Посчитаем выходной вклад для одной позиции. Для cross-entropy с правильным токеном градиент по логитам равен ( — вектор вероятностей, — one-hot правильного токена; см. language-modeling.md, вывод — в training.md). Так как , то и
где:
- — вероятность, которую модель дала токену ;
- — 1 для правильного токена и 0 для остальных;
- — скрытое состояние.
Градиент получают все строк: у каждого токена. Шаг градиентного спуска () сдвигает эмбеддинг правильного токена к (множитель ), а остальных — от , тем сильнее, чем выше их вероятность.
Пример. , , матрица из примера выше, , правильный токен :
z = h·Eᵀ = [0.1·1 + 0.2·2, 0.3·1 − 0.1·2, 0·1 + 0.5·2] = [0.5, 0.1, 1.0]p = softmax(z) ≈ [0.301, 0.202, 0.497]p − y ≈ [0.301, 0.202, −0.503]∂L/∂E (выход) = (p − y)ᵀ·h ≈ [[ 0.301, 0.603], [ 0.202, 0.404], [−0.503, −1.007]]Строка 2 после шага сдвинется в сторону , строки 0 и 1 — прочь от неё.
Реализация: output_projection и tie_word_embeddings
Заголовок раздела «Реализация: output_projection и tie_word_embeddings»Выходную проекцию создаёт функция output_projection в core/token_embeddings.py:
def output_projection(token_embeddings, tie_weights=False) -> nn.Linear: linear = nn.Linear( token_embeddings.embedding_dim, token_embeddings.num_embeddings, bias=not tie_weights ) if tie_weights: linear.weight = token_embeddings._embedding.weight return linearКак она реализует формулу:
nn.Linear(d, V)хранит вес в форме[V, d](выход × вход) и считаетx @ weight.T + bias. Значит,linear.weight— это .- У
nn.Embedding(V, d)вес тоже[V, d]— это . Присваиваниеlinear.weight = ...weightделает их одним параметром, иlinear(h)= — ровно формула weight tying. - С
tie_weights=Trueслой создаётся без bias (bias=not tie_weights). - Параметр один, поэтому autograd складывает в его
.gradоба вклада — от входа и от выхода.
Где используется:
| Модель | Ключ конфига | По умолчанию | Что без ключа |
|---|---|---|---|
GPT, GPT2 | tie_word_embeddings | false | отдельный Linear с bias (output_projection(..., tie_weights=False)) |
Gemma | tie_word_embeddings | false | отдельный nn.Linear с bias по ключу bias |
Llama, Mistral, Mixtral | нет | — | всегда отдельный nn.Linear(embed_dim, vocab_size, bias=bias) |
По умолчанию ключ выключен ради совместимости со старыми чекпоинтами, где есть отдельные _linear.weight и _linear.bias. Для загрузки весов OpenAI GPT-1/GPT-2 и Gemma его нужно включить (см. gpt.md, gemma.md). Проверка экономии на учебной модели:
from llm.models.gpt import GPT
cfg = {"vocab_size": 50, "embed_dim": 32, "num_heads": 4, "num_layers": 2, "max_position_embeddings": 16, "dropout": 0.0}untied = GPT(cfg)tied = GPT({**cfg, "tie_word_embeddings": True})n = lambda m: sum(p.numel() for p in m.parameters())print(n(untied) - n(tied)) # 1650 = 50·32 + 50print(tied._linear.weight is tied._token_embeddings._embedding.weight) # Truemodel.parameters() возвращает общий параметр один раз, поэтому разница — ровно . В state_dict связанной модели оба ключа (_token_embeddings._embedding.weight и _linear.weight) всё равно присутствуют — они указывают на один тензор.
Масштабирование эмбеддингов на √d
Заголовок раздела «Масштабирование эмбеддингов на √d»Что делается
Заголовок раздела «Что делается»В исходном трансформере (Vaswani et al., 2017, разд. 3.4) эмбеддинги перед подачей в сеть умножаются на :
где — размерность модели, — эмбеддинг токена на позиции . Gemma делает то же самое (Gemma Team, 2024; в HF — множитель normalizer/embed_scale, см. бэклог, пункт 42). В статье трансформера причина не объясняется; ниже — обоснование через нормы векторов, которое обычно приводят.
Почему: нормы при связанных весах
Заголовок раздела «Почему: нормы при связанных весах»При weight tying одна матрица служит и входом, и выходом, а масштаб, удобный для выхода, неудобен для входа.
Выход. Пусть элементы независимы, со средним 0 и дисперсией , а скрытое состояние после финальной RMSNorm имеет среднеквадратичное значение координат около 1, то есть . Тогда дисперсия логита
где считаются фиксированными, а — независимыми случайными величинами с дисперсией . Чтобы логиты в начале обучения были порядка 1 (а не порядка , как было бы при , — тогда softmax сразу почти one-hot), нужно , то есть .
Вход. С такой дисперсией эмбеддинг — маленький вектор:
где RMS — среднеквадратичное значение координат вектора. А residual-поток внутри модели, куда блоки прибавляют свои выходы, и синусоидальные позиционные коды (координаты в ) имеют координаты порядка 1. Умножение на поднимает RMS эмбеддинга с до 1 — на тот же масштаб.
Что будет без множителя: при вход в первый блок в раз меньше «ожидаемого». В pre-LN модели каждый блок нормализует свой вход, но прибавляет выход к ненормализованному residual-потоку. Крохотный эмбеддинг тонет в первых же добавках блоков, и информация о токене теряется. Для загруженных весов Gemma без множителя результат просто неверный: сеть обучалась с ним (gemma.md).
Численный пример. Инициализация N(0, 0.02) (GPT, HF), : . Норма эмбеддинга , RMS координаты — 0.02. После умножения на RMS координаты , норма .
Реализация: scale_embeddings
Заголовок раздела «Реализация: scale_embeddings»В репозитории масштабирование есть только у Gemma и включается ключом scale_embeddings (по умолчанию false). В конструкторе models/gemma/gemma.py:
self._embedding_scale = math.sqrt(config["embed_dim"]) if config.get("scale_embeddings", False) else Noneи в forward:
tok_out = self._token_embeddings(x) # [batch, seq_len, emb_size]if self._embedding_scale is not None: # как в HF: множитель приводится к dtype эмбеддингов tok_out = tok_out * torch.tensor(self._embedding_scale, dtype=tok_out.dtype)Множитель приводится к dtype эмбеддингов, как в HF: в bfloat16 округляется до 45.25, и так же округляет эталон — иначе логиты разошлись бы в последних битах.
Dropout на эмбеддингах
Заголовок раздела «Dropout на эмбеддингах»После эмбеддингов (и позиционных эмбеддингов у GPT) все модели репозитория применяют dropout: при обучении каждая координата с вероятностью обнуляется, остальные делятся на , чтобы математическое ожидание не изменилось:
где:
- — координата входного вектора;
- — случайная маска, 1 с вероятностью ;
- — вероятность обнуления (ключ
dropoutв конфиге; не путать с вероятностями токенов из раздела о выходной проекции).
В режиме eval() dropout ничего не делает. Он мешает модели полагаться на отдельные координаты эмбеддингов и работает как регуляризация (Srivastava et al., 2014). В GPT-1 это embd_pdrop = 0.1 (gpt.md); в Gemma dropout нет вовсе, и для соответствия оригиналу нужно dropout: 0 (gemma.md).
В коде это self._dropout = nn.Dropout(config["dropout"]): у GPT и GPT-2 — self._dropout(tok_out + pos_out.unsqueeze(0)), у LLaMA, Mistral, Mixtral и Gemma — self._dropout(tok_out) (у Gemma — после масштабирования). Подробнее о dropout — в training.md.
Доля параметров эмбеддингов
Заголовок раздела «Доля параметров эмбеддингов»Эмбеддинги — это параметров, голова без связывания — ещё . У маленьких моделей с большим словарём это заметная доля всей модели:
| Модель | Всего параметров | Доля одной таблицы | Связывание | |
|---|---|---|---|---|
| GPT-1 | 31,1M | 116,5M (со связыванием) | ≈ 27 % | да |
| GPT-2 124M | 38,6M | 124,4M (со связыванием) | ≈ 31 % | да |
| Gemma 2B | 524,3M | ≈ 2,5B (со связыванием) | ≈ 21 % | да; без него ещё +524M |
| LLaMA 7B | 131,1M | ≈ 6,7B | ≈ 2 % (≈ 4 % вместе с отдельной головой) | нет |
Число параметров GPT-1 и GPT-2 — из gpt.md и gpt2.md (сверено с HF), Gemma — из gemma.md и бэклога.
Выводы:
- При большом словаре и маленьком эмбеддинги занимают до трети модели. Поэтому в маленьких моделях связывание особенно выгодно: у Gemma 2B отдельная голова добавила бы ещё пятую часть модели.
- В больших моделях доля быстро падает: число параметров блоков растёт как , а таблицы — как .
- Параметры таблицы почти не стоят вычислений на входе (lookup), но выходная проекция стоит операций на токен, связана она или нет.
Путь от индексов токенов к логитам в моделях репозитория. Пунктир — необязательные шаги.
%%{init: {"flowchart": {"rankSpacing": 28, "nodeSpacing": 28}}}%%
flowchart TB
Ids(["token ids [B, T]"]):::io --> Emb["TokenEmbeddings<br/>строки E [V, d]"]:::blue
Emb --> Scale["× √d<br/>(Gemma, scale_embeddings)"]:::dim
Scale --> Pos(("+")):::add
PosEmb["позиционные эмбеддинги<br/>(GPT, GPT-2)"]:::purple --> Pos
Pos --> Drop["Dropout"]:::gray
Drop --> Dec["блоки декодера × L"]:::gray
Dec --> Norm["финальная нормализация<br/>(кроме GPT-1)"]:::gray
Norm --> Head["выходная проекция<br/>Linear d → V"]:::blue
Head --> Out(["logits [B, T, V]"]):::io
Emb -. "tie_word_embeddings: та же матрица E" .-> Head
classDef io fill:#ffffff,stroke:#999999,color:#1a1a1a;
classDef add fill:#ffffff,stroke:#666666,color:#1a1a1a;
classDef blue fill:#dae8fc,stroke:#6c8ebf,color:#1a1a1a;
classDef purple fill:#e1d5e7,stroke:#9673a6,color:#1a1a1a;
classDef gray fill:#f5f5f5,stroke:#666666,color:#1a1a1a;
classDef dim fill:#f5f5f5,stroke:#bbbbbb,color:#999999,stroke-dasharray:4 3;
У моделей с RoPE (LLaMA, Mistral, Mixtral, Gemma) узла сложения с позиционными эмбеддингами нет: позиция вносится поворотом Q и K внутри attention (см. positional-encoding.md).
Реализация в репозитории: TokenEmbeddings
Заголовок раздела «Реализация в репозитории: TokenEmbeddings»Класс TokenEmbeddings в core/token_embeddings.py — тонкая обёртка над nn.Embedding:
class TokenEmbeddings(nn.Module): def __init__(self, vocab_size: int, emb_size: int): super().__init__() self._embedding = nn.Embedding(num_embeddings=vocab_size, embedding_dim=emb_size)
def forward(self, x: Tensor) -> Tensor: return self._embedding(x)self._embedding.weight— матрица формы[vocab_size, emb_size]= .forwardпринимает целочисленный тензор любой формы[...]и возвращает[..., emb_size]: для входа модели[B, T]→[B, T, d]. Это и есть lookup .- Свойства
num_embeddings() иembedding_dim() нужныoutput_projection, чтобы построить голову нужной формы. padding_idxне задаётся: у pad-токена обычная обучаемая строка.
Все шесть моделей создают эмбеддинги одинаково:
self._token_embeddings = TokenEmbeddings(vocab_size=config["vocab_size"], emb_size=config["embed_dim"])Инициализация. nn.Embedding по умолчанию заполняет из N(0, 1). Все шесть моделей переинициализируют все Linear и Embedding из N(0, 0.02) (init_normal_ в core/weight_init.py) — как в статьях OpenAI для GPT и GPT2 и как _init_weights HuggingFace для Llama, Mistral, Mixtral и Gemma; стандартное отклонение — ключ initializer_range. При загрузке чекпоинта инициализация не важна: веса перезаписываются.
Типичные ошибки и тонкости
Заголовок раздела «Типичные ошибки и тонкости»- Индекс вне словаря. Токен даёт
IndexError: index out of range in selfна CPU и невнятный device-side assert на CUDA. Если токенизатор и модель расходятся вvocab_size, ошибка всплывёт именно здесь. - Связывание и загрузка чекпоинтов. Чекпоинт с
tie_word_embeddings: trueне загружается в модель сfalseи наоборот: у отдельной головы есть_linear.bias, у связанной — нет. - Связывание и копирование весов. Присваивание
linear.weight = embedding.weightдолжно выполняться после создания обоих модулей и не должно заменяться копированием (.data.copy_), иначе параметры разойдутся после первого шага. Перенос модели на устройство (.to(device)) связь сохраняет. scale_embeddingsбез подходящей инициализации. Множитель рассчитан на малые эмбеддинги. С эмбеддингами N(0, 1) (инициализацияnn.Embeddingпо умолчанию) координаты входа при обучении с нуля будут порядка (16 при ).Gemmaв репозитории инициализирует эмбеддинги N(0, 0.02), как HF.- Логиты — не вероятности.
forwardвозвращает логиты; softmax нужен только для вероятностей.cross_entropyв PyTorch ожидает именно логиты — повторный softmax перед ней даёт неверный loss. - Нулевой градиент ≠ неизменная строка. Weight decay и момент AdamW двигают и строки токенов, которых не было в батче.
- Эмбеддинг токена — строка обучаемой матрицы ; lookup эквивалентен умножению one-hot вектора на , но стоит операций вместо .
- Градиент по — сумма градиентов по позициям, где стоит токен; строки невстреченных токенов получают ноль.
- Похожие по употреблению токены получают близкие векторы (дистрибутивная гипотеза); сходство меряют скалярным произведением или косинусом.
- Выходная проекция даёт логиты; при weight tying без bias, экономия параметров. В репозитории —
output_projectionи ключtie_word_embeddings(GPT, GPT-2, Gemma). - При связанных весах масштаб подобран под выход (RMS координат ), поэтому на входе эмбеддинги умножают на (Vaswani 2017, Gemma; ключ
scale_embeddings). - У маленьких моделей с большим словарём таблица эмбеддингов — 20–30 % всех параметров.
Вопросы и упражнения
Заголовок раздела «Вопросы и упражнения»- Почему скалярное произведение любых двух разных one-hot векторов равно нулю, а расстояние между ними — ? Что это говорит о «геометрии» one-hot кодирования?
Ответ
У и при единицы стоят на разных местах, поэтому в сумме все произведения нулевые. Разность векторов имеет ровно две ненулевые координаты, и , и её норма . Все пары токенов одинаково далеки и ортогональны — никакого понятия сходства в one-hot кодировании нет.
- Батч из двух строк:
[[5, 2, 5], [2, 7, 5]]. Какие строки матрицы получат ненулевой градиент на этом шаге? Во сколько раз градиент строки 5 больше градиента по одной позиции, если все одинаковы?
Ответ
Ненулевой градиент получат строки 2, 5 и 7. Токен 5 встречается трижды, поэтому при одинаковых его градиент — . У токена 2 — , у 7 — .
- Посчитайте, сколько параметров экономит
tie_word_embeddings: trueдля учебной модели GPT сvocab_size = 1000,embed_dim = 256.
Ответ
: матрица головы и её bias.
- Пусть , элементы — N(0, σ²) с . Какова ожидаемая квадратичная норма эмбеддинга до и после умножения на ? Какое среднеквадратичное значение координаты получится после умножения?
Ответ
До: . После: (норма ). RMS координаты после умножения — .
- В примере раздела «Градиент при связывании» (, правильный токен 2) сделайте шаг градиентного спуска только по выходному вкладу с . Как изменится логит правильного токена при том же ?
Ответ
Новая строка 2: . Логит: вместо 1.0 — вырос на . Логиты токенов 0 и 1 уменьшатся: на и .
- Для Gemma 2B (, ) оцените число операций выходной проекции на один токен и сравните с числом операций матриц Q/K/V/O одного слоя attention с MQA (8 голов Q по 256, 1 голова K/V по 256).
Ответ
Голова: операций. Attention одного слоя: — , и — по , — , всего M параметров, операций. Выходная проекция дороже проекций attention одного слоя примерно в 55 раз и дороже проекций attention всех 18 слоёв примерно втрое.
- Почему weight tying не применяют (или применяют реже) в больших моделях вроде LLaMA 7B? Оцените долю, которую сэкономило бы связывание.
Ответ
Экономия — M из B, около 2 %. Выигрыш мал, а отдельные матрицы дают входу и выходу независимые масштабы и больше свободы. Решение — эмпирическое; в статье LLaMA оно не обсуждается.
- (Код.) Используя
TokenEmbeddingsиoutput_projection, соберите «модель» без блоков декодера:logits = head(emb(x))со связанными весами. Какой токен будет самым вероятным следующим для входного токена в начале обучения и почему?
Ответ
Логит токена равен . Максимум почти всегда при : , а скалярные произведения с другими случайными строками в среднем 0 с разбросом . Такая модель без обучения «предсказывает» повтор входного токена — побочный эффект связывания, который обучение устраняет.
Литература
Заголовок раздела «Литература»- Harris, Z. Distributional Structure. Word, 10(2–3), 1954 — дистрибутивная гипотеза.
- Firth, J. R. A Synopsis of Linguistic Theory 1930–1955. 1957 — «слово узнаётся по его окружению».
- Bengio, Ducharme, Vincent, Jauvin. A Neural Probabilistic Language Model. JMLR, 2003 — обучаемые векторы слов в нейросетевой языковой модели.
- Mikolov, Chen, Corrado, Dean. Efficient Estimation of Word Representations in Vector Space. 2013. arXiv:1301.3781 — word2vec (CBOW, skip-gram).
- Srivastava, Hinton, Krizhevsky, Sutskever, Salakhutdinov. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR, 2014.
- Press, Wolf. Using the Output Embedding to Improve Language Models. EACL 2017. arXiv:1608.05859 — weight tying.
- Inan, Khosravi, Socher. Tying Word Vectors and Word Classifiers: A Loss Framework for Language Modeling. ICLR 2017. arXiv:1611.01462 — weight tying.
- Vaswani et al. Attention Is All You Need. 2017. arXiv:1706.03762 — разд. 3.4: общая матрица эмбеддингов и softmax, множитель .
- Gemma Team. Gemma: Open Models Based on Gemini Research and Technology. 2024. arXiv:2403.08295.