[diffusion] optimize: cosmos3 fused qknorm rope (#27096)

This commit is contained in:
Chi McIsaac
2026-06-06 09:15:42 +08:00
committed by GitHub
parent bf4f2ccc78
commit 25d8f431d1
5 changed files with 242 additions and 102 deletions
@@ -869,15 +869,27 @@ def apply_qk_norm_rope(
raise ValueError(
f"apply_qk_norm_rope expects 4D q/k tensors, got q:{tuple(q.shape)} k:{tuple(k.shape)}"
)
if q.shape != k.shape:
if q.shape[:2] != k.shape[:2] or q.shape[-1] != k.shape[-1]:
raise ValueError(
f"apply_qk_norm_rope expects q/k to have the same shape, got {q.shape} vs {k.shape}"
"apply_qk_norm_rope expects q/k to share batch, sequence, and head size, "
f"got {q.shape} vs {k.shape}"
)
if not (isinstance(cos_sin_cache, torch.Tensor) and cos_sin_cache.dim() == 2):
raise ValueError("cos_sin_cache must be a 2D torch.Tensor")
if k.device != q.device or cos_sin_cache.device != q.device:
raise ValueError(
"q, k, and cos_sin_cache must be on the same device, "
f"got q={q.device}, k={k.device}, cos_sin_cache={cos_sin_cache.device}"
)
batch_size, seq_len, _, _ = q.shape
q_eps = q_norm.variance_epsilon
k_eps = k_norm.variance_epsilon
rope_dim = cos_sin_cache.size(-1)
if rope_dim % 2 != 0 or rope_dim > head_dim:
raise ValueError(
f"cos_sin_cache width must be even and <= head_dim, got {rope_dim} vs {head_dim}"
)
fused_enabled = os.getenv("SGLANG_ENABLE_FUSED_QKNORM_ROPE", "1").lower() not in {
"0",
"false",
@@ -898,6 +910,7 @@ def apply_qk_norm_rope(
raise ValueError(
f"positions must be 1D of length {batch_size * seq_len}, got shape={tuple(positions.shape)}"
)
positions = positions.to(device=q.device, dtype=torch.long)
if (
fused_enabled
@@ -123,11 +123,7 @@ class Qwen3VLTextRotaryEmbedding(torch.nn.Module):
freqs_t[..., idx] = freqs[dim, ..., idx]
return freqs_t
@torch.no_grad()
def forward(
self, x: torch.Tensor, position_ids: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return cos/sin for position IDs shaped [3, B, S], [B, S, 3], or [B, S]."""
def _normalize_position_ids(self, position_ids: torch.Tensor) -> torch.Tensor:
if position_ids.ndim == 3 and position_ids.shape[-1] == 3:
position_ids = position_ids.permute(2, 0, 1)
elif position_ids.ndim == 2:
@@ -137,6 +133,10 @@ class Qwen3VLTextRotaryEmbedding(torch.nn.Module):
"Qwen3 mRoPE position_ids must have shape [3, B, S], [B, S, 3], "
f"or [B, S], got {tuple(position_ids.shape)}"
)
return position_ids
def _compute_interleaved_freqs(self, position_ids: torch.Tensor) -> torch.Tensor:
position_ids = self._normalize_position_ids(position_ids)
inv_freq_expanded = (
self.inv_freq[None, None, :, None]
@@ -147,7 +147,31 @@ class Qwen3VLTextRotaryEmbedding(torch.nn.Module):
position_ids_expanded = position_ids[:, :, None, :].float()
freqs = (inv_freq_expanded @ position_ids_expanded).transpose(2, 3)
freqs = self.apply_interleaved_mrope(freqs, self.mrope_section)
return self.apply_interleaved_mrope(freqs, self.mrope_section)
@torch.no_grad()
def build_rope_cache_inputs(
self, position_ids: torch.Tensor, *, cache_dtype: torch.dtype | None = None
) -> tuple[torch.Tensor, torch.Tensor]:
freqs = self._compute_interleaved_freqs(position_ids)
cos = freqs.cos() * self.attention_scaling
sin = freqs.sin() * self.attention_scaling
if cache_dtype is not None and cache_dtype != torch.float32:
cos = cos.to(cache_dtype).float()
sin = sin.to(cache_dtype).float()
cos_sin_cache = torch.cat((cos, sin), dim=-1).reshape(-1, self.head_dim)
cos_sin_cache = cos_sin_cache.contiguous()
cache_positions = torch.arange(
cos_sin_cache.shape[0], device=cos_sin_cache.device, dtype=torch.long
)
return cos_sin_cache, cache_positions
@torch.no_grad()
def forward(
self, x: torch.Tensor, position_ids: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return cos/sin for position IDs shaped [3, B, S], [B, S, 3], or [B, S]."""
freqs = self._compute_interleaved_freqs(position_ids)
emb = torch.cat((freqs, freqs), dim=-1)
cos = emb.cos() * self.attention_scaling
sin = emb.sin() * self.attention_scaling
@@ -80,58 +80,91 @@ def apply_flashinfer_rope_qk_inplace(
f"Expected q/k to be 4D [bsz, seqlen, nheads, head_size], "
f"got q:{tuple(q.shape)} k:{tuple(k.shape)}"
)
if q.shape != k.shape:
if q.shape[:2] != k.shape[:2] or q.shape[-1] != k.shape[-1]:
raise ValueError(
f"q and k must have the same shape, got {q.shape} vs {k.shape}"
f"q and k must share batch, sequence, and head size, got {q.shape} vs {k.shape}"
)
if not (isinstance(cos_sin_cache, torch.Tensor) and cos_sin_cache.dim() == 2):
raise ValueError("cos_sin_cache must be a 2D torch.Tensor")
bsz, seqlen, nheads, d = q.shape
bsz, seqlen, q_heads, d = q.shape
k_heads = k.shape[2]
rope_dim = cos_sin_cache.shape[-1]
if k.device != q.device or cos_sin_cache.device != q.device:
raise ValueError(
"q, k, and cos_sin_cache must be on the same device, "
f"got q={q.device}, k={k.device}, cos_sin_cache={cos_sin_cache.device}"
)
if rope_dim % 2 != 0 or rope_dim > d:
raise ValueError(
f"cos_sin_cache width must be even and <= head_size, got {rope_dim} vs {d}"
)
if head_size is None:
head_size = d
if head_size != d:
raise ValueError(f"head_size mismatch: inferred {d}, but head_size={head_size}")
if flashinfer_apply_rope_inplace is None:
# Triton fallback for AMD/ROCm where FlashInfer is not available
use_flashinfer = (
flashinfer_apply_rope_inplace is not None
and q.is_cuda
and k.is_cuda
and cos_sin_cache.is_cuda
and q_heads == k_heads
)
_warn_about_missing_flashinfer()
if not use_flashinfer:
if flashinfer_apply_rope_inplace is None:
_warn_about_missing_flashinfer()
half_size = cos_sin_cache.shape[-1] // 2
half_size = rope_dim // 2
if positions is None:
cos = cos_sin_cache[:seqlen, :half_size].to(q.dtype)
sin = cos_sin_cache[:seqlen, half_size:].to(q.dtype)
cos = cos.unsqueeze(0).expand(bsz, -1, -1).reshape(bsz * seqlen, -1)
sin = sin.unsqueeze(0).expand(bsz, -1, -1).reshape(bsz * seqlen, -1)
else:
positions = positions.to(cos_sin_cache.device).view(-1)
positions = positions.to(device=q.device, dtype=torch.long).view(-1)
cos = cos_sin_cache[positions, :half_size].to(q.dtype)
sin = cos_sin_cache[positions, half_size:].to(q.dtype)
q_flat = q.reshape(bsz * seqlen, nheads, d)
k_flat = k.reshape(bsz * seqlen, nheads, d)
q_rot = apply_rotary_embedding(q_flat, cos, sin, interleaved=not is_neox)
k_rot = apply_rotary_embedding(k_flat, cos, sin, interleaved=not is_neox)
return q_rot.view(bsz, seqlen, nheads, d), k_rot.view(bsz, seqlen, nheads, d)
def apply_rope_prefix(x: torch.Tensor, num_heads: int) -> torch.Tensor:
x_flat = x.reshape(bsz * seqlen, num_heads, d)
x_rot = x_flat[..., :rope_dim]
out_rot = torch.empty_like(x_rot)
cos_b = cos.unsqueeze(-2)
sin_b = sin.unsqueeze(-2)
if is_neox:
x1, x2 = torch.chunk(x_rot, 2, dim=-1)
out_rot[..., :half_size] = x1 * cos_b - x2 * sin_b
out_rot[..., half_size:] = x2 * cos_b + x1 * sin_b
else:
x1 = x_rot[..., ::2]
x2 = x_rot[..., 1::2]
out_rot[..., ::2] = x1 * cos_b - x2 * sin_b
out_rot[..., 1::2] = x2 * cos_b + x1 * sin_b
if rope_dim == d:
return out_rot.view(bsz, seqlen, num_heads, d)
out = x_flat.clone()
out[..., :rope_dim] = out_rot
return out.view(bsz, seqlen, num_heads, d)
return apply_rope_prefix(q, q_heads), apply_rope_prefix(k, k_heads)
if positions is None:
pos_1d = torch.arange(seqlen, device=q.device, dtype=torch.long)
positions = pos_1d if bsz == 1 else pos_1d.repeat(bsz)
else:
if not (
isinstance(positions, torch.Tensor)
and positions.dtype == torch.long
and positions.dim() == 1
):
raise ValueError("positions must be a 1D torch.long Tensor")
if not (isinstance(positions, torch.Tensor) and positions.dim() == 1):
raise ValueError("positions must be a 1D Tensor")
if positions.numel() != bsz * seqlen:
raise ValueError(
f"positions length must be bsz*seqlen={bsz*seqlen}, got {positions.numel()}"
)
positions = positions.to(device=q.device, dtype=torch.long)
q_flat = q.reshape(bsz * seqlen, nheads * d).contiguous()
k_flat = k.reshape(bsz * seqlen, nheads * d).contiguous()
q_flat = q.reshape(bsz * seqlen, q_heads * d).contiguous()
k_flat = k.reshape(bsz * seqlen, k_heads * d).contiguous()
flashinfer_apply_rope_inplace(
positions=positions,
query=q_flat,
@@ -140,7 +173,7 @@ def apply_flashinfer_rope_qk_inplace(
cos_sin_cache=cos_sin_cache,
is_neox=is_neox,
)
return q_flat.view(bsz, seqlen, nheads, d), k_flat.view(bsz, seqlen, nheads, d)
return q_flat.view(bsz, seqlen, q_heads, d), k_flat.view(bsz, seqlen, k_heads, d)
@torch.compiler.assume_constant_result
@@ -33,10 +33,10 @@ class VocoderLoader(ComponentLoader):
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None)
class_name = config.pop("_class_name", None) or self.component_architecture
assert (
class_name is not None
), "Model config does not contain a _class_name attribute. Only diffusers format is supported."
), "Vocoder class name must be available from component config or pipeline config."
server_args.model_paths[component_name] = component_model_path
@@ -22,7 +22,11 @@ from sglang.multimodal_gen.runtime.distributed import (
)
from sglang.multimodal_gen.runtime.layers.activation import SiluAndMul
from sglang.multimodal_gen.runtime.layers.attention import USPAttention
from sglang.multimodal_gen.runtime.layers.layernorm import RMSNorm, apply_qk_norm
from sglang.multimodal_gen.runtime.layers.layernorm import (
RMSNorm,
apply_qk_norm,
apply_qk_norm_rope,
)
from sglang.multimodal_gen.runtime.layers.linear import (
MergedColumnParallelLinear,
ReplicatedLinear,
@@ -34,7 +38,6 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config impor
)
from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
Qwen3VLTextRotaryEmbedding,
qwen3_apply_rotary_pos_emb,
)
from sglang.multimodal_gen.runtime.layers.visual_embedding import timestep_embedding
from sglang.multimodal_gen.runtime.layers.vocab_parallel_embedding import (
@@ -131,6 +134,66 @@ def compute_mrope_position_ids_vision(
return mrope_ids, next_offset
# -----------------------------------------------------------------------------
# Qwen3-style RoPE functions
# -----------------------------------------------------------------------------
def _apply_qwen3_qk_norm_rope(
q: torch.Tensor,
k: torch.Tensor,
q_norm: RMSNorm,
k_norm: RMSNorm,
head_dim: int,
cos_sin_cache: torch.Tensor,
rope_cache_positions: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
return apply_qk_norm_rope(
q=q.contiguous(),
k=k.contiguous(),
q_norm=q_norm,
k_norm=k_norm,
head_dim=head_dim,
cos_sin_cache=cos_sin_cache,
is_neox=True,
positions=rope_cache_positions,
)
def _apply_qwen3_rope_from_cache(
q: torch.Tensor, k: torch.Tensor, cos_sin_cache: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
batch_size, seq_len = q.shape[:2]
half = q.shape[-1] // 2
cos = cos_sin_cache[:, :half].view(batch_size, seq_len, 1, half).to(q.dtype)
sin = cos_sin_cache[:, half:].view(batch_size, seq_len, 1, half).to(q.dtype)
q1 = q[..., :half]
q2 = q[..., half:]
q_out = torch.empty_like(q)
q_out[..., :half] = q1 * cos - q2 * sin
q_out[..., half:] = q2 * cos + q1 * sin
k1 = k[..., :half]
k2 = k[..., half:]
k_out = torch.empty_like(k)
k_out[..., :half] = k1 * cos - k2 * sin
k_out[..., half:] = k2 * cos + k1 * sin
return q_out, k_out
def _apply_qwen3_qk_norm_rope_split(
q: torch.Tensor,
k: torch.Tensor,
q_norm: RMSNorm,
k_norm: RMSNorm,
head_dim: int,
cos_sin_cache: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
q, k = apply_qk_norm(q.contiguous(), k.contiguous(), q_norm, k_norm, head_dim)
return _apply_qwen3_rope_from_cache(q, k, cos_sin_cache)
# -----------------------------------------------------------------------------
# Cosmos3 Timestep Embedder
# -----------------------------------------------------------------------------
@@ -295,15 +358,15 @@ class Cosmos3CausalAttention(nn.Module):
prefix=add_prefix("to_out", prefix),
)
# Per-head QK norm. Modules hold the weights; F.rms_norm in forward.
# Per-head QK norm.
self.norm_q = RMSNorm(head_dim, eps=1e-6)
self.norm_k = RMSNorm(head_dim, eps=1e-6)
def forward(
self,
hidden_states: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
cos_sin_cache: torch.Tensor,
rope_cache_positions: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Forward with KV cache return.
@@ -335,7 +398,7 @@ class Cosmos3CausalAttention(nn.Module):
k = F.rms_norm(
k, (self.head_dim,), self.norm_k.weight, self.norm_k.variance_epsilon
)
q, k = qwen3_apply_rotary_pos_emb(q, k, freqs_cos, freqs_sin)
q, k = _apply_qwen3_rope_from_cache(q, k, cos_sin_cache)
out = F.scaled_dot_product_attention(
q.transpose(1, 2),
@@ -409,8 +472,9 @@ class Cosmos3CrossAttention(nn.Module):
hidden_states: torch.Tensor,
k_und: torch.Tensor,
v_und: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
cos_sin_cache: torch.Tensor,
rope_cache_positions: torch.Tensor,
use_fused_qk_norm_rope: bool,
) -> torch.Tensor:
"""Cross-attention from GEN to cached UND K/V.
@@ -418,8 +482,8 @@ class Cosmos3CrossAttention(nn.Module):
hidden_states: [B, S_gen_local, hidden_size] visual tokens (may be sharded)
k_und: [B, S_und, H_kv, D] pre-computed UND keys (always full/replicated)
v_und: [B, S_und, H_kv, D] pre-computed UND values (always full/replicated)
freqs_cos: [B, S_gen_local, 1, D] cosine part of RoPE (for local shard)
freqs_sin: [B, S_gen_local, 1, D] sine part of RoPE (for local shard)
cos_sin_cache: [B*S_gen_local, D] local rows of [cos, sin]
rope_cache_positions: identity row positions into cos_sin_cache
"""
batch_size, seq_len_gen = hidden_states.shape[:2]
@@ -440,10 +504,20 @@ class Cosmos3CrossAttention(nn.Module):
]
v = qkv[:, :, self.num_attention_heads + self.num_key_value_heads :, :]
q, k = apply_qk_norm(
q.contiguous(), k.contiguous(), self.norm_q, self.norm_k, self.head_dim
)
q, k = qwen3_apply_rotary_pos_emb(q, k, freqs_cos, freqs_sin)
if use_fused_qk_norm_rope:
q, k = _apply_qwen3_qk_norm_rope(
q,
k,
self.norm_q,
self.norm_k,
self.head_dim,
cos_sin_cache,
rope_cache_positions,
)
else:
q, k = _apply_qwen3_qk_norm_rope_split(
q, k, self.norm_q, self.norm_k, self.head_dim, cos_sin_cache
)
# K/V = [text (replicated full on every SP rank) | image (sharded same as Q)].
# USPAttention routes through the registered attention backend (FA, sage,
@@ -497,8 +571,8 @@ class Cosmos3UndDecoderLayer(nn.Module):
def forward(
self,
hidden_states: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
cos_sin_cache: torch.Tensor,
rope_cache_positions: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Forward pass.
@@ -508,7 +582,9 @@ class Cosmos3UndDecoderLayer(nn.Module):
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
attn_out, k, v = self.self_attn(hidden_states, freqs_cos, freqs_sin)
attn_out, k, v = self.self_attn(
hidden_states, cos_sin_cache, rope_cache_positions
)
hidden_states = residual + attn_out
residual = hidden_states
@@ -565,8 +641,9 @@ class Cosmos3GenDecoderLayer(nn.Module):
hidden_states: torch.Tensor,
k_und: torch.Tensor,
v_und: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
cos_sin_cache: torch.Tensor,
rope_cache_positions: torch.Tensor,
use_fused_qk_norm_rope: bool,
residual: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
# Fused add+rmsnorm: each `(hidden_states, residual) = norm(...)`
@@ -580,7 +657,12 @@ class Cosmos3GenDecoderLayer(nn.Module):
hidden_states, residual = self.input_layernorm(hidden_states, residual)
hidden_states = self.cross_attention(
hidden_states, k_und, v_und, freqs_cos, freqs_sin
hidden_states,
k_und,
v_und,
cos_sin_cache,
rope_cache_positions,
use_fused_qk_norm_rope,
)
hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
@@ -646,31 +728,28 @@ class Cosmos3LanguageModel(nn.Module):
self,
text_ids: torch.Tensor,
text_mask: torch.Tensor,
freqs_cos: torch.Tensor,
freqs_sin: torch.Tensor,
position_ids: torch.Tensor,
) -> list[tuple[torch.Tensor, torch.Tensor]]:
"""Process text tokens and return per-layer K/V cache.
Args:
text_ids: [B, S] token IDs
text_mask: [B, S] float mask (1=real, 0=pad)
freqs_cos: [B, S, D] RoPE cosines
freqs_sin: [B, S, D] RoPE sines
position_ids: [3, B, S] mRoPE position IDs
Returns:
List of (K, V) per layer for GEN cross-attention
"""
hidden = self.embed_tokens(text_ids)
mask_3d = text_mask.unsqueeze(-1)
# Add dimension for per-head broadcast
freqs_cos = freqs_cos.unsqueeze(2) # [B, S, 1, D]
freqs_sin = freqs_sin.unsqueeze(2)
cos_sin_cache, rope_cache_positions = self.rotary_emb.build_rope_cache_inputs(
position_ids, cache_dtype=hidden.dtype
)
cached_kv: list[tuple[torch.Tensor, torch.Tensor]] = []
for layer in self.layers:
hidden = hidden * mask_3d
hidden, k, v = layer(hidden, freqs_cos, freqs_sin)
hidden, k, v = layer(hidden, cos_sin_cache, rope_cache_positions)
cached_kv.append((k, v))
return cached_kv
@@ -800,7 +879,7 @@ class Cosmos3OmniTransformer(CachableDiT):
# This allows maintaining separate caches for conditional and unconditional
# prompts, avoiding recomputation on every denoising step
self.cached_kv: dict[str, list[tuple[torch.Tensor, torch.Tensor]]] = {}
self.cached_freqs_gen: dict[str, tuple[torch.Tensor, torch.Tensor]] = {}
self.cached_gen_rope_inputs: dict[str, tuple[torch.Tensor, torch.Tensor]] = {}
self.__post_init__()
@@ -840,7 +919,7 @@ class Cosmos3OmniTransformer(CachableDiT):
x = x[:, :, :, :H, :W]
return x
def _compute_rope_freqs(
def _compute_rope_position_ids(
self,
text_mask: torch.Tensor,
T: int,
@@ -848,12 +927,8 @@ class Cosmos3OmniTransformer(CachableDiT):
Wp: int,
fps: float | None,
device: torch.device,
dtype: torch.dtype,
) -> tuple[
tuple[torch.Tensor, torch.Tensor],
tuple[torch.Tensor, torch.Tensor],
]:
"""Compute mRoPE cos/sin for UND (text) and GEN (visual) pathways."""
) -> tuple[torch.Tensor, torch.Tensor]:
"""Compute mRoPE position IDs for UND text and GEN visual tokens."""
B = text_mask.shape[0]
S_text = text_mask.shape[1]
text_lengths = text_mask.sum(dim=1).long()
@@ -892,14 +967,7 @@ class Cosmos3OmniTransformer(CachableDiT):
text_pos_ids = torch.stack(text_pos_list, dim=1).to(device) # [3, B, S_text]
vis_pos_ids = torch.stack(vis_pos_list, dim=1).to(device) # [3, B, S_vis]
rotary_emb = self.language_model.rotary_emb
_dummy = torch.tensor([], dtype=dtype, device=device)
cos_und, sin_und = rotary_emb(_dummy, position_ids=text_pos_ids)
cos_gen, sin_gen = rotary_emb(_dummy, position_ids=vis_pos_ids)
freqs_und = (cos_und, sin_und)
freqs_gen = (cos_gen, sin_gen)
return freqs_und, freqs_gen
return text_pos_ids, vis_pos_ids
def reset_cache(self, cache_key: str | None = None):
"""Reset cached K/V from UND pathway.
@@ -911,20 +979,20 @@ class Cosmos3OmniTransformer(CachableDiT):
if cache_key is None:
# Reset all caches
self.cached_kv = {}
self.cached_freqs_gen = {}
self.cached_gen_rope_inputs = {}
else:
# Reset specific cache
if cache_key in self.cached_kv:
del self.cached_kv[cache_key]
if cache_key in self.cached_freqs_gen:
del self.cached_freqs_gen[cache_key]
if cache_key in self.cached_gen_rope_inputs:
del self.cached_gen_rope_inputs[cache_key]
def _ensure_cache_dicts(self):
"""Ensure cache dictionaries exist (for backwards compatibility)."""
if not isinstance(self.cached_kv, dict):
self.cached_kv = {}
if not isinstance(self.cached_freqs_gen, dict):
self.cached_freqs_gen = {}
if not isinstance(self.cached_gen_rope_inputs, dict):
self.cached_gen_rope_inputs = {}
def forward(
self,
@@ -1035,47 +1103,49 @@ class Cosmos3OmniTransformer(CachableDiT):
# Compute UND K/V cache for this cache_key if not already cached
# This allows reusing the cache across denoising steps for the same text
if cache_key not in self.cached_kv:
freqs_und, freqs_gen = self._compute_rope_freqs(
text_mask, T, Hp, Wp, fps, hidden_states.device, hidden_states.dtype
if (
cache_key not in self.cached_kv
or cache_key not in self.cached_gen_rope_inputs
):
text_pos_ids, vis_pos_ids = self._compute_rope_position_ids(
text_mask, T, Hp, Wp, fps, hidden_states.device
)
# UND K/V cache is kept FULL on all ranks (not sharded). Text
# sequence is short, so memory impact is minimal, and the GEN
# cross-attention needs the full K/V on every SP rank.
self.cached_kv[cache_key] = self.language_model(
text_ids, text_mask, freqs_und[0], freqs_und[1]
text_ids, text_mask, text_pos_ids
)
cos_gen, sin_gen = freqs_gen
if sequence_shard_enabled:
if seq_shard_pad > 0:
pad_cos = cos_gen[:, -1:].expand(-1, seq_shard_pad, -1)
pad_sin = sin_gen[:, -1:].expand(-1, seq_shard_pad, -1)
cos_gen = torch.cat([cos_gen, pad_cos], dim=1)
sin_gen = torch.cat([sin_gen, pad_sin], dim=1)
cos_gen = cos_gen.view(batch_size, self.sp_size, local_seq_len, -1)
sin_gen = sin_gen.view(batch_size, self.sp_size, local_seq_len, -1)
cos_gen = cos_gen[:, self.sp_rank, :, :]
sin_gen = sin_gen[:, self.sp_rank, :, :]
cos_gen = cos_gen.unsqueeze(2) # [B, S, 1, D]
sin_gen = sin_gen.unsqueeze(2)
self.cached_freqs_gen[cache_key] = (cos_gen, sin_gen)
pad_pos = vis_pos_ids[:, :, -1:].expand(-1, -1, seq_shard_pad)
vis_pos_ids = torch.cat([vis_pos_ids, pad_pos], dim=2)
vis_pos_ids = vis_pos_ids.view(
3, batch_size, self.sp_size, local_seq_len
)[:, :, self.sp_rank, :]
self.cached_gen_rope_inputs[cache_key] = (
self.language_model.rotary_emb.build_rope_cache_inputs(
vis_pos_ids, cache_dtype=hidden_gen.dtype
)
)
freqs_gen = self.cached_freqs_gen[cache_key]
cos_gen, sin_gen = freqs_gen
cos_sin_gen, gen_rope_cache_positions = self.cached_gen_rope_inputs[cache_key]
# Run GEN layers. `residual` is threaded so each layer's
# input_layernorm and post_attention_layernorm can use the
# fused add+rmsnorm path instead of separate add + norm kernels.
cached_kv_for_key = self.cached_kv[cache_key]
residual: torch.Tensor | None = None
use_fused_qk_norm_rope = T > 1
for i, layer in enumerate(self.gen_layers):
k_und, v_und = cached_kv_for_key[i]
hidden_gen, residual = layer(
hidden_gen,
k_und,
v_und,
cos_gen,
sin_gen,
cos_sin_gen,
gen_rope_cache_positions,
use_fused_qk_norm_rope,
residual=residual,
)