2026-09-12 13:39:19 +08:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
|
"""CPU fused-RMSNorm IR registration."""
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
import torch
|
|
|
|
|
|
|
|
|
|
from vllm import ir
|
|
|
|
|
from vllm_omni.model_executor.layers import cpu_ir_ops
|
|
|
|
|
|
|
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
|
|
|
|
|
|
|
|
|
pytest.importorskip("vllm")
|
|
|
|
|
|
|
|
|
|
_SKIP = not cpu_ir_ops.CPU_FUSED_NORMS
|
|
|
|
|
_REASON = "vLLM CPU build without _C rms_norm kernels"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _snr(a: torch.Tensor, b: torch.Tensor) -> float:
|
|
|
|
|
a, b = a.float(), b.float()
|
|
|
|
|
return float(20 * torch.log10(a.norm() / (a - b).norm().clamp(min=1e-9)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registered_under_its_own_provider():
|
|
|
|
|
"""Registering a new provider, rather than editing vllm_c.py, is what keeps
|
|
|
|
|
this working across wheel upgrades."""
|
|
|
|
|
assert cpu_ir_ops.PROVIDER in ir.ops.rms_norm.impls
|
|
|
|
|
assert cpu_ir_ops.PROVIDER in ir.ops.fused_add_rms_norm.impls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(_SKIP, reason=_REASON)
|
|
|
|
|
@pytest.mark.parametrize("tokens", [1, 5, 64])
|
|
|
|
|
def test_rms_norm_matches_the_native_impl(tokens):
|
|
|
|
|
torch.manual_seed(0)
|
|
|
|
|
x = torch.randn(tokens, 2048, dtype=torch.bfloat16)
|
|
|
|
|
w = torch.randn(2048, dtype=torch.bfloat16)
|
|
|
|
|
native = ir.ops.rms_norm.impls["native"].impl_fn(x, w, 1e-6)
|
|
|
|
|
ours = ir.ops.rms_norm.impls[cpu_ir_ops.PROVIDER].impl_fn(x, w, 1e-6)
|
|
|
|
|
assert ours.shape == native.shape and ours.dtype == native.dtype
|
|
|
|
|
# Both accumulate in their own order at bf16 output precision, so ~48 dB is
|
|
|
|
|
# the ceiling; a wrong kernel or layout lands far below.
|
|
|
|
|
assert _snr(native, ours) > 40.0, _snr(native, ours)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(_SKIP, reason=_REASON)
|
|
|
|
|
def test_fused_add_rms_norm_writes_both_outputs():
|
|
|
|
|
torch.manual_seed(0)
|
|
|
|
|
x = torch.randn(4, 2048, dtype=torch.bfloat16)
|
|
|
|
|
res = (x * 0.5).contiguous()
|
|
|
|
|
w = torch.randn(2048, dtype=torch.bfloat16)
|
|
|
|
|
n_out, n_res = ir.ops.fused_add_rms_norm.impls["native"].impl_fn(
|
|
|
|
|
x.clone(), res.clone(), w, 1e-6
|
|
|
|
|
)
|
|
|
|
|
o_out, o_res = ir.ops.fused_add_rms_norm.impls[cpu_ir_ops.PROVIDER].impl_fn(
|
|
|
|
|
x.clone(), res.clone(), w, 1e-6
|
|
|
|
|
)
|
|
|
|
|
assert _snr(n_out, o_out) > 40.0, _snr(n_out, o_out)
|
|
|
|
|
# The residual is a plain sum, so it should agree far more tightly.
|
|
|
|
|
assert _snr(n_res, o_res) > 100.0, _snr(n_res, o_res)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(_SKIP, reason=_REASON)
|
|
|
|
|
def test_three_dimensional_input_round_trips_its_shape():
|
|
|
|
|
x = torch.randn(2, 3, 2048, dtype=torch.bfloat16)
|
|
|
|
|
w = torch.randn(2048, dtype=torch.bfloat16)
|
|
|
|
|
out = ir.ops.rms_norm.impls[cpu_ir_ops.PROVIDER].impl_fn(x, w, 1e-6)
|
|
|
|
|
assert out.shape == x.shape
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_declines_arguments_the_kernel_cannot_take():
|
|
|
|
|
"""variance_size and a non-contiguous input must fall back to native."""
|
|
|
|
|
impl = ir.ops.rms_norm.impls[cpu_ir_ops.PROVIDER]
|
|
|
|
|
x = torch.randn(4, 2048, dtype=torch.bfloat16)
|
|
|
|
|
w = torch.randn(2048, dtype=torch.bfloat16)
|
|
|
|
|
assert impl.supports_args(x, w, 1e-6)
|
|
|
|
|
assert not impl.supports_args(x, w, 1e-6, 1024)
|
|
|
|
|
assert not impl.supports_args(x.t(), w, 1e-6)
|
|
|
|
|
# Mismatched weight dtype is the other case the kernel rejects.
|
|
|
|
|
assert not impl.supports_args(x, w.float(), 1e-6)
|
|
|
|
|
|
|
|
|
|
|
2026-09-12 14:55:24 +08:00
|
|
|
def test_priority_patch_is_opt_in_and_idempotent(monkeypatch):
|
|
|
|
|
"""Off by default, because that is what the measurement says.
|
|
|
|
|
|
|
|
|
|
In isolation these kernels are 3.6x and 4.5x faster than the native path;
|
|
|
|
|
in the compiled model they measured 72.7 tok/s against 74.0, because
|
|
|
|
|
inductor already fuses the native norm into the residual add. So the
|
|
|
|
|
registration always happens -- the provider stays selectable through
|
|
|
|
|
`kernel_config.ir_op_priority` -- but the default ranking does not change
|
|
|
|
|
unless it is asked for.
|
|
|
|
|
"""
|
2026-09-12 13:39:19 +08:00
|
|
|
if not cpu_ir_ops.CPU_FUSED_NORMS:
|
|
|
|
|
pytest.skip(_REASON)
|
|
|
|
|
from vllm.platforms.cpu import CpuPlatform
|
|
|
|
|
|
2026-09-12 14:55:24 +08:00
|
|
|
monkeypatch.delenv("VLLM_OMNI_CPU_FUSED_NORMS", raising=False)
|
|
|
|
|
cpu_ir_ops.prefer_cpu_fused_norms()
|
|
|
|
|
assert CpuPlatform.get_default_ir_op_priority(None).rms_norm == ["native"]
|
|
|
|
|
|
|
|
|
|
monkeypatch.setenv("VLLM_OMNI_CPU_FUSED_NORMS", "1")
|
2026-09-12 13:39:19 +08:00
|
|
|
cpu_ir_ops.prefer_cpu_fused_norms()
|
|
|
|
|
cpu_ir_ops.prefer_cpu_fused_norms() # second call must not double-wrap
|
|
|
|
|
priority = CpuPlatform.get_default_ir_op_priority(None)
|
|
|
|
|
assert priority.rms_norm[0] == cpu_ir_ops.PROVIDER
|
|
|
|
|
assert priority.rms_norm.count(cpu_ir_ops.PROVIDER) == 1
|
|
|
|
|
assert "native" in priority.rms_norm
|