Misc. bug: gpu-cuda test-backend-ops fails fused ADD_ADD f16/f16 (1.02e-7 > 1e-7)

这个报错通常出现在 Linux CUDA 环境下运行 llama.cpp 的 test-backend-ops 测试套件时,由 ADD_ADD 融合算子的 f16/f16 用例触发。它并非 CUDA kernel 本身算错,而是 fused ADD 保留 FP32 中间结果、CPU 每次 ADD 都

快速结论:这个报错通常出现在 Linux CUDA 环境下运行 llama.cpp 的 test-backend-ops 测试套件时,由 ADD_ADD 融合算子的 f16/f16 用例触发。它并非 CUDA kernel 本身算错,而是 fused ADD 保留 FP32 中间结果、CPU 每次 ADD 都舍入到 FP16,两者舍入顺序不同,导致误差略超 test_add_add 继承的默认 NMSE 上限 1e-7。优先检查测试容差而不是内核实现。

适用环境:Issue 已确认:llama.cpp master(约 fa67698,另在 8212c7802 复现);Linux(self-hosted gpu-cuda runner);Windows + CUDA 13.4 + RTX 5070 Ti(评论中的测量环境)。命令为 ctest -C Release --output-on-failure -L 'main|python'。

最快修复方案:暂无确认的一步修复方案。Issue 中建议并测试过的处理是给 tests/test-backend-ops.cpp 里的 test_add_add 增加与 test_bin_bcast(见 #28691)相同的 max_nmse_err() 覆盖:当 type == GGML_TYPE_F16 时返回 1e-6。该改动在 PR #28707 的 d5cb9ea 中用于解除该 PR 阻塞,Issue 作者认为它应单独合入 master。可优先尝试。

注意事项:该方案放宽的是测试容差,不是修复 kernel;它只针对 fused f16 ADD 链的融合与 CPU 分步舍入差异,不改变 CUDA 计算结果。修改前请确认失败确为 ADD_ADD(type=f16,type_addend=f16,...) 且误差量级约 1.0e-7,而非其他类型或形状的 ADD 失败。

问题场景

在 Linux 的 gpu-cuda 环境下运行 llama.cpp 的 Release 测试套件(ctest -C Release --output-on-failure -L 'main|python')时,test-backend-ops 失败。触发点是 tests/test-backend-ops.cpp 中的 test_add_add,即融合图 ggml_add(ggml_add(a, b), c) 的 f16/f16 用例,形状 ne=[64,5,4,3]。同一 job 中 f32/f32、f16/f32 以及 f16/f16 的 ne=[1025,5,4,3] 均通过,说明不是通用 ADD kernel 缺陷。

报错原文

[ADD] ERR = 0.000000102 > 0.000000100
ADD_ADD(type=f16,type_addend=f16,ne=[64,5,4,3],broadcast=0,view=0): FAIL

Failing tests:
  Backend CUDA0: FAIL
98% tests passed, 1 tests failed out of 54
The following tests FAILED:
  45 - test-backend-ops (Failed)

原因分析

最可能的原因是 fused ADD 与 CPU 分步 ADD 的舍入顺序不同。CUDA 融合路径会把 a+b+c 的中间结果保留为 FP32,只在最后做一次 FP16 舍入;而 CPU 路径每次 ADD 都舍入回 FP16,再做下一次加法。两种顺序在 ne=[64,5,4,3] 这类形状上会产生约 1.0e-7 量级的 NMSE 差异,恰好超过 test_add_add 未覆盖时继承的默认上限 1e-7。

该问题与 #28691 同类:#28691 已为 test_bin_bcast 中融合 FP16 ADD 链(nf > 1)把上限提高到 1e-6,但 test_add_add 没有获得同样的覆盖。评论中的测量支持这一判断:CUDA 结果与“对 f32 和做一次 f16 舍入”一致,CPU 与“每次加法后舍入”一致;GGML_CUDA_DISABLE_FUSION=1 时 NMSE 精确为 0;100,000 次运行中约 0.827% 超过 1e-7。因此这并不是近期 kernel 回归。

环境排查

  • 确认 llama.cpp 版本(Issue 中为 master 约 fa67698,另在 8212c7802 复现)。
  • 确认运行平台:Linux gpu-cuda runner;评论测量环境为 Windows、CUDA 13.4、RTX 5070 Ti。
  • 确认执行命令为 ctest -C Release --output-on-failure -L 'main|python'。
  • 确认失败用例是否精确为 ADD_ADD(type=f16,type_addend=f16,ne=[64,5,4,3],broadcast=0,view=0),并对比 f32/f32、f16/f32 是否通过。
  • 确认 test_add_add 是否缺少 max_nmse_err() 覆盖,因默认限制仍为 1e-7。

解决步骤

  1. 在 tests/test-backend-ops.cpp 中定位 test_add_add 测试用例类。
  2. 参照 test_bin_bcast 在 #28691 中的处理,为 test_add_add 增加 max_nmse_err() 覆盖:当 type == GGML_TYPE_F16 时返回 1e-6,否则回退到 test_case::max_nmse_err()。评论中给出的写法为:
double max_nmse_err() override {
    if (type == GGML_TYPE_F16) {
        // Fused ADDs can keep FP32 intermediates while the CPU rounds each ADD to FP16 (see #28691).
        return 1e-6;
    }
    return test_case::max_nmse_err();
}
  1. 重新构建并运行同一 ctest 命令,确认 test-backend-ops 通过。
  2. 若该修复尚未合入 master,可暂时在本地应用上述改动以解除 CI 阻塞;不要把它当作 CUDA kernel 修正。

验证方法

重新执行 ctest -C Release --output-on-failure -L 'main|python',确认 test-backend-ops 不再报 ADD_ADD f16/f16 失败。评论中的验证结果:应用该覆盖后完整测试套件为 16449/16449 通过,ADD_ADD 重复运行 20/20 通过。也可以单独重复运行 ADD_ADD f16 用例,观察是否还会出现 NMSE 略高于 1e-7 的偶发失败。

参考来源

ggml-org/llama.cpp #28719

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 25454

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注