Prhub

#46981 [XPU] Unify XPU RMSNorm kernels with vllm_c and drop redundant XPU-specific implementation

原始 PR 作者 chaojun-zhang 合并时间 2026-07-31 17:28 文件变更 7 提交数 2 评论 4 代码增减 +19 / -74

执行摘要

XPU RMSNorm 统一到 vllm_c,删除重复实现

PR body 明确说明:此前 vllm_c RMSNorm 内核只对 CUDA_ALIKE 启用,XPU 使用重复的 xpu_kernels provider;本 PR 将 XPU 切换到共享的 vllm_c 实现,并设置 IR 优先级为 vllm_c > native,目标是移除冗余的 XPU dispatch 代码并复用 vllm_c 的 RMSNorm 实现(No duplicate work)。本质上是对 IR 内核注册表的一次收敛,避免同一算子在不同平台维护多份几乎相同的实现。

值得快速精读,尤其是 vllm/kernels/vllm_c.pyGPGPU_DEVICE 的引入方式——通过一个平台聚合布尔值来扩展内核注册范围,是 IR 内核统一化的简洁范例。同时可关注 get_default_ir_op_priorityvllm_c > native 的优先级设计,理解 IR 的注册-选择-回退机制。对于 XPU 内核维护者,建议在合并后补充一次针对非连续输入与高维输入在真实模型上的 smoke 验证。

讨论亮点

该 PR 的 Review Comments 为空,技术讨论主要发生在 Issue 评论区:mergify[bot] 两次提示 PR 与 main 存在 merge conflicts 并要求 rebase,jikunshang 也留言请求解决冲突;作者通过第二个 commit(Merge branch 'main')完成合并解决。此外 jikunshang 指出 XPU v1 测试失败应由 PR#50530 修复,言下之意该失败不阻塞本 PR 合入。整个讨论没有出现实现方案层面的争论,说明这一重构方向在维护者间基本达成共识。

实现拆解

  1. 删除 XPU 专属实现:整体删除 vllm/kernels/xpu_ops.py(59 行),其中包含 is_xpu_kernels_found() 探测函数以及 rms_normfused_add_rms_norm 两个注册在 xpu_kernels 名下的 IR 实现;同时在 vllm/kernels/__init__.py 中移除对 xpu_ops 的导入与 __all__ 条目。
  2. 扩展 vllm_c 注册条件:在 vllm/kernels/vllm_c.py 中新增 GPGPU_DEVICE = CUDA_ALIKE or current_platform.is_xpu(),并将 rms_normfused_add_rms_norm 两个 register_implsupported 参数从 CUDA_ALIKE 改为 GPGPU_DEVICE,使 XPU 直接复用同一份 C 内核实现;ROCm 相关的 shape 归一化分支仅在 IS_ROCM 时生效,不影响 XPU。
  3. 调整 XPU IR 优先级:在 vllm/platforms/xpu.pyget_default_ir_op_priority 中,将默认优先级从 ["xpu_kernels", "native"] 改为 ["vllm_c", "native"],保证 XPU 在非 inductor 模式下优先选择 vllm_c,native 作为 fallback。
  4. 同步测试tests/kernels/ir/test_layernorm.py 将平台判断统一提取为 IS_GPGPU_DEVICE,移除 xpu_kernels provider 的参数化与注册断言,并保持对非连续、高维输入用例的覆盖;test_torch_opcheck 的 provider 列表也去掉 xpu_kernels
  5. CI 配套.buildkite/intel_jobs/engine_intel.yaml 在 V1 e2e job 的 source_file_dependencies 中加入 tests/test_config/,并在命令中追加 VLLM_XPU_ENABLE_XPU_GRAPH=1 pytest -v -s test_config.py.buildkite/intel_jobs/kernels_intel.yaml 新增 1 行(具体内容在提供材料中未展开,推测与内核测试文件依赖有关)。
文件 模块 状态 重要度
vllm/kernels/xpu_ops.py 内核分发 removed 8.29
vllm/kernels/vllm_c.py 内核分发 modified 5.22
vllm/platforms/xpu.py 平台适配 modified 5.1
tests/kernels/ir/test_layernorm.py 内核测试 modified 4.77
.buildkite/intel_jobs/engine_intel.yaml CI 配置 modified 3.76
.buildkite/intel_jobs/kernels_intel.yaml CI 配置 modified 2.6

关键符号

is_xpu_kernels_found rms_norm fused_add_rms_norm get_default_ir_op_priority

关键源码片段

vllm/kernels/xpu_ops.py deletion

整个文件被删除,是本 PR 的核心变更对象:移除 XPU 专属的 RMSNorm 与 fused_add_rms_norm 注册实现及 vllm_xpu_kernels 探测逻辑。

# 删除前的 vllm/kernels/xpu_ops.py 完整实现(PR#46981 中已删除)
# 注意:该实现与 vllm_c.py 中同名实现几乎完全相同,
# 都是分配输出张量后直接调用 torch.ops._C.rms_norm(vLLM 的 C 内核),
# 这正是本 PR 判定它为冗余重复、可以整体删除的依据。
import torch
from torch import Tensorfrom vllm import ir
from vllm.platforms import current_platformcurrent_platform.import_kernels()
​
​
def is_xpu_kernels_found() -> bool:
    # 通过 module spec 探测 vllm_xpu_kernels 是否安装,
    # 只有安装时才注册 xpu_kernels 实现
    from importlib.util import find_spec
    return find_spec("vllm_xpu_kernels") is not None
​
​
XPU_KERNELS_SUPPORTED = is_xpu_kernels_found()# 仅当不需要 variance_size 且 weight 为空或 dtype 匹配时才支持
rms_no_var = lambda x, weight, epsilon, variance_size=None: (
    variance_size is None and (weight is None or weight.dtype == x.dtype)
)
​
​
@ir.ops.rms_norm.register_impl(
    "xpu_kernels", supports_args=rms_no_var, supported=XPU_KERNELS_SUPPORTED
)
def rms_norm(
    x: Tensor, weight: Tensor | None, epsilon: float, variance_size: int | None = None
) -> Tensor:
    assert variance_size is None
    output = torch.empty(x.shape, device=x.device, dtype=x.dtype)
    torch.ops._C.rms_norm(output, x, weight, epsilon)
    return output
​
​
# fused_add_rms_norm 同样是重复实现,以 inplace 方式返回 x 与 x_residual
rms_add_no_var_size = (
    lambda x, x_residual, weight, epsilon, variance_size=None: variance_size is None
    and (weight is None or weight.dtype == x.dtype)
)
​
​
@ir.ops.fused_add_rms_norm.register_impl(
    "xpu_kernels",
    supports_args=rms_add_no_var_size,
    supported=XPU_KERNELS_SUPPORTED,
    inplace=True,
)
def fused_add_rms_norm(
    x: Tensor,
    x_residual: Tensor,
    weight: Tensor | None,
    epsilon: float,
    variance_size: int | None = None,
) -> tuple[Tensor, Tensor]:
    assert variance_size is None
    torch.ops._C.fused_add_rms_norm(x, x_residual, weight, epsilon)
    return x, x_residual
vllm/kernels/vllm_c.py core-logic

内核注册的核心改动文件:新增 GPGPU_DEVICE 聚合判定,将 rms_norm 与 fused_add_rms_norm 的注册支持面从 CUDA_ALIKE 扩展到包含 XPU。

# vllm/kernels/vllm_c.py(PR#46981 改动后的关键部分)
# 改动核心:把“仅 CUDA_ALIKE”扩展为“CUDA_ALIKE 或 XPU”,
# 使 XPU 直接复用同一份 vllm_c 实现,消除了 xpu_ops.py 的重复代码。
import torch
from torch import Tensorfrom vllm import ir
from vllm.platforms import current_platformcurrent_platform.import_kernels()CUDA_ALIKE = current_platform.is_cuda_alike()
IS_ROCM = current_platform.is_rocm()# 新增的 GPGPU_DEVICE 判断:CUDA/ROCm/XPU 都走 vllm_c
GPGPU_DEVICE = CUDA_ALIKE or current_platform.is_xpu()# 仅当不需要 variance_size 且 weight 为空或 dtype 匹配时才支持
rms_no_var_size = lambda x, weight, epsilon, variance_size=None: (
    variance_size is None and (weight is None or weight.dtype == x.dtype)
)
​
​
@ir.ops.rms_norm.register_impl(
    "vllm_c", supports_args=rms_no_var_size, supported=GPGPU_DEVICE
)
def rms_norm(
    x: Tensor, weight: Tensor | None, epsilon: float, variance_size: int | None = None
) -> Tensor:
    assert variance_size is None
    # ROCm 的 C 内核只接受连续 2D 张量,高维 / 非连续输入先 reshape;
    # XPU 与 CUDA 直接走原路径,不进入该分支。
    if IS_ROCM and (x.dim() > 2 or not x.is_contiguous()):
        original_shape = x.shape
        x = x.reshape(-1, original_shape[-1])
        output = torch.empty(x.shape, device=x.device, dtype=x.dtype)
        torch.ops._C.rms_norm(output, x, weight, epsilon)
        return output.reshape(original_shape)
​
    output = torch.empty(x.shape, device=x.device, dtype=x.dtype)
    torch.ops._C.rms_norm(output, x, weight, epsilon)
    return output
​
​
rms_add_no_var_size = lambda x, x_residual, weight, epsilon, variance_size=None: (
    variance_size is None and (weight is None or weight.dtype == x.dtype)
)
​
​
@ir.ops.fused_add_rms_norm.register_impl(
    "vllm_c",
    supports_args=rms_add_no_var_size,
    supported=GPGPU_DEVICE,
    inplace=True,
)
def fused_add_rms_norm(
    x: Tensor,
    x_residual: Tensor,
    weight: Tensor | None,
    epsilon: float,
    variance_size: int | None = None,
) -> tuple[Tensor, Tensor]:
    assert variance_size is None
    # ROCm 非连续时回退到 native 实现并 copy 回原张量,保持 inplace 语义
    if IS_ROCM and (not x.is_contiguous() or not x_residual.is_contiguous()):
        output, residual = ir.ops.fused_add_rms_norm.impls["native"].impl_fn(
            x, x_residual, weight, epsilon
        )
        x.copy_(output)
        x_residual.copy_(residual)
        return x, x_residual
​
    # XPU/CUDA 直接调用 libtorch 稳定的 C 内核
    torch.ops._C.fused_add_rms_norm(x, x_residual, weight, epsilon)
    return x, x_residual
vllm/platforms/xpu.py core-logic

XPU 平台 IR 默认优先级的关键调整点,决定 RMSNorm 在 XPU 上优先选择 vllm_c 还是 native。

# vllm/platforms/xpu.py 中 get_default_ir_op_priority 的改动(head 版本)
# 改动核心:把默认 IR 优先级从 ["xpu_kernels", "native"] 改为 ["vllm_c", "native"],
# 让 XPU 在非 inductor 模式下优先复用统一的 vllm_c 内核,native 兜底。
@classmethod
def get_default_ir_op_priority(
    cls, vllm_config: "VllmConfig"
) -> "IrOpPriorityConfig":
    from vllm.config.compilation import CompilationMode
    from vllm.config.kernel import IrOpPriorityConfig
​
    # 启用 inductor 代码生成时默认用 native,避免 fusion 与 codegen 冲突;
    # 否则把 vllm_c 放在 native 之前,让 XPU 优先使用 C 内核。
    cc = vllm_config.compilation_config
    using_inductor = cc.backend == "inductor" and cc.mode != CompilationMode.NONE
    default = ["native"] if using_inductor else ["vllm_c", "native"]
​
    return IrOpPriorityConfig.with_default(default)

评论区精华

Merge conflict 处理 other

mergify[bot] 两次提示 pull request 与 main 产生 merge conflicts,要求 rebase;jikunshang 也留言要求解决冲突。

结论:作者通过合并 main 分支解决冲突(提交 56a98ef 为 Merge branch 'main')。 · 已解决

XPU v1 测试失败与 PR#50530 的依赖 测试

jikunshang 在评论中指出 xpu v1 test failed case 应由 PR#50530 合并后修复。

结论:测试失败独立于本 PR,后续由 PR#50530 修复,不阻塞本 PR 合入。 · 已解决

风险与影响

  1. C 内核行为差异风险vllm_c.pyrms_norm / fused_add_rms_norm 对 XPU 直接调用 torch.ops._C.*,没有像 ROCm 分支那样对非连续或高维输入做 reshape 处理。虽然测试 test_layernorm.pytest_vllm_c_rms_norm_accepts_transposed_input 等用例在 IS_GPGPU_DEVICE 下也会覆盖 XPU,但真实模型中可能出现未覆盖的 stride 布局,若 XPU 上的 libtorch 稳定 C 内核与 CUDA 行为不一致,可能产生精度或崩溃问题。
  2. IR 优先级变更性能影响:XPU 默认从 xpu_kernels 切换到 vllm_c,若两者底层内核实现或优化程度不同,可能带来性能波动;从代码看两者最终都调用 torch.ops._C.rms_norm,风险较低,但仍需 benchmark 确认。
  3. 依赖缺失场景:原 xpu_ops.py 通过 find_spec("vllm_xpu_kernels") 判断支持性,删除后 XPU 直接依赖 vllm_c 的内核;若某些 vLLM 构建未编译 XPU 的 C 内核,会 fallback 到 native,功能正确性有保障,但性能可能回退。
  4. CI 覆盖变化engine_intel.yaml 新增 test_config.py 在 XPU graph 下的运行,同时对 source_file_dependencies 的调整可能改变测试触发的粒度,存在 CI 偶发失败或漏跑的可能。

对 XPU 用户而言,RMSNorm 类算子的 IR 实现由专属 xpu_kernels 统一到 vllm_c,行为上保持一致(底层同为 torch.ops._C 内核),且默认优先级变为 vllm_c > native,能复用到 CUDA/ROCm 路径的后续优化;对维护者而言,删除 59 行重复代码,减少 IR 内核注册表中需要维护的平台分支;对团队而言,Intel CI 增加 XPU graph 下的 test_config.py 执行,覆盖更广。整体影响范围集中在 XPU 平台的内核分发路径,属于低风险、正向的清理性变更。

删除重复实现路径 IR 优先级变更 XPU 非连续输入路径未特判 依赖 PR#50530 修复 v1 测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论