abot-world-interactive / wan /modules /causal_model.py
multimodalart's picture
multimodalart HF Staff
Initial ABot-World interactive rollout demo
28404e6 verified
Raw
History Blame Contribute Delete
112 kB
# causal_wan2_2.py
# 基于 Wan 2.2 结构 + Wan 2.1 Causal 版本改造
import math
import os
from typing import Any
import torch
import torch.nn as nn
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.models.modeling_utils import ModelMixin
from wan.modules.model import SimpleAdapter, MLPProj, WanI2VCrossAttention
from torch.nn.attention.flex_attention import (
flex_attention,
create_block_mask,
BlockMask,
)
from wan.modules.attention import attention # 你 2.1 causal 版本里用的那个 attention
from wan.modules.model import (
WanRMSNorm,
rope_apply,
WanLayerNorm,
rope_params,
sinusoidal_embedding_1d,
WanSelfAttention,
)
from wan.modules.attention import flash_attention
# ===== Debug helpers:只在异常/即将越界时打印,正常路径不刷日志 =====
def _dbg_tensor(name, x):
if torch.is_tensor(x):
return f"{name}: shape={tuple(x.shape)}, dtype={x.dtype}, device={x.device}"
return f"{name}: {type(x)}={x}"
def _dbg_print(tag, **kwargs):
print(f"\n[DEBUG][{tag}]")
for k, v in kwargs.items():
try:
print(" ", _dbg_tensor(k, v))
except Exception as e:
print(f" {k}: <print failed: {e}>")
def _dbg_block_mask(mask):
# BlockMask 不同 PyTorch 版本内部字段不稳定,这里只安全打印类型和 repr。
try:
return repr(mask)
except Exception as e:
return f"<BlockMask repr failed: {e}>"
def _is_checkpoint_stop_signal(err: BaseException) -> bool:
"""gradient checkpoint 重算时使用的内部控制流,不是真实错误;勿打印 DEBUG failed。"""
return type(err).__name__ == "_StopRecomputationError"
# ===== 新增:带 start_frame 的 causal_rope_apply =====
@torch.amp.autocast('cuda', enabled=False)
def causal_rope_apply(x, grid_sizes, freqs, start_frame=0):
"""
与 2.1 causal 版本一致:在时间维上加入起始帧偏移,用于推理时逐帧累积。
"""
n, c = x.size(2), x.size(3) // 2
freqs = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1)
output = []
for i, (f, h, w) in enumerate(grid_sizes.tolist()):
seq_len = f * h * w
try:
x_i = torch.view_as_complex(x[i, :seq_len].to(torch.float64).reshape(
seq_len, n, -1, 2))
freqs_i = torch.cat([
freqs[0][start_frame:start_frame + f].view(f, 1, 1, -1).expand(f, h, w, -1),
freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1)
], dim=-1).reshape(seq_len, 1, -1)
x_i = torch.view_as_real(x_i * freqs_i).flatten(2)
x_i = torch.cat([x_i, x[i, seq_len:]])
output.append(x_i)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"causal_rope_apply.failed",
error=repr(e),
x=x,
grid_sizes=grid_sizes,
freqs=freqs,
start_frame=start_frame,
batch_index=i,
f=f,
h=h,
w=w,
seq_len=seq_len,
n=n,
c=c,
)
raise
return torch.stack(output).float()
# Relative-RoPE freqs are keyed by (f, h, w, clamped frame ids, head dim,
# device). In steady state the visible window size and frame ids are constant,
# so the expanded [seq_len, 1, dim] complex tensor is identical every block and
# can be reused instead of rebuilt (arange/clamp/expand/cat/reshape).
_REL_FREQS_I_CACHE: dict = {}
def _get_relative_freqs_i(freqs, f, h, w, t_index, device):
c = int(freqs.size(1))
cache_key = (int(f), int(h), int(w), tuple(t_index.tolist()), c, str(device))
cached = _REL_FREQS_I_CACHE.get(cache_key)
if cached is not None:
return cached
freqs_split = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1)
freqs_i = torch.cat([
freqs_split[0][t_index].view(f, 1, 1, -1).expand(f, h, w, -1),
freqs_split[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
freqs_split[2][:w].view(1, 1, w, -1).expand(f, h, w, -1),
], dim=-1).reshape(f * h * w, 1, -1).to(torch.complex64)
_REL_FREQS_I_CACHE[cache_key] = freqs_i
return freqs_i
@torch.amp.autocast('cuda', enabled=False)
def relative_rope_apply(x, grid_sizes, freqs, frame_indices=None):
"""
Apply RoPE with frame ids local to the currently visible attention window.
KV-cache inference can evict old frames. When relative RoPE is enabled we
keep raw keys in cache and re-apply RoPE to the visible window so the query
and keys share a stable local coordinate system instead of growing absolute
frame ids forever.
"""
n = x.size(2)
output = []
for i, (f, h, w) in enumerate(grid_sizes.tolist()):
seq_len = f * h * w
# fp32/complex64 RoPE: numerically sufficient, ~half the memory traffic
# of the previous fp64/complex128 path.
x_i = torch.view_as_complex(
x[i, :seq_len].to(torch.float32).reshape(seq_len, n, -1, 2)
)
if frame_indices is None:
t_index = torch.arange(f, device=x.device, dtype=torch.long)
else:
t_index = frame_indices[:f].to(device=x.device, dtype=torch.long)
# Echo/Infinity-style block-relative RoPE keeps temporal ids inside the
# visible local window. For local_attn_size=21, the max valid id is 20.
t_index = torch.clamp(t_index, min=0, max=20)
freqs_i = _get_relative_freqs_i(freqs, f, h, w, t_index, x.device)
x_i = torch.view_as_real(x_i * freqs_i).flatten(2)
x_i = torch.cat([x_i, x[i, seq_len:]])
output.append(x_i)
return torch.stack(output).float()
def freqs_at(indices, dim, theta=10000, device='cpu'):
assert dim % 2 == 0
t = torch.tensor(indices, dtype=torch.float64, device=device)
freqs = torch.outer(
t,
1.0 / torch.pow(theta, torch.arange(0, dim, 2, device=device, dtype=torch.float64).div(dim))
)
return torch.polar(torch.ones_like(freqs), freqs)
def rope_apply_with_refimg(x, freqs, num_heads):
if x.dim() == 3:
b, s, _ = x.shape
x = x.view(b, s, num_heads, -1)
# RoPE in fp32 (complex64) is numerically sufficient and roughly halves the
# memory traffic vs. the previous fp64/complex128 path.
x_out = torch.view_as_complex(x.to(torch.float32).reshape(
x.shape[0], x.shape[1], x.shape[2], -1, 2
))
freqs = freqs.to(device=x.device)
if freqs.dtype != torch.complex64:
freqs = freqs.to(torch.complex64)
x_out = torch.view_as_real(x_out * freqs).flatten(3)
return x_out.to(x.dtype)
# Ref-image RoPE freqs depend only on (num_slots, tokens_per_slot, ref grid,
# head dim, device); they are constant across denoising blocks, so cache them
# instead of rebuilding every call.
_REF_FREQS_CACHE: dict = {}
# Optimization E: in the relative-RoPE KV-cache path, cache the post-RoPE video
# keys and only re-rope the new block's query each step, instead of re-roping
# the whole visible window every block. This relies on RoPE attention logits
# depending only on query-key position *differences*: with a local window of
# <=local_attn_size frames the window-local ids never exceed the trained range
# (the clamp is a no-op), so an absolute counter with periodic re-basing yields
# identical logits. We re-base (re-rope the window once) before the counter
# nears the rotary table limit (1024). Only valid when sink_size == 0.
_REL_ROPE_CACHE_ENABLED = os.environ.get("REL_ROPE_CACHE", "1") != "0"
# Keep roped positions well below the 1024-entry rotary table. Comparable in
# magnitude to the absolute-RoPE path for short runs, so quality is unaffected.
_REL_ROPE_REBASE_MAX_POS = int(os.environ.get("REL_ROPE_REBASE_MAX_POS", "256"))
_REL_ROPE_DEBUG = os.environ.get("REL_ROPE_DEBUG", "0").lower() in {"1", "true", "yes", "on"}
_REL_ROPE_DEBUG_LIMIT = int(os.environ.get("REL_ROPE_DEBUG_LIMIT", "50"))
_REL_ROPE_DEBUG_COUNT = 0
_REL_ROPE_DEBUG_ONCE_KEYS = set()
def _rel_rope_debug_print(tag, once_key=None, **kwargs):
global _REL_ROPE_DEBUG_COUNT
if not _REL_ROPE_DEBUG or _REL_ROPE_DEBUG_COUNT >= _REL_ROPE_DEBUG_LIMIT:
return
if once_key is not None:
if once_key in _REL_ROPE_DEBUG_ONCE_KEYS:
return
_REL_ROPE_DEBUG_ONCE_KEYS.add(once_key)
_REL_ROPE_DEBUG_COUNT += 1
parts = []
for key, value in kwargs.items():
if torch.is_tensor(value):
if value.numel() == 1:
value = value.item()
else:
value = tuple(value.shape)
parts.append(f"{key}={value}")
print(f"[REL_ROPE_DEBUG][{tag}] " + " ".join(parts), flush=True)
def _build_ref_freqs(freqs, num_slots, tokens_per_slot, ref_grid, device):
patch_t, patch_h, patch_w = [int(v) for v in ref_grid]
freq_dim = int(freqs.shape[1])
cache_key = (
int(num_slots), int(tokens_per_slot), patch_t, patch_h, patch_w,
freq_dim, str(device),
)
cached = _REF_FREQS_CACHE.get(cache_key)
if cached is not None:
return cached
f_band = freq_dim - 2 * (freq_dim // 3)
h_band = freq_dim // 3
w_band = freq_dim // 3
temporal_step = max(int(tokens_per_slot), 256)
neg_temporal = [-(int(num_slots) - i) * temporal_step for i in range(int(num_slots))]
t_freqs = freqs_at(neg_temporal, 2 * f_band, device=device)
freqs_split = freqs.split([f_band, h_band, w_band], dim=1)
h_freqs = freqs_split[1][:patch_h].to(device)
w_freqs = freqs_split[2][:patch_w].to(device)
ref_freqs = torch.cat([
t_freqs[:, None, None, None, :].expand(num_slots, patch_t, patch_h, patch_w, f_band),
h_freqs[None, None, :, None, :].expand(num_slots, patch_t, patch_h, patch_w, h_band),
w_freqs[None, None, None, :, :].expand(num_slots, patch_t, patch_h, patch_w, w_band),
], dim=-1).reshape(int(num_slots) * int(tokens_per_slot), 1, -1).to(torch.complex64)
_REF_FREQS_CACHE[cache_key] = ref_freqs
return ref_freqs
# ===== Causal Self-Attention(替换原 WanSelfAttention) =====
# Keep the causal-forcing flex attention path on the default inductor mode.
# max-autotune can fail on BlockMask symbolic sparse shapes during inference.
flex_attention = torch.compile(
flex_attention, dynamic=False, mode="max-autotune-no-cudagraphs"
)
# #Casual Forcing 配置
# flex_attention = torch.compile(
# flex_attention,
# dynamic=False,
# mode="default"
# )
class CausalWanSelfAttention(nn.Module):
def __init__(self,
dim,
num_heads,
local_attn_size=-1,
sink_size=0,
qk_norm=True,
eps=1e-6,
use_relative_rope=False):
assert dim % num_heads == 0
super().__init__()
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.local_attn_size = local_attn_size
self.sink_size = sink_size
self.qk_norm = qk_norm
self.eps = eps
self.use_relative_rope = bool(use_relative_rope)
# 注意:max_attention_size 只用于 KV cache 推理路径
self.max_attention_size = 880 * 21 if local_attn_size == -1 else local_attn_size * 880
# self.max_attention_size = 32760 if local_attn_size == -1 else local_attn_size * 1560
print(
f"CausalWanSelfAttention: max_attention_size={self.max_attention_size}, local_attn_size={self.local_attn_size}, sink_size={self.sink_size}")
self.q = nn.Linear(dim, dim)
self.k = nn.Linear(dim, dim)
self.v = nn.Linear(dim, dim)
self.o = nn.Linear(dim, dim)
self.norm_q = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
self.norm_k = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
def _run_flex_attention(
self,
*,
query,
key,
value,
block_mask,
padded_length=0,
tag="self_attn.flex_attention.failed",
**debug_ctx,
):
"""
flex_attention 的薄封装:正常时不打印;只在 kernel/shape/mask 崩时打印关键上下文。
"""
try:
out = flex_attention(
query=query.transpose(2, 1),
key=key.transpose(2, 1),
value=value.transpose(2, 1),
block_mask=block_mask,
)
if padded_length > 0:
out = out[:, :, :-padded_length]
return out.transpose(2, 1)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
tag,
error=repr(e),
query=query,
key=key,
value=value,
query_t=query.transpose(2, 1),
key_t=key.transpose(2, 1),
value_t=value.transpose(2, 1),
block_mask=_dbg_block_mask(block_mask),
padded_length=padded_length,
**debug_ctx,
)
raise
def forward(
self,
x, # [B, L, C]
seq_lens,
grid_sizes,
freqs,
block_mask: BlockMask | None = None,
kv_cache: dict | None = None,
current_start: int = 0,
cache_start: int | None = None,
):
"""
训练:kv_cache is None,使用 flex_attention + block_mask
推理:kv_cache not None,使用显式 KV cache + attention()
"""
b, s, n, d = *x.shape[:2], self.num_heads, self.head_dim
if cache_start is None:
cache_start = current_start
def qkv_fn(x_):
q = self.norm_q(self.q(x_)).view(b, s, n, d)
k = self.norm_k(self.k(x_)).view(b, s, n, d)
v = self.v(x_).view(b, s, n, d)
return q, k, v
try:
q, k, v = qkv_fn(x)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"self_attn.qkv.failed",
error=repr(e),
x=x,
b=b,
s=s,
n=n,
d=d,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
)
raise
if kv_cache is None:
frame_seqlen = int(math.prod(grid_sizes[0][1:]).item())
current_start_frame = current_start // frame_seqlen if frame_seqlen > 0 else 0
num_ref = int(getattr(self, "_num_ref_tokens", 0) or 0)
try:
is_tf = bool(getattr(self, "_is_teacher_forcing", False)) or (
num_ref == 0 and s == seq_lens[0].item() * 2
) or (
num_ref > 0 and s == num_ref + seq_lens[0].item() * 2
)
except Exception:
is_tf = False
if num_ref > 0:
ref_info = {
"num_slots": int(getattr(self, "_ref_num_slots", 0) or 0),
"tokens_per_slot": int(getattr(self, "_ref_tokens_per_frame", 0) or 0),
"grid": getattr(self, "_ref_grid_sizes", None),
}
if ref_info["num_slots"] <= 0 or ref_info["tokens_per_slot"] <= 0 or ref_info["grid"] is None:
raise RuntimeError("Ref tokens are present but ref RoPE metadata is incomplete.")
ref_freqs = _build_ref_freqs(
freqs=freqs,
num_slots=ref_info["num_slots"],
tokens_per_slot=ref_info["tokens_per_slot"],
ref_grid=ref_info["grid"],
device=q.device,
)
if is_tf:
branch_len = (s - num_ref) // 2
roped_query = torch.cat([
rope_apply_with_refimg(q[:, :num_ref], ref_freqs, self.num_heads),
rope_apply(q[:, num_ref:num_ref + branch_len], grid_sizes, freqs),
rope_apply(q[:, num_ref + branch_len:], grid_sizes, freqs),
], dim=1).type_as(v)
roped_key = torch.cat([
rope_apply_with_refimg(k[:, :num_ref], ref_freqs, self.num_heads),
rope_apply(k[:, num_ref:num_ref + branch_len], grid_sizes, freqs),
rope_apply(k[:, num_ref + branch_len:], grid_sizes, freqs),
], dim=1).type_as(v)
else:
roped_query = torch.cat([
rope_apply_with_refimg(q[:, :num_ref], ref_freqs, self.num_heads),
causal_rope_apply(
q[:, num_ref:],
grid_sizes,
freqs,
start_frame=current_start_frame,
),
], dim=1).type_as(v)
roped_key = torch.cat([
rope_apply_with_refimg(k[:, :num_ref], ref_freqs, self.num_heads),
causal_rope_apply(
k[:, num_ref:],
grid_sizes,
freqs,
start_frame=current_start_frame,
),
], dim=1).type_as(v)
padded_length = math.ceil(q.shape[1] / 128) * 128 - q.shape[1]
if padded_length > 0:
roped_query = torch.cat(
[roped_query, roped_query.new_zeros(q.shape[0], padded_length, q.shape[2], q.shape[3])],
dim=1,
)
roped_key = torch.cat(
[roped_key, roped_key.new_zeros(k.shape[0], padded_length, k.shape[2], k.shape[3])],
dim=1,
)
v = torch.cat(
[v, v.new_zeros(v.shape[0], padded_length, v.shape[2], v.shape[3])],
dim=1,
)
x = self._run_flex_attention(
query=roped_query,
key=roped_key,
value=v,
block_mask=block_mask,
padded_length=padded_length,
x=x,
q=q,
k=k,
v=v,
roped_query=roped_query,
roped_key=roped_key,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
is_tf=is_tf,
)
elif is_tf:
q_chunk = torch.chunk(q, 2, dim=1)
k_chunk = torch.chunk(k, 2, dim=1)
roped_query = []
roped_key = []
# rope should be same for clean and noisy parts
for ii in range(2):
try:
rq = rope_apply(q_chunk[ii], grid_sizes, freqs).type_as(v)
rk = rope_apply(k_chunk[ii], grid_sizes, freqs).type_as(v)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"self_attn.rope_apply.tf.failed",
error=repr(e),
ii=ii,
q=q,
k=k,
v=v,
q_chunk=q_chunk[ii],
k_chunk=k_chunk[ii],
grid_sizes=grid_sizes,
freqs=freqs,
seq_lens=seq_lens,
)
raise
roped_query.append(rq)
roped_key.append(rk)
roped_query = torch.cat(roped_query, dim=1)
roped_key = torch.cat(roped_key, dim=1)
padded_length = math.ceil(q.shape[1] / 128) * 128 - q.shape[1]
if padded_length > 0:
padded_roped_query = torch.cat(
[roped_query,
torch.zeros([q.shape[0], padded_length, q.shape[2], q.shape[3]],
device=q.device, dtype=v.dtype)],
dim=1
)
padded_roped_key = torch.cat(
[roped_key,
torch.zeros([k.shape[0], padded_length, k.shape[2], k.shape[3]],
device=k.device, dtype=v.dtype)],
dim=1
)
padded_v = torch.cat(
[v, torch.zeros([v.shape[0], padded_length, v.shape[2], v.shape[3]],
device=v.device, dtype=v.dtype)],
dim=1
)
x = self._run_flex_attention(
query=padded_roped_query,
key=padded_roped_key,
value=padded_v,
block_mask=block_mask,
padded_length=padded_length,
x=x,
q=q,
k=k,
v=v,
roped_query=roped_query,
roped_key=roped_key,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
is_tf=is_tf,
)
else:
x = self._run_flex_attention(
query=roped_query,
key=roped_key,
value=v,
block_mask=block_mask,
padded_length=0,
x=x,
q=q,
k=k,
v=v,
roped_query=roped_query,
roped_key=roped_key,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
is_tf=is_tf,
)
else:
try:
roped_query = causal_rope_apply(
q,
grid_sizes,
freqs,
start_frame=current_start_frame,
).type_as(v)
roped_key = causal_rope_apply(
k,
grid_sizes,
freqs,
start_frame=current_start_frame,
).type_as(v)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"self_attn.rope_apply.failed",
error=repr(e),
x=x,
q=q,
k=k,
v=v,
grid_sizes=grid_sizes,
freqs=freqs,
seq_lens=seq_lens,
)
raise
padded_length = math.ceil(q.shape[1] / 128) * 128 - q.shape[1]
if padded_length > 0:
padded_roped_query = torch.cat(
[roped_query,
torch.zeros([q.shape[0], padded_length, q.shape[2], q.shape[3]],
device=q.device, dtype=v.dtype)],
dim=1
)
padded_roped_key = torch.cat(
[roped_key,
torch.zeros([k.shape[0], padded_length, k.shape[2], k.shape[3]],
device=k.device, dtype=v.dtype)],
dim=1
)
padded_v = torch.cat(
[v, torch.zeros([v.shape[0], padded_length, v.shape[2], v.shape[3]],
device=v.device, dtype=v.dtype)],
dim=1
)
x = self._run_flex_attention(
query=padded_roped_query,
key=padded_roped_key,
value=padded_v,
block_mask=block_mask,
padded_length=padded_length,
x=x,
q=q,
k=k,
v=v,
roped_query=roped_query,
roped_key=roped_key,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
is_tf=is_tf,
)
else:
x = self._run_flex_attention(
query=roped_query,
key=roped_key,
value=v,
block_mask=block_mask,
padded_length=0,
x=x,
q=q,
k=k,
v=v,
roped_query=roped_query,
roped_key=roped_key,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
is_tf=is_tf,
)
else:
try:
frame_seqlen = int(math.prod(grid_sizes[0][1:]).item())
ref_token_len = int(getattr(self, "_num_ref_tokens", 0) or 0)
query_ref_token_len = int(getattr(self, "_query_ref_token_len", 0) or 0)
video_token_len = q.shape[1] - query_ref_token_len
num_video_frames = video_token_len // frame_seqlen if frame_seqlen > 0 else 0
video_grid_sizes = grid_sizes.clone()
video_grid_sizes[:, 0] = num_video_frames
ref_info = {
"num_slots": int(getattr(self, "_ref_num_slots", 0) or 0),
"tokens_per_slot": int(getattr(self, "_ref_tokens_per_frame", 0) or 0),
"grid": getattr(self, "_ref_grid_sizes", None),
}
if "ref_token_len" not in kv_cache:
kv_cache["ref_token_len"] = torch.tensor(
[0], dtype=torch.long, device=q.device
)
kv_cache["ref_token_len"].fill_(ref_token_len)
sink_tokens = ref_token_len + self.sink_size * frame_seqlen
kv_cache_size = kv_cache["k"].shape[1]
cache_current_start = current_start + ref_token_len
if query_ref_token_len > 0:
cache_current_start = 0
cache_current_end = ref_token_len + current_start + video_token_len
if self.use_relative_rope:
fast_rel = _REL_ROPE_CACHE_ENABLED and int(self.sink_size) == 0
if "k_raw" not in kv_cache or kv_cache["k_raw"].shape != kv_cache["k"].shape:
kv_cache["k_raw"] = torch.empty_like(kv_cache["k"])
if fast_rel:
kv_cache["k_roped"] = torch.empty_like(kv_cache["k"])
kv_cache["rel_rope_base_frame"] = 0
if fast_rel and "k_roped" not in kv_cache:
kv_cache["k_roped"] = torch.empty_like(kv_cache["k"])
kv_cache["rel_rope_base_frame"] = 0
# Reset the rope base at the start of a new stream (reset_stream
# zeroes global_end_index but reuses the cache tensors).
if fast_rel and int(kv_cache["global_end_index"].item()) == 0:
kv_cache["rel_rope_base_frame"] = 0
if self.local_attn_size != -1 and (cache_current_end > kv_cache["global_end_index"].item()) and (
video_token_len + kv_cache["local_end_index"].item() > kv_cache_size):
num_evicted_tokens = video_token_len + kv_cache["local_end_index"].item() - kv_cache_size
num_rolled_tokens = kv_cache["local_end_index"].item() - num_evicted_tokens - sink_tokens
if num_evicted_tokens < 0 or num_rolled_tokens < 0:
_dbg_print(
"relative_kv_cache.roll.bad_sizes",
current_start=current_start,
current_end=cache_current_end,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
query_ref_token_len=query_ref_token_len,
sink_tokens=sink_tokens,
kv_cache_size=kv_cache_size,
video_token_len=video_token_len,
num_evicted_tokens=num_evicted_tokens,
num_rolled_tokens=num_rolled_tokens,
)
raise RuntimeError(
f"Invalid relative KV cache roll sizes: "
f"evict={num_evicted_tokens}, roll={num_rolled_tokens}"
)
kv_cache["k_raw"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \
kv_cache["k_raw"][:,
sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone()
kv_cache["v"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \
kv_cache["v"][:,
sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone()
if fast_rel:
kv_cache["k_roped"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \
kv_cache["k_roped"][:,
sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone()
local_end_index = kv_cache["local_end_index"].item() + cache_current_end - \
kv_cache["global_end_index"].item() - num_evicted_tokens
else:
local_end_index = kv_cache["local_end_index"].item() + cache_current_end - kv_cache[
"global_end_index"].item()
local_start_index = local_end_index - video_token_len
if local_start_index < sink_tokens or local_end_index > kv_cache["k_raw"].shape[1]:
_dbg_print(
"relative_kv_cache.index.out_of_range",
current_start=current_start,
current_end=cache_current_end,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
query_ref_token_len=query_ref_token_len,
sink_tokens=sink_tokens,
kv_cache_size=kv_cache_size,
video_token_len=video_token_len,
local_start_index=local_start_index,
local_end_index=local_end_index,
kv_cache_k_raw=kv_cache["k_raw"],
kv_cache_v=kv_cache["v"],
global_end_index=kv_cache["global_end_index"],
local_end_index_tensor=kv_cache["local_end_index"],
)
raise RuntimeError(
f"Relative KV cache write out of range: "
f"[{local_start_index}:{local_end_index}] vs cache_len={kv_cache['k_raw'].shape[1]}"
)
if query_ref_token_len > 0:
kv_cache["k_raw"][:, :ref_token_len] = k[:, :query_ref_token_len].detach()
kv_cache["v"][:, :ref_token_len] = v[:, :query_ref_token_len]
kv_cache["k_raw"][:, local_start_index:local_end_index] = k[:, query_ref_token_len:].detach()
kv_cache["v"][:, local_start_index:local_end_index] = v[:, query_ref_token_len:]
max_attention_tokens = (
local_end_index - sink_tokens
if self.local_attn_size == -1
else int(self.local_attn_size) * frame_seqlen
)
recent_start = max(sink_tokens, local_end_index - max_attention_tokens)
visible_recent_tokens = local_end_index - recent_start
misalign = visible_recent_tokens % frame_seqlen
if misalign:
recent_start += misalign
visible_recent_tokens = local_end_index - recent_start
protected_video_tokens = max(0, sink_tokens - ref_token_len)
if not fast_rel:
video_key_parts = []
video_value_parts = []
if protected_video_tokens > 0:
video_key_parts.append(kv_cache["k_raw"][:, ref_token_len:sink_tokens])
video_value_parts.append(kv_cache["v"][:, ref_token_len:sink_tokens])
if visible_recent_tokens > 0:
video_key_parts.append(kv_cache["k_raw"][:, recent_start:local_end_index])
video_value_parts.append(kv_cache["v"][:, recent_start:local_end_index])
visible_video_raw = torch.cat(video_key_parts, dim=1) if video_key_parts else kv_cache["k_raw"][:, :0]
visible_video_v = torch.cat(video_value_parts, dim=1) if video_value_parts else kv_cache["v"][:, :0]
visible_video_frames = visible_video_raw.shape[1] // frame_seqlen if frame_seqlen > 0 else 0
attn_k_parts = []
attn_v_parts = []
query_parts = []
if ref_token_len > 0:
if ref_info["num_slots"] <= 0 or ref_info["tokens_per_slot"] <= 0 or ref_info["grid"] is None:
raise RuntimeError("Ref cache write requested but ref RoPE metadata is incomplete.")
ref_freqs = _build_ref_freqs(
freqs=freqs,
num_slots=ref_info["num_slots"],
tokens_per_slot=ref_info["tokens_per_slot"],
ref_grid=ref_info["grid"],
device=q.device,
)
attn_k_parts.append(
rope_apply_with_refimg(
kv_cache["k_raw"][:, :ref_token_len],
ref_freqs,
self.num_heads,
).type_as(v)
)
attn_v_parts.append(kv_cache["v"][:, :ref_token_len])
if query_ref_token_len > 0:
query_parts.append(
rope_apply_with_refimg(
q[:, :query_ref_token_len],
ref_freqs,
self.num_heads,
).type_as(v)
)
if visible_video_frames > 0:
visible_video_grid_sizes = grid_sizes.clone()
visible_video_grid_sizes[:, 0] = visible_video_frames
rel_k_frame_indices = torch.arange(
visible_video_frames, device=q.device, dtype=torch.long
)
attn_k_parts.append(
relative_rope_apply(
visible_video_raw,
visible_video_grid_sizes,
freqs,
frame_indices=rel_k_frame_indices,
).type_as(v)
)
attn_v_parts.append(visible_video_v)
if num_video_frames <= visible_video_frames:
rel_q_frame_indices = rel_k_frame_indices[-num_video_frames:]
else:
rel_q_frame_indices = torch.arange(
num_video_frames, device=q.device, dtype=torch.long
)
_rel_rope_debug_print(
"window_local",
once_key=("window_local", int(current_start)),
current_start=current_start,
frame_seqlen=frame_seqlen,
abs_frame_start=current_start // frame_seqlen if frame_seqlen > 0 else 0,
visible_video_frames=visible_video_frames,
num_video_frames=num_video_frames,
recent_start=recent_start,
local_start_index=local_start_index,
local_end_index=local_end_index,
rel_k_first=rel_k_frame_indices[0] if rel_k_frame_indices.numel() else -1,
rel_k_last=rel_k_frame_indices[-1] if rel_k_frame_indices.numel() else -1,
rel_q_first=rel_q_frame_indices[0] if rel_q_frame_indices.numel() else -1,
rel_q_last=rel_q_frame_indices[-1] if rel_q_frame_indices.numel() else -1,
ref_token_len=ref_token_len,
)
query_parts.append(
relative_rope_apply(
q[:, query_ref_token_len:],
video_grid_sizes,
freqs,
frame_indices=rel_q_frame_indices,
).type_as(v)
)
roped_query = torch.cat(query_parts, dim=1) if len(query_parts) > 1 else query_parts[0]
attn_k = torch.cat(attn_k_parts, dim=1) if len(attn_k_parts) > 1 else attn_k_parts[0]
attn_v = torch.cat(attn_v_parts, dim=1) if len(attn_v_parts) > 1 else attn_v_parts[0]
x = attention(roped_query, attn_k, attn_v)
else:
# ── Fast relative path (opt E): cache post-RoPE video keys ──
# Rope only the new block's keys/query per step; the visible
# window's keys are already roped in kv_cache["k_roped"].
# Positions use an absolute counter (base = rel_rope_base_frame)
# whose differences match the window-local scheme; re-base
# (re-rope the window once) before nearing the rotary table.
visible_video_frames = (
visible_recent_tokens // frame_seqlen if frame_seqlen > 0 else 0
)
abs_frame_start = current_start // frame_seqlen
base_frame = int(kv_cache["rel_rope_base_frame"])
new_start_pos = abs_frame_start - base_frame
rope_table = int(freqs.shape[0])
rebase_limit = min(
_REL_ROPE_REBASE_MAX_POS,
rope_table - int(self.local_attn_size) - num_video_frames,
)
need_rebase = visible_video_frames > 0 and (
(new_start_pos + num_video_frames) > rebase_limit
or new_start_pos < 0
)
debug_new_start_pos_before_rebase = new_start_pos
if need_rebase:
# Re-base so the oldest visible frame maps to position 0,
# then re-rope the whole visible window once.
oldest_visible_abs = (
abs_frame_start + num_video_frames - visible_video_frames
)
base_frame = oldest_visible_abs
kv_cache["rel_rope_base_frame"] = base_frame
win_grid = grid_sizes.clone()
win_grid[:, 0] = visible_video_frames
kv_cache["k_roped"][:, recent_start:local_end_index] = causal_rope_apply(
kv_cache["k_raw"][:, recent_start:local_end_index],
win_grid,
freqs,
start_frame=0,
).type_as(v)
new_start_pos = abs_frame_start - base_frame
else:
# Rope only the newly written block's keys.
kv_cache["k_roped"][:, local_start_index:local_end_index] = causal_rope_apply(
k[:, query_ref_token_len:],
video_grid_sizes,
freqs,
start_frame=new_start_pos,
).type_as(v)
_rel_rope_debug_print(
"fast_cache",
once_key=("fast_cache", int(current_start)),
current_start=current_start,
frame_seqlen=frame_seqlen,
abs_frame_start=abs_frame_start,
base_frame=base_frame,
new_start_pos=new_start_pos,
temporal_index_start=new_start_pos,
temporal_index_end=new_start_pos + num_video_frames - 1,
new_start_pos_before_rebase=debug_new_start_pos_before_rebase,
num_video_frames=num_video_frames,
visible_video_frames=visible_video_frames,
recent_start=recent_start,
local_start_index=local_start_index,
local_end_index=local_end_index,
ref_token_len=ref_token_len,
rebase_limit=rebase_limit,
need_rebase=need_rebase,
rope_table=rope_table,
)
roped_video_query = causal_rope_apply(
q[:, query_ref_token_len:],
video_grid_sizes,
freqs,
start_frame=new_start_pos,
).type_as(v)
video_k = kv_cache["k_roped"][:, recent_start:local_end_index]
video_v = kv_cache["v"][:, recent_start:local_end_index]
if ref_token_len > 0:
if ref_info["num_slots"] <= 0 or ref_info["tokens_per_slot"] <= 0 or ref_info["grid"] is None:
raise RuntimeError("Ref cache write requested but ref RoPE metadata is incomplete.")
ref_freqs = _build_ref_freqs(
freqs=freqs,
num_slots=ref_info["num_slots"],
tokens_per_slot=ref_info["tokens_per_slot"],
ref_grid=ref_info["grid"],
device=q.device,
)
ref_k = rope_apply_with_refimg(
kv_cache["k_raw"][:, :ref_token_len],
ref_freqs,
self.num_heads,
).type_as(v)
attn_k = torch.cat([ref_k, video_k], dim=1)
attn_v = torch.cat([kv_cache["v"][:, :ref_token_len], video_v], dim=1)
if query_ref_token_len > 0:
ref_q = rope_apply_with_refimg(
q[:, :query_ref_token_len],
ref_freqs,
self.num_heads,
).type_as(v)
roped_query = torch.cat([ref_q, roped_video_query], dim=1)
else:
roped_query = roped_video_query
else:
attn_k = video_k
attn_v = video_v
roped_query = roped_video_query
x = attention(roped_query, attn_k, attn_v)
kv_cache["global_end_index"].fill_(cache_current_end)
kv_cache["local_end_index"].fill_(local_end_index)
else:
current_start_frame = current_start // frame_seqlen
if query_ref_token_len > 0:
if ref_info["num_slots"] <= 0 or ref_info["tokens_per_slot"] <= 0 or ref_info["grid"] is None:
raise RuntimeError("Ref cache write requested but ref RoPE metadata is incomplete.")
ref_freqs = _build_ref_freqs(
freqs=freqs,
num_slots=ref_info["num_slots"],
tokens_per_slot=ref_info["tokens_per_slot"],
ref_grid=ref_info["grid"],
device=q.device,
)
roped_query = torch.cat([
rope_apply_with_refimg(q[:, :query_ref_token_len], ref_freqs, self.num_heads),
causal_rope_apply(
q[:, query_ref_token_len:],
video_grid_sizes,
freqs,
start_frame=current_start_frame,
),
], dim=1).type_as(v)
roped_key = torch.cat([
rope_apply_with_refimg(k[:, :query_ref_token_len], ref_freqs, self.num_heads),
causal_rope_apply(
k[:, query_ref_token_len:],
video_grid_sizes,
freqs,
start_frame=current_start_frame,
),
], dim=1).type_as(v)
else:
roped_query = causal_rope_apply(
q, grid_sizes, freqs, start_frame=current_start_frame).type_as(v)
roped_key = causal_rope_apply(
k, grid_sizes, freqs, start_frame=current_start_frame).type_as(v)
num_new_tokens = roped_query.shape[1]
if self.local_attn_size != -1 and (cache_current_end > kv_cache["global_end_index"].item()) and (
num_new_tokens + kv_cache["local_end_index"].item() > kv_cache_size):
num_evicted_tokens = num_new_tokens + kv_cache["local_end_index"].item() - kv_cache_size
num_rolled_tokens = kv_cache["local_end_index"].item() - num_evicted_tokens - sink_tokens
if num_evicted_tokens < 0 or num_rolled_tokens < 0:
_dbg_print(
"kv_cache.roll.bad_sizes",
current_start=current_start,
current_end=cache_current_end,
current_start_frame=current_start_frame,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
query_ref_token_len=query_ref_token_len,
sink_tokens=sink_tokens,
kv_cache_size=kv_cache_size,
num_new_tokens=num_new_tokens,
num_evicted_tokens=num_evicted_tokens,
num_rolled_tokens=num_rolled_tokens,
kv_cache_k=kv_cache["k"],
kv_cache_v=kv_cache["v"],
global_end_index=kv_cache["global_end_index"],
local_end_index_tensor=kv_cache["local_end_index"],
grid_sizes=grid_sizes,
)
raise RuntimeError(
f"Invalid KV cache roll sizes: evict={num_evicted_tokens}, roll={num_rolled_tokens}"
)
kv_cache["k"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \
kv_cache["k"][:,
sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone()
kv_cache["v"][:, sink_tokens:sink_tokens + num_rolled_tokens] = \
kv_cache["v"][:,
sink_tokens + num_evicted_tokens:sink_tokens + num_evicted_tokens + num_rolled_tokens].clone()
local_end_index = kv_cache["local_end_index"].item() + cache_current_end - \
kv_cache["global_end_index"].item() - num_evicted_tokens
local_start_index = local_end_index - num_new_tokens
else:
local_end_index = kv_cache["local_end_index"].item() + cache_current_end - kv_cache[
"global_end_index"].item()
local_start_index = local_end_index - num_new_tokens
# 只在即将越界时打印;正常路径无输出。
if local_start_index < 0 or local_end_index > kv_cache["k"].shape[1]:
_dbg_print(
"kv_cache.index.out_of_range",
current_start=current_start,
current_end=cache_current_end,
current_start_frame=current_start_frame,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
query_ref_token_len=query_ref_token_len,
sink_tokens=sink_tokens,
kv_cache_size=kv_cache_size,
num_new_tokens=num_new_tokens,
cache_current_start=cache_current_start,
kv_cache_k=kv_cache["k"],
kv_cache_v=kv_cache["v"],
roped_key=roped_key,
value=v,
global_end_index=kv_cache["global_end_index"],
local_end_index_tensor=kv_cache["local_end_index"],
grid_sizes=grid_sizes,
)
raise RuntimeError(
f"KV cache write out of range: "
f"[{local_start_index}:{local_end_index}] vs cache_len={kv_cache['k'].shape[1]}"
)
kv_cache["k"][:, local_start_index:local_end_index] = roped_key.detach()
kv_cache["v"][:, local_start_index:local_end_index] = v
attn_start = max(sink_tokens, local_end_index - self.max_attention_size)
if sink_tokens > 0 and attn_start > sink_tokens:
attn_k = torch.cat([
kv_cache["k"][:, :sink_tokens],
kv_cache["k"][:, attn_start:local_end_index],
], dim=1)
attn_v = torch.cat([
kv_cache["v"][:, :sink_tokens],
kv_cache["v"][:, attn_start:local_end_index],
], dim=1)
else:
attn_k = kv_cache["k"][:, :local_end_index]
attn_v = kv_cache["v"][:, :local_end_index]
x = attention(roped_query, attn_k, attn_v)
kv_cache["global_end_index"].fill_(cache_current_end)
kv_cache["local_end_index"].fill_(local_end_index)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"self_attn.kv_cache_path.failed",
error=repr(e),
x=x,
q=q,
k=k,
v=v,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
current_start=current_start,
cache_start=cache_start,
kv_cache_k=kv_cache.get("k", None) if isinstance(kv_cache, dict) else None,
kv_cache_v=kv_cache.get("v", None) if isinstance(kv_cache, dict) else None,
global_end_index=kv_cache.get("global_end_index", None) if isinstance(kv_cache, dict) else None,
local_end_index_tensor=kv_cache.get("local_end_index", None) if isinstance(kv_cache,
dict) else None,
)
raise
x = x.flatten(2)
try:
x = self.o(x)
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"self_attn.output_proj.failed",
error=repr(e),
x=x,
)
raise
return x
# =========================================================
# Cross-Attention
# =========================================================
class CausalWanCrossAttention(WanSelfAttention):
def forward(self, x, context, context_lens, crossattn_cache=None):
b, n, d = x.size(0), self.num_heads, self.head_dim
q = self.norm_q(self.q(x)).view(b, -1, n, d)
if crossattn_cache is not None:
if not crossattn_cache["is_init"]:
crossattn_cache["is_init"] = True
k = self.norm_k(self.k(context)).view(b, -1, n, d)
v = self.v(context).view(b, -1, n, d)
crossattn_cache["k"] = k
crossattn_cache["v"] = v
else:
k = crossattn_cache["k"]
v = crossattn_cache["v"]
else:
k = self.norm_k(self.k(context)).view(b, -1, n, d)
v = self.v(context).view(b, -1, n, d)
x = flash_attention(q, k, v, k_lens=context_lens)
x = x.flatten(2)
x = self.o(x)
return x
class CausalWanI2VCrossAttention(WanI2VCrossAttention):
def forward(self, x, context, context_lens, crossattn_cache=None):
context_img = context[:, :257]
context = context[:, 257:]
b, n, d = x.size(0), self.num_heads, self.head_dim
q = self.norm_q(self.q(x)).view(b, -1, n, d)
if crossattn_cache is not None:
if not crossattn_cache["is_init"]:
crossattn_cache["is_init"] = True
k = self.norm_k(self.k(context)).view(b, -1, n, d)
v = self.v(context).view(b, -1, n, d)
k_img = self.norm_k_img(self.k_img(context_img)).view(b, -1, n, d)
v_img = self.v_img(context_img).view(b, -1, n, d)
crossattn_cache["k"] = k
crossattn_cache["v"] = v
crossattn_cache["k_img"] = k_img
crossattn_cache["v_img"] = v_img
else:
k = crossattn_cache["k"]
v = crossattn_cache["v"]
k_img = crossattn_cache["k_img"]
v_img = crossattn_cache["v_img"]
else:
k_img = self.norm_k_img(self.k_img(context_img)).view(b, -1, n, d)
v_img = self.v_img(context_img).view(b, -1, n, d)
k = self.norm_k(self.k(context)).view(b, -1, n, d)
v = self.v(context).view(b, -1, n, d)
img_x = flash_attention(q, k_img, v_img, k_lens=None)
x = flash_attention(q, k, v, k_lens=context_lens)
x = x.flatten(2)
img_x = img_x.flatten(2)
x = x + img_x
x = self.o(x)
return x
# =========================================================
# Attention Block
# =========================================================
WAN_CROSSATTENTION_CLASSES = {
't2v_cross_attn': CausalWanCrossAttention,
'i2v_cross_attn': CausalWanI2VCrossAttention,
}
class CausalWanAttentionBlock(nn.Module):
def __init__(self,
dim,
ffn_dim,
num_heads,
local_attn_size=-1,
sink_size=0,
qk_norm=True,
cross_attn_norm=False,
eps=1e-6,
cross_attn_type="t2v_cross_attn",
use_relative_rope=False):
super().__init__()
self.dim = dim
self.ffn_dim = ffn_dim
self.num_heads = num_heads
self.local_attn_size = local_attn_size
self.qk_norm = qk_norm
self.cross_attn_norm = cross_attn_norm
self.eps = eps
self.norm1 = WanLayerNorm(dim, eps)
self.self_attn = CausalWanSelfAttention(
dim, num_heads, local_attn_size, sink_size, qk_norm, eps,
use_relative_rope=use_relative_rope,
)
self.norm3 = WanLayerNorm(
dim, eps, elementwise_affine=True
) if cross_attn_norm else nn.Identity()
self.cross_attn = WAN_CROSSATTENTION_CLASSES[cross_attn_type](dim, num_heads, (-1, -1), qk_norm, eps)
self.norm2 = WanLayerNorm(dim, eps)
self.ffn = nn.Sequential(
nn.Linear(dim, ffn_dim), nn.GELU(approximate='tanh'),
nn.Linear(ffn_dim, dim)
)
self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim ** 0.5)
def forward(
self,
x, # [B, L, C]
e, # [B, L_frame, 6, C]
seq_lens,
grid_sizes,
freqs,
context,
context_lens,
block_mask: BlockMask | None = None,
kv_cache: dict | None = None,
crossattn_cache=None,
current_start: int = 0,
cache_start: int | None = None,
):
token_level_modulation = e.shape[1] == x.shape[1]
num_frames = e.shape[1]
if (not token_level_modulation) and x.shape[1] % num_frames != 0:
_dbg_print(
"attention_block.bad_frame_token_split",
x=x,
e=e,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
current_start=current_start,
cache_start=cache_start,
)
raise RuntimeError(
f"x token length {x.shape[1]} is not divisible by e frames {num_frames}"
)
if token_level_modulation:
num_frames, frame_seqlen = x.shape[1], 1
else:
frame_seqlen = x.shape[1] // num_frames
try:
e = (self.modulation.unsqueeze(0) + e).chunk(6, dim=2)
def modulate_norm1(value):
normed = self.norm1(value)
if token_level_modulation:
return normed * (1 + e[1].squeeze(2)) + e[0].squeeze(2)
return (
normed.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[1]) + e[0]
).flatten(1, 2)
def modulate_norm2(value):
normed = self.norm2(value)
if token_level_modulation:
return normed * (1 + e[4].squeeze(2)) + e[3].squeeze(2)
return (
normed.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[4]) + e[3]
).flatten(1, 2)
def apply_gate(value, gate):
if token_level_modulation:
return value * gate.squeeze(2)
return (value.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * gate).flatten(1, 2)
y = self.self_attn(
modulate_norm1(x),
seq_lens,
grid_sizes,
freqs,
block_mask=block_mask,
kv_cache=kv_cache,
current_start=current_start,
cache_start=cache_start,
)
x = x + apply_gate(y, e[2])
def cross_attn_ffn(x, context, context_lens, e, crossattn_cache=None):
x = x + self.cross_attn(self.norm3(x), context,
context_lens, crossattn_cache=crossattn_cache)
y = self.ffn(modulate_norm2(x))
x = x + apply_gate(y, e[5])
return x
x = cross_attn_ffn(x, context, context_lens, e, crossattn_cache)
return x
except Exception as err:
if _is_checkpoint_stop_signal(err):
raise
_dbg_print(
"attention_block.forward.failed",
error=repr(err),
x=x,
e0=e[0] if isinstance(e, tuple) else e,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
context=context,
context_lens=context_lens,
block_mask=_dbg_block_mask(block_mask),
kv_cache_is_none=(kv_cache is None),
crossattn_cache_is_none=(crossattn_cache is None),
current_start=current_start,
cache_start=cache_start,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
)
raise
# ===== Causal Head:沿用 2.2 的形状,只把 e 理解为 [B, L, C] =====
class CausalHead(nn.Module):
def __init__(self, dim, out_dim, patch_size, eps=1e-6):
super().__init__()
self.dim = dim
self.out_dim = out_dim
self.patch_size = patch_size
self.eps = eps
out_dim_ = math.prod(patch_size) * out_dim
self.norm = WanLayerNorm(dim, eps)
self.head = nn.Linear(dim, out_dim_)
self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim ** 0.5)
def forward(self, x, e):
"""
和 2.2 一样:
x: [B, L_token, C]
e: [B, F, 1, C] or [B, F, C] before unsqueeze
"""
num_frames = e.shape[1]
if x.shape[1] % num_frames != 0:
_dbg_print(
"head.bad_frame_token_split",
x=x,
e=e,
num_frames=num_frames,
)
raise RuntimeError(
f"Head split failed: x_len={x.shape[1]}, e_frames={num_frames}"
)
frame_seqlen = x.shape[1] // num_frames
try:
e = (self.modulation.unsqueeze(1) + e).chunk(2, dim=2)
x = self.head(
self.norm(x).unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * (1 + e[1]) + e[0]
)
return x
except Exception as err:
if _is_checkpoint_stop_signal(err):
raise
_dbg_print(
"head.forward.failed",
error=repr(err),
x=x,
e=e[0] if isinstance(e, tuple) else e,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
)
raise
# ===== CausalWanModel 2.2 =====
class CausalWanModel(ModelMixin, ConfigMixin):
"""
基于 Wan 2.2 结构,加入:
- causal / blockwise / local attention
- flex_attention + BlockMask 训练
- KV cache 推理
- teacher forcing (通过 clean_x / aug_t)
"""
ignore_for_config = [
'patch_size', 'cross_attn_norm', 'qk_norm', 'text_dim', 'window_size'
]
_no_split_modules = ['WanAttentionBlock']
_supports_gradient_checkpointing = True
@register_to_config
def __init__(self,
model_type='t2v',
patch_size=(1, 2, 2),
text_len=512,
in_dim=16,
dim=2048,
ffn_dim=8192,
freq_dim=256,
text_dim=4096,
out_dim=16,
num_heads=16,
num_layers=32,
window_size=(-1, -1),
local_attn_size=-1,
num_frame_per_block=3,
sink_size=0,
qk_norm=True,
cross_attn_norm=True,
eps=1e-6,
act_control_in_dim=32,
use_relative_rope=False,
downscale_factor_control_adapter=8):
super().__init__()
print(f"Initializing CausalWanModel with model_type={model_type}, use_relative_rope={use_relative_rope}")
assert model_type in ['t2v', 'i2v', 'ti2v', 's2v', 'ci2v']
self.model_type = model_type
self.patch_size = patch_size
self.text_len = text_len
self.in_dim = in_dim
self.dim = dim
self.ffn_dim = ffn_dim
self.freq_dim = freq_dim
self.text_dim = text_dim
self.out_dim = out_dim
self.num_heads = num_heads
self.num_layers = num_layers
self.window_size = window_size
self.local_attn_size = local_attn_size
self.sink_size = sink_size
self.qk_norm = qk_norm
self.cross_attn_norm = cross_attn_norm
self.eps = eps
self.use_relative_rope = bool(use_relative_rope)
self.patch_embedding = nn.Conv3d(
in_dim, dim, kernel_size=patch_size, stride=patch_size
)
self.text_embedding = nn.Sequential(
nn.Linear(text_dim, dim), nn.GELU(approximate='tanh'),
nn.Linear(dim, dim)
)
self.time_embedding = nn.Sequential(
nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim)
)
self.time_projection = nn.Sequential(
nn.SiLU(), nn.Linear(dim, dim * 6)
)
# cross_attn_type = 'i2v_cross_attn' if model_type in ['i2v', 'ti2v'] else 't2v_cross_attn'
cross_attn_type = 'i2v_cross_attn' if model_type in ['i2v'] else 't2v_cross_attn'
self.blocks = nn.ModuleList([
CausalWanAttentionBlock(
dim, ffn_dim, num_heads,
local_attn_size=local_attn_size,
sink_size=sink_size,
qk_norm=qk_norm,
cross_attn_norm=cross_attn_norm,
eps=eps,
cross_attn_type=cross_attn_type,
use_relative_rope=use_relative_rope,
)
for _ in range(num_layers)
])
self.head = CausalHead(dim, out_dim, patch_size, eps)
self.act_control_adapter = SimpleAdapter(
act_control_in_dim, self.dim,
kernel_size=self.patch_size[1:], stride=self.patch_size[1:],
downscale_factor=downscale_factor_control_adapter)
self.act_control_adapter.requires_grad_(False)
assert (dim % num_heads) == 0 and (dim // num_heads) % 2 == 0
d = dim // num_heads
self.freqs = torch.cat([
rope_params(1024, d - 4 * (d // 6)),
rope_params(1024, 2 * (d // 6)),
rope_params(1024, 2 * (d // 6))
], dim=1)
self.init_weights()
self.gradient_checkpointing = False
self.block_mask: BlockMask | None = None
self._block_mask_cache_key = None
self._block_mask_cache = {}
self.num_frame_per_block = num_frame_per_block
self.independent_first_frame = False
def _set_gradient_checkpointing(self, module, value: bool = False):
self.gradient_checkpointing = value
@staticmethod
def _frame_block_token_ranges(
device: torch.device | str,
num_frames: int,
frame_seqlen: int,
num_frame_per_block=1,
independent_first_frame=False,
):
if independent_first_frame and num_frames > 0:
starts = [0]
ends = [frame_seqlen]
for frame_start in range(1, num_frames, num_frame_per_block):
frame_end = min(frame_start + num_frame_per_block, num_frames)
starts.append(frame_start * frame_seqlen)
ends.append(frame_end * frame_seqlen)
else:
starts = list(range(0, num_frames * frame_seqlen, frame_seqlen * num_frame_per_block))
ends = [
min(start + frame_seqlen * num_frame_per_block, num_frames * frame_seqlen)
for start in starts
]
return (
torch.tensor(starts, device=device, dtype=torch.long),
torch.tensor(ends, device=device, dtype=torch.long),
)
@staticmethod
def _build_block_mask_from_visibility(
block_visibility: torch.Tensor,
mask_mod,
seq_length: int,
device: torch.device | str,
block_size: int = 128,
) -> BlockMask:
"""Create a BlockMask from block-level visibility without materializing a dense token mask."""
dense_blocks = block_visibility.unsqueeze(0).unsqueeze(0).to(dtype=torch.int32)
kv_num_blocks = dense_blocks.sum(dim=-1).to(torch.int32, memory_format=torch.contiguous_format)
kv_indices = torch.argsort(dense_blocks, dim=-1, descending=True, stable=True).to(
torch.int32, memory_format=torch.contiguous_format
)
return BlockMask.from_kv_blocks(
kv_num_blocks.to(device),
kv_indices.to(device),
full_kv_num_blocks=None,
full_kv_indices=None,
BLOCK_SIZE=(block_size, block_size),
mask_mod=mask_mod,
seq_lengths=(seq_length, seq_length),
)
@staticmethod
def _blockwise_causal_visibility(
total_padded: int,
total_length: int,
ref_token_len: int,
video_length: int,
block_starts: torch.Tensor,
block_ends: torch.Tensor,
frame_seqlen: int,
local_attn_size=-1,
block_size: int = 128,
) -> torch.Tensor:
"""Build only [query_block, key_block] visibility, avoiding seq_len^2 dense masks."""
q_blocks = math.ceil(total_padded / block_size)
block_visibility = torch.zeros((q_blocks, q_blocks), dtype=torch.bool)
ref_token_len = int(ref_token_len)
video_offset = ref_token_len
video_end = ref_token_len + int(video_length)
def mark_token_interval(row: int, start: int, end: int):
start = max(0, min(int(start), total_padded))
end = max(0, min(int(end), total_padded))
if start >= end:
return
block_start = start // block_size
block_end = (end - 1) // block_size + 1
block_visibility[row, block_start:block_end] = True
frame_ranges = [(int(s), int(e)) for s, e in zip(block_starts.tolist(), block_ends.tolist())]
for q_block in range(q_blocks):
q_start = q_block * block_size
q_end = min((q_block + 1) * block_size, total_padded)
block_visibility[q_block, q_block] = True
q_has_ref = ref_token_len > 0 and q_start < ref_token_len and q_end > 0
if q_has_ref:
mark_token_interval(q_block, 0, ref_token_len)
q_video_start = max(q_start, video_offset)
q_video_end = min(q_end, video_end)
if q_video_start >= q_video_end:
continue
if ref_token_len > 0:
mark_token_interval(q_block, 0, ref_token_len)
q_video_start -= video_offset
q_video_end -= video_offset
for frame_block_start, frame_block_end in frame_ranges:
if max(q_video_start, frame_block_start) >= min(q_video_end, frame_block_end):
continue
if local_attn_size == -1:
visible_start = 0
else:
visible_start = max(0, frame_block_end - int(local_attn_size) * int(frame_seqlen))
mark_token_interval(q_block, video_offset + visible_start, video_offset + frame_block_end)
return block_visibility
@staticmethod
def _prepare_blockwise_causal_attn_mask(
device: torch.device | str,
num_frames: int,
frame_seqlen: int,
num_frame_per_block=1,
local_attn_size=-1,
independent_first_frame=False,
) -> BlockMask:
total_length = num_frames * frame_seqlen
padded_length = math.ceil(total_length / 128) * 128 - total_length
ends = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
block_starts, block_ends = CausalWanModel._frame_block_token_ranges(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=num_frame_per_block,
independent_first_frame=independent_first_frame,
)
for start, end in zip(block_starts, block_ends):
ends[start:end] = end
def attention_mask(b, h, q_idx, kv_idx):
if local_attn_size == -1:
return (kv_idx < ends[q_idx]) | (q_idx == kv_idx)
else:
return (
(kv_idx < ends[q_idx])
& (kv_idx >= (ends[q_idx] - local_attn_size * frame_seqlen))
) | (q_idx == kv_idx)
block_visibility = CausalWanModel._blockwise_causal_visibility(
total_padded=total_length + padded_length,
total_length=total_length,
ref_token_len=0,
video_length=total_length,
block_starts=block_starts,
block_ends=block_ends,
frame_seqlen=frame_seqlen,
local_attn_size=local_attn_size,
)
return CausalWanModel._build_block_mask_from_visibility(
block_visibility,
mask_mod=attention_mask,
seq_length=total_length + padded_length,
device=device,
)
@staticmethod
def _prepare_teacher_forcing_mask(
device: torch.device | str,
num_frames: int,
frame_seqlen: int,
num_frame_per_block=1,
independent_first_frame=False,
) -> BlockMask:
total_length = num_frames * frame_seqlen * 2
padded_length = math.ceil(total_length / 128) * 128 - total_length
clean_ends = num_frames * frame_seqlen
context_ends = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
noise_context_starts = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
noise_context_ends = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
noise_noise_starts = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
noise_noise_ends = torch.zeros(
total_length + padded_length, device=device, dtype=torch.long
)
block_starts, block_ends = CausalWanModel._frame_block_token_ranges(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=num_frame_per_block,
independent_first_frame=independent_first_frame,
)
for start, end in zip(block_starts, block_ends):
clean_end = torch.minimum(end, end.new_tensor(clean_ends))
context_ends[start:end] = clean_end
for clean_start, clean_end in zip(block_starts, block_ends):
start = clean_ends + clean_start
end = clean_ends + clean_end
noise_noise_starts[start:end] = start
noise_noise_ends[start:end] = end
noise_context_ends[start:end] = clean_start
def attention_mask(b, h, q_idx, kv_idx):
clean_mask = (q_idx < clean_ends) & (kv_idx < context_ends[q_idx])
C1 = (kv_idx < noise_noise_ends[q_idx]) & (kv_idx >= noise_noise_starts[q_idx])
C2 = (kv_idx < noise_context_ends[q_idx]) & (kv_idx >= noise_context_starts[q_idx])
noise_mask = (q_idx >= clean_ends) & (C1 | C2)
eye_mask = (q_idx == kv_idx)
return eye_mask | clean_mask | noise_mask
block_mask = create_block_mask(
attention_mask,
B=None,
H=None,
Q_LEN=total_length + padded_length,
KV_LEN=total_length + padded_length,
_compile=False,
device=device,
)
return block_mask
@staticmethod
def _prepare_blockwise_causal_attn_mask_with_ref(
device: torch.device | str,
num_frames: int,
frame_seqlen: int,
ref_token_len: int,
num_frame_per_block=1,
local_attn_size=-1,
independent_first_frame=False,
) -> BlockMask:
video_length = num_frames * frame_seqlen
total_length = int(ref_token_len) + video_length
padded_length = math.ceil(total_length / 128) * 128 - total_length
ends = torch.zeros(video_length + padded_length, device=device, dtype=torch.long)
block_starts, block_ends = CausalWanModel._frame_block_token_ranges(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=num_frame_per_block,
independent_first_frame=independent_first_frame,
)
for start, end in zip(block_starts, block_ends):
ends[start:end] = end
def attention_mask(b, h, q_idx, kv_idx):
q_is_ref = q_idx < ref_token_len
kv_is_ref = kv_idx < ref_token_len
q_video = torch.clamp(q_idx - ref_token_len, min=0, max=max(video_length - 1, 0))
kv_video = torch.clamp(kv_idx - ref_token_len, min=0, max=max(video_length - 1, 0))
if local_attn_size == -1:
video_visible = (kv_video < ends[q_video]) | (q_video == kv_video)
else:
video_visible = (
(kv_video < ends[q_video])
& (kv_video >= (ends[q_video] - local_attn_size * frame_seqlen))
) | (q_video == kv_video)
video_mask = (q_idx >= ref_token_len) & (q_idx < total_length) & (
kv_is_ref | ((kv_idx >= ref_token_len) & (kv_idx < total_length) & video_visible)
)
return (q_is_ref & kv_is_ref) | video_mask | (q_idx == kv_idx)
block_visibility = CausalWanModel._blockwise_causal_visibility(
total_padded=total_length + padded_length,
total_length=total_length,
ref_token_len=ref_token_len,
video_length=video_length,
block_starts=block_starts,
block_ends=block_ends,
frame_seqlen=frame_seqlen,
local_attn_size=local_attn_size,
)
return CausalWanModel._build_block_mask_from_visibility(
block_visibility,
mask_mod=attention_mask,
seq_length=total_length + padded_length,
device=device,
)
@staticmethod
def _prepare_teacher_forcing_mask_with_ref_i2v(
device: torch.device | str,
num_frames: int,
frame_seqlen: int,
ref_token_len: int,
num_frame_per_block=1,
independent_first_frame=False,
) -> BlockMask:
branch_len = num_frames * frame_seqlen
total_length = int(ref_token_len) + branch_len * 2
padded_length = math.ceil(total_length / 128) * 128 - total_length
total_padded = total_length + padded_length
clean_offset = int(ref_token_len)
noisy_offset = clean_offset + branch_len
context_ends = torch.zeros(total_padded, device=device, dtype=torch.long)
noise_context_ends = torch.zeros(total_padded, device=device, dtype=torch.long)
noise_noise_starts = torch.zeros(total_padded, device=device, dtype=torch.long)
noise_noise_ends = torch.zeros(total_padded, device=device, dtype=torch.long)
block_starts, block_ends = CausalWanModel._frame_block_token_ranges(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=num_frame_per_block,
independent_first_frame=independent_first_frame,
)
for start, end in zip(block_starts, block_ends):
context_ends[clean_offset + start:clean_offset + end] = clean_offset + end
noise_start = noisy_offset + start
noise_end = noisy_offset + end
noise_noise_starts[noise_start:noise_end] = noise_start
noise_noise_ends[noise_start:noise_end] = noise_end
noise_context_ends[noise_start:noise_end] = clean_offset + start
def attention_mask(b, h, q_idx, kv_idx):
q_is_ref = q_idx < ref_token_len
kv_is_ref = kv_idx < ref_token_len
clean_q = (q_idx >= clean_offset) & (q_idx < noisy_offset)
noisy_q = (q_idx >= noisy_offset) & (q_idx < total_length)
clean_mask = clean_q & (kv_idx >= clean_offset) & (kv_idx < context_ends[q_idx])
noisy_self = noisy_q & (kv_idx >= noise_noise_starts[q_idx]) & (kv_idx < noise_noise_ends[q_idx])
noisy_history = noisy_q & (kv_idx >= clean_offset) & (kv_idx < noise_context_ends[q_idx])
return (q_is_ref & kv_is_ref) | ((~q_is_ref) & (kv_is_ref | clean_mask | noisy_self | noisy_history)) | (q_idx == kv_idx)
return create_block_mask(
attention_mask,
B=None,
H=None,
Q_LEN=total_padded,
KV_LEN=total_padded,
_compile=False,
device=device,
)
def _maybe_build_block_mask(
self,
device,
num_frames,
frame_seqlen,
is_teacher_forcing,
ref_token_len=0,
independent_first_frame=None,
):
"""
只有 mask 配置变化时重建,避免不同帧数/分辨率/TF 状态复用旧 BlockMask。
正常不打印;只有后续 flex_attention 崩时会打印 mask repr 和 shape 上下文。
"""
if independent_first_frame is None:
independent_first_frame = self.independent_first_frame
cache_key = (
str(device),
int(num_frames),
int(frame_seqlen),
int(self.num_frame_per_block),
int(self.local_attn_size),
bool(is_teacher_forcing),
bool(independent_first_frame),
int(ref_token_len),
)
cached_mask = self._block_mask_cache.get(cache_key)
if cached_mask is not None:
self.block_mask = cached_mask
self._block_mask_cache_key = cache_key
return
if is_teacher_forcing and ref_token_len > 0:
block_mask = self._prepare_teacher_forcing_mask_with_ref_i2v(
device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
num_frame_per_block=self.num_frame_per_block,
independent_first_frame=bool(independent_first_frame),
)
elif is_teacher_forcing:
block_mask = self._prepare_teacher_forcing_mask(
device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=self.num_frame_per_block,
independent_first_frame=bool(independent_first_frame),
)
elif ref_token_len > 0:
block_mask = self._prepare_blockwise_causal_attn_mask_with_ref(
device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
ref_token_len=ref_token_len,
num_frame_per_block=self.num_frame_per_block,
local_attn_size=self.local_attn_size,
independent_first_frame=bool(independent_first_frame),
)
else:
block_mask = self._prepare_blockwise_causal_attn_mask(
device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
num_frame_per_block=self.num_frame_per_block,
local_attn_size=self.local_attn_size,
independent_first_frame=bool(independent_first_frame),
)
self.block_mask = block_mask
self._block_mask_cache_key = cache_key
self._block_mask_cache[cache_key] = block_mask
def _apply_control_adapters(self, x, act_context=None, act_context_scale=1.0):
"""复用action control adapter 逻辑;异常时打印 shape。"""
try:
y_action = None
if act_context is not None and hasattr(self,
"act_control_adapter") and self.act_control_adapter is not None:
x_new = []
y_action = [self.act_control_adapter(u.unsqueeze(0)) for u in act_context]
for u, v in zip(x, y_action):
t_f = u.shape[2]
c_f = v.shape[2]
if t_f > c_f:
offset = t_f - c_f
u = torch.cat([u[:, :, :offset], u[:, :, offset:] + v * act_context_scale], dim=2)
else:
u = u + v * act_context_scale
x_new.append(u)
x = x_new
return x, y_action
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"control_adapter.failed",
error=repr(e),
act_context=act_context,
act_context_scale=act_context_scale,
)
raise
def _prepare_ref_tokens(self, ref_latents=None, ref_mask=None, batch_size=None, device=None, dtype=None):
if ref_latents is None:
return None
ref_latents = ref_latents.detach()
in_ch_need = self.patch_embedding.in_channels
if ref_latents.ndim == 4:
ref_latents = ref_latents.unsqueeze(0).unsqueeze(3)
elif ref_latents.ndim == 5 and ref_latents.shape[2] == in_ch_need:
ref_latents = ref_latents.unsqueeze(3)
elif ref_latents.ndim == 5:
ref_latents = ref_latents.unsqueeze(0)
if ref_latents.ndim != 6:
raise ValueError(
f"ref_latents must be [B,K,C,T,H,W], [K,C,T,H,W], or [K,C,H,W], got {tuple(ref_latents.shape)}"
)
if device is not None or dtype is not None:
ref_latents = ref_latents.to(device=device, dtype=dtype)
B, K, C, T, H, W = ref_latents.shape
if batch_size is not None and B == 1 and batch_size != 1:
ref_latents = ref_latents.expand(batch_size, -1, -1, -1, -1, -1)
B = batch_size
elif batch_size is not None and B != batch_size:
raise ValueError(f"ref_latents batch size {B} does not match video batch size {batch_size}")
if ref_mask is not None:
ref_mask = ref_mask.detach()
if device is not None or dtype is not None:
ref_mask = ref_mask.to(device=device, dtype=dtype)
if ref_mask.ndim == 1:
ref_mask = ref_mask.unsqueeze(0)
if ref_mask.shape[0] == 1 and B != 1:
ref_mask = ref_mask.expand(B, -1)
if ref_mask.shape[:2] != (B, K):
raise ValueError(f"ref_mask shape {tuple(ref_mask.shape)} does not match ref slots {(B, K)}")
else:
ref_mask = ref_latents.new_ones(B, K)
ref_flat = ref_latents.reshape(B * K, C, T, H, W)
if C < in_ch_need:
pad = ref_flat.new_zeros(B * K, in_ch_need - C, T, H, W)
ref_flat = torch.cat([ref_flat, pad], dim=1)
elif C > in_ch_need:
ref_flat = ref_flat[:, :in_ch_need]
ref_features = self.patch_embedding(ref_flat)
_, _, patch_t, patch_h, patch_w = ref_features.shape
tokens_per_slot = patch_t * patch_h * patch_w
ref_tokens = ref_features.flatten(2).transpose(1, 2)
ref_tokens = ref_tokens.reshape(B, K, tokens_per_slot, self.dim)
ref_tokens = ref_tokens * ref_mask[:, :, None, None].to(dtype=ref_tokens.dtype)
return {
"tokens": ref_tokens.reshape(B, K * tokens_per_slot, self.dim),
"num_slots": int(K),
"tokens_per_slot": int(tokens_per_slot),
"token_len": int(K * tokens_per_slot),
"grid": (int(patch_t), int(patch_h), int(patch_w)),
}
def _estimate_ref_token_len(self, ref_latents=None):
if ref_latents is None:
return 0
in_ch_need = self.patch_embedding.in_channels
if ref_latents.ndim == 4:
k, _, h, w = ref_latents.shape
t = 1
elif ref_latents.ndim == 5 and ref_latents.shape[2] == in_ch_need:
_, k, _, h, w = ref_latents.shape
t = 1
elif ref_latents.ndim == 5:
k, _, t, h, w = ref_latents.shape
elif ref_latents.ndim == 6:
_, k, _, t, h, w = ref_latents.shape
else:
return 0
patch_t, patch_h, patch_w = self.patch_size
return int(k) * (int(t) // int(patch_t)) * (int(h) // int(patch_h)) * (int(w) // int(patch_w))
def _prepend_ref_tokens(self, x, e=None, ref_info=None, *args, **kwargs):
return x, e
def _expand_frame_modulation_to_tokens(self, e0, frame_seqlen):
if e0.shape[1] == 0:
return e0
return e0.repeat_interleave(int(frame_seqlen), dim=1)
def _zero_ref_modulation(self, batch_size, token_len, device, dtype):
ref_t = torch.zeros((batch_size, 1), dtype=torch.long, device=device)
ref_e = self.time_embedding(
sinusoidal_embedding_1d(self.freq_dim, ref_t.flatten()).to(dtype)
)
ref_e0 = self.time_projection(ref_e).unflatten(1, (6, self.dim)).unflatten(dim=0, sizes=ref_t.shape)
return ref_e0.expand(-1, int(token_len), -1, -1)
# ==== 推理:逐帧/逐块,带 kv_cache ====
def _forward_inference(
self,
x,
t,
context,
seq_len,
y=None,
kv_cache: list[dict] | None = None,
crossattn_cache: dict = None,
current_start: int = 0,
cache_start: int = 0,
clip_fea=None,
act_context=None,
ref_latents=None,
ref_mask=None,
act_context_scale=1.0,
):
if torch.is_grad_enabled():
return self._forward_train(
x=x,
t=t,
context=context,
seq_len=seq_len,
y=y,
clip_fea=clip_fea,
act_context=act_context,
ref_latents=ref_latents,
ref_mask=ref_mask,
act_context_scale=act_context_scale,
)
if self.model_type == 'i2v':
assert y is not None
device = self.patch_embedding.weight.device
if self.freqs.device != device:
self.freqs = self.freqs.to(device)
if seq_len is None:
seq_len = x.shape[2] * x.shape[-2] * x.shape[-1] // (
self.patch_size[1] * self.patch_size[2] * self.patch_size[0])
if y is not None and self.model_type in ['i2v', 'ti2v']:
x = [torch.cat([u, v], dim=0) for u, v in zip(x, y)]
x = [self.patch_embedding(u.unsqueeze(0)) for u in x]
x, _ = self._apply_control_adapters(
x,
act_context=act_context,
act_context_scale=act_context_scale,
)
grid_sizes = torch.stack(
[torch.tensor(u.shape[2:], dtype=torch.long, device=device) for u in x]
)
x = [u.flatten(2).transpose(1, 2) for u in x]
seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long, device=device)
assert seq_lens.max() <= seq_len
x = torch.cat(x, dim=0)
e = self.time_embedding(
sinusoidal_embedding_1d(self.freq_dim, t.flatten()).to(x.dtype))
e0 = self.time_projection(e).unflatten(1, (6, self.dim)).unflatten(dim=0, sizes=t.shape)
context_lens = None
context = self.text_embedding(
torch.stack([
torch.cat(
[u, u.new_zeros(self.text_len - u.size(0), u.size(1))]
)
for u in context
])
)
if clip_fea is not None and hasattr(self, "img_emb"):
context_clip = self.img_emb(clip_fea)
context = torch.concat([context_clip, context], dim=1)
ref_info = self._prepare_ref_tokens(
ref_latents=ref_latents,
ref_mask=ref_mask,
batch_size=x.shape[0],
device=device,
dtype=x.dtype,
)
N_r = ref_info["token_len"] if ref_info is not None else 0
include_ref_tokens = ref_info is not None and (kv_cache is None or current_start == 0)
query_ref_token_len = N_r if include_ref_tokens else 0
if include_ref_tokens:
ref_tokens = ref_info["tokens"]
e0 = self._expand_frame_modulation_to_tokens(e0, math.prod(grid_sizes[0][1:]).item())
ref_e0 = self._zero_ref_modulation(
batch_size=x.shape[0],
token_len=query_ref_token_len,
device=device,
dtype=x.dtype,
)
x = torch.cat([ref_tokens, x], dim=1)
e0 = torch.cat([ref_e0, e0], dim=1)
for block in self.blocks:
block.self_attn._is_teacher_forcing = False
block.self_attn._num_ref_tokens = N_r
block.self_attn._query_ref_token_len = query_ref_token_len
if ref_info is not None:
block.self_attn._ref_num_slots = ref_info["num_slots"]
block.self_attn._ref_tokens_per_frame = ref_info["tokens_per_slot"]
block.self_attn._ref_grid_sizes = ref_info["grid"]
else:
block.self_attn._ref_num_slots = 0
block.self_attn._ref_tokens_per_frame = None
block.self_attn._ref_grid_sizes = None
kwargs = dict(
e=e0,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
freqs=self.freqs,
context=context,
context_lens=context_lens,
block_mask=self.block_mask,
)
def create_custom_forward(module):
def custom_forward(*inputs, **kw):
return module(*inputs, **kw)
return custom_forward
for idx, block in enumerate(self.blocks):
if torch.is_grad_enabled() and self.gradient_checkpointing:
kwargs.update(
dict(
kv_cache=kv_cache[idx],
current_start=current_start,
cache_start=cache_start,
)
)
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x, **kwargs, use_reentrant=False,
)
else:
kwargs.update(
{
"kv_cache": kv_cache[idx],
"crossattn_cache": crossattn_cache[idx] if crossattn_cache is not None else None,
"current_start": current_start,
"cache_start": cache_start
}
)
x = block(x, **kwargs)
if query_ref_token_len > 0:
x = x[:, query_ref_token_len:]
x = self.head(x, e.unflatten(dim=0, sizes=t.shape).unsqueeze(2))
x = self.unpatchify(x, grid_sizes)
return torch.stack(x)
# ==== 训练:flex_attention + BlockMask,支持 teacher forcing ====
def _forward_train(
self,
x,
t,
context,
seq_len,
y=None,
clean_x=None,
aug_t=None,
clip_fea=None,
act_context=None,
ref_latents=None,
ref_mask=None,
act_context_scale=1.0,
current_start: int = 0,
):
try:
if self.model_type == 'i2v':
assert y is not None
device = self.patch_embedding.weight.device
if self.freqs.device != device:
self.freqs = self.freqs.to(device)
if seq_len is None:
seq_len = x.shape[2] * x.shape[-2] * x.shape[-1] // (
self.patch_size[1] * self.patch_size[2] * self.patch_size[0])
num_frames = x.shape[2]
frame_seqlen = x.shape[-2] * x.shape[-1] // (self.patch_size[1] * self.patch_size[2])
print("_forward_train: x[0]", x[0].shape)
absolute_start_frame = int(current_start) // int(frame_seqlen) if frame_seqlen > 0 else 0
mask_independent_first_frame = bool(self.independent_first_frame) and absolute_start_frame == 0
ref_token_len = self._estimate_ref_token_len(ref_latents)
self._maybe_build_block_mask(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
is_teacher_forcing=(clean_x is not None),
ref_token_len=ref_token_len,
independent_first_frame=mask_independent_first_frame,
)
if y is not None and self.model_type in ['i2v', 'ti2v']:
x = [torch.cat([u, v], dim=0) for u, v in zip(x, y)]
x = [self.patch_embedding(u.unsqueeze(0)) for u in x]
x, y_action = self._apply_control_adapters(
x,
act_context=act_context,
act_context_scale=act_context_scale,
)
grid_sizes = torch.stack(
[torch.tensor(u.shape[2:], dtype=torch.long, device=device) for u in x]
)
x = [u.flatten(2).transpose(1, 2) for u in x]
seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long, device=device)
assert seq_lens.max() <= seq_len
max_len = seq_lens[0].item()
x = torch.cat([
torch.cat([u, u.new_zeros(1, max_len - u.size(1), u.size(2))], dim=1)
for u in x
])
e = self.time_embedding(
sinusoidal_embedding_1d(self.freq_dim, t.flatten()).to(x.dtype))
e0 = self.time_projection(e).unflatten(1, (6, self.dim)).unflatten(dim=0, sizes=t.shape)
context_lens = None
context = self.text_embedding(
torch.stack([
torch.cat(
[u, u.new_zeros(self.text_len - u.size(0), u.size(1))]
)
for u in context
])
)
if clip_fea is not None and hasattr(self, "img_emb"):
context_clip = self.img_emb(clip_fea)
context = torch.concat([context_clip, context], dim=1)
ref_info = self._prepare_ref_tokens(
ref_latents=ref_latents,
ref_mask=ref_mask,
batch_size=x.shape[0],
device=device,
dtype=x.dtype,
)
N_r = ref_info["token_len"] if ref_info is not None else 0
if N_r != ref_token_len:
ref_token_len = N_r
self._maybe_build_block_mask(
device=device,
num_frames=num_frames,
frame_seqlen=frame_seqlen,
is_teacher_forcing=(clean_x is not None),
ref_token_len=ref_token_len,
independent_first_frame=mask_independent_first_frame,
)
if clean_x is not None:
if y is not None and self.model_type in ['i2v', 'ti2v']:
clean_x = [torch.cat([u, v], dim=0) for u, v in zip(clean_x, y)]
clean_x = [self.patch_embedding(u.unsqueeze(0)) for u in clean_x]
if act_context is not None and hasattr(self,
"act_control_adapter") and self.act_control_adapter is not None:
x_new = []
for u, v in zip(clean_x, y_action):
t_f = u.shape[2]
c_f = v.shape[2]
if t_f > c_f:
offset = t_f - c_f
u = torch.cat([u[:, :, :offset], u[:, :, offset:] + v * act_context_scale], dim=2)
else:
u = u + v * act_context_scale
x_new.append(u)
clean_x = x_new
clean_x = [u.flatten(2).transpose(1, 2) for u in clean_x]
seq_lens_clean = torch.tensor(
[u.size(1) for u in clean_x], dtype=torch.long, device=device
)
assert seq_lens_clean.max() <= seq_len
max_len_clean = seq_lens_clean[0].item()
clean_x = torch.cat([
torch.cat(
[u, u.new_zeros(1, max_len_clean - u.size(1), u.size(2))], dim=1
)
for u in clean_x
])
if clean_x.shape[1] != x.shape[1]:
_dbg_print(
"teacher_forcing.clean_noisy_len_mismatch",
clean_x=clean_x,
x=x,
seq_lens_clean=seq_lens_clean,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
)
raise RuntimeError(
f"clean_x token len {clean_x.shape[1]} != noisy x token len {x.shape[1]}"
)
if aug_t is None:
aug_t = torch.zeros_like(t)
e_clean = self.time_embedding(
sinusoidal_embedding_1d(self.freq_dim, aug_t.flatten()).to(x.dtype))
e0_clean = self.time_projection(e_clean).unflatten(1, (6, self.dim)).unflatten(dim=0, sizes=t.shape)
if ref_info is not None:
ref_tokens = ref_info["tokens"]
branch_token_len = x.shape[1]
ref_e0 = self._zero_ref_modulation(
batch_size=x.shape[0],
token_len=N_r,
device=device,
dtype=x.dtype,
)
e0 = self._expand_frame_modulation_to_tokens(e0, frame_seqlen)
e0_clean = self._expand_frame_modulation_to_tokens(e0_clean, frame_seqlen)
x = torch.cat([ref_tokens, clean_x, x], dim=1)
e0 = torch.cat([ref_e0, e0_clean, e0], dim=1)
noisy_branch_start = ref_token_len + branch_token_len
else:
x = torch.cat([clean_x, x], dim=1)
e0 = torch.cat([e0_clean, e0], dim=1)
noisy_branch_start = x.shape[1] // 2
elif ref_info is not None:
ref_tokens = ref_info["tokens"]
branch_token_len = x.shape[1]
ref_e0 = self._zero_ref_modulation(
batch_size=x.shape[0],
token_len=N_r,
device=device,
dtype=x.dtype,
)
e0 = self._expand_frame_modulation_to_tokens(e0, frame_seqlen)
x = torch.cat([ref_tokens, x], dim=1)
e0 = torch.cat([ref_e0, e0], dim=1)
noisy_branch_start = ref_token_len
else:
noisy_branch_start = 0
for block in self.blocks:
block.self_attn._is_teacher_forcing = clean_x is not None
block.self_attn._num_ref_tokens = N_r
block.self_attn._query_ref_token_len = N_r
if ref_info is not None:
block.self_attn._ref_num_slots = ref_info["num_slots"]
block.self_attn._ref_tokens_per_frame = ref_info["tokens_per_slot"]
block.self_attn._ref_grid_sizes = ref_info["grid"]
else:
block.self_attn._ref_num_slots = 0
block.self_attn._ref_tokens_per_frame = None
block.self_attn._ref_grid_sizes = None
kwargs = dict(
e=e0,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
freqs=self.freqs,
context=context,
context_lens=context_lens,
block_mask=self.block_mask,
current_start=current_start,
)
def create_custom_forward(module):
def custom_forward(*inputs, **kw):
return module(*inputs, **kw)
return custom_forward
for block_idx, block in enumerate(self.blocks):
try:
if torch.is_grad_enabled() and self.gradient_checkpointing:
x = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
x, **kwargs, use_reentrant=False,
)
else:
x = block(x, **kwargs)
except Exception as e_block:
if _is_checkpoint_stop_signal(e_block):
raise
_dbg_print(
"model.forward_train.block.failed",
error=repr(e_block),
block_idx=block_idx,
x=x,
e=e0,
seq_lens=seq_lens,
grid_sizes=grid_sizes,
context=context,
block_mask=_dbg_block_mask(self.block_mask),
clean_x_is_not_none=(clean_x is not None),
)
raise
if ref_info is not None or clean_x is not None:
x = x[:, noisy_branch_start:]
elif clean_x is not None:
x = x[:, x.shape[1] // 2:]
x = self.head(x, e.unflatten(dim=0, sizes=t.shape).unsqueeze(2))
x = self.unpatchify(x, grid_sizes)
return torch.stack(x)
except Exception as err:
if _is_checkpoint_stop_signal(err):
raise
_dbg_print(
"model.forward_train.failed",
error=repr(err),
t=t,
seq_len=seq_len,
y=y,
clean_x_is_not_none=(clean_x is not None),
aug_t=aug_t,
clip_fea=clip_fea,
act_context=act_context,
)
raise
# ===== 对外 forward:根据是否传 kv_cache 判断 train/inference =====
# def forward(self, *args, **kwargs):
# if kwargs.get('kv_cache', None) is not None:
# return self._forward_inference(*args, **kwargs)
# else:
# return self._forward_train(*args, **kwargs)
def forward(self, *args, **kwargs):
# 关键:只要当前在建梯度图,就不要走 kv_cache inference path。
# 不要依赖 self.training,因为蒸馏/采样训练里经常是 eval() + grad enabled。
if torch.is_grad_enabled() and kwargs.get("kv_cache", None) is not None:
for k in ["kv_cache", "crossattn_cache", "current_start", "cache_start"]:
kwargs.pop(k, None)
return self._forward_train(*args, **kwargs)
if kwargs.get("kv_cache", None) is not None:
return self._forward_inference(*args, **kwargs)
return self._forward_train(*args, **kwargs)
# ===== 其余保持 2.2 一致 =====
def unpatchify(self, x, grid_sizes):
c = self.out_dim
out = []
try:
for u, v in zip(x, grid_sizes.tolist()):
u = u[:math.prod(v)].view(*v, *self.patch_size, c)
u = torch.einsum('fhwpqrc->cfphqwr', u)
u = u.reshape(c, *[i * j for i, j in zip(v, self.patch_size)])
out.append(u)
return out
except Exception as e:
if _is_checkpoint_stop_signal(e):
raise
_dbg_print(
"unpatchify.failed",
error=repr(e),
x=x,
grid_sizes=grid_sizes,
out_dim=c,
patch_size=self.patch_size,
)
raise
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
nn.init.xavier_uniform_(self.patch_embedding.weight.flatten(1))
for m in self.text_embedding.modules():
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, std=.02)
for m in self.time_embedding.modules():
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, std=.02)
nn.init.zeros_(self.head.head.weight)