Spaces:
Running on Zero
Running on Zero
File size: 8,882 Bytes
1811aab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | """Masked / cached attention for the MiniMax-H3 blocks.
musubi's H3 Attention runs dense FlashAttention over the whole packed sequence. install() patches its
forward with two extra modes, selected per block call through `active(...)`:
mask : torch flex_attention with a BlockMask over the whole packed sequence (training, reference)
dense: a masks.DenseDF instead of a BlockMask -- the same training mask as FlashAttention-3 prefix calls
kv : the call's rows attend to cached keys/values of earlier rows plus their own (KV-cache sampling,
FlashAttention-3; see KV_ATTENTION);
with store=list the call's own keys/values are appended per layer. A mask may be combined with
kv to fill the cache (chunk 0 together with the condition rows).
The mode is set inside each block call (not around the loop) so that activation-checkpoint
recomputation in backward sees it too.
"""
from __future__ import annotations
import contextlib
import torch
import torch.nn.functional as F
from torch.nn.attention.flex_attention import flex_attention
from musubi_tuner.minimax_h3 import model as h3model
from .layout import CHUNK_SIZES
from .masks import CHUNK_ROWS, DenseDF
_compiled_flex = torch.compile(flex_attention, dynamic=True)
# KV-cache attention over the concatenated past: "fa3" FlashAttention-3 (23% faster per sampled chunk);
# "sdpa" torch SDPA (the earlier behaviour, kept for tests)
KV_ATTENTION = "fa3"
_state = {"mask": None, "kv": None}
_orig_forward = h3model.Attention.forward
_orig_rope = h3model._apply_rope_split_half # kept for tests/test_samplers.py
def _forward(self, hidden_states, rotation_table=None):
mask, kv = _state["mask"], _state["kv"]
if mask is None and kv is None:
return _orig_forward(self, hidden_states, rotation_table)
b, s, _ = hidden_states.shape
q, k, v = self.qkv_proj(hidden_states).split(self.inner_dim, dim=-1)
q = self.q_norm(q.reshape(b, s, self.num_heads, self.head_dim))
k = self.k_norm(k.reshape(b, s, self.num_heads, self.head_dim))
v = v.reshape(b, s, self.num_heads, self.head_dim)
if rotation_table is not None:
q = rope(q, rotation_table)
k = rope(k, rotation_table)
dt = hidden_states.dtype if hidden_states.dtype in (torch.float16, torch.bfloat16) else torch.bfloat16
if isinstance(mask, DenseDF):
out = dense_df(q.to(dt), k.to(dt), v.to(dt), mask)
return self.out_proj(out.reshape(b, s, self.inner_dim))
q, k, v = (t.transpose(1, 2).contiguous().to(dt) for t in (q, k, v)) # [B,H,S,D]
if kv is not None and kv.get("store") is not None:
kv["store"].append((k, v))
if mask is not None:
out = _compiled_flex(q, k, v, block_mask=mask)
else:
pieces = kv["past"][self._rtcwm_layer] if kv.get("past") is not None else []
if kv.get("causal_blocks"): # the call's rows are blocks of n rows, block p seeing the
n = kv["causal_blocks"] # past and blocks <= p (the proxy rows of a span)
pk = torch.cat([_key(p) for p in pieces], 2).transpose(1, 2)
pv = torch.cat([p[1] for p in pieces], 2).transpose(1, 2)
qs, ks, vs = (t.transpose(1, 2) for t in (q, k, v))
out = torch.cat([_fa3(qs[:, i:i + n], torch.cat([pk, ks[:, :i + n]], 1), torch.cat([pv, vs[:, :i + n]], 1))
for i in range(0, s, n)], 1)
return self.out_proj(out.reshape(b, s, self.inner_dim))
if pieces: # concatenated per layer: one layer's copy at a time;
k = torch.cat([_key(p) for p in pieces] + [k], 2) # a piece may carry a pending time shift
v = torch.cat([p[1] for p in pieces] + [v], 2)
if KV_ATTENTION == "sdpa":
out = F.scaled_dot_product_attention(q, k, v).transpose(1, 2)
else:
out = _fa3(q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2))
return self.out_proj(out.reshape(b, s, self.inner_dim))
return self.out_proj(out.transpose(1, 2).reshape(b, s, self.inner_dim))
def _key(piece):
"""Cached keys [B,H,S,D] of a piece (k, v) or (k, v, rotation): the rotation (rtcwm.model.sampler._moved)
moves them on the time axis -- RoPE rotations compose -- without storing a moved copy."""
if len(piece) == 2:
return piece[0]
return rope(piece[0].transpose(1, 2), piece[2]).transpose(1, 2)
def _fa3(q, k, v):
from flash_attn_interface import flash_attn_func
out = flash_attn_func(q, k, v)
return out[0] if isinstance(out, tuple) else out
def dense_df(q, k, v, lay: DenseDF):
"""Attention of a packed training span [head | DUV 0..5 | audio | video 0..n-1] under df_mask, as
unmasked FlashAttention-3 calls over key prefixes (see masks.DenseDF). q, k, v [B,S,H,D]."""
h, a = lay.head_rows, lay.audio_rows
dr, cr = lay.duv_rows // len(CHUNK_SIZES), CHUNK_ROWS
d1 = h + lay.duv_rows
v0 = d1 + a
assert q.shape[1] == v0 + lay.n_chunks * cr, (q.shape, lay)
duv = lambda t, p: t[:, h + p * dr:h + (p + 1) * dr]
vid = lambda t, c: t[:, v0 + c * cr:v0 + (c + 1) * cr]
def main(t):
parts = [t[:, :h], t[:, d1:v0]]
for c in range(lay.n_chunks):
parts += [duv(t, c), vid(t, c)]
return torch.cat(parts, 1)
km, vm = main(k), main(v)
kp, vp = (torch.cat([t[:, :h], t[:, d1:v0], t[:, h:d1]], 1) for t in (k, v))
st = h + a # static keys lead both orders
n = st + (dr + cr if lay.static_sees_first else 0)
o_st = _fa3(torch.cat([q[:, :h], q[:, d1:v0]], 1), km[:, :n], vm[:, :n])
o_duv = [_fa3(duv(q, p), kp[:, :st + (p + 1) * dr], vp[:, :st + (p + 1) * dr]) for p in range(len(CHUNK_SIZES))]
o_vid = [_fa3(vid(q, c), km[:, :st + (c + 1) * (dr + cr)], vm[:, :st + (c + 1) * (dr + cr)])
for c in range(lay.n_chunks)]
return torch.cat([o_st[:, :h], *o_duv, o_st[:, h:], *o_vid], 1)
def rope(hidden_states, rotation_table):
"""musubi's _apply_rope_split_half written element-wise. The original multiplies every (token, head,
pair) by its 2x2 rotation with a batched matmul -- one tiny cuBLAS gemv per few thousand elements,
~40% of a sampling step. Same rotation table, accumulated in fp32 like the matmul."""
pairs = rotation_table.shape[-3]
r = rotation_table.float() # [B,S,1,pairs,2,2] = [[cos, -sin], [sin, cos]]
a, b = hidden_states[..., :pairs].float(), hidden_states[..., pairs:2 * pairs].float()
rotated = torch.cat((r[..., 0, 0] * a + r[..., 0, 1] * b, r[..., 1, 0] * a + r[..., 1, 1] * b), dim=-1)
return torch.cat((rotated.to(hidden_states.dtype), hidden_states[..., 2 * pairs:]), dim=-1)
_orig_mod_scale_shift, _orig_mod_gate = h3model._mod_scale_shift, h3model._mod_gate # kept for tests
_row_index = {}
def _rows(batch_segments, device):
"""Per-row modulation index of the (single-sample) segment plan, built once per forward: every block
of a forward call shares the same plan object."""
key = (id(batch_segments), device)
hit = _row_index.get(key)
if hit is not None and hit[0] is batch_segments:
return hit[1]
(segments,) = batch_segments
seg = torch.tensor(segments, dtype=torch.long)
idx = torch.repeat_interleave(seg[:, 2], seg[:, 1] - seg[:, 0]).to(device, non_blocking=True)
_row_index.clear()
_row_index[key] = (batch_segments, idx)
return idx
def mod_scale_shift(hidden_states, shift, scale, batch_segments):
"""musubi's _mod_scale_shift without the Python loop over segments (hundreds of small kernels per block
with the text's token tags): the same x * (1 + scale) + shift, gathered per row."""
idx = _rows(batch_segments, hidden_states.device)
return hidden_states * (1.0 + scale[idx]).unsqueeze(0) + shift[idx].unsqueeze(0)
def mod_gate(residual, update, gate, batch_segments):
"""musubi's _mod_gate, vectorised the same way: residual + update * gate."""
idx = _rows(batch_segments, residual.device)
return residual + update * gate[idx].unsqueeze(0)
def install(model) -> None:
"""Patch H3 attention, its RoPE and its AdaLN modulation (idempotent) and number the layers for the KV
cache."""
h3model.Attention.forward = _forward
h3model._apply_rope_split_half = rope # the released (unmasked) forward too
h3model._mod_scale_shift, h3model._mod_gate = mod_scale_shift, mod_gate
for i, blk in enumerate(model.blocks):
(blk.attn if hasattr(blk, "attn") else blk.module.attn)._rtcwm_layer = i # FSDP-wrapped or not
@contextlib.contextmanager
def active(mask=None, kv=None):
_state["mask"], _state["kv"] = mask, kv
try:
yield
finally:
_state["mask"], _state["kv"] = None, None
|