DeepSeek关键技术拆解:MoE、MLA、FP8与百万Token工程实践
1. 都知道它强但强在哪儿属于典型的外行看热闹内行看门道做模型训练和推理这么久我拿到一份技术报告第一反应通常是这模型效果又刷榜了。但 DeepSeek 这次给人的感觉完全不同光从标题就能感受到一股“Infra 仙人”的味道细粒度 MoE、MLA、FP8、DualPipe、GRPO、稀疏 Attention、百万 Token Agent这些词单独拎出来任何一个都是能让人折腾半年的工程难题而它居然把这一整套全部集成在同一个技术体系里。这不是单纯的模型创新这是一张基础设施级的硬核工程量表。这篇博文要做的就是把这些名词逐个拆开讲清楚每一项到底解决什么问题、为什么选这种方案、实际落地时有哪些容易翻车的坑。适合正在做大模型预训练、推理优化或 Agent 场景工程化的从业者参考也适合读过 DeepSeek 论文但没来得及细抠原理、渴望有人用大白话把关键设计串起来的朋友。我尽量少放公式多画场景多给实操心得保证每个概念后面都有你能直接拿去跟同事解释的说法。2. 细粒度 MoE专家数量不是越多越好关键是拆得足够碎2.1 传统 MoE 的计算经济学模型MoEMixture of Experts专家混合这几年能成为超大模型的主流架构核心逻辑其实特别朴素一个 FFN前馈网络层的参数量非常大但并非每个 token 都需要走完整个 FFN。MoE 的做法是把 FFN 拆成若干个“专家”子网络每个 token 只经过其中少数几个专家。这样理论参数量可以做得很夸张但实际计算量和显存需求却只取决于激活的专家数量。传统 MoE 模型如 Mixtral 使用 8 个专家每个专家保留完整的 FFN 维度token 挑选 top-2 专家。这个方案在 7B 左右规模的模型上表现还不错但在更大规模下会遇到一个尴尬的矛盾如果专家数量少路由组合的自由度不够每个 token 都被迫跟大量其他 token 共享非常粗糙的专家表示如果专家数量多每个专家又需要跟完整维度匹配参数总量膨胀得非常快通信和缓存开销也会失控。2.2 细粒度专家到底切出了什么效果DeepSeek 的细粒度 MoE 思路本质上是把“专家变多”和“每个专家变小”这两件事同时做。它不再让每个专家拥有一个接近标准 FFN 的隐层宽度而是把标准 FFN 的中间维度切分成很多份每份单独作为一个专家子网络。这样在总参数量不变或略增的情况下可用专家的数量可以提升一个数量级而每个 token 的激活足迹反而变小了。具体来说DeepSeek 的 MoE 层里设置了上百个路由专家每个专家中间维度只有 512 左右token 激活前几个专家再加上两个共享专家始终参与计算。这种设计与传统 MoE 的差异如果用生活化类比就像传统方案是每家餐厅只有川菜和粤菜两个大厨所有客人都只能二选一而细粒度方案是一家大型食堂里面有上百个专门做回锅肉、麻婆豆腐、清蒸鱼的窗口式小师傅每位客人只需要选六个窗口就能吃到更合口味的菜。路由的组合空间大了模型对 token 的个性化表达自然更强。2.3 细粒度专家真正难的点负载均衡与通信开销细粒度专家带来的第一个麻烦是负载不均衡。专家数量多了如果路由器总把热门 token 导向少数几个专家那几个专家就会成为热点GPU 利用率严重倾斜。传统 MoE 还可以用简单的 Softmax 归一化来缓解但几百个专家的场景下必须额外叠加负载均衡损失函数。这里有一个非常容易踩的坑负载均衡 loss 的权重系数不能设得太大否则模型会为了均衡而放弃真实的路由偏好最终表现是 loss 很难压低但专家确实都忙起来了系数太小又恢复两极分化。我交过不少次学费后总结的经验是预训练初期可以稍微加大系数稳住负载中后期逐步衰减到原来的三分之一甚至更低让路由偏好逐渐回到真正的语义模式上。第二个麻烦是通信开销。MoE 的 token 要按路由结果从当前 GPU 发送到专家所在 GPU专家越碎跨机的消息数量就越多通信效率会变得非常难看。这里常见的工程缓解手段是在卡间使用高效的 all-to-all 通信并把多个小专家的交换机放在同一个节点内减少跨节点消息。还要确保每个 batch 的 token 分配足够均匀否则 all-to-all 通信时会频繁出现等待。我的经验是如果集群的跨节点带宽不到 400Gbps细粒度专家收益会被通信严重抵消不是所有团队都适合直接照搬这套配置。3. MLA把 KV Cache 的体积摁下去长上下文才敢谈3.1 长上下文推理的主要瓶颈居然是缓存生成模型在推理时需要不断读取历史 token 的 Key 和 Value 参与注意力计算。如果把这些 K 和 V 每次都重新算一遍浪费就太大了所以工程上会把它们缓存在显存里这就是 KV Cache。问题在于上下文长度一上来KV Cache 的体积增长非常快。假设模型有 48 层每层都存 K 和 V每个 token 需要存储几千个浮点数那么 32K 上下文就要几百 GB 显存这还没算参数本身。百万 Token 场景下全量 KV Cache 只能用恐怖来形容。这事儿的本质是注意力状态矩阵的信息密度太低。每个 token 的 K 和 V 其实高度冗余相邻 token 之间的表示在很多维度上都很接近把它们全部独立存下来是巨大的浪费。MLAMulti-head Latent Attention的核心思想就是先把这个矩阵压缩成一个更低的潜在维度缓存时只存压缩后的隐向量等到真正做注意力计算时再从隐向量里恢复出完整的 K 和 V。3.2 低秩压缩的实现思路与实践细节MLA 在训练时给每个头的 Key 和 Value 加一个共享的低秩投影层把 K 和 V 联合映射到一个低维空间里。举个例子假设原本每个注意力头的配置是 128 维把 K 和 V 拼接后做一次线性变换压缩到 128 维或者比原来低得多的 latent 维度缓存时只需要存这个 latent。实际效果上KV Cache 可以被压缩到原来的十分之一左右这是一个非常可观的数字因为 KV Cache 是长上下文推理成本的大头。这里有个技术细节值得特别注意RoPE旋转位置编码和低秩压缩天生有冲突。RoPE 需要把旋转矩阵作用到 K 和 Q 上这就意味着 K 必须是原始维度才能跟旋转矩阵相乘。如果直接把 K 低秩化位置信息就会变得一团糟。DeepSeek 的做法是把 RoPE 部分从低秩压缩的路径里拆出来单独处理或者通过某种解耦 is 方式让它依然保留位置感知能力。我自己的复现经验也验证了这种做法千万别图省事把 RoPE 作用在压缩后的低维向量上效果会立刻报警还是老老实实分别处理更稳。3.3 推理时重参数化让 MLA 不只省显存还省计算MLA 的真正有趣之处不只是缓存变小它还可以在推理阶段进行数学上的重参数化把一部分矩阵乘法提前融合掉。推理时权重和低秩投影矩阵是静态的我可以在加载模型时先把 W_Q 与低秩投影 K 侧的矩阵乘起来生成一个融合后的 query 变换矩阵。这样在推理阶段每个 token 计算 attention score 的时候就不需要先把低秩 latent 展开成完整 K直接在低维空间里做点积计算量又省掉一大截。实际部署的时候还要注意 FlashAttention 这类内核优化与 MLA 的适配。FlashAttention 本身是为标准 K/V 设计的高效算子如果你直接把 MLA 的低秩 latent 塞进去未必能享受到全部优化收益。很多团队的做法是先把 latent 恢复成完整 K/V再走 FlashAttention这样 KV Cache 的节省依旧保留计算端也不会损失太多。我自己在工程上还会把低秩压缩后的 latent 用连续内存块缓存避免每次生成时做碎片化的内存寻址。3.4 一个容易被人忽视的收益长上下文训练稳定性MLA 带来的另一个隐性收益是训练稳定性。KV Cache 是训练前向传播中的显存占用大户压缩之后同样一台 80GB 显存的 GPU可以放进更大的 batch size这直接利好梯度估计的质量。尤其是做超长上下文预训练时不需要用一堆梯度检查点换显存前向传播的计算路径也不会被打断太多次。长序列训练最怕的就是为了显存把序列切碎导致注意力片段之间的上下文信息断裂。MLA 让整段处理更容易训练损失的收敛曲线自然更好看。4. FP8低精度训练不是单纯把精度降低而是一场精细微操4.1 FP8 数字格式的两副面孔FP8 在训练中的应用不是简单地把 float32 换成 float16 再换成 float8 这么一路降级。FP8 实际上有两种格式E4M3 和 E5M2。E4M3 有 4 位指数和 3 位尾数动态范围相对有限但精度更细E5M2 有 5 位指数和 2 位尾数动态范围大但尾数精度低。工程上通常用 E4M3 来表示权重和激活值用 E5M2 来存储梯度和一些中间累加状态。这两种格式必须混搭才能既保证动态范围容纳得下不同数量级的数据又不至于在细节上损失太多。这个选择背后的逻辑跟人类记账很像。日常记录开销用精确到分的账本合适统计整年开销时用一个能装大数的总账更合适。你不可能只为了方便大数字就放弃小额记录的精度也不可能只盯着生活细节而不管全年汇总。FP8 训练里的权重、激活值、梯度正好处在不同数量级所以必须对它们区别对待。4.2 混合精度训练的关键缩放因子与主权重FP8 训练的核心技术框架是主权重和优化器状态依然保留 BF16 或 FP32计算图中的正向和反向传播用 FP8梯度累积在更高精度下完成。这样做的原因是避免误差在整个训练过程中不断积累。FP8 的数值区间虽然有一定动态范围但也远无法覆盖整个训练过程中出现的量级变化所以每个 tensor 前都要加一个缩放因子把数值映射到合适的区间。缩放因子的设置不是一成不变的很多实现会采用延迟更新策略根据最近的梯度统计动态调整。在实操中要注意的不是“该不该用 FP8”而是“缩放因子到底怎么更新”。把缩放因子设得太小小梯度会被直接抹成 0设得太大数值会溢出成 Inf然后再反向传播时变成 NaN。我见过最典型的 FP8 训练事故就是 loss 在某个节点突然暴跳成一个极大值然后怎么都回不来。排查下来绝大多数是因为异常值没有防护。改进方法是在计算缩放因子的过程中加入基于百分位数的剪枝把极端的 outlier 单独处理掉只让缩放因子反映常见数值范围。4.3 FP8 到底能赚到什么算力和显存的双重红利FP8 的收益主要体现在两块。第一是算力现代 GPU 的 FP8 算力通常是 FP16 的两倍左右训练侧的理论峰值 token 吞吐能大幅提升第二是显存带宽和容量同样大小的 Cache 数据用 FP8 存储能减少一半占用这在推理侧尤其有价值。不过 FP8 不是所有场景都是无损的。敏感层比如输出头、某些归一化层精度波动的影响往往被下游放大不要一刀切全部压到 FP8我倾向于把这些关键处保留高精度计算。我实际测试下来FP8 在预训练大语言模型时的收敛曲线绝大多数时刻和 BF16 没有肉眼可见的差异但偶尔会在比较高的学习率阶段出现短暂偏离。这需要你接受一定程度的波动而不是一看到偏离就立刻调回高精度。另外要检查你的推理框架是否真的用了 FP8 的 tensor 核心很多框架只是格式上存成 FP8实际运算还是被反解回 FP16那样只省了显存没赚到算力属于典型的吃哑巴亏。5. DualPipe流水线并行的气泡靠双向调度来堵5.1 流水线并行的老问题每个 GPU 都在等别人模型太大单张 GPU 放不下时就必须把不同层切到不同 GPU 上这就是流水线并行。流水线并行的直观思路是第一张卡算完第一层把结果传给第二张卡第二张卡算第二层以此类推。然而实际上因为反向传播要依赖前向的结果如果一个 GPU 没有下一个 GPU 传来的数据它就只能干等着。这些等待时间被称为“气泡”一个常规 4 级流水线的气泡比例能高达 25% 甚至更多四分之一算力白白浪费在空转上。传统的解决方案是用更小的 microbatch 穿插让每个 GPU 尽早进入下一批数据计算。但穿插是有上限的因为每个 GPU 的前向和反向计算不是完全独立的。反向计算开始之前必须确保整个前向的中间结果都保存在显存里于是显存占用又成了新的瓶颈。5.2 双向调度减少等待的几何思考DualPipe 的做法很有几何直观性。它把流水线中的所有计算块分成两组一组从初始方向第一层方向开始调度另一组从末尾方向最后一层方向反向调度。你可以理解为一条公路上双向同时发车避免了所有车辆都从同一个入口进入后一路拥堵到尾。在双向调度的实现中每个计算块都被定义为一个包含前向和反向的单元调度器会把不同方向的块交错排列使得每个 GPU 在前一批次和后一批次之间的等待时间尽可能重叠。结果就是气泡比例比传统流水线明显下降特别是在深层模型、小 microbatch 的场景下DualPipe 的收益尤其明显。5.3 通信与计算的重叠让 GPU 变身多面手DualPipe 单独还能解决通信瓶颈问题。传统流水线并行里GPU 在等待通信时计算核心是闲着的。DualPipe 通过调度顺序的优化让一部分 GPU 在等待接收远端数据的同时先处理本地已经算好的另一块任务。这就相当于 CPU 的多线程GPU 也不再是一个只会线性执行任务的机械手。实际操作中通信和计算重叠的效果非常依赖通信库的实现和网卡型号。我建议在部署 DualPipe 之前先单独跑一遍 all-reduce 基准测试明确知道通信耗时和计算耗时之间的比例。如果通信耗时远大于计算耗时那么任何调度优化都很难填满空隙这时候优先要解决的是硬件互联问题。如果是计算耗时远大于通信耗时DualPipe 对吞吐的提升就比较有限瓶颈根本不在流水线上。5.4 DualPipe 的适用边界与我的调参心得DualPipe 并非万金油。如果你的模型只有 8 层切成 4 个 stage 后每个 stage 只有 2 层双向调度带来的收益会被微小的通信开销淹没。我的经验是DualPipe 更适合 60 层以上的大模型且 batch size 比较大的训练任务。调参时主要关注两个量microbatch 数量和流水线 stage 数量。两者之间的比例会影响气泡比例通常 microbatch 数量至少是 stage 数量的两倍才能有效隐藏等待。我先用小规模试跑用 nvidia-smi 看 GPU 利用率如果有一张卡的利用率周期性掉到 60% 以下就说明调度参数还有问题。6. GRPO强化学习不一定需要 Critic数学推理照样能训6.1 RLHF 的传统路线Critic 模型又大又难训大模型的强化学习微调业界最常用的算法是 PPO。PPO 里除了有策略模型Actor之外还有一个价值模型Critic用来估计状态的价值函数。问题在于Critic 模型通常与 Actor 同规模这就意味着训练时显存开销几乎翻倍训练复杂度也直线上升。Critic 训得不好还会给策略模型提供错误信号直接拖慢整个强化学习过程。我之前折腾过不少 PPO 的调参实验最大的痛苦就是决策这个 Critic 的学习率该放在什么范围以及它的 loss 和 Actor 的 loss 怎么平衡稍不注意整个训练就飘了。6.2 组内相对奖励用一个样本组替代整体价值估计GRPOGroup Relative Policy Optimization的思路是既然我们用规则奖励或一个轻量奖励模型就能给出每个回答的分数那为什么还要一个完整规模的 Critic 呢GRPO 的做法是对同一个提示词让策略模型生成多个回答组成一个组然后基于组内所有回答的奖励做归一化。比如一个提示词生成 4 个回答分别打分 10、6、4、0那么得分 10 的那个回答就是相对优势的GRPO 会让策略模型加大生成类似回答的概率得分 0 的则减少。这种做法在数学和推理类任务上天然适配因为这些任务的答案可以通过规则或自动验证给出确定性奖励不需要 Critic 去估计模糊的价值函数。在与可验证奖励结合的时候GRPO 可以做到每个样本组内部就完成优势函数估计训练起来非常稳定。对我来说它最爽的地方是显存占用降下来了训练吞吐提上去了而且不用再纠结 Critic 的主权重到底存 BF16 还是 FP32。6.3 采样组大小与奖励缩放的经验GRPO 里最需要调的参数是采样组大小 G。G 太小组内奖励的相对比较样本不足方差会很大训练不稳定G 太大虽然方差降了但训练吞吐急剧下降因为同一份 prompt 要生成太多回答。我大部分实验里 G 设置在 4 到 8 之间比较合适。如果任务奖励特别稀疏比如大部分回答都是 0 分偶尔出现一个 1 分那不妨先把奖励做一次缩放让正样本和负样本的差距更明显这比盲目加大 G 有用得多。另一个容易被忽略的点是奖励模型的尺度在不同批次之间可能会有漂移。GRPO 是完全基于组内相对比较的这个特性其实天然免疫奖励分布的绝对变化。所以我在实践中很少为奖励模型做额外归一化只要保证同一组内的回答用的是同一个奖励模型打分就行。这里也提醒大家如果奖励数据出现明显异常值最好先单独清洗奖励集合可别让一个错误奖励把整个组相对排序扭曲了。6.4 GRPO 与长推理链的配合GRPO 在长推理链任务上能发挥作用还有一个依据是模型生成的推理链可能很长但过程中的中间步骤不一定每个都有可验证的奖励。组内相对排序在整体得分上做文章就行不需要细粒度的过程估计。不过长链任务里一个常见问题是模型为了获得更高奖励而过度冗长、绕远路最后分数虽然没变但生成长度膨胀得很厉害。这个问题需要在奖励函数里加上一个长度惩罚或者在采样组里设计一个效率和正确性的联合指标。我一般会把长度惩罚系数调得比较温和避免模型为了省几个 token而砍掉必要的推理步骤那样反而会牺牲准确率。7. 稀疏 Attention 与百万 Token Agent长上下文不是用来炫技的7.1 全量 Attention 的复杂度悬崖传统 Transformer 的 Attention 计算量随序列长度平方增长。上下文从 32K 扩到 1M计算量要涨 900 多倍显存开销更是让人绝望。许多人对长上下文还停留在“显存不够”的认知里但实际上即便你把显存堆到足够大计算速度也会慢到无法接受。只用全量 Attention 支撑百万 Token无论在训练还是推理侧都是一个极其奢侈的选择。稀疏 Attention 的意思不是随机忽略几个 token而是设计一套聪明的策略让每个 query 只关注它最需要的那一小部分 token。理论上只要选对了 token稀疏注意力可以接近全量注意力的表达力但计算量只和选择集合的大小成正比而不是整体序列长度成正比。7.2 DeepSeek 的稀疏 Attention 设计逻辑先粗选再精选具体到实现上DeepSeek 的稀疏注意力方案并不是简单使用局部窗口加固定全局 token 的老路子而是建立多阶段的 token 选择机制。第一步用一个比较轻量的模块从全部 token 中粗筛出一批候选第二步再用更精细的逻辑在候选集合里做 attention。这种先粗选再精选的流程就像大型会议的第一轮简历筛选和第二轮结构化面试目的是用少量计算先把最相关的对象圈出来再把宝贵的高精度计算留给最有希望的候选。这种方案必须处理好训练和推理的一致性。如果训练时用粗选推理时用精选两边选出来的 token 分布不一样线上效果就会断崖。DeepSeek 的路线是在训练阶段就模拟推理时的选择逻辑缩小二者之间的漂移。这里我也多提醒一句如果你的稀疏方案是自己设计的一定要写一组对齐测试分别记录训练集和推理集里每个 token 被选中的频率分布差异超过几个百分点就该排查选择模块的输入特征是否一致。7.3 百万 Token 的真正价值在于 Agent 化支持百万 Token 上下文最直接的应用不是让模型简单回答一篇文章的问题而是把它变成一个能够同时处理大量工具调用、多轮检索、多文档对比的 Agent。百万 Token 意味着 Agent 可以一次性把完整代码库、几十份报告、数百条日志全部塞进上下文让模型在全局信息的基础上做决策。这和我们以往“每轮检索后再汇总”的 RAG 架构完全是两种体验前者更像一个拥有超强工作记忆的专家后者像一个需要反复翻文件夹的实习生。不过从工程角度看百万 Token 给 Agent 带来的最大挑战已经不是模型能力而是工程链路的稳定性。上下文越长模型对关键信息的注意力会被稀释推理链路中越容易发生 Error 级事故。我正在使用的一种实践是把长上下文切成多个分段每个分段先让模型做局部摘要或标签提取再由一个临时聚合器统一分析。这样既利用了稀疏 Attention 带来的大上下文容量又避免把所有垃圾信息一股脑灌进最终决策入口。7.4 长上下文评测中的隐藏陷阱Position Bias做长上下文模型最怕被公开评测榜单忽悠。经典的 Needle in a haystack 测试是在长文本里埋一个关键句子看模型能否找到它。这类测试对位置非常敏感。很多模型在上下文开头和结尾的召回率高中间部分直接断崖。模型堵住了中间位置的缺陷后榜单分上涨非常快但真实场景中用户的信息恰恰是随机分布在中间区域。我在评测时会给同样的内容做三种不同的位置排列关键信息在前部、中部、后部。只有三种位置的召回率都保持在可接受水平我才会认为这个模型真正掌握了长上下文能力。你可以把这一套自动化脚本跑进回归流程频繁发布新版本时特别好用。另外千万不能只测单次检索要多轮连续提问模拟 Agent 多次思考后继续读取上下文的情况因为每次读取都会改变 attention 分布容易复现定位丢失。8. 常见问题与避坑实录8.1 MoE 的显存碎片化与专家热点细粒度 MoE 在训练时专家参数分散在不同 GPU 上每个 expert 的访问频率又不一样容易造成显存碎片化。我的做法是预分配一个统一大小的 expert 存储池避免反复动态申请显存。同时在数据加载阶段加入序列长度均匀化保证每个 batch 的 token 分布接近从而降低专家热点的概率。问题现象排查与解决专家热点某几个 GPU 显存或算力利用率远高于其他增加负载均衡 loss 权重打乱专家分配顺序显存碎片训练到一半 OOM但整体显存余量还很高使用显存池预先分配关闭过多动态缓存all-to-all 阻塞GPU 利用率周期性掉坑检查是否有慢节点调整通信拓扑或切分策略8.2 FP8 训练中的 Loss SpikeFP8 训练中 loss 偶尔飙到天际最容易被误认为是“模型发散”而急刹车降低学习率。实际上很多 spike 只是少数异常数值越过了 FP8 的动态范围导致梯度污染。这时候不要急着调学习率先关闭几个分钟级内的更新打印当前各层权重和梯度的绝对值分布看看最大值是不是已经达到 E4M3 的边界。如果确实触顶把对应层的缩放因子调大一个数量级并给该层换用 E5M2 存储通常能稳定下来。我还建议在训练日志里加入缩放因子的历史曲线。如果某个缩放因子持续单调上升说明该 tensor 的数值分布正在发生漂移模型可能已经进到一个异常的参数空间这时候要回滚到最近一个 checkpoint 而不是硬扛。8.3 长上下文 Agent 的项目落地建议如果你的业务真要把百万 Token Agent 用到生产环境别指望一个精简模型一把梭。我会按下面三层来搭架构底层用稀疏 Attention 的长上下文模型做全局记忆中间层用一个中等模型做工具调用与结果过滤上层用一个更小的模型做决策融合。这样长上下文的优势不会被单次决策的小模型给拖死同时价格和延迟也更可控。此外为了确保线上安全一定要加一个“上下文裁剪服务”定期把模型已经完全消化过的陈旧信息折叠成摘要防止 Agent 陷入上下文无边界膨胀的失控状态。9. 我个人实操下来的最终感慨写到这里最深的体会其实不是 DeepSeek 有多聪明而是它把每一个工程决策都放到了整个训练和推理链路里通盘考虑。细粒度 MoE 是为了省算力MLA 是为了省显存FP8 是为了用满硬件DualPipe 是为了让通信别浪费算力GRPO 是为了把强化学习成本降到可接受范围稀疏 Attention 是为了让长上下文真的可用。单看每一项都是局部优化合在一起就是一套基础设施级的系统能力。我过去做技术选型经常只盯着单点加速、单点省显存却忽视了上下游的匹配结果往往是省下来的资源被别处的排队和开销又吃回去了。最后再分享一个小技巧读这类技术报告别只盯着模型性能曲线真正值得反复看的是它们对显存、带宽、延迟的拆解表格以及 baseline 之间的消融对比。那里面藏着大量踩坑之后得出的经验。希望这篇文章里的拆解和踩坑记录能让你在规划设计自己的训练推理方案时少走几步弯路。