Перейти к содержимому
Ступень 44.5 Архитектура трансформера

Современный блок LLM — RMSNorm, SwiGLU, GQA

Чем блок Llama, Qwen и DeepSeek отличается от блока GPT-2 — RMSNorm, SwiGLU, RoPE, GQA, QK-Norm, отсутствие смещений, — зачем каждое изменение и как читать конфиги реальных моделей.

УРОКstage-4/4-5-transformer/03-modern-block.mdx1 ч 50 минУрок 3 из 4
После урока вы сможете
  • Перечислить, чем блок Llama и Qwen отличается от блока GPT-2, и объяснить, зачем каждое изменение
  • Реализовать RMSNorm и SwiGLU и объяснить, откуда берётся скрытый размер 83d\tfrac{8}{3}d
  • Собрать современный блок на PyTorch — RMSNorm, GQA с QK-Norm и RoPE, SwiGLU, без смещений
  • Пересчитать по конфигу число параметров Llama 3 8B, Qwen3-8B и DeepSeek-V3

Блок GPT-2 из первого урока модуля — это 2019 год. С тех пор LLM выросли в сотни раз, а блок внешне почти не изменился: всё та же Pre-Norm схема «норма → внимание → сложение → норма → FFN → сложение». Но если открыть код Llama 3, Qwen3 или MiniMind, каждая деталь внутри окажется другой. Нормализация другая, FFN другой, позиции переехали внутрь внимания, у внимания меньше ключей, из линейных слоёв пропали смещения.

Каждое изменение — небольшой, проверенный экспериментами выигрыш в стабильности, скорости или качестве. Вместе они и составляют «современный блок», на котором построены почти все открытые модели 2023–2026 годов. В этом уроке разберём изменения по одному, соберём блок на PyTorch и научимся читать config.json настоящих моделей — от GPT-2 до DeepSeek-V3.

flowchart LR
subgraph G["Блок GPT-2 (2019)"]
  g1["LayerNorm"] --> g2["MHA, 12 голов<br/>со смещениями"] --> g3(("+"))
  g3 --> g4["LayerNorm"] --> g5["FFN: d → 4d → d<br/>GELU"] --> g6(("+"))
end
subgraph M["Блок Llama 3 / Qwen3 (2024–2025)"]
  m1["RMSNorm"] --> m2["GQA: 32 Q / 8 KV<br/>RoPE, без bias<br/>(у Qwen3 ещё QK-Norm)"] --> m3(("+"))
  m3 --> m4["RMSNorm"] --> m5["SwiGLU<br/>3 матрицы, без bias"] --> m6(("+"))
end
G -.-> M
Скелет тот же — Pre-Norm и два подслоя с остаточными связями. Поменялась начинка каждой детали.
ДетальGPT-2 (2019)Llama 3, Qwen3 (2024–2025)Что даёт
НормализацияLayerNorm, Pre-NormRMSNormRMSNorm (нормализация по среднеквадратичному)EN root mean square layer normalization (RMSNorm)x / sqrt(mean(x²) + ε) · g — LayerNorm без вычитания среднего и без сдвига β (Zhang, Sennrich, 2019). Стандарт Llama, Qwen, DeepSeek, MiniMind., Pre-Normдешевле, качество то же
Позицииобучаемая таблица на входеротационные позиционные эмбеддингиротационные позиционные эмбеддинги (RoPE)EN rotary position embedding (RoPE)RoPE предложен в Китае (Су Цзяньлинь и соавторы, компания Zhuiyi). Сейчас используется почти во всех LLM. в каждом слоеотносительность, растяжимый контекст
ВниманиеMHAвнимание с группами запросоввнимание с группами запросов (GQA)EN grouped-query attention (GQA)Головы запросов делятся на G групп, у каждой группы своя пара K/V. G = H — это MHA, G = 1 — MQA. Стандарт Llama 3 и Qwen (Ainslie и др., 2023). (в Qwen3 ещё QK-нормализацияQK-нормализация (QK-Norm)EN QK-NormНормализация (обычно RMSNorm) запросов и ключей перед скалярным произведением. Ограничивает рост attention-логитов; используется, например, в Qwen3.)KV-кэш в 4 раза меньше, стабильнее логиты
FFNGELU, 4d4dSwiGLUSwiGLUEN SwiGLU (Swish-gated linear unit)FFN современных LLM: down(SiLU(gate(x)) · up(x)), три матрицы без смещений; скрытый размер обычно ≈ 8d/3 и больше (Shazeer, 2020)., 3d3d–3.5d3.5dлучше качество при том же бюджете
Смещения (bias)во всех линейных слоях и нормахнигдестабильность обучения, проще код
Dropout0.10на претрейне нет переобучения
Эмбеддинги и LM-головаобщиеу больших моделей раздельныегибкость; у маленьких — общие ради экономии

Позиции (RoPE) вы разобрали в прошлом уроке. Остальное — ниже, по порядку: нормализация, FFN, внимание с GQA и QK-Norm, мелкие детали.

С RMSNorm вы уже встречались в уроке 3.4.1, где его сравнивали с BatchNorm и LayerNorm. Напомним главное и посмотрим, как он живёт внутри блока.

LayerNorm делает две вещи: центрирует вектор токена (вычитает среднее μ\mu) и масштабирует (делит на σ\sigma), а потом применяет обучаемые γ\gamma и β\beta. Чжан и Зеннрих (2019) предположили, что пользу приносит в основном масштабирование: оно делает слой нечувствительным к общему «размаху» входа. Центрирование можно выбросить:

RMSNorm(x)=xRMS(x)⊙g,RMS(x)=1d∑i=1dxi2+ε\mathrm{RMSNorm}(x) = \frac{x}{\mathrm{RMS}(x)} \odot g, \qquad \mathrm{RMS}(x) = \sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \varepsilon}

Что здесь написано: считаем среднеквадратичное значение компонент вектора (root mean square), делим на него вектор и умножаем поэлементно на обучаемый масштаб gg (инициализируется единицами). Ни среднего, ни сдвига β\beta.

Сравним на том же векторе, что в первом уроке, x=(2,4,6,8)x = (2, 4, 6, 8):

Что считаемРезультат
LayerNormμ=5\mu = 5, σ≈2.236\sigma \approx 2.236(−1.342, −0.447, 0.447, 1.342)(-1.342,\ -0.447,\ 0.447,\ 1.342)
RMSNormRMS=(4+16+36+64)/4≈5.477\mathrm{RMS} = \sqrt{(4 + 16 + 36 + 64)/4} \approx 5.477(0.365, 0.730, 1.095, 1.461)(0.365,\ 0.730,\ 1.095,\ 1.461)

RMSNorm не сдвигает вектор к нулю: положительные компоненты остаются положительными. Но длина приведена к одному масштабу (d\sqrt{d}), а это главное.

Зачем. Одна редукция (проход по вектору с суммированием) вместо двух — у LayerNorm это среднее и дисперсия — и на один вектор параметров меньше. По данным авторов статьи, RMSNorm сокращал время работы на 7–64% в зависимости от модели при сопоставимом качестве. Для LLM нормализация — малая доля FLOPs, но она выполняется 2N+12N + 1 раз за проход и упирается в пропускную способность памятипропускная способность памятиEN memory bandwidthСколько байт в секунду память отдаёт вычислителю. RTX 4080 — около 717 ГБ/с, H100 — 3.35 ТБ/с, двухканальная DDR5 — около 90 ГБ/с. Главное число для скорости decode.: GPU больше ждёт, пока числа прочитаются из видеопамяти, чем считает (подробно — в уроке 7.1.1). Так что простота окупается. Ещё T5 (2019) использовал нормализацию без среднего и смещения, затем LLaMA (2023) сделала RMSNorm стандартом.

Обычный FFN пропускает вектор через одну нелинейность: φ(xW1)W2\varphi(xW_1)W_2. В вентильном линейном блокеGLU (вентильный линейный блок)EN gated linear unit (GLU)Две параллельные проекции, одна из которых через нелинейность работает «вентилем» для другой: f(xW)·(xV). Варианты: ReGLU, GEGLU, SwiGLU. (GLU, Dauphin и соавт., 2017) две параллельные проекции: одна несёт содержание, другая через нелинейность решает, сколько содержания пропустить по каждой координате:

GLU(x)=φ(xWgate)⊙(xWup)\mathrm{GLU}(x) = \varphi(xW_{\text{gate}}) \odot (xW_{\text{up}})

Знак ⊙\odot — поэлементное умножение. Если вентиль по какой-то координате близок к нулю, эта координата «закрыта». В исходном GLU вентилем была сигмоида σ\sigma, позже Шазир (2020) перебрал другие нелинейности. В SwiGLU вентилем служит SiLUSiLU (Swish)EN sigmoid linear unit (SiLU), SwishSiLU(z) = z·σ(z). Внутри SwiGLU — в FFN Llama, Qwen, DeepSeek. Минимум ≈ −0.278 при z ≈ −1.28. (она же Swish):

SiLU(z)=z⋅σ(z)=z1+e−z\mathrm{SiLU}(z) = z \cdot \sigma(z) = \frac{z}{1 + e^{-z}}
zz−2-2−1-1001122
SiLU−0.238-0.238−0.269-0.26900.7311.762
GELU (для сравнения)−0.046-0.046−0.159-0.15900.8411.954

SiLU похожа на GELU: гладкая, большие положительные числа пропускает почти без изменений, отрицательные гасит, минимум ≈−0.278\approx -0.278 при z≈−1.28z \approx -1.28. Полный блок:

FFNSwiGLU(x)=(SiLU(xWgate)⊙xWup) Wdown\mathrm{FFN}_{\text{SwiGLU}}(x) = \Big(\mathrm{SiLU}(xW_{\text{gate}}) \odot xW_{\text{up}}\Big)\, W_{\text{down}}

Три матрицы: Wgate,Wup∈Rd×hW_{\text{gate}}, W_{\text{up}} \in \mathbb{R}^{d \times h} и Wdown∈Rh×dW_{\text{down}} \in \mathbb{R}^{h \times d}, без смещений. В коде они так и называются: gate_proj, up_proj, down_proj.

Почему лучше. Ноам Шазир (2020) сравнил варианты GLU в FFN трансформера T5 при равном числе параметров: GEGLU и SwiGLU дали лучшую perplexity и лучшие результаты на дообучении, чем ReLU и GELU. Объяснение он дал знаменитое: «мы не предлагаем объяснения, почему эти архитектуры работают; их успех, как и всё остальное, мы приписываем божественной благосклонности». Интуиция всё же есть: произведение двух проекций — это мультипликативное взаимодействие признаков, функция второго порядка от входа. Один слой может выразить больше.

Классический FFN с 4d4d стоит 2⋅d⋅4d=8d22 \cdot d \cdot 4d = 8d^2 параметров. У SwiGLU три матрицы d×hd \times h. Чтобы бюджет не изменился:

3dh=8d2⇒h=83d≈2.67d3dh = 8d^2 \quad \Rightarrow \quad h = \tfrac{8}{3}d \approx 2.67d

При d=768d = 768 это ровно 2048. LLaMA-7B (2023) взяла 83⋅4096≈10 923\tfrac{8}{3} \cdot 4096 \approx 10\,923 и округлила вверх до кратного 256 — получилось 11 008. Дальше компании стали отступать от правила в обе стороны:

МодельddСкрытый размер hhh/dh / d
LLaMA-7B (2023)409611 0082.69
Llama 3 8B409614 3363.5
Qwen3-8B409612 2883.0
DeepSeek-V3, плотные слои716818 4322.57
MiniMind (minimind-3)768⌈768π/64⌉⋅64=2432\lceil 768\pi / 64 \rceil \cdot 64 = 24323.17

Числа в последнем столбце — это решение «сколько параметров отдать FFN, а сколько вниманию и глубине», его подбирают экспериментально. MiniMind выбрала πd\pi d — красивое число, не хуже других.

В уроке 4.4.3 вы видели мост от MHA к GQA, здесь он становится частью блока. Напомним суть. При генерации модель хранит в KV-кэше ключи и значения всех прошлых токенов (подробно — в уроке 4.6.4), и в MHA у каждой головы запросов своя пара KK, VV. GQA (grouped-query attention, Ainslie и соавт., 2023) делит головы запросов на группы, и каждая группа смотрит в одну общую пару KK, VV. В Llama 3 8B и Qwen3-8B 32 головы запросов и 8 KV-голов: четыре запроса — один набор ключей.

Экономию вы уже считали в уроке 4.4.3: у Llama 3 8B KV-кэш — 128 КиБ на токен вместо 512 КиБ у MHA, то есть 4 ГиБ вместо 16 ГиБ на запрос длиной 32K токенов. Заодно худеют и проекции: WKW_K и WVW_V занимают на слой 2⋅4096⋅1024≈8.42 \cdot 4096 \cdot 1024 \approx 8.4 млн параметров вместо 2⋅4096⋅4096≈33.62 \cdot 4096 \cdot 4096 \approx 33.6 млн.

Запросов по-прежнему 32: модель задаёт столько же разных «вопросов», просто отвечают на них 8 наборов ключей и значений. По данным авторов GQA, качество получается близким к MHA, а скорость генерации — к MQA, где пара KK, VV вообще одна на все головы.

В коде GQA — одна строчка: перед вниманием каждую KV-голову повторяют столько раз, сколько запросов в группе, чтобы формы совпали (repeat_kv в Hugging Face и MiniMind, repeat_interleave в нашем блоке ниже). В config.json число KV-голов лежит в поле num_key_value_heads. Компромисс «качество против памяти» с таблицами для Llama и Qwen разобран в модуле 5.2.

Смещения убрали. В PaLM (Google, 2022) отказались от всех bias в линейных слоях и нормах и отметили, что это повышает стабильность обучения больших моделей. Параметров смещения почти не занимают (меньше 0.1%), так что потеря выразительности ничтожна, а код и шардирование (разрезание весов между видеокартами, модуль 5.4) проще. LLaMA повторила этот выбор, за ней — почти все. Примечательное исключение — Qwen2 и Qwen2.5: в них bias остался в проекциях QQ, KK, VV (и только там).

QK-Norm в Qwen3. В Qwen3 эти смещения убрали и добавили QK-Norm: запрос и ключ каждой головы перед RoPE проходят через RMSNorm по размеру головы (128 чисел; обучаемый масштаб свой у qq и у kk, общий для всех голов). Зачем: оценка внимания — скалярное произведение q⋅kq \cdot k, и если нормы qq и kk при обучении разрастаются, логиты взлетают, softmax превращается в one-hot, градиенты становятся резкими, а loss — скачущим (вспомните урок 4.4.2 про d\sqrt{d}). После нормализации длины qq и kk ограничены обучаемыми масштабами, и логиты не могут убежать. Отчёт Qwen3 прямо называет цель: стабильное обучение. Тот же приём есть в MiniMind.

  • Dropout = 0. GPT-2 использовала dropout 0.1. Современный претрейн идёт примерно одну эпоху по триллионам токенов: модель почти не видит пример дважды, и переобучаться ей не на чем. Dropout только тратил бы сигнал. В конфигах Qwen3 attention_dropout = 0.0, а в исходном коде Llama от Meta dropout нет совсем.
  • Связанные эмбеддингисвязывание весов (общие эмбеддинги входа и выхода)EN weight tying (tied embeddings)Одна и та же матрица служит эмбеддингом токенов и LM head. Её градиент — сумма вкладов от обоих мест. В Hugging Face — флаг tie_word_embeddings.. У маленьких моделей матрица эмбеддингов огромна относительно остального: у Qwen3-0.6B она занимает 26% всех параметров, и вторая такая же матрица для LM-головы была бы расточительством. Поэтому Qwen3-0.6B, 1.7B, 4B, MiniMind и GPT-2 делят одну матрицу на вход и выход, а Llama 3 8B, Qwen3-8B и DeepSeek-V3 держат две.
  • Финальная RMSNorm перед LM-головой — как и положено в Pre-Norm (урок 4.5.1).
GPT-2 smallLlama 3 8BQwen3-8BDeepSeek-V3
Год2019202420252024
Параметров124 млн8.03 млрд8.19 млрд671 млрд (37 млрд активныхактивные параметрыEN activated (active) parametersСколько параметров MoE-модели реально участвует в обработке одного токена. «A22B» в названии Qwen3-235B-A22B — 22 млрд активных из 235 млрд. — работают на каждом токене, модуль 5.3)
Слоёв × ширина12 × 76832 × 409636 × 409661 × 7168
Головы Q / KV12 / 1232 / 832 / 8128, MLA (латент 512 + 64)
Размер головы64128128128 (+ 64 под RoPE)
ВниманиеMHAGQAGQA + QK-Normмногоголовое латентное вниманиемногоголовое латентное внимание (MLA)EN multi-head latent attention (MLA)Предложено DeepSeek в DeepSeek-V2 (2024).
FFNGELU, 3072SwiGLU, 14 336SwiGLU, 12 288SwiGLU: 3 плотных слоя по 18 432, затем MoE — 1 общий + 256 экспертов по 2048, top-8
НормализацияLayerNormRMSNorm, ε=10−5\varepsilon = 10^{-5}RMSNorm, ε=10−6\varepsilon = 10^{-6}RMSNorm, ε=10−6\varepsilon = 10^{-6}
Позициитаблица, 1024RoPE, b=500 000b = 500\,000RoPE, b=106b = 10^6развязанный RoPE, b=104b = 10^4 + YaRN
Контекст10248K (Llama 3.1 — 128K)32K (128K с YaRN)128K
Словарь50 257128 256151 936129 280
Смещениявезденетнетнет
Эмбеддинги = головаданетнетнет

Что видно из таблицы. Llama 3 8B и Qwen3-8B — почти близнецы: одинаковые ширина, число голов и тип нормализации; Qwen глубже (36 слоёв против 32), но с FFN поуже, словарь больше, добавлен QK-Norm. DeepSeek-V3 оставила тот же Pre-Norm скелет с RMSNorm и SwiGLU, но заменила обе половины блока: внимание — на MLA (модуль 5.2), плотный FFN — на смесь экспертов DeepSeekMoE (модуль 5.3). Блок стал конструктором: скелет общий, детали меняются.

Соберём блок в стиле Qwen3: RMSNorm, GQA с QK-Norm и RoPE (раскладка half), SwiGLU, никаких смещений. Возьмём ширину GPT-2 small (d=768d = 768), чтобы сравнить число параметров с блоком из первого урока. Скрытый размер SwiGLU — 83⋅768=2048\tfrac{8}{3} \cdot 768 = 2048, KV-голов 4 на 12 голов запросов.

import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
class RMSNorm(nn.Module):
"""y = x / sqrt(mean(x²) + eps) * g — без вычитания среднего и без сдвига."""
def __init__(self, dim, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim)) # g, обучаемый масштаб
def forward(self, x):
x32 = x.float() # норму считаем в FP32 даже при BF16-обучении
# rsqrt(a) = 1 / sqrt(a); mean(-1) — среднее квадратов по последней оси (по координатам вектора токена)
y = x32 * torch.rsqrt(x32.pow(2).mean(-1, keepdim=True) + self.eps)
return (self.weight * y).type_as(x) # type_as — вернуть исходный тип (например, BF16)
class SwiGLU(nn.Module):
"""FFN современной LLM: down( SiLU(gate(x)) * up(x) ). Три матрицы, без смещений."""
def __init__(self, dim, hidden):
super().__init__()
self.gate = nn.Linear(dim, hidden, bias=False)
self.up = nn.Linear(dim, hidden, bias=False)
self.down = nn.Linear(hidden, dim, bias=False)
def forward(self, x):
return self.down(F.silu(self.gate(x)) * self.up(x)) # «*» — поэлементное умножение, это ⊙ из формулы
def rope(x, base):
"""RoPE в раскладке half (как в HF/Qwen). x: (B, H, T, d)."""
T, d = x.shape[-2], x.shape[-1]
theta = base ** (-torch.arange(0, d, 2, device=x.device).float() / d)
ang = torch.outer(torch.arange(T, device=x.device).float(), theta) # (T, d/2)
cos, sin = ang.cos(), ang.sin()
x1, x2 = x.float().chunk(2, dim=-1)
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1).type_as(x)
class GQAttention(nn.Module):
"""GQA + QK-Norm + RoPE, без смещений — как в Qwen3."""
def __init__(self, dim, n_heads, n_kv_heads, head_dim, rope_base):
super().__init__()
self.h, self.kv, self.hd, self.base = n_heads, n_kv_heads, head_dim, rope_base
self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False)
self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False)
self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False)
self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False)
self.q_norm = RMSNorm(head_dim) # QK-Norm: RMSNorm по размеру головы
self.k_norm = RMSNorm(head_dim)
def forward(self, x):
B, T, _ = x.shape
q = self.q_norm(self.q_proj(x).view(B, T, self.h, self.hd)).transpose(1, 2) # (B, H, T, hd)
k = self.k_norm(self.k_proj(x).view(B, T, self.kv, self.hd)).transpose(1, 2) # (B, KV, T, hd)
v = self.v_proj(x).view(B, T, self.kv, self.hd).transpose(1, 2)
q, k = rope(q, self.base), rope(k, self.base) # позиции — только в q и k
rep = self.h // self.kv # сколько Q-голов делят одну KV-голову
# repeat_interleave копирует каждую KV-голову rep раз подряд: [k0, k1] → [k0, k0, k0, k1, k1, k1]
k, v = k.repeat_interleave(rep, dim=1), v.repeat_interleave(rep, dim=1)
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
return self.o_proj(y.transpose(1, 2).reshape(B, T, self.h * self.hd))
class ModernBlock(nn.Module):
"""Pre-Norm блок Llama/Qwen: RMSNorm → GQA → +, RMSNorm → SwiGLU → +."""
def __init__(self, dim, n_heads, n_kv_heads, head_dim, hidden, rope_base=1e6):
super().__init__()
self.input_layernorm = RMSNorm(dim)
self.self_attn = GQAttention(dim, n_heads, n_kv_heads, head_dim, rope_base)
self.post_attention_layernorm = RMSNorm(dim)
self.mlp = SwiGLU(dim, hidden)
def forward(self, x):
x = x + self.self_attn(self.input_layernorm(x))
return x + self.mlp(self.post_attention_layernorm(x))
count = lambda m: sum(p.numel() for p in m.parameters())
# Тот же размер, что у блока GPT-2 small (d = 768), но «по-современному»
blk = ModernBlock(dim=768, n_heads=12, n_kv_heads=4, head_dim=64, hidden=2048)
print(f"внимание: {count(blk.self_attn):>9,}")
print(f"SwiGLU: {count(blk.mlp):>9,}")
print(f"нормы: {count(blk.input_layernorm) + count(blk.post_attention_layernorm):>9,}")
print(f"блок: {count(blk):>9,} (у блока GPT-2 small было 7,087,872)")
x = torch.randn(2, 10, 768)
print("форма:", tuple(blk(x).shape))
# Наш RMSNorm совпадает со встроенным (PyTorch ≥ 2.4)
n = RMSNorm(768)
print("RMSNorm == F.rms_norm:", torch.allclose(n(x), F.rms_norm(x, (768,), eps=1e-6), atol=1e-6))
внимание: 1,572,992
SwiGLU: 4,718,592
нормы: 1,536
блок: 6,293,120 (у блока GPT-2 small было 7,087,872)
форма: (2, 10, 768)
RMSNorm == F.rms_norm: True

Разберём числа. SwiGLU стоит 4 718 592 — почти ровно столько, сколько FFN GPT-2 без смещений (8d2=4 718 5928d^2 = 4\,718\,592): правило 83d\tfrac{8}{3}d сработало точно. Внимание подешевело на треть: у KK и VV по 4 головы вместо 12. Последние 128 параметров внимания — масштабы QK-Norm (2×642 \times 64). Имена модулей (input_layernorm, self_attn.q_proj, mlp.gate…) мы взяли близкими к Hugging Face, чтобы потом было легко читать настоящий код.

Главный навык этого урока — открыть config.json любой модели и понять, сколько в ней параметров и где они лежат. Функция ниже работает на чистом Python:

def llama_like(vocab, d, layers, n_heads, n_kv, head_dim, hidden, tied, qk_norm=False):
# W_Q: d × (n_heads·head_dim); W_K и W_V: d × (n_kv·head_dim) каждая; W_O: (n_heads·head_dim) × d
attn = d * n_heads * head_dim + 2 * d * n_kv * head_dim + n_heads * head_dim * d
if qk_norm: # масштабы двух RMSNorm размера head_dim
attn += 2 * head_dim
ffn = 3 * d * hidden # SwiGLU: gate, up, down
block = attn + ffn + 2 * d # + два RMSNorm
emb = vocab * d * (1 if tied else 2) # эмбеддинги и LM-голова
return layers * block + emb + d # + финальный RMSNorm
def gpt2(vocab=50257, d=768, layers=12, ctx=1024):
# слагаемые: qkv (веса + смещения), W_O, FFN up, FFN down, два LayerNorm (γ и β по d чисел каждый)
block = (d * 3 * d + 3 * d) + (d * d + d) + (d * 4 * d + 4 * d) + (4 * d * d + d) + 4 * d
return layers * block + vocab * d + ctx * d + 2 * d
print(f"GPT-2 small: {gpt2():,}")
print(f"GPT-2 XL: {gpt2(d=1600, layers=48):,}")
print(f"Llama 3 8B: {llama_like(128256, 4096, 32, 32, 8, 128, 14336, tied=False):,}")
print(f"Qwen3-8B: {llama_like(151936, 4096, 36, 32, 8, 128, 12288, tied=False, qk_norm=True):,}")
print(f"Qwen3-0.6B: {llama_like(151936, 1024, 28, 16, 8, 128, 3072, tied=True, qk_norm=True):,}")
ne = llama_like(151936, 4096, 36, 32, 8, 128, 12288, tied=False, qk_norm=True) - 2 * 151936 * 4096
print(f"Qwen3-8B без эмбеддингов: {ne:,}")
# DeepSeek-V3: MLA + MoE. Формулу внимания MLA (низкоранговое сжатие Q и KV) разберём в уроке 5.2.2,
# а MoE — в модуле 5.3; пока просто убедитесь, что сумма сходится с официальными 671B / 37B.
d, L, H, V = 7168, 61, 128, 129280
qr, kvr, nope, rd, vd = 1536, 512, 128, 64, 128 # ранги сжатия и размеры частей головы
attn = d * qr + qr + qr * H * (nope + rd) + d * (kvr + rd) + kvr + kvr * H * (nope + vd) + H * vd * d
dense = 3 * d * 18432 # первые 3 слоя — плотный SwiGLU
moe = 257 * 3 * d * 2048 + 256 * d + 256 # 256 экспертов + 1 общий + роутер
total = L * (attn + 2 * d) + 3 * dense + 58 * moe + 2 * V * d + d
active = L * (attn + 2 * d) + 3 * dense + 58 * (9 * 3 * d * 2048 + 256 * d) + 2 * V * d + d
print(f"DeepSeek-V3: {total/1e9:.1f}B всего, {active/1e9:.1f}B активных; внимание на слой {attn/1e6:.1f}M")
GPT-2 small: 124,439,808
GPT-2 XL: 1,557,611,200
Llama 3 8B: 8,030,261,248
Qwen3-8B: 8,190,735,360
Qwen3-0.6B: 596,049,920
Qwen3-8B без эмбеддингов: 6,946,075,648
DeepSeek-V3: 671.0B всего, 37.6B активных; внимание на слой 187.1M

Сверьте с официальными карточками: Qwen3-8B — «8.2B, из них 6.95B без эмбеддингов», Qwen3-0.6B — «0.6B», DeepSeek-V3 — «671B всего, 37B активных на токен». Все опубликованные цифры сошлись, значит, вы правильно понимаете каждую матрицу. Точные суммы можно сверить с тем, что напечатает transformers для настоящих весов (раздел ниже). В DeepSeek-V3 активны на токен 8 маршрутизируемых экспертов и 1 общий — отсюда множитель 9 (подробно — в модуле 5.3).

Проверь себя6 вопросов
1 Вычислите первую компоненту RMSNorm(x)\mathrm{RMSNorm}(x) для x=(1,−1,3,1)x = (1, -1, 3, 1) при g=1g = 1 и ε=0\varepsilon = 0. Округлите до двух знаков.
2 Каким должен быть скрытый размер SwiGLU hh при d=1536d = 1536, чтобы число параметров совпало с классическим FFN d→4d→dd \to 4d \to d (без смещений)?
3 Что изменилось в блоке Llama 3 / Qwen3 по сравнению с GPT-2?Можно выбрать несколько ответов
4 Что делает QK-Norm в Qwen3?
5 Сколько миллионов параметров в SwiGLU одного слоя Qwen3-8B (d=4096d = 4096, h=12 288h = 12\,288)? Округлите до целого.
6 Почему Qwen3-0.6B делит одну матрицу между эмбеддингами и LM-головой, а Qwen3-8B — нет?
  • Скелет блока со времён GPT-2 тот же: Pre-Norm, внимание и FFN с остаточными связями. Изменилась начинка.
  • RMSNorm — нормализация без центрирования и без сдвига: x/RMS(x)⊙gx / \mathrm{RMS}(x) \odot g. Дешевле LayerNorm, качество то же; считать в float32.
  • SwiGLU — FFN с вентилем: Wdown(SiLU(xWgate)⊙xWup)W_{\text{down}}(\mathrm{SiLU}(xW_{\text{gate}}) \odot xW_{\text{up}}), три матрицы, скрытый размер от 83d\tfrac{8}{3}d до 3.5d3.5d.
  • RoPE в каждом слое, GQA вместо MHA, нет смещений и dropout, у Qwen3 — QK-Norm для стабильности логитов.
  • По конфигу можно пересчитать модель до последнего параметра: Llama 3 8B — 8 030 261 248, Qwen3-8B — 8 190 735 360, DeepSeek-V3 — 671 млрд (37 млрд активных).

Дальше: прежде чем собирать GPT, посмотрим на другую ветку семейства — энкодеры вроде BERT — и освоим рабочий цикл Hugging Face, на котором держатся все практики «На вашей машине» — Энкодеры (BERT) и рабочий цикл Hugging Face.