☰
DeepSeek-V3训练优化:MLA、MoE路由与FP8通信重叠
2026/11/4 2:46:25 网站建设 项目流程

简介:DeepSeek-V3 技术报告 PDF 面向大模型算法工程师、研究者以及希望深入理解 MoE 架构的开发者,系统呈现 DeepSeek-AI 团队 671B 总参数、每 token 激活 37B 的混合专家语言模型整体情况。压缩包内只有 1 个 PDF 文件,约 5.44MB,按引言、架构、基础设施、预训练、后训练、结论与附录组织,包含多头潜在注意力(MLA)、DeepSeekMoE 与无辅助损失负载均衡、多 token 预测目标等核心章节,并说明 DualPipe 计算与通信重叠、FP8 混合精度训练、推理部署及通信与计算硬件设计建议等工程细节。读者可据此了解 148 万亿 token 的预训练流程、监督微调与强化学习后训练策略,以及 MMLU-Pro、GPQA-Diamond、MATH-500、Codeforces 等基准的横向对比结果;报告还提供多 token 预测、负载均衡策略的消融实验和批量与序列负载均衡对比,便于复现思路与选型参考。全文目录与图表完整,适合精读或按模块查阅。目前已有 157 人学习。

1. 671B 参数只激活 37B:省下来的到底是什么

V3 技术报告里最反直觉的一组数字不是榜单分数:总参数 671B,每处理一个 token 只激活 37B;14.8 万亿 token 预训练,完整训练 2.788M GPU 小时,按每小时 2 美元的租赁价折算约 557.6 万美元;整个训练过程没有出现不可恢复的 loss 峰值,也没有回滚。按稠密模型的经验,参数规模和算力开销几乎是绑死的,671B 的模型不该只花这个量级。省下来的不是参数,而是「每个 token 走的路」:KV 缓存被压成一个潜在向量、专家只有少数被选中、流水线气泡被重排掉、乘加运算跑在 FP8 上。这四刀分别切在显存、路由、通信和数值精度上,每一刀都有代价,也都留了补救措施。后面就按「省在哪、凭什么能省、省完埋了什么坑」逐层拆。

2. 多头潜在注意力:KV 缓存为什么能压成一个向量

推理阶段的显存大头从来不是权重,而是 KV 缓存。要理解 MLA 的价值,得先把这笔账算清楚:一个 61 层、128 头、每头 128 维的模型,用 BF16 缓存 K 和 V,每个 token 每层要存 2 × 128 × 128 × 2 字节 = 64KB,61 层就是约 3.9MB。单个序列撑到 128K 上下文,光缓存就是几百 GB,比模型权重还夸张。批处理一开,显存直接被吃干净,长上下文能力变成纸面指标。所以「能不能把 KV 压小」不是优化项,而是长上下文能不能落地的前置条件。

2.1 低秩联合压缩:缓存的对象换了一个

MLA 的做法不是对 K、V 做量化或稀疏化,而是换掉缓存的「对象」:不再缓存每层的 K 和 V,而是缓存一个低维的压缩潜在向量 c,K 和 V 在需要时由 c 上投影得到。原始注意力的输入是 h_t ∈ R^d,覆盖所有头;MLA 先用下投影矩阵把 h_t 压到 d_c 维(d_c 远小于 d_h·n_h),再从这个 c 上投影出所有头的 K 和 V。

# MLA 单层前向的等价结构(省略 batch 与 head 维,d_c << n_h * d_h) c_kv = h @ W_dkv # [T, d_c] 推理时缓存这个 k_c = c_kv @ W_uk # [T, n_h*d_h] 由 c 现算,不缓存 v = c_kv @ W_uv # [T, n_h*d_h] 同上 k_r = rope(h @ W_kr) # [T, d_r] 解耦的 RoPE 键,单独缓存 # 键由「内容部分」和「位置部分」拼接而成 k = cat([k_c, k_r.repeat_interleave(n_h, dim=-1)], dim=-1) q_c = (h @ W_dq) @ W_uq # 查询侧同样做低秩压缩 q_r = rope((h @ W_dq) @ W_qr) q = cat([q_c, q_r.repeat_interleave(n_h, dim=-1)], dim=-1)

这段伪代码里最关键的是三行注释:c_kv和k_r是仅有的两份需要驻留显存的量,k_c、v都是推理时现算的中间结果。每层每 token 的缓存开销从 2·n_h·d_h 降到 d_c + d_r,量级差一个数量级。查询侧的低秩压缩则是为训练服务的:它把激活内存压下来,让更长的序列塞得进单卡。

提示:上投影矩阵在推理时可以被吸收进相邻矩阵,避免每个 token 都真的做一次投影。这类矩阵吸收是 MLA 能落地的前提,光压缩不吸收,计算量反而上升。

2.2 解耦 RoPE:位置信息为什么不能一起压

一个自然的疑问是:既然 K 都能从 c 上投影出来,为什么还要单独留一路 k_r?原因是旋转位置编码是位置相关的时变操作,作用在维度对上做旋转,不同位置的旋转角不同。它没法在训练前就被吸收进静态的投影矩阵里,也没法塞进那个与位置无关的低秩子空间。硬压的结果是位置信息被矩阵乘法搅碎,长距离依赖直接失效。

所以 MLA 采用的是「内容走低秩、位置走独立通道」的拆法:k_c 负责语义匹配,k_r 只负责携带相对位置,两者在注意力打分前拼接。把 d_r 维的位置通道单独缓存,成本很小,却保住了 RoPE 的外推性质。这也是后面做 32K → 128K 两阶段上下文扩展时,位置部分还能单独调的原因。

这套拆法的另一个收益是缓存的下界被固定住了:不管序列多长、batch 多大,每层每 token 的常量开销就是 d_c + d_r,与头数解耦。头数可以从 128 加到更多,缓存不跟着涨,这在稠密注意力里是不可想象的。

2.3 多 token 预测:让一次前向多产出几份监督

架构层的第二个改动是训练目标。标准自回归只监督下一个 token,一个长度为 T 的序列产生 T 个监督信号;MTP 在同一个位置上额外预测后面第 2、第 3 个 token,监督信号密度直接翻倍。V3 的实现不是并行加几个独立输出头,而是串成一条因果链:第 k 个预测模块吃掉「主模型在第 k-1 层的表示」和「第 i+k 个 token 的嵌入」,过一个 Transformer 块,再交给共享输出头。

# 第 k 个 MTP 模块(k = 1..D),嵌入层与输出头与主模型共享 h_prev = h_main if k == 1 else h_prev_list[k - 1] # 上一层深度的表示 e_next = emb(token[i + k]) # 未来第 k 个 token 的嵌入 h_in = M_k(cat([rmsnorm(h_prev), rmsnorm(e_next)], dim=-1)) # 线性投影合并 h_k = trm_k(h_in)[:, :T - k] # 切片,保持因果对齐 logits = out_head(h_k) # 与主模型共享输出头 loss_k = cross_entropy(logits, token[i + k]) # 总损失:主模型交叉熵 + λ/D * 各深度 MTP 损失的平均 loss = ce_main + lam * sum(loss_k for k in 1..D) / D

h_prev与e_next都先过 RMSNorm 再拼接,是为了让两路信号的尺度可比,否则投影矩阵会被量纲更大的那一路主导。切片[:, :T-k]是因果性的保证:预测第 i+k 个 token 时,只能用位置 i 及之前的表示,不能偷看未来。输出头共享意味着词表投影只有一份,参数量不随预测深度线性增长。

MTP 在推理时是可以整段丢掉的——它的设计目标就是「训练时加密度、推理时不加成本」。留下来的额外收益是它天然构成一条草稿链,可以直接拿去做推测解码,用小代价换取生成延迟下降。这条「训练加负担、推理减负担」的取舍,后面还会在 DualPipe 上再出现一次。

3. DeepSeekMoE 与无辅助损失负载均衡:路由偏置如何替代辅助损失

MoE 的核心矛盾是:专家越多,单 token 计算越省,但路由越容易塌。所有 token 都涌向少数几个专家,其余专家全程空转,等于花了大显存买了个小模型,这在工程上叫路由崩溃。传统解法是加辅助损失,强行把负载拉平,但辅助损失本质是往主任务里掺一个与语言建模无关的目标,权重给大了掉点,给小了不起作用。

3.1 辅助损失为什么必然伤性能

MoE 的前馈层由 N_s 个共享专家和 N_r 个路由专家组成,每个 token 选亲和力最高的 K 个路由专家。辅助损失的典型形式是「专家被选中的频率 × 平均亲和力」的乘积求和,再乘一个系数。它逼迫路由分布均匀,但均匀不等于正确:某个 token 本来最该去的专家,可能因为该专家已经过载而被惩罚性降权,模型被迫走一条更差的路径。

这个惩罚是全局的、连续的,会一路回传到路由打分上,让模型学到的亲和力本身发生偏移。训练越久,偏移累积越大,最终表现为「负载很均衡,但每个专家的专业化程度都不高」。这就是辅助损失的两难:它解决的是系统问题(并行效率),却污染了模型问题(表示质量)。

3.2 偏置项:只改排序,不改数值

V3 的做法是把「均衡」和「打分」拆成两件事。给每个路由专家挂一个偏置 b_i,选 top-K 时用 s_i + b_i 排序,但真正参与加权的门控值仍然来自原始亲和力 s_i。也就是说,偏置只影响「谁能被选中」,不影响「选中后贡献多少」。

# 每个训练 step 结束后的专家偏置更新(按整个 batch 的负载统计) s = sigmoid(h @ W_router) # [T, N_r] 亲和力,注意是 sigmoid 不是 softmax s_topk, idx = topk(s + bias, K) # 偏置只参与排序 gate = normalize(s.gather(idx)) # 门控值取自原始 s,不含 bias y = sum(gate[j] * ffn_expert[idx[j]](h) for j in range(K)) # 统计本 step 每个专家的分配量,与目标均值比较 load = bincount(idx.flatten(), minlength=N_r) target = T * K / N_r for i in range(N_r): if load[i] > target * (1 + eps): bias[i] -= gamma # 过载,降权 elif load[i] < target * (1 - eps): bias[i] += gamma # 空闲,提权

这段逻辑有两个容易被忽略的细节。第一,亲和力用 sigmoid 而不是 softmax,再对选中的 K 个分数做归一化——这样每个 token 的门控值只在自己选中的专家之间分配,不会被未选中的专家摊薄,路由决策更锐利。第二,偏置的更新是批次级的反馈控制,不是在反向传播里学出来的,所以它不产生梯度、不参与优化器状态,纯粹是一个「负载恒温器」。γ 是偏置更新速度,它是这套机制里最需要调的超参:太大路由会抖,太小跟不上负载漂移。

注意:偏置只用于路由排序,这一点是整个机制成立的前提。一旦把 b_i 也加进门控值,等价于给专家输出乘了一个与内容无关的缩放,等于变相引入辅助损失。

3.3 序列级损失作为兜底

批次级偏置解决的是全局均衡,但单个序列内部仍可能出现极端集中:一句话里的 token 全挤到同一个专家上,那个专家所在的 GPU 瞬间成为热点。为此还保留了一个互补的序列级平衡损失,形式是各专家在序列内的分配比例与平均亲和力的乘积求和,再乘系数 α。

关键在于 α 必须给得极小。主力均衡靠偏置完成,序列损失只是防止极端情况,属于安全阀而不是主控制器。如果按老经验把 α 给到能明显影响路由的量级,等于又回到了辅助损失伤性能的老路。

3.4 节点限制路由:把通信成本写进路由规则

MoE 的通信开销来自全到全(all-to-all):token 要按路由结果发送到持有目标专家的 GPU 上,算完再收回来。专家并行度越高,跨节点流量越大。V3 的做法是在路由阶段就限制扇出:每个 token 最多发往 M 个节点(这里是 4 个),节点选择依据是「该节点上所有专家的最高亲和力之和」。

这条约束把通信成本变成了可计算的上界。集群里节点内走 NVLink,带宽约 160GB/s;跨节点走 IB,约 50GB/s,前者是后者的 3.2 倍。于是「每个 token 分发到 4 个节点、每节点平均命中 3.2 个专家」意味着:实际每个 token 只选 8 个路由专家,但在同等通信代价下理论上可以扩到 13 个(4 节点 × 3.2 专家/节点)。这是把硬件拓扑直接写进算法规则的典型案例——路由不是先决定再算通信,而是路由决策时就已经知道代价。

约束项取值影响
每 token 激活路由专家数 K8决定单 token 计算量
每 token 最大扇出节点数 M4决定 IB 跨节点流量上界
节点内平均命中专家数3.2NVLink 与 IB 带宽比决定
同等通信成本下的理论上限134 × 3.2,模型扩容的余量
token 丢弃全程不丢依赖偏置均衡 + 推理侧部署策略

不丢弃 token 是个容易被低估的成果。很多 MoE 实现靠 capacity factor 截断来保证显存可控,被截断的 token 直接跳过专家层,相当于静默丢信息。V3 因为均衡做得好,训练和推理全程都不需要丢,这对长尾输入的稳定性影响很大。

4. DualPipe、全到全通信与 FP8:计算通信比 1:1 怎么拆

MoE + 专家并行的组合会带来一个硬约束:计算与通信的时间比接近 1:1。传统流水线并行只处理计算内部的排布,通信时间裸露在外,等于一半的机器在等数据。要压成本,就必须把通信藏进计算里,或者干脆把通信量本身降下来。

4.1 DualPipe:把块拆成四段再重排

DualPipe 的核心思想不是「重叠计算和通信」这句话本身,而是把 Transformer 块拆细到可以自由重排的粒度。每个块被切成四部分:注意力、全到全分发、MLP、全到全合并。反向块再拆成「对输入的反向」和「对权重的反向」两段,再加上流水线通信这一段。

# DualPipe 单对前向/反向块的重排示意(组件级,非完整调度) # 前向块: ATTN -> DISPATCH -> MLP -> COMBINE # 反向块: COMBINE.B -> MLP.B(W) -> MLP.B(I) -> DISPATCH.B -> ATTN.B(W) -> ATTN.B(I) # 重排原则:让通信组件与不依赖它的计算组件在时间轴上错开并重叠 schedule = interleave( forward_chunks = [attn_f, dispatch_f, mlp_f, combine_f], backward_chunks = [combine_b, mlp_bw, mlp_bi, dispatch_b, attn_bw, attn_bi], pp_comm = [send_recv_pairs], # 与计算流错峰 ) # 双向调度:同时从流水线两端注入微批次,两侧的通信互为掩护

双向注入微批次是关键:管道两端同时灌数据,一侧在做分发通信时,另一侧正好在做 MLP 计算,通信窗口被另一侧的算力填满。这样做的代价是必须保留两份模型参数的副本,显存占用上升;但因为专家并行度开得很大,参数本来就被切得很碎,这个代价被摊薄了。

气泡方面,1F1B 的气泡是 (PP-1)(F+B),ZB1P 靠拆分反向省掉一部分,DualPipe 进一步把气泡压到约 (PP/2-1)(F&B+B-3W) 的量级。

方法流水线气泡激活内存约束条件
1F1B(PP-1)(F+B)1×PP无
ZB1P(PP-1)(F+B-2W)1×PP需拆分反向
DualPipe(PP/2-1)(F&B+B-3W)约 2×PP 缓冲阶段数与微批数需被 2 整除

F 是前向块耗时,B 是完整反向块耗时,W 是「对权重的反向」耗时,F&B 表示两个相互重叠的前向反向块的耗时。DualPipe 的额外内存主要是激活缓冲翻倍,而微批次数增加时气泡和激活都不再增长——这跟 Chimera 那种要求微批数被流水阶段数整除的方案相比,调度自由度大得多。

4.2 20 个 SM 的全到全内核

通信要藏进计算,前提是通信本身不能吃掉太多算力。V3 定制的跨节点全到全内核只占用 20 个 SM,分成 10 个通信通道,用 warp 专业化把不同职责分给不同 warp:分发阶段里,IB 发送、IB 到 NVLink 的转发、NVLink 接收各由专门 warp 处理;合并阶段的 NVLink 发送、转发累加、IB 接收累加同理。每个任务的 warp 数量按实际负载动态调整。

转发路径的设计利用了带宽差:token 先通过 IB 发到目标节点上「节点内索引相同」的那张卡,到达后立刻经 NVLink 转到真正持有目标专家的卡,不被后续到达的 token 阻塞。这样 IB 和 NVLink 的传输完全重叠,等效于每节点免费获得 3.2 个专家的路由能力。

还有一层容易被忽略的优化是缓存干扰:通信内核和计算内核共享 L2,通信块太大会把计算侧的热数据挤出去。定制 PTX 指令加上通信块大小的自动调优,目的就是把 L2 占用压下来,减少对其他 SM 上计算内核的干扰。

4.3 FP8:分块缩放与高精度累加

FP8 的难点从来不是「能不能用」,而是「用在哪、怎么防溢出」。V3 的混合精度框架采用块级量化:GEMM 的输入不共用一个全局缩放因子,而是按块分组,每组单独算 scale。这样个别离群值只会污染自己所在的块,不会把整层动态范围拉爆。累加过程保持在更高精度,避免长序列求和时的精度塌陷。

哪些算子必须留在高精度也有明确边界:MoE 门控、各种归一化、softmax 这类对数值范围敏感的算子,一律不进 FP8;注意力里的部分计算和输出层同理。低精度还被用在存储和通信上——专家分发与合并时传输的激活用 FP8,直接砍掉一半的全到全带宽。这一步和 4.2 的通信内核是配套的:带宽省一半,20 个 SM 才能撑住。

提示:FP8 收益的大头往往不在算力,而在带宽和显存。激活以低精度传输、低精度驻留,等于同时缓解了通信瓶颈和激活内存压力。

5. 踩坑排查:复现这套训练时最容易翻车的几个点

纸面上的机制和真跑起来的差距,基本都集中在数值稳定性、路由抖动和资源抢占这三类问题上。下面几条是照着这套设计做小规模复现时最常撞上的。

5.1 loss 在几万步后突然抬升

现象:前几万步 loss 曲线平滑,之后出现一次不可恢复的抬升,重启也回不到原轨迹。

原因:块级缩放的 scale 跟不上激活分布漂移。MoE 里不同专家的激活分布差异很大,某个专家在训练中期突然变成热点,它那一块的 amax 猛涨,旧的 scale 导致上溢,反向传播的梯度被截断成 inf 或 0。

解决:对每个块的 scale 做滑动平均而不是逐 step 硬替换;逐层打印 amax 直方图,定位是哪一层先异常;门控、归一化和输出层强制留在 BF16。V3 能全程无不可恢复峰值,靠的就是这类「关键算子不进低精度」的硬边界,而不是运气。

5.2 负载看着均衡,吞吐却在抖

现象:专家分配统计很均匀,但每步耗时波动 20% 以上,全到全流量忽高忽低。

原因:偏置更新速度 γ 给大了。偏置每步都在跳,路由结果在相邻两步之间来回翻转,同一个专家一会儿过载一会儿空闲,通信量跟着震荡。

解决:把 γ 降到千分之一量级起步;负载统计按整个 batch 算,不要按 micro-batch 算,样本量太小统计噪声大;把每个专家的 bias 和 load 打成曲线,看它是否收敛到一条水平线,而不是持续振荡。

5.3 通信内核抢了计算核的 SM

现象:单独测计算内核很快,合起来训练却只有一半吞吐,nvidia-smi 上看 SM 利用率上不去。

原因:全到全内核没有做 warp 专业化,占用的 SM 数量过多;通信块尺寸没调优,L2 被通信数据打爆,计算侧频繁 miss。

解决:固定少量 SM 专供通信(V3 是 20 个),其余全部留给计算;用 warp 专业化拆分发送、转发、接收三类职责;对通信块大小做自动调优并观察 L2 命中率。判断标准很简单:通信时间应该完全落在计算时间的阴影里,如果时间轴上能看到裸露的通信段,说明重叠没做对。

5.4 照搬 MTP 结构后推理显存暴涨

现象:训练正常,推理时显存比预期多出一大截,吞吐反而下降。

原因:MTP 模块被一起加载进推理图,且共享嵌入层和输出头没有做物理共享,每个深度都存了一份词表投影。词表投影是参数量最大的单层之一,多存几份立刻显存告急。

解决:推理阶段直接丢弃 MTP 模块,主模型独立运行;如果要用它做推测解码,单独起一条轻量草稿路径。训练阶段则要把最浅层(含嵌入)和最深层(含输出头)放在同一个流水线 rank 上,让共享参数和梯度物理共享,而不是靠框架的逻辑共享。

5.5 序列级辅助损失系数照旧给

现象:加回序列级平衡损失后,验证集指标掉了一截,负载却没见更均衡。

原因:主力均衡已经由偏置完成,序列损失只是防极端的安全阀。按以前调辅助损失的经验给 α,等于在已经平衡的系统上再压一个强约束,把路由自由度压死。

解决:α 从 0 或极小值起步,做单变量消融,确认它在「不加也不出极端」时才保留;同时监控单个序列内的专家分配熵,只有在熵确实过低时才需要它介入。

6. 设计取舍:所有节省都必须不改变表达能力

把这几层拆完,会发现一个统一的约束贯穿始终:每一项优化都必须是「表达等价」的。MLA 压缩的是缓存的表示形式,不是注意力的计算内容,位置通道被单独留出来正是为了不损失信息;无辅助损失均衡改的是路由排序,门控值仍取自原始亲和力,模型学到的专家偏好没有被污染;FP8 只用在数值不敏感的位置,门控和归一化一步不退;MTP 在训练时增加监督,在推理时被整段丢弃,主模型的推理图完全不变。所有省下来的钱,都是从不改变模型能力的地方抠出来的。

这也是判断一套训练优化能不能信的简单方法:把它关掉,看 loss 曲线是否还能对齐。V3 报告里的消融就是干这个的——MTP 关掉后基准分数下降、无辅助损失换成纯辅助损失后性能变差、FP8 与 BF16 的 loss 曲线需要肉眼对齐。如果一项优化开启后 loss 曲线出现系统性偏移,那它就不是优化,而是换了个模型。

真要做验证,我建议按这个顺序:先在小规模上把 FP8 关掉跑一遍基线,确认 loss 曲线形状一致;再单独打开偏置均衡,观察专家负载熵是否收敛;最后才上 DualPipe,因为流水线一开,前面所有数值问题都会被通信时序放大,定位成本翻倍。这套顺序看起来笨,但能把「数值问题」和「调度问题」分开归因。

注意:不要用最终 benchmark 分数验证优化是否有效,分数噪声太大。用固定步数的 loss 曲线对齐、每层 amax 分布、专家负载熵这三个指标,它们对机制是否被破坏更敏感。

我最早做 MoE 复现时吃过一次亏:为了让负载曲线好看,把辅助损失系数调大,训练集 loss 降得很漂亮,验证集一路走低,查了两天才发现是路由被压平、专家专业化消失。从那以后我每次动路由相关的东西,都强制先跑一遍「关掉均衡看负载熵、打开均衡看 loss 偏移」的对照,宁可慢两天,也不想再对着一条好看的训练曲线猜原因。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询