refactor(kv): index_copy_ for KV-cache writes (enabler for multi-user batching + CUDA Graphs) - #53
Open
prashant182 wants to merge 3 commits into
Open
Conversation
perf(dit): eliminate ~5600 .item() syncs from DiT forward (-13% generate())
E1 of the autoresearch sequence — see /workspace/lingbot-world-artifacts/EXPERIMENTS.md. Replaces `kv_cache["k"][:, current_start:current_end] = roped_key` with `kv_cache["k"].index_copy_(1, kv_write_index, roped_key)` in the local_attn_size == -1 fast-path. kv_write_index is a [seq_lens]-shape tensor built once per chunk in generate() via torch.arange and threaded through as a kwarg. Mirrored in both the non-SP (CausalWanSelfAttention.forward) and SP (sp_attn_forward_causal) paths. Why - Multi-tenant prerequisite: at B>1 different users can be batched into one forward, each writing the same positions of their own KV slab. Slice-assign with a Python-int range works at B=1 but doesn't generalize cleanly; index_copy_ does. - Graph capture: removes one of two Python-int slice sources keeping the DiT forward out of CUDA Graphs. (The cache READ slice `cache["k"][:, :local_end_index]` is still Python-int — that's the next experiment, E2.) Verification - Isolated test (test_e1_index_copy.py): B=1 and B=2, 7-chunk sequences, output and cache state bit-equal to slice path. - End-to-end bench MD5 ed2f82628308a3f8acd9b7935bb84401 (locked). - generate() 13414 ms — within noise of the post-B3 baseline (13523 ms). This commit ships no perf gain on its own; it's the enabler. Defaults preserved: every new kwarg defaults to None; the slice path remains the eager fallback for callers that don't pass kv_write_index.
This was referenced May 20, 2026
Contributor
Author
|
Nudge for review when you have a moment please cc @Robbyant @JingyeChen @qiuyu96 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces
kv_cache[\"k\"][:, current_start:current_end] = roped_keywithkv_cache[\"k\"].index_copy_(1, kv_write_index, roped_key)in thelocal_attn_size == -1fast-path.kv_write_indexis a[seq_lens]-shape tensor built once per chunk ingenerate()and threaded as a kwarg. Mirrored in both non-SP (CausalWanSelfAttention.forward) and SP (sp_attn_forward_causal) paths.Bit-identical at B=1 (MD5
ed2f82628308a3f8acd9b7935bb84401), works correctly at B=2 (verified in an isolated test).What this is and isn't
This PR ships no perf win on its own at B=1 — it's a refactor that swaps one correct primitive for another. The reason it's worth landing now is that it opens two doors that the slice-indexing form keeps shut:
current_startworks at B=1 but doesn't generalize to multi-user inference, where each batch element may be writing the same positions of its own KV slab.index_copy_does generalize — the samekv_write_indexis correct for all batch elements in homogeneous batching (different users on the same chunk timeline). Heterogeneous batching needs PagedAttention; explicitly out of scope here.torch.compile(mode='reduce-overhead'). The cache READ slice (cache[\"k\"][:, :local_end_index]) is still Python-int; addressing that is a separate experiment.Verification
CausalWanSelfAttention.forward's control flow with tiny dims (no FSDP/SP). Three cases:torch.compileprobe —dynamic=Truecollapses unique graph count from 5 → 2 (read-slice work pending for a follow-up).ed2f82628308a3f8acd9b7935bb84401matches the locked baseline;generate()13414 ms is within run-to-run noise of the post-B3 baseline (13523 ms).Non-regressive design
Every new kwarg (
kv_write_index) defaults toNone. WithNone, the slice path remains the eager fallback. External callers ofWanModelFast.forward,sp_dit_forward_causal, or the attention forwards keep working unchanged.Stack
Stacked on #51 (B3). Reported diff shrinks once that lands.