Spaces:
Running on Zero
Running on Zero
File size: 18,486 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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | """Chunk-autoregressive streaming generation.
KVSampler works inside one packed span (rtcwm.model.packing.Window): chunk 0 runs together with the
condition rows (fixed condition times, the span mask of rtcwm.model.masks); a finished chunk runs once more
at model time 1 to add its keys/values (chunk 0 also the condition ones, kept per piece: text + Picture 1,
each proxy chunk, audio), and a denoising step of chunk c >= 1 runs only its own rows against the cache --
the static pieces, proxy chunks <= c (no future control) and the history. Equal to the full packed
forward (tests/test_samplers.py).
Step rules:
euler : the CWM flow sampler (velocity Euler steps), many steps
x0 : few-step (distilled students): predict the clean chunk, re-noise it to the next level
generate_stream produces a whole clip chunk by chunk, with no restart. Stream chunk k is generated with
its attention span, chunks j..k (j = span_start(k)), packed with that span's conditions and positions;
the history chunks j..k-1 (already generated, clean) are re-run into the cache first. This is exactly the
training situation of diffusion forcing with a clean prefix. kv_reuse=True instead keeps the history's keys
and values across chunks (moved to the new positions), re-running only the conditions -- faster, approximate.
"""
from __future__ import annotations
from typing import Callable
import torch
from cwm_h3_inference.constants import VIDEO_SHIFT
from .layout import CLEAN_T, NUM_CHUNKS, SPAN_CHUNKS, chunk_row_slice, chunk_shape, span_start
from .masks import df_mask
from .objectives import make_window, shift
from .packing import Window, run_blocks, video_output
def schedule(steps: int, device, shift_value: float = VIDEO_SHIFT) -> torch.Tensor:
"""steps + 1 noise levels from 1 to 0, flow-shifted."""
return shift(torch.linspace(1.0, 0.0, steps + 1, dtype=torch.float64, device=device), shift_value).float()
class KVSampler:
"""`train=True` builds the cache under no_grad (its tensors may then enter an autograd graph) and
checkpoints gradient steps; otherwise everything runs under inference_mode."""
def __init__(self, model, win: Window, train: bool = False):
self.model, self.win, self.train = model, win, train
self.ca_kv = None # per layer: condition pieces [head, proxy 0..5, audio], cached with chunk 0
self.chunks_kv = [] # per finished chunk: [(k, v)] per layer; None once out of the window
def _no_grad(self):
return torch.no_grad() if self.train else torch.inference_mode()
def past(self, c: int):
if c == 0:
return None # chunk 0 runs with the condition rows themselves
hist = self.chunks_kv[max(0, c - (SPAN_CHUNKS - 1)):c]
assert all(h is not None for h in hist)
return [[self.ca_kv[li][0]] + self.ca_kv[li][1:2 + c] + [self.ca_kv[li][-1]] + [h[li] for h in hist]
for li in range(len(self.ca_kv))]
def step(self, c: int, latents, model_t: float, past=None, store=None, grad: bool = False):
"""One forward of chunk c (latents [1,24,5,H,W]) at model time model_t -> its velocity."""
if grad:
# autocast caches the bf16 casts of the (fp32) LoRA weights for the whole autocast region; the
# casts made by earlier no-grad steps carry no autograd history and would cut the gradient
torch.clear_autocast_cache()
ctx = torch.enable_grad() if grad else self._no_grad()
with ctx:
win = self.win
hv = win.video_rows(self.model, latents)
t_rows = torch.full((hv.shape[1],), float(model_t))
tags = torch.zeros(hv.shape[1], dtype=torch.int64)
pos = win.target_pos[chunk_row_slice(c)]
if c == 0:
if getattr(self, "mask0", None) is None:
self.mask0 = df_mask(win.head_rows, win.duv_rows, win.audio_rows, 1, win.dev,
static_sees_first=win.static_sees_first)
hidden, temb, inv = run_blocks(self.model, torch.cat([win.rows, hv], 1),
torch.cat([win.cond_times(), t_rows]), torch.cat([win.tags, tags]),
torch.cat([win.ca_pos, pos]), mask=self.mask0, train=grad,
kv=dict(past=None, store=store))
return video_output(self.model, hidden, temb, inv, win.rows.shape[1], latents.shape[2])
hidden, temb, inv = run_blocks(self.model, hv, t_rows, tags, pos, train=grad,
kv=dict(past=past if past is not None else self.past(c), store=store))
return video_output(self.model, hidden, temb, inv, 0, latents.shape[2])
def add_conditions(self, static=None):
"""Cache the condition pieces alone: a span that does not start the stream, whose condition rows see
no video row (rtcwm.model.masks), so they come out exactly as in add_chunk(0, ...). static: the
static pieces [(head, audio)] per layer of an earlier such span -- the same in every one -- so only
the proxy rows are run, each proxy chunk seeing the static rows and the proxy chunks up to it."""
assert not self.win.static_sees_first
win, store = self.win, []
if static is None:
mask = df_mask(win.head_rows, win.duv_rows, win.audio_rows, 0, win.dev, static_sees_first=False)
with self._no_grad():
run_blocks(self.model, win.rows, win.cond_times(), win.tags, win.ca_pos, mask=mask,
kv=dict(past=None, store=store))
self.ca_kv = [self._pieces(k, v) for k, v in store]
return
rows = slice(win.head_rows, win.head_rows + win.duv_rows)
dr = win.duv_rows // NUM_CHUNKS
with self._no_grad():
run_blocks(self.model, win.rows[:, rows], win.cond_times()[rows], win.tags[rows], win.ca_pos[rows],
kv=dict(past=[[head, audio] for head, audio in static], store=store, causal_blocks=dr))
self.ca_kv = [[head] + [(k[:, :, a:a + dr].clone(), v[:, :, a:a + dr].clone()) for a in range(0, win.duv_rows, dr)]
+ [audio] for (head, audio), (k, v) in zip(static, store)]
def _pieces(self, k, v):
"""Condition keys/values [B,H,rows,D] -> pieces [head, proxy 0..5, audio] (copies: views would keep
the whole run alive)."""
w = self.win
dr = w.duv_rows // NUM_CHUNKS
cuts = [0, w.head_rows] + [w.head_rows + (p + 1) * dr for p in range(NUM_CHUNKS)] + [w.rows.shape[1]]
return [(k[:, :, a:b].clone(), v[:, :, a:b].clone()) for a, b in zip(cuts[:-1], cuts[1:])]
def add_chunk(self, c: int, latents, past=None, model_t: float = CLEAN_T):
"""Cache the keys/values of finished chunk c (run at model time model_t: 1 = clean)."""
store = []
self.step(c, latents.detach(), model_t, past=past, store=store)
assert len(store) == len(self.model.blocks)
if c == 0: # split off the condition pieces
n = self.win.rows.shape[1]
self.ca_kv = []
for li in range(len(store)): # layer by layer
k, v = store[li]
self.ca_kv.append(self._pieces(k[:, :, :n], v[:, :, :n]))
store[li] = (k[:, :, n:].clone(), v[:, :, n:].clone())
del k, v
self.chunks_kv.append(store)
old = c + 1 - SPAN_CHUNKS # no later chunk looks at it any more
if old >= 0:
self.chunks_kv[old] = None
def generate_chunk(self, c: int, noise, sigmas, rule: str = "euler", gen=None, grad_step: int | None = None,
cache: bool = True, exit_step: int | None = None, keep_last_kv: bool = False):
"""Denoise chunk c from `noise` over `sigmas`. x0 rule only: with exit_step=k the chunk is the clean
prediction of step k (self forcing's random exit; later steps are skipped), with grad_step=k also
with its graph kept. cache: add the finished chunk to the cache (not needed when no later chunk of
this window follows). keep_last_kv: keep the keys/values the chunk's rows had in its last step (the
one its output comes from) in self.last_kv -- a history without the extra clean forward of
add_chunk (HISTORY_FROM_LAST_STEP)."""
past = self.past(c)
x, out = noise, None
stop = grad_step if grad_step is not None else exit_step
for i in range(len(sigmas) - 1):
s, s_next = float(sigmas[i]), float(sigmas[i + 1])
g = grad_step == i
last = i == stop or i == len(sigmas) - 2
store = [] if keep_last_kv and last else None
v = self.step(c, x, 1.0 - s, past=past, grad=g, store=store).float()
if store is not None:
self.last_kv = [(k.detach(), v_.detach()) for k, v_ in store]
if rule == "euler":
x = x + (s - s_next) * v
continue
x0 = x + s * v # noisy = (1-s) x0 + s n, v = x0 - n
if i == stop or i == len(sigmas) - 2:
out = x0
break
with self._no_grad():
x = (1 - s_next) * x0 + s_next * torch.randn(x0.shape, generator=gen).to(x0.device)
out = x if rule == "euler" else out
if cache and c < NUM_CHUNKS - 1:
self.add_chunk(c, out, past=past)
return out
ROTATE_IN_ATTENTION = True # kv_reuse: cached keys keep their positions, moved by rotation inside attention
HISTORY_FROM_LAST_STEP = True # kv_reuse, sampling only: cache a chunk with the keys/values of its last denoising
# step (noisy input) instead of one more clean forward -- one forward fewer per chunk,
# and the noisy history damps the drift of a long rollout (a training rollout gets the
# same history from rollout(history_noise=...))
PROXY_ONLY_CONDITIONS = False # kv_reuse: on a span move re-run only the proxy rows against the shared static
# pieces (experimental, pending tests/test_conditions.py) instead of all conditions
def _move(model, kv, delta: float, dev):
"""[cached chunk, its pending time shift] after the span moved by delta. Normally the keys stay as they
were cached and only the shift accumulates (_moved hands it to attention), so every user of a chunk --
including the autograd graphs of a training rollout, which keep what they attended to until the
backward pass -- shares one copy. ROTATE_IN_ATTENTION = False re-rotates a copy of the keys at every
move (the earlier behaviour, kept for tests)."""
store, shift = kv
if not ROTATE_IN_ATTENTION:
return [_shift_time(model, store, delta, dev), 0.0]
return [store, shift + delta]
def _moved(model, store, shift: float, dev, tables: dict):
"""Per-layer pieces of a cached chunk at its current position: (k, v) or (k, v, rotation by shift)."""
if shift == 0:
return store
tab = tables.get(shift)
if tab is None:
tab = tables[shift] = model._rotation_table(torch.tensor([[[shift, 0.0, 0.0]]], device=dev), store[0][0].dtype)
return [(k, v, tab) for k, v in store]
def _share_static(ca, static, sees_first: bool):
"""Static condition pieces (caption + Picture 1, audio) of spans that do not start the stream see only
static rows at fixed positions, so they are the same in every such span: keep the first span's and
hand it to the later ones (the re-run's own copies are dropped), one copy instead of one per span."""
if sees_first:
return static
if static is None:
return [(layer[0], layer[-1]) for layer in ca]
for layer, (head, audio) in zip(ca, static):
layer[0], layer[-1] = head, audio
return static
def _shift_time(model, store, delta: float, dev):
"""Cached keys re-positioned by delta on the time axis: RoPE rotations compose, so rotating the keys
of position p by delta gives exactly the keys of position p + delta (values carry no position)."""
from .attention import rope
table = model._rotation_table(torch.tensor([[[delta, 0.0, 0.0]]], device=dev), store[0][0].dtype)
return [(rope(k.transpose(1, 2), table).transpose(1, 2).contiguous(), v) for k, v in store]
@torch.inference_mode()
def generate_stream(model, window_sample: Callable[[int], dict], n_chunks: int, steps: int, gen, dev,
rule: str = "euler", shift_value: float = VIDEO_SHIFT, history_noise: float = 0.0,
kv_reuse: bool = False, on_chunk=None) -> torch.Tensor:
"""Stream chunks 0..n_chunks-1 -> [1,24,5*n_chunks,H,W]. window_sample(j) returns the inputs of the
span starting at stream chunk j (rtcwm.train.dataset.load_window); on_chunk(k, seconds) reports progress.
history_noise s > 0: the history chunks enter the cache noised to level s (model time 1 - s), as
diffusion forcing trains them -- damps the drift of a long rollout on its own output.
kv_reuse: keep each history chunk's keys/values from when it was cached and only move them to the
new span's positions (a rotation applied inside attention, _move); only the condition rows (whose proxy
changes with the span) are re-run, alone. Saves the re-run of the history, but its keys and values were
computed in the earlier spans -- an approximation of the exact default."""
out = _stream(model, window_sample, n_chunks, schedule(steps, dev, shift_value), gen, dev, rule, history_noise,
kv_reuse, on_chunk=on_chunk)
return torch.cat(out, 2)
def rollout(model, window_sample: Callable[[int], dict], n_chunks: int, sigmas, gen, dev, exit_steps,
grad_chunks=(), kv_reuse: bool = True, history_noise: float = 0.0, on_chunk=None) -> list[torch.Tensor]:
"""Self-forcing rollout of a training student (x0 rule), the generate_stream procedure except that
chunk k stops at its step exit_steps[k] and is that step's clean prediction (and so the history of the
later chunks); the chunks in grad_chunks keep the graph of that step (the history they attend to enters
detached). Returns the chunks [1,24,5,H,W]. Every rank must pass the same exit_steps / grad_chunks:
they fix the sequence of block calls, and so of the FSDP gathers. history_noise s > 0: every history
chunk but the stream's first enters the cache noised to level s -- with s = sigmas[-2] the history a
HISTORY_FROM_LAST_STEP sampler caches (the last step's input), whatever step the chunk exited at."""
return _stream(model, window_sample, n_chunks, sigmas, gen, dev, "x0", history_noise, kv_reuse, on_chunk=on_chunk,
train=True, exit_steps=exit_steps, grad_chunks=set(grad_chunks), clean_first=True)
def _stream(model, window_sample, n_chunks, sig, gen, dev, rule, history_noise, kv_reuse, on_chunk=None,
train: bool = False, exit_steps=None, grad_chunks=frozenset(), clean_first: bool = False) -> list[torch.Tensor]:
import time
from musubi_tuner.minimax_h3.packing import _video_t_spans
chunk_span = sum(_video_t_spans(chunk_shape()[2])) # time-axis length of one chunk
out, hist_lat, hist_kv, ca, prev_j = [], [], {}, None, None
static, tables = None, {} # see _share_static / _moved
for k in range(n_chunks):
t0 = time.time()
j = span_start(k)
with torch.no_grad():
win = make_window(model, window_sample(j), dev, gen)
kvs = KVSampler(model, win, train=train)
with kvs._no_grad():
if kv_reuse:
if prev_j is not None and j != prev_j: # the span moved: drop its old first chunk,
delta = -(j - prev_j) * chunk_span # move the rest, re-run the conditions
hist_kv = {i: _move(model, kv, delta, dev) for i, kv in hist_kv.items() if i >= j}
ca = None
if k > j and ca is None: # (a moved span never starts the stream:
kvs.add_conditions(static if PROXY_ONLY_CONDITIONS else None) # its conditions see no video, and the
ca = kvs.ca_kv # span's first chunk keeps its keys/values)
static = _share_static(ca, static, win.static_sees_first)
kvs.ca_kv, kvs.chunks_kv = ca, [_moved(model, *hist_kv[i], dev, tables) for i in range(j, k)]
else:
for c in range(k - j): # the history of this span, re-run
kvs.add_chunk(c, *hist_lat[j + c])
noise = torch.randn(chunk_shape(), generator=gen).to(dev)
e = exit_steps[k] if exit_steps is not None else None
last_kv = HISTORY_FROM_LAST_STEP and not train and kv_reuse and k > j and k < n_chunks - 1
x = kvs.generate_chunk(k - j, noise, sig, rule, gen, grad_step=e if k in grad_chunks else None, cache=False,
exit_step=e, keep_last_kv=last_kv).float()
out.append(x)
with kvs._no_grad():
h, hn = x.detach(), 0.0 if clean_first and k == 0 else history_noise
if hn > 0:
h = (1 - hn) * h + hn * torch.randn(h.shape, generator=gen).to(dev)
hist_lat.append((h, 1.0 - hn))
if kv_reuse and k < n_chunks - 1: # cache the new chunk for the next spans
if k == j: # (only k = 0: it runs with the conditions)
kvs.add_chunk(0, h, model_t=1.0 - hn)
ca, hist_kv[k] = kvs.ca_kv, [kvs.chunks_kv[0], 0.0]
static = _share_static(ca, static, win.static_sees_first)
elif last_kv:
hist_kv[k] = [kvs.last_kv, 0.0]
else:
kvs.add_chunk(k - j, h, model_t=1.0 - hn)
hist_kv[k] = [kvs.chunks_kv[-1], 0.0]
prev_j = j
del kvs, win
if on_chunk:
on_chunk(k, time.time() - t0)
return out
|