ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

重塑大模型推理数据流:DeepSeek V4 950 低精度优化全复盘

重塑大模型推理数据流:DeepSeek V4 950 低精度优化全复盘 刚开始接手 DeepSeek V4 950 的推理优化时我犯过一个典型错误——把注意力全放在权重矩阵的低精度转换上。FP8 权重、INT8 激活一套组合拳打下去显存确实降了但端到端吞吐几乎没有变化甚至在某些 batch 下延迟还涨了。真正的问题不在权重算得多快而在推理链路里张量怎么流动哪些数据在反复搬运、哪里在无意义地做格式转换、哪些算子因为低精度反而引入了额外的内存访问。这篇就完整复盘一下我们在 DeepSeek V4 950 上做整网统一低精度数据流优化的全过程包括方案设计、实测数据、精度保卫战和踩过的一个重要性能坑希望能给正在啃大模型推理优化的朋友一些参考。DeepSeek V4 950 这个规模的模型950B 级总参数激活参数约 35BMoE 架构推理瓶颈早就不在 compute 上了而在数据搬运。这不是理论推断是压测后的直接感受把 FP16 换成 FP8 之后理论算力供给远过剩可显存带宽、L2 缓存命中率、跨卡通信量却成了限制吞吐的硬约束。低精度优化的核心目标必须从“减少 FLOPs”变成“减少字节流动”。1. 为什么大模型推理的性能瓶颈出在“数据流”上1.1 算力与搬运的失衡任何算力系统都有一个计算强度arithmetic intensity的概念——每个字节从显存搬到计算单元后能支撑多少次浮点运算。以 H800 SXM 为例FP8 稠密算力大约是 1500-2000 TFLOPS 级别而 HBM 带宽在 3.35TB/s 左右。算下来哪怕要把算力全部喂饱每个字节的数据至少也要支撑几百次 FLOPs。大模型推理的尴尬在于权重虽然足够大但激活值、KV cache、中间张量才是真正频繁读写的对象。DeepSeek V4 950 在 decode 阶段每个 token 虽然只需要读一部分专家权重MoE 激活参数约 35B但 KV cache 和激活值却要反复经过桥接层、attention 层和多个专家网络。数据搬运量不会因为你把单个算子里的矩阵乘法算得快就减少它取决于整条链路上有多少字节从 HBM 拉进拉出。很多“权重量化方案”只看权重体积声称能减少 X% 的显存和带宽但真正压测时收益远低于预期就是因为中间激活、KV cache 和残差流的搬运仍然以 FP16/FP32 为主。你把矩阵乘法的输入改成低精度又把输出立即转回高精度等于只在一个很窄的窗口里省了带宽整体流动还是老样子。1.2 传统逐算子低精度的三个致命伤第一层问题是层间量化-反量化切换。逐算子做低精度时每个 Linear 之前需要做 FP32 scale 计算矩阵乘完又需要 dequant 回高精度给下一个算子用。一次在线推理链路里可能有几百个这样的切换点每一步都产生额外的 HBM 读写。看起来单个算子省了 50% 的带宽实际上省下的全被 quant/dequant kernel 的额外读写吃回去了。第二层问题是布局不统一。不同 kernel 对 FP8 等低精度数据的排布要求完全不一样有的要求 per-channel scale有的要求 per-block scale有的数据需要先做 NC 到 NK 的 layout 转换。这些转换 kernel 在 profile 里经常会被归到“others”类别容易被忽略但它们的代价是实打实的显存读写开销。第三层问题是精度失配。不是所有计算都对低精度免疫。残差连接、注意力 logits、MoE router 的 logits 都是精度敏感点一旦被无差别地压到低比特模型输出质量立刻崩。传统方案只能靠“人工找敏感层、单独保高精度”来补救但这种补丁式做法又会让数据流在高低精度之间来回切换前面说的切换开销全回来了。1.3 整网统一低精度数据流是什么一句话解释不是每个算子各自决定用什么精度而是全链路共用一套低精度主格式、一套缩放因子体系和一套张量内存布局。算子的输入直接吃上一个算子的低精度输出输出也保持低精度继续往下传中间不落地到高精度不重复计算 scale不做布局变换。这个理念和 CPU/GPU 上的“数据流处理器”设计有些类似——把注意力从“计算单元利用率”转移到“数据移动效率”。对 DeepSeek V4 950 这种 MoE 大模型来说统一数据流能带来的收益是结构性的每个 token 跨越多层、多个专家张量要被重复读很多遍只要每遍搬运的字节数都能降下来端到端的吞吐提升就会非常可观。2. 核心设计统一张量布局、缩放因子流与算子融合2.1 量化格式选型为什么选块级缩放低精度格式的选型直接影响精度和硬件效率的平衡。当前可选路线无非三种E4M3 的 FP8、INT8 加 per-tensor scale、以及 MXFP8 风格的块级缩放。FP8 E4M3 动态范围和精度都能兼顾但它的精度是“静态”的——整层一个 scale一旦某层激活值的数值分布起伏较大就容易出现溢出或者精度损失。INT8 更极端动态范围完全依赖 scale激活值里只要出现一个异常大的离群点per-tensor scale 会被拉得很大其他正常数值的有效位宽就会骤降。我们在 DeepSeek V4 950 上最终采用的是“类 MXFP8 块级缩放”方案每 32 个元素共享一个 8-bit scale配合 E4M3 尾数。这样既保留了 FP8 的低比特优势又不会因为某个离群值拉崩整个张量的精度。选择块级缩放的直接原因是实测数据per-tensor FP8 在做激活量化时95 分位误差比块级缩放高出一个数量级而且这种误差会在多层之间累积最后表现为下游任务的精度不可控。块级缩放相当于给数据流加了一层“局部自适应的保险”。2.2 统一缩放因子流在传统做法里每一个量化算子都会根据输入张量重新计算 scale这样做的好处是每层都能自适应坏处是层与层之间数值范围“各自为政”数据流中反复出现大的动态范围变化。我们的做法是设计了一条“主 scale 流”从网络入口开始通过校准集统计出一条全局激活 scale 曲线沿主干网络传递。Attention 内部的 logits 和 softmax 路径单独使用一组 scale因为这里的数值范围与注意力权重直接相关容易波动而 FFN/专家层的输入输出统一走主 scale。这样设计的目的很简单——避免每一层重新统计 scale 带来的计算开销同时减少层间数值范围突变带来的精度损失。这里要特别说明所谓“统一”并不意味着全模型一个 scale。那太粗暴了。实际落地是“分段统一”每个 Stage比如前 N 层内部共享同一套 scaleStage 边界允许做一次尺度切换。这个粒度既能减少切换点又不至于因为动态范围差距太大导致精度劣化。2.3 算子融合把量化-反量化节点彻底干掉统一数据流要想真正减少字节流动就必须在计算图层面把 quant/dequant 节点尽可能消除。我们重点做了三类融合RMSNorm 与量化融合。原本 RMSNorm 输出 FP32然后量化成 FP8 送给下一个 Linear。融合后 RMSNorm 直接在寄存器里完成 normalization并以 FP8 格式写回避免了 FP32 中间张量在 HBM 上落盘再被读回的过程。GELU/SwiGLU 与量化融合。FFN 里 Gate 分支的激活函数输出后立即进入另一个 Linear这个“激活输出-量化-矩阵乘”的路径也是显存搬运大户。融合后激活函数计算结果直接量化并保持低精度省掉一次整张激活的写回与重读。FlashAttention 输出直接低精度化。Attention 输出不再先写回 FP16 再做后面的 Linear而是在 kernel 内部完成 low-precision 转换直接以 FP8 块格式输出。这一步对 decode 阶段尤其有效因为单 token 的 attention 输出虽然不大但乘以 batch 数和层数后累计的搬运量非常可观。融合后的数据流可以简化成这样的链路输入 token - RMSNorm(FP8) - Linear(FP8) - 低精度残差更新 - Attention(FP8 输入FP32 logits 内部计算) - 输出 FP8 - MoE 路由(高精度 logits) - 专家 FFN(全 FP8)。整个主干上几乎没有 FP16 中间张量。2.4 张量布局统一带来的额外收益低精度数据流的另一个关键点是张量排布。不同硬件后端对 FP8 张量的内存对齐要求不同我们统一使用 128 字节对齐的 NC 布局并让所有相关 kernel 遵循同一约定从而彻底消除了 kernel 间的 layout 转换。省下的开销比想象中大。最初做 PPpipeline profile时发现layout 转换 kernel 的耗时能占到总 kernel 时间的 5% 到 8%。这还不算它引发的 L2 cache miss——一旦数据排布不连续后续算子读数据时的缓存命中率就会显著下降这部分隐藏开销在端到端性能里很难定位但体感非常明显。3. DeepSeek V4 950 落地的实测数据与性能瓶颈排查3.1 测试环境与基线设置压测环境是 8 卡 H800 80GB 节点模型配置为 DeepSeek V4 950950B 总参MoE 架构256 个路由专家8 个共享专家使用 Tensor Parallel8 Expert Parallel8TP 和 EP 重叠挂在同一组卡上。输入输出长度混合分布平均输入 2048 token平均输出 512 tokenbatch 大小动态调整目标吞吐 4800 token/s。基线是 FP16 全精度推理使用 vLLM 框架FlashAttention 开启KV cache 使用 FP16。这一基线配置下的显存占用、prefill 吞吐、decode 吞吐和 TTFT 记录如下表表中数据是我们环境的实际测量值不同集群可能会有差异指标FP16 基线统一低精度数据流变化显存占用峰值约 620GB约 415GB降低约 33%Prefill 吞吐token/s约 5200约 7800提升约 50%Decode 吞吐token/s约 3100约 4700提升约 52%端到端 TTFTms约 320约 260降低约 19%单 token 平均延迟ms/token约 1.45约 1.02降低约 30%Calibration 后精度偏差下游平均— 0.8%可接受表格里的数据不是一次跑出来的前后调了两周才稳定。注意 decode 吞吐的提升比 prefill 更明显原因在于 decode 阶段算子更小、更密集瓶颈更加偏向带宽和搬运。低精度数据流正好打在这个痛点上。3.2 一个“假阳性”性能坑显存降了延迟反而涨了这是整个过程里最有价值的一个排查案例值得单独写出来。现象第一版统一数据流实现跑通后显存占用确实降了但单 token decode 延迟从基线的 1.45ms/token 反而涨到了 1.6ms/token。预想中的性能提升完全没出现当时的第一反应是量化精度有问题模型在疯狂重试或者数值异常导致 kernel 效率降低。排查过程是这样先用nsys profile抓 kernel 时间占比发现耗时的前三名里出现了一个奇怪的算子——Gather。它的耗时占比高达 18%而在基线版本里它几乎可以忽略不计。直觉告诉我这不是真实的计算需求变大而是低精度张量的内存布局出了问题。进一步看数据发现 MoE 路由之后token 会被分派给不同的专家这本身就需要Gather操作。但在 FP16 基线里token 的 hidden state 是连续存储的Gather 可以在 L2 缓存里完成大部分工作。而统一低精度后为了满足矩阵乘对 128 字节对齐的要求每个专家收到的 token 被单独存成一块连续内存导致 Gather 从“大块连续搬”变成了“大量碎片搬”L2 cache 命中率骤降大量请求直接打到 HBM 上。定位到根因后修复方式是在 MoE 分发前先把 tokens 按专家 ID 排序确保同一个专家的 token 在内存中是连续存放的。这个排序本身也会引入开销但与省下的 HBM 访问相比非常划算。修复后 decode 延迟直接从 1.6ms/token 降到 1.02ms/tokenGather 的 kernel 时间占比也从 18% 降到了 3% 以内。这个坑给我们的教训是低精度优化的收益不是自动兑现的数据排布必须跟着精度一起改否则“省带宽”会变成“制造缓存 miss”。3.3 长序列场景下的 KV Cache 低精度策略DeepSeek V4 950 在长上下文场景下会面临很大的 KV cache 显存压力。32K 上下文、大 batch 时KV cache 占用的显存甚至能超过模型权重本身。低精度化 KV cache 是绕不开的。我们的做法是KV cache 的 K 和 V 分别使用不同的量化策略。K 矩阵用 FP8 E4M3 加 per-head 缩放V 矩阵用块级缩放 FP8。原因很简单——K 在 attention 计算中参与点积对动态范围更敏感V 只是被加权求和对精度的容忍度略高。更激进的做法是 V 直接压到 INT4但实测在 950 上会导致下游任务出现约 1.5% 的精度损失最终没有采用。KV cache 低精度化加上统一数据流之后长上下文场景的 decode batch 可以扩大约 40%这意味着同样的 GPU 资源能服务更多并发请求。但这里有个隐性风险如果序列长度超过某个阈值量化误差会在长距离 attention 中累积模型可能出现重复生成或上下文遗忘。后文会讲到如何通过层粒度策略缓解。4. 精度保卫战哪些层必须留在高精度4.1 敏感层定位定量分析替代拍脑袋低精度优化最怕“全模型量化后才知道哪里崩了”。我们的方法是先做敏感性分析再决定每一层能不能量化。具体做法分三步第一步用与线上分布一致的数据构建校准集大概 2000 条样本第二步逐模块替换为低精度实现其余部分保持高精度跑完校准集记录困惑度PPL和一组下游任务指标第三步把替换后相比基线劣化超过阈值的模块标记为“敏感模块”。在 950 上跑下来的敏感性排序由高到低大致是Attention 的 QKV 投影输入路径、MoE Router 的 logits 计算、残差流主干上的部分全连接层、顶层分类头、中间层 FFN 的 Gate 分支。前两者是几乎不能碰的雷区后几者可以按规模选择性地低精度化。4.2 注意力路径的高精度旁路设计Attention 路径是精度保卫战的重中之重。点积结果一旦被量化softmax 后的分布很容易失真——本来接近 one-hot 的注意力分布变得平滑模型就会“注意力涣散”。我们的设计是Attention 的 Q/K 点积计算内部保留 FP32 累加softmax 的输入和输出保持 FP32但 Attention 的最终输出会以 FP8 写回供后续线性层使用。这里其实是通过“高精度内部计算 低精度对外输出”的方式既保证了注意力分布的准确性又不破坏统一数据流的连续性。如果整个 attention 都用高精度那数据流在这里就要经历一次“低-高-低”的切换前面强调的统一性就破了。高精度旁路只承担 attention 内部的计算不改变主干数据流的精度连续性。4.3 MoE Router 高精度保留的必要性很多人会低估 MoE Router 对精度的敏感度。Router 的 logits 虽然只有很小的计算量但它决定了 token 发往哪些专家。一旦 logits 被量化误差污染路由结果可能发生剧烈变化——某个 token 被错误地发到一个不擅长的专家整个 decode 质量都会波动。在统一数据流方案里MoE Router 是我们刻意保留下来的少数 FP32 计算路径。它不是模型主干上的瓶颈计算量小所以保留 FP32 不会对性能产生明显负面影响。但它的收益很大路由稳定性直接关系到多点采样、长对话一致性等场景的实际体验。我们的原则是计算量小的组件不要为了“统一”而强行压低精度该高就高否则捡了芝麻丢了西瓜。4.4 动态切换机制敏感场景自动保精度静态固定哪些层用高精度、哪些层用低精度只能应对“平均情况”。实际使用中代码生成、数学推理、长文档问答等场景对数值精度的敏感度完全不同。一个在通用对话上精度无损的量化方案可能在数学推理上直接崩掉。我们为关键层设计了一个动态切换开关。系统运行时根据输入类型检测器判断当前请求的场景如果命中高敏感场景就把敏感模块动态切换回高精度实现。这个开关不是模型热加载而是预编译了同一份计算图的高/低精度两个版本运行时通过 kernel 选择器完成切换切换代价小于 5 毫秒对在线服务影响可以忽略。这个方法也有代价——需要同时维护两套 kernel 实现而且显存占用会略有上升。但对于一个需要同时服务多种业务的生产系统来说精度可用性比那一点显存更重要。实际线上运行后高敏感场景的精度回退率从 3.2% 降到了 1% 以内。5. 可复现的执行清单与关键参数配置建议5.1 从校准到上线的完整工程链路如果要在自己的模型上复现这套方案建议严格按下面的顺序走不要跳步第一步数据流分析。先用nsys或ncu跑一遍基线推理输出 kernel 耗时占比、HBM 读写量、L2 命中率。这一步的目的不是找“哪个算子慢”而是找出“哪些张量在 HBM 上反复读写”。只有数据流看得清楚后面的低精度优化才不会走偏。第二步离线仿真。在实际改代码之前先写一个量化仿真脚本把权重和激活值在 Python 层模拟成 FP8 块级缩放格式测一下端到端精度损失。这个仿真不一定完全准确但能快速筛掉明显不可行的方案。第三步逐模块替换。按照敏感性分析的结果从最不敏感的位置开始替换为低精度实现每替换一个模块就做一次精度回测。如果某一步 PPL 出现不可逆上升立即回退改用更保守的量化策略。第四步整网统一。所有模块都完成低精度替换后再统一 scale 体系、统一内存布局、消除切换点。这一步是性能提升的大头也是工程上最容易反复的地方。第五步在线灰度。先切 5% 流量观察下游指标确认无异常后再逐步放大到全量。5.2 FlashAttention 参数调优与低精度的配合大模型推理基本绕不开 FlashAttention我们在 DeepSeek V4 950 上对相关参数做了专门调优这几个值和低精度数据流配合得好不好直接影响最终性能。block_size参数影响 attention 计算分块的大小。低精度数据流下建议把 block_size 适当调大比如从 64 提升到 128。原因是低精度数据每个块读入的数据量减半同样大小的 block 只需要一半的 HBM 带宽分块大一点可以摊薄块间同步的开销。但 block_size 不能无限大否则 L2 cache 放不下反而导致 cache miss 上升。我们实测在 128 到 256 之间达到平衡最终取 128。window_size要结合具体场景调。窗口注意力sliding window attention在长上下文场景下可以显著减少 KV cache 读取量但如果窗口太小模型会丢失远期信息在低精度叠加下更加明显。我们在线上的经验是32K 上下文场景 window_size 设在 4096 到 8192 之间比较稳再小就会出现长程依赖下降的问题。softmax_scale不要随意改。它本质上是 attention logits 的缩放系数直接关系 softmax 的数值范围。低精度数据流里如果softmax_scale设置不合适logits 很容易在低精度下溢出或下溢出。我们的建议是让softmax_scale保持与高精度基线一致不要为了让 logits 落在 FP8 最佳数值区间而强行调整它因为这会改变注意力分布的语义。如果后端支持local/global attention混合策略建议把低精度 KV cache 主要用在 local 部分global 部分保持相对高精度FP8 E4M3 且不做进一步压缩这样能兼顾长程依赖和显存占用。5.3 常见问题对照表这里整理了我们在落地和线上运行中遇到的几个典型问题都附了根因和解决方向问题现象可能根因解决方向PPL 在量化后飙升激活值有极端离群点块级缩放的块大小不合适缩小块大小如从 32 降到 16或对离群维度单独处理长文本生成开始重复KV cache 低精度导致长程注意力信息丢失global 部分保留高精度 KV或提高 V 的量化精度Decode 延迟不降反升MoE 路由后张量分散Gather 成为瓶颈先按 expert 排序 token再让同一专家数据连续存放吞吐提升但显存没下降多少中间激活仍以高精度落盘检查算子融合覆盖范围重点看 Norm 和激活函数后的量化点量化后模型在数学任务上崩数学任务对某些 logits 精度敏感敏感层需要保留高精度用动态切换机制在高敏感场景下切回高精度实现5.4 几点个人体会这个项目做下来我最大的体会是低精度优化不是“权重压成 FP8”这么简单它是一个数据流工程。模型越大数据搬运问题越突出低精度统一数据流的收益就越明显。但收益不会自动落袋需要配套的布局统一、算子融合和敏感性分析一起做才能把理论带宽节省变成实打实的吞吐提升。另一个心得是预热和校准集的质量非常关键。校准集如果和线上分布不一致量化 scale 会在上线后“暴露真面目”。我们第一次上线就是因为校准集过度集中在对话样本导致代码场景下表现不佳。后来从线上抽了多场景混合样本重新校准问题才解决。如果环境允许可以考虑做在线 scale 动态微调但那就涉及更复杂的运行时系统和 kernel 设计不是所有团队都有必要做到这一步。最后想说的是DeepSeek V4 950 这次实践的可复制性其实很高——统一的思路、敏感性分析方法、量化选型和闪存参数调优都是可以直接迁移到其他 MoE 大模型上的。只要肯在数据流分析和敏感层定位上多花时间大部分推理性能瓶颈都能被系统地拆解掉。
返回列表