[diffusion] refactor: remove stale kernels and dead code (#32651)

This commit is contained in:
Xiaoyu Zhang
2026-07-29 06:23:23 +08:00
committed by GitHub
parent 7f438a6031
commit 7778dd23ea
29 changed files with 301 additions and 763 deletions
@@ -1,6 +1,6 @@
"""Diffusion-model kernels (group-norm+silu, residual-gate-add, qk-norm+rope). """Registered diffusion-model kernels and their public wrappers.
These are JIT CUDA kernels; the wrappers forward to ``sglang.kernels.ops.diffusion``. Implementations use the backend recorded by each kernel specification.
""" """
from __future__ import annotations from __future__ import annotations
@@ -25,11 +25,11 @@ _CUDA = frozenset({CapabilityRequirement.CUDA})
register_kernel( register_kernel(
KernelSpec( KernelSpec(
op="diffusion.apply_group_norm_silu", op="diffusion.apply_group_norm_silu",
backend=KernelBackend.JIT, backend=KernelBackend.TRITON,
target="sglang.kernels.ops.diffusion.group_norm_silu:apply_group_norm_silu", target="sglang.kernels.ops.diffusion.group_norm_silu:apply_group_norm_silu",
capabilities=_CUDA, capabilities=_CUDA,
format_signature=FormatSignature(description="fused GroupNorm + SiLU"), format_signature=FormatSignature(description="fused GroupNorm + SiLU"),
description="Fused group-norm + SiLU (sglang.kernels.jit).", description="Fused group-norm + SiLU (Triton).",
) )
) )
register_kernel( register_kernel(
@@ -60,7 +60,7 @@ def apply_group_norm_silu(
x: torch.Tensor, norm: nn.Module, activation: nn.Module x: torch.Tensor, norm: nn.Module, activation: nn.Module
) -> torch.Tensor: ) -> torch.Tensor:
"""Fused GroupNorm + SiLU (falls back to eager when unsupported).""" """Fused GroupNorm + SiLU (falls back to eager when unsupported)."""
return get_kernel("diffusion.apply_group_norm_silu", KernelBackend.JIT)( return get_kernel("diffusion.apply_group_norm_silu", KernelBackend.TRITON)(
x, norm, activation x, norm, activation
) )
@@ -1,344 +0,0 @@
from typing import Optional, Tuple
import cuda.bindings.driver as cuda
import cutlass
import cutlass.cute as cute
import torch
from sglang.kernels.ops.diffusion.cutedsl.common.norm_fusion import (
apply_norm_cta,
broadcast_tensor_for_bsfd,
tensor_slice_for_bsfd,
)
from sglang.kernels.ops.diffusion.cutedsl.utils import (
WARP_SIZE,
to_cute_arg,
to_fake_cute_args,
)
_COMPILE_CACHE = {}
class NormTanhMulAddNormScale:
@classmethod
def make_hash_key(cls, *inputs):
"""
Compile-time values:
- D: hidden dimension (size of the last dimension)
- norm_type: layer norm or RMS norm
- tensor dtype
- tensor rank (i.e., tensor.ndim)
Runtime values:
- all other inputs
This hash key defines the compile-time specialization boundary for
NormTanhMulAddNormScale kernels.
"""
def _sig(val):
if isinstance(val, torch.Tensor):
return (val.dtype, val.ndim, val.shape[-1])
return val
return tuple(_sig(val) for val in inputs)
def __init__(self, D: int, norm_type: str, is_norm2: bool):
self.D = D
self.norm_type = norm_type # "layer" or "rms"
self.is_norm2 = is_norm2 # single norm or double norm
self.num_warps = self.D // 256 # num of warps per cta
self.num_threads = self.num_warps * WARP_SIZE # num of threads per cta
@cute.jit
def __call__(
self,
mY,
mY2,
mX,
mWeight,
mBias,
mScale,
mShift,
mWeight2,
mBias2,
mScale2,
eps: cutlass.Float32 = cutlass.Float32(1e-5),
stream: cuda.CUstream = cuda.CUstream(cuda.CUstream_flags.CU_STREAM_DEFAULT),
):
# Tensor shapes
B, S, _ = mX.shape # (batch, seq_len, hidden_dim)
# Vectorized copy configuration
num_vectorized = 8 # maximum num of elem per copy
atom_copy = cute.make_copy_atom(
cute.nvgpu.CopyUniversalOp(),
mX.element_type,
num_bits_per_copy=128,
)
# Thread/value layouts for tiled copy
t_layout = cute.make_layout(self.num_threads) # thread layout within a CTA
v_layout = cute.make_layout(num_vectorized) # per-thread vector layout
tiled_copy = cute.make_tiled_copy_tv(atom_copy, t_layout, v_layout)
self.kernel(
mY,
mY2,
mX,
mWeight,
mBias,
mScale,
mShift,
mWeight2,
mBias2,
mScale2,
tiled_copy,
eps,
).launch(
grid=[B * S, 1, 1],
block=[self.num_threads, 1, 1],
stream=stream,
)
@cute.kernel
def kernel(
self,
mY,
mY2,
mX,
mWeight,
mBias,
mScale,
mShift,
mWeight2,
mBias2,
mScale2,
tiled_copy: cute.TiledCopy,
eps: cutlass.Float32,
):
_, S, _ = mX.shape
tidx, _, _ = cute.arch.thread_idx() # thread index
bid, _, _ = cute.arch.block_idx() # cta index
bidx = cutlass.Int32(bid // S) # batch index
bidy = cutlass.Int32(bid % S) # seq_len index
thr_copy = tiled_copy.get_slice(tidx)
@cute.jit
def slice_if(mV):
if cutlass.const_expr(isinstance(mV, cute.Tensor)):
return tensor_slice_for_bsfd(mV, thr_copy, bidx, bidy, S, self.D)
return mV, mV
@cute.jit
def copy_if(src, dst):
if cutlass.const_expr(
isinstance(src, cute.Tensor) and isinstance(dst, cute.Tensor)
):
cute.autovec_copy(src, dst) # LDG.128
@cute.jit
def norm(x, weight, bias):
return apply_norm_cta(
self.norm_type, self.num_warps, tidx, x, weight, bias, self.D, eps
)
# Slice: retrieve the per-thread data slices for both global memory (gmem)
tXgX, tXrX = slice_if(mX) # x
tWgW, tWrW = slice_if(mWeight) # weight
tBgB, tBrB = slice_if(mBias) # bias
tSCgSC, tSCrSC = slice_if(mScale) # scale
tSHgSH, tSHrSH = slice_if(mShift) # shift
tYgY, tYrY = slice_if(mY) # y
if cutlass.const_expr(self.is_norm2):
tYgY2, tYrY2 = slice_if(mY2) # y2
tWgW2, tWrW2 = slice_if(mWeight2) # weight2
tBgB2, tBrB2 = slice_if(mBias2) # bias2
tSCgSC2, tSCrSC2 = slice_if(mScale2) # scale2
# Load: load tensor from global memory to registers
copy_if(tXgX, tXrX) # gmem -> rmem
copy_if(tWgW, tWrW) # gmem -> rmem
copy_if(tBgB, tBrB) # gmem -> rmem
tNrN = norm(tXrX, tWrW, tBrB)
# Compute: value = value * tanh(<scale>) + <shift>
copy_if(tSCgSC, tSCrSC) # gmem -> rmem
copy_if(tSHgSH, tSHrSH) # gmem -> rmem
value = tNrN.load() * cute.tanh(tSCrSC.load()) + tSHrSH.load()
# Store: y
tYrY.store(value.to(tYrY.element_type))
copy_if(tYrY, tYgY) # rmem -> gmem
if cutlass.const_expr(self.is_norm2):
copy_if(tWgW2, tWrW2) # gmem -> rmem
copy_if(tBgB2, tBrB2) # gmem -> rmem
tNrN2 = norm(tYrY, tWrW2, tBrB2)
# Compute: value2 = value2 * (1 + <scale2>)
copy_if(tSCgSC2, tSCrSC2) # gmem -> rmem
value2 = tNrN2.load() * (1 + tSCrSC2.load())
# Store: y2
tYrY2.store(value2.to(tYrY2.element_type))
copy_if(tYrY2, tYgY2) # rmem -> gmem
def validate_3d(t: torch.Tensor, B: int, S: int, D: int):
if t.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError(f"Validate failed: unsupported dtype: {t.dtype}")
if (
t.ndim != 3
or (t.shape[0] not in (1, B))
or (t.shape[1] not in (1, S) or t.shape[2] != D)
):
raise ValueError(f"Validate failed: unsupported 3d-tensor: {t.shape}.")
if t.stride()[-1] != 1:
raise ValueError(f"Validate failed: not contiguous on dim D.")
def validate_weight_bias(t: Optional[torch.Tensor], D: int):
if t is None:
return
if t.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError(f"Validate failed: unsupported dtype: {t.dtype}")
if t.shape != (D,):
raise ValueError(f"Validate failed: unsupported tensor shape: {t.shape}.")
if t.stride()[-1] != 1:
raise ValueError(f"Validate failed: not contiguous on dim D.")
@torch.library.custom_op("sglang::fused_norm_tanh_mul_add", mutates_args=())
def fused_norm_tanh_mul_add(
x: torch.Tensor,
weight: Optional[torch.Tensor],
bias: Optional[torch.Tensor],
scale: torch.Tensor,
shift: torch.Tensor,
norm_type: str,
eps: float = 1e-5,
) -> torch.Tensor:
"""
Fuse: norm(x) * tanh(scale) + shift
where norm is either layernorm or rmsnorm.
Expects:
- x: [B, S, D]
- weight/bias: None, [D]
- scale/shift: [1/B, 1/S, D]
- norm_type: str, "layer" or "rms"
- eps: Optional[float], default: 1e-5
D must be a multiple of 256 and <= 8192 to enable LDG.128 vectorized loads per
thread and avoid predicated loads (e.g., bounds checks such as `index < D`).
"""
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
# Tensor Validation
BSD = x.shape
validate_3d(x, *BSD)
validate_weight_bias(weight, BSD[2])
validate_weight_bias(bias, BSD[2])
validate_3d(scale, *BSD)
validate_3d(shift, *BSD)
if norm_type == "layer" or norm_type == "rms":
D = x.shape[-1]
if D % 256 != 0 or D > 8192:
raise ValueError(
f"D={D} not supported, must be multiple of 256 and <= 8192"
)
y = torch.empty_like(x) # create output tensor
scale = broadcast_tensor_for_bsfd(scale, *x.shape) # handle various shapes
shift = broadcast_tensor_for_bsfd(shift, *x.shape) # handle various shapes
# y2, weight2, bias2, scale2 is None
torch_tensors = [y, None, x, weight, bias, scale, shift, None, None, None]
cute_tensor_args = [to_cute_arg(t) for t in torch_tensors]
# Compile cache
hash_key = NormTanhMulAddNormScale.make_hash_key(norm_type, *torch_tensors)
compiled_fn = _COMPILE_CACHE.get(hash_key)
if compiled_fn is None:
kernel = NormTanhMulAddNormScale(D, norm_type, is_norm2=False)
fake_sig_args = [to_fake_cute_args(t) for t in torch_tensors]
compiled_fn = cute.compile(
kernel, *fake_sig_args, options="--enable-tvm-ffi"
)
_COMPILE_CACHE[hash_key] = compiled_fn
# Execute
compiled_fn(*cute_tensor_args, eps, stream)
return y
else:
raise ValueError(f'norm_type must be one of "layer" and "rms"')
@fused_norm_tanh_mul_add.register_fake
def _fused_norm_tanh_mul_add_fake(x, weight, bias, scale, shift, norm_type, eps=1e-5):
return x.new_empty(x.shape)
@torch.library.custom_op("sglang::fused_norm_tanh_mul_add_norm_scale", mutates_args=())
def fused_norm_tanh_mul_add_norm_scale(
x: torch.Tensor,
weight: Optional[torch.Tensor],
bias: Optional[torch.Tensor],
scale: torch.Tensor,
shift: torch.Tensor,
weight2: Optional[torch.Tensor],
bias2: Optional[torch.Tensor],
scale2: torch.Tensor,
norm_type: str,
eps: float = 1e-5,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Fuse:
y = norm(x) * tanh(scale) + shift
y2 = norm(y) * (1 + scale2)
where norm is either layernorm or rmsnorm.
Expects:
- x: [B, S, D]
- weight/bia/weight2/bias2: None, [D]
- scale/shift/scale2: [1/B, 1/S, D]
- norm_type: str, "layer" or "rms"
- eps: Optional[float], default: 1e-5
D must be a multiple of 256 and <= 8192 to enable LDG.128 vectorized loads per
thread and avoid predicated loads (e.g., bounds checks such as `index < D`).
"""
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
# Tensor Validation
BSD = x.shape
validate_3d(x, *BSD)
validate_weight_bias(weight, BSD[2])
validate_weight_bias(bias, BSD[2])
validate_3d(scale, *BSD)
validate_3d(shift, *BSD)
validate_weight_bias(weight2, BSD[2])
validate_weight_bias(bias2, BSD[2])
validate_3d(scale2, *BSD)
if norm_type == "layer" or norm_type == "rms":
D = x.shape[-1]
if D % 256 != 0 or D > 8192:
raise ValueError(
f"D={D} not supported, must be multiple of 256 and <= 8192"
)
y = torch.empty_like(x) # create output tensor
y2 = torch.empty_like(x) # create output tensor
scale = broadcast_tensor_for_bsfd(scale, *x.shape) # handle various shapes
shift = broadcast_tensor_for_bsfd(shift, *x.shape) # handle various shapes
scale2 = broadcast_tensor_for_bsfd(scale2, *x.shape) # handle various shapes
torch_tensors = [y, y2, x, weight, bias, scale, shift, weight2, bias2, scale2]
cute_tensor_args = [to_cute_arg(t) for t in torch_tensors]
# Compile cache
hash_key = NormTanhMulAddNormScale.make_hash_key(norm_type, *torch_tensors)
compiled_fn = _COMPILE_CACHE.get(hash_key)
if compiled_fn is None:
kernel = NormTanhMulAddNormScale(D, norm_type, is_norm2=True)
fake_sig_args = [to_fake_cute_args(t) for t in torch_tensors]
compiled_fn = cute.compile(
kernel, *fake_sig_args, options="--enable-tvm-ffi"
)
_COMPILE_CACHE[hash_key] = compiled_fn
# Execute
compiled_fn(*cute_tensor_args, eps, stream)
return y, y2
else:
raise ValueError(f'norm_type must be one of "layer" and "rms"')
@fused_norm_tanh_mul_add_norm_scale.register_fake
def _fused_norm_tanh_mul_add_norm_scale_fake(
x, weight, bias, scale, shift, weight2, bias2, scale2, norm_type, eps=1e-5
):
return x.new_empty(x.shape), x.new_empty(x.shape)
@@ -621,6 +621,6 @@ Before submitting, verify:
After the model produces non-noise output, read After the model produces non-noise output, read
[references/testing-and-accuracy.md](references/testing-and-accuracy.md) before [references/testing-and-accuracy.md](references/testing-and-accuracy.md) before
adding GPU cases, component-accuracy skips/hooks, suite entries, or benchmark adding GPU cases, component-accuracy skips/hooks, suite entries, or benchmark
claims. That reference tracks the current `gpu_cases.py` / `testcase_configs.py` claims. That reference tracks the current `gpu_cases.py`,
/ `accuracy_testcase_configs.py` / `run_suite.py` split and the component-accuracy `DiffusionTestCase.run_component_accuracy_check`,
decision rules. `single_test_file/component_accuracy/`, and `run_suite.py` split.
@@ -7,9 +7,10 @@ produce a non-noise image or video.
- Add concrete GPU integration cases in `python/sglang/multimodal_gen/test/server/gpu_cases.py`. - Add concrete GPU integration cases in `python/sglang/multimodal_gen/test/server/gpu_cases.py`.
- Keep reusable dataclasses, constants, thresholds, and testcase factory helpers in `python/sglang/multimodal_gen/test/server/testcase_configs.py`. - Keep reusable dataclasses, constants, thresholds, and testcase factory helpers in `python/sglang/multimodal_gen/test/server/testcase_configs.py`.
- Add the case id to `python/sglang/multimodal_gen/test/server/accuracy_testcase_configs.py` - Set `DiffusionTestCase.run_component_accuracy_check=False` only when the case
only when it should be part of component-accuracy coverage. Adding a GPU case should not enter component-accuracy coverage. Eligible cases default to
alone does not enroll it there. `True`; `python/sglang/multimodal_gen/test/single_test_file/component_accuracy/testcase_configs.py`
enrolls them automatically.
- Let `python/sglang/multimodal_gen/test/run_suite.py` own suite selection, runtime-based partitioning, and standalone test files. Do not hard-code CI shard lists elsewhere. - Let `python/sglang/multimodal_gen/test/run_suite.py` own suite selection, runtime-based partitioning, and standalone test files. Do not hard-code CI shard lists elsewhere.
- If a new standalone test file is added to a suite, update `STANDALONE_FILE_EST_TIMES` after the first measured CI/runtime value is known. - If a new standalone test file is added to a suite, update `STANDALONE_FILE_EST_TIMES` after the first measured CI/runtime value is known.
@@ -23,38 +24,53 @@ PYTHONPATH=python python3 python/sglang/multimodal_gen/test/run_suite.py --suite
## Component Accuracy When Adding A GPU Case ## Component Accuracy When Adding A GPU Case
If you add a new entry to `ONE_GPU_CASES`, `TWO_GPU_CASES`, or a B200-specific If you add a new entry to `ONE_GPU_CASES` or `TWO_GPU_CASES`, treat component
case group in `gpu_cases.py`, treat component accuracy as part of the accuracy as part of the model-adding workflow. Cases with
model-adding workflow. Do not assume the new testcase will automatically fit or `run_component_accuracy_check=True` are selected automatically. The selector
enter the existing component-accuracy harness. deduplicates each component by source model, component override, and GPU
topology; later equivalent cases receive an automatic duplicate skip reason.
B200-only groups are not currently inputs to the component-accuracy selector;
add or identify a representative regular GPU case when that coverage is
required.
The component-accuracy harness compares SGLang components against Diffusers/HF The component-accuracy harness compares SGLang components against Diffusers/HF
reference components. This is stricter than pipeline-level inference. New GPU reference components. This is stricter than pipeline-level inference. New GPU
cases commonly fail here for one of three reasons: cases commonly fail here for one of three reasons:
1. The model family needs explicit hook wiring in `python/sglang/multimodal_gen/test/server/accuracy_hooks.py`. 1. The model family needs explicit hook wiring in `python/sglang/multimodal_gen/test/single_test_file/component_accuracy/hooks.py`.
- Add hook logic only when the harness cannot call the raw component correctly without it. - Add hook logic only when the harness cannot call the raw component correctly without it.
- Valid reasons include missing required forward arguments, required autocast/runtime context, or family-specific input preparation for the same component contract. - Valid reasons include missing required forward arguments, required autocast/runtime context, or family-specific input preparation for the same component contract.
- Do not change the compared output mode or add harness-side behavior that changes the component contract just to make the test pass. - Do not change the compared output mode or add harness-side behavior that changes the component contract just to make the test pass.
2. The component is already covered by another testcase with the same source component and topology. 2. The component is already covered by another testcase with the same source component and topology.
- Do not add redundant component-accuracy coverage. - Do not add redundant component-accuracy coverage.
- Add a skip entry in `python/sglang/multimodal_gen/test/server/accuracy_config.py` with a concrete reason such as `Representative VAE accuracy is already covered by ... for the same source component and topology`. - Let `_select_accuracy_cases` in
`python/sglang/multimodal_gen/test/single_test_file/component_accuracy/testcase_configs.py`
deduplicate the component automatically. Do not add a manual skip for a
duplicate that the selector can identify.
- This is the preferred path for variant-only cases such as LoRA, Cache-DiT, upscaling, or other cases that reuse the same underlying component weights and topology. - This is the preferred path for variant-only cases such as LoRA, Cache-DiT, upscaling, or other cases that reuse the same underlying component weights and topology.
3. The HF/Diffusers reference component cannot be loaded or compared faithfully in the harness. 3. The HF/Diffusers reference component cannot be loaded or compared faithfully in the harness.
- Add a skip entry in `accuracy_config.py` with the exact technical failure. - Add a skip entry in
`python/sglang/multimodal_gen/test/single_test_file/component_accuracy/config.py`
with the exact technical failure.
- Good reasons include missing/unsupported HF component layout, incomplete checkpoints, unsupported raw component contract, or proven divergence after matched weight transfer and matching output shape. - Good reasons include missing/unsupported HF component layout, incomplete checkpoints, unsupported raw component contract, or proven divergence after matched weight transfer and matching output shape.
- Keep the skip reason concrete and technical. Do not write vague reasons like "component accuracy flaky" or "needs investigation." - Keep the skip reason concrete and technical. Do not write vague reasons like "component accuracy flaky" or "needs investigation."
When adding a new GPU case, make this decision explicitly: When adding a new GPU case, make this decision explicitly:
- if the case should have component-accuracy coverage, add its case id to - if the case should have component-accuracy coverage, leave
`accuracy_testcase_configs.py` `run_component_accuracy_check=True`
- if the family needs minimal harness wiring, add the smallest possible change in `accuracy_hooks.py` - if the family needs minimal harness wiring, add the smallest possible change
- if the case is only a variant of an already covered source component and topology, add a skip in `accuracy_config.py` in
- if the HF/Diffusers reference component cannot be compared faithfully, add a skip in `accuracy_config.py` `python/sglang/multimodal_gen/test/single_test_file/component_accuracy/hooks.py`
- if the case is intentionally GPU-smoke-only, leave it out of `accuracy_testcase_configs.py` and keep that choice explicit in the PR notes - if the case is only a variant of an already covered source component and
topology, rely on automatic per-component deduplication
- if the HF/Diffusers reference component cannot be compared faithfully, add a
concrete skip in
`python/sglang/multimodal_gen/test/single_test_file/component_accuracy/config.py`
- if the case is intentionally GPU-smoke-only, set
`run_component_accuracy_check=False` and explain the choice in the PR notes
Do not add a new GPU case and wait for CI to discover missing component-accuracy Do not add a new GPU case and wait for CI to discover missing component-accuracy
wiring. wiring.
@@ -54,7 +54,7 @@ Before calling a diffusion hotspot "new", first classify it with `existing-fast-
Always rule out these existing families first: Always rule out these existing families first:
- HunyuanVideo VAE GroupNorm+SiLU - HunyuanVideo VAE GroupNorm+SiLU
- LTX upsampler GroupNorm+SiLU - LTX upsampler GroupNorm+SiLU
- Z-Image residual-form modulation - Z-Image bf16-native Triton RMSNorm scale/tanh-residual modulation
- SANA packed self-attention Q/K/V and cross-attention K/V GEMMs - SANA packed self-attention Q/K/V and cross-attention K/V GEMMs
- fused diffusion `QK norm + RoPE` - fused diffusion `QK norm + RoPE`
- LTX2 split RoPE - LTX2 split RoPE
@@ -198,9 +198,11 @@ Use the preset categories this way:
| `zimage` | `Tongyi-MAI/Z-Image-Turbo` | Yes: `zimage_turbo_t2i_1024` | Prompt, 1024x1024, seed 42, 2 GPUs, TP size 2; no explicit steps/guidance override | | `zimage` | `Tongyi-MAI/Z-Image-Turbo` | Yes: `zimage_turbo_t2i_1024` | Prompt, 1024x1024, seed 42, 2 GPUs, TP size 2; no explicit steps/guidance override |
| `wan-t2v` | `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | Yes: `wan22_t2v_a14b_720p` | 1280x720, 81 frames, 4 GPUs, CFG parallel, Ulysses degree 2, text encoder CPU offload and pinned CPU memory | | `wan-t2v` | `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | Yes: `wan22_t2v_a14b_720p` | 1280x720, 81 frames, 4 GPUs, CFG parallel, Ulysses degree 2, text encoder CPU offload and pinned CPU memory |
| `wan-ti2v` | `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | Yes: `wan22_ti2v_5b_720p` | Nightly cat image and motion prompt, 1280x720, 81 frames, seed 42 | | `wan-ti2v` | `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | Yes: `wan22_ti2v_5b_720p` | Nightly cat image and motion prompt, 1280x720, 81 frames, seed 42 |
| `ltx2` | `Lightricks/LTX-2` | Yes: `ltx2_twostage_t2v` | `LTX2TwoStagePipeline`, 2 GPUs, CFG parallel, 768x512, 121 frames, seed 42 |
| `ltx23-ti2v-two-stage` | `Lightricks/LTX-2.3` | Yes: `ltx2.3_twostage_ti2v_2gpus` | Nightly cat image, motion prompt, `LTX2TwoStagePipeline`, 2 GPUs, `--cfg-parallel-size 2`, 768x512, 121 frames, seed 42 | | `ltx23-ti2v-two-stage` | `Lightricks/LTX-2.3` | Yes: `ltx2.3_twostage_ti2v_2gpus` | Nightly cat image, motion prompt, `LTX2TwoStagePipeline`, 2 GPUs, `--cfg-parallel-size 2`, 768x512, 121 frames, seed 42 |
| `ideogram4-fp8` | `ideogram-ai/ideogram-4-fp8` | Yes: `ideogram4_fp8_t2i_2gpu` | Prompt, 1024x1024, seed 42, 2 GPUs, TP size 2, FlashAttention backend; sampling preset owns steps/guidance |
| `cosmos3-super-t2v` | `nvidia/Cosmos3-Super` | Yes: `cosmos3_super_t2v_2gpu` | Prompt, 1280x720, 81 frames, seed 42, 2 GPUs, TP size 2, guardrails disabled for benchmark isolation |
| `wan-i2v` | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | Yes: `wan22_i2v_a14b_720p` | Nightly cat image and motion prompt, 1280x720, 81 frames, 4 GPUs, CFG parallel, Ulysses degree 2, text encoder CPU offload and pinned CPU memory | | `wan-i2v` | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | Yes: `wan22_i2v_a14b_720p` | Nightly cat image and motion prompt, 1280x720, 81 frames, 4 GPUs, CFG parallel, Ulysses degree 2, text encoder CPU offload and pinned CPU memory |
| `ltx2` | `Lightricks/LTX-2` | No | Current-source two-stage LTX-2 preset with 2 GPUs, CFG parallel, 768x512, 121 frames |
| `qwen-image` | `Qwen/Qwen-Image` | No | Current-source extra covering the base Qwen-Image native path, separate from the nightly `Qwen-Image-2512` case | | `qwen-image` | `Qwen/Qwen-Image` | No | Current-source extra covering the base Qwen-Image native path, separate from the nightly `Qwen-Image-2512` case |
| `qwen-edit-2509` | `Qwen/Qwen-Image-Edit-2509` | No | Current-source extra for the pre-2511 edit-plus path; uses the cat image, 1024x1024 | | `qwen-edit-2509` | `Qwen/Qwen-Image-Edit-2509` | No | Current-source extra for the pre-2511 edit-plus path; uses the cat image, 1024x1024 |
| `zimage-base` | `Tongyi-MAI/Z-Image` | No | Current-source extra for non-turbo Z-Image; keep it separate from `zimage` / `Z-Image-Turbo` | | `zimage-base` | `Tongyi-MAI/Z-Image` | No | Current-source extra for non-turbo Z-Image; keep it separate from `zimage` / `Z-Image-Turbo` |
@@ -208,7 +210,6 @@ Use the preset categories this way:
| `flux2-klein-base` | `black-forest-labs/FLUX.2-klein-base-4B` | No | Current-source extra for the undistilled FLUX.2 Klein Base path; gated repo, 1024x1024, DiT layerwise offload disabled | | `flux2-klein-base` | `black-forest-labs/FLUX.2-klein-base-4B` | No | Current-source extra for the undistilled FLUX.2 Klein Base path; gated repo, 1024x1024, DiT layerwise offload disabled |
| `cosmos3-nano-t2i` | `nvidia/Cosmos3-Nano` | No | Current-source extra for the single-frame Cosmos3 image path; sets `SGLANG_DISABLE_COSMOS3_GUARDRAILS=1` in the helper environment | | `cosmos3-nano-t2i` | `nvidia/Cosmos3-Nano` | No | Current-source extra for the single-frame Cosmos3 image path; sets `SGLANG_DISABLE_COSMOS3_GUARDRAILS=1` in the helper environment |
| `cosmos3-nano-t2v` | `nvidia/Cosmos3-Nano` | No | Current-source extra for a short Cosmos3 video path; sets `SGLANG_DISABLE_COSMOS3_GUARDRAILS=1` in the helper environment | | `cosmos3-nano-t2v` | `nvidia/Cosmos3-Nano` | No | Current-source extra for a short Cosmos3 video path; sets `SGLANG_DISABLE_COSMOS3_GUARDRAILS=1` in the helper environment |
| `ideogram4-fp8` | `ideogram-ai/ideogram-4-fp8` | No | Current-source extra matching the native Ideogram 4 FP8 pipeline; do not override steps/guidance directly because the sampling preset owns them |
| `ernie-image-turbo` | `baidu/ERNIE-Image-Turbo` | No | Current-source extra for ERNIE-Image Turbo | | `ernie-image-turbo` | `baidu/ERNIE-Image-Turbo` | No | Current-source extra for ERNIE-Image Turbo |
| `glm-image` | `zai-org/GLM-Image` | No | Current-source extra for GLM-Image | | `glm-image` | `zai-org/GLM-Image` | No | Current-source extra for GLM-Image |
| `sana-1.5-1.6b` | `Efficient-Large-Model/SANA1.5_1.6B_1024px_diffusers` | No | Current-source extra for a SANA native image path | | `sana-1.5-1.6b` | `Efficient-Large-Model/SANA1.5_1.6B_1024px_diffusers` | No | Current-source extra for a SANA native image path |
@@ -488,7 +489,7 @@ the known mainline families.
| --- | --- | | --- | --- |
| `fused_inplace_qknorm_rope` missing, but separate qk norm plus rope show up | Check whether the fused diffusion `QK norm + RoPE` path should have engaged | | `fused_inplace_qknorm_rope` missing, but separate qk norm plus rope show up | Check whether the fused diffusion `QK norm + RoPE` path should have engaged |
| `to_q -> to_k -> to_v` on NVFP4 or Nunchaku FLUX-family checkpoints | Treat as a packed-QKV fast-path miss or checkpoint-format mismatch | | `to_q -> to_k -> to_v` on NVFP4 or Nunchaku FLUX-family checkpoints | Treat as a packed-QKV fast-path miss or checkpoint-format mismatch |
| `fused_norm_tanh_mul_add*` missing on Z-Image | Treat as a missing mainline modulation path, not a new fusion request | | `zimage_rmsnorm_scale` or `zimage_rmsnorm_tanh_residual` missing on Z-Image | Check the bf16-native Triton eligibility guards before proposing a new fusion |
| LTX-2 split RoPE appears as a long PyTorch elementwise chain | Check the `apply_ltx2_split_rotary_emb` Triton path and its shape guards | | LTX-2 split RoPE appears as a long PyTorch elementwise chain | Check the `apply_ltx2_split_rotary_emb` Triton path and its shape guards |
| masked attention spends time packing/unpacking Q/K/V | Check whether fused varlen USP pack/scatter should have engaged | | masked attention spends time packing/unpacking Q/K/V | Check whether fused varlen USP pack/scatter should have engaged |
| `all_to_all`, ring attention, or async A2A dominate | Classify against Ulysses, USP, or turbo-layer overlap first | | `all_to_all`, ring attention, or async A2A dominate | Classify against Ulysses, USP, or turbo-layer overlap first |
@@ -15,20 +15,22 @@ framework-specific optimization workflow.
- `python/sglang/kernels/ops/diffusion/triton/group_norm_silu.py` - `python/sglang/kernels/ops/diffusion/triton/group_norm_silu.py`
- `python/sglang/kernels/ops/diffusion/triton/norm.py` - `python/sglang/kernels/ops/diffusion/triton/norm.py`
- `python/sglang/kernels/ops/diffusion/triton/rmsnorm_onepass.py` - `python/sglang/kernels/ops/diffusion/triton/rmsnorm_onepass.py`
- `python/sglang/kernels/ops/diffusion/triton/zimage_native_norm.py`
- `python/sglang/kernels/ops/diffusion/triton/rotary.py` - `python/sglang/kernels/ops/diffusion/triton/rotary.py`
- `python/sglang/kernels/ops/diffusion/triton/ltx2_rotary.py` - `python/sglang/kernels/ops/diffusion/triton/ltx2_rotary.py`
- `python/sglang/kernels/ops/diffusion/residual_gate_add.py` - `python/sglang/kernels/ops/diffusion/residual_gate_add.py`
- `python/sglang/kernels/jit/csrc/diffusion/residual_gate_add.cuh` - `python/sglang/kernels/jit/csrc/diffusion/residual_gate_add.cuh`
- `python/sglang/kernels/ops/diffusion/triton/varlen_pack_pad.py` - `python/sglang/kernels/ops/diffusion/triton/varlen_pack_pad.py`
- `python/sglang/kernels/ops/diffusion/cutedsl/scale_residual_norm_scale_shift.py` - `python/sglang/kernels/ops/diffusion/cutedsl/scale_residual_norm_scale_shift.py`
- `test/registered/jit/diffusion/test_qwen_image_modulation.py` - `test/registered/kernels/ops/diffusion/test_qwen_image_modulation.py`
- `test/registered/jit/diffusion/test_group_norm_silu.py` - `test/registered/kernels/ops/diffusion/test_group_norm_silu.py`
- `test/registered/jit/diffusion/test_residual_gate_add.py` - `test/registered/kernels/ops/diffusion/test_residual_gate_add.py`
- `test/registered/jit/diffusion/test_varlen_pack_pad.py` - `test/registered/kernels/ops/diffusion/test_varlen_pack_pad.py`
- `test/registered/jit/diffusion/test_varlen_uspattn_equivalence.py` - `test/registered/kernels/ops/diffusion/test_varlen_uspattn_equivalence.py`
- `test/registered/jit/benchmark/diffusion/bench_qwen_image_modulation.py` - `test/registered/kernels/ops/diffusion/test_zimage_native_norm.py`
- `test/registered/jit/benchmark/diffusion/bench_group_norm_silu.py` - `test/registered/kernels/benchmark/diffusion/bench_qwen_image_modulation.py`
- `test/registered/jit/benchmark/diffusion/bench_residual_gate_add.py` - `test/registered/kernels/benchmark/diffusion/bench_group_norm_silu.py`
- `test/registered/kernels/benchmark/diffusion/bench_residual_gate_add.py`
- `python/sglang/kernels/ops/layernorm/norm.py` - `python/sglang/kernels/ops/layernorm/norm.py`
- `python/sglang/multimodal_gen/runtime/platforms/cuda.py` - `python/sglang/multimodal_gen/runtime/platforms/cuda.py`
- `python/sglang/multimodal_gen/runtime/layers/attention/selector.py` - `python/sglang/multimodal_gen/runtime/layers/attention/selector.py`
@@ -42,7 +44,7 @@ framework-specific optimization workflow.
- Use cases: `x * (1 + scale) + shift`, `a * (k + b) + c`, and Qwen-style `(layernorm/residual layernorm) + scale/shift + gate select`. - Use cases: `x * (1 + scale) + shift`, `a * (k + b) + c`, and Qwen-style `(layernorm/residual layernorm) + scale/shift + gate select`.
- Constraints: `x` must be CUDA and contiguous. `scale/shift` support 0D/1D/2D/3D/4D broadcast. 4D `[B, F, 1, C]` requires `L % F == 0`. - Constraints: `x` must be CUDA and contiguous. `scale/shift` support 0D/1D/2D/3D/4D broadcast. 4D `[B, F, 1, C]` requires `L % F == 0`.
- NPU fallback: `scale_shift.py` swaps to `npu_fallback` native path. - NPU fallback: `scale_shift.py` swaps to `npu_fallback` native path.
- Validation: `test/registered/jit/diffusion/test_qwen_image_modulation.py`. - Validation: `test/registered/kernels/ops/diffusion/test_qwen_image_modulation.py`.
2. Norm + Scale/Shift fusion (CuTe DSL) 2. Norm + Scale/Shift fusion (CuTe DSL)
- Kernels: `fused_norm_scale_shift`, `fused_scale_residual_norm_scale_shift` - Kernels: `fused_norm_scale_shift`, `fused_scale_residual_norm_scale_shift`
@@ -53,22 +55,25 @@ framework-specific optimization workflow.
- Constraints: `D % 256 == 0` and `D <= 8192`. `x/residual/gate/scale/shift` must pass shape and stride validation. Dtypes limited to fp16/bf16/fp32. - Constraints: `D % 256 == 0` and `D <= 8192`. `x/residual/gate/scale/shift` must pass shape and stride validation. Dtypes limited to fp16/bf16/fp32.
- Behavior: CuTe DSL compilation cached by `(dtype, ndim, D, norm_type)`. `None` tensors replaced by scalar placeholders. If constraints fail, `layernorm.py` warns and falls back to native PyTorch. - Behavior: CuTe DSL compilation cached by `(dtype, ndim, D, norm_type)`. `None` tensors replaced by scalar placeholders. If constraints fail, `layernorm.py` warns and falls back to native PyTorch.
3. Z-Image fused tanh/gate modulation 3. Z-Image bf16-native RMSNorm modulation (Triton)
- Kernels: `fused_norm_tanh_mul_add`, `fused_norm_tanh_mul_add_norm_scale` - Kernels: `zimage_rmsnorm_scale`, `zimage_rmsnorm_tanh_residual`
- Locations: `layernorm.py`, `cutedsl/norm_tanh_mul_add_norm_scale.py`, `zimage.py` - Locations: `triton/zimage_native_norm.py`, `zimage.py`
- Use cases: - Use cases:
- `y = tanh(gate) * norm(x) + shift` - `y = rmsnorm(x) * scale`
- `y, y2 = tanh(gate) * norm(x) + shift`, then `y2 = norm(y) * (1 + scale)` - `y = residual + tanh(gate) * rmsnorm(x)`
- Constraints: same CuTe DSL envelope as the norm+scale/shift family in practice: contiguous last dim, fp16/bf16/fp32, and `D % 256 == 0`, `D <= 8192`. - Constraints: CUDA bf16 tensors, contiguous weights, flattenable row strides,
- Validation: `test/registered/jit/diffusion/test_norm_tanh_mul_add_norm_scale.py` compatible modulation row counts, and `D <= 8192`.
- Behavior: this is already a mainline fast path, so if Z-Image traces show the unfused chain, treat it as a missing or regressed existing optimization before proposing a new kernel. - Validation: `test/registered/kernels/ops/diffusion/test_zimage_native_norm.py`
- Behavior: the kernels preserve Z-Image's native bf16 arithmetic. They return
`None` when an eligibility guard fails, and the runtime wrapper executes the
native PyTorch formula.
4. Triton LayerNorm/RMSNorm fusion 4. Triton LayerNorm/RMSNorm fusion
- Kernels: `rms_norm_fn`, `layer_norm_fn`, `norm_infer` - Kernels: `rms_norm_fn`, `layer_norm_fn`, `norm_infer`
- Locations: `triton/norm.py`, `layernorm.py` - Locations: `triton/norm.py`, `layernorm.py`
- Use cases: fp32 RMSNorm with residual/dropout/rowscale/x1 branches, and inference-friendly `norm_infer`. - Use cases: fp32 RMSNorm with residual/dropout/rowscale/x1 branches, and inference-friendly `norm_infer`.
- Constraints: last dim must be contiguous, and `N * element_size < 64KB`. - Constraints: last dim must be contiguous, and `N * element_size < 64KB`.
- Validation: `test/registered/jit/test_rmsnorm.py`. - Validation: `test/registered/kernels/ops/layernorm/test_rmsnorm.py`.
5. Triton one-pass RMSNorm (small hidden size fast path) 5. Triton one-pass RMSNorm (small hidden size fast path)
- Kernel: `triton_one_pass_rms_norm` - Kernel: `triton_one_pass_rms_norm`
@@ -82,7 +87,7 @@ framework-specific optimization workflow.
- Use case: GPT-J style RoPE when not Neox. - Use case: GPT-J style RoPE when not Neox.
- Constraints: `head_size` must be even. - Constraints: `head_size` must be even.
- NPU fallback: `npu_fallback.apply_rotary_embedding_native`. - NPU fallback: `npu_fallback.apply_rotary_embedding_native`.
- Validation: `test/registered/jit/test_rope.py`. - Validation: `test/registered/kernels/ops/attention/test_rope.py`.
7. LTX2 split RoPE fusion 7. LTX2 split RoPE fusion
- Kernel: `apply_ltx2_split_rotary_emb` - Kernel: `apply_ltx2_split_rotary_emb`
@@ -97,8 +102,8 @@ framework-specific optimization workflow.
- Use case: `residual + update * gate` in LTX2 self-attention, prompt cross-attention, audio/video cross-attention, and feed-forward residual updates. - Use case: `residual + update * gate` in LTX2 self-attention, prompt cross-attention, audio/video cross-attention, and feed-forward residual updates.
- Constraints: `residual`, `update`, and `gate` must be CUDA tensors on the same device, contiguous, same dtype (`fp16`, `bf16`, or `fp32`), with `update.shape == residual.shape`; `gate` can match `residual` or be row-broadcast with the last dimension matching. - Constraints: `residual`, `update`, and `gate` must be CUDA tensors on the same device, contiguous, same dtype (`fp16`, `bf16`, or `fp32`), with `update.shape == residual.shape`; `gate` can match `residual` or be row-broadcast with the last dimension matching.
- Behavior: `_ltx2_residual_gate_add(...)` uses the CUDA custom op while guards pass. On a runtime exception outside `torch.compile`, it logs once, disables the fast path for the process, and falls back to `residual + update * gate`. - Behavior: `_ltx2_residual_gate_add(...)` uses the CUDA custom op while guards pass. On a runtime exception outside `torch.compile`, it logs once, disables the fast path for the process, and falls back to `residual + update * gate`.
- Validation: `test/registered/jit/diffusion/test_residual_gate_add.py`. - Validation: `test/registered/kernels/ops/diffusion/test_residual_gate_add.py`.
- Microbench: `test/registered/jit/benchmark/diffusion/bench_residual_gate_add.py`. - Microbench: `test/registered/kernels/benchmark/diffusion/bench_residual_gate_add.py`.
- Workflow rule: if LTX2 traces show repeated elementwise `mul` + `add` ladders around attention or MLP residuals, check whether this existing CUDA path was disabled by shape, dtype, contiguity, or a prior runtime failure before proposing another elementwise fusion. - Workflow rule: if LTX2 traces show repeated elementwise `mul` + `add` ladders around attention or MLP residuals, check whether this existing CUDA path was disabled by shape, dtype, contiguity, or a prior runtime failure before proposing another elementwise fusion.
9. HunyuanVideo / LTX upsampler GroupNorm + SiLU fusion 9. HunyuanVideo / LTX upsampler GroupNorm + SiLU fusion
@@ -107,8 +112,8 @@ framework-specific optimization workflow.
- Use case: `activation(group_norm(x))` when the activation is non-inplace `nn.SiLU` and the GroupNorm is affine. - Use case: `activation(group_norm(x))` when the activation is non-inplace `nn.SiLU` and the GroupNorm is affine.
- Enablement: mainline uses `apply_group_norm_silu(...)` in HunyuanVideo VAE paths and LTX latent upsampler paths by default; there is no env toggle. The wrapper dispatches to Triton only when guards pass. - Enablement: mainline uses `apply_group_norm_silu(...)` in HunyuanVideo VAE paths and LTX latent upsampler paths by default; there is no env toggle. The wrapper dispatches to Triton only when guards pass.
- Constraints: CUDA inference path only; no grad, `x.requires_grad == False`, `nn.GroupNorm`, `nn.SiLU(inplace=False)`, affine norm with weight and bias. Unsupported cases fall back to native `activation(norm(x))`. - Constraints: CUDA inference path only; no grad, `x.requires_grad == False`, `nn.GroupNorm`, `nn.SiLU(inplace=False)`, affine norm with weight and bias. Unsupported cases fall back to native `activation(norm(x))`.
- Validation: `test/registered/jit/diffusion/test_group_norm_silu.py`. - Validation: `test/registered/kernels/ops/diffusion/test_group_norm_silu.py`.
- Microbench: `test/registered/jit/benchmark/diffusion/bench_group_norm_silu.py`. - Microbench: `test/registered/kernels/benchmark/diffusion/bench_group_norm_silu.py`.
**Faster CUDA Kernel Usage Points** **Faster CUDA Kernel Usage Points**
@@ -116,7 +121,8 @@ framework-specific optimization workflow.
- Location: `layernorm.py` - Location: `layernorm.py`
- Behavior: - Behavior:
- Standard `bf16`/`fp16` CUDA paths use `sgl_kernel.fused_add_rmsnorm` and `sgl_kernel.rmsnorm`. - Standard `bf16`/`fp16` CUDA paths use `sgl_kernel.fused_add_rmsnorm` and `sgl_kernel.rmsnorm`.
- The Z-Image `fp32` `32x2560` path under `torch.compile` avoids `wrap_triton` and uses the native fp32 path. - Z-Image keeps bf16 arithmetic and uses its dedicated Triton native-norm
kernels when their guards pass.
- `hidden_size <= 128` uses Triton one-pass. - `hidden_size <= 128` uses Triton one-pass.
- ROCm falls back to native. - ROCm falls back to native.
@@ -131,7 +137,7 @@ framework-specific optimization workflow.
4. Varlen USP attention pack/scatter 4. Varlen USP attention pack/scatter
- Locations: `runtime/layers/attention/layer.py`, `triton/varlen_pack_pad.py` - Locations: `runtime/layers/attention/layer.py`, `triton/varlen_pack_pad.py`
- Behavior: masked `USPAttention.forward` can gather dense Q/K/V into packed `[total_valid, H, D]` rows with `fused_pack_qkv`, run varlen attention, then scatter back with `fused_scatter_to_padded`. - Behavior: masked `USPAttention.forward` can gather dense Q/K/V into packed `[total_valid, H, D]` rows with `fused_pack_qkv`, run varlen attention, then scatter back with `fused_scatter_to_padded`.
- Validation: `test/registered/jit/diffusion/test_varlen_pack_pad.py` and `test_varlen_uspattn_equivalence.py`. - Validation: `test/registered/kernels/ops/diffusion/test_varlen_pack_pad.py` and `test/registered/kernels/ops/diffusion/test_varlen_uspattn_equivalence.py`.
- Workflow rule: if a masked attention trace spends time in Python/advanced indexing pack or scatter, first check whether this fused varlen path should have engaged. - Workflow rule: if a masked attention trace spends time in Python/advanced indexing pack or scatter, first check whether this fused varlen path should have engaged.
**QK Norm Optimization** **QK Norm Optimization**
@@ -144,7 +150,7 @@ framework-specific optimization workflow.
- `can_use_fused_inplace_qknorm(head_dim, dtype)` returns true. - `can_use_fused_inplace_qknorm(head_dim, dtype)` returns true.
- Supported head dims: `64, 128, 256, 512, 1024`. - Supported head dims: `64, 128, 256, 512, 1024`.
- Behavior: Fused path operates on `q` and `k` in place after reshaping to `[B, -1, head_dim]`. If preconditions fail, fall back to per-tensor RMSNorm. - Behavior: Fused path operates on `q` and `k` in place after reshaping to `[B, -1, head_dim]`. If preconditions fail, fall back to per-tensor RMSNorm.
- Validation: `test/registered/jit/test_qknorm.py` and `test/registered/jit/test_qknorm_across_heads.py`. - Validation: `test/registered/kernels/ops/layernorm/test_qknorm.py` and `test/registered/kernels/ops/layernorm/test_qknorm_across_heads.py`.
**QK Norm + RoPE Optimization** **QK Norm + RoPE Optimization**
@@ -159,7 +165,7 @@ framework-specific optimization workflow.
- `can_use_fused_inplace_qknorm_rope(head_dim, rope_dim, is_neox, dtype)` returns true. - `can_use_fused_inplace_qknorm_rope(head_dim, rope_dim, is_neox, dtype)` returns true.
- Supported head dims: `64, 128, 256`. - Supported head dims: `64, 128, 256`.
- Behavior: `apply_qk_norm_rope` prefers the fused JIT kernel when all guards pass; otherwise it falls back to `apply_qk_norm(...)` plus `apply_flashinfer_rope_qk_inplace(...)`. - Behavior: `apply_qk_norm_rope` prefers the fused JIT kernel when all guards pass; otherwise it falls back to `apply_qk_norm(...)` plus `apply_flashinfer_rope_qk_inplace(...)`.
- Validation: `test/registered/jit/diffusion/test_qknorm_rope.py`. - Validation: `test/registered/kernels/ops/diffusion/test_qknorm_rope.py`.
- Workflow rule: treat LTX2 traces that miss the generic fused path as an enablement/shape-guard issue first, and check the separate LTX2 split-RoPE path before proposing new attention-prep kernels. - Workflow rule: treat LTX2 traces that miss the generic fused path as an enablement/shape-guard issue first, and check the separate LTX2 split-RoPE path before proposing new attention-prep kernels.
**Nunchaku Fused GELU MLP** **Nunchaku Fused GELU MLP**
@@ -187,7 +193,9 @@ framework-specific optimization workflow.
**Common Entry Points in Diffusion Models** **Common Entry Points in Diffusion Models**
- AdaLN modulation: `LayerNormScaleShift`, `RMSNormScaleShift`, `ScaleResidual*` in `layernorm.py`. - AdaLN modulation: `LayerNormScaleShift`, `RMSNormScaleShift`, `ScaleResidual*` in `layernorm.py`.
- Qwen-Image gating: `fuse_layernorm_scale_shift_gate_select01_kernel` and `fuse_residual_layernorm_scale_shift_gate_select01_kernel` through `fused_scale_shift_gate.py` and `qwen_image.py`. - Qwen-Image gating: `fuse_layernorm_scale_shift_gate_select01_kernel` and `fuse_residual_layernorm_scale_shift_gate_select01_kernel` through `fused_scale_shift_gate.py` and `qwen_image.py`.
- Z-Image residual-form modulation: `fused_norm_tanh_mul_add` and `fused_norm_tanh_mul_add_norm_scale` in `zimage.py`. - Z-Image native norm modulation: `zimage_rmsnorm_scale` and
`zimage_rmsnorm_tanh_mul_add` in `zimage.py`, backed by
`triton/zimage_native_norm.py`.
- HunyuanVideo VAE and LTX upsampler GroupNorm+SiLU: `apply_group_norm_silu` in `hunyuanvae.py` and `latent_upsampler.py`; default-eligible when wrapper guards pass. - HunyuanVideo VAE and LTX upsampler GroupNorm+SiLU: `apply_group_norm_silu` in `hunyuanvae.py` and `latent_upsampler.py`; default-eligible when wrapper guards pass.
- QK norm: `apply_qk_norm` used in `flux.py`, `flux_2.py`, `qwen_image.py`, `zimage.py`, `wanvideo.py`, `ltx_2.py`, `hunyuanvideo.py`. - QK norm: `apply_qk_norm` used in `flux.py`, `flux_2.py`, `qwen_image.py`, `zimage.py`, `wanvideo.py`, `ltx_2.py`, `hunyuanvideo.py`.
- QK norm + RoPE: `apply_qk_norm_rope` in `layernorm.py`; use this path when the model wants fused attention prep instead of separate QK norm and RoPE calls. - QK norm + RoPE: `apply_qk_norm_rope` in `layernorm.py`; use this path when the model wants fused attention prep instead of separate QK norm and RoPE calls.
@@ -77,8 +77,9 @@ NIGHTLY_PRESET_ORDER = (
"zimage", "zimage",
"wan-t2v", "wan-t2v",
"wan-ti2v", "wan-ti2v",
"ltx2",
"ltx23-ti2v-two-stage", "ltx23-ti2v-two-stage",
"ideogram4-fp8",
"cosmos3-super-t2v",
"wan-i2v", "wan-i2v",
) )
@@ -184,21 +185,7 @@ MODELS = {
"--num-frames=81", "--num-frames=81",
], ],
}, },
# 8. Nightly: ltx2_twostage_t2v # 8. Nightly: ltx2.3_twostage_ti2v_2gpus
"ltx2": {
"nightly_case_id": "ltx2_twostage_t2v",
"path": "Lightricks/LTX-2",
"prompt": "A cat and a dog baking a cake together in a kitchen.",
"extra_args": [
"--pipeline-class-name=LTX2TwoStagePipeline",
"--width=768",
"--height=512",
"--num-frames=121",
"--num-gpus=2",
"--enable-cfg-parallel",
],
},
# 9. Nightly: ltx2.3_twostage_ti2v_2gpus
# Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png
"ltx23-ti2v-two-stage": { "ltx23-ti2v-two-stage": {
"nightly_case_id": "ltx2.3_twostage_ti2v_2gpus", "nightly_case_id": "ltx2.3_twostage_ti2v_2gpus",
@@ -214,7 +201,36 @@ MODELS = {
"--cfg-parallel-size=2", "--cfg-parallel-size=2",
], ],
}, },
# 10. Nightly: wan22_i2v_a14b_720p # 9. Nightly: ideogram4_fp8_t2i_2gpu
"ideogram4-fp8": {
"nightly_case_id": "ideogram4_fp8_t2i_2gpu",
"path": "ideogram-ai/ideogram-4-fp8",
"prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",
"extra_args": [
"--width=1024",
"--height=1024",
"--num-gpus=2",
"--tp-size=2",
"--attention-backend=fa",
],
},
# 10. Nightly: cosmos3_super_t2v_2gpu
"cosmos3-super-t2v": {
"nightly_case_id": "cosmos3_super_t2v_2gpu",
"path": "nvidia/Cosmos3-Super",
"prompt": "A cat and a dog baking a cake together in a kitchen.",
"env": {
"SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",
},
"extra_args": [
"--width=1280",
"--height=720",
"--num-frames=81",
"--num-gpus=2",
"--tp-size=2",
],
},
# 11. Nightly: wan22_i2v_a14b_720p
# Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png
"wan-i2v": { "wan-i2v": {
"nightly_case_id": "wan22_i2v_a14b_720p", "nightly_case_id": "wan22_i2v_a14b_720p",
@@ -233,6 +249,18 @@ MODELS = {
], ],
}, },
# Source-tracked extras from current registry / GPU test coverage. # Source-tracked extras from current registry / GPU test coverage.
"ltx2": {
"path": "Lightricks/LTX-2",
"prompt": "A cat and a dog baking a cake together in a kitchen.",
"extra_args": [
"--pipeline-class-name=LTX2TwoStagePipeline",
"--width=768",
"--height=512",
"--num-frames=121",
"--num-gpus=2",
"--enable-cfg-parallel",
],
},
"qwen-image": { "qwen-image": {
"path": "Qwen/Qwen-Image", "path": "Qwen/Qwen-Image",
"prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets", "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",
@@ -305,14 +333,6 @@ MODELS = {
"--num-inference-steps=4", "--num-inference-steps=4",
], ],
}, },
"ideogram4-fp8": {
"path": "ideogram-ai/ideogram-4-fp8",
"prompt": "A clean product poster for a new open-source inference engine",
"extra_args": [
"--width=1024",
"--height=1024",
],
},
"ernie-image-turbo": { "ernie-image-turbo": {
"path": "baidu/ERNIE-Image-Turbo", "path": "baidu/ERNIE-Image-Turbo",
"prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets", "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",
@@ -657,6 +677,8 @@ def validate_nightly_alignment() -> int:
errors.append(f"{model_key}: reference image presence differs") errors.append(f"{model_key}: reference image presence differs")
if preset.get("seed", 42) != case.get("seed"): if preset.get("seed", 42) != case.get("seed"):
errors.append(f"{model_key}: seed differs") errors.append(f"{model_key}: seed differs")
if preset.get("env", {}) != case["frameworks"]["sglang"].get("extra_env", {}):
errors.append(f"{model_key}: environment differs")
actual_args = { actual_args = {
key: _normalize_cli_value(value) key: _normalize_cli_value(value)
@@ -403,5 +403,5 @@ When documenting results:
| `tools/build_modelopt_nvfp4_transformer.py` | Build mixed BF16+NVFP4 transformer directories when a family needs preserved BF16 layers | | `tools/build_modelopt_nvfp4_transformer.py` | Build mixed BF16+NVFP4 transformer directories when a family needs preserved BF16 layers |
| `tools/compare_diffusion_trajectory_similarity.py` | reduced deterministic BF16-vs-quantized validation | | `tools/compare_diffusion_trajectory_similarity.py` | reduced deterministic BF16-vs-quantized validation |
| `docs_new/docs/sglang-diffusion/quantization.mdx` | public ModelOpt support matrix and CLI examples | | `docs_new/docs/sglang-diffusion/quantization.mdx` | public ModelOpt support matrix and CLI examples |
| `test/server/testcase_configs.py` | reusable ModelOpt testcase constants, thresholds, and helpers | | `python/sglang/multimodal_gen/test/server/testcase_configs.py` | reusable ModelOpt testcase constants, thresholds, and helpers |
| `test/server/gpu_cases.py` | concrete GPU and B200 ModelOpt CI case lists | | `python/sglang/multimodal_gen/test/server/gpu_cases.py` | concrete GPU and B200 ModelOpt CI case lists |
@@ -82,7 +82,7 @@ For Wan2.2 specifically:
- for pure latency tuning, benchmark pure Ulysses too, for example `--ulysses-degree=4 --ring-degree=1` on 4 GPUs - for pure latency tuning, benchmark pure Ulysses too, for example `--ulysses-degree=4 --ring-degree=1` on 4 GPUs
- on 8 GPUs, compare pure `--ulysses-degree=8` against `--enable-cfg-parallel --ulysses-degree=4` - on 8 GPUs, compare pure `--ulysses-degree=8` against `--enable-cfg-parallel --ulysses-degree=4`
### Nightly-aligned model, 2 GPUs: LTX-2 two-stage ### Current-source model, 2 GPUs: LTX-2 two-stage
```bash ```bash
sglang generate --model-path Lightricks/LTX-2 \ sglang generate --model-path Lightricks/LTX-2 \
@@ -94,7 +94,11 @@ sglang generate --model-path Lightricks/LTX-2 \
--enable-torch-compile --warmup --save-output --enable-torch-compile --warmup --save-output
``` ```
Note: this generate recipe is aligned with the nightly comparison case `ltx2_twostage_t2v`. The nightly config omits explicit steps and guidance, so this command omits them too and uses runtime defaults. `LTX2TwoStagePipeline` is a native path and auto-resolves the spatial upsampler plus distilled LoRA from the same model snapshot unless you override them. Note: LTX-2 is a current-source benchmark preset rather than a nightly
comparison case. The command uses runtime-default steps and guidance.
`LTX2TwoStagePipeline` is a native path and auto-resolves the spatial
upsampler plus distilled LoRA from the same model snapshot unless you override
them.
### Nightly-aligned model, 2 GPUs: LTX-2.3 TI2V two-stage ### Nightly-aligned model, 2 GPUs: LTX-2.3 TI2V two-stage
@@ -254,7 +258,7 @@ Use these as first commands to benchmark, not as universal winners.
| FLUX.1 / FLUX.2 image | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup --dit-layerwise-offload false` | `black-forest-labs/FLUX.*` repos are gated; for FP8/NVFP4 use validated `--transformer-path` or `--transformer-weights-path` flows from the quant skill. | | FLUX.1 / FLUX.2 image | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup --dit-layerwise-offload false` | `black-forest-labs/FLUX.*` repos are gated; for FP8/NVFP4 use validated `--transformer-path` or `--transformer-weights-path` flows from the quant skill. |
| FLUX.2 Klein / Klein Base | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup --dit-layerwise-offload false` | Current registry has `black-forest-labs/FLUX.2-klein-4B`, `FLUX.2-klein-9B`, and base variants. Klein is step-distilled; Klein Base is not. | | FLUX.2 Klein / Klein Base | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup --dit-layerwise-offload false` | Current registry has `black-forest-labs/FLUX.2-klein-4B`, `FLUX.2-klein-9B`, and base variants. Klein is step-distilled; Klein Base is not. |
| Qwen-Image / Qwen-Image-Edit | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup`; optionally native `SGLANG_CACHE_DIT_ENABLED=true` | Cache-DiT is lossy. For edit tasks, keep reference image, seed, and output size fixed. | | Qwen-Image / Qwen-Image-Edit | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup`; optionally native `SGLANG_CACHE_DIT_ENABLED=true` | Cache-DiT is lossy. For edit tasks, keep reference image, seed, and output size fixed. |
| Z-Image / Z-Image-Turbo | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup` | Keep base Z-Image separate from Turbo: base uses 50-step CFG defaults, Turbo uses 9-step zero-CFG defaults. Mainline has Z-Image tanh/gate norm fusions. | | Z-Image / Z-Image-Turbo | 1024x1024, runtime-default steps/guidance, 1 GPU | `--enable-torch-compile --warmup` | Keep base Z-Image separate from Turbo: base uses 50-step CFG defaults, Turbo uses 9-step zero-CFG defaults. Mainline has bf16-native Triton RMSNorm scale and tanh-residual fusions. |
| Wan2.2 A14B T2V/I2V | 1280x720, 81 frames | Nightly: `--num-gpus 4 --enable-cfg-parallel --ulysses-degree 2 --text-encoder-cpu-offload --pin-cpu-memory` | For lowest latency, also benchmark pure Ulysses on the same GPUs. | | Wan2.2 A14B T2V/I2V | 1280x720, 81 frames | Nightly: `--num-gpus 4 --enable-cfg-parallel --ulysses-degree 2 --text-encoder-cpu-offload --pin-cpu-memory` | For lowest latency, also benchmark pure Ulysses on the same GPUs. |
| Wan2.2 TI2V 5B | 1280x720, 81 frames, 1 GPU | `--enable-torch-compile --warmup` | Keep the input image and motion prompt fixed when comparing sparse attention or Cache-DiT. | | Wan2.2 TI2V 5B | 1280x720, 81 frames, 1 GPU | `--enable-torch-compile --warmup` | Keep the input image and motion prompt fixed when comparing sparse attention or Cache-DiT. |
| Wan2.1 / FastWan / TurboWan variants | 480p or 720p video, family defaults | `--enable-torch-compile --warmup`; add `--ulysses-degree` / CFG parallel only after measuring | Current registry includes Wan2.1, FastWan2.1, FastWan2.2 TI2V, TurboWan2.1, TurboWan2.2 I2V, and Wan2.1-Fun InP. Use the compatibility matrix and benchmark presets before choosing topology. | | Wan2.1 / FastWan / TurboWan variants | 480p or 720p video, family defaults | `--enable-torch-compile --warmup`; add `--ulysses-degree` / CFG parallel only after measuring | Current registry includes Wan2.1, FastWan2.1, FastWan2.2 TI2V, TurboWan2.1, TurboWan2.2 I2V, and Wan2.1-Fun InP. Use the compatibility matrix and benchmark presets before choosing topology. |
@@ -285,6 +289,6 @@ about whether the work has merged:
- **Offload tuning**: after the first request, the runtime logs peak GPU memory and which components could stay resident. Use this to decide which `--*-cpu-offload` flags to disable. - **Offload tuning**: after the first request, the runtime logs peak GPU memory and which components could stay resident. Use this to decide which `--*-cpu-offload` flags to disable.
- **Backend selection**: `--backend sglang` (default, auto-detected) enables native optimizations (fused kernels, SP, native Cache-DiT env knobs, etc.). `--backend diffusers` falls back to Diffusers pipelines and is the path that accepts `--cache-dit-config` plus diffusers attention backend names. - **Backend selection**: `--backend sglang` (default, auto-detected) enables native optimizations (fused kernels, SP, native Cache-DiT env knobs, etc.). `--backend diffusers` falls back to Diffusers pipelines and is the path that accepts `--cache-dit-config` plus diffusers attention backend names.
- **Wan2.2-I2V sizing**: explicit `--width/--height` on `Wan2.2-I2V-A14B` control the target area while preserving the condition-image aspect ratio. - **Wan2.2-I2V sizing**: explicit `--width/--height` on `Wan2.2-I2V-A14B` control the target area while preserving the condition-image aspect ratio.
- **Mainline diffusion fast paths**: before proposing a new kernel or overlap scheme, check `sglang-diffusion-benchmark-profile/existing-fast-paths.md`. It covers GroupNorm+SiLU, Z-Image residual-form modulation, fused diffusion `QK norm + RoPE`, LTX2 split RoPE, LTX2 residual-gate add, varlen USP pack/scatter, packed QKV/NVFP4 expectations, and existing multi-GPU overlap families such as Ulysses / USP and turbo-layer async all-to-all. - **Mainline diffusion fast paths**: before proposing a new kernel or overlap scheme, check `sglang-diffusion-benchmark-profile/existing-fast-paths.md`. It covers GroupNorm+SiLU, Z-Image bf16-native Triton norm modulation, fused diffusion `QK norm + RoPE`, LTX2 split RoPE, LTX2 residual-gate add, varlen USP pack/scatter, packed QKV/NVFP4 expectations, and existing multi-GPU overlap families such as Ulysses / USP and turbo-layer async all-to-all.
- **NVFP4 trace interpretation**: on FLUX.2 NVFP4 and Nunchaku-style checkpoints, packed QKV is expected. SGLang intentionally uses fused projection modules such as `to_qkv` / `to_added_qkv` instead of separate `to_q` / `to_k` / `to_v`, so a split-QKV trace usually means the quantized path did not engage rather than a brand new fusion opportunity. - **NVFP4 trace interpretation**: on FLUX.2 NVFP4 and Nunchaku-style checkpoints, packed QKV is expected. SGLang intentionally uses fused projection modules such as `to_qkv` / `to_added_qkv` instead of separate `to_q` / `to_k` / `to_v`, so a split-QKV trace usually means the quantized path did not engage rather than a brand new fusion opportunity.
- **Hotspot workflow split**: use `sglang-diffusion-benchmark-profile` to prove and classify a slowdown with perf dumps plus `torch.profiler`; hand concrete kernel work off with the perf/profile evidence attached instead of expanding the benchmark skill. - **Hotspot workflow split**: use `sglang-diffusion-benchmark-profile` to prove and classify a slowdown with perf dumps plus `torch.profiler`; hand concrete kernel work off with the perf/profile evidence attached instead of expanding the benchmark skill.
@@ -716,8 +716,3 @@ class LTX2PipelineConfig(PipelineConfig):
@dataclasses.dataclass @dataclasses.dataclass
class LTX23PipelineConfig(LTX2PipelineConfig): class LTX23PipelineConfig(LTX2PipelineConfig):
"""Configuration overrides for LTX-2.3.""" """Configuration overrides for LTX-2.3."""
@dataclasses.dataclass
class LTX2I2VPipelineConfig(LTX2PipelineConfig):
task_type: ModelTaskType = ModelTaskType.TI2V
@@ -57,10 +57,6 @@ def zimage_postprocess_text(
return pad_text_embeddings_with_mask(split_hidden_states) return pad_text_embeddings_with_mask(split_hidden_states)
class TransformersModelConfig(EncoderConfig):
tokenizer_kwargs: dict = field(default_factory=lambda: {})
@dataclass @dataclass
class ZImagePipelineConfig(ZImageRolloutPipelineMixin, ImagePipelineConfig): class ZImagePipelineConfig(ZImageRolloutPipelineMixin, ImagePipelineConfig):
should_use_guidance: bool = False should_use_guidance: bool = False
@@ -19,11 +19,3 @@ class Krea2SamplingParams(SamplingParams):
width: int = 1024 width: int = 1024
guidance_scale: float = 1.0 guidance_scale: float = 1.0
num_inference_steps: int = 8 num_inference_steps: int = 8
@dataclass
class Krea2RawSamplingParams(Krea2SamplingParams):
"""Base `oss_raw` defaults: full sampler with CFG."""
guidance_scale: float = 4.5
num_inference_steps: int = 52
-51
View File
@@ -12,17 +12,14 @@ from sglang.multimodal_gen.runtime.utils.common import get_bool_env_var
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL: int = 60
SGLANG_DIFFUSION_NCCL_SO_PATH: str | None = None SGLANG_DIFFUSION_NCCL_SO_PATH: str | None = None
LD_LIBRARY_PATH: str | None = None LD_LIBRARY_PATH: str | None = None
LOCAL_RANK: int = 0 LOCAL_RANK: int = 0
CUDA_VISIBLE_DEVICES: str | None = None CUDA_VISIBLE_DEVICES: str | None = None
SGLANG_DIFFUSION_CACHE_ROOT: str = os.path.expanduser("~/.cache/sgl_diffusion") SGLANG_DIFFUSION_CACHE_ROOT: str = os.path.expanduser("~/.cache/sgl_diffusion")
SGLANG_DIFFUSION_CONFIG_ROOT: str = os.path.expanduser("~/.config/sgl_diffusion") SGLANG_DIFFUSION_CONFIG_ROOT: str = os.path.expanduser("~/.config/sgl_diffusion")
SGLANG_DIFFUSION_CONFIGURE_LOGGING: int = 1
SGLANG_DIFFUSION_LOGGING_LEVEL: str = "INFO" SGLANG_DIFFUSION_LOGGING_LEVEL: str = "INFO"
SGLANG_DIFFUSION_LOGGING_PREFIX: str = "" SGLANG_DIFFUSION_LOGGING_PREFIX: str = ""
SGLANG_DIFFUSION_LOGGING_CONFIG_PATH: str | None = None
SGLANG_DIFFUSION_TRACE_FUNCTION: int = 0 SGLANG_DIFFUSION_TRACE_FUNCTION: int = 0
SGLANG_DIFFUSION_WORKER_MULTIPROC_METHOD: str = "fork" SGLANG_DIFFUSION_WORKER_MULTIPROC_METHOD: str = "fork"
SGLANG_DIFFUSION_TARGET_DEVICE: str = "cuda" SGLANG_DIFFUSION_TARGET_DEVICE: str = "cuda"
@@ -62,7 +59,6 @@ if TYPE_CHECKING:
SGLANG_DIFFUSION_FLASHINFER_FP4_GEMM_BACKEND: str | None = None SGLANG_DIFFUSION_FLASHINFER_FP4_GEMM_BACKEND: str | None = None
SGLANG_DIFFUSION_ENABLE_W8A8_FP8_GEMM: bool = False SGLANG_DIFFUSION_ENABLE_W8A8_FP8_GEMM: bool = False
SGLANG_DIFFUSION_VAE_CHANNELS_LAST_3D: str = "auto" SGLANG_DIFFUSION_VAE_CHANNELS_LAST_3D: str = "auto"
SGLANG_USE_CUDA_HUNYUANVIDEO_GROUP_NORM_SILU: bool = False
SGLANG_USE_ROCM_VAE: bool = False SGLANG_USE_ROCM_VAE: bool = False
SGLANG_USE_ROCM_CUDNN_BENCHMARK: bool = False SGLANG_USE_ROCM_CUDNN_BENCHMARK: bool = False
SGLANG_USE_ROCM_VAE_CONV2D: bool = False SGLANG_USE_ROCM_VAE_CONV2D: bool = False
@@ -83,10 +79,6 @@ def get_default_config_root() -> str:
) )
def maybe_convert_int(value: str | None) -> int | None:
return int(value) if value is not None else None
# helpers for environment variable definitions # helpers for environment variable definitions
def _lazy_str(key: str, default: str | None = None) -> Callable[[], str | None]: def _lazy_str(key: str, default: str | None = None) -> Callable[[], str | None]:
return lambda: os.getenv(key, default) return lambda: os.getenv(key, default)
@@ -110,20 +102,6 @@ def _lazy_bool(key: str, default: str = "false") -> Callable[[], bool]:
return lambda: get_bool_env_var(key, default) return lambda: get_bool_env_var(key, default)
def _lazy_bool_any(keys: list[str], default: str = "false") -> Callable[[], bool]:
def _getter():
for key in keys:
if get_bool_env_var(key, "false"):
return True
return (
get_bool_env_var("", default)
if not keys
else get_bool_env_var(keys[0], default)
)
return _getter
def _lazy_path( def _lazy_path(
key: str, default_func: Callable[[], str] | None = None key: str, default_func: Callable[[], str] | None = None
) -> Callable[[], str | None]: ) -> Callable[[], str | None]:
@@ -157,13 +135,6 @@ environment_variables: dict[str, Callable[[], Any]] = {
# By default this is 1. # By default this is 1.
# If set, `MAX_JOBS` will be reduced to avoid oversubscribing the CPU. # If set, `MAX_JOBS` will be reduced to avoid oversubscribing the CPU.
"NVCC_THREADS": _lazy_str("NVCC_THREADS"), "NVCC_THREADS": _lazy_str("NVCC_THREADS"),
# If set, sgl_diffusion will use precompiled binaries (*.so)
"SGLANG_DIFFUSION_USE_PRECOMPILED": _lazy_bool_any(
[
"SGLANG_DIFFUSION_USE_PRECOMPILED",
"SGLANG_DIFFUSION_PRECOMPILED_WHEEL_LOCATION",
]
),
# CMake build type # CMake build type
# If not set, defaults to "Debug" or "RelWithDebInfo" # If not set, defaults to "Debug" or "RelWithDebInfo"
# Available options: "Debug", "Release", "RelWithDebInfo" # Available options: "Debug", "Release", "RelWithDebInfo"
@@ -186,39 +157,17 @@ environment_variables: dict[str, Callable[[], Any]] = {
"SGLANG_DIFFUSION_CACHE_ROOT", "SGLANG_DIFFUSION_CACHE_ROOT",
lambda: os.path.join(get_default_cache_root(), "sgl_diffusion"), lambda: os.path.join(get_default_cache_root(), "sgl_diffusion"),
), ),
# Interval in seconds to log a warning message when the ring buffer is full
"SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL": _lazy_int(
"SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL", 60
),
# Path to the NCCL library file. It is needed because nccl>=2.19 brought # Path to the NCCL library file. It is needed because nccl>=2.19 brought
# by PyTorch contains a bug: https://github.com/NVIDIA/nccl/issues/1234 # by PyTorch contains a bug: https://github.com/NVIDIA/nccl/issues/1234
"SGLANG_DIFFUSION_NCCL_SO_PATH": _lazy_str("SGLANG_DIFFUSION_NCCL_SO_PATH"), "SGLANG_DIFFUSION_NCCL_SO_PATH": _lazy_str("SGLANG_DIFFUSION_NCCL_SO_PATH"),
# when `SGLANG_DIFFUSION_NCCL_SO_PATH` is not set, sgl_diffusion will try to find the nccl # when `SGLANG_DIFFUSION_NCCL_SO_PATH` is not set, sgl_diffusion will try to find the nccl
# library file in the locations specified by `LD_LIBRARY_PATH` # library file in the locations specified by `LD_LIBRARY_PATH`
"LD_LIBRARY_PATH": _lazy_str("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH": _lazy_str("LD_LIBRARY_PATH"),
# Internal flag to enable Dynamo fullgraph capture
"SGLANG_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE": _lazy_bool(
"SGLANG_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE", "1"
),
# local rank of the process in the distributed setting, used to determine # local rank of the process in the distributed setting, used to determine
# the GPU device id # the GPU device id
"LOCAL_RANK": _lazy_int("LOCAL_RANK", 0), "LOCAL_RANK": _lazy_int("LOCAL_RANK", 0),
# used to control the visible devices in the distributed setting # used to control the visible devices in the distributed setting
"CUDA_VISIBLE_DEVICES": _lazy_str("CUDA_VISIBLE_DEVICES"), "CUDA_VISIBLE_DEVICES": _lazy_str("CUDA_VISIBLE_DEVICES"),
# timeout for each iteration in the engine
"SGLANG_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S": _lazy_int(
"SGLANG_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S", 60
),
# Logging configuration
# If set to 0, sgl_diffusion will not configure logging
# If set to 1, sgl_diffusion will configure logging using the default configuration
# or the configuration file specified by SGLANG_DIFFUSION_LOGGING_CONFIG_PATH
"SGLANG_DIFFUSION_CONFIGURE_LOGGING": _lazy_int(
"SGLANG_DIFFUSION_CONFIGURE_LOGGING", 1
),
"SGLANG_DIFFUSION_LOGGING_CONFIG_PATH": _lazy_str(
"SGLANG_DIFFUSION_LOGGING_CONFIG_PATH"
),
# this is used for configuring the default logging level # this is used for configuring the default logging level
"SGLANG_DIFFUSION_LOGGING_LEVEL": _lazy_str( "SGLANG_DIFFUSION_LOGGING_LEVEL": _lazy_str(
"SGLANG_DIFFUSION_LOGGING_LEVEL", "INFO" "SGLANG_DIFFUSION_LOGGING_LEVEL", "INFO"
@@ -111,7 +111,6 @@ class GeluAndMul(CustomOp):
@CustomOp.register("gelu_new") @CustomOp.register("gelu_new")
class NewGELU(CustomOp): class NewGELU(CustomOp):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -161,18 +160,3 @@ def get_act_fn(act_fn_name: str) -> nn.Module:
raise ValueError(f"Activation function {act_fn_name!r} is not supported.") raise ValueError(f"Activation function {act_fn_name!r} is not supported.")
return _ACTIVATION_REGISTRY[act_fn_name]() return _ACTIVATION_REGISTRY[act_fn_name]()
_ACTIVATION_AND_MUL_REGISTRY = {
"gelu": GeluAndMul,
"silu": SiluAndMul,
}
def get_act_and_mul_fn(act_fn_name: str) -> nn.Module:
"""Get an activation-and-mul (i.e. SiluAndMul) function by name."""
act_fn_name = act_fn_name.lower()
if act_fn_name not in _ACTIVATION_AND_MUL_REGISTRY:
raise ValueError(f"Activation function {act_fn_name!r} is not supported.")
return _ACTIVATION_AND_MUL_REGISTRY[act_fn_name]()
@@ -541,7 +541,7 @@ class _ScaleResidualNormScaleShift(CustomOp):
if residual.numel() == 0 or x.numel() == 0: if residual.numel() == 0 or x.numel() == 0:
return self.forward_native(residual, x, gate, shift, scale) return self.forward_native(residual, x, gate, shift, scale)
if x.shape[-1] % 256 != 0 and x.shape[-1] <= 8192: if x.shape[-1] % 256 != 0 or x.shape[-1] > 8192:
import warnings import warnings
warnings.warn( warnings.warn(
@@ -719,7 +719,7 @@ class _NormScaleShift(CustomOp):
def forward_cuda( def forward_cuda(
self, x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor self, x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor
) -> torch.Tensor: ) -> torch.Tensor:
if x.shape[-1] % 256 != 0 and x.shape[-1] <= 8192: if x.shape[-1] % 256 != 0 or x.shape[-1] > 8192:
import warnings import warnings
warnings.warn( warnings.warn(
@@ -810,81 +810,6 @@ class RMSNormScaleShift(_NormScaleShift):
norm_type = "rms" norm_type = "rms"
################################################################################
# NormTanhMulAdd
# y = norm(x) * tanh(scale) + shift (where norm is layernorm or rmsnorm)
# See details in norm_tanh_mul_add_norm_scale.py
################################################################################
class _NormTanhMulAdd(CustomOp):
norm_type: str
def __init__(
self,
hidden_size: int,
eps: float = 1e-6,
affine: bool = False,
dtype: torch.dtype = torch.float32,
):
super().__init__()
self.eps = eps
if self.norm_type == "rms":
self.norm = RMSNorm(hidden_size, eps=eps, dtype=dtype)
elif self.norm_type == "layer":
self.norm = FP32LayerNorm(
hidden_size, elementwise_affine=affine, eps=eps, dtype=dtype
)
else:
raise NotImplementedError(f"Norm type {self.norm_type} not implemented")
def forward_cuda(
self, x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor
) -> torch.Tensor:
if x.shape[-1] % 256 != 0 and x.shape[-1] <= 8192:
import warnings
warnings.warn(
"FusedNormScaleShift cuda not available, using native fallback",
stacklevel=2,
)
return self.forward_native(x, scale, shift)
from sglang.kernels.ops.diffusion.cutedsl.norm_tanh_mul_add_norm_scale import (
fused_norm_tanh_mul_add,
)
x, scale, shift = x.contiguous(), scale.contiguous(), shift.contiguous()
weight = _ensure_contiguous(getattr(self.norm, "weight", None))
bias = _ensure_contiguous(getattr(self.norm, "bias", None))
return fused_norm_tanh_mul_add(
x,
weight,
bias,
scale,
shift,
self.norm_type,
self.eps,
)
def forward_hip(self, *args, **kwargs):
# Fallback to native because ROCm does not support CuTeDSL.
return self.forward_native(*args, **kwargs)
@torch.compile(disable=current_platform.is_npu() or current_platform.is_rocm())
def forward_native(
self, x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor
) -> torch.Tensor:
y = self.norm(x) * torch.tanh(scale) + shift
return y.to(x.dtype)
class LayerNormTanhMulAdd(_NormTanhMulAdd):
norm_type = "layer"
class RMSNormTanhMulAdd(_NormTanhMulAdd):
norm_type = "rms"
def apply_qk_norm( def apply_qk_norm(
q: torch.Tensor, q: torch.Tensor,
k: torch.Tensor, k: torch.Tensor,
@@ -1078,34 +1003,6 @@ def apply_qk_norm_rope(
) )
def apply_rmsnorm_tanh_mul_add(
x: torch.Tensor,
gate: torch.Tensor,
residual: torch.Tensor,
norm: "RMSNorm",
) -> torch.Tensor:
"""Compute residual + tanh(gate) * rmsnorm(x), with a fused CUDA fast path."""
if get_bool_env_var("SGLANG_ENABLE_DETERMINISTIC_INFERENCE"):
return residual + torch.tanh(gate) * norm(x)
if _is_cuda and x.is_cuda and x.shape[-1] % 256 == 0 and x.shape[-1] <= 8192:
from sglang.kernels.ops.diffusion.cutedsl.norm_tanh_mul_add_norm_scale import (
fused_norm_tanh_mul_add,
)
return fused_norm_tanh_mul_add(
x.contiguous(),
norm.weight.data.contiguous(),
None,
gate.contiguous(),
residual.contiguous(),
"rms",
norm.variance_epsilon,
)
return residual + torch.tanh(gate) * norm(x)
def tensor_parallel_rms_norm(x: torch.Tensor, norm: "RMSNorm") -> torch.Tensor: def tensor_parallel_rms_norm(x: torch.Tensor, norm: "RMSNorm") -> torch.Tensor:
tp_rank = get_tensor_model_parallel_rank() tp_rank = get_tensor_model_parallel_rank()
tp_size = get_tensor_model_parallel_world_size() tp_size = get_tensor_model_parallel_world_size()
@@ -115,7 +115,7 @@ def norm_scale_shift(
``weight`` is the effective RMSNorm weight (K2 stores ``scale``, so callers ``weight`` is the effective RMSNorm weight (K2 stores ``scale``, so callers
pass ``scale + 1``), kept off the checkpoint so the identity load is unaffected. pass ``scale + 1``), kept off the checkpoint so the identity load is unaffected.
""" """
if x.is_cuda and x.shape[-1] % 256 == 0: if x.is_cuda and x.shape[-1] % 256 == 0 and x.shape[-1] <= 8192:
from sglang.kernels.ops.diffusion.cutedsl.scale_residual_norm_scale_shift import ( from sglang.kernels.ops.diffusion.cutedsl.scale_residual_norm_scale_shift import (
fused_norm_scale_shift, fused_norm_scale_shift,
) )
@@ -1,8 +1,8 @@
# Copied and adapted from: mossVG/mova/diffusion/models/wan_audio_dit.py # Copied and adapted from: mossVG/mova/diffusion/models/wan_audio_dit.py
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
# NOTE: This module reuses common functions from mova_video_dit.py to reduce code duplication. # NOTE: This module reuses common functions from mova_video_dit.py to reduce
# Audio-specific functions (precompute_freqs_cis_1d, legacy_precompute_freqs_cis_1d) are kept here. # code duplication. Audio-specific precompute_freqs_cis_1d is kept here.
import math import math
from typing import Any, Optional, Tuple from typing import Any, Optional, Tuple
@@ -27,23 +27,6 @@ from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
from .mova_video_dit import DiTBlock, precompute_freqs_cis, sinusoidal_embedding_1d from .mova_video_dit import DiTBlock, precompute_freqs_cis, sinusoidal_embedding_1d
# Audio-specific positional encoding functions
def legacy_precompute_freqs_cis_1d(
dim: int,
end: int = 16384,
theta: float = 10000.0,
base_tps=4.0,
target_tps=44100 / 2048,
):
s = float(base_tps) / float(target_tps)
# 1d rope precompute
f_freqs_cis = precompute_freqs_cis(dim - 2 * (dim // 3), end, theta, s)
# No positional encoding is applied to the remaining dimensions
no_freqs_cis = precompute_freqs_cis(dim // 3, end, theta, s)
no_freqs_cis = torch.ones_like(no_freqs_cis)
return f_freqs_cis, no_freqs_cis, no_freqs_cis
def precompute_freqs_cis_1d(dim: int, end: int = 16384, theta: float = 10000.0): def precompute_freqs_cis_1d(dim: int, end: int = 16384, theta: float = 10000.0):
f_freqs_cis = precompute_freqs_cis(dim, end, theta) f_freqs_cis = precompute_freqs_cis(dim, end, theta)
return f_freqs_cis.chunk(3, dim=-1) return f_freqs_cis.chunk(3, dim=-1)
@@ -82,15 +82,6 @@ def precompute_freqs_cis(
return freqs_cis return freqs_cis
def rope_apply(x, freqs, num_heads):
x = rearrange(x, "b s (n d) -> b s n d", n=num_heads)
x_out = torch.view_as_complex(
x.to(torch.float64).reshape(x.shape[0], x.shape[1], x.shape[2], -1, 2)
)
x_out = torch.view_as_real(x_out * freqs).flatten(2)
return x_out.to(x.dtype)
def rope_apply_head_dim(x, freqs, head_dim): def rope_apply_head_dim(x, freqs, head_dim):
x = rearrange(x, "b s (n d) -> b s n d", d=head_dim) x = rearrange(x, "b s (n d) -> b s n d", d=head_dim)
x_out = torch.view_as_complex( x_out = torch.view_as_complex(
@@ -127,14 +127,6 @@ def zimage_rmsnorm_scale(
return norm(x) * scale return norm(x) * scale
class SelectFirstElement(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return x[0]
class TimestepEmbedder(nn.Module): class TimestepEmbedder(nn.Module):
def __init__(self, out_size, mid_size=None, frequency_embedding_size=256): def __init__(self, out_size, mid_size=None, frequency_embedding_size=256):
super().__init__() super().__init__()
@@ -1,46 +0,0 @@
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
# SPDX-License-Identifier: Apache-2.0
# type: ignore
import os
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
class HunyuanClip(nn.Module):
"""
Hunyuan clip code copied from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/hunyuandit/pipeline_hunyuandit.py
hunyuan's clip used BertModel and BertTokenizer, so we copy it.
"""
def __init__(self, model_dir, max_length=77):
super().__init__()
self.max_length = max_length
self.tokenizer = BertTokenizer.from_pretrained(
os.path.join(model_dir, "tokenizer")
)
self.text_encoder = BertModel.from_pretrained(
os.path.join(model_dir, "clip_text_encoder")
)
@torch.no_grad
def forward(self, prompts, with_mask=True):
self.device = next(self.text_encoder.parameters()).device
text_inputs = self.tokenizer(
prompts,
padding="max_length",
max_length=self.max_length,
truncation=True,
return_attention_mask=True,
return_tensors="pt",
)
prompt_embeds = self.text_encoder(
text_inputs.input_ids.to(self.device),
attention_mask=(
text_inputs.attention_mask.to(self.device) if with_mask else None
),
)
return prompt_embeds.last_hidden_state, prompt_embeds.pooler_output
@@ -126,21 +126,6 @@ def _can_use_unmasked_causal_attention(
return bool(torch.all(attention_mask > 0).item()) return bool(torch.all(attention_mask > 0).item())
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
"""
This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep).
The hidden states go from (batch, num_key_value_heads, seqlen, head_dim) to
(batch, num_attention_heads, seqlen, head_dim)
"""
batch, num_key_value_heads, slen, head_dim = hidden_states.shape
if n_rep == 1:
return hidden_states
hidden_states = hidden_states[:, :, None, :, :].expand(
batch, num_key_value_heads, n_rep, slen, head_dim
)
return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
class MistralAttention(nn.Module): class MistralAttention(nn.Module):
"""Multi-headed attention from 'Attention Is All You Need' paper""" """Multi-headed attention from 'Attention Is All You Need' paper"""
@@ -71,7 +71,6 @@ from transformers.activations import ACT2FN
from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import ( from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import (
Qwen2_5_VisionRotaryEmbedding, Qwen2_5_VisionRotaryEmbedding,
Qwen2_5_VisionTransformerPretrainedModel, Qwen2_5_VisionTransformerPretrainedModel,
Qwen2_5_VLAttention,
Qwen2_5_VLCausalLMOutputWithPast, Qwen2_5_VLCausalLMOutputWithPast,
Qwen2_5_VLModelOutputWithPast, Qwen2_5_VLModelOutputWithPast,
Qwen2_5_VLRotaryEmbedding, Qwen2_5_VLRotaryEmbedding,
@@ -3,51 +3,12 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Adapted from vllm: https://github.com/vllm-project/vllm/blob/v0.7.3/vllm/model_executor/models/vision.py # Adapted from vllm: https://github.com/vllm-project/vllm/blob/v0.7.3/vllm/model_executor/models/vision.py
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
import torch import torch
from transformers import PretrainedConfig
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__) logger = init_logger(__name__)
_C = TypeVar("_C", bound=PretrainedConfig)
class VisionEncoderInfo(ABC, Generic[_C]):
def __init__(self, vision_config: _C) -> None:
super().__init__()
self.vision_config = vision_config
@abstractmethod
def get_num_image_tokens(
self,
*,
image_width: int,
image_height: int,
) -> int:
raise NotImplementedError
@abstractmethod
def get_max_image_tokens(self) -> int:
raise NotImplementedError
@abstractmethod
def get_image_size(self) -> int:
raise NotImplementedError
@abstractmethod
def get_patch_size(self) -> int:
raise NotImplementedError
@abstractmethod
def get_patch_grid_length(self) -> int:
raise NotImplementedError
def resolve_visual_encoder_outputs( def resolve_visual_encoder_outputs(
encoder_outputs: torch.Tensor | list[torch.Tensor], encoder_outputs: torch.Tensor | list[torch.Tensor],
@@ -123,10 +123,6 @@ class Platform:
def is_cuda(self) -> bool: def is_cuda(self) -> bool:
return self.is_cuda_static() return self.is_cuda_static()
@lru_cache(maxsize=1)
def is_npu(self) -> bool:
return self._enum == PlatformEnum.NPU
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def is_rocm(self) -> bool: def is_rocm(self) -> bool:
return self.is_rocm_static() return self.is_rocm_static()
@@ -163,7 +163,7 @@ async def run_async_client_warmup(
response = await forward(req) response = await forward(req)
if response.error is not None: if response.error is not None:
raise RuntimeError(response.error) raise RuntimeError(response.error)
except Exception as e: except Exception:
if fail_open: if fail_open:
logger.warning( logger.warning(
"Synthetic server warmup failed; continuing startup", exc_info=True "Synthetic server warmup failed; continuing startup", exc_info=True
@@ -0,0 +1,71 @@
import sys
from types import SimpleNamespace
from unittest.mock import patch
import pytest
import torch
from sglang.multimodal_gen.runtime.layers.layernorm import (
RMSNormScaleShift,
ScaleResidualRMSNormScaleShift,
)
_CUTEDSL_MODULE = "sglang.kernels.ops.diffusion.cutedsl.scale_residual_norm_scale_shift"
@pytest.mark.parametrize("hidden_size", [257, 8448])
def test_norm_scale_shift_cuda_falls_back_for_unsupported_hidden_size(hidden_size):
layer = RMSNormScaleShift(hidden_size)
x = torch.empty(1, 1, hidden_size)
shift = torch.empty(1, 1, hidden_size)
scale = torch.empty(1, 1, hidden_size)
expected = object()
with (
patch.object(layer, "forward_native", return_value=expected) as native,
pytest.warns(UserWarning, match="native fallback"),
):
actual = layer.forward_cuda(x, shift, scale)
assert actual is expected
native.assert_called_once_with(x, shift, scale)
@pytest.mark.parametrize("hidden_size", [257, 8448])
def test_scale_residual_cuda_falls_back_for_unsupported_hidden_size(hidden_size):
layer = ScaleResidualRMSNormScaleShift(hidden_size)
residual = torch.empty(1, 1, hidden_size)
x = torch.empty(1, 1, hidden_size)
gate = torch.empty(1, 1, hidden_size)
shift = torch.empty(1, 1, hidden_size)
scale = torch.empty(1, 1, hidden_size)
expected = object()
with (
patch.object(layer, "forward_native", return_value=expected) as native,
pytest.warns(UserWarning, match="native fallback"),
):
actual = layer.forward_cuda(residual, x, gate, shift, scale)
assert actual is expected
native.assert_called_once_with(residual, x, gate, shift, scale)
def test_norm_scale_shift_cuda_uses_cutedsl_for_supported_hidden_size(monkeypatch):
hidden_size = 256
layer = RMSNormScaleShift(hidden_size)
x = torch.empty(1, 1, hidden_size)
shift = torch.empty(1, 1, hidden_size)
scale = torch.empty(1, 1, hidden_size)
expected = object()
def fused_norm_scale_shift(*args):
return expected
monkeypatch.setitem(
sys.modules,
_CUTEDSL_MODULE,
SimpleNamespace(fused_norm_scale_shift=fused_norm_scale_shift),
)
assert layer.forward_cuda(x, shift, scale) is expected
@@ -0,0 +1,85 @@
import pytest
import torch
from sglang.kernels.ops.diffusion.triton.zimage_native_norm import (
zimage_rmsnorm_scale,
zimage_rmsnorm_tanh_residual,
)
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=8, stage="base-b-kernel-unit", runner_config="1-gpu-large")
EPS = 1e-5
def _native_bf16_rmsnorm(x: torch.Tensor, weight: torch.Tensor) -> torch.Tensor:
square = (x * x).to(torch.bfloat16)
mean_square = square.mean(dim=-1, keepdim=True).to(torch.bfloat16)
rstd = torch.rsqrt((mean_square + EPS).to(torch.bfloat16).float()).to(
torch.bfloat16
)
return ((x * rstd).to(torch.bfloat16) * weight).to(torch.bfloat16)
def test_zimage_native_norm_rejects_cpu_inputs():
x = torch.randn(2, 3, 16, dtype=torch.bfloat16)
weight = torch.randn(16, dtype=torch.bfloat16)
modulation = torch.randn(2, 1, 16, dtype=torch.bfloat16)
residual = torch.randn_like(x)
assert zimage_rmsnorm_scale(x, weight, modulation, EPS) is None
assert zimage_rmsnorm_tanh_residual(x, modulation, residual, weight, EPS) is None
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
@pytest.mark.parametrize("shape", [(1, 32, 2560), (2, 17, 256)])
def test_zimage_rmsnorm_scale_matches_native_bf16(shape):
torch.manual_seed(0)
batch, _, dim = shape
x = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
weight = torch.randn(dim, device="cuda", dtype=torch.bfloat16)
scale = torch.randn(batch, 1, dim, device="cuda", dtype=torch.bfloat16)
actual = zimage_rmsnorm_scale(x, weight, scale, EPS)
expected = (_native_bf16_rmsnorm(x, weight) * scale).to(torch.bfloat16)
assert actual is not None
torch.testing.assert_close(actual, expected, atol=2e-2, rtol=2e-2)
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
@pytest.mark.parametrize("shape", [(1, 32, 2560), (2, 17, 256)])
def test_zimage_rmsnorm_tanh_residual_matches_native_bf16(shape):
torch.manual_seed(0)
batch, _, dim = shape
x = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
gate = torch.randn(batch, 1, dim, device="cuda", dtype=torch.bfloat16)
residual = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
weight = torch.randn(dim, device="cuda", dtype=torch.bfloat16)
actual = zimage_rmsnorm_tanh_residual(x, gate, residual, weight, EPS)
norm = _native_bf16_rmsnorm(x, weight)
gated = (torch.tanh(gate.float()).to(torch.bfloat16) * norm).to(torch.bfloat16)
expected = (residual + gated).to(torch.bfloat16)
assert actual is not None
# Triton's exp-based tanh can differ slightly from torch.tanh in BF16.
torch.testing.assert_close(actual, expected, atol=4e-2, rtol=2e-2)
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
def test_zimage_native_norm_rejects_hidden_size_above_limit():
dim = 8448
x = torch.empty(1, 1, dim, device="cuda", dtype=torch.bfloat16)
weight = torch.empty(dim, device="cuda", dtype=torch.bfloat16)
modulation = torch.empty(1, 1, dim, device="cuda", dtype=torch.bfloat16)
residual = torch.empty_like(x)
assert zimage_rmsnorm_scale(x, weight, modulation, EPS) is None
assert zimage_rmsnorm_tanh_residual(x, modulation, residual, weight, EPS) is None
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__]))
@@ -46,6 +46,7 @@ EXPECTED = {
"moe.moe_align_block_size": {"aot", "jit"}, "moe.moe_align_block_size": {"aot", "jit"},
"quantization.nvfp4_gemm_swiglu_nvfp4_quant": {"cute_dsl"}, "quantization.nvfp4_gemm_swiglu_nvfp4_quant": {"cute_dsl"},
"kvcache.reshape_and_cache_flash": {"triton"}, "kvcache.reshape_and_cache_flash": {"triton"},
"diffusion.apply_group_norm_silu": {"triton"},
} }
_CPU = PlatformInfo(device_type="cpu") _CPU = PlatformInfo(device_type="cpu")