Prhub

#47773 [ROCm] Cache fp32 upcast of static e8m0 weight scale in AITER scaled_mm

原始 PR 作者 jiacao-amd 合并时间 2026-07-29 00:34 文件变更 1 提交数 5 评论 9 代码增减 +16 / -8

执行摘要

提前缓存 AITER e8m0 权重标量 fp32 转换

在 AITER 的块缩放 GEMM 路径中,静态 e8m0 权重标量在每个 decode 步骤、每层都执行 <<23 的 fp32 upcast 和 .contiguous(),而该值从未改变。在 DeepSeek-V4 FP4 (MI355X / gfx950) 上,这约占 GPU 时间的 3.5%(分布在 aten::lshift 和 direct_copy)。该 PR 旨在通过将转换移至模型加载时消除这部分开销。

值得精读,尤其是对 vLLM 的权重加载钩子 process_weights_after_loading 的使用展示了如何优雅地将重复计算从热路径移除。对于从事 ROCm 性能优化或量化推理的开发者有直接参考价值。

讨论亮点

核心讨论围绕导入风格和实现细节:

  • 导入位置:dllehr-amd 建议将 _upcast_e8m0_to_fp32replace_parameter 的导入从函数内部移到模块顶部以保持一致性,作者随后修改。
  • FP8BlockParams 导入:dllehr-amd 指出应从已导入的 BlockScaledMMLinearKernel 中直接导入 FP8BlockParams,而非在函数内懒加载,作者修正。
  • 其他 scale 形状:dllehr-amd 询问是否考虑过 uint8 等形状,作者通过路径追踪确认当前 AITER kernel 仅处理 e8m0 或 float32 权重 scale,因此仅对 e8m0 upcast 是安全的。

实现拆解

  1. 导入提升:在文件顶部新增 from vllm.model_executor.layers.quantization.utils.fp8_utils import _upcast_e8m0_to_fp32from .BlockScaledMMLinearKernel import FP8BlockParams,将原本在函数内部的懒加载导入统一移到模块级别,提升代码一致性。
  2. 新增 process_weights_after_loading:在 AiterFp8BlockScaledMMKernel 类中重写 process_weights_after_loading 方法。先调用父类方法,然后通过 FP8BlockParams.from_layer(layer) 获取权重 scale 参数,若存在且为 float8_e8m0fnu 类型,则用 _upcast_e8m0_to_fp32 转换为 fp32,并通过 replace_parameter 替换回 layer。这样模型加载后权重 scale 恒为 fp32
  3. 简化 apply_block_scaled_mm:移除原来针对 Bs 的 e8m0 upcast 分支(if Bs.dtype == torch.float8_e8m0fnu: ... else: ...),改为统一的 Bs = Bs.to(torch.float32)。由于 Bs 已在加载时转为 fp32,该行实际为空操作。同时移除方法内对 _upcast_e8m0_to_fp32 的本地导入以避免重复。
  4. 性能与精度验证:通过在 DeepSeek-V4 FP4 8×MI355X 上的解码测压(1024/1024,并发 16–128)验证,__lshift__direct_copy 从 profile 中消失,吞吐量提升 4.2–8.8%,TPOT 降低 4.8–9.7%。gsm8k 5-shot 全量 1319 样本精度验证显示 strict-match 从 0.9500 变为 0.9484,差异在正常波动范围内,确认无精度损失。
文件 模块 状态 重要度
vllm/model_executor/kernels/linear/scaled_mm/aiter.py 量化核 modified 6.8

关键符号

process_weights_after_loading apply_block_scaled_mm

关键源码片段

vllm/model_executor/kernels/linear/scaled_mm/aiter.py core-logic

所有核心改动均在此文件:新增 process_weights_after_loading、修改 apply_block_scaled_mm、调整导入。是唯一修改的文件。

# 文件路径 : vllm/model_executor/kernels/linear/scaled_mm/aiter.py
# 关键方法 : process_weights_after_loading 与 apply_block_scaled_mm 片段def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
    super().process_weights_after_loading(layer) # 先调用父类处理
​
    # 从 layer 获取权重 scale 参数(兼容 weight_scale_inv 和 weight_scale)
    params = FP8BlockParams.from_layer(layer)
    if params.weight_scale_inv is not None:
        ws, attr = params.weight_scale_inv, params.WEIGHT_SCALE_INV
    else:
        ws, attr = params.weight_scale, params.WEIGHT_SCALE
​
    # 如果权重 scale 是 e8m0,则在加载时一次性转换为 fp32 并替换参数
    if ws is not None and ws.dtype == torch.float8_e8m0fnu:
        replace_parameter(layer, attr, _upcast_e8m0_to_fp32(ws).contiguous())def apply_block_scaled_mm(
    self,
    A: torch.Tensor,
    B: torch.Tensor,
    As: torch.Tensor,
    Bs: torch.Tensor,
) -> torch.Tensor:
    if As.dtype != Bs.dtype:
        # As 仍是动态的(e8m0),需要 upcast
        if As.dtype == torch.float8_e8m0fnu:
            As = _upcast_e8m0_to_fp32(As).contiguous()
        else:
            As = As.to(torch.float32)
​
        # Bs 已在 process_weights_after_loading 中转为 fp32,这里仅做类型对账
        Bs = Bs.to(torch.float32)
​
    out_dtype = self.config.out_dtype
    gemm_op = rocm_aiter_ops.triton_gemm_a8w8_blockscale if self.use_triton \
              else rocm_aiter_ops.gemm_a8w8_blockscale
​
    return gemm_op(A, B, As, Bs, list(self.weight_group_shape), output_dtype=out_dtype)

评论区精华

导入位置:函数内懒加载 vs 模块顶部 style

dllehr-amd 建议将 _upcast_e8m0_to_fp32 和 replace_parameter 导入从函数内部移到模块顶部,以保持代码一致性。

结论:作者将导入提升到模块顶部(commit e77653ab)。 · 已解决

FP8BlockParams 导入方式 设计

dllehr-amd 指出 .BlockScaledMMLinearKernel 已在顶部导入,应直接将 FP8BlockParams 放在顶部,而非函数内懒加载。

结论:作者修改为模块顶部导入(commit e77653ab)。 · 已解决

重复导入 _upcast_e8m0_to_fp32 style

dllehr-amd 指出 _upcast_e8m0_to_fp32 现在被导入两次,建议移到顶部。

结论:作者删除重复导入,统一在模块顶部导入(commit e77653ab)。 · 已解决

是否考虑其他 scale 形状(如 uint8) question

dllehr-amd 提问是否考虑过其他 scale 形状(如 uint8),当前 kernel 可能接收其他类型。

结论:作者追踪量化路径后确认,当前 kernel 仅处理 e8m0 或 float32 权重 scale,所以只对 e8m0 做 upcast 足够。 · 已解决

风险与影响

风险较低。主要风险:

  1. 若某些模型在使用 AITER 路径时 weight scale 不是 e8m0 或 float32(如 uint8),process_weights_after_loading 中的判断会跳过转换,apply_block_scaled_mm 中的 Bs.to(torch.float32) 仍能兼容处理,但失去优化机会,不影响正确性。
  2. 依赖父类 process_weights_after_loading 被框架正确调用,vLLM 框架保证这一点。
  3. 缺少针对该路径的单元测试,但性能测试和精度验证覆盖了功能正确性。

影响范围:仅限使用 AITER block-scaled GEMM 的 ROCm 平台用户(主要是 MI355X/gfx950 运行 DeepSeek-V4 FP4 等模型)。用户无感升级即可获得 4–9% 的吞吐量提升和 5–10% 的延迟降低。系统无需重新编译或配置。团队无额外维护成本。

仅 ROCm AITER 路径 依赖框架钩子 无单元测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论