Prhub

#49688 [Bugfix][CPU] Enable C++ causal_conv1d GDN path and float32 SSM cache on non-AMX AVX-512BF16 CPUs

原始 PR 作者 dineshchitlangia 合并时间 2026-08-19 23:50 文件变更 4 提交数 2 评论 13 代码增减 +290 / -17

执行摘要

修复非 AMX CPU 上 GDN conv 回退慢路径,启用 C++ 内核

关联 issue #49640 指出:GDN 的 C++ conv 内核 csrc/cpu/sgl-kernels/conv.cpp 实际使用 _mm512_dpbf16_ps(VDPBF16PS)而非 AMX tiles,编译期门控为 CPU_CAPABILITY_AVX512,可在任何 AVX-512BF16 CPU 上运行;但 Python 层 is_amx 门控将其限制在 Intel GNR,导致 AMD Zen5/Turin 无谓损失约 9-10% 吞吐。issue 建议将耦合的 conv 步骤门控改为 _is_avx512_bf16_supported()

值得精读。核心看点是“门控条件与内核真实指令依赖对齐”这一 bug 的定位方法:C++ 内核用 VDPBF16PS 而非 AMX tiles,却被 is_amx 挡在门外。配套的 float32 state、SD 布局、权重预打包三处同步放宽,展示了平台级功能开关需要端到端一致的典型模式;新增 fp32 oracle 测试的断言设计也值得借鉴。

讨论亮点

review 中 dllehr-amd 在初次评审时指出,仅放宽 conv 内核门控不够,float32 SSM state 目前也以 is_amx 为条件,若不处理会导致非 AMX 加速路径拿到非 float32 状态、影响正确性;作者随后补交第二个 commit Extend AVX512 path for GDN float32 SSM state 解决了该问题。合并前后还有一个 pre-commit 插曲:DarkLight1337 报告本 PR 导致 main 上 pre-commit 失败,作者回应立即修复,mgoin 抢先提交了修复,dllehr-amd 承认是后续新 commit 触发,wjabbour 另开 PR#53039 把 pre-commit 从跳过改为失败以避免再次漏检。

实现拆解

  1. 核心选择逻辑:vllm/model_executor/layers/mamba/ops/cpu/gdn_attention.py_cpu_gdn_attention_nonspecis_amx = torch.cpu._is_amx_tile_supported() 替换为 use_cpp_conv = torch.cpu._is_avx512_bf16_supported(),decode 分支与 prefill 分支据此选择 causal_conv1d_update_cpu / causal_conv1d_fwd_cpu,并把 conv_state 布局的错误提示从“AMX GDN”改为“C++ CPU GDN”,原因在于 C++ 内核只依赖 AVX-512BF16 指令集。这一步直接影响推理热点路径,使非 AMX CPU 能进入 C++ 快路径。
  2. 配置配套:vllm/platforms/cpu.pycheck_and_update_config 将强制 float32 SSM state 的条件从 AMX 扩为 AVX-512BF16,并把 VLLM_SSM_CONV_STATE_LAYOUT=SD 环境变量的设置条件同步放宽,确保加速 GDN 路径的 float32 状态与 SD 布局在 AMD Turin 等平台上同样成立;这是 mamba_ssm_cache_dtype 正确性的关键前置。
  3. 权重预打包配套:vllm/model_executor/layers/utils.pydispatch_cpu_unquantized_gemm 对 3D conv1d 权重做 VNNI 预打包的触发条件同步改为 _is_avx512_bf16_supported(),同时保留 _cpu_unpacked_conv_weight 供 speculative-decode 的 torch conv 路径使用,避免权重表示变化影响其他消费方。
  4. 测试扩展:tests/kernels/mamba/cpu/test_cpu_gdn_ops.py 新增约 270 行,将相关 skipif 从 AMX 改为 AVX-512BF16,并新增 _conv_fp32_oracle(全 float32 高精度参考)、_run_prefill_cpp / _run_prefill_torch 对照,以及 C++ 与 torch、C++ 与 fp32 oracle、VNNI 打包往返、update 内核一致性等测试;作者在 Intel GNR 和 AMD Turin 上均验证 105 passed / 0 skipped。
文件 模块 状态 重要度
vllm/model_executor/layers/mamba/ops/cpu/gdn_attention.py 注意力算子 modified 5.54
tests/kernels/mamba/cpu/test_cpu_gdn_ops.py GDN 算子 modified 7.24
vllm/platforms/cpu.py 平台配置 modified 5.8
vllm/model_executor/layers/utils.py 权重派发 modified 5.44

关键符号

_cpu_gdn_attention_nonspec check_and_update_config dispatch_cpu_unquantized_gemm _conv_fp32_oracle _run_prefill_torch _run_prefill_cpp test_conv_cpp_matches_torch test_conv_cpp_vnni_packed_matches_torch test_conv_update_cpp_matches_torch

关键源码片段

vllm/model_executor/layers/mamba/ops/cpu/gdn_attention.py core-logic

核心修复点:将 causal_conv1d 实现选择从 AMX 门控改为 AVX-512BF16 门控,decode 和 prefill 分支均切换到 C++ 内核,并同步更新 conv_state 布局要求。

重写后的核心选择逻辑(基于 _cpu_gdn_attention_nonspec 的改动):

# causal_conv1d 实现选择:C++ 内核只依赖 AVX-512BF16 的 VDPBF16PS 指令,
# 不依赖 AMX tiles,因此门控从 `is_amx` 放宽到 `is_avx512_bf16`,
# AMD EPYC Turin (Zen5) 等非 AMX CPU 也能进入 C++ 快路径。
use_cpp_conv = torch.cpu._is_avx512_bf16_supported()
conv_state = layer.kv_cache[0]if use_cpp_conv:
    # C++ 内核消费 SD 布局 [num_allocated_slots, kernel - 1, conv_dim]
    if is_conv_state_dim_first():
        raise RuntimeError('C++ CPU GDN attention requires SD conv_state layout.')
    conv_state = conv_state.transpose(1, 2)
else:
    # torch 回退路径使用 [dim, kernel - 1] 的 D-first 布局
    if not is_conv_state_dim_first():
        raise RuntimeError('torch CPU GDN attention requires D-first conv_state layout.')# decode 分支:逐 token 更新 conv state,走 C++ update 内核
if use_cpp_conv:
    decode_mixed_qkv = ops.causal_conv1d_update_cpu(
        x=decode_mixed_qkv,
        conv_states=conv_state,
        weight=layer.conv1d.weight,
        bias=layer.conv1d.bias,
        silu_activation=True,
        conv_state_indices=decode_state_indices,
        is_vnni=...,
        num_accepted_tokens=...)
else:
    decode_mixed_qkv = causal_conv1d_update_torch(...)# prefill 分支:varlen 形态一次性前向,走 C++ fwd 内核
if use_cpp_conv:
    prefill_mixed_qkv = ops.causal_conv1d_fwd_cpu(
        x=prefill_mixed_qkv.transpose(0, 1),
        weight=layer.conv1d.weight,
        bias=layer.conv1d.bias,
        conv_states=conv_state,
        query_start_loc=...,
        cache_indices=...,
        has_initial_state=...,
        silu_activation=True,
        is_vnni=...)
else:
    prefill_mixed_qkv = causal_conv1d_fn_cpu(...)
tests/kernels/mamba/cpu/test_cpu_gdn_ops.py test-coverage

新增 270 行测试,将 skipif 从 AMX 改为 AVX-512BF16,用 fp32 oracle 验证 C++ 内核与 torch 回退的一致性,是门控放宽后防止精度回归的关键保障。

新增测试的 fp32 参考实现与对比断言(整理片段):

# 高精度参考实现:所有中间量保持 float32,不引入 bf16 舍入,
# 作为 C++ 内核与 torch 回退共同比较的基准。
def _conv_fp32_oracle(x, weight, bias, seq_lens, activation='silu'):
    xf = x.float()
    wf = weight.float().unsqueeze(1)
    bf = bias.float()
    out = torch.empty_like(xf)
    start = 0
    for n in seq_lens:
        seg = xf[start:start + n].transpose(0, 1).unsqueeze(0) # [1, dim, n]
        conv_in = F.pad(seg, (_STATE_LEN, 0)) # 因果 padding
        seg_out = F.conv1d(conv_in, wf, bf, padding=0, groups=CONV_DIM)[..., -n:]
        if activation in ('silu', 'swish'):
            seg_out = F.silu(seg_out)
        out[start:start + n] = seg_out.squeeze(0).transpose(0, 1)
        start += n
    return out# C++ 内核与 torch 回退都必须落在 fp32 oracle 的误差带内,
# 这样即使门控放宽,也能保证数值行为不劣化。
def test_conv_cpp_matches_torch():
    x, weight, bias, seq_lens = _conv_inputs(...)
    cpp_out = _run_prefill_cpp(x, weight, bias, seq_lens)
    torch_out = _run_prefill_torch(x, weight, bias, seq_lens)
    oracle = _conv_fp32_oracle(x, weight, bias, seq_lens)
    torch.testing.assert_close(cpp_out, torch_out, atol=1e-2, rtol=1e-2)
    torch.testing.assert_close(cpp_out, oracle, atol=2e-2, rtol=2e-2)
vllm/platforms/cpu.py core-logic

将 float32 SSM state 强制条件与 VLLM_SSM_CONV_STATE_LAYOUT=SD 环境变量的设置条件从 AMX 扩展到 AVX-512BF16,确保门控放宽后整个 GDN 路径的配置自洽。

check_and_update_config 中与 GDN 加速相关的配置强制项(整理片段):

# 加速的 GDN conv 在 BF16 下要求 float32 SSM state,否则数值精度无法保证;
# 原来只对 AMX CPU 生效,现在扩展到所有 AVX-512BF16 CPU,
# 因为 conv.cpp 使用 VDPBF16PS,不依赖 AMX tiles。
if (
    torch.cpu._is_avx512_bf16_supported()
    and cache_config.mamba_ssm_cache_dtype != 'float32'
):
    cache_config.mamba_ssm_cache_dtype = 'float32'
    logger.warning(
        'Reset SSM cache type to float32 for accelerated GDN mamba attention.'
    )# C++ 内核消费 SD 布局,提前设置环境变量以避免运行时转换;
# 该变量的影响面随门控放宽到全部 AVX-512BF16 CPU(如 AMD Zen5/Turin)。
if torch.cpu._is_avx512_bf16_supported():
    os.environ['VLLM_SSM_CONV_STATE_LAYOUT'] = 'SD'

评论区精华

float32 SSM state 也需随门控放宽 正确性

dllehr-amd 在 review 中指出,仅放宽 conv 内核门控不够,float32 SSM state 目前也以 `is_amx` 为条件,若不处理会导致非 AMX 加速路径拿到非 float32 状态、影响正确性;作者回复已在新 commit 中解决。

结论:第二提交将 cpu.py 的 float32 state 强制逻辑与 SD 布局设置改为基于 `_is_avx512_bf16_supported()`。 · 已解决

合并后 main pre-commit 失败 other

DarkLight1337 报告本 PR 使 main 的 pre-commit 失败,要求另开 PR 修复;作者表示立即处理,随后 mgoin 抢先提交;dllehr-amd 承认是后续 commit 触发;wjabbour 提议将 pre-commit 从跳过改为失败。

结论:修复已由 mgoin 提交,wjabbour 另开 PR#53039 强化 pre-commit 校验流程,避免跳过导致的漏检。 · 已解决

风险与影响

  1. 门控放宽后,C++ conv 内核、VNNI 权重预打包和 VLLM_SSM_CONV_STATE_LAYOUT=SD 环境变量会在所有 AVX-512BF16 CPU 上生效,超出原 AMX 平台范围;若某些 AVX-512BF16 微架构存在 VDPBF16PS 行为差异或 VNNI 布局假设不一致,可能暴露未在 CI 中覆盖的问题。当前验证平台仅 Intel GNR 与 AMD Turin 两类。
  2. vllm/model_executor/layers/utils.py 改变 conv 权重预打包条件后,layer.weight 在更多平台上变为 VNNI 打包形态;虽然保留了 _cpu_unpacked_conv_weight 给 spec decode 的 torch 路径,但其他直接读取 layer.weight 的代码路径需要确认是否感知打包形态。
  3. VLLM_SSM_CONV_STATE_LAYOUT 环境变量从仅 AMX 扩展到所有 AVX-512BF16 CPU,会影响依赖默认 conv_state 布局的其他 CPU 组件或用户自定义布局配置。
  4. 合并后曾触发 main pre-commit 失败,说明该 PR 的提交链在 CI 合规上存在过疏漏,后续需关注提交前自检。

对用户:AMD EPYC Turin(Zen5)等非 AMX AVX-512BF16 CPU 上 Qwen3.5 GDN 模型的解码吞吐提升约 9-10%;Intel GNR(AMX)路径不变,无回归。对系统:CPU 平台配置逻辑(vllm/platforms/cpu.py)与权重加载路径(vllm/model_executor/layers/utils.py)的行为面扩大,float32 SSM state 在更多 CPU 上被强制启用,会相应增加内存占用。对团队:GDN 在 CPU 上的测试覆盖从 AMX 独占扩展到所有 AVX-512BF16 平台,为后续 CPU 内核质量提供基准。

门控放宽影响面扩大 权重预打包行为变化 环境变量全局生效 CI 未覆盖全部平台 曾触发 main pre-commit 失败

关联 Issue

#49640 [Bug]: [CPU] GDN attention falls back to slow torch conv1d on non-AMX AVX-512BF16 CPUs

完整报告

参与讨论