海光 DCU 实测:BF16 逐位一致为什么仍可能出现文本漂移
海光 DCU 实测BF16 逐位一致为什么仍可能出现文本漂移摘要在海光 DCU gfx936 上优化大模型算子时“误差很小”并不是一个足够明确的正确性结论。即使某个形状下输出逐位一致也不代表其他真实形状、完整计算图或自回归生成过程一定保持一致。本文使用 Qwen3.5-27B BF16 推理的本地实测数据分析一个典型现象融合 kernel 在 T4096 的 full-graph 对照中完全逐位一致并获得 1.92159x 的局部加速但在完整服务中10 个请求有 4 个生成文本发生变化输出 token 总数从 1786 变为 1776。这不是“BF16 天生不可靠”而是说明算子正确性需要沿着真实 shape、计算图边界和生成链路逐层验证。1. BF16 的正确性不是一个布尔值对一个 BF16 kernel可以设置很多层次的正确性门槛输出没有 NaN 或 Inf最大绝对误差在容差内相对 L2 误差足够小输出 BF16 bitwise equalCUDAGraph 重放结果稳定所有生产 shape 都满足相同结论完整服务输出长度和文本保持一致。前几项属于算子层后几项属于系统层。问题在于大模型是自回归系统。某一步 logits 的极小差异如果恰好出现在两个候选 token 的分数边界附近就可能改变下一枚 token。下一步的输入随之变化后续状态和输出轨迹也会完全分叉。因此单个张量的误差很小不等于生成行为一定不变单个 shape 逐位一致也不等于所有 shape 都逐位一致。2. 为什么归约顺序会改变 BF16 输出RMSNorm、矩阵乘法和 Attention 都包含归约。以 RMSNorm 为例核心项可以简化为variance sum(x[i] * x[i]) / hidden_size output x * rsqrt(variance eps) * weight数学上加法满足结合律有限精度浮点数并不严格满足。(a b) c 可能不等于 a (b c)当 kernel 改变 row block、wave 分工或图融合边界时归约树也可能变化。即使最终仍然写回 BF16中间 FP32 累加的最低几位也可能不同。这种差异通常很小但“很小”和“逐位相同”是两个不同标准。3. 一个看起来已经完全正确的 standalone 结果本地测试过一条 Q/K RMSNorm、MRoPE 和 gate 提取的融合路径。对 T4096 的完整链路显式 full-graph 基线与融合实现结果如下项目基线融合实现耗时1.235199 ms0.642799 ms加速比1.00000x1.92159xQ mismatch00K mismatch00gate mismatch00重复运行稳定稳定如果只看到这张表很容易得出“性能和正确性都已经通过”的结论。但这里严格证明的其实只有在 T4096 在这一份 full-graph 基线下 Q/K/gate 输出逐位一致它并没有证明所有尾部 token 长度都采用相同归约布局。4. Shape sweep 找到了隐藏的正确性空洞继续对真实 token 数进行检查后结果并不是简单的“全部正确”或“全部错误”。逐位一致的形状包括T 1 / 17 / 2004 / 2231 / 2992 3712 / 3713 / 4087 / 4095 / 4096出现小量不一致的形状包括T 65 / 1024 / 1502 / 1674 / 1777在 T1502 上继续扫描 rows-per-block 为 1、2、4、8、16仍没有找到逐位一致的布局。这组结果说明不一致并不是由某一个简单的 block 参数决定。编译器可能针对不同 token 数选择不同 reduction、tile 或融合计划。一个对 T4096 完全正确的 kernel不能自动外推到所有 T1。5. 完整服务给出了最终答案该实现随后完成了一次固定 10 请求的本地服务测试指标参考配置融合配置output throughput11.892692312.0612512吞吐变化-1.4173%p99 TPOT44.9788 ms44.6850 ms输出 token 总数17861776文本不一致0/104/10failed00局部性能信号确实存在p99 TPOT 也有所下降但输出 token 数和 4 个请求的文本发生变化因此不能把这次测试描述成“等价加速”。这里最关键的不是 1.4173%而是 standalone 与完整服务的判断发生了分歧T4096 full-graph逐位一致 真实混合 shape部分不一致 完整生成4/10 文本漂移6. 一个真正穿过全部门槛的对照案例另一条 BF16 M1 GEMM 调整提供了正向对照。它只改变 rows per block不改变每一行的累加顺序。三种真实权重形状中它相对参考实现的最大差异均为 0重复运行也保持一致。进入完整服务后4K-8K 10/10 文本 byte-identical 8K-16K 10/10 文本 byte-identical 16K-32K 10/10 文本 byte-identical 输出长度三档全部一致 failed 0三档合计 30 个请求都通过局部加速也稳定传递到端点。这个对照说明严格正确并不意味着不能提速真正的要求是优化不能只在一个“方便测试的 shape”上正确。7. 一套更可靠的 BF16 验证顺序第一层算术边界finite 检查max abs、mean abs、relative L2BF16 mismatch count重复运行确定性。第二层shape 边界至少覆盖T1 Decode主要 Prefill chunk非整齐尾块block 和 tile 的边界值实际请求中出现过的 token 数。第三层图边界分别比较eager 参考单独编译的 full graph原始整模型图中的实现CUDAGraph capture 与 replay。同一段数学在不同图边界中可能被编译成不同归约树。不能拿 eager 的结果替代整模型图也不能拿独立 full graph 替代原始 AOT 图。第四层生成边界完整服务至少记录每个请求的输出 token 数生成文本failed 数量TPOT 和 TTFT重复运行是否保持同一方向。8. 这对海光 DCU 开发者意味着什么这组结果不应被理解成“海光 DCU 的 BF16 有特殊问题”。它真正说明的是在 gfx936 目标环境里kernel、DTK 编译链、Inductor/AOT 图边界和 CUDAGraph 共同决定了最终浮点执行顺序。对海光 DCU 上游算子移植和本地优化建议额外坚持三点在真实 gfx936 设备上覆盖生产 shape而不是只凭其他 GPU 的结果推断同时保留 standalone 与原始整模型图的数值对照把生成文本和输出长度作为 DCU 推理链路的最终验收层。海光 DCU 论坛里讨论“算子是否正确”时也应明确它通过的是哪一级单 kernel、CUDAGraph还是完整 vLLM 服务。9. 结论海光 DCU 上的 BF16 优化需要区分三件事数学公式是否相同浮点归约顺序是否相同自回归生成行为是否相同。本地数据表明一个 T4096 下 1.92159x、Q/K/gate 全部逐位一致的融合实现仍可能在其他 token 形状上出现差异并最终造成 4/10 文本漂移。因此算子正确性的最终定义不应停留在单个张量从真实 shape 到原始计算图再到完整生成整条链路都通过才算真正正确。文中数据来自同一套本地 DCU 实验记录。不同模型、编译器版本、图边界和 kernel 实现可能得到不同结果本文结论不应外推为所有 BF16 算子的统一误差规律。