Перейти к содержимому
Ступень 55.3 Смесь экспертов (MoE)

DeepSeekMoE — мелкие и общие эксперты

Три идеи DeepSeek, которые превратили MoE из капризной архитектуры в рабочую лошадку — мелкозернистые эксперты, общие эксперты и балансировка смещениями без вспомогательного лосса, плюс как всё это раскладывают по сотням GPU.

УРОКstage-5/5-3-moe/02-deepseekmoe.mdx2 чУрок 2 из 2
После урока вы сможете
  • Объяснить, зачем DeepSeekMoE дробит экспертов и выделяет общих, и подтвердить это цифрами из статьи
  • Разобрать роутинг DeepSeek-V3 по официальному коду — сигмоида, группы, нормировка, масштаб
  • Реализовать балансировку без вспомогательного лосса через смещения и измерить MaxVio
  • Объяснить, как работает параллелизм экспертов и почему маршрут токена ограничивают несколькими узлами

В прошлом уроке у классической MoE обнаружились три болезни. Экспертов мало (8–16), поэтому каждому достаётся каша из разнородных знаний. Общие для всех токенов знания (грамматика, частые слова) дублируются в нескольких экспертах. А вспомогательный лосс, который держит баланс, мешает основному обучению.

DeepSeek за 2024 год предложила лекарство от каждой болезни. В январе вышла статья DeepSeekMoE с мелкими и общими экспертами, в августе — балансировка без вспомогательного лосса, в декабре всё это собрали в DeepSeek-V3. Сегодня по этим рецептам строят MoE-модели и другие лаборатории. В этом уроке разберём все три идеи и посмотрим, как модель с 256 экспертами раскладывают по сотням GPU.

Авторы DeepSeekMoE назвали две проблемы архитектуры:

  • Гибридность знаний (knowledge hybridity). Экспертов мало, и к каждому попадают очень разные токены. Эксперт вынужден хранить в одних параметрах, скажем, и математику, и кулинарию, и использовать эти знания одновременно ему трудно.
  • Избыточность знаний (knowledge redundancy). Токенам у разных экспертов нужно и общее знание. В итоге несколько экспертов учат одно и то же, и параметры тратятся впустую.

Цель DeepSeekMoE — «предельная специализация экспертов»: каждый эксперт знает своё, и знания не пересекаются.

Возьмём классическую MoE: NN экспертов, выбираем KK. Разрежем каждого эксперта на mm маленьких, уменьшив промежуточную размерность FFN в mm раз, и будем выбирать mKmK из mNmN. Параметров столько же, FLOPs на токен столько же. Это мелкозернистые экспертымелкозернистые экспертыEN fine-grained expertsИдея DeepSeekMoE — разрезать каждого эксперта на m маленьких и активировать в m раз больше. Параметры и FLOPs те же, а комбинаций экспертов на порядки больше..

Что изменилось? Число комбинаций. Запись (nk)\binom{n}{k} — число сочетаний (урок 1.0.5): сколькими способами можно выбрать kk предметов из nn, если порядок не важен (в Python math.comb(n, k)). Например, (162)=16⋅15/2=120\binom{16}{2} = 16 \cdot 15 / 2 = 120: первого эксперта выбираем 16 способами, второго 15, и делим на 2, потому что пара «A, B» и пара «B, A» — одно и то же. Пример из статьи: N=16N = 16, top-2 даёт (162)=120\binom{16}{2} = 120 вариантов «команды экспертов» для токена. Разрежем каждого на 4 части: (648)=4 426 165 368\binom{64}{8} = 4\,426\,165\,368 вариантов. Это как перейти от больших блоков конструктора к маленьким: из мелких деталей можно собрать гораздо более точную форму.

Мелкие эксперты специализируются уже, и знания для конкретного токена собираются из нужных «кусочков». Предел у идеи технический: слишком маленькие матрицы плохо загружают GPU, а накладные расходы на раскладку токенов по экспертам растут. Поэтому дробят до разумного размера и останавливаются. Масштаб видно по конфигу DeepSeek-V3: промежуточная размерность эксперта 2048 при плотном FFN на 18 432 — каждый эксперт примерно в 9 раз меньше обычного FFN, и на токен их берут 8 плюс один общий.

Выделим KsK_s экспертов, которые обрабатывают каждый токен, без участия роутера. Это общие экспертыобщий экспертEN shared expertКлючевая идея DeepSeekMoE.. Чтобы вычисления не выросли, из маршрутизируемых выбираем на KsK_s меньше. Выход слоя:

ht′=ut+∑i=1KsFFNi(s)(ut)+∑i=1Nrgi,t FFNi(r)(ut)h'_t = u_t + \sum_{i=1}^{K_s} \mathrm{FFN}^{(s)}_i(u_t) + \sum_{i=1}^{N_r} g_{i,t} \, \mathrm{FFN}^{(r)}_i(u_t)

Здесь utu_t — вход слоя (первое слагаемое — residual-связь), NrN_r — число маршрутизируемых экспертов, gi,tg_{i,t} — вес эксперта ii для токена tt. У выбранных top-k экспертов вес равен нормированной оценке роутера, у остальных он равен нулю, и их FFN вообще не считается. Общий эксперт берёт на себя знания, которые нужны всем, и маршрутизируемым больше не нужно их дублировать.

flowchart LR
U["токен u_t"] --> S["общий эксперт: всегда"]
U --> R["роутер: сродство с 256 экспертами"]
R -->|"top-8"| E["8 мелких экспертов из 256"]
U --> E
S --> SUM["сумма + residual"]
E --> SUM
SUM --> H["h'_t"]
MoE-слой DeepSeek-V3: 1 общий и 8 из 256 маршрутизируемых экспертов на каждый токен.

Авторы сравнили MoE-архитектуры с 2B параметров всего (и плотную модель на 0.2B для ориентира), все обучены на 100B токенов. Pile — большой открытый английский корпус (книги, статьи, код, веб); loss на нём — та же cross-entropy, чем меньше, тем лучше. HellaSwag — тест «выбери самое правдоподобное продолжение бытовой ситуации», число — процент верных ответов:

МодельАктивных параметровLoss на PileHellaSwag
Плотная0.2B2.06038.8
Hash Layer0.2B1.93246.2
Switch Transformer0.2B1.88149.1
GShard0.3B1.86750.5
DeepSeekMoE0.3B1.80854.8

Ещё несколько результатов из той же статьи:

  • Общий эксперт незаменим. У уже обученной DeepSeekMoE 2B (1 общий эксперт и 7 из 63 маршрутизируемых) общего эксперта отключили и вместо него включили ещё одного маршрутизируемого. FLOPs те же, а loss вырос с 1.808 до 2.414. Значит, общий эксперт хранит знания, которых нет у маршрутизируемых, и заменить его нечем.
  • Сколько общих. При 64 экспертах пробовали 1, 2 и 4 общих: loss 1.808, 1.806 и 1.811. Разница мала, и при масштабировании авторы держали соотношение «общие : выбранные маршрутизируемые» около 1 : 3.
  • Масштаб. DeepSeekMoE 16B (2.8B активных) сравнялась с LLaMA2 7B, тратя около 40% её вычислений.
DeepSeekMoE 16B (01.2024)DeepSeek-V2 (05.2024)DeepSeek-V3 (12.2024)
Всего / активных16.4B / 2.8B236B / 21B671B / 37B
Общих экспертов221
Маршрутизируемых (выбрано)64 (6)160 (6)256 (8)
Функция сродстваsoftmaxsoftmaxсигмоида
Балансировкаaux loss3 вида aux loss и отбрасывание токеновсмещения и крошечный aux loss, без отбрасывания
Ограничение маршрута—не более 3 устройствне более 4 узлов

В V3 роутинг заметно изменился. Сродство токена с экспертом считается сигмоидой, si,t=σ(ut⊤ei)s_{i,t} = \sigma(u_t^\top e_i), где eie_i — обучаемый «центроидцентроидEN centroid» эксперта (вектор-«представитель», как центр кластера в k-means из модуля 2.3). В отличие от softmax, каждая оценка сигмоиды считается независимо от остальных, и в сумме они не обязаны давать 1. Поэтому веса выбранных экспертов нормируют отдельно. Вот сокращённый фрагмент официального инференс-кода:

# DeepSeek-V3, inference/model.py, класс Gate (лицензия MIT); сокращено, комментарии наши
def forward(self, x):
scores = linear(x, self.weight).sigmoid() # (T, 256): сродство σ(u·e_i)
original_scores = scores
scores = scores + self.bias # смещение балансировки — только для выбора
scores = scores.view(x.size(0), self.n_groups, -1) # 8 групп по 32 эксперта
group_scores = scores.topk(2, dim=-1)[0].sum(dim=-1) # сила группы = сумма двух лучших
indices = group_scores.topk(self.topk_groups, dim=-1)[1] # оставляем 4 группы из 8
# маска групп: True у всех, затем scatter_ ставит False на места 4 выбранных групп → закрываем остальные 4
mask = scores.new_ones(x.size(0), self.n_groups, dtype=bool).scatter_(1, indices, False)
scores = scores.masked_fill_(mask.unsqueeze(-1), float("-inf")).flatten(1)
indices = torch.topk(scores, self.topk, dim=-1)[1] # 8 экспертов из оставшихся 128
weights = original_scores.gather(1, indices) # gather: взять из каждой строки значения по indices; веса — по ИСХОДНЫМ оценкам
weights /= weights.sum(dim=-1, keepdim=True) # нормировка среди выбранных
weights *= self.route_scale # × 2.5 из конфига
return weights.type_as(x), indices

Обратите внимание на три детали. Смещение self.bias участвует в выборе, но не в весах. Выбор двухступенчатый: сначала 4 группы из 8, потом 8 экспертов внутри них. Группы нужны для ограничения маршрута, о котором речь пойдёт в разделе про параллелизм. Наконец, множитель route_scale = 2.5 берётся из конфига модели и задаёт масштаб вклада маршрутизируемых экспертов после нормировки.

Идея 3: балансировка без вспомогательного лосса

Заголовок раздела «Идея 3: балансировка без вспомогательного лосса»

Вспомним дилемму aux loss: маленький коэффициент не спасает от перекоса, большой портит качество. Статья «Auxiliary-Loss-Free Load Balancing» (DeepSeek, август 2024) предлагает разделить две задачи роутера: решать, кому достанется токен, и решать, с каким весом учитывать ответ эксперта.

балансировка без вспомогательного лоссабалансировка без вспомогательного лоссаEN auxiliary-loss-free load balancingМетод DeepSeek (arXiv:2408.15664, использован в V3) — к оценкам роутера прибавляется смещение b_i, влияющее только на выбор экспертов; после каждого шага b_i сдвигается на ±γ по загрузке. устроена так:

  1. У каждого эксперта есть смещение bib_i, вначале 0. Это не параметр модели, градиенты его не трогают.
  2. Выбор top-k идёт по si,t+bis_{i,t} + b_i, а веса берутся по исходным si,ts_{i,t}.
  3. После каждого шага считаем, сколько токенов cic_i досталось каждому эксперту, и сдвигаем смещения:
bi←bi+γ⋅sign(cˉ−ci)b_i \leftarrow b_i + \gamma \cdot \mathrm{sign}(\bar c - c_i)

Здесь cˉ\bar c — средняя загрузка, γ\gamma — маленький шаг, а sign(x)\mathrm{sign}(x) — знак числа: +1+1 для положительных, −1-1 для отрицательных, 0 для нуля (np.sign в NumPy). Недогруженный эксперт (ci<cˉc_i < \bar c) получает +γ+\gamma и чаще проходит в top-k. Перегруженный получает −γ-\gamma. Это как светофор с адаптивным переключением: машины (токены) едут сами, светофор лишь чуть меняет приоритеты.

Пример. Четыре эксперта, в батче 100 назначений, загрузка [40,30,20,10][40, 30, 20, 10], среднее 25. Знаки cˉ−ci\bar c - c_i: [−,−,+,+][-, -, +, +], и при γ=0.001\gamma = 0.001 смещения становятся [−0.001,−0.001,+0.001,+0.001][-0.001, -0.001, +0.001, +0.001].

Почему это лучше aux loss:

  • Нет мешающих градиентов. Основной лосс оптимизирует только качество.
  • Выход модели не искажается. Смещение меняет лишь то, кто обработает токен, а не как сложатся ответы.
  • Баланс по батчу, а не внутри каждой последовательности. Это оставляет экспертам свободу специализироваться по доменам, как и в подходе Qwen из прошлого урока.

Качество баланса меряют метрикой MaxVio — насколько самый загруженный эксперт превышает среднюю загрузку:

MaxVio=max⁡ici−cˉcˉ\mathrm{MaxVio} = \frac{\max_i c_i - \bar c}{\bar c}

В статье на моделях до 3B параметров метод дал и лучшую perplexity, и лучший баланс, чем aux loss. Скорость обновления γ\gamma (в статье она обозначена uu) равна 0.001: при слишком маленькой баланс устанавливается медленно, при слишком большой смещения «дрожат» — в задании ниже вы увидите оба режима своими глазами. Су Цзяньлинь в части 3 цикла «Путешествие по MoE» замечает, что это обновление — знаковый градиентный спуск (SignSGD) по смещениям.

В DeepSeek-V3: γ=0.001\gamma = 0.001 первые 14.3T токенов обучения и γ=0\gamma = 0 последние 500B. Дополнительно стоит sequence-wise aux loss с крошечным коэффициентом α=0.0001\alpha = 0.0001, только чтобы не было экстремального перекоса внутри одной последовательности. Благодаря балансировке V3 не отбрасывает токены ни при обучении, ни на инференсе.

Как разместить 256 экспертов в 58 слоях? Ни на одну GPU они не поместятся. Решение — параллелизм экспертовпараллелизм экспертовEN expert parallelism (EP)Разные эксперты MoE-слоя живут на разных GPU; токены пересылаются к своим экспертам и обратно через all-to-all-обмен.: эксперты слоя раскладываются по разным GPU, а токены пересылаются к своим экспертам и обратно.

Пересылка идёт обменом «все со всеми» (all-to-all): каждая GPU отправляет каждой другой ту часть своих токенов, чьи эксперты там живут, и одновременно принимает чужие. На MoE-слой таких обменов два: dispatch (токены к экспертам) и combine (результаты назад).

flowchart LR
A["токены на GPU 0"] -->|"dispatch (all-to-all)"| E0["GPU 0: эксперты 0–31"]
A -->|dispatch| E1["GPU 1: эксперты 32–63"]
B["токены на GPU 1"] -->|dispatch| E0
B -->|dispatch| E1
E0 -->|"combine (all-to-all)"| A2["выходы токенов GPU 0"]
E1 -->|combine| A2
E0 -->|combine| B2["выходы токенов GPU 1"]
E1 -->|combine| B2
Параллелизм экспертов: каждый MoE-слой — две пересылки «все со всеми» (dispatch и combine).

Отсюда три инженерных следствия.

  1. Коммуникации дороги. Каждый узел, куда уходит токен, — это пересылка по сети. Поэтому DeepSeek ограничивает маршрут: в V2 — не более 3 устройств, в V3 — не более 4 узлов. Это и есть «группы» в коде выше. В V3 8 групп по 32 эксперта, при обучении группа соответствует узлу, и токен попадает максимум в 4 группы.
  2. Баланс — это скорость. Шаг длится столько, сколько работает самая загруженная GPU. Поэтому балансировка в MoE — вопрос не только качества, но и денег.
  3. Горячие эксперты дублируют. На инференсе DeepSeek держит избыточные копии самых популярных экспертов и периодически переставляет экспертов между GPU по статистике загрузки. Этот балансировщик открыт как EPLB, а пересылки dispatch/combine делает библиотека DeepEP.

Масштаб впечатляет. По опубликованному DeepSeek обзору инференс-системы V3/R1 (февраль 2025), prefill работает в конфигурации EP32: блок из 4 узлов, на каждой GPU 9 маршрутизируемых экспертов и 1 общий, плюс 32 избыточных эксперта. Decode — EP144: 18 узлов, на каждой GPU 2 маршрутизируемых и 1 общий эксперт. Чтобы GPU не простаивали во время пересылок, батч делят на два микро-батча: пока один считается, другой пересылается. Как то же самое делают при обучении (DualPipe) и почему урезанная связь H800 сделала это необходимым — в уроке 5.4.3.

Реализуйте роутинг со смещениями, обновление смещений, MaxVio и цикл «обучения» на потоке батчей, где первые эксперты заметно популярнее последних. Кнопка «Запустить» нарисует загрузку экспертов без балансировки и с ней.

Балансировка экспертов смещениями (Loss-Free Balancing)numpy · нужен интернет6 тестов
import numpy as np


# ── Готовые помощники ────────────────────────────────────────────────────────
def make_batches(n_steps, T=256, E=16, d=16, seed=0):
    """Батчи сродства токен–эксперт (sigmoid), где первые эксперты заметно «популярнее» последних."""
    rng = np.random.default_rng(seed)
    C = rng.normal(size=(d, E)) / np.sqrt(d)
    skew = np.linspace(1.5, -1.5, E)                  # перекос: эксперт 0 любим, эксперт 15 — нет
    return [1 / (1 + np.exp(-(rng.normal(size=(T, d)) @ C + skew))) for _ in range(n_steps)]


def bar_chart(counts, width=40):
    """Текстовый «график» загрузки экспертов."""
    top = max(counts.max(), 1)
    for i, c in enumerate(counts):
        print(f"E{i:<2d} {'█' * int(width * c / top):<{width}} {int(c)}")


# ── Ваша часть ───────────────────────────────────────────────────────────────
def route_with_bias(scores, bias, k):
    """Выбор экспертов по scores + bias, а веса — по исходным scores.

    scores: (T, E) — сродство токенов и экспертов (после sigmoid); bias: (E,).
    Возвращает (idx, weights): idx — (T, k), top-k по (scores + bias), по убыванию,
    при равенстве меньший номер раньше (np.argsort(..., kind='stable'));
    weights — (T, k): ИСХОДНЫЕ scores выбранных экспертов, нормированные к сумме 1.
    """
    # TODO
    ...


def update_bias(bias, counts, gamma):
    """Шаг балансировки: b_i <- b_i + gamma * sign(mean(counts) - counts_i).

    Недогруженные эксперты получают +gamma, перегруженные -gamma, ровно средние — 0.
    Возвращает новый массив смещений.
    """
    # TODO
    ...


def max_violation(counts):
    """MaxVio = (max_i load_i - mean load) / mean load. 0 — идеальный баланс."""
    # TODO
    ...


def simulate(batches, k, gamma):
    """Обучение без aux loss: для каждого батча
       1) роутинг с текущими смещениями, 2) счётчики токенов по экспертам,
       3) записать MaxVio, 4) обновить смещения.
    Возвращает (history, bias): список MaxVio по шагам и итоговые смещения.
    """
    E = batches[0].shape[1]
    bias = np.zeros(E)
    history = []
    # TODO
    ...


# Проверьте себя кнопкой «Запустить»:
batches = make_batches(300)
for gamma in (0.0, 0.01):
    res = simulate(batches, k=4, gamma=gamma)
    if res is None:
        break
    hist, bias = res
    print(f"gamma={gamma}: MaxVio на шаге 0 = {hist[0]:.2f}, в среднем за последние 50 шагов = {np.mean(hist[-50:]):.2f}")
    idx, _ = route_with_bias(batches[-1], bias, 4)
    bar_chart(np.bincount(idx.ravel(), minlength=16))

Поэкспериментируйте со скоростью gamma. В той же симуляции на 300 шагах при γ=0.0001\gamma = 0.0001 MaxVio почти не меняется (≈ 1.8), при 0.0010.001 и 0.010.01 падает примерно до 0.2 (при 0.010.01 — уже за 20 шагов, при 0.0010.001 — только к двухсотому), а при 0.10.1 застревает около 0.8: смещения прыгают слишком сильно. Качественно ту же картину описывают авторы статьи: слишком маленькая скорость медленно наводит баланс, слишком большая раскачивает смещения.

Общий эксперт, 16 мелких маршрутизируемых, сигмоидное сродство и балансировка смещениями прямо внутри обучения. Задача та же, что в прошлом уроке, и никакого aux loss.

import math
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.set_num_threads(1)
class SwiGLU(nn.Module):
"""Эксперт = маленький FFN со SwiGLU, как в DeepSeek/Qwen/Llama."""
def __init__(self, d, d_ff):
super().__init__()
self.w1, self.w3 = nn.Linear(d, d_ff, bias=False), nn.Linear(d, d_ff, bias=False)
self.w2 = nn.Linear(d_ff, d, bias=False)
def forward(self, x):
return self.w2(F.silu(self.w1(x)) * self.w3(x))
class DeepSeekMoE(nn.Module):
"""Общие эксперты + мелкие маршрутизируемые, sigmoid-сродство, балансировка смещениями."""
def __init__(self, d, d_expert, n_routed=16, n_shared=1, k=4, gamma=0.01):
super().__init__()
self.k, self.E, self.gamma = k, n_routed, gamma
self.centroids = nn.Parameter(torch.randn(n_routed, d) / d ** 0.5) # e_i из статьи
# buffer — тензор, который живёт в модуле и сохраняется в state_dict, но не является параметром: градиент его не трогает
self.register_buffer("bias", torch.zeros(n_routed))
self.routed = nn.ModuleList(SwiGLU(d, d_expert) for _ in range(n_routed))
self.shared = SwiGLU(d, n_shared * d_expert) # общие эксперты = один FFN пошире
def forward(self, x): # x: (T, d)
s = torch.sigmoid(x @ self.centroids.T) # сродство (T, E)
idx = (s + self.bias).topk(self.k, dim=-1).indices # смещение — только для выбора
w = s.gather(-1, idx)
w = w / w.sum(dim=-1, keepdim=True) # веса — из исходных s
y = self.shared(x) # общие эксперты видят всех
for e, expert in enumerate(self.routed):
rows, slot = torch.where(idx == e)
if rows.numel():
y = y.index_add(0, rows, w[rows, slot, None] * expert(x[rows]))
load = torch.bincount(idx.flatten(), minlength=self.E).float()
if self.training: # шаг балансировки после батча
self.bias += self.gamma * torch.sign(load.mean() - load)
self.maxvio = ((load.max() - load.mean()) / load.mean()).item()
return y
def run(gamma, steps=200, seed=0):
torch.manual_seed(seed)
d, n_clusters = 16, 8
centers, common = torch.randn(n_clusters, d), 2.0 * torch.randn(d)
maps = torch.randn(n_clusters, d, d) / d ** 0.5
moe = DeepSeekMoE(d, 8, n_routed=16, n_shared=1, k=4, gamma=gamma)
opt = torch.optim.Adam(moe.parameters(), lr=3e-3)
hist = []
for _ in range(steps):
c = torch.randint(0, n_clusters, (256,))
x = centers[c] + common + 0.3 * torch.randn(256, d)
x = x / x.pow(2).mean(-1, keepdim=True).sqrt()
target = torch.einsum("td,tde->te", x, maps[c])
loss = F.mse_loss(moe(x), target) # никакого aux loss
opt.zero_grad()
loss.backward()
opt.step()
hist.append(moe.maxvio)
return loss.item(), hist
print("комбинаций: C(16,2) =", math.comb(16, 2), " C(64,8) =", f"{math.comb(64, 8):,}")
for gamma in (0.0, 0.01):
mse, hist = run(gamma)
print(f"gamma={gamma:<4} MSE={mse:.3f} MaxVio: шаг 1 = {hist[0]:.2f}, "
f"среднее за последние 50 шагов = {sum(hist[-50:]) / 50:.2f}")

Вывод (на CPU от нескольких секунд до полуминуты; на другой версии PyTorch числа могут немного отличаться):

комбинаций: C(16,2) = 120 C(64,8) = 4,426,165,368
gamma=0.0 MSE=0.025 MaxVio: шаг 1 = 2.08, среднее за последние 50 шагов = 1.17
gamma=0.01 MSE=0.026 MaxVio: шаг 1 = 2.08, среднее за последние 50 шагов = 0.26

Балансировка смещениями снизила перекос в 4.5 раза, не добавив ни одного члена в лосс. На качество в этой игрушечной задаче она почти не повлияла, что и требовалось: смещения не должны мешать обучению. Настоящий выигрыш виден на кластере, где перекос нагрузки превращается в простаивающие GPU.

Проверь себя6 вопросов
1 Сколько различных наборов экспертов можно выбрать при top-2 из 16 экспертов?
2 Зачем в DeepSeekMoE нужны общие эксперты?
3 Какие утверждения о балансировке без вспомогательного лосса верны?Можно выбрать несколько ответов
4 Загрузка четырёх экспертов: [10,30,20,20][10, 30, 20, 20] токенов. Чему равен MaxVio?
5 Сколько экспертов (маршрутизируемых и общих вместе) обрабатывают один токен в MoE-слое DeepSeek-V3?
6 Зачем DeepSeek-V3 ограничивает маршрут токена не более чем 4 узлами?
  • Мелкозернистые эксперты при тех же параметрах и FLOPs дают на порядки больше комбинаций, и знания раскладываются точнее.
  • Общие эксперты забирают общее знание; без них качество заметно падает.
  • Балансировка смещениями отделяет выбор эксперта от веса его ответа и обходится без вспомогательного лосса. В V3: γ=0.001\gamma = 0.001, сигмоидное сродство, никакого отбрасывания токенов.
  • Параллелизм экспертов раскладывает экспертов по GPU. Маршрут ограничивают узлами, горячих экспертов дублируют.

Дальше: модуль 5.4 об эффективном обучении. Начнём со смешанной точности — FP32, BF16 и FP8, с помощью которых DeepSeek обучила V3.