Современный блок LLM — RMSNorm, SwiGLU, GQA
Чем блок Llama, Qwen и DeepSeek отличается от блока GPT-2 — RMSNorm, SwiGLU, RoPE, GQA, QK-Norm, отсутствие смещений, — зачем каждое изменение и как читать конфиги реальных моделей.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Блок 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Карта изменений
Заголовок раздела «Карта изменений»| Деталь | GPT-2 (2019) | Llama 3, Qwen3 (2024–2025) | Что даёт |
|---|---|---|---|
| Нормализация | LayerNorm, Pre-Norm | RMSNormRMSNorm (нормализация по среднеквадратичному)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 раза меньше, стабильнее логиты |
| FFN | GELU, | SwiGLUSwiGLUEN SwiGLU (Swish-gated linear unit)FFN современных LLM: down(SiLU(gate(x)) · up(x)), три матрицы без смещений; скрытый размер обычно ≈ 8d/3 и больше (Shazeer, 2020)., – | лучше качество при том же бюджете |
| Смещения (bias) | во всех линейных слоях и нормах | нигде | стабильность обучения, проще код |
| Dropout | 0.1 | 0 | на претрейне нет переобучения |
| Эмбеддинги и LM-голова | общие | у больших моделей раздельные | гибкость; у маленьких — общие ради экономии |
Позиции (RoPE) вы разобрали в прошлом уроке. Остальное — ниже, по порядку: нормализация, FFN, внимание с GQA и QK-Norm, мелкие детали.
RMSNorm: нормализация без центрирования
Заголовок раздела «RMSNorm: нормализация без центрирования»С RMSNorm вы уже встречались в уроке 3.4.1, где его сравнивали с BatchNorm и LayerNorm. Напомним главное и посмотрим, как он живёт внутри блока.
LayerNorm делает две вещи: центрирует вектор токена (вычитает среднее ) и масштабирует (делит на ), а потом применяет обучаемые и . Чжан и Зеннрих (2019) предположили, что пользу приносит в основном масштабирование: оно делает слой нечувствительным к общему «размаху» входа. Центрирование можно выбросить:
Что здесь написано: считаем среднеквадратичное значение компонент вектора (root mean square), делим на него вектор и умножаем поэлементно на обучаемый масштаб (инициализируется единицами). Ни среднего, ни сдвига .
Сравним на том же векторе, что в первом уроке, :
| Что считаем | Результат | |
|---|---|---|
| LayerNorm | , | |
| RMSNorm |
RMSNorm не сдвигает вектор к нулю: положительные компоненты остаются положительными. Но длина приведена к одному масштабу (), а это главное.
Зачем. Одна редукция (проход по вектору с суммированием) вместо двух — у LayerNorm это среднее и дисперсия — и на один вектор параметров меньше. По данным авторов статьи, RMSNorm сокращал время работы на 7–64% в зависимости от модели при сопоставимом качестве. Для LLM нормализация — малая доля FLOPs, но она выполняется раз за проход и упирается в пропускную способность памятипропускная способность памятиEN memory bandwidthСколько байт в секунду память отдаёт вычислителю. RTX 4080 — около 717 ГБ/с, H100 — 3.35 ТБ/с, двухканальная DDR5 — около 90 ГБ/с. Главное число для скорости decode.: GPU больше ждёт, пока числа прочитаются из видеопамяти, чем считает (подробно — в уроке 7.1.1). Так что простота окупается. Ещё T5 (2019) использовал нормализацию без среднего и смещения, затем LLaMA (2023) сделала RMSNorm стандартом.
SwiGLU: FFN с вентилем
Заголовок раздела «SwiGLU: FFN с вентилем»Идея вентиля
Заголовок раздела «Идея вентиля»Обычный FFN пропускает вектор через одну нелинейность: . В вентильном линейном блокеGLU (вентильный линейный блок)EN gated linear unit (GLU)Две параллельные проекции, одна из которых через нелинейность работает «вентилем» для другой: f(xW)·(xV). Варианты: ReGLU, GEGLU, SwiGLU. (GLU, Dauphin и соавт., 2017) две параллельные проекции: одна несёт содержание, другая через нелинейность решает, сколько содержания пропустить по каждой координате:
Знак — поэлементное умножение. Если вентиль по какой-то координате близок к нулю, эта координата «закрыта». В исходном GLU вентилем была сигмоида , позже Шазир (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 | 0 | 0.731 | 1.762 | ||
| GELU (для сравнения) | 0 | 0.841 | 1.954 |
SiLU похожа на GELU: гладкая, большие положительные числа пропускает почти без изменений, отрицательные гасит, минимум при . Полный блок:
Три матрицы: и ,
без смещений. В коде они так и называются: gate_proj, up_proj, down_proj.
Почему лучше. Ноам Шазир (2020) сравнил варианты GLU в FFN трансформера T5 при равном числе параметров: GEGLU и SwiGLU дали лучшую perplexity и лучшие результаты на дообучении, чем ReLU и GELU. Объяснение он дал знаменитое: «мы не предлагаем объяснения, почему эти архитектуры работают; их успех, как и всё остальное, мы приписываем божественной благосклонности». Интуиция всё же есть: произведение двух проекций — это мультипликативное взаимодействие признаков, функция второго порядка от входа. Один слой может выразить больше.
Откуда 8/3 · d
Заголовок раздела «Откуда 8/3 · d»Классический FFN с стоит параметров. У SwiGLU три матрицы . Чтобы бюджет не изменился:
При это ровно 2048. LLaMA-7B (2023) взяла и округлила вверх до кратного 256 — получилось 11 008. Дальше компании стали отступать от правила в обе стороны:
| Модель | Скрытый размер | ||
|---|---|---|---|
| LLaMA-7B (2023) | 4096 | 11 008 | 2.69 |
| Llama 3 8B | 4096 | 14 336 | 3.5 |
| Qwen3-8B | 4096 | 12 288 | 3.0 |
| DeepSeek-V3, плотные слои | 7168 | 18 432 | 2.57 |
| MiniMind (minimind-3) | 768 | 3.17 |
Числа в последнем столбце — это решение «сколько параметров отдать FFN, а сколько вниманию и глубине», его подбирают экспериментально. MiniMind выбрала — красивое число, не хуже других.
GQA: одни ключи на группу голов
Заголовок раздела «GQA: одни ключи на группу голов»В уроке 4.4.3 вы видели мост от MHA к GQA, здесь он становится частью блока. Напомним суть. При генерации модель хранит в KV-кэше ключи и значения всех прошлых токенов (подробно — в уроке 4.6.4), и в MHA у каждой головы запросов своя пара , . GQA (grouped-query attention, Ainslie и соавт., 2023) делит головы запросов на группы, и каждая группа смотрит в одну общую пару , . В Llama 3 8B и Qwen3-8B 32 головы запросов и 8 KV-голов: четыре запроса — один набор ключей.
Экономию вы уже считали в уроке 4.4.3: у Llama 3 8B KV-кэш — 128 КиБ на токен вместо 512 КиБ у MHA, то есть 4 ГиБ вместо 16 ГиБ на запрос длиной 32K токенов. Заодно худеют и проекции: и занимают на слой млн параметров вместо млн.
Запросов по-прежнему 32: модель задаёт столько же разных «вопросов», просто отвечают на них 8 наборов ключей и значений. По данным авторов GQA, качество получается близким к MHA, а скорость генерации — к MQA, где пара , вообще одна на все головы.
В коде GQA — одна строчка: перед вниманием каждую KV-голову повторяют столько раз, сколько запросов в группе, чтобы
формы совпали (repeat_kv в Hugging Face и MiniMind, repeat_interleave в нашем блоке ниже). В config.json число
KV-голов лежит в поле num_key_value_heads. Компромисс «качество против памяти» с таблицами для Llama и Qwen разобран
в модуле 5.2.
Без смещений, с QK-Norm
Заголовок раздела «Без смещений, с QK-Norm»Смещения убрали. В PaLM (Google, 2022) отказались от всех bias в линейных слоях и нормах и отметили, что это повышает стабильность обучения больших моделей. Параметров смещения почти не занимают (меньше 0.1%), так что потеря выразительности ничтожна, а код и шардирование (разрезание весов между видеокартами, модуль 5.4) проще. LLaMA повторила этот выбор, за ней — почти все. Примечательное исключение — Qwen2 и Qwen2.5: в них bias остался в проекциях , , (и только там).
QK-Norm в Qwen3. В Qwen3 эти смещения убрали и добавили QK-Norm: запрос и ключ каждой головы перед RoPE проходят через RMSNorm по размеру головы (128 чисел; обучаемый масштаб свой у и у , общий для всех голов). Зачем: оценка внимания — скалярное произведение , и если нормы и при обучении разрастаются, логиты взлетают, softmax превращается в one-hot, градиенты становятся резкими, а loss — скачущим (вспомните урок 4.4.2 про ). После нормализации длины и ограничены обучаемыми масштабами, и логиты не могут убежать. Отчёт 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 → Llama 3 → Qwen3 → DeepSeek-V3
Заголовок раздела «Таблица: GPT-2 → Llama 3 → Qwen3 → DeepSeek-V3»| GPT-2 small | Llama 3 8B | Qwen3-8B | DeepSeek-V3 | |
|---|---|---|---|---|
| Год | 2019 | 2024 | 2025 | 2024 |
| Параметров | 124 млн | 8.03 млрд | 8.19 млрд | 671 млрд (37 млрд активныхактивные параметрыEN activated (active) parametersСколько параметров MoE-модели реально участвует в обработке одного токена. «A22B» в названии Qwen3-235B-A22B — 22 млрд активных из 235 млрд. — работают на каждом токене, модуль 5.3) |
| Слоёв × ширина | 12 × 768 | 32 × 4096 | 36 × 4096 | 61 × 7168 |
| Головы Q / KV | 12 / 12 | 32 / 8 | 32 / 8 | 128, MLA (латент 512 + 64) |
| Размер головы | 64 | 128 | 128 | 128 (+ 64 под RoPE) |
| Внимание | MHA | GQA | GQA + QK-Norm | многоголовое латентное вниманиемногоголовое латентное внимание (MLA)EN multi-head latent attention (MLA)Предложено DeepSeek в DeepSeek-V2 (2024). |
| FFN | GELU, 3072 | SwiGLU, 14 336 | SwiGLU, 12 288 | SwiGLU: 3 плотных слоя по 18 432, затем MoE — 1 общий + 256 экспертов по 2048, top-8 |
| Нормализация | LayerNorm | RMSNorm, | RMSNorm, | RMSNorm, |
| Позиции | таблица, 1024 | RoPE, | RoPE, | развязанный RoPE, + YaRN |
| Контекст | 1024 | 8K (Llama 3.1 — 128K) | 32K (128K с YaRN) | 128K |
| Словарь | 50 257 | 128 256 | 151 936 | 129 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). Блок стал конструктором: скелет общий, детали меняются.
Практика
Заголовок раздела «Практика»Современный блок на PyTorch
Заголовок раздела «Современный блок на PyTorch»Соберём блок в стиле Qwen3: RMSNorm, GQA с QK-Norm и RoPE (раскладка half), SwiGLU, никаких смещений. Возьмём ширину GPT-2 small (), чтобы сравнить число параметров с блоком из первого урока. Скрытый размер SwiGLU — , KV-голов 4 на 12 голов запросов.
import torchimport torch.nn as nnimport 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,992SwiGLU: 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 без смещений ():
правило сработало точно. Внимание подешевело на треть: у и по 4 головы вместо 12. Последние
128 параметров внимания — масштабы QK-Norm (). Имена модулей (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 * 4096print(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, 129280qr, 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 * ddense = 3 * d * 18432 # первые 3 слоя — плотный SwiGLUmoe = 257 * 3 * d * 2048 + 256 * d + 256 # 256 экспертов + 1 общий + роутерtotal = L * (attn + 2 * d) + 3 * dense + 58 * moe + 2 * V * d + dactive = L * (attn + 2 * d) + 3 * dense + 58 * (9 * 3 * d * 2048 + 256 * d) + 2 * V * d + dprint(f"DeepSeek-V3: {total/1e9:.1f}B всего, {active/1e9:.1f}B активных; внимание на слой {attn/1e6:.1f}M")GPT-2 small: 124,439,808GPT-2 XL: 1,557,611,200Llama 3 8B: 8,030,261,248Qwen3-8B: 8,190,735,360Qwen3-0.6B: 596,049,920Qwen3-8B без эмбеддингов: 6,946,075,648DeepSeek-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).
Проверь себя
Заголовок раздела «Проверь себя»- Скелет блока со времён GPT-2 тот же: Pre-Norm, внимание и FFN с остаточными связями. Изменилась начинка.
- RMSNorm — нормализация без центрирования и без сдвига: . Дешевле LayerNorm, качество то же; считать в float32.
- SwiGLU — FFN с вентилем: , три матрицы, скрытый размер от до .
- 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.