[Perf] Fuse AdaLayerNorm CUDA eager path with Triton (#8231)

Signed-off-by: GUOGUO <xwdong1998@163.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
Dong1017andamy-why-3459 authored and GitHub committed 2026-10-06 15:11:45 +00:00
1 parent 182b77da4c
commit ee9ab9ea88
8 files changed
+1255 -14

No files matched your search

+1 -1
View File
@@ -105,7 +105,7 @@ steps:
- label: "Diffusion · Other Test"
commands:
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion -m 'core_model and cuda' --run-level "core_model"
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion tests/diffusion/layers/test_adalayernorm_*.py -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1
- group: ":card_index_dividers: Tiny Model Tests [Single GPU]"
+15 -11
View File
@@ -80,9 +80,13 @@ parallelism at `--usp 4` so the two CFG branches cover all 8 GPUs.
- `--use-hsdp` is recommended for memory efficiency on the 14B DiT backbone.
- `--vae-patch-parallel-size 8` is recommended for the standard 8-card setup;
disabling VAE patch parallelism can significantly increase VAE latency.
- The CUDA Triton fused AdaLayerNorm path is not available in the current
release. The `VLLM_OMNI_ENABLE_TRITON_ADALN` toggle is reserved for that
future implementation and should not be used until the kernel lands.
- CUDA eager execution uses the fused Triton AdaLayerNorm path for supported
contiguous 3D inputs with shared `(C,)`, `(1, C)`, or `(1, 1, C)` modulation,
or per-sample `(B, 1, C)` modulation, including supported strided chunk views.
Unsupported inputs use the native path; per-token `(B, L, C)` modulation
used by TI2V remains native.
- During `torch.compile`, AdaLayerNorm uses the native operation chain, which
Inductor can fuse. The eager Triton path remains available after compilation.
- In an 8x NVIDIA H20 test with the official model, `num_inference_steps=4`,
`flow_shift=12.0`, `--use-hsdp`, `--cfg-parallel-size 2`, `--usp 4`, and
`--vae-patch-parallel-size 8`, an internal Triton AdaLayerNorm prototype
@@ -181,20 +185,20 @@ for complete client examples and request formats.
#### Notes
- **Key flags:**
- `--omni` — enables vLLM-Omni diffusion serving.
- `--use-hsdp` — enables Hybrid Sharded Data Parallelism for the DiT model
- `--omni` — enables vLLM-Omni diffusion serving.
- `--use-hsdp` — enables Hybrid Sharded Data Parallelism for the DiT model
weights.
- `--usp <N>` — Unified Sequence Parallelism degree.
- `--cfg <N>` — Classifier-Free Guidance parallelism; set to 2 for models
- `--usp <N>` — Unified Sequence Parallelism degree.
- `--cfg <N>` — Classifier-Free Guidance parallelism; set to 2 for models
that require negative-prompt computation, omit for distilled models.
- `--vae-patch-parallel-size 8` — parallelizes VAE decoding across all 8
- `--vae-patch-parallel-size 8` — parallelizes VAE decoding across all 8
cards.
- `--vae-use-tiling` — enables tiled VAE decoding to reduce peak memory.
- `--vae-use-tiling` — enables tiled VAE decoding to reduce peak memory.
- **Performance tips:**
- Installing mindie-sd and enabling Laser Attention
- Installing mindie-sd and enabling Laser Attention
(`MINDIE_SD_FA_TYPE=ascend_laser_attention`) provides up to ~40%
performance improvement at 720p resolution due to long-sequence attention
optimization.
- **Known limitations:**
- `MULTI_STREAM_MEMORY_REUSE=2` is required on NPU when using HSDP/FSDP2
- `MULTI_STREAM_MEMORY_REUSE=2` is required on NPU when using HSDP/FSDP2
due to a multi-stream memory reuse bug. This is not needed on CUDA.
@@ -0,0 +1,110 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Compile and CUDA-graph smoke tests for the fused AdaLayerNorm fast path.
The serving stack reaches AdaLayerNorm.forward_cuda through CustomOp dispatch
from compiled diffusion workloads (lazy regional torch.compile on repeated
blocks) and can capture CUDA graphs around pipeline stages. These smokes
verify that torch.compile uses the native chain and leaves the eager fused
path eligible, and that the fused path can be captured/replayed inside a CUDA
graph with outputs matching native. They are smoke tests, not performance
measurements.
"""
import pytest
import torch
from vllm_omni.diffusion.layers.adalayernorm import (
_FAILED_ADALN_KEYS,
AdaLayerNorm,
_adaln_fused_forward,
)
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cuda]
def _make(bs=2, seq=512, hidden=3072, dtype=torch.bfloat16, seed=0, framewise=False):
if framewise:
hidden = 2240
m = AdaLayerNorm(hidden).to(device="cuda", dtype=dtype)
g = torch.Generator(device="cuda").manual_seed(seed)
if framewise:
x = torch.randn(bs, 4, seq // 4, hidden, generator=g, device="cuda", dtype=dtype)
modulation = torch.randn(bs, 4, 6, hidden, generator=g, device="cuda", dtype=dtype)
scale, shift = modulation.chunk(6, dim=2)[:2]
return m, x, scale, shift
x = torch.randn(bs, seq, hidden, generator=g, device="cuda", dtype=dtype)
scale = torch.randn(bs, 1, hidden, generator=g, device="cuda", dtype=dtype)
shift = torch.randn(bs, 1, hidden, generator=g, device="cuda", dtype=dtype)
return m, x, scale, shift
@pytest.mark.parametrize("framewise", [False, True])
def test_compile_smoke(framewise):
# torch.compile over the guarded forward_cuda: with the is_compiling
# guard the compiled region takes the NATIVE chain, so this compares
# inductor's own LN+modulation fusion against the eager 4-kernel chain.
# Cross-implementation BF16 outputs differ at rounding level (up to a few
# ulps at O(1-8) magnitudes), hence the loose tolerance. The smoke
# verifies the compiled workload stays functional - no crash, finite
# outputs, no request failure.
m, x, scale, shift = _make(framewise=framewise)
compiled = torch.compile(m.forward_cuda, dynamic=False, fullgraph=True)
out1 = compiled(x, scale, shift)
out2 = compiled(x, scale, shift)
native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out1.float(), native.float(), atol=5e-2, rtol=2e-2)
torch.testing.assert_close(out2.float(), native.float(), atol=5e-2, rtol=2e-2)
runtime_key = (
x.device.index,
x.shape[-1],
x.dtype,
m.layernorm.weight is not None,
m.layernorm.bias is not None,
True,
True,
)
assert runtime_key not in _FAILED_ADALN_KEYS, "compile must not cache an eager launch failure"
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "compile must leave the eager fused path eligible"
torch.testing.assert_close(fused.float(), native.float(), atol=2e-2, rtol=2e-2)
assert runtime_key not in _FAILED_ADALN_KEYS
@pytest.mark.parametrize("framewise", [False, True])
def test_cuda_graph_capture_replay_smoke(framewise):
# CUDA graph capture must prove the FUSED path itself is captured: call
# _adaln_fused_forward inside the capture and assert it returned a tensor
# (None would mean the guard/fallback silently degraded the graph to the
# native chain). Warm the JIT on a side stream so capture contains no
# compile-time allocations, then replay and compare against the native
# fallback on the same static input buffers.
m, x, scale, shift = _make(framewise=framewise)
s = torch.cuda.Stream()
s.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(s):
for _ in range(3):
fused_warm = _adaln_fused_forward(m, x, scale, shift)
assert fused_warm is not None
torch.cuda.current_stream().wait_stream(s)
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
out_graph = _adaln_fused_forward(m, x, scale, shift)
assert out_graph is not None
graph.replay()
torch.accelerator.synchronize()
native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out_graph.float(), native.float(), atol=2e-2, rtol=2e-2)
# Replay must consume NEW input values: mutate the captured buffers and
# verify the replay output tracks them (a no-op replay would pass the
# check above trivially).
x2, scale2, shift2 = torch.randn_like(x), torch.randn_like(scale), torch.randn_like(shift)
x.copy_(x2)
scale.copy_(scale2)
shift.copy_(shift2)
graph.replay()
torch.accelerator.synchronize()
native2 = m.forward_native(x, scale, shift)
torch.testing.assert_close(out_graph.float(), native2.float(), atol=2e-2, rtol=2e-2)
assert not torch.equal(native, native2), "replay must consume the new input"
@@ -0,0 +1,234 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Candidate-added tests for the fused Triton AdaLayerNorm CUDA path.
Complements (never replaces) the frozen suite: exercises the fused fast path
and the forward_native fallback branches of forward_cuda. Uses the same
tolerance scheme as the frozen suite; golden = forward_native real behavior.
"""
import pytest
import torch
from vllm_omni.diffusion.layers.adalayernorm import (
AdaLayerNorm,
_adaln_fused_forward,
)
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cuda]
TOL_STRICT = {"bf16": (2e-2, 2e-2), "fp32": (1e-3, 1e-3)}
# Matches the frozen suite: the double-rounded golden chain deviates from a
# pure-fp32 reference more than the strict band, so the fp32-reference check
# uses the same loose band as the frozen tests.
TOL_LOOSE = {"bf16": (5e-2, 2e-2), "fp32": (5e-3, 5e-3)}
DTYPES = [torch.bfloat16, torch.float32]
def fp32_reference(x, scale, shift, eps):
xf = x.float()
xn = torch.nn.functional.layer_norm(xf, (x.shape[-1],), None, None, eps)
return xn * (1 + scale.float().reshape(1, 1, -1)) + shift.float().reshape(1, 1, -1)
def make_module(hidden, affine, eps, device, dtype):
m = AdaLayerNorm(hidden, elementwise_affine=affine, eps=eps)
if affine:
m = m.to(dtype)
return m.to(device)
def assert_close(a, b, dtype, loose=False):
atol, rtol = (TOL_LOOSE if loose else TOL_STRICT)["bf16" if dtype == torch.bfloat16 else "fp32"]
torch.testing.assert_close(a.float(), b.float(), atol=atol, rtol=rtol)
def make_inputs(bs, seq, hidden, dtype, device, seed=0):
g = torch.Generator(device=device).manual_seed(seed)
x = torch.randn(bs, seq, hidden, generator=g, device=device, dtype=dtype)
scale = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
return x, scale, shift
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("affine", [False, True])
@pytest.mark.parametrize("bs,seq,hidden", [(1, 512, 3072), (2, 128, 1536), (1, 3, 1000)])
@torch.no_grad()
def test_fused_fast_path_matches_native(dtype, affine, bs, seq, hidden):
# hidden=1000 exercises BLOCK_C masking (next_pow2(1000)=1024 > 1000)
device = "cuda"
m = make_module(hidden, affine, 1e-6, device, dtype)
x, scale, shift = make_inputs(bs, seq, hidden, dtype, device)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "supported inputs must take the fused path so BLOCK_C masking is exercised"
out = fused
assert out.shape == x.shape and out.dtype == dtype and out.device == x.device
assert_close(out, m.forward_native(x, scale, shift), dtype)
assert_close(out, fp32_reference(x, scale, shift, 1e-6), dtype, loose=True)
@pytest.mark.parametrize("dtype", DTYPES)
def test_fused_noncontiguous_x(dtype):
# Non-contiguous x routes to the forward_native fallback (the fused kernel
# requires contiguous x); result must still match native exactly.
device = "cuda"
hidden = 1536
m = make_module(hidden, False, 1e-6, device, dtype)
g = torch.Generator(device=device).manual_seed(3)
x_big = torch.randn(1, 1024, hidden, generator=g, device=device, dtype=dtype)
x = x_big[:, ::2, :]
scale = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
out = m.forward_cuda(x, scale, shift)
assert_close(out, m.forward_native(x, scale, shift), dtype)
assert_close(out, fp32_reference(x.contiguous(), scale, shift, 1e-6), dtype, loose=True)
def test_normalized_shape_mismatch_preserves_native_error():
m = AdaLayerNorm(16, elementwise_affine=False).cuda()
x = torch.randn(1, 2, 32, device="cuda")
scale = torch.zeros(32, device="cuda")
shift = torch.zeros_like(scale)
assert _adaln_fused_forward(m, x, scale, shift) is None
with pytest.raises(RuntimeError) as native_error:
m.forward_native(x, scale, shift)
with pytest.raises(RuntimeError) as cuda_error:
m.forward_cuda(x, scale, shift)
assert str(cuda_error.value) == str(native_error.value)
@pytest.mark.parametrize("grad_target", ["x", "scale", "shift", "weight", "bias"])
def test_grad_fallback_matches_native_backward(grad_target):
m = make_module(32, grad_target in ("weight", "bias"), 1e-6, "cuda", torch.float32)
m.requires_grad_(False)
x, scale, shift = make_inputs(1, 2, 32, torch.float32, "cuda", seed=12)
targets = {"x": x, "scale": scale, "shift": shift, "weight": m.layernorm.weight, "bias": m.layernorm.bias}
target = targets[grad_target]
target.requires_grad_(True)
with torch.enable_grad():
assert _adaln_fused_forward(m, x, scale, shift) is None
native = m.forward_native(x, scale, shift)
out = m.forward_cuda(x, scale, shift)
assert out.requires_grad
torch.testing.assert_close(out, native)
upstream = torch.randn_like(out)
native_grad = torch.autograd.grad(native, target, upstream)[0]
cuda_grad = torch.autograd.grad(out, target, upstream)[0]
torch.testing.assert_close(cuda_grad, native_grad)
def test_no_grad_keeps_fused_path_for_grad_requiring_tensors():
m = make_module(32, True, 1e-6, "cuda", torch.float32)
x, scale, shift = make_inputs(1, 2, 32, torch.float32, "cuda", seed=14)
for tensor in (x, scale, shift):
tensor.requires_grad_(True)
with torch.no_grad():
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None
assert not fused.requires_grad
torch.testing.assert_close(fused, m.forward_native(x, scale, shift), atol=1e-3, rtol=1e-3)
def test_1d_scale_shift_fast_path():
# (C,) scale/shift broadcast per-channel exactly like (1, C)
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, torch.bfloat16)
g = torch.Generator(device=device).manual_seed(10)
x = torch.randn(1, 256, hidden, generator=g, device=device, dtype=torch.bfloat16)
scale = torch.randn(hidden, generator=g, device=device, dtype=torch.bfloat16)
shift = torch.randn(hidden, generator=g, device=device, dtype=torch.bfloat16)
out = _adaln_fused_forward(m, x, scale, shift)
assert out is not None, "(C,) scale/shift must take the fused path"
assert_close(out, m.forward_native(x, scale, shift), torch.bfloat16)
assert_close(out, fp32_reference(x, scale, shift, 1e-6), torch.bfloat16, loose=True)
@pytest.mark.parametrize("dtype", DTYPES)
def test_per_row_scale_falls_back_to_native(dtype):
# (L, C) scale with B=1 is a valid per-row torch broadcast; it is not
# per-channel, so forward_cuda must route it to forward_native.
device = "cuda"
hidden = 512
m = make_module(hidden, False, 1e-6, device, dtype)
g = torch.Generator(device=device).manual_seed(4)
x = torch.randn(1, 64, hidden, generator=g, device=device, dtype=dtype)
scale = torch.randn(64, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(64, hidden, generator=g, device=device, dtype=dtype)
out = m.forward_cuda(x, scale, shift)
ref = m.forward_native(x, scale, shift)
assert out.dtype == ref.dtype
assert_close(out, ref, dtype)
@pytest.mark.parametrize("dtype", DTYPES)
def test_mixed_dtype_falls_back_to_native(dtype):
# scale/shift dtype != x.dtype: native type-promotes; fused path requires
# matching dtypes, so this must go through forward_native.
other = torch.float32 if dtype == torch.bfloat16 else torch.bfloat16
device = "cuda"
hidden = 256
m = make_module(hidden, False, 1e-6, device, dtype)
g = torch.Generator(device=device).manual_seed(5)
x = torch.randn(1, 32, hidden, generator=g, device=device, dtype=dtype)
scale = torch.randn(1, hidden, generator=g, device=device, dtype=other)
shift = torch.randn(1, hidden, generator=g, device=device, dtype=other)
out = m.forward_cuda(x, scale, shift)
ref = m.forward_native(x, scale, shift)
assert out.dtype == ref.dtype
torch.testing.assert_close(out, ref)
@pytest.mark.parametrize("dtype", DTYPES)
def test_zero_dim_scale_falls_back_to_native(dtype):
# 0-d scalar modulation is a valid torch broadcast; not per-channel.
device = "cuda"
hidden = 256
m = make_module(hidden, False, 1e-6, device, dtype)
g = torch.Generator(device=device).manual_seed(6)
x = torch.randn(1, 32, hidden, generator=g, device=device, dtype=dtype)
scale = torch.tensor(0.5, device=device, dtype=dtype)
shift = torch.tensor(-0.25, device=device, dtype=dtype)
out = m.forward_cuda(x, scale, shift)
assert_close(out, m.forward_native(x, scale, shift), dtype)
@pytest.mark.parametrize("dtype", DTYPES)
def test_per_sample_scale_raises_like_native(dtype):
# Frozen behavior: B>1 with (B, C) scale/shift -> RuntimeError.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
g = torch.Generator(device=device).manual_seed(7)
x = torch.randn(4, 64, hidden, generator=g, device=device, dtype=dtype)
scale = torch.randn(4, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(4, hidden, generator=g, device=device, dtype=dtype)
with pytest.raises(RuntimeError):
m.forward_native(x, scale, shift)
with pytest.raises(RuntimeError):
m.forward_cuda(x, scale, shift)
@pytest.mark.parametrize("dtype", DTYPES)
def test_fused_determinism_repeat_calls(dtype):
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(1, 1024, hidden, dtype, device, seed=8)
out1 = _adaln_fused_forward(m, x, scale, shift)
out2 = _adaln_fused_forward(m, x, scale, shift)
assert out1 is not None and out2 is not None, "determinism must exercise the fused path"
assert torch.equal(out1, out2)
@pytest.mark.parametrize("eps", [1e-5, 1e-3])
def test_fused_eps_variants(eps):
device = "cuda"
hidden = 3072
m = make_module(hidden, False, eps, device, torch.bfloat16)
x, scale, shift = make_inputs(1, 256, hidden, torch.bfloat16, device, seed=9)
out = _adaln_fused_forward(m, x, scale, shift)
assert out is not None, "supported eps variants must take the fused path"
assert_close(out, m.forward_native(x, scale, shift), torch.bfloat16)
assert_close(out, fp32_reference(x, scale, shift, eps), torch.bfloat16, loose=True)
@@ -0,0 +1,135 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Direct tests for the synchronous launch-failure fallback (C3a).
Simulates a backend-specific Triton launch failure (the launcher raises
synchronously) and verifies the two-layer response: the failed config is
recorded, the caller gets the correct native result, the same config is not
retried, and a different constexpr variant stays eligible.
The failed-key set is module-global; the autouse fixture snapshots and
restores it so tests are self-contained and independent of execution order.
Collection is safe on non-Triton environments: the Triton-only kernel symbol
is resolved lazily inside each test and skips if unavailable.
"""
import pytest
import torch
from vllm_omni.diffusion.layers import adalayernorm as adaln_mod
from vllm_omni.diffusion.layers.adalayernorm import (
_FAILED_ADALN_KEYS,
AdaLayerNorm,
)
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cuda]
# Unique channels + eps so the failed keys used here cannot collide with
# other tests in the same session.
_HIDDEN = 2048
_EPS = 1e-5
@pytest.fixture(autouse=True)
def _isolate_failed_keys():
saved = set(_FAILED_ADALN_KEYS)
_FAILED_ADALN_KEYS.clear()
try:
yield
finally:
_FAILED_ADALN_KEYS.clear()
_FAILED_ADALN_KEYS.update(saved)
def _make(bs=1):
m = AdaLayerNorm(_HIDDEN, eps=_EPS).to(device="cuda", dtype=torch.bfloat16)
g = torch.Generator(device="cuda").manual_seed(31)
x = torch.randn(bs, 512, _HIDDEN, generator=g, device="cuda", dtype=torch.bfloat16)
scale = torch.randn(1, _HIDDEN, generator=g, device="cuda", dtype=torch.bfloat16)
shift = torch.randn(1, _HIDDEN, generator=g, device="cuda", dtype=torch.bfloat16)
return m, x, scale, shift
def _get_kernel():
kernel = getattr(adaln_mod, "_adaln_scale_shift_layernorm_kernel", None)
if kernel is None:
pytest.skip("Triton AdaLayerNorm kernel unavailable")
return kernel
def test_shared_launch_failure_fallback_and_cache(monkeypatch):
kernel = _get_kernel()
m, x, scale, shift = _make(1)
native = m.forward_native(x, scale, shift)
calls = {"n": 0}
def raising_run(self, *args, **kwargs):
calls["n"] += 1
raise RuntimeError("simulated synchronous launch failure")
monkeypatch.setattr(type(kernel), "run", raising_run)
# Phase 1: the launch raises synchronously; the caller must receive the
# correct native result and the failed key must be recorded.
out = m.forward_cuda(x, scale, shift)
torch.testing.assert_close(out.float(), native.float())
expected_key = (x.device.index, _HIDDEN, x.dtype, False, False, False, False)
assert expected_key in _FAILED_ADALN_KEYS
assert calls["n"] == 1 # the fused launcher was attempted exactly once
# Phase 2: the same config must NOT retry the launcher (failed-key
# cache) and still return the correct native result.
calls["n"] = 0
out2 = m.forward_cuda(x, scale, shift)
assert calls["n"] == 0
torch.testing.assert_close(out2.float(), native.float())
def test_shared_failure_then_per_sample_variant_eligible(monkeypatch):
# The failed-key cache is per constexpr variant: after the shared
# (False, False) variant fails, the per-sample (True, True) variant must
# still attempt the launcher and succeed.
kernel = _get_kernel()
m, x, shared_scale, shared_shift = _make(2)
shared_native = m.forward_native(x, shared_scale, shared_shift)
shared_key = (x.device.index, _HIDDEN, x.dtype, False, False, False, False)
per_sample_key = (x.device.index, _HIDDEN, x.dtype, False, False, True, True)
calls = {"n": 0}
def raising_run(self, *args, **kwargs):
calls["n"] += 1
raise RuntimeError("simulated synchronous shared launch failure")
# Establish the shared failure in this test; the autouse fixture clears
# failures from other tests, so their cache entries cannot prove isolation.
with monkeypatch.context() as shared_patch:
shared_patch.setattr(type(kernel), "run", raising_run)
shared_out = m.forward_cuda(x, shared_scale, shared_shift)
torch.testing.assert_close(shared_out.float(), shared_native.float())
assert calls["n"] == 1
assert shared_key in _FAILED_ADALN_KEYS
assert per_sample_key not in _FAILED_ADALN_KEYS
m.forward_cuda(x, shared_scale, shared_shift)
assert calls["n"] == 1, "the cached shared failure must not retry"
g = torch.Generator(device="cuda").manual_seed(37)
scale = torch.randn(2, 1, _HIDDEN, generator=g, device="cuda", dtype=torch.bfloat16)
shift = torch.randn(2, 1, _HIDDEN, generator=g, device="cuda", dtype=torch.bfloat16)
calls["n"] = 0
def counting_run(self, *args, **kwargs):
calls["n"] += 1
return real_run(self, *args, **kwargs)
real_run = type(kernel).run
monkeypatch.setattr(type(kernel), "run", counting_run)
out = adaln_mod._adaln_fused_forward(m, x, scale, shift)
assert out is not None, "the per-sample variant must remain eligible after a shared failure"
native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out.float(), native.float(), atol=2e-2, rtol=2e-2)
assert calls["n"] == 1
assert shared_key in _FAILED_ADALN_KEYS
assert per_sample_key not in _FAILED_ADALN_KEYS
@@ -0,0 +1,462 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""P0 correctness matrix for the fused AdaLayerNorm CUDA path.
Covers: fp32/bf16/fp16, B>1 (shared and per-sample modulation), C in
{1536, 3072, 4096}, varying L, elementwise_affine False/True with
NON-DEFAULT weight/bias, eps variants, legal non-contiguous inputs,
broadcast scale/shift shapes ((C,), (1, C), (1, 1, C), (B, 1, C)),
fallback completeness (fp64, oversized hidden size, cross-device
parameters), large-offset/small-variance stability, and the frozen
B > 1 (B, C) native broadcast-error contract.
"""
import pytest
import torch
from vllm_omni.diffusion.layers.adalayernorm import (
AdaLayerNorm,
_adaln_fused_forward,
)
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cuda]
TOL_STRICT = {"bf16": (2e-2, 2e-2), "fp16": (1e-2, 1e-2), "fp32": (1e-3, 1e-3)}
TOL_LOOSE = {"bf16": (5e-2, 2e-2), "fp16": (2e-2, 1e-2), "fp32": (5e-3, 5e-3)}
DTYPES = [torch.bfloat16, torch.float16, torch.float32]
def tol_key(dtype):
if dtype == torch.bfloat16:
return "bf16"
if dtype == torch.float16:
return "fp16"
return "fp32"
def fp32_reference(x, scale, shift, eps, weight=None, bias=None):
xf = x.float()
w = weight.float() if weight is not None else None
b = bias.float() if bias is not None else None
xn = torch.nn.functional.layer_norm(xf, (x.shape[-1],), w, b, eps)
return xn * (1 + scale.float()[:, None]) + shift.float()[:, None]
def make_module(hidden, elementwise_affine, eps, device, dtype, nondefault_affine=False):
m = AdaLayerNorm(hidden, elementwise_affine=elementwise_affine, eps=eps)
if elementwise_affine:
m = m.to(dtype)
if nondefault_affine:
with torch.no_grad():
g = torch.Generator(device="cpu").manual_seed(42)
m.layernorm.weight.copy_(0.5 + torch.rand(hidden, generator=g))
m.layernorm.bias.copy_(0.2 * torch.randn(hidden, generator=g))
return m.to(device)
def make_inputs(bs, seq, hidden, dtype, device, seed=0, mod_shape=None):
g = torch.Generator(device=device).manual_seed(seed)
x = torch.randn(bs, seq, hidden, generator=g, device=device, dtype=dtype)
ms = mod_shape or (1, hidden)
scale = torch.randn(ms, generator=g, device=device, dtype=dtype)
shift = torch.randn(ms, generator=g, device=device, dtype=dtype)
return x, scale, shift
def assert_close(a, b, dtype, loose=False):
atol, rtol = (TOL_LOOSE if loose else TOL_STRICT)[tol_key(dtype)]
torch.testing.assert_close(a.float(), b.float(), atol=atol, rtol=rtol)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("chunks", [2, 6])
@pytest.mark.parametrize("bs,frames,spatial", [(1, 21, 880), (2, 3, 5), (2, 1, 5), (1, 3, 5)])
def test_matrix_framewise_modulation(dtype, chunks, bs, frames, spatial):
hidden = 2240
m = make_module(hidden, False, 1e-6, "cuda", dtype)
g = torch.Generator(device="cuda").manual_seed(61)
x = torch.randn(bs, frames, spatial, hidden, generator=g, device="cuda", dtype=dtype)
modulation = torch.randn(bs, frames, chunks, hidden, generator=g, device="cuda", dtype=dtype)
scale, shift = modulation.chunk(chunks, dim=2)[:2]
# Sana's block/final modulation uses chunk views spanning frames.
assert scale.shape == (bs, frames, 1, hidden)
assert scale.stride(1) == chunks * hidden
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "framewise chunk views must take the fused path"
assert fused.shape == x.shape and fused.dtype == dtype
assert_close(fused, m.forward_native(x, scale, shift), dtype)
ref = torch.nn.functional.layer_norm(x.float(), (hidden,), eps=m.eps)
ref = ref * (1 + scale.float()) + shift.float()
assert_close(fused, ref, dtype, loose=True)
@pytest.mark.parametrize("shared_scale,shared_shift", [(False, True), (True, False), (True, True)])
@torch.no_grad()
def test_matrix_framewise_shared_modulation(shared_scale, shared_shift):
dtype = torch.bfloat16
hidden = 2240
m = make_module(hidden, True, 1e-6, "cuda", dtype, nondefault_affine=True)
x = torch.randn(2, 3, 5, hidden, device="cuda", dtype=dtype)
scale = torch.randn((hidden,) if shared_scale else (2, 3, 1, hidden), device="cuda", dtype=dtype)
shift = torch.randn((1, 1, 1, hidden) if shared_shift else (2, 3, 1, hidden), device="cuda", dtype=dtype)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None
assert_close(fused, m.forward_native(x, scale, shift), dtype)
def test_matrix_framewise_irregular_group_stride_falls_back():
dtype = torch.bfloat16
hidden = 2240
m = make_module(hidden, False, 1e-6, "cuda", dtype)
x = torch.randn(2, 3, 5, hidden, device="cuda", dtype=dtype)
modulation = torch.randn(3, 2, 2, hidden, device="cuda", dtype=dtype).transpose(0, 1)
scale, shift = modulation.chunk(2, dim=2)
assert scale.stride(0) != x.shape[1] * scale.stride(1)
assert _adaln_fused_forward(m, x, scale, shift) is None
assert_close(m.forward_cuda(x, scale, shift), m.forward_native(x, scale, shift), dtype)
@pytest.mark.parametrize("dtype", DTYPES)
def test_matrix_sana_patch_embed_reaches_framewise_fused_path(dtype):
from vllm_omni.diffusion.models.sana_wm.sana_wm_transformer import SanaWmPatchEmbedMS3D
hidden = 2240
patch = SanaWmPatchEmbedMS3D((1, 1, 1), 8, hidden).to(device="cuda", dtype=dtype)
latents = torch.randn(2, 8, 3, 2, 3, device="cuda", dtype=dtype)
with torch.no_grad():
projected = patch.proj(latents)
tokens, (frames, height, width) = patch.project_with_shape(latents)
torch.testing.assert_close(tokens, projected.flatten(2).transpose(1, 2), atol=0, rtol=0)
assert tokens.is_contiguous()
x = tokens.reshape(2, frames, height * width, hidden)
modulation = torch.randn(2, frames, 6, hidden, device="cuda", dtype=dtype)
scale, shift = modulation.chunk(6, dim=2)[:2]
m = make_module(hidden, False, 1e-6, "cuda", dtype)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "real Sana patch features must reach the fused path"
assert_close(fused, m.forward_native(x, scale, shift), dtype)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("affine", [False, True])
@pytest.mark.parametrize(
"bs,seq,hidden", [(1, 4096, 3072), (2, 1024, 1536), (3, 128, 4096), (1, 1, 3072), (1, 8192, 1536)]
)
@torch.no_grad()
def test_matrix_main(dtype, affine, bs, seq, hidden):
device = "cuda"
m = make_module(hidden, affine, 1e-6, device, dtype)
x, scale, shift = make_inputs(bs, seq, hidden, dtype, device)
out_cuda = _adaln_fused_forward(m, x, scale, shift)
assert out_cuda is not None, "supported matrix inputs must take the fused path"
out_native = m.forward_native(x, scale, shift)
assert out_cuda.shape == x.shape and out_cuda.dtype == dtype and out_cuda.device == x.device
assert_close(out_cuda, out_native, dtype, loose=False)
w = m.layernorm.weight if affine else None
b = m.layernorm.bias if affine else None
assert_close(out_cuda, fp32_reference(x, scale, shift, 1e-6, w, b), dtype, loose=True)
@pytest.mark.parametrize("dtype", DTYPES)
@torch.no_grad()
def test_matrix_weight_bias_nondefault(dtype):
# weight/bias semantics with non-identity initialization:
# out = (w * ln(x) + b) * (1 + scale) + shift
device = "cuda"
hidden = 3072
m = make_module(hidden, True, 1e-6, device, dtype, nondefault_affine=True)
x, scale, shift = make_inputs(1, 1024, hidden, dtype, device, seed=13)
out_cuda = _adaln_fused_forward(m, x, scale, shift)
assert out_cuda is not None, "non-default affine parameters must take the fused path"
out_native = m.forward_native(x, scale, shift)
assert_close(out_cuda, out_native, dtype)
ref = fp32_reference(x, scale, shift, 1e-6, m.layernorm.weight, m.layernorm.bias)
assert_close(out_cuda, ref, dtype, loose=True)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
@pytest.mark.parametrize("eps", [1e-5, 1e-3, 1e-2])
def test_matrix_eps_variants(dtype, eps):
device = "cuda"
hidden = 3072
m = make_module(hidden, False, eps, device, dtype)
x, scale, shift = make_inputs(1, 512, hidden, dtype, device, seed=3)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "supported eps variants must take the fused path"
assert_close(fused, m.forward_native(x, scale, shift), dtype)
assert_close(
fused,
fp32_reference(x, scale, shift, eps),
dtype,
loose=True,
)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
@pytest.mark.parametrize("mod_shape", ["C", "1x1xC", "1xC"])
def test_matrix_broadcast_scale_shift(dtype, mod_shape):
# (C,), (1, 1, C) and (1, C) are all legal per-channel broadcasts against x (B, L, C)
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, _, _ = make_inputs(2, 512, hidden, dtype, device, seed=5)
g = torch.Generator(device=device).manual_seed(6)
if mod_shape == "C":
scale = torch.randn(hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(hidden, generator=g, device=device, dtype=dtype)
elif mod_shape == "1x1xC":
scale = torch.randn(1, 1, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(1, 1, hidden, generator=g, device=device, dtype=dtype)
else:
scale = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(1, hidden, generator=g, device=device, dtype=dtype)
out_cuda = _adaln_fused_forward(m, x, scale, shift)
assert out_cuda is not None, f"shared modulation layout {mod_shape} must take the fused path"
out_native = m.forward_native(x, scale, shift)
assert_close(out_cuda, out_native, dtype)
s = scale.float().reshape(1, 1, hidden) if scale.ndim == 1 else scale.float()
sh = shift.float().reshape(1, 1, hidden) if shift.ndim == 1 else shift.float()
ref = fp32_reference(x, s.reshape(-1, hidden)[0:1], sh.reshape(-1, hidden)[0:1], 1e-6)
assert_close(out_cuda, ref, dtype, loose=True)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_per_sample_modulation(dtype):
# Qwen-Image's _modulate produces (B, 1, C): one modulation row per sample.
# B > 1 must take the fused path (not fall back to native) and match native.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, _, _ = make_inputs(4, 512, hidden, dtype, device, seed=7, mod_shape=(4, hidden))
g = torch.Generator(device=device).manual_seed(8)
scale = torch.randn(4, 1, hidden, generator=g, device=device, dtype=dtype)
shift = torch.randn(4, 1, hidden, generator=g, device=device, dtype=dtype)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "(B, 1, C) per-sample modulation must take the fused path"
out_cuda = fused
out_native = m.forward_native(x, scale, shift)
assert_close(out_cuda, out_native, dtype)
# fp32_reference's [:, None] expects a 2D (B, C) modulation row; the
# (B, 1, C) tensor reshapes to it losslessly.
ref = fp32_reference(x, scale.reshape(x.shape[0], hidden), shift.reshape(x.shape[0], hidden), 1e-6)
assert_close(out_cuda, ref, dtype, loose=True)
# Per-sample: different rows must receive different modulation.
row_diff = (out_cuda[0] - out_cuda[1]).abs().max().item()
assert row_diff > 0
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_chunk_view_modulation(dtype):
# Wan2.2-style producer: the modulation projection is (B, 6, C) and the
# consumer chunks it along dim=1. The resulting (B, 1, C) views are
# NON-CONTIGUOUS (row stride 6*C) - the fused path must consume them
# directly via the explicit row stride, without a contiguous copy.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, _, _ = make_inputs(4, 512, hidden, dtype, device, seed=27)
g = torch.Generator(device=device).manual_seed(28)
source = torch.randn(4, 6, hidden, generator=g, device=device, dtype=dtype) * 0.1
chunks = source.chunk(6, dim=1)
scale, shift = chunks[1], chunks[2]
assert scale.shape == (4, 1, hidden) and shift.shape == (4, 1, hidden)
assert not scale.is_contiguous()
assert scale.stride(-1) == 1
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, "chunk-view modulation must take the fused path"
out_native = m.forward_native(x, scale, shift)
assert_close(fused, out_native, dtype)
ref = fp32_reference(x, scale.reshape(4, hidden), shift.reshape(4, hidden), 1e-6)
assert_close(fused, ref, dtype, loose=True)
def test_matrix_3d_modulation_fallback():
# Wan2.2 TI2V-style per-token modulation: scale/shift (B, L, C) full 3D.
# This is intentionally outside the fused kernel contract - the fast path
# must be rejected (None) and forward_cuda must match native.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, torch.bfloat16)
x, scale, shift = make_inputs(2, 128, hidden, torch.bfloat16, device, seed=29, mod_shape=(2, 128, hidden))
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is None, "(B, L, C) per-token modulation must be rejected"
out = m.forward_cuda(x, scale, shift)
native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out.float(), native.float(), atol=2e-2, rtol=2e-2)
@pytest.mark.parametrize("dtype", [torch.bfloat16])
def test_matrix_production_widths(dtype):
# Real consumer widths: Sana-WM 2240 (BLOCK_C 4096, 45.3% mask) and
# Wan2.2 A14B 5120 (BLOCK_C 8192, the max supported block, 16-warp
# compiled variant). BF16 + elementwise_affine=False.
device = "cuda"
for hidden in (2240, 5120):
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(1, 512, hidden, dtype, device, seed=31)
fused = _adaln_fused_forward(m, x, scale, shift)
assert fused is not None, f"hidden={hidden} must take the fused path"
out_native = m.forward_native(x, scale, shift)
assert_close(fused, out_native, dtype)
assert_close(fused, fp32_reference(x, scale, shift, 1e-6), dtype, loose=True)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_noncontiguous_fallback(dtype):
# Non-contiguous inputs must fall back to native and stay correct
# (the fused kernel only serves the contiguous fast path).
device = "cuda"
hidden = 1536
m = make_module(hidden, False, 1e-6, device, dtype)
x_big, scale, shift = make_inputs(1, 1024, hidden, dtype, device, seed=7)
x = x_big[:, ::2, :]
assert not x.is_contiguous()
out_cuda = m.forward_cuda(x, scale, shift)
out_native = m.forward_native(x, scale, shift)
assert_close(out_cuda, out_native, dtype)
assert_close(out_cuda, fp32_reference(x.contiguous(), scale, shift, 1e-6), dtype, loose=True)
def test_matrix_fallback_fp64():
# fp64 is not in the kernel's supported dtype list -> must fall back to
# native and stay correct (fallback completeness).
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, torch.float64)
x, scale, shift = make_inputs(1, 256, hidden, torch.float64, device, seed=9)
out_cuda = m.forward_cuda(x, scale, shift)
out_native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out_cuda, out_native)
def test_matrix_fallback_oversized_hidden():
# hidden sizes whose next_power_of_2 exceeds the kernel's supported block
# bound must fall back to native and stay correct.
device = "cuda"
hidden = 10000 # next_power_of_2 = 16384 > MAX_BLOCK_C
m = make_module(hidden, False, 1e-6, device, torch.bfloat16)
x, scale, shift = make_inputs(1, 128, hidden, torch.bfloat16, device, seed=10)
out_cuda = m.forward_cuda(x, scale, shift)
out_native = m.forward_native(x, scale, shift)
torch.testing.assert_close(out_cuda, out_native)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_zero_scale_shift_identity(dtype):
# With scale = shift = 0 the output must equal a pure LayerNorm.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, _, _ = make_inputs(1, 256, hidden, dtype, device, seed=19)
scale = torch.zeros(1, hidden, device=device, dtype=dtype)
shift = torch.zeros(1, hidden, device=device, dtype=dtype)
out_cuda = _adaln_fused_forward(m, x, scale, shift)
assert out_cuda is not None, "zero modulation must take the fused path"
assert_close(out_cuda, m.forward_native(x, scale, shift), dtype)
assert_close(out_cuda, m.layernorm(x), dtype)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_determinism(dtype):
# Two calls on the same input must agree bitwise (a hard constraint for
# any cached/direct-launch scheme).
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(1, 4096, hidden, dtype, device, seed=11)
out1 = _adaln_fused_forward(m, x, scale, shift)
out2 = _adaln_fused_forward(m, x, scale, shift)
assert out1 is not None and out2 is not None, "determinism must exercise the fused path"
assert torch.equal(out1, out2)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_multi_batch_shared_modulation(dtype):
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(4, 512, hidden, dtype, device, seed=15)
out = _adaln_fused_forward(m, x, scale, shift)
assert out is not None, "multi-batch shared modulation must take the fused path"
assert_close(out, m.forward_native(x, scale, shift), dtype)
for b in range(4):
single = _adaln_fused_forward(m, x[b : b + 1], scale, shift)
assert single is not None
assert_close(out[b : b + 1], single, dtype)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_bc_modulation_preserves_native_error(dtype):
# Frozen native contract: (B, C) modulation with B > 1 raises
# RuntimeError (torch broadcasts B against L). The fused path preserves
# this behavior - it neither supports nor silently changes it.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(4, 64, hidden, dtype, device, seed=17, mod_shape=(4, hidden))
with pytest.raises(RuntimeError):
m.forward_native(x, scale, shift)
with pytest.raises(RuntimeError):
m.forward_cuda(x, scale, shift)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
def test_matrix_cross_device_fallback(dtype):
# Raw pointers go to one Triton kernel, which does no cross-device
# checking: the fast-path guard must reject cross-device parameters so
# they fall back to native, which raises torch's own cross-device error
# instead of crashing in the kernel.
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(1, 512, hidden, dtype, device, seed=21)
cpu_scale = scale.to("cpu")
with pytest.raises(RuntimeError):
m.forward_cuda(x, cpu_scale, shift)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16, torch.float32])
def test_matrix_large_offset_small_variance(dtype):
"""A large constant offset with a small variance must stay stable.
Mirrors the fused_adaptive_group_norm_silu case: x ~ 10000 +/- 0.1 has
mean ~1e4 and variance ~1e-2, so a plain fp32 sum of large-offset values
rounds away the low-order bits that the variance is built from. The fused
kernel centers the row on its first element before the tree sum
(shift-invariant two-pass), keeping mean/variance accurate.
For fp32 the meaningful reference is an fp64 computation, NOT the native
eager chain: at mean/std = 1e5 the native chain itself carries a
~0.07 absolute deviation from the fp64 truth (its own fp32 dynamic-range
floor), while the fused kernel stays within ~2e-6. For fp16/bf16 inputs
the +/-0.1 perturbation quantizes away at magnitude 1e4, so the
meaningful assertion is that outputs stay finite and match native.
"""
device = "cuda"
hidden = 3072
m = make_module(hidden, False, 1e-6, device, dtype)
x, scale, shift = make_inputs(1, 4096, hidden, dtype, device, seed=23)
# Apply the large constant offset AFTER generation: x ~ 10000 +/- 0.1 is
# the cancellation case that motivates the shift-invariant two-pass.
x = x * 0.1 + 10000
out_cuda = _adaln_fused_forward(m, x, scale, shift)
assert out_cuda is not None, "large-offset stability must exercise the fused path"
out_native = m.forward_native(x, scale, shift)
assert torch.isfinite(out_cuda).all()
# fp64 reference computed from the SAME fp32 input the implementations see
# (the kernel cannot recover bits that fp32 quantization never stored).
x64 = x.double()
mean64 = x64.mean(dim=-1, keepdim=True)
var64 = x64.var(dim=-1, unbiased=False, keepdim=True)
ref64 = (x64 - mean64) * torch.rsqrt(var64 + 1e-6) * (1 + scale.double()[:, None, :]) + shift.double()[:, None, :]
if dtype is torch.float32:
# fp32 fused output vs the fp64 truth of the same input: within the
# fp32 representation floor (~2e-6 measured). The native eager chain
# deviates ~0.07 on the same input (its own dynamic-range floor) -
# documented, not asserted.
torch.testing.assert_close(out_cuda.double(), ref64, atol=1e-4, rtol=1e-4)
else:
# bf16/fp16 quantize the perturbation away: no NaN, matches native.
assert_close(out_cuda, out_native, dtype, loose=True)
+295
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from collections.abc import Callable
from importlib.util import find_spec
from typing import TYPE_CHECKING
@@ -5,6 +9,8 @@ import torch
import torch.nn as nn
from vllm.logger import init_logger
from vllm.model_executor.layers.linear import ReplicatedLinear
from vllm.triton_utils import HAS_TRITON as _HAS_TRITON
from vllm.triton_utils import tl, triton
from vllm_omni.diffusion.layers.custom_op import CustomOp
from vllm_omni.diffusion.layers.norm import LayerNorm
@@ -16,6 +22,282 @@ logger = init_logger(__name__)
_HAS_MINDIESD = find_spec("mindiesd") is not None
# Fast-path bound: one Triton program covers the whole normalized dimension, so
# BLOCK_C = next_power_of_2(C) grows with the hidden size. The diffusion models
# consuming AdaLayerNorm use hidden sizes 2240 (Sana-WM), 3072/3584 (Wan2.2,
# Qwen-Image) and 5120 (Wan2.2 A14B), i.e. BLOCK_C up to 8192, which compiles
# and runs efficiently on H200 (measured; C=5120 -> 30-111 us depending on L).
# Beyond that, masked-lane waste and register pressure grow without a measured
# benefit, so the fast path falls back to forward_native.
_MAX_BLOCK_C = 8192
_ADALN_CONFIGS: dict = {}
_ADALN_DTYPES = (torch.bfloat16, torch.float16, torch.float32)
# Runtime configs whose Triton compile/launch failed synchronously; they fall
# back to forward_native and are not retried (mirrors the LTX2
# residual-AdaLN failed-key cache in residual_adaln.py).
_FAILED_ADALN_KEYS: set = set()
_adaln_fused_forward: Callable[["AdaLayerNorm", torch.Tensor, torch.Tensor, torch.Tensor], torch.Tensor | None] | None
if _HAS_TRITON:
@triton.jit
def _adaln_scale_shift_layernorm_kernel(
x_ptr,
scale_ptr,
shift_ptr,
out_ptr,
weight_ptr,
bias_ptr,
eps,
channels,
seq_len,
scale_row_stride,
shift_row_stride,
has_weight: tl.constexpr,
has_bias: tl.constexpr,
is_half: tl.constexpr,
per_sample_scale: tl.constexpr,
per_sample_shift: tl.constexpr,
block_c: tl.constexpr,
):
"""One program per LayerNorm row: out = ln(x) * (1 + scale) + shift.
Rows enumerate contiguous (B, L, C) or (B, F, S, C) input.
sample_idx = row // seq_len selects a sample, or a flattened
batch/frame group with seq_len = S. Per-group modulation is
(B, 1, C) or (B, F, 1, C); explicit row strides support projection
chunk views. Shared modulation addresses one channel vector.
(B, C) at B > 1 is a native-path broadcast error and never reaches
this kernel.
Mean/variance use a shift-invariant two-pass: the row is centered on
its first element (x0) before the fp32 tree sum, so for inputs with a
large constant offset and small variance the summation and the
x - mean cancellation stay accurate where a plain fp32 sum of
large-offset values loses the low-order bits (the reason
fused_adaptive_group_norm_silu uses Welford/Chan). Reductions
accumulate in fp32 (matches the golden path, which computes
F.layer_norm on x.float()). For half-precision outputs the golden
chain rounds after every torch op (LN result, 1+scale, product, sum),
so the kernel replicates exactly those roundings - keeping the output
bit-faithful to the frozen semantics. fp32 outputs have no
intermediate rounding in the golden path and use a plain fp32 chain.
block_c covers the whole normalized dim (masked); it is never
autotuned. Non-contiguous last-dim inputs never reach this kernel
(routed to forward_native upstream).
"""
row = tl.program_id(0).to(tl.int64)
sample_idx = row // seq_len
cols = tl.arange(0, block_c)
mask = cols < channels
x = tl.load(x_ptr + row * channels + cols, mask=mask, other=0.0).to(tl.float32)
# Center on the first element before summing: for x ~ offset + noise
# this keeps every value entering the tree sum at noise magnitude.
x0 = tl.load(x_ptr + row * channels).to(tl.float32)
xs = tl.where(mask, x - x0, 0.0)
mean_xs = tl.sum(xs, axis=0) / channels
# xm = xs - mean_xs is exactly x - (x0 + mean_xs): the row mean enters
# only through this centering, and the shift keeps it accurate.
xm = tl.where(mask, xs - mean_xs, 0.0)
var = tl.sum(xm * xm, axis=0) / channels
xn = xm * tl.rsqrt(var + eps)
if has_weight:
xn = xn * tl.load(weight_ptr + cols, mask=mask, other=0.0).to(tl.float32)
if has_bias:
xn = xn + tl.load(bias_ptr + cols, mask=mask, other=0.0).to(tl.float32)
if per_sample_scale:
s = tl.load(scale_ptr + sample_idx * scale_row_stride + cols, mask=mask, other=0.0).to(tl.float32)
else:
s = tl.load(scale_ptr + cols, mask=mask, other=0.0).to(tl.float32)
if per_sample_shift:
sh = tl.load(shift_ptr + sample_idx * shift_row_stride + cols, mask=mask, other=0.0).to(tl.float32)
else:
sh = tl.load(shift_ptr + cols, mask=mask, other=0.0).to(tl.float32)
out_dtype = out_ptr.dtype.element_ty
if is_half:
# Golden bf16/fp16 chain: round after each op, exactly as the
# sequence of torch elementwise kernels does (fp32 internal math).
ln16 = xn.to(out_dtype)
t1 = (1.0 + s).to(out_dtype)
t2 = (ln16.to(tl.float32) * t1.to(tl.float32)).to(out_dtype)
y = (t2.to(tl.float32) + sh).to(out_dtype)
else:
y = xn * (1.0 + s) + sh
# Preserve the input shape and store output rows contiguously.
tl.store(out_ptr + row * channels + cols, y.to(out_dtype), mask=mask)
def _adaln_modulation_mode(t: torch.Tensor, x: torch.Tensor):
"""Classify scale/shift against 3D/4D x for the fused kernel.
"shared" every leading dim is 1: (C,), (1, C), (1, 1, C)
"per_sample" (B, 1, C) or (B, F, 1, C): one row per sample/frame
None anything else - native torch broadcasting on the
forward_native path handles it (or raises, for the
frozen B > 1 (B, C) RuntimeError)
"""
shape = t.shape
if t.ndim < 1 or t.ndim > x.ndim or shape[-1] != x.shape[-1]:
return None
if all(d == 1 for d in shape[:-1]):
return "shared"
if x.ndim == 3 and t.ndim == 3 and shape == (x.shape[0], 1, x.shape[-1]):
return "per_sample"
if x.ndim == 4 and t.ndim == 4 and shape == (x.shape[0], x.shape[1], 1, x.shape[-1]):
# Batch/frame groups must be addressable with one row stride, including chunk views.
if shape[0] > 1 and shape[1] > 1 and t.stride(0) != shape[1] * t.stride(1):
return None
return "per_sample"
return None
def _adaln_param_matches(x: torch.Tensor, p: torch.Tensor) -> bool:
return (
p.dtype is x.dtype
and p.is_cuda
and p.device == x.device
and p.ndim == 1
and p.shape[0] == x.shape[-1]
and p.is_contiguous()
)
def _adaln_inputs_supported(module: "AdaLayerNorm", x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor):
"""Strict fast-path eligibility (mirrors the LTX2 residual-AdaLN
guard pattern): every tensor the kernel dereferences must be a dense
CUDA tensor on x's device with a supported layout and dtype; anything
else falls back to forward_native, which reproduces torch semantics
exactly - including the frozen B > 1 (B, C) RuntimeError. Raw pointers
go to one kernel, which does no cross-device checking. Runs native
under torch.compile so regional compilation is not disrupted."""
if torch.compiler.is_compiling():
return None
if (
not _HAS_TRITON
or x.device.type != "cuda"
or x.ndim not in (3, 4)
or x.numel() == 0
or not x.is_contiguous()
):
return None
if x.dtype not in _ADALN_DTYPES:
return None
if scale.dtype is not x.dtype or shift.dtype is not x.dtype:
return None
if not (scale.is_cuda and shift.is_cuda):
return None
if scale.device != x.device or shift.device != x.device:
return None
mode_s = _adaln_modulation_mode(scale, x)
mode_h = _adaln_modulation_mode(shift, x)
if mode_s is None or mode_h is None:
return None
# The kernel addresses the modulation rows densely per channel and
# strides per sample: the last dim must be unit-stride; other dims
# are handled by the explicit row stride (chunk views supported).
# 0-dim (scalar) modulation is classified None above and falls back.
if scale.stride(-1) != 1 or shift.stride(-1) != 1:
return None
if x.data_ptr() % 16 or scale.data_ptr() % 16 or shift.data_ptr() % 16:
return None
channels = x.shape[-1]
ln = module.layernorm
if tuple(ln.normalized_shape) != (channels,):
return None
if torch.is_grad_enabled() and any(
t is not None and t.requires_grad for t in (x, scale, shift, ln.weight, ln.bias)
):
return None
block_c = triton.next_power_of_2(channels)
if block_c > _MAX_BLOCK_C:
return None
for p in (ln.weight, ln.bias):
if p is not None and not _adaln_param_matches(x, p):
return None
return channels, block_c, mode_s == "per_sample", mode_h == "per_sample"
def _adaln_fused_forward_impl(module: "AdaLayerNorm", x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor):
"""Fused fast path. Returns the output tensor, or None when the inputs
are unsupported or the optimized path failed synchronously (the caller
then uses forward_native). The runtime fallback is best-effort: it
catches synchronous Triton compile/launch errors, not asynchronous
CUDA execution faults."""
supported = _adaln_inputs_supported(module, x, scale, shift)
if supported is None:
return None
channels, block_c, per_sample_scale, per_sample_shift = supported
seq_len = x.shape[-2]
rows = x.numel() // channels
out = torch.empty(x.shape, dtype=x.dtype, device=x.device)
ln = module.layernorm
weight = ln.weight
bias = ln.bias
has_weight = weight is not None
has_bias = bias is not None
is_half = x.dtype is not torch.float32
cfg = _ADALN_CONFIGS.get(block_c)
if cfg is None:
num_warps = 4 if block_c <= 1024 else (8 if block_c <= 4096 else 16)
cfg = (block_c, num_warps)
_ADALN_CONFIGS[block_c] = cfg
# Step across frames when F > 1, otherwise across batches.
scale_row_stride = scale.stride(1) if scale.ndim == 4 and scale.shape[1] > 1 else scale.stride(0)
shift_row_stride = shift.stride(1) if shift.ndim == 4 and shift.shape[1] > 1 else shift.stride(0)
# Dummy pointer args for disabled branches: never dereferenced because
# the loads are constexpr-pruned when has_weight/has_bias is False.
args = (
x,
scale,
shift,
out,
weight if has_weight else scale,
bias if has_bias else scale,
module.eps,
channels,
seq_len,
scale_row_stride,
shift_row_stride,
has_weight,
has_bias,
is_half,
per_sample_scale,
per_sample_shift,
block_c,
)
# The key carries every field that selects the compiled binary
# (dtype covers is_half; per-sample flags and affine presence select
# constexpr variants; eps is a runtime scalar). A failure in one
# variant must not disable the others.
runtime_key = (
x.device.index,
channels,
x.dtype,
has_weight,
has_bias,
per_sample_scale,
per_sample_shift,
)
if runtime_key in _FAILED_ADALN_KEYS:
return None
try:
_adaln_scale_shift_layernorm_kernel[(rows,)](*args, num_warps=cfg[1])
except Exception as exc: # noqa: BLE001 - fail closed after optimized-path failure
_FAILED_ADALN_KEYS.add(runtime_key)
logger.warning(
"Disabling the AdaLayerNorm fused fast path on %s after failure: %s",
x.device,
exc,
)
return None
return out
_adaln_fused_forward = _adaln_fused_forward_impl
else:
# No Triton: the fused fast path is unavailable; forward_cuda falls back
# to forward_native below.
_adaln_fused_forward = None
class AdaLayerNorm(CustomOp):
"""
@@ -36,6 +318,11 @@ class AdaLayerNorm(CustomOp):
scale: torch.Tensor,
shift: torch.Tensor,
) -> torch.Tensor:
if _adaln_fused_forward is None:
return self.forward_native(x, scale, shift)
out = _adaln_fused_forward(self, x, scale, shift)
if out is not None:
return out
return self.forward_native(x, scale, shift)
def forward_hip(
@@ -46,6 +333,14 @@ class AdaLayerNorm(CustomOp):
) -> torch.Tensor:
return self.forward_native(x, scale, shift)
def forward_musa(
self,
x: torch.Tensor,
scale: torch.Tensor,
shift: torch.Tensor,
) -> torch.Tensor:
return self.forward_native(x, scale, shift)
def forward_npu(
self,
x: torch.Tensor,
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""SANA-WM Stage-1 transformer.
Native vLLM-Omni port of the NVlabs SANA-WM DiT. Modules are built eagerly at
@@ -563,7 +563,8 @@ class SanaWmPatchEmbedMS3D(nn.Module):
def project_with_shape(self, latents: torch.Tensor) -> tuple[torch.Tensor, tuple[int, int, int]]:
hidden_states = self.norm(self.proj(latents))
_, _, frames, height, width = hidden_states.shape
return hidden_states.flatten(2).transpose(1, 2), (frames, height, width)
# Use a contiguous token/channel layout for framewise AdaLayerNorm.
return hidden_states.flatten(2).transpose(1, 2).contiguous(), (frames, height, width)
def forward(self, latents: torch.Tensor) -> torch.Tensor:
return self.project_with_shape(latents)[0]