
简介DeepSeek-V3技术报告以PDF形式提供面向具备深度学习与自然语言处理基础的研发人员和技术爱好者旨在帮助读者理解大规模混合专家MoE语言模型的设计思路与工程落地。报告围绕671B总参数、单Token激活37B参数的DeepSeek-V3系统阐述多头潜在注意力MLA、DeepSeekMoE架构、无辅助损失负载均衡策略和多Token预测训练目标并给出预训练、后训练与硬件部署的完整方案。资源压缩包仅含1个PDF文件大小约1.8MB阅读方便适合存档与反复查阅。目前已有653人学习下载读者可从MMLU-Pro、GPQA-Diamond、MATH 500、AIME 2024、Codeforces、SWE-bench Verified等基准结果中直观比较其与开源及闭源模型的性能差异。报告还深入介绍FP8训练、DualPipe计算通信重叠、跨节点全对全通信等基础设施优化以及低精度存储与通信带来的成本优势同时也客观指出大规模部署对小型团队的负担并展望借助更先进硬件持续降本增效、靠近人工通用智能的发展路径。1. DeepSeek-V3的技术报告到底在解决什么问题混合专家大模型从“能训练”到“用得起”DeepSeek-V3的技术报告写的不是又一张精度排行表而是把“用更少的钱把大模型训出来、跑起来”这件事拆成了可以照着落地的几条技术路线。混合专家MoE这个方向并不新但这是第一次有开放权重的大模型把总参数推到六百多亿、而每个 token 实际激活的只有三十多亿——这个“总参数 / 激活参数”的差距直接决定了训练账单和推理延迟。报告里真正值得反复读的是怎么用稀疏激活换来接近稠密模型的能力怎么用 FP8 混合精度把训练成本压下去以及 MLA 怎么把长上下文推理的显存大头砍掉。给预算有限但又想自己训或部署大模型的人这篇笔记按这条技术线把原理、参数、落地细节和踩坑点一次讲清楚。2. 架构选型为什么这样走从稠密模型到稀疏专家DeepSeek-V3的关键设计2.1 稀疏激活这笔账总参数决定显存激活参数决定算力先区分两个容易混淆的概念。稠密模型里总参数和激活参数是同一个数每个 token 都要经过全部参数计算而 MoE 模型把参数拆成很多个专家模块每个 token 经过一个路由层只被送到少数几个专家里。DeepSeek-V3 这类规模模型的常见做法是把专家数做得很多、每个 token 只挑一部分走再放几个共享专家兜底保证通用能力不丢。于是总参数可以堆得很大但每次前向传播真正参与运算的激活参数反而只有总参数的零头。这个结构带来的第一个好处是单位 token 的计算量大幅下降。第二个好处是模型容量没缩水——不同的 token 可以落在不同的专家子集里专家之间形成隐式分工。代价也有所有专家权重都要放在显存里推理时一张卡放不下必须拆到多卡训练时专家之间的通信量比稠密模型高出一截。部署前算账我一般先看两个数。def moe_compute_estimate(total_params, activated_params, seq_len): 估算单序列一次前向传播的 FLOPs单位换算成 TFLOPs。 total_params: 总参数量 activated_params: 单 token 实际激活的参数量 seq_len: 输入序列长度 flops 2 * activated_params * seq_len return round(flops / 1e12, 1)这里系数 2 是前向传播里乘加操作的大致倍数训练时还要再乘 3反向传播和权重更新大约是前向的两倍多。真正落到具体机器上这个值还要除以硬件利用率和并行规模才能得到时间估算。实际做容量规划时先看权重文件多大再约束激活参数量对应的算力需求最后才算需要的卡数。对比项稠密模型稀疏混合专家模型单 token 算力随总参数线性上涨随激活参数上涨权重显存必须全部加载必须全部加载训练账单高低一大截但多出通信开销路由调参无有且直接影响负载均衡2.2 MLA 怎么把 KV 缓存降下来从多头注意力到潜在向量压缩注意力机制在推理阶段有个绕不开的开销每生成一个 token都要把历史 token 的 Key 和 Value 缓存下来供后续计算注意力分数用。多头注意力MHA里缓存大小随层数、头数、序列长度线性增长。上下文一长KV 缓存比权重还占显存这也是很多长对话场景推理吃紧的主要原因。GQA分组查询注意力是常见的折中方案多个查询头共享一组 Key/Value缓存能降不少。DeepSeek-V3 的 MLAMulti-head Latent Attention走得更远它不缓存完整 Key/Value而是先把它们压缩成一个低秩的潜在向量存下来计算注意力时再解压回高维。你可以理解成GQA 是减少备份份数MLA 是直接缩小备份本身。对长上下文的推理这个差异非常可观。我在做显存预估时会用一个简化模型对比两者。def kv_cache_bytes(seq_len, hidden_dim, num_layers, kv_rank, attn_typemha): 粗算不同注意力结构在 decode 阶段的 KV 缓存占用。 mha: 每层存全量的 K/V gqa: K/V 头数减少这里用 group 数近似 mla: 每层只存一份压缩后的潜在向量 返回值单位是字节按 2 字节一个元素计算。 if attn_type mha: elements_per_token 2 * hidden_dim * num_layers elif attn_type gqa: elements_per_token 2 * (hidden_dim // 4) * num_layers else: # mla elements_per_token 2 * kv_rank * num_layers return elements_per_token * seq_len * 2这里 kv_rank 是 MLA 压缩后的向量维度它决定了缓存缩小的上限。实际部署里KV 缓存还有分块管理、内存对齐等额外开销上面的值只是做数量级判断。如果你从 MHA 结构切到 MLA 结构同一个长上下文任务KV 部分占用的显存通常能降一个数量级省下来的空间可以用来开更大的并发或者塞更长的上下文。2.3 无辅助损失负载均衡少一个损失项反而更稳MoE 训练里最经典的问题是“专家崩溃”路由层总把 token 送到少数几个专家大部分专家饿死模型容量白白浪费。过去的解决方案是加辅助损失惩罚那些负载过高的专家让路由分布均匀一点。这个方法有效但引入了一个很敏感的超参数——辅助损失的系数。调大了模型为了迎合均匀分布而牺牲任务质量调小了专家崩溃压不住。很多团队在这个系数上反复试错耗掉大量训练预算。DeepSeek-V3 的做法是无辅助损失负载均衡。训练过程中动态维护每个专家的负载统计给过热的专家加一个负向偏置给过冷的专家加正向偏置让路由倾向于冷门专家。这个偏置不参与损失计算只在路由打分时生效训练完了固定下来不影响推理。这个设计在工程上最直观的好处是可以少调一个超参数训练过程少一个不稳定因素。主流 MoE 训练里常见的辅助损失方案和无辅助损失方案的差异可以做这样一个对比。对比项辅助损失方案无辅助损失方案均衡控制方式损失函数里加惩罚项路由打分加动态偏置关键超参数辅助损失系数偏置更新步长调参成本高系数敏感低对模型质量的影响系数过大容易损伤能力相对更直接作用在路由分布上这里要注意无辅助损失不等于不均衡治理它只是把治理手段从“反向传播的梯度”挪到了“路由决策的前置逻辑”上。实际复现这类结构时还是要监控每个专家的 token 分布一旦出现个别专家长期超载说明路由初始化或数据分布有问题偏置调整救不回来。3. 经济训练怎么落地FP8 精度、MTP 多 token 预测与训练成本核算3.1 FP8 混合精度哪些计算可以用 8 位哪些必须留在 16 位大模型训练成本的大头是矩阵乘法它占了绝大部分算力和显存带宽。FP8 精度把每个参与矩阵乘的数压到 1 字节相比 BF16/FP16 直接省一半显存带宽需求也跟着降。DeepSeek-V3 训练中用 FP8 做主要矩阵乘但注意力计算、归一化层这些对精度敏感的部分保留在 BF16。为什么不能全量降到 FP8因为 FP8 的动态范围窄数值很容易溢出或下溢。激活值和梯度经过深层的归一化后分布很宽直接全量化训练容易发散。常见的做法是按张量或者按通道做缩放把数值范围映射到 FP8 可表达的区间里再配合梯度缩放。这个“缩放因子”本身就是一个需要反复验证的配置。我在复现这类训练流程时精度配置一般长这样。# 训练配置里的精度开关展示的是逻辑结构不是某个具体版本的配置 train_precision { linear: fp8, # 主要矩阵乘用 FP8 attention: bf16, # 注意力分数计算保留 BF16 layernorm: bf16, # 归一化层保留 BF16 embeddings: bf16, # 词嵌入保留 BF16 fp8_scale_mode: per_tensor, # 按张量缩放稳定优先 grad_scaling: True, # 梯度缩放防止下溢 }这里 per_tensor 表示整个张量共用一个缩放因子实现简单、额外开销小。per_channel 更精细但计算和存储缩放因子的成本更高。训练稳定性出问题时优先把靠近输出层的线性层从 FP8 拉回 BF16通常能压住异常。FP8 训练还有一个硬件门槛需要 GPU 支持 FP8 算子驱动和算子库版本也得匹配。硬件不支持时强行开 FP8计算会退回高精度模式不仅没省时间还可能因为算子切换增加额外延迟。所以上 FP8 训练前先做一个小规模 smoke test确认算子是真正跑在 FP8 上而不是默默回退。3.2 MTP一次前向多预测几个 token训练和推理两头吃收益DeepSeek-V3 在训练目标上做了一个不那么显眼但很有效的改动多 token 预测MTP。传统语言模型训练是给定前文预测下一个 tokenMTP 是在主干之外叠一个小模块让它同时预测当前 token 后面的第二个、第三个 token。代价是多维护一个轻量预测头收益是每个训练样本的梯度信号更密模型能更快模到更长范围的依赖关系。这个模块在推理阶段还能二次利用。MTP 模块本身是一个小型预测器可以在正式解码前快速生成一段候选 token然后用主模型做一次并行校验通过的直接采纳。这是投机解码的思路MTP 天然就是为它准备的。配置里相关参数拆开看是这个意思。# 示例参数用来理解报告里 MTP 和 MoE 的配置项不是某个开源仓的完整配置 model_config { n_routed_experts: 8, # 路由专家数量实际模型会大得多 n_shared_experts: 2, # 共享专家数量 top_k: 2, # 每个 token 实际选几个路由专家 aux_loss_coef: 0.0, # 0 表示不用辅助损失 mtp_layers: 1, # MTP 模块堆叠层数 mtp_predict_n: 2, # 一次预测当前 token 之外的额外 token 数 }mtp_layers 和 mtp_predict_n 的取值要平衡收益和开销。MTP 模块叠太多层训练前向的时间会明显变长但预测精度的边际收益递减。mtp_predict_n 加大时投机解码的候选长度变长校验的并行收益更大但也意味着一次错一个 token 整段作废的概率变高。我在实际调的时候一般从 1 层、额外预测 1 个 token 开始用训练吞吐作为基线看增加预测数量后有效 token 吞吐是否真的涨。真正的收益要跑到训练中段之后才明显短训练很容易被噪声掩盖。3.3 训练成本到底怎么算FLOPs、硬件利用率和通信开销经济训练的宣传点往往是“多少卡训出什么效果”但落到自己的项目要算的账就三笔总算力、硬件利用率、数据成本。总算力可以用激活参数估算MoE 模型每个 token 的 FLOPs 由激活参数决定和总参数关系不大。训练总 FLOPs 大约是激活参数、token 数和常量系数一般取 6的乘积。def train_flops(activated_params, num_tokens): 估算训练总 FLOPs。 activated_params: 激活参数量 num_tokens: 训练 token 数 return 6 * activated_params * num_tokens这是把前向、反向、参数更新一起折算的惯例算法。实际值会因为序列长度、batch size、专家路由分布有偏差但用来横向对比不同模型架构的训练成本足够准确。硬件利用率是第二笔账。MoE 训练里有大量 all-to-all 通信token 在专家之间搬来搬去通信占比比稠密模型高。这意味着同样的 FLOPsMoE 的端到端训练时间不一定按理论值直线下降通信瓶颈可能吃掉一半收益。我看到过的工程实践里通常的做法是让每个卡上的专家负载尽量均衡再把序列切成长度相近的 chunk减少通信频率。最后才是数据成本MoE 模型容量大需要的数据量往往比同规模稠密模型更多训练数据本身的采集和清洗成本要算进“经济”里。这也能解释为什么 DeepSeek-V3 的“经济”不是绝对的便宜而是把参数容量做大、单位 token 算力摊薄的策略。要复现这个路线不是买一堆卡把模型跑起来就行而是先算清楚自己的预算够不够支撑足够多的优质 token。4. 高效推理这样做显存预估、服务参数和长上下文的 KV 管理4.1 权重、KV、激活各占多少部署前的显存账部署 MoE 大模型时第一个要面对的问题就是权重根本放不进一张卡。总参数六百多亿的模型用 BF16 存储每参数占 2 字节光权重就要一千多 GB单卡普遍只有几十 GB 到一百多 GB 显存必须拆到多张卡上做张量并行。换 FP8 存储权重缩到约一半卡数需求跟着降。我的部署规划习惯是先把三块显存分开算权重、KV 缓存、前向计算时的中间激活。def plan_deployment(total_params, precision_bits, kv_bytes, activation_bytes, gpu_mem): 部署前的显存规划。 total_params: 总参数量 precision_bits: 权重存储位宽16 或 8 kv_bytes: 预估 KV 缓存 activation_bytes: 激活值临时占用 gpu_mem: 单卡显存 weight_bytes total_params * precision_bits / 8 total_bytes weight_bytes kv_bytes activation_bytes gpus_needed (total_bytes gpu_mem - 1) // gpu_mem return weight_bytes, total_bytes, gpus_needed这个过程里最容易漏的是 KV 缓存随着并发数翻倍。并发请求越多同时存在的 KV 缓存越大。权重是静态的KV 是动态的两者加一起才是真实的显存水位。中间激活在 prefill 阶段会冲高decode 阶段回落规划时留给它一点余量别卡得太死。存储形式大概权重占用常见部署方式BF16约 1300 GB多节点或多卡张量并行FP8约 700 GB 左右8 卡以上单节点4bit 量化约 350 GB 左右5~6 卡但要验证质量表格里的数字是粗算具体以实际权重格式为准。关键原则是显存够不够要看“权重 KV × 并发 激活 框架开销”四者之和只看权重一定会翻车。4.2 服务化参数连续批处理、Prefill / Decode 和吞吐量的取舍模型加载起来只是第一步对外提供服务时吞吐和延迟才是真正要被用户体感检验的。主流推理服务框架普遍支持连续批处理不等一个请求生成完再处理下一个而是把多个请求的 token 塞进同一个 batch谁有新 token 就算谁。这种模式能把 GPU 利用率拉高很多但配套参数要调。服务配置里几个关键参数的经验值我简单列一下。# 推理服务参数示意数值按业务场景调整 service_config { max_model_len: 8192, # 单请求最长上下文 max_num_seqs: 16, # 一个 batch 里最多几个并发请求 gpu_mem_util: 0.9, # 显存使用上限留出 10% 余量 block_size: 16, # KV 缓存块大小 swap_space: 2, # 给调度和临时激活留的空间GB }gpu_mem_util 是最先要动的参数。设得过高长上下文请求一起来就 OOM设得过低卡上大量显存闲置并发上不去。max_num_seqs 决定吞吐和单请求延迟的平衡点它越大批处理越充分但单个请求的排队等待时间也越长。block_size 影响 KV 缓存的碎片化程度块越小越省内存但管理开销越大。你在压测时如果发现显存没满但延迟高多半是 max_num_seqs 太小、请求排队严重如果显存频繁告警先看 gpu_mem_util 是不是压得太满再看是不是有长上下文请求把 KV 缓存挤爆了。prefill 和 decode 两种阶段的瓶颈不一样prefill 重计算看 GPU 算力decode 重访存看显存带宽。有的服务框架会把这俩阶段拆到不同资源上起步阶段没必要上这套先把 batch 调度调顺。4.3 长上下文场景MLA 省了一截剩下要管好的是“截断策略”MLA 把 KV 缓存压小以后长上下文推理的下限门槛低了不少但不代表可以无限开上下文。序列越长prefill 阶段的计算量越大首 token 延迟跟着涨。另一个隐蔽陷阱是用户请求里自带的历史对话过长如果不做截断要么超出 max_model_len 直接报错要么被框架静默截掉开头。处理长上下文的常见做法是分层管理系统提示词固定保留在上下文头部不让它被截断中间的历史对话按时间窗口保留最近若干轮更早的内容用摘要压缩成一小段。我一般会这样配置。context_policy { system_prompt: preserve, # system 始终保留 history_strategy: keep_recent, # 保留最近 N 轮 max_input_len: 4096, # 输入上限 summary: True, # 超长历史转摘要 }这里的核心逻辑是长度不够时优先牺牲中间历史而不是开头指令。很多翻车现场都是模型把 system 里的角色设定给丢了然后行为开始“人格漂移”。配置 max_input_len 别贪大长上下文的价值是能容纳关键信息不是把所有垃圾都塞给模型。对绝大多数业务4K 到 8K 的输入窗口已经能覆盖主要场景更大的窗口留给真正需要跨大量材料的任务。5. 避坑记录部署和微调 MoE 模型时最容易翻车的几个现场5.1 现象报错 OOM但按权重算显存明明够原因是显存规划只算了权重没算 KV 缓存、激活值和框架预留。尤其是有长上下文请求混进来时KV 缓存会瞬间膨胀。有时候 block_size 设得太大小请求也会占满整个块浪费显存。解决办法是先把服务参数调保守gpu_mem_util 从 0.85 起步观察一晚上显存水位再往上加同时把 max_num_seqs 调小限制瞬时并发再看请求里是不是有大量长输入有就要在接入层做长度限制和分片。排查步骤是按“权重 → KV → 临时激活 → 框架开销”逐项估算找到真正吃满显存的那一项再动手。5.2 现象长对话前半段正常后半段模型像“失忆”原因是上下文窗口满了以后截断策略把开头的重要信息裁掉了。默认截断通常是“从头截”先丢掉的是 system prompt 和早期对话设定。模型后面生成的每个 token 都看不到最初的任务约束能力自然崩。解决方法是自定义截断策略把 system 标记为不可丢弃中间历史按“越新越重要”的规则保留实在放不下的旧内容先过一遍摘要。还有一类原因是旋转位置编码在超长上下文下的外推能力不足长距离位置信息失真。这种情况要把 max_model_len 控制在验证过的范围内而不是按显存余量硬开。5.3 现象量化到 4bit 之后关键任务的质量明显下滑原因是激活值分布范围太宽按静态校准得到的缩放比例覆盖不了长尾数值。常见量化方案在校准时只用了较短的通用文本业务里的长输入、特殊格式、生僻实体一进来量化误差就暴露了。还有一个原因是部分关键层对精度极敏感比如输出层附近的线性和注意力分数计算这些层被压到 4bit 后误差被后续层放大。解决方法是先做“分层评分”把每一层单独量化在验证集上对比量化前后输出差异找出最敏感的那些层保留 8bit 或 BF16。校准集合要覆盖真实业务输入分布至少包含最长序列、最少见格式、最难的推理题。整体 4bit 方案用于内存受限场景可以接受但正式上线前必须在业务验证集上做全面对比不能只看榜单分数。5.4 现象8 卡部署但有几张卡负载明显低于其他卡原因是专家路由分布不均衡热门专家集中在某几张卡上冷门专家在别的卡上空转。MoE 的路由是数据驱动的训练数据里某些模式特别多对应专家就会被高频调用。再加上不同 batch 的序列长度不一致调度器分配不均进一步放大卡间负载差。解决方法是先看每张卡的 token 分布和专家命中次数确认是哪类专家过载。然后把热门专家尽量复制或拆到多张卡冷门专家聚到一块减少跨卡通信。在服务层面把请求按长度分桶或限制单 batch 的最大 token 数也能让卡间负载更平稳。这个问题不是一次配置能根治的需要根据线上真实流量不断重新分配专家。5.5 现象微调之后通用能力掉了越学越“窄”原因是 MoE 模型在特定任务上微调时梯度集中在少数几个专家上其他专家的权重几乎没动模型整体变成了“偏科”状态。辅助损失系数设得太低时这种不均衡更容易被放大。另一个原因是微调数据里任务单一模型为了拟合新任务牺牲了原有的推理和泛化能力。解决方法是微调数据里保持足够的通用语料比例拿领域数据和通用数据混着训每多少步插入一批通用数据。同时要在训练中监控各专家的负载变化如果某些专家权重更新异常快要考虑是不是学习率太高或者数据太偏。上线前用一组固定的通用评测集和领域评测集双验证通用分掉得明显就要回退训练参数。6. 把 DeepSeek-V3 接进业务的一个习惯先压测、再影子跑、最后盯长尾不管你是准备部署开源权重还是自己在它基础上做微调我建议都按同一套流程走先压测再影子跑最后盯长尾请求。压测解决容量问题影子跑解决质量问题盯长尾解决稳定性问题。影子跑的做法是让新模型和老模型同时处理同一批请求新模型的输出只记录不下发积累一段时间的对比结果。这一步能提前暴露很多离线评测看不出来的问题比如语气变化、格式偏好、敏感话题的措辞差异。# 影子流量对比的骨架脚本记录两路输出和延迟指标 def shadow_compare(old_service, new_service, sample_prompts): rows [] for prompt in sample_prompts: r_old old_service.generate(prompt) r_new new_service.generate(prompt) rows.append({ prompt: prompt, old_text: r_old.text, new_text: r_new.text, old_ttft: r_old.ttft, # 首 token 延迟 new_ttft: r_new.ttft, old_tps: r_old.tps, # 生成速度 new_tps: r_new.tps, }) return rows对比的重点不是平均分而是坏例和新模型比老模型差的那部分。平均指标会骗人少数几类长尾请求才是用户体感的决定性因素。我的习惯是先定义底线指标p95 首 token 延迟、p95 生成速度、错误格式比例这三个不合格直接回滚不进入人工评测。人工评测阶段再按业务场景打分优先看新模型是否引入“看似合理但实际错误”的输出。有次我赶进度跳过影子跑直接切流量结果新模型在特定格式的请求上稳定生成超长输出把下游解析全打爆花了半天才定位到是温度参数和 top_p 的交互问题。那之后我学到一个教训调度参数永远要配合真实流量验证离线评测再好看长尾分布一变就可能翻车。现在我的流程固定是先小流量影子跑三天让新模型在真实数据里自然暴露问题再决定要不要升级。希望这些经验和踩过的坑能帮到你。本文还有配套的精品资源点击获取