Prhub

#49309 [ROCm][CI] Use explicit wvSplitKrc skinny-GEMM test tolerance for bf16 (gfx950)

原始 PR 作者 stefankoncarevic 合并时间 2026-07-31 11:25 文件变更 1 提交数 12 评论 5 代码增减 +8 / -3

执行摘要

修复 gfx950 上 wvSplitKrc bf16 测试容差误报

在 gfx950(MI355X)上,test_rocm_wvsplitkrc_kernelxnorm=True + bf16 + bias 参数化组合在旧断言 atol=1e-3, rtol=1e-8 下失败。作者在 PR body 中论证这是 bounded bf16 rounding 而非 kernel bug:带 bias 时 ref = A·Bᵀ + bias 处于 ~O(1) 量级,bf16 一个可表示步长 2^-8 = 0.00390625 约为旧 atol 的 3.9 倍;2034 个非跳过组合中最坏绝对差恰好为一个 bf16 step(0.00390625),且不随 K 增长、固定 seed 下可复现。同时 review 指出测试所用的 Xavier 初始化不严格正确(均匀分布缩放不满足 Xavier 的均值/方差要求),需要一并修正。

值得快速浏览而非精读。代码改动极小(+8/-3),但 PR body、issue 评论与 review 中关于 bf16 ULP、catastrophic cancellation、Xavier 初始化以及 ULP 距离断言为何不适用的数值分析,对任何编写或维护 kernel 数值测试的工程师都有参考价值。值得关注的决策点:容差放宽必须 scoped 到具体失效分支、以可观测的最坏误差和 finfo(eps) 为上下界、并用 golden standard 交叉验证“不是 kernel bug”。

讨论亮点

Review 中最有价值的交锋集中在三点:

  1. Xavier 初始化指正:mawong-amd 指出旧写法 (torch.rand(...) * 2 - 1) * xavier 并非 Xavier 初始化——Xavier normal 要求每个元素独立服从均值为 0、方差 2/(n+k) 的正态分布;Xavier uniform 要求 [-r, r]r = sqrt(6/(n+k)))上的均匀分布。作者采纳并改为 torch.randn(...) * xavier
  2. 分歧根源的交叉验证:mawong-amd 用 float64 计算再舍入到 bf16 的 golden standard 验证,结论是当 golden 输出足够大时,bf16 参考与 kernel 都最多偏离 1 ULP;接近 0 时两者因 FP 减法(正负抵消,catastrophic cancellation)偏离更多 ULP 但幅度相近——即分歧是舍入与抵消的本质结果,不是 kernel bug。
  3. 容差方案的取舍:作者在 issue 评论中说明 ULP 距离方案不可行——(ulp > 0) + outlier 预算在 46/2034 组合失败(近零输出 ±1 ULP 数量远超预算),严格 (ulp <= 1).all() 最坏 ULP 距离达 8;且 review 反馈 finfo(bf16).eps 语义不清,最终才选择显式 scoped 的 5e-3。

实现拆解

  1. 修正测试输入分布(Xavier 初始化)tests/kernels/quantization/test_rocm_skinny_gemms.py 第 153-154 行将 A = (torch.rand(n, k, ...) * 2 - 1) * xavier 改为 A = torch.randn(n, k, ...) * xavier(B 同理),使每个元素独立服从 N(0, xavier^2),符合 Xavier normal 定义。该改动由 review 中 mawong-amd 的指正驱动,虽然不改变实测 kernel 与参考的对比行为,但使测试前提在数值上成立。
  2. 断言容差 scoped 放宽xnorm=True 分支从固定 atol=1e-3, rtol=1e-8 改为 atol = 5e-3 if (dtype == torch.bfloat16 and BIAS is not None) else 1e-3rtol 保持 1e-8。5e-3 的选取依据:高于观测最坏差 3.9e-3 留有缓冲,低于 finfo(bf16).eps 约 7.8e-3,避免掩盖一个 ULP 以上的真实回归;fp16 最坏差 4.88e-4 远低于 1e-3,无需放宽。
  3. 容差方案的演进:初版直接松绑 bf16(提交 5b533cb)→ 中间版统一用 finfo(dtype).eps(review 认为语义不清,其为 1.0 幅值处的 1 ULP,与本测试 ~O(1) 输出量级的关联不明显)→ 最终改为显式、bias-scoped 的 5e-3(提交 271b9e2);期间曾尝试将 rtol 放宽至 1e-3,因该检查以 atol 为主导而恢复严格 1e-8(提交 e111b4a)。
  4. 验证与配套:纯测试变更,无配置、schema、部署改动。gfx950 上全组合 sweep(bf16 + fp16 各 2034 combos)0 失败;test_rocm_skinny_gemms.py 整组运行 9960 passed / 1080 skipped(约 68 分钟)。CI 由维护者触发并通过(Buildkite build #81381)。
文件 模块 状态 重要度
tests/kernels/quantization/test_rocm_skinny_gemms.py 内核测试 modified 4.07

关键符号

test_rocm_wvsplitkrc_kernel

关键源码片段

tests/kernels/quantization/test_rocm_skinny_gemms.py test-coverage

唯一变更文件,承载两项修复:修正测试输入的 Xavier 初始化(rand 改 randn),以及对 xnorm=True + biased bf16 分支进行 scoped atol 放宽;本 PR 的全部内容都集中在此,直接决定 gfx950 CI 是否通过。

# 完整参数空间来自:xnorm、n/k/m、dtype、seed、padded_a、bias_mode 的多维参数化,
# 其中 xnorm、n/k/m 的参数化装饰器在本函数上方(此处省略)
@pytest.mark.parametrize("m", M_FACTORS_WVSPLITKRC)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("seed", SEEDS)
@pytest.mark.parametrize("padded_a", [False, True])
@pytest.mark.parametrize("bias_mode", BIAS_MODES)
@pytest.mark.skipif(not current_platform.is_rocm(), reason="only test for rocm")
@pytest.mark.skipif(not on_gfx950(), reason="only meant for gfx950")
def test_rocm_wvsplitkrc_kernel(xnorm, n, k, m, dtype, seed, padded_a, bias_mode):
    torch.manual_seed(seed)
    cu_count = num_compute_units()
​
    # 根据 m、k、N 与 CU 数量判断 wvSplitKrc 是否适用,过大的配置直接跳过
    N_p2 = 1 << (n - 1).bit_length()
    rndup_cus = ((m + 64 - 1) // 64) * ((k + 512 - 1) // 512)
    GrpsShrB = min(N_p2 // 16, 4)
    CuNeeded = rndup_cus * GrpsShrB
    fits_wvsplitkrc = (N_p2 * m * ((k + 512 - 1) // 512)) <= 128 * 1024 * 12
    fits_wvsplitkrc &= CuNeeded <= cu_count
    if not fits_wvsplitkrc:
        pytest.skip("Too large for wvSplitKrc")
​
    # 修正后的 Xavier 初始化:每个元素独立服从 N(0, xavier^2)。
    # 旧写法 (torch.rand * 2 - 1) * xavier 只是均匀分布缩放,方差仅 xavier^2 / 3,
    # 不满足 Xavier normal 的均值 / 方差要求(review 指正后修正)
    xavier = math.sqrt(2 / k) if xnorm else 1
    A = torch.randn(n, k, dtype=dtype, device="cuda") * xavier
    B = torch.randn(m, k, dtype=dtype, device="cuda") * xavier
    if padded_a:
        A = pad_fp8(A)
​
    # bias 未归一化、取值在 [-1, 1],会把 ref = A·Bᵀ + bias 抬到 ~O(1) 量级,
    # 这正是 bf16 舍入边界问题出现的地方
    BIAS = None
    if bias_mode == 1:
        BIAS = torch.rand(m, dtype=dtype, device="cuda") * 2 - 1
    elif bias_mode == 2:
        BIAS = torch.rand(n, m, dtype=dtype, device="cuda") * 2 - 1
    elif bias_mode == 3:
        BIAS = torch.rand(1, m, dtype=dtype, device="cuda") * 2 - 1
​
    ref_out = torch.nn.functional.linear(A, B, BIAS)
    out = ops.wvSplitKrc(A, B, cu_count, BIAS)
​
    if xnorm:
        # xnorm 分支以 atol 为主导(rtol = 1e-8 几乎不起作用)。
        # 带 bias 时输出位于 ~O(1),bf16 一个 ULP 约 3.9e-3 超过旧 atol 1e-3:
        # 只对 biased bf16 放宽到 5e-3(高于观测最坏差 3.9e-3,低于
        # finfo(bf16).eps 约 7.8e-3),其余组合保持 1e-3 严格容差;
        # fp16 的 ULP 约 4.9e-4 远低于 1e-3,无需放宽
        atol = 5e-3 if (dtype == torch.bfloat16 and BIAS is not None) else 1e-3
        torch.testing.assert_close(out, ref_out, atol=atol, rtol=1e-8)
    else:
        torch.testing.assert_close(out, ref_out, atol=1e-3, rtol=1e-2)

评论区精华

Xavier 初始化不严格正确 正确性

mawong-amd 指出 `(torch.rand(...) * 2 - 1) * xavier` 仅是 [-1, 1] 均匀分布缩放,不满足 Xavier 初始化对均值 0、方差 2/(n+k)(Xavier normal)或 [-r, r] 均匀(Xavier uniform,r=sqrt(6/(n+k)))的要求。

结论:作者将 A、B 改为 `torch.randn(...) * xavier`,使每个元素服从 N(0, xavier^2),符合 Xavier normal 定义。 · 已解决

bf16 分歧来源与容差放宽的合理性 正确性

mawong-amd 用 float64 计算再舍入到 bf16 的 golden standard 做对比:输出较大时 ref 与 kernel 都最多偏离 golden 1 ULP;接近 0 时因偏置与 matmul 结果符号相反相加产生 catastrophic cancellation,两者偏离更多 ULP 但幅度相近。作者确认分歧来自 bias(ref ~O(1)),一个 bf16 ULP(3.9e-3)超过旧 atol 1e-3。

结论:分歧被刻画为 bf16 舍入边界而非 kernel bug,scoped 放宽 atol 到 5e-3 合理,评审者 approve。 · 已解决

为何不采用 ULP 距离断言 设计

作者评估仓库已有两种 ULP 模式:(ulp > 0) + outlier 预算(layernorm 用例)在 46/2034 combos 失败——接近零的输出相差 ≥1 ULP 的数量远超预算;严格 (ulp <= 1).all()(deepseek qnorm/rope 用例)最坏 ULP 距离达 8,同样失败。

结论:放弃 ULP 方案,改用基于绝对误差观测的显式 scoped atol=5e-3。 · 已解决

风险与影响

  • 容差放宽的回归敏感度(低风险):放宽严格限定在 biased bf16 的 xnorm 分支,5e-3 仍低于 bf16 eps 约 7.8e-3,且 fp16、非 bias bf16 与 xnorm=False 分支保持 1e-3 严格容差。但该测试从此对 biased bf16 分支绝对误差小于 5e-3 的 kernel 回归不再敏感,需要依赖其他精度分析或 golden-standard 测试兜底。
  • 平台覆盖有限:测试整体 skipif on_gfx950(),本 PR 的数值观测(1 ULP ≈ 3.9e-3、最坏差 0.00390625)仅在 MI355X 上验证;其他 ROCm 平台(如 gfx90a、gfx942)的 bf16 舍入特征未必相同,未来若在这些平台启用该测试需重新评估容差。
  • 输入分布变更的潜在影响:A、B 从均匀分布改为正态分布,改变了数值覆盖域;作者以 2034 combos × 2 dtypes 全量 sweep 验证 0 失败,但长期 flakiness 仍需 CI 观察。
  • 测试成本:该测试组单次全量运行约 68 分钟(9960 用例),是 CI 的持续重负载,本 PR 未改变用例数,但有累积性成本。
  • 用户/运行时:无影响,纯测试变更,不涉及 kernel、推理路径或任何生产代码。
  • 系统/CI:解除 gfx950(MI355X)上的 CI 阻塞,使 wvSplitKrc kernel 的正确性测试在该平台可稳定通过,避免误报失败掩盖真实回归。
  • 团队:为 ROCm 测试确立了“显式 scoped atol + 保持严格 rtol + 注释说明数值依据”的容差调整模式,后续同类问题可复用该决策框架;评审中确立的 double-precision golden standard 对比方法也是可复用的验证手段。
测试容差放宽 仅 gfx950 平台验证 重测试组(约 68 分钟) 输入分布变更(均匀→正态)

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论