[Diffusion] Fuse LingBot per-token gated residual and RMSNorm modulate (#37910)

This commit is contained in:
Xiaoyu Zhang
2026-09-04 14:35:13 +08:00
committed by GitHub
parent cb32dbc9e0
commit 54c2c99feb
9 changed files with 448 additions and 15 deletions
@@ -16,6 +16,7 @@ from sglang.kernels.jit.utils import get_ci_test_range
from sglang.kernels.ops.diffusion import (
can_use_modulate_scale_shift_cuda,
can_use_residual_gate_add_cuda,
can_use_rmsnorm_scale_shift_per_token,
fuse_layernorm_scale_shift_gate_select01_kernel,
fuse_residual_layernorm_scale_shift_gate_select01_kernel,
fuse_scale_shift_kernel,
@@ -25,6 +26,7 @@ from sglang.kernels.ops.diffusion import (
norm_infer,
residual_gate_add,
residual_gate_add_cuda,
rmsnorm_scale_shift_per_token,
timestep_embedding,
try_fused_scaled_residual_add_exact,
)
@@ -164,6 +166,37 @@ def test_residual_gate_add_matches_torch(residual_shape, gate_shape):
assert torch.equal(residual_gate_add(residual, update, gate), ref)
# LingBot per-token gates are [B, S, 1]: one scalar per token, broadcast
# along the hidden dimension.
PER_TOKEN_GATE_CASES = [
((1, 2560, 512), (1, 2560, 1)),
((1, 17, 65), (1, 17, 1)),
((2, 33, 128), (2, 33, 1)),
]
@pytest.mark.parametrize("residual_shape,gate_shape", PER_TOKEN_GATE_CASES)
def test_residual_gate_add_per_token_matches_torch(residual_shape, gate_shape):
residual = torch.randn(residual_shape, device=DEVICE, dtype=torch.bfloat16)
update = torch.randn_like(residual)
gate = torch.randn(gate_shape, device=DEVICE, dtype=torch.bfloat16)
assert can_use_residual_gate_add_cuda(residual, update, gate)
ref = residual + update * gate
_assert_gate_add(residual_gate_add_cuda(residual, update, gate), ref)
assert torch.equal(residual_gate_add(residual, update, gate), ref)
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32])
def test_residual_gate_add_per_token_dtypes(dtype):
residual = torch.randn((1, 2560, 512), device=DEVICE, dtype=dtype)
update = torch.randn_like(residual)
gate = torch.randn((1, 2560, 1), device=DEVICE, dtype=dtype)
_assert_gate_add(
residual_gate_add_cuda(residual, update, gate), residual + update * gate
)
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32])
@pytest.mark.parametrize("gate_shape", [(1, 1, 64), (1, 9, 64)])
def test_residual_gate_add_dtypes(dtype, gate_shape):
@@ -524,3 +557,59 @@ def test_timestep_embedding_matches_diffusers(
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"]))
# ---------------------------------------------------------------------------
# fused RMSNorm + per-token adaLN scale/shift (quality-gated, LingBot)
# ---------------------------------------------------------------------------
def _eager_lingbot_norm_modulate(x, weight, scale, shift, eps):
xf = x.to(torch.float32)
var = xf.pow(2).mean(-1, keepdim=True)
xf = xf * torch.rsqrt(var + eps)
normed = (weight.to(torch.float32) * xf).to(x.dtype)
return (normed * (1.0 + scale.to(torch.float32)) + shift.to(torch.float32)).to(
x.dtype
)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
@pytest.mark.parametrize("shape", [(1, 4813, 2048), (1, 2560, 512), (2, 33, 128)])
def test_rmsnorm_scale_shift_per_token_matches_eager(shape, dtype):
B, S, H = shape
x = torch.randn(shape, device=DEVICE, dtype=dtype)
weight = torch.randn(H, device=DEVICE, dtype=torch.float32)
# scale/shift are non-contiguous chunk views of the [B, S, 6D] modulation,
# matching the LingBot adaLN layout the kernel is built for.
mod = torch.randn((B, S, 6 * H), device=DEVICE, dtype=torch.float32)
shift, scale = mod.chunk(6, dim=-1)[0], mod.chunk(6, dim=-1)[1]
eps = 1e-6
assert can_use_rmsnorm_scale_shift_per_token(x, weight, scale, shift)
ref = _eager_lingbot_norm_modulate(x, weight, scale, shift, eps)
out = rmsnorm_scale_shift_per_token(x, weight, scale, shift, eps)
assert out.dtype == x.dtype and out.shape == x.shape
# Not bit-exact (single fp32 pass); assert bf16/fp16 rounding tolerance.
torch.testing.assert_close(out, ref, atol=0.13, rtol=0.02)
def test_rmsnorm_scale_shift_per_token_guards():
B, S, H = 1, 64, 128
x = torch.randn((B, S, H), device=DEVICE, dtype=torch.bfloat16)
weight = torch.randn(H, device=DEVICE, dtype=torch.float32)
scale = torch.randn((B, S, H), device=DEVICE, dtype=torch.float32)
shift = torch.randn((B, S, H), device=DEVICE, dtype=torch.float32)
assert can_use_rmsnorm_scale_shift_per_token(x, weight, scale, shift)
assert not can_use_rmsnorm_scale_shift_per_token(
x.cpu(), weight, scale, shift
) # not on device
assert not can_use_rmsnorm_scale_shift_per_token(
x, weight, scale, shift[:, :, ::2]
) # strided rows (stride(2) != 1)
assert not can_use_rmsnorm_scale_shift_per_token(
x, weight, scale.float(), shift.double()
) # mismatched scale/shift dtype
assert not can_use_rmsnorm_scale_shift_per_token(
x, weight[:-1], scale, shift
) # weight size mismatch