Перейти к содержанию
С нуля
Программа курса
EN Открыть

Программа курса

Неделя 8. Собрать трансформер целиком

Фаза 2. Современный трансформер · неделя 8 из 24

Учиться в приложении: тьютор, задачи с кодом →

Ядро: полный проход и лосс, инициализация, сверка логитов с настоящей моделью, трансформер с пустого файла · Глубина: трек D и вторая половина трека C · ≈ 11 ч ядро / 19 ч всё

Детали из недель 6–7 становятся моделью, и появляется единственная настоящая проверка: совпадение логитов (сырых выходов модели до softmax) с чужой эталонной реализацией. Всё дальше (обучение, инференс, сэмплирование) молча предполагает, что модель верна. Ошибка здесь не падает, а тихо ухудшает всё, что построено сверху.

Шаг 1. Полный проход и лосс

  • Полный forward: embed → L×(RMSNorm → attn → residual → RMSNorm → FFN → residual) → norm → head. Формы: id (B, T) → эмбеддинги (B, T, D) → блоки форму не меняют → финальная норма → логиты (B, T, V)
  • Лосс со сдвигом: позиция t предсказывает токен t+1, то есть logits[:, :-1] против targets[:, 1:]. Проверка на старте: необученная модель даёт почти равномерное распределение, лосс ≈ ln V (при V = 32 000 это 10.37). Если сильно выше, сломан масштаб инициализации

Шаг 2. Инициализация

На пальцах. В residual stream каждый из 2L подслоёв прибавляет свой вклад. Если вклады независимы и у каждого дисперсия 1, дисперсии складываются. При L = 8 это 16 добавок: дисперсия потока 1 + 16 = 17, разброс вырос в √17 ≈ 4 раза. Чем глубже модель, тем сильнее разгон. Если уменьшить каждый вклад в √(2L) раз, сумма всех добавок имеет дисперсию 1 при любой глубине.

  • Инициализация, scaled init для residual-веток: все матрицы N(0, 0.02²), а выходные проекции веток (w_o, w_down) получают std = 0.02/√(2L). Вывод (D7): Var(x_L) = Var(x_0) + 2L·σ², и σ² ∝ 1/(2L) делает сумму независимой от глубины
  • Dropout: почему в современных LLM его почти нет. Dropout борется с запоминанием повторяющихся примеров, а при предобучении модель видит почти каждый токен один раз. Запоминать нечего, а шум замедляет сходимость. Его иногда возвращают при дообучении на маленьких данных

Шаг 3. Сверка логитов с настоящей моделью

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

  • Порядок: fp32, одинаковые id на входе, forward-хуки на каждом блоке, допуск ~1e-4
  • Где ломается чаще всего. RoPE у HF-реализаций Llama/Qwen поворачивает пары (i, i + H/2), а в nanolm соседние (2i, 2i+1): строки W_q и W_k внутри головы надо переставить. В nanolm Swish стоит на w_up, а у HF на gate_proj: значит gate_proj → w_up, up_proj → w_gate. Дальше проверяй ε нормы, rope_theta, связаны ли эмбеддинги, явное F вместо 8D/3

Типичные ошибки

  • Потерять сдвиг на один токен: модель учится копировать вход, лосс подозрительно быстро падает к нулю. Отдельного теста нет. Утечку будущего через маску ловит test_model_is_causal
  • Скопировать матрицу вместо связывания: два тензора вместо одного, ловит test_weight_tying_shares_one_tensor
  • Слой создан, но не подключён к графу: test_gradients_reach_every_parameter. Общая проверка здоровья test_model_can_overfit_one_batch: если модель не запоминает один батч, дальше не идти

Код → nanolm/model.py: полная модель: Block, NanoLM (_init_weights, scaled init в __init__, forward(input_ids, targets, cache) со сдвигом, num_parameters). Загрузить веса реальной маленькой модели (например, Qwen3-0.6B) в свою реализацию и сверить логиты. Это единственная настоящая проверка. У Qwen3 есть QK-norm (в Attention она есть, но Block её не включает), а размерность головы задана отдельно: N·H ≠ D. Проще начать с модели архитектуры Llama, где H = D/N и QK-norm нет. Тесты tests/test_model.py импортируют эталон напрямую: для своей модели скопируй их и поменяй импорт.

Трек E стартует здесь и идёт до конца программы: каждую пятницу с пустого файла пишешь трансформер на время. Цель: уйти с ~90 минут до ~25. Разбор 57 интервью, с которым сверялась программа, говорит прямо: имплементация и отладка трансформера всплывают на интервью так часто, что терять на них баллы просто нельзя.

Математика (трек D): D7: рост дисперсии в residual stream и выбор масштаба инициализации; D8: ранг матрицы внимания, почему softmax поднимает ранг QKᵀ, а линейное внимание нет.

Интервью-вопрос недели: «Твои логиты расходятся с эталоном на 1e-2. Как будешь искать?» Структура на 3 минуты: те же id и токенизатор → fp32 → хуки, первый расходящийся слой → внутри слоя: норма (ε, fp32), q и k после RoPE (соглашение о парах), маска, порядок repeat_kv, гейт SwiGLU → вернуть исходную точность и назвать ожидаемый допуск.

Источники: Radford et al., GPT-2 (2019), масштабированная инициализация; Touvron et al., LLaMA (2023).

Результаты недели

  • Могу написать полный forward трансформера и загрузить в него веса реальной модели с совпадением логитов.
  • Могу найти причину расхождения логитов, сверяя активации слой за слоем.
  • Могу объяснить, зачем уменьшают инициализацию выходных проекций residual-веток с ростом глубины.
  • Могу написать трансформер с пустого файла и зафиксировать своё стартовое время для трека E.

Самопроверка

  1. Перечисли по порядку все операции forward pass с формой тензора после каждой.
  2. Что станет с дисперсией residual stream после L слоёв без scaled init?
  3. Почему в современных LLM почти нет dropout?

В приложении у недели есть навыки для самооценки, вопросы с проверкой ответа, задачи с кодом на Python и тьютор по материалам курса.

Учиться в приложении: тьютор, задачи с кодом
← НазадНеделя 7. Внимание Дальше →Неделя 9. Бухгалтерия: параметры, FLOPs, память

С нуля
С нуля: курс по LLM

  • Главная
  • Программа курса
  • Приложение
  • Конфиденциальность
  • Условия

Текст курса распространяется по лицензии CC BY-NC-SA 4.0, код nanolm по лицензии Apache-2.0.