[diffusion] Ideogram-4: fuse Qwen3-style RoPE and SwiGLU silu-mul (denoise -5.1% H100 / -4.7% H200, bit-exact) (#34314)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b1b8ce715b
commit
37c631ef23
@@ -0,0 +1,78 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Bit-exact fused ``silu(a) * b`` over two same-shape tensors.
|
||||
|
||||
For SwiGLU MLPs whose gate/up projections are separate GEMMs (so the
|
||||
concatenated-input ``silu_and_mul`` kernels don't apply without an extra
|
||||
full-width ``cat`` pass), this fuses the eager pair
|
||||
|
||||
``s = F.silu(a)`` (one kernel) ``out = s * b`` (another kernel)
|
||||
|
||||
into one pass while reproducing both aten bf16 rounding boundaries:
|
||||
``silu`` is a single aten op (fp32 opmath, one round), the multiply rounds
|
||||
once more. ``tl.sigmoid`` lowers to the same fp32 sigmoid aten uses, which
|
||||
makes the replication exact (verified ``torch.equal`` on 1M random bf16
|
||||
values); callers still verify the first call and fall back on mismatch.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
import triton # type: ignore
|
||||
import triton.language as tl # type: ignore
|
||||
|
||||
from sglang.kernels.ops.diffusion.triton.numerics import round_bf16_to_fp32
|
||||
from sglang.srt.utils.custom_op import register_custom_op
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _silu_mul_kernel(
|
||||
out_ptr,
|
||||
a_ptr,
|
||||
b_ptr,
|
||||
numel,
|
||||
BLOCK: tl.constexpr,
|
||||
):
|
||||
offs = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
|
||||
mask = offs < numel
|
||||
a = tl.load(a_ptr + offs, mask=mask, other=0.0).to(tl.float32)
|
||||
b = tl.load(b_ptr + offs, mask=mask, other=0.0).to(tl.float32)
|
||||
s = round_bf16_to_fp32(a * tl.sigmoid(a))
|
||||
tl.store(out_ptr + offs, s * b, mask=mask) # store rounds the multiply
|
||||
|
||||
|
||||
def can_use_fused_silu_mul(a: torch.Tensor, b: torch.Tensor) -> bool:
|
||||
return (
|
||||
a.dtype is torch.bfloat16
|
||||
and b.dtype is torch.bfloat16
|
||||
and a.is_cuda
|
||||
and b.is_cuda
|
||||
and a.device == b.device
|
||||
and a.shape == b.shape
|
||||
and a.is_contiguous()
|
||||
and b.is_contiguous()
|
||||
and a.numel() > 0
|
||||
)
|
||||
|
||||
|
||||
def _fake_silu_mul(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
||||
return torch.empty_like(a)
|
||||
|
||||
|
||||
@register_custom_op(
|
||||
op_name="triton_fused_silu_mul_bitexact",
|
||||
mutates_args=[],
|
||||
fake_impl=_fake_silu_mul,
|
||||
)
|
||||
def fused_silu_mul_bitexact(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
||||
"""``silu(a) * b``, bit-exact vs the eager two-kernel chain."""
|
||||
out = torch.empty_like(a)
|
||||
numel = a.numel()
|
||||
with torch.cuda.device(a.device):
|
||||
_silu_mul_kernel[(triton.cdiv(numel, 1024),)](
|
||||
out,
|
||||
a,
|
||||
b,
|
||||
numel,
|
||||
BLOCK=1024,
|
||||
)
|
||||
return out
|
||||
@@ -7,12 +7,23 @@ import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from sglang.kernels.ops.diffusion.bitexact_gate import (
|
||||
BitExactFusionGate,
|
||||
tensors_equal,
|
||||
)
|
||||
from sglang.kernels.ops.diffusion.fused_gate_rmsnorm import (
|
||||
fused_gate_rmsnorm_active,
|
||||
fused_rmsnorm_scale,
|
||||
fused_rmsnorm_tanh_residual,
|
||||
mark_fused_gate_rmsnorm_site,
|
||||
)
|
||||
from sglang.kernels.ops.diffusion.triton.rope_rotate_half_bitexact import (
|
||||
fused_rope_rotate_half_bitexact,
|
||||
)
|
||||
from sglang.kernels.ops.diffusion.triton.silu_mul_bitexact import (
|
||||
can_use_fused_silu_mul,
|
||||
fused_silu_mul_bitexact,
|
||||
)
|
||||
from sglang.multimodal_gen.configs.models.dits.ideogram import Ideogram4DiTConfig
|
||||
from sglang.multimodal_gen.configs.models.fsdp import is_layer
|
||||
from sglang.multimodal_gen.runtime.distributed import (
|
||||
@@ -48,10 +59,115 @@ from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload im
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import BaseDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
OUTPUT_IMAGE_INDICATOR = 2
|
||||
LLM_TOKEN_INDICATOR = 3
|
||||
|
||||
_IDEOGRAM_ROPE = BitExactFusionGate("Ideogram fused RoPE")
|
||||
_IDEOGRAM_SWIGLU = BitExactFusionGate("Ideogram fused SiLU-mul")
|
||||
|
||||
|
||||
def _can_use_fused_rope(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
cos: torch.Tensor,
|
||||
sin: torch.Tensor,
|
||||
) -> bool:
|
||||
# cos/sin are full-span (B, S, 1, D) rows broadcast over heads.
|
||||
expected = (q.shape[0], q.shape[1], 1, q.shape[-1])
|
||||
return (
|
||||
q.dtype is torch.bfloat16
|
||||
and k.dtype is torch.bfloat16
|
||||
and q.is_cuda
|
||||
and q.dim() == 4
|
||||
and q.is_contiguous()
|
||||
and k.shape == q.shape
|
||||
and k.is_contiguous()
|
||||
and k.device == q.device
|
||||
and cos.dtype is torch.bfloat16
|
||||
and sin.dtype is torch.bfloat16
|
||||
and cos.device == q.device
|
||||
and sin.device == q.device
|
||||
and cos.shape == expected
|
||||
and sin.shape == expected
|
||||
and cos.is_contiguous()
|
||||
and sin.is_contiguous()
|
||||
and q.shape[-1] % 2 == 0
|
||||
)
|
||||
|
||||
|
||||
def _ideogram_rope(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
cos: torch.Tensor,
|
||||
sin: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Single-kernel Qwen3-style RoPE per projection, bit-exact vs eager.
|
||||
|
||||
The eager chain is ~6 kernels per projection (four muls, two add/subs,
|
||||
plus the sliced ``empty_like`` fills); the Triton kernel reproduces every
|
||||
aten bf16 rounding boundary (``round(round(q1*cos1) + round(-q2*sin1))``
|
||||
equals the eager ``round(round(q1*cos1) - round(q2*sin1))`` exactly), so
|
||||
it mounts by default with first-call self-verification.
|
||||
"""
|
||||
verified = _IDEOGRAM_ROPE.verified
|
||||
if (
|
||||
not _IDEOGRAM_ROPE.disabled
|
||||
and _can_use_fused_rope(q, k, cos, sin)
|
||||
and (verified or _IDEOGRAM_ROPE.can_attempt_once())
|
||||
):
|
||||
try:
|
||||
cos_rows = cos.reshape(-1, cos.shape[-1])
|
||||
sin_rows = sin.reshape(-1, sin.shape[-1])
|
||||
q_fused = fused_rope_rotate_half_bitexact(q, cos_rows, sin_rows)
|
||||
k_fused = fused_rope_rotate_half_bitexact(k, cos_rows, sin_rows)
|
||||
except Exception as exc:
|
||||
_IDEOGRAM_ROPE.on_exception(exc, logger=logger)
|
||||
else:
|
||||
if verified:
|
||||
return q_fused, k_fused
|
||||
return _IDEOGRAM_ROPE.accept_or_fallback(
|
||||
(q_fused, k_fused),
|
||||
qwen3_apply_rotary_pos_emb(q, k, cos, sin),
|
||||
equal=tensors_equal,
|
||||
logger=logger,
|
||||
mismatch_msg=(
|
||||
"Ideogram fused RoPE fast path is not bit-exact on this "
|
||||
"platform; falling back to eager"
|
||||
),
|
||||
)
|
||||
return qwen3_apply_rotary_pos_emb(q, k, cos, sin)
|
||||
|
||||
|
||||
def _ideogram_swiglu(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
||||
"""``silu(a) * b`` in one kernel, bit-exact vs the eager pair."""
|
||||
verified = _IDEOGRAM_SWIGLU.verified
|
||||
if (
|
||||
not _IDEOGRAM_SWIGLU.disabled
|
||||
and can_use_fused_silu_mul(a, b)
|
||||
and (verified or _IDEOGRAM_SWIGLU.can_attempt_once())
|
||||
):
|
||||
try:
|
||||
out = fused_silu_mul_bitexact(a, b)
|
||||
except Exception as exc:
|
||||
_IDEOGRAM_SWIGLU.on_exception(exc, logger=logger)
|
||||
else:
|
||||
if verified:
|
||||
return out
|
||||
return _IDEOGRAM_SWIGLU.accept_or_fallback(
|
||||
out,
|
||||
F.silu(a) * b,
|
||||
logger=logger,
|
||||
mismatch_msg=(
|
||||
"Ideogram fused SiLU-mul fast path is not bit-exact on "
|
||||
"this platform; falling back to eager"
|
||||
),
|
||||
)
|
||||
return F.silu(a) * b
|
||||
|
||||
|
||||
class Ideogram4RMSNorm(nn.Module):
|
||||
def __init__(self, dim: int, eps: float = 1e-6) -> None:
|
||||
@@ -255,7 +371,7 @@ class Ideogram4Attention(nn.Module):
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
q = self.norm_q(q)
|
||||
k = self.norm_k(k)
|
||||
q, k = qwen3_apply_rotary_pos_emb(q, k, cos, sin)
|
||||
q, k = _ideogram_rope(q, k, cos, sin)
|
||||
out = self.attn(q, k, v, attn_mask=attn_mask, attn_mask_meta=attn_mask_meta)
|
||||
out = out.reshape(batch_size, seq_len, self.local_num_heads * self.head_dim)
|
||||
return self.o(out)
|
||||
@@ -299,7 +415,7 @@ class Ideogram4MLP(nn.Module):
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.w2(F.silu(self.w1(x)) * self.w3(x))
|
||||
return self.w2(_ideogram_swiglu(self.w1(x), self.w3(x)))
|
||||
|
||||
|
||||
def _norm_scale(
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
qwen3_apply_rotary_pos_emb,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.ideogram import (
|
||||
_ideogram_rope,
|
||||
_ideogram_swiglu,
|
||||
)
|
||||
|
||||
|
||||
def _make_qk_cos_sin(batch, seq, heads, head_dim, device):
|
||||
q = torch.randn(batch, seq, heads, head_dim, device=device, dtype=torch.bfloat16)
|
||||
k = torch.randn(batch, seq, heads, head_dim, device=device, dtype=torch.bfloat16)
|
||||
# Qwen3-style full-span duplicated-halves cos/sin, row-broadcast over heads.
|
||||
half = head_dim // 2
|
||||
freqs = torch.randn(batch, seq, 1, half, device=device) * 3.0
|
||||
emb = torch.cat((freqs, freqs), dim=-1)
|
||||
cos = emb.cos().to(torch.bfloat16)
|
||||
sin = emb.sin().to(torch.bfloat16)
|
||||
return q, k, cos, sin
|
||||
|
||||
|
||||
class TestIdeogramRopeFusion(unittest.TestCase):
|
||||
@unittest.skipUnless(torch.cuda.is_available(), "requires CUDA")
|
||||
def test_fused_rope_matches_eager(self):
|
||||
torch.manual_seed(0)
|
||||
for batch, seq, heads, head_dim in [(2, 257, 16, 128), (1, 64, 3, 64)]:
|
||||
q, k, cos, sin = _make_qk_cos_sin(batch, seq, heads, head_dim, "cuda")
|
||||
q_ref, k_ref = qwen3_apply_rotary_pos_emb(q, k, cos, sin)
|
||||
q_fused, k_fused = _ideogram_rope(q, k, cos, sin)
|
||||
self.assertTrue(torch.equal(q_ref, q_fused))
|
||||
self.assertTrue(torch.equal(k_ref, k_fused))
|
||||
|
||||
def test_eager_fallback_cpu(self):
|
||||
torch.manual_seed(1)
|
||||
q, k, cos, sin = _make_qk_cos_sin(1, 9, 2, 32, torch.device("cpu"))
|
||||
q_ref, k_ref = qwen3_apply_rotary_pos_emb(q, k, cos, sin)
|
||||
q_out, k_out = _ideogram_rope(q, k, cos, sin)
|
||||
self.assertTrue(torch.equal(q_ref, q_out))
|
||||
self.assertTrue(torch.equal(k_ref, k_out))
|
||||
|
||||
|
||||
class TestIdeogramSwigluFusion(unittest.TestCase):
|
||||
@unittest.skipUnless(torch.cuda.is_available(), "requires CUDA")
|
||||
def test_fused_swiglu_matches_eager(self):
|
||||
torch.manual_seed(0)
|
||||
a = torch.randn(2, 513, 3584, device="cuda", dtype=torch.bfloat16) * 4
|
||||
b = torch.randn(2, 513, 3584, device="cuda", dtype=torch.bfloat16)
|
||||
self.assertTrue(torch.equal(F.silu(a) * b, _ideogram_swiglu(a, b)))
|
||||
|
||||
def test_eager_fallback_cpu(self):
|
||||
torch.manual_seed(1)
|
||||
a = torch.randn(3, 8, dtype=torch.float32)
|
||||
b = torch.randn(3, 8, dtype=torch.float32)
|
||||
self.assertTrue(torch.equal(F.silu(a) * b, _ideogram_swiglu(a, b)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user