Repository navigation
Wan2.2 step: one-pass adaLN per frame, fused q/k norm+RoPE, no host syncs; Cosmos3: keep the dense attention plan #963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
6ac1bc6
Cosmos3: the batched-step warmup hands the step the rotary table
merceod 2e3ecc5
wan22: per-frame modulation through a one-pass adaLN component
7a7a2b1
wan22: fuse the self-attn q/k RMSNorm and RoPE
9749442
wan22: keep the denoise step free of host syncs
9a7b20b
dense attn: don't replan an unchanged layout
d4871e8
tests and docs for the DiT kernel components
ce81c45
Merge main into dit-step-b300
merceod 93bc40c
wan22: forward stages its tables with to_device_nonblocking
merceod 1af7dfe
wan22: k comes from the loop counter, time_index edge goes
merceod 4090bb6
wan22: CUDA test for the sync-free step
merceod f2b23ab
wan22: per-frame time embedding is rounding-close, not bitwise
merceod 3f49241
test: eager rope against the slice-write rotation it replaced
merceod 4b6b703
Merge main into dit-step-b300
Gaurav-Shah05 da66a31
test: hold the wan22 time embedder bitwise on per-token rows
Gaurav-Shah05 f109674
test: no-cfg wan22 trajectory is bitwise on per-token rows
Gaurav-Shah05 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| """adaLN for DiT blocks: an optional gated residual add, LayerNorm and the | ||
| shift/scale modulation in one pass over the hidden stream. | ||
|
|
||
| A DiT block normalizes its residual stream three times, each time right after | ||
| adding a branch output back into it, and the result feeds a bf16 GEMM: | ||
|
|
||
| x = (x.float() + y.float() * gate).to(x.dtype) # the residual add | ||
| out = (LN(x.float()) * (1 + scale) + shift).to(x.dtype) # adaLN | ||
|
|
||
| The modulation vectors (``shift``, ``scale``, ``gate``) are per group of | ||
| tokens: ``[B, G, D]``, group ``g`` covering tokens ``[g * S/G, (g+1) * S/G)``. | ||
| ``G = 1`` is a per-sample modulation (FLUX), ``G = frames`` a per-frame one | ||
| (Wan's per-token timestep is constant over a latent frame). | ||
|
|
||
| Compiled, Inductor lowers this to a looped two-pass reduction once ``D`` is | ||
| past its persistent-reduction width (1024), reading the row twice, so inside a | ||
| compiled region the kernel below does it in one: the row is read once into | ||
| registers, the mean and variance are taken there, and both outputs are | ||
| written once. Eager calls run the same math as plain PyTorch ops, which is | ||
| what the reference implementations hold bit-exact. The kernel rounds the | ||
| residual to the stream dtype before normalizing it, as eager does; its mean and | ||
| variance are reduced in a different order, so it agrees with eager to fp32 | ||
| rounding rather than bitwise. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import torch | ||
|
|
||
| try: | ||
| import triton | ||
| import triton.language as tl | ||
| from torch.library import triton_op, wrap_triton | ||
| except ImportError: # CPU-only installs | ||
| triton = None | ||
|
|
||
|
|
||
| def reference_ada_layer_norm( | ||
| x: torch.Tensor, | ||
| shift: torch.Tensor | None, | ||
| scale: torch.Tensor | None, | ||
| *, | ||
| eps: float, | ||
| weight: torch.Tensor | None = None, | ||
| bias: torch.Tensor | None = None, | ||
| residual: torch.Tensor | None = None, | ||
| gate: torch.Tensor | None = None, | ||
| ) -> tuple[torch.Tensor, torch.Tensor]: | ||
| """The eager formulation; see ``ada_layer_norm`` for the arguments.""" | ||
| batch, seq, dim = x.shape | ||
| groups = next((t.shape[1] for t in (shift, scale, gate) if t is not None), 1) | ||
|
|
||
| def grouped(t: torch.Tensor) -> torch.Tensor: | ||
| return t.unflatten(1, (groups, seq // groups)) | ||
|
|
||
| def per_group(t: torch.Tensor) -> torch.Tensor: | ||
| return t.unsqueeze(2) | ||
|
|
||
| xg = grouped(x) | ||
| if residual is not None: | ||
| branch = grouped(residual).float() | ||
| if gate is not None: | ||
| branch = branch * per_group(gate) | ||
| xg = (xg.float() + branch).to(x.dtype) | ||
| normed = torch.nn.functional.layer_norm( | ||
| xg.float(), (dim,), weight.float() if weight is not None else None, | ||
| bias.float() if bias is not None else None, eps, | ||
| ) | ||
| if scale is not None: | ||
| normed = normed * (1 + per_group(scale)) | ||
| if shift is not None: | ||
| normed = normed + per_group(shift) | ||
| return xg.flatten(1, 2), normed.to(x.dtype).flatten(1, 2) | ||
|
|
||
|
|
||
| if triton is not None: | ||
|
|
||
| @triton.jit | ||
| def _ada_layer_norm_kernel( | ||
| x_ptr, res_ptr, gate_ptr, shift_ptr, scale_ptr, w_ptr, b_ptr, x_out_ptr, out_ptr, | ||
| seq, tokens_per_group, groups, mod_batch_stride, mod_group_stride, dim, eps, | ||
| HAS_RES: tl.constexpr, HAS_GATE: tl.constexpr, HAS_SHIFT: tl.constexpr, HAS_SCALE: tl.constexpr, | ||
| HAS_W: tl.constexpr, HAS_B: tl.constexpr, BLOCK_D: tl.constexpr, | ||
| ): | ||
| row = tl.program_id(0).to(tl.int64) | ||
| cols = tl.arange(0, BLOCK_D) | ||
| mask = cols < dim | ||
| offs = row * dim + cols | ||
| batch = row // seq | ||
| group = (row % seq) // tokens_per_group | ||
| mod = batch * mod_batch_stride + group * mod_group_stride + cols | ||
|
|
||
| x = tl.load(x_ptr + offs, mask=mask, other=0.0) | ||
| if HAS_RES: | ||
| y = tl.load(res_ptr + offs, mask=mask, other=0.0).to(tl.float32) | ||
| if HAS_GATE: | ||
| y = y * tl.load(gate_ptr + mod, mask=mask, other=0.0) | ||
| x = (x.to(tl.float32) + y).to(x_ptr.dtype.element_ty) | ||
| tl.store(x_out_ptr + offs, x, mask=mask) | ||
| xf = x.to(tl.float32) | ||
| mean = tl.sum(xf, axis=0) / dim | ||
| diff = tl.where(mask, xf - mean, 0.0) | ||
| var = tl.sum(diff * diff, axis=0) / dim | ||
| # eps arrives as fp64 from a compiled caller; keep the row in fp32 | ||
| out = diff * tl.math.rsqrt(var + eps.to(tl.float32)) | ||
| if HAS_W: | ||
| out = out * tl.load(w_ptr + cols, mask=mask, other=0.0) | ||
| if HAS_B: | ||
| out = out + tl.load(b_ptr + cols, mask=mask, other=0.0) | ||
| if HAS_SCALE: | ||
| out = out * (1 + tl.load(scale_ptr + mod, mask=mask, other=0.0)) | ||
| if HAS_SHIFT: | ||
| out = out + tl.load(shift_ptr + mod, mask=mask, other=0.0) | ||
| tl.store(out_ptr + offs, out.to(out_ptr.dtype.element_ty), mask=mask) | ||
|
|
||
| def _launch(x, residual, gate, shift, scale, weight, bias, eps, x_out, out): | ||
| batch, seq, dim = x.shape | ||
| mods = [t for t in (gate, shift, scale) if t is not None] | ||
| groups = mods[0].shape[1] if mods else 1 | ||
| for t in mods: | ||
| assert t.shape == mods[0].shape and t.stride() == mods[0].stride() and t.stride(2) == 1 | ||
| mod_strides = (mods[0].stride(0), mods[0].stride(1)) if mods else (0, 0) | ||
| # absent tensors go in as None (constexpr), never as an alias of x: a | ||
| # compiled caller treats every pointer the kernel could store through as | ||
| # mutated and copies it | ||
| wrap_triton(_ada_layer_norm_kernel)[(batch * seq,)]( | ||
| x, residual, gate, shift, scale, weight, bias, | ||
| x_out, out, seq, seq // groups, groups, mod_strides[0], mod_strides[1], dim, eps, | ||
| HAS_RES=residual is not None, HAS_GATE=gate is not None, HAS_SHIFT=shift is not None, | ||
| HAS_SCALE=scale is not None, HAS_W=weight is not None, HAS_B=bias is not None, | ||
| BLOCK_D=triton.next_power_of_2(dim), num_warps=8, | ||
| ) | ||
|
|
||
| @triton_op("mstar::ada_layer_norm", mutates_args=()) | ||
| def _ada_layer_norm_op( | ||
| x: torch.Tensor, | ||
| shift: torch.Tensor | None, | ||
| scale: torch.Tensor | None, | ||
| weight: torch.Tensor | None, | ||
| bias: torch.Tensor | None, | ||
| eps: float, | ||
| ) -> torch.Tensor: | ||
| out = torch.empty_like(x) | ||
| _launch(x, None, None, shift, scale, weight, bias, eps, None, out) | ||
| return out | ||
|
|
||
| @triton_op("mstar::residual_ada_layer_norm", mutates_args=()) | ||
| def _residual_ada_layer_norm_op( | ||
| x: torch.Tensor, | ||
| residual: torch.Tensor, | ||
| gate: torch.Tensor | None, | ||
| shift: torch.Tensor | None, | ||
| scale: torch.Tensor | None, | ||
| weight: torch.Tensor | None, | ||
| bias: torch.Tensor | None, | ||
| eps: float, | ||
| ) -> tuple[torch.Tensor, torch.Tensor]: | ||
| x_out = torch.empty_like(x) | ||
| out = torch.empty_like(x) | ||
| _launch(x, residual, gate, shift, scale, weight, bias, eps, x_out, out) | ||
| return x_out, out | ||
|
|
||
|
|
||
| def ada_layer_norm( | ||
| x: torch.Tensor, | ||
| shift: torch.Tensor | None, | ||
| scale: torch.Tensor | None, | ||
| *, | ||
| eps: float, | ||
| weight: torch.Tensor | None = None, | ||
| bias: torch.Tensor | None = None, | ||
| residual: torch.Tensor | None = None, | ||
| gate: torch.Tensor | None = None, | ||
| ) -> tuple[torch.Tensor, torch.Tensor]: | ||
| """``(x', LN(x') * (1 + scale) + shift)`` with ``x' = x + residual * gate``. | ||
|
|
||
| ``x`` and ``residual`` are ``[B, S, D]`` in the stream dtype; ``shift``, | ||
| ``scale`` and ``gate`` are fp32 ``[B, G, D]`` with ``G`` dividing ``S`` | ||
| (any of them may be None, as may ``residual``); ``weight`` and ``bias`` are | ||
| the LayerNorm affine, if any. Math is fp32; both outputs are in ``x``'s | ||
| dtype, and ``x'`` is ``x`` itself when there is no residual. | ||
| """ | ||
| if triton is not None and x.is_cuda and torch.compiler.is_compiling(): | ||
| if residual is None: | ||
| return x, _ada_layer_norm_op(x.contiguous(), shift, scale, weight, bias, eps) | ||
| return _residual_ada_layer_norm_op( | ||
| x.contiguous(), residual.contiguous(), gate, shift, scale, weight, bias, eps, | ||
| ) | ||
| return reference_ada_layer_norm( | ||
| x, shift, scale, eps=eps, weight=weight, bias=bias, residual=residual, gate=gate, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| """Query/key RMSNorm followed by the interleaved-pair rotary embedding, for DiTs | ||
| whose q/k norm spans the whole projection (one RMSNorm across all heads, as in | ||
| Wan's ``rms_norm_across_heads``). | ||
|
|
||
| Eager, this is the model's own ``nn.RMSNorm`` and ``rope.apply_rotary_interleaved``. | ||
| Compiled, Inductor lowers the pair to several passes over each row (the norm's | ||
| reduction, then the rotated copy of the row, then the rotation), so inside a | ||
| compiled region one kernel does both q and k: each program holds a row in | ||
| registers, normalizes it, rounds the normalized row to the activation dtype | ||
| where the eager norm's output does, and rotates it in fp32. Its mean square is | ||
| reduced in a different order than the eager norm's, so the two agree to fp32 | ||
| rounding rather than bitwise. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import torch | ||
| from torch import nn | ||
|
|
||
| from mstar.model.components.diffusion.rope import apply_rotary_interleaved | ||
|
|
||
| try: | ||
| import triton | ||
| import triton.language as tl | ||
| from torch.library import triton_op, wrap_triton | ||
| except ImportError: # CPU-only installs | ||
| triton = None | ||
|
|
||
|
|
||
| if triton is not None: | ||
|
|
||
| @triton.jit | ||
| def _rms_norm_rope_kernel( | ||
| q_ptr, k_ptr, qw_ptr, kw_ptr, cos_ptr, sin_ptr, q_out_ptr, k_out_ptr, | ||
| seq, width, q_eps, k_eps, HEAD_DIM: tl.constexpr, BLOCK_PAIRS: tl.constexpr, | ||
| ): | ||
| row = tl.program_id(0).to(tl.int64) | ||
| if tl.program_id(1) == 0: | ||
| x_ptr, w_ptr, out_ptr, eps = q_ptr, qw_ptr, q_out_ptr, q_eps | ||
| else: | ||
| x_ptr, w_ptr, out_ptr, eps = k_ptr, kw_ptr, k_out_ptr, k_eps | ||
| pos = row % seq | ||
| pairs = tl.arange(0, BLOCK_PAIRS) | ||
| pmask = pairs < width // 2 | ||
| offs = pairs[:, None] * 2 + tl.arange(0, 2)[None, :] | ||
| mask = pmask[:, None] | ||
|
|
||
| x = tl.load(x_ptr + row * width + offs, mask=mask, other=0.0).to(tl.float32) | ||
| mean_sq = tl.sum(tl.sum(x * x, axis=1), axis=0) / width | ||
| # eps arrives as fp64 from a compiled caller; keep the row in fp32 | ||
| rstd = tl.math.rsqrt(mean_sq + eps.to(tl.float32)) | ||
| w = tl.load(w_ptr + offs, mask=mask, other=0.0).to(tl.float32) | ||
| x = (x * rstd * w).to(out_ptr.dtype.element_ty).to(tl.float32) | ||
|
|
||
| even, odd = tl.split(x) | ||
| # the tables repeat each frequency over its pair, so the even column serves both | ||
| col = pos * HEAD_DIM + (pairs % (HEAD_DIM // 2)) * 2 | ||
| cos = tl.load(cos_ptr + col, mask=pmask, other=0.0) | ||
| sin = tl.load(sin_ptr + col, mask=pmask, other=0.0) | ||
| out = tl.join(even * cos - odd * sin, odd * cos + even * sin) | ||
| tl.store(out_ptr + row * width + offs, out.to(out_ptr.dtype.element_ty), mask=mask) | ||
|
|
||
| @triton_op("mstar::rms_norm_rope_pair", mutates_args=()) | ||
| def _rms_norm_rope_op( | ||
| q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, | ||
| cos: torch.Tensor, sin: torch.Tensor, q_eps: float, k_eps: float, | ||
| ) -> tuple[torch.Tensor, torch.Tensor]: | ||
| batch, seq, width = q.shape | ||
| q_out, k_out = torch.empty_like(q), torch.empty_like(k) | ||
| wrap_triton(_rms_norm_rope_kernel)[(batch * seq, 2)]( | ||
| q, k, q_weight, k_weight, cos, sin, q_out, k_out, seq, width, q_eps, k_eps, | ||
| HEAD_DIM=cos.shape[-1], BLOCK_PAIRS=triton.next_power_of_2(width // 2), num_warps=8, | ||
| ) | ||
| return q_out, k_out | ||
|
|
||
|
|
||
| def rms_norm_rope( | ||
| q: torch.Tensor, k: torch.Tensor, q_norm: nn.RMSNorm, k_norm: nn.RMSNorm, | ||
| cos: torch.Tensor, sin: torch.Tensor, num_heads: int, | ||
| ) -> tuple[torch.Tensor, torch.Tensor]: | ||
| """``rope(q_norm(q)), rope(k_norm(k))`` for ``q, k [B, S, H * D]`` and fp32 | ||
| tables ``cos, sin [S, D]`` (each frequency repeated over its pair), returned | ||
| as ``[B, S, H, D]``. Both norms span the whole ``H * D`` row.""" | ||
|
|
||
| def eager(x: torch.Tensor, norm: nn.RMSNorm) -> torch.Tensor: | ||
| return apply_rotary_interleaved(norm(x).unflatten(2, (num_heads, -1)), cos, sin) | ||
|
|
||
| if triton is not None and q.is_cuda and torch.compiler.is_compiling(): | ||
| assert q.shape == k.shape and cos.shape == (q.shape[1], q.shape[2] // num_heads) | ||
| q_out, k_out = _rms_norm_rope_op( | ||
| q.contiguous(), k.contiguous(), q_norm.weight, k_norm.weight, cos.contiguous(), sin.contiguous(), | ||
| q_norm.eps, k_norm.eps, | ||
| ) | ||
| return q_out.unflatten(2, (num_heads, -1)), k_out.unflatten(2, (num_heads, -1)) | ||
| return eager(q, q_norm), eager(k, k_norm) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wrapper holds the schedule, so this check could live in
_RaggedPrefillBase._plan. The ragged managers behind FLUX.2-klein, Z-Image and LTX-2.5 replan the same wrappers every step, and a comment in theirplanalready asks for this cache. Moving the check there is for another PR, but what do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's the right home, and it should be its own PR. The wrapper holds the schedule either way, so the check moves down cleanly and FLUX.2-klein, Z-Image and LTX-2.5 get it for free. I'd want their numbers measured on that PR since it changes three more models' planning. I left this one as it is here.