Prhub

#32046 [AMD]Qwen3.5 integration gfx950 fmha fp8 hd256

原始 PR 作者 amd-oshkarav 合并时间 2026-08-03 15:25 文件变更 1 提交数 23 评论 20 代码增减 +38 / -0

执行摘要

Qwen3.5 在 gfx950 新增 opt-in fp8 hd256 prefill 快速路径

PR body 明确说明动机:Qwen3.5 在 gfx950 (MI350x) 上使用 attention head_dim 256,无法命中 aiter 快速 attention 路径,prefill 回退到较慢的 mha_batch_prefill;ROCm/aiter PR #3732(flash_attn_varlen_fp8_pertensor_func)新增了 fp8 head_dim-256 FMHA asm 内核,可加速该形状的 compute-bound prefill attention。本 PR 将该内核作为 opt-in 快速路径接入,针对性提升长上下文 prefill 的吞吐与 TTFT,重点覆盖 MoE 变体 amd/Qwen3.5-397B-A17B-MoE-MXFP4。作者还解释了 chunked-prefill 的约束:快速路径只对无前缀 chunk 生效,因此长 prompt 需要 --chunked-prefill-size -1 或 65536 与 --max-prefill-tokens 65536 保持单块无前缀。

建议精读。这是"在共享注意力后端安全接入硬件专属 kernel 快速路径"的典型范例:严格的正确性门控、opt-in 环境变量、默认路径字节级不变、以及用大规模 A/B 数据支撑内核选择决策。值得关注的设计点:

1) 所有正确性风险条件(sinks/softcap/滑动窗口/缓存前缀)都显式出现在门控里而非依赖调用方约定;
2) 回退内核的取舍基于同容器同版本实测而非直觉;
3) 未附带测试导致的 CI 盲区,应作为反面教材与 #33399 一起跟踪。

讨论亮点

review 的 13 条内联评论集中在正确性与性能取舍,主要交锋:

  1. 反量化尺度:yichiche 指出应使用 q/k/v 各自 scale 而非 one_scale,作者以 fixing commit 落实;
  2. 全局变量:yichiche 建议减少全局变量、仅用 is_gfx95_supported 门控,作者采纳并内联环境变量;
  3. 回退内核选择:yichiche 建议回退到 flash_attn_varlen_func,作者用同容器同版本 A/B 证明该函数在 head 256 下非 asm 且大 ISL 不占优,最终保留 mha_batch_prefill_func;
  4. 模式门控:yichiche 指出 target_verify 后 forward_extend 可能从非 extend 模式到达,要求改用 is_extend() 并显式排除 sinks 与 softcap,合并版本全部纳入;
  5. fp8 KV 前提:yichiche 指出非 fp8 KV 时额外量化伤性能,最终以 kv_cache_dtype == fp8_dtype 收口。
    此外 amd-bot 的 CI 审查明确警告:新路径默认关闭且无测试设置,PR CI 从未执行该代码,绿色不能证明正确性。

实现拆解

  1. 变更入口与符号:在 python/sglang/srt/layers/attention/aiter_backend.py 的 aiter 导入块中新增 flash_attn_varlen_fp8_pertensor_func 导入,这是 fp8 per-tensor varlen FMHA 内核的唯一新增外部依赖;该符号只存在于 aiter main 分支构建(含 ROCm/aiter PR #3732)。
  2. 快速路径门控:在 AiterAttnBackend.forward_extend 中、window_size 确定之后、原有 NHD paged 路径之前插入一个复合条件分支。条件包括环境变量 SGLANG_AITER_FMHA_FP8_ASM=1、is_gfx95_supported()、forward_mode.is_extend()、extend_prefix_lens_cpu 非空且全零(无缓存前缀纯 prefill)、window_size == (-1, -1)、sinks is None、logits_soft_cap == 0.0、layer.qk_head_dim 与 v_head_dim 均为 256、kv_cache_dtype == fp8_dtype。这些门控与姊妹 PR #28482 保持一致,并吸收了 review 中关于 target_verify 模式与 sink/softcap 安全性的意见。
  3. fp8 量化与内核调用:命中快速路径时,Q/K/V 先 contiguous 再按 (num_tokens, num_heads, head_dim) 重排,然后 .to(fp8_dtype);反量化使用 per-tensor 尺度——q 的 scale 取 layer.k_scale 或 self.k_scale,k/v 沿用现有变量 k_descale/v_descale(对应 review 中 one_scale 修正);调用 flash_attn_varlen_fp8_pertensor_func,cu_seqlens 使用 self.qo_indptr[:bs0],max_q_len 使用 forward_metadata.max_q_len,softmax_scale=layer.scaling、causal=True;输出若 dtype 与 input_dtype 不一致则转回,最后按 (-1, tp_q_head_num * head_dim) 返回。
  4. 回退路径不变:任何条件不满足(例如 chunked prefill 续块带缓存前缀、bf16 KV、非 gfx950、head_dim 非 256)都会原样落入 vectorized-5d 或原有 mha_batch_prefill_func 路径,保证默认行为与未打补丁版本一致。
  5. 测试与配置配套:本 PR 没有新增任何单元测试或 CI 用例(合计 +38/-0,仅 1 个文件);正确性依赖作者在 MI350x 上手动执行的 gsm8k 评测(0.977-0.98,与基线噪声相当),性能依托 Databricks 上的大规模 A/B 基准。部署需要 aiter main 分支,且长文本场景要求大 chunked-prefill 配置。合并前 CI 状态(amd-bot 评论)确认新代码路径在 PR CI 中一次都没有执行。后续 #33399 将移除全局变量、修复 shape 问题并在 fp8 KV cache 下默认启用。
文件 模块 状态 重要度
python/sglang/srt/layers/attention/aiter_backend.py 注意力后端 modified 6.35

关键符号

AiterAttnBackend.forward_extend flash_attn_varlen_fp8_pertensor_func

关键源码片段

python/sglang/srt/layers/attention/aiter_backend.py core-logic

唯一变更文件:在 AiterAttnBackend.forward_extend 中新增 fp8 hd256 prefill 快速路径,涵盖 aiter 导入、复合门控、fp8 量化与内核调用、dtype 回退,是全部功能与风险的载体。

# python/sglang/srt/layers/attention/aiter_backend.py(合并后结构,已整理)
# 新增的 aiter 导入(原有 try 导入块内追加一行):
# from aiter import flash_attn_varlen_fp8_pertensor_func
# 该符号来自 ROCm/aiter PR #3732,只在 aiter main 分支构建中存在。# AiterAttnBackend.forward_extend:window_size 计算之后、NHD paged 路径之前
if (
    get_bool_env_var("SGLANG_AITER_FMHA_FP8_ASM", "False") # 显式 opt-in,默认关闭
    and is_gfx95_supported() # 仅 gfx950 (MI350x)
    and forward_batch.forward_mode.is_extend() # 仅 extend 模式
    and forward_batch.extend_prefix_lens_cpu is not None
    and not any(forward_batch.extend_prefix_lens_cpu) # 无缓存前缀:纯 prefill
    and window_size == (-1, -1) # 内核不支持滑动窗口
    and sinks is None # 内核不支持 attention sinks
    and self.logits_soft_cap == 0.0 # 内核不支持 softcap
    and layer.qk_head_dim == 256
    and layer.v_head_dim == 256 # Qwen3.5 的 head_dim
    and self.kv_cache_dtype == fp8_dtype # 仅 fp8 KV cache 下启用
):
    # Q/K/V 先转连续并重排为 (num_tokens, num_heads, head_dim),
    # 再量化到 fp8;per-tensor 反量化尺度分别来自 layer 或后端缓存。
    q_c = q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim)
    k_c = k.contiguous().view(-1, layer.tp_k_head_num, layer.head_dim)
    v_c = v.contiguous().view(-1, layer.tp_v_head_num, layer.v_head_dim)
    fp8_q_descale = (
        layer.k_scale if layer.k_scale is not None else self.k_scale
    )
    o = flash_attn_varlen_fp8_pertensor_func(
        q_c.to(fp8_dtype),
        k_c.to(fp8_dtype),
        v_c.to(fp8_dtype),
        fp8_q_descale,
        k_descale,
        v_descale,
        self.qo_indptr[:bs0], # cu_seqlens_q
        self.qo_indptr[:bs0], # cu_seqlens_k
        self.forward_metadata.max_q_len,
        self.forward_metadata.max_q_len,
        softmax_scale=layer.scaling,
        causal=True,
    )
    # 内核输出为 fp8 时转回模型输入 dtype,保证下游接口一致。
    if o.dtype != self.input_dtype:
        o = o.to(self.input_dtype)
    return o.view(-1, layer.tp_q_head_num * layer.head_dim)

评论区精华

反量化尺度应使用 q/k/v 各自 scale 而非 one_scale 正确性

yichiche 指出 "Should use q / k / v scale, instead of one_scale." 原实现用 torch.ones 作为统一 scale,会丢失真实幅值。

结论:作者以 fixing commit "replacing one_scale with k,v,q scale by the reviewer remark for the new pertensor kernel" 落实,合并版本使用 fp8_q_descale/k_descale/v_descale。 · 已解决

减少全局变量,仅用 is_gfx95_supported 门控 设计

yichiche 建议 "Try to minimize the use of global variables to reduce management overhead... no need to introduce another global variable." 作者最初用模块级 _use_fmha_fp8_hd256 承载环境变量。

结论:作者采纳,最终在 forward_extend 内直接内联 get_bool_env_var("SGLANG_AITER_FMHA_FP8_ASM", "False") and is_gfx95_supported()。 · 已解决

回退内核选择:flash_attn_varlen_func vs mha_batch_prefill_func 性能

yichiche 建议 "It might be better to fall back to flash_attn_varlen_func... it should outperform mha_batch_prefill_func." 作者做了同容器同版本 A/B:flash_attn_varlen_func 在 num heads 256 下并非 asm,大 ISL 下 batch prefill 反而略好。

结论:保留 mha_batch_prefill_func 作为回退路径,并附上竞争基准图佐证。 · 已解决

门控条件收紧:is_extend、sinks、logits_soft_cap 正确性

yichiche 指出 target_verify 后 forward_extend 仍可能被非 extend 模式到达,应改用 forward_mode.is_extend();hd256 fp8 内核不支持 attention sinks 与 softcap,应显式门控而非依赖模型默认值。

结论:合并版本已包含 forward_mode.is_extend()、sinks is None、logits_soft_cap == 0.0 三个门控,与 #28482 保持一致。 · 已解决

非 fp8 KV cache 时不应走 fp8 内核 性能

yichiche 指出 "If FP8 KV cache is not enabled, this extra quantization step will ultimately hurt performance... if FP8 KV cache is enabled, use the FP8 kernel; otherwise, fall back to the original kernel."

结论:最终以 kv_cache_dtype == fp8_dtype 作为门控之一,bf16 KV 场景完整走原始路径。 · 已解决

PR CI 从未执行新代码路径 测试

amd-bot 审查指出:SGLANG_AITER_FMHA_FP8_ASM 仅在本文件引用,默认关闭,CI runner 为 gfx942 且无 fp8 KV + hd256 组合,因此 "Green here proves nothing";并列出多个 pipeline 在 gate 阶段 fast-fail。

结论:合并时未解决,仅依赖作者手动验证;后续由 #33399 负责默认启用与测试补齐。 · unresolved

风险与影响

  • 正确性风险:快速路径完全依赖手动验证,PR CI 从未执行(amd-bot 确认:环境变量仅在本文件引用、runner 为 gfx942、需要 fp8 KV + hd256)。若后续有人在 gfx950 上开启该变量,错误将直接进入生产。门控虽严,但无自动化回归保护,属于低概率高风险。
  • 性能风险:A/B 数据显示标准 IL8192/OL1024 在低并发(conc 4)下 TTFT 反而 +91%,conc 256 下 +427% 并伴随吞吐 -12.3%;60k ISL 场景 conc 256 下 ITL 只提升 1.6% 而 TTFT 仍达 168s。混合负载或小 batch 开启快速路径可能得不偿失,需要按负载特征选择是否开启。
  • 兼容性风险:flash_attn_varlen_fp8_pertensor_func 仅存在于 aiter main 分支(PR #3732 之后);旧版 aiter 构建会 ImportError,虽然 import 块有 try/except 兜底,但 aiter 后端整体本来就需要 aiter,升级 aiter 版本是硬前提。环境变量命名在 PR 演进中发生过不一致(body 中为 SGLANG_AITER_FMHA_FP8_HD256,最终代码为 SGLANG_AITER_FMHA_FP8_ASM),文档与代码存在轻微漂移风险。
  • 维护风险:与 #28482 的 SGLANG_AITER_FP8_CONTIG_PREFILL 并存,两条 opt-in 路径作用于同一函数、同一内核,互斥关系未在代码中显式声明;后续 #33399 又删变量改默认启用,说明该 PR 是过渡实现。
  • 用户影响:范围严格限定为 AMD gfx950 (MI350x) + aiter 后端 + head_dim 256 模型(如 Qwen3.5)+ fp8 KV cache 且显式设置 SGLANG_AITER_FMHA_FP8_ASM=1 的部署;默认用户零感知,无 API/模型行为变化。
  • 系统影响:目标场景为 60k 级长上下文 prefill,可获 TTFT -21~-40%、吞吐 +21~+39% 的显著收益;但短序列低并发与超高并发下存在 TTFT 回退观测,不建议无条件开启。
  • 团队影响:为 AMD 平台 Qwen3.5/MI350x 性能路径铺路,与 #28482 形成互补(MoE 长上下文 vs 标准形状),并直接孕育 #33399 的默认启用演进;同时暴露了 AMD 专用路径缺乏 CI 覆盖的流程问题。
新代码路径未被 CI 执行 默认关闭的 opt-in 分支 依赖 aiter main 分支内核 低并发短序列 TTFT 回退 环境变量命名前后不一致

关联 Issue

#3732 HD256 FMHA FP8 GFX950
#28482 [AMD] Contiguous varlen FMHA for Qwen3.5 mxfp4

完整报告

参与讨论