Skip to content

Sliding Window Attention (AR video gen) + KV cache lifecycle management - #198

Open
merceod wants to merge 10 commits into
mainfrom
cosmos3_swa
Open

Sliding Window Attention (AR video gen) + KV cache lifecycle management#198
merceod wants to merge 10 commits into
mainfrom
cosmos3_swa

Conversation

@merceod

@merceod merceod commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds windowed autoregressive video generation to M*: instead of denoising a whole clip at once, the video is produced window-by-window, and each finished window streams to the decoder while the next one denoises. Two layers:

Engine (model-agnostic):

  • PagedAllocationManager.protect_prefix / release_oldest: free the oldest pages of a live request's KV stream (whole-page front compaction) while a protected prefix (e.g. the text-prompt K/V) survives i.e. the core primitive a sliding-window KV lifecycle needs. A prefix_epoch counter lets caches of prefix-derived views (the dense-attention fast path) detect mid-request context changes, and CPU offload/reload round-trips the new state.
  • mstar/engine/windowing.py: WindowSchedule (pure window arithmetic; commit spans tile the sequence exactly, schedules pad to whole windows) and WindowedKVSession (drives protect/release against the cache handle so models never hand-roll page bookkeeping).

Cosmos3 (first adopter, opt-in):

  • A video_gen_ar walk: the existing denoise loop run window-by-window, emitting each finished window's latents on a streaming edge to a new vae_decoder_ar node in its own partition. The decoder consumes one window per chunk, decodes it behind a re-decoded left context (seam-free with the causal Wan VAE), and assembles the final mp4 so decode of window k overlaps denoising of window k+1.
  • Chained conditioning mode (window_mode: "chained"): each window is a full bidirectional denoise conditioned on the previous window's tail via the existing v2v clean-frame machinery, so it works with the released checkpoints and lifts the clip-length ceiling (e.g. 381-frame videos).
  • Everything is gated by enable_windowed_video (default off): node set, walks, partitions, and outputs of existing deployments are unchanged, and requests opt in per call (window_frames / overlap_frames via extra_body). New serving config: configs/cosmos3_nano_ar.yaml.

Cross-window attention through committed K/V with eviction (built on the release primitives above) lands next on this branch.

How was it tested?

  • Unit tests: test/modular/test_kv_release.py + test_windowing.py (allocator invariants under randomized grow/release sequences, protection rules, offload round-trip, thread-safety; schedule tiling/overlap/padding properties; page-floor release behavior) and windowed wiring + decoder-assembly tests in the cosmos3 suite - the decoder's context-re-decode-and-trim reconstruction is checked against a whole-stream decode oracle.
  • Full CPU suite: the failure set is byte-identical to main's baseline after each commit (no new failures; all new tests pass).
  • Zero regression on existing serving: fixed-seed outputs on an unchanged config are byte-identical to the recorded baselines (t2i at 320×192 and 832×480, t2v at 320×192), and the windowed-enabled config reproduces the same t2i bytes.
  • End-to-end serving (H200): windowed 57-frame (3-window) and 381-frame (16-window) generations: frame counts exact, every output visually inspected (window boundaries seam-free; gradual long-horizon drift is the known limit of conditioning-only chaining and is what the upcoming KV-context mode addresses), server logs clean.
  • To be extended as the branch grows: KV-mode parity against a reference implementation, TP/SP configs, 480p/720p tiers, and the second cluster.

Checklist

  • ruff check . passes
  • Added or updated tests / docs where relevant

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should these test files be moved into ./test/?

tail = st.get("ar_tail")
if tail is None:
pixels = self._decode_pixels(new)
else:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do some reshuffling here:

"ar_tail" is obtained from stream_tail[:,:,-st["ar_ctx"]:] (line 2271), so tail.shape[2] <= ar_ctz. Then, ctx = tail[:, :, -st["ar_ctx"]:] (line 2263) is a noop.

Because it's a noop, torch.cat([ctx, new.to(ctx.dtype)], dim=2) (line 2264) is the same computation as torch.cat([tail, new.to(tail.dtype)], dim=2) (line 2270).

We can hoist the conditional assignment on line 2270 into the if-else statements above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants