Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

907 lines
34 KiB
Python

# SPDX-License-Identifier: Apache-2.0
import os
from typing import Callable, List, Optional, Tuple
import torch
import torch.nn as nn
from sglang.multimodal_gen.configs.models.dits.sana_wm import SanaWMConfig
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
LayerwiseOffloadableModuleMixin,
)
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
# Re-exported for back-compat: callers import these names from this module path.
from sglang.multimodal_gen.runtime.models.dits.sana_wm_components import ( # noqa: F401
_CACHE_TYPE_CONCAT,
_CACHE_TYPE_STATE,
_INT32_SAFE_CONV_ELEMENTS,
_NUM_STREAM_CACHE_SLOTS,
_SLOT_CAM_K,
_SLOT_CAM_V,
_SLOT_FFN_TCONV,
_SLOT_K,
_SLOT_SHORTCONV,
_SLOT_TYPE_FLAG,
_SLOT_V,
BidirectionalGDNUCPESinglePathLiteLA,
CaptionEmbedder,
GLUMBConvTemp,
MultiHeadCrossAttention,
PatchEmbedMS3D,
T2IFinalLayer,
TimestepEmbedder,
WanRotaryPosEmbed,
_apply_block_diagonal,
_apply_complex_rope,
_apply_ray_projmat,
_apply_rotary_emb_bhnd,
_apply_rotary_emb_dn,
_bidirectional_short_conv,
_build_ucpe_apply_fns,
_compute_fov_from_focal,
_compute_frame_gates,
_ConvLayer,
_downscale_to_reference_rms,
_flip_and_shift,
_gdn_chunk_scan_forward,
_gdn_scan_bidirectional,
_gdn_scan_cached,
_gdn_scan_forward,
_gdn_scan_forward_stateful,
_invert_SE3,
_log_sana_wm_triton_cam_gdn_fallback,
_log_sana_wm_triton_gdn_fallback,
_RMSNorm,
_sana_wm_chunk_boundaries_for_attention,
_sana_wm_chunk_index_from_chunk_size,
_sana_wm_chunked_attention,
_sana_wm_normalize_chunk_index,
_sana_wm_padded_scale,
_sana_wm_sdpa,
_ShortConvolution,
_single_path_delta_chunk_scan_forward,
_single_path_delta_scan_bidirectional,
_single_path_delta_scan_cached,
_single_path_delta_scan_forward,
_single_path_delta_scan_forward_stateful,
_sinusoidal_timestep_embedding,
_slice_rope_for_cam,
_slice_rope_to_current_chunk,
_temporal_short_conv_cached,
_tensor_cache_key,
_UpstreamMlp,
compute_chunk_plucker,
process_camera_conditions_ucpe,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.sana_wm import (
parity_probe,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
class SanaWMBlock(nn.Module):
"""One transformer block of SANA-WM."""
def __init__(
self,
hidden_size: int,
num_heads: int,
head_dim: int,
mlp_ratio: float,
t_kernel_size: int,
qk_norm: bool,
cross_norm: bool,
conv_kernel_size: int,
k_conv_only: bool,
softmax_main: bool,
use_chunk_plucker_post_attn: bool,
chunk_size: Optional[int] = None,
chunk_split_strategy: str = "uniform",
update_rule: str = "torch_chunk",
cam_update_rule: str = "torch_chunk",
chunk_gdn_chunk_size: int = 21,
use_chunked_softmax_attention: bool = False,
gdn_backend: str = "auto",
) -> None:
super().__init__()
self.softmax_main = softmax_main
self.chunk_size = chunk_size
self.chunk_split_strategy = chunk_split_strategy
self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.attn = BidirectionalGDNUCPESinglePathLiteLA(
in_dim=hidden_size,
heads=num_heads,
head_dim=head_dim,
qk_norm=qk_norm,
conv_kernel_size=conv_kernel_size,
k_conv_only=k_conv_only,
softmax_main=softmax_main,
update_rule=update_rule,
cam_update_rule=cam_update_rule,
chunk_gdn_chunk_size=chunk_gdn_chunk_size,
use_chunked_softmax_attention=use_chunked_softmax_attention,
gdn_backend=gdn_backend,
)
self.cross_attn = MultiHeadCrossAttention(
d_model=hidden_size,
num_heads=num_heads,
qk_norm=cross_norm,
)
self.mlp = GLUMBConvTemp(
in_features=hidden_size,
hidden_features=int(hidden_size * mlp_ratio),
t_kernel_size=t_kernel_size,
)
self.scale_shift_table = nn.Parameter(
torch.randn(6, hidden_size) / hidden_size**0.5
)
if use_chunk_plucker_post_attn:
self.plucker_proj = nn.Linear(hidden_size, hidden_size, bias=True)
nn.init.zeros_(self.plucker_proj.weight)
nn.init.zeros_(self.plucker_proj.bias)
else:
self.plucker_proj = None
@staticmethod
def _modulate(
x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor
) -> torch.Tensor:
return x * (1 + scale) + shift
@staticmethod
def _reshape_framewise_modulation(
x: torch.Tensor,
num_frames: int,
) -> tuple[torch.Tensor, int]:
B, N, C = x.shape
tokens_per_frame = N // num_frames
return x.reshape(B, num_frames, tokens_per_frame, C), tokens_per_frame
def forward(
self,
x: torch.Tensor, # (B, N, D)
y: torch.Tensor, # (B, L, D) text embeds
t: torch.Tensor, # (B, 6*D) AdaLN-single
HW: Tuple[int, int, int],
rotary_emb: Optional[torch.Tensor],
prope_fns: Optional[Tuple[Callable, Callable, Callable]],
plucker_emb: Optional[torch.Tensor],
mask: Optional[torch.Tensor],
chunk_size: Optional[int] = None,
chunk_split_strategy: Optional[str] = None,
chunk_index: Optional[List[int]] = None,
) -> torch.Tensor:
B = x.shape[0]
if t.dim() == 2:
num_frames = None
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.scale_shift_table[None] + t.reshape(B, 6, -1)
).chunk(6, dim=1)
else:
num_frames = t.reshape(B, -1, 6, t.shape[-1] // 6).shape[1]
t = t.reshape(B, num_frames, 6, -1)
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.scale_shift_table[None, None, :, :] + t
).chunk(6, dim=2)
# Self-attention with UCPE camera branch
if num_frames is None:
x_in = self._modulate(self.norm1(x), shift_msa, scale_msa)
else:
x_norm, tokens_per_frame = self._reshape_framewise_modulation(
self.norm1(x), num_frames
)
x_in = self._modulate(x_norm, shift_msa, scale_msa).reshape_as(x)
attn_out = self.attn(
x_in,
HW=HW,
rotary_emb=rotary_emb,
prope_fns=prope_fns,
chunk_size=self.chunk_size if chunk_size is None else chunk_size,
chunk_split_strategy=(
self.chunk_split_strategy
if chunk_split_strategy is None
else chunk_split_strategy
),
chunk_index=chunk_index,
)
if num_frames is None:
x = x + gate_msa * attn_out
else:
attn_out = attn_out.reshape(B, num_frames, tokens_per_frame, -1)
x = x + (gate_msa * attn_out).reshape_as(x)
# Plücker post-attn injection (zero-init linear)
if self.plucker_proj is not None and plucker_emb is not None:
x = x + self.plucker_proj(plucker_emb)
# Cross-attention
x = x + self.cross_attn(x, y, mask=mask)
# FFN
if num_frames is None:
x_in = self._modulate(self.norm2(x), shift_mlp, scale_mlp)
x = x + gate_mlp * self.mlp(x_in, HW=HW)
else:
x_norm, tokens_per_frame = self._reshape_framewise_modulation(
self.norm2(x), num_frames
)
x_in = self._modulate(x_norm, shift_mlp, scale_mlp).reshape_as(x)
mlp_out = self.mlp(x_in, HW=HW).reshape(B, num_frames, tokens_per_frame, -1)
x = x + (gate_mlp * mlp_out).reshape_as(x)
return x
def forward_long(
self,
x: torch.Tensor,
y: torch.Tensor,
t: torch.Tensor,
HW: Tuple[int, int, int],
rotary_emb: Optional[torch.Tensor],
prope_fns: Optional[Tuple[Callable, Callable, Callable]],
plucker_emb: Optional[torch.Tensor],
mask: Optional[torch.Tensor],
*,
kv_cache: list,
save_kv_cache: bool,
) -> Tuple[torch.Tensor, list]:
"""Streaming counterpart of ``forward``: threads the per-block 10-slot ``kv_cache`` through cached attention + FFN."""
B = x.shape[0]
if t.dim() == 2:
num_frames = None
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.scale_shift_table[None] + t.reshape(B, 6, -1)
).chunk(6, dim=1)
else:
num_frames = t.reshape(B, -1, 6, t.shape[-1] // 6).shape[1]
t = t.reshape(B, num_frames, 6, -1)
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.scale_shift_table[None, None, :, :] + t
).chunk(6, dim=2)
if num_frames is None:
x_in = self._modulate(self.norm1(x), shift_msa, scale_msa)
else:
x_norm, tokens_per_frame = self._reshape_framewise_modulation(
self.norm1(x), num_frames
)
x_in = self._modulate(x_norm, shift_msa, scale_msa).reshape_as(x)
attn_out, kv_cache = self.attn.forward_long(
x_in,
HW=HW,
rotary_emb=rotary_emb,
prope_fns=prope_fns,
kv_cache=kv_cache,
save_kv_cache=save_kv_cache,
)
if num_frames is None:
x = x + gate_msa * attn_out
else:
attn_out = attn_out.reshape(B, num_frames, tokens_per_frame, -1)
x = x + (gate_msa * attn_out).reshape_as(x)
if self.plucker_proj is not None and plucker_emb is not None:
x = x + self.plucker_proj(plucker_emb)
x = x + self.cross_attn(x, y, mask=mask)
if num_frames is None:
x_in = self._modulate(self.norm2(x), shift_mlp, scale_mlp)
else:
x_norm, tokens_per_frame = self._reshape_framewise_modulation(
self.norm2(x), num_frames
)
x_in = self._modulate(x_norm, shift_mlp, scale_mlp).reshape_as(x)
# GLUMBConvTemp returns a tuple whenever the streaming path is active
# (ffn_tail set OR save requested); branch on tuple-ness, never on
# save_kv_cache alone (a read-only pass with a populated slot 9 still
# returns a tuple).
mlp_out = self.mlp(
x_in,
HW=HW,
ffn_tail=kv_cache[_SLOT_FFN_TCONV],
save_ffn_tail=save_kv_cache,
)
if isinstance(mlp_out, tuple):
mlp_out, ffn_tail = mlp_out
if save_kv_cache:
kv_cache[_SLOT_FFN_TCONV] = ffn_tail
if num_frames is None:
x = x + gate_mlp * mlp_out
else:
mlp_out = mlp_out.reshape(B, num_frames, tokens_per_frame, -1)
x = x + (gate_mlp * mlp_out).reshape_as(x)
return x, kv_cache
class SanaWMTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
"""SANA-WM 2.6B TI2V world model.
Forward inputs:
hidden_states: (B, C, T, H, W) 128-ch LTX-2 latent
encoder_hidden_states: (B, L, 2304) Gemma-2 embeddings
timestep: (B,)
encoder_attention_mask: (B, L) optional bool
camera_conditions: (B, T, 20) latent-frame raymap:
16 c2w + (fx,fy,cx,cy)
chunk_plucker: (B, 48, T, H, W) optional, computed
from camera_conditions
if absent.
Returns: ``(B, C, T, H, W)`` predicted velocity / noise.
"""
_fsdp_shard_conditions = SanaWMConfig()._fsdp_shard_conditions
_compile_conditions = SanaWMConfig()._compile_conditions
_supported_attention_backends = SanaWMConfig()._supported_attention_backends
param_names_mapping = SanaWMConfig().param_names_mapping
reverse_param_names_mapping = SanaWMConfig().reverse_param_names_mapping
lora_param_names_mapping: dict = {}
def __init__(self, config: SanaWMConfig, hf_config=None, **kwargs) -> None:
super().__init__(config, hf_config=hf_config or {}, **kwargs)
arch = config.arch_config
self.patch_size = (arch.patch_size_t, arch.patch_size, arch.patch_size)
self.inner_dim = arch.num_attention_heads * arch.attention_head_dim
self.hidden_size = self.inner_dim
self.num_attention_heads = arch.num_attention_heads
self.attention_head_dim = arch.attention_head_dim
self.out_channels = arch.out_channels
self.num_channels_latents = arch.num_channels_latents
self.vae_temporal_stride = arch.vae_temporal_stride
self.timestep_norm_scale_factor = getattr(
arch, "timestep_norm_scale_factor", 1.0
)
# --- Embedders ---
self.x_embedder = PatchEmbedMS3D(
self.patch_size,
arch.in_channels,
self.inner_dim,
bias=True,
)
self.t_embedder = TimestepEmbedder(self.inner_dim, frequency_embedding_size=256)
self.t_block = nn.Sequential(
nn.SiLU(),
nn.Linear(self.inner_dim, 6 * self.inner_dim, bias=True),
)
self.y_embedder = CaptionEmbedder(
in_channels=arch.caption_channels,
hidden_size=self.inner_dim,
token_num=arch.model_max_length,
)
self.y_norm = bool(getattr(arch, "y_norm", True))
self.attention_y_norm = _RMSNorm(
self.inner_dim,
scale_factor=getattr(arch, "y_norm_scale_factor", 1.0),
eps=getattr(arch, "y_norm_eps", 1e-5),
)
# 3-channel raymap embedder -- kept for state_dict compatibility but
# only invoked when ``use_chunk_plucker_post_attn`` is False.
# When ``True`` (the case for the released checkpoint) the absmap
# path is skipped entirely.
self.raymap_embedder = PatchEmbedMS3D(
self.patch_size,
3,
self.inner_dim,
bias=True,
)
# 48-channel plucker embedder (chunk-packed)
if arch.use_chunk_plucker_post_attn or arch.use_chunk_plucker_input:
self.plucker_embedder = PatchEmbedMS3D(
self.patch_size,
arch.chunk_plucker_channels,
self.inner_dim,
bias=True,
)
nn.init.zeros_(self.plucker_embedder.proj.weight)
nn.init.zeros_(self.plucker_embedder.proj.bias)
else:
self.plucker_embedder = None
self.use_chunk_plucker_post_attn = arch.use_chunk_plucker_post_attn
self.use_chunk_plucker_input = arch.use_chunk_plucker_input
self.chunk_size = getattr(arch, "chunk_size", None)
self.chunk_split_strategy = getattr(arch, "chunk_split_strategy", "uniform")
# --- RoPE ---
self.rope = WanRotaryPosEmbed(
attention_head_dim=arch.linear_head_dim,
patch_size=self.patch_size,
max_seq_len=1024,
)
# --- Transformer blocks ---
depth = arch.num_layers
self.softmax_every_n = arch.softmax_every_n
softmax_idx = set(
i
for i in range(depth)
if arch.softmax_every_n > 0 and (i + 1) % arch.softmax_every_n == 0
)
self.softmax_block_indices = tuple(sorted(softmax_idx))
self.blocks = nn.ModuleList(
[
SanaWMBlock(
hidden_size=self.inner_dim,
num_heads=arch.num_attention_heads,
head_dim=arch.linear_head_dim,
mlp_ratio=arch.mlp_ratio,
t_kernel_size=arch.t_kernel_size,
qk_norm=arch.qk_norm,
cross_norm=arch.cross_norm,
conv_kernel_size=arch.conv_kernel_size,
k_conv_only=arch.k_conv_only,
softmax_main=(i in softmax_idx),
use_chunk_plucker_post_attn=(
arch.use_chunk_plucker_post_attn
and (
arch.chunk_plucker_post_attn_blocks < 0
or i < arch.chunk_plucker_post_attn_blocks
)
),
chunk_size=self.chunk_size,
chunk_split_strategy=self.chunk_split_strategy,
update_rule=getattr(arch, "update_rule", "torch_chunk"),
cam_update_rule=getattr(arch, "cam_update_rule", "torch_chunk"),
chunk_gdn_chunk_size=getattr(arch, "chunk_gdn_chunk_size", 21),
use_chunked_softmax_attention=getattr(
arch, "use_chunked_softmax_attention", False
),
gdn_backend=getattr(arch, "gdn_backend", "auto"),
)
for i in range(depth)
]
)
self.final_layer = T2IFinalLayer(
self.inner_dim, self.patch_size, self.out_channels
)
# Cache RoPE freqs per shape -- avoids recomputation across denoising
# steps with constant latent shapes.
self._freqs_cache: dict = {}
self._ucpe_apply_fns_cache: Optional[
Tuple[Tuple, torch.Tensor, Tuple[Callable, Callable, Callable]]
] = None
self._plucker_emb_cache: Optional[Tuple[Tuple, torch.Tensor, torch.Tensor]] = (
None
)
# FSDP shard targets
self.layer_names = ["blocks"]
def post_load_weights(self) -> None:
# FSDP loader initializes the model on meta and only materializes
# tensors that appear in the checkpoint. WanRotaryPosEmbed._freqs is a
# derived, non-persistent constant, so recompute it deterministically.
for module in self.modules():
if isinstance(module, WanRotaryPosEmbed):
if module._freqs.is_meta:
module._init_freqs_buffer()
# ------------------------------------------------------------------ #
# Forward
# ------------------------------------------------------------------ #
def _get_freqs(self, T: int, H: int, W: int, device: torch.device) -> torch.Tensor:
key = (T, H, W, str(device))
if key not in self._freqs_cache:
self._freqs_cache[key] = self.rope((T, H, W), device)
return self._freqs_cache[key]
def _get_freqs_window(
self,
start: int,
end: int,
H: int,
W: int,
device: torch.device,
frame_index: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""RoPE freqs for a streaming chunk at GLOBAL frame positions.
``frame_index`` (per-token global positions) overrides ``(start, end)``;
the count branch is cached, the tensor branch is computed fresh.
"""
if frame_index is not None:
return self.rope((end - start, H, W), device, frame_index=frame_index)
key = ("win", int(start), int(end), H, W, str(device))
if key not in self._freqs_cache:
self._freqs_cache[key] = self.rope(((int(start), int(end)), H, W), device)
return self._freqs_cache[key]
def _get_ucpe_apply_fns(
self,
camera_conditions: torch.Tensor,
*,
HW: Tuple[int, int, int],
freqs: torch.Tensor,
) -> Tuple[Callable, Callable, Callable]:
head_dim = self.attention_head_dim
if torch.is_grad_enabled():
raymats = process_camera_conditions_ucpe(
camera_conditions,
HW=HW,
patch_size=self.patch_size,
)
raymats_flat = raymats.reshape(camera_conditions.shape[0], -1, 4, 4)
return _build_ucpe_apply_fns(head_dim, raymats_flat, freqs)
key = (
"ucpe",
HW,
self.patch_size,
head_dim,
_tensor_cache_key(camera_conditions),
_tensor_cache_key(freqs),
)
cached = self._ucpe_apply_fns_cache
if cached is not None and cached[0] == key:
return cached[2]
raymats = process_camera_conditions_ucpe(
camera_conditions,
HW=HW,
patch_size=self.patch_size,
)
raymats_flat = raymats.reshape(camera_conditions.shape[0], -1, 4, 4)
prope_fns = _build_ucpe_apply_fns(head_dim, raymats_flat, freqs)
self._ucpe_apply_fns_cache = (key, camera_conditions, prope_fns)
return prope_fns
def _get_plucker_emb(
self,
chunk_plucker: torch.Tensor,
*,
latent_token_count: int,
) -> torch.Tensor:
if self.plucker_embedder is None:
raise ValueError("SANA-WM plucker_embedder is not initialized.")
weight = self.plucker_embedder.proj.weight
bias = self.plucker_embedder.proj.bias
key = (
"plucker_emb",
latent_token_count,
self.patch_size,
_tensor_cache_key(chunk_plucker),
_tensor_cache_key(weight),
None if bias is None else _tensor_cache_key(bias),
)
if not torch.is_grad_enabled():
cached = self._plucker_emb_cache
if cached is not None and cached[0] == key:
return cached[2]
plucker_emb = self.plucker_embedder(chunk_plucker.to(weight.dtype))
if plucker_emb.shape[1] != latent_token_count:
raise ValueError(
f"plucker_emb token count {plucker_emb.shape[1]} != "
f"latent token count {latent_token_count}; "
"expected chunk_plucker shape (B, 48, T, H, W)."
)
if not torch.is_grad_enabled():
self._plucker_emb_cache = (key, chunk_plucker, plucker_emb)
return plucker_emb
def forward(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
timestep: Optional[torch.Tensor] = None,
encoder_attention_mask: Optional[torch.Tensor] = None,
camera_conditions: Optional[torch.Tensor] = None,
chunk_plucker: Optional[torch.Tensor] = None,
guidance: Optional[torch.Tensor] = None, # kept for compat
**kwargs,
) -> torch.Tensor:
if encoder_hidden_states is None:
raise ValueError("SANA-WM forward requires encoder_hidden_states.")
if timestep is None:
raise ValueError("SANA-WM forward requires timestep.")
B, C, T_raw, H_raw, W_raw = hidden_states.shape
p_t, p_h, p_w = self.patch_size
T = T_raw // p_t
H = H_raw // p_h
W = W_raw // p_w
chunk_size = kwargs.get("chunk_size", self.chunk_size)
chunk_split_strategy = kwargs.get(
"chunk_split_strategy", self.chunk_split_strategy
)
chunk_index = kwargs.get("chunk_index", None)
# Patch embed: (B, C, T, H, W) -> (B, T*H*W, D)
x = self.x_embedder(hidden_states.to(dtype=self.x_embedder.proj.weight.dtype))
# Timestep AdaLN-single. SANA-WM's LTX sampler passes per-frame
# timesteps shaped (B, 1, T) so the clean first-frame condition can stay
# at timestep 0 while remaining latent frames denoise. Keep the scalar
# path for generic scheduler compatibility.
if self.timestep_norm_scale_factor != 1.0:
timestep_for_embed = (
timestep.float() / self.timestep_norm_scale_factor
).to(torch.float32)
else:
timestep_for_embed = timestep.long().to(torch.float32)
if timestep_for_embed.dim() == 1:
t_emb = self.t_embedder(timestep_for_embed) # (B, D)
t6 = self.t_block(t_emb) # (B, 6D)
else:
timestep_shape = tuple(timestep_for_embed.shape)
t_flat = self.t_embedder(timestep_for_embed.flatten())
t6_flat = self.t_block(t_flat)
t_emb = t_flat.unflatten(0, timestep_shape)
t6 = t6_flat.unflatten(0, timestep_shape)
if isinstance(encoder_attention_mask, (list, tuple)):
encoder_attention_mask = encoder_attention_mask[0]
y = encoder_hidden_states
if y.dim() == 3:
y = y.unsqueeze(1)
y = self.y_embedder(y).squeeze(1) # (B, L, D)
if y.shape[0] != B:
y = y.expand(B, -1, -1).contiguous()
if self.y_norm:
y = self.attention_y_norm(y)
if encoder_attention_mask is not None and encoder_attention_mask.shape[0] != B:
encoder_attention_mask = encoder_attention_mask.expand(B, -1).contiguous()
freqs = self._get_freqs(T, H, W, x.device)
# Camera conditioning: UCPE prope_fns + Plücker
prope_fns = None
if camera_conditions is not None:
if camera_conditions.shape[1] != T:
raise ValueError(
"SANA-WM camera_conditions must be sampled at latent "
f"frames: got {camera_conditions.shape[1]} frames, "
f"expected T={T}."
)
prope_fns = self._get_ucpe_apply_fns(
camera_conditions,
HW=(T, H, W),
freqs=freqs,
)
# Plücker post-attn embedding (shared across all blocks)
plucker_emb = None
needs_plucker_emb = (
chunk_plucker is not None
and self.plucker_embedder is not None
and (self.use_chunk_plucker_post_attn or self.use_chunk_plucker_input)
)
if needs_plucker_emb:
plucker_emb = self._get_plucker_emb(
chunk_plucker,
latent_token_count=x.shape[1],
) # (B, T*H*W, D)
if self.use_chunk_plucker_input and plucker_emb is not None:
x = x + plucker_emb
if not self.use_chunk_plucker_post_attn:
plucker_emb = None
# --- 6. Transformer blocks ---
HW = (T, H, W)
for block in self.blocks:
x = block(
x,
y=y,
t=t6,
HW=HW,
rotary_emb=freqs,
prope_fns=prope_fns,
plucker_emb=plucker_emb,
mask=encoder_attention_mask,
chunk_size=chunk_size,
chunk_split_strategy=chunk_split_strategy,
chunk_index=chunk_index,
)
x = self.final_layer(x, t_emb) # (B, N, p_t*p_h*p_w*C_out)
# Un-patch
x = x.reshape(B, T, H, W, p_t, p_h, p_w, self.out_channels)
x = x.permute(0, 7, 1, 4, 2, 5, 3, 6).contiguous()
x = x.reshape(B, self.out_channels, T * p_t, H * p_h, W * p_w)
return x
def forward_long(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
timestep: Optional[torch.Tensor] = None,
encoder_attention_mask: Optional[torch.Tensor] = None,
camera_conditions: Optional[torch.Tensor] = None,
chunk_plucker: Optional[torch.Tensor] = None,
*,
kv_cache: Optional[list] = None,
save_kv_cache: bool = True,
start_f: Optional[int] = None,
end_f: Optional[int] = None,
frame_index: Optional[torch.Tensor] = None,
**kwargs,
) -> Tuple[torch.Tensor, list]:
"""Streaming autoregressive forward over a chunk of latent frames.
RoPE / camera / plücker are windowed to the chunk's GLOBAL frame range
``[start_f, end_f)``; a per-block 10-slot ``kv_cache`` carries recurrent
state / concat-windows across chunks. Returns ``(out, new_cache)``.
"""
if encoder_hidden_states is None:
raise ValueError("SANA-WM forward_long requires encoder_hidden_states.")
if timestep is None:
raise ValueError("SANA-WM forward_long requires timestep.")
if kv_cache is None:
kv_cache = [[None] * _NUM_STREAM_CACHE_SLOTS for _ in self.blocks]
B, C, T_raw, H_raw, W_raw = hidden_states.shape
p_t, p_h, p_w = self.patch_size
T = T_raw // p_t
H = H_raw // p_h
W = W_raw // p_w
start = 0 if start_f is None else int(start_f)
end = start + T if end_f is None else int(end_f)
x = self.x_embedder(hidden_states.to(dtype=self.x_embedder.proj.weight.dtype))
# Timestep AdaLN-single: force the framewise (B, 1, T) path so blocks
# always apply per-frame modulation.
if timestep.dim() == 1:
timestep = timestep[:, None, None].expand(-1, 1, T)
elif timestep.dim() == 2:
timestep = timestep[:, None, :]
if self.timestep_norm_scale_factor != 1.0:
timestep_for_embed = (
timestep.float() / self.timestep_norm_scale_factor
).to(torch.float32)
else:
timestep_for_embed = timestep.long().to(torch.float32)
timestep_shape = tuple(timestep_for_embed.shape)
t_flat = self.t_embedder(timestep_for_embed.flatten())
t6_flat = self.t_block(t_flat)
t_emb = t_flat.unflatten(0, timestep_shape)
t6 = t6_flat.unflatten(0, timestep_shape)
if isinstance(encoder_attention_mask, (list, tuple)):
encoder_attention_mask = encoder_attention_mask[0]
y = encoder_hidden_states
if y.dim() == 3:
y = y.unsqueeze(1)
y = self.y_embedder(y).squeeze(1)
if y.shape[0] != B:
y = y.expand(B, -1, -1).contiguous()
if self.y_norm:
y = self.attention_y_norm(y)
if encoder_attention_mask is not None and encoder_attention_mask.shape[0] != B:
encoder_attention_mask = encoder_attention_mask.expand(B, -1).contiguous()
# RoPE windowed to global frame positions [start, end)
freqs = self._get_freqs_window(
start, end, H, W, x.device, frame_index=frame_index
)
# Camera conditioning: slice to the chunk, co-windowed w/ freqs
prope_fns = None
if camera_conditions is not None:
if camera_conditions.shape[1] != T:
# .contiguous(): canonical layout regardless of how the caller
# built the full-length tensor, so batch and realtime windows are
# kernel-level identical (slice offset/stride changes the reduction
# order otherwise — measured 1e-7 seeds amplifying to %-level drift
# through the bf16 block stack).
camera_conditions = camera_conditions[:, start:end].contiguous()
if camera_conditions.shape[0] != B:
camera_conditions = camera_conditions.repeat(
B // camera_conditions.shape[0], 1, 1
)
prope_fns = self._get_ucpe_apply_fns(
camera_conditions, HW=(T, H, W), freqs=freqs
)
# Plücker post-attn / input embedding, sliced to the chunk.
if chunk_plucker is not None and chunk_plucker.shape[2] != T:
chunk_plucker = chunk_plucker[
:, :, start:end
].contiguous() # see camera note
if chunk_plucker is not None and chunk_plucker.shape[0] != B:
chunk_plucker = chunk_plucker.repeat(
B // chunk_plucker.shape[0], 1, 1, 1, 1
)
plucker_emb = None
needs_plucker_emb = (
chunk_plucker is not None
and self.plucker_embedder is not None
and (self.use_chunk_plucker_post_attn or self.use_chunk_plucker_input)
)
if needs_plucker_emb:
plucker_emb = self._get_plucker_emb(
chunk_plucker, latent_token_count=x.shape[1]
)
if self.use_chunk_plucker_input and plucker_emb is not None:
x = x + plucker_emb
if not self.use_chunk_plucker_post_attn:
plucker_emb = None
# parity harness (env-gated, no-op in prod): on the FIRST sink-path call
# (frame_index not None), checksum the pre-block tensors and x after every
# block to localize where the two execution paths first diverge.
_probe_path = os.environ.get(parity_probe.ENV_BLOCK_PROBE)
_probe = None
if (
_probe_path
and frame_index is not None
and not getattr(self, "_block_probe_done", False)
):
_ck = parity_probe.checksum
_probe = {
"x_embed": _ck(x),
"t6": _ck(t6),
"y": _ck(y),
"freqs": (
(
tuple(freqs.shape),
float(freqs.real.detach().double().sum().item()),
float(freqs.imag.detach().double().sum().item()),
)
if freqs is not None
else None
),
"plucker_emb": _ck(plucker_emb),
"frame_index": frame_index.tolist(),
}
HW = (T, H, W)
new_cache = []
for i, block in enumerate(self.blocks):
x, block_cache = block.forward_long(
x,
y,
t6,
HW,
freqs,
prope_fns,
plucker_emb,
encoder_attention_mask,
kv_cache=kv_cache[i],
save_kv_cache=save_kv_cache,
)
new_cache.append(block_cache)
if _probe is not None:
_probe[f"x_after_block_{i:02d}"] = parity_probe.checksum(x)
if _probe is not None:
torch.save(_probe, _probe_path)
self._block_probe_done = True
x = self.final_layer(x, t_emb)
x = x.reshape(B, T, H, W, p_t, p_h, p_w, self.out_channels)
x = x.permute(0, 7, 1, 4, 2, 5, 3, 6).contiguous()
x = x.reshape(B, self.out_channels, T * p_t, H * p_h, W * p_w)
return x, new_cache
EntryClass = SanaWMTransformer3DModel