Skip to content

[None][perf] Overlap DSA heuristic prev_topk write-back on the aux stream - #16666

Open
longcheng-nv wants to merge 1 commit into
NVIDIA:mainfrom
longcheng-nv:perf/prevtopk-aux-stream-copy
Open

[None][perf] Overlap DSA heuristic prev_topk write-back on the aux stream#16666
longcheng-nv wants to merge 1 commit into
NVIDIA:mainfrom
longcheng-nv:perf/prevtopk-aux-stream-copy

Conversation

@longcheng-nv

@longcheng-nv longcheng-nv commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Description

The per-layer heuristic top-k feedback copy in Indexer.sparse_attn_indexer
(this step's decode top-k → next step's pre_idx hint,
metadata.heuristic_prev_topk[local_layer].copy_(...)) is a strided gather
that sits on the main stream's critical path, once per indexer layer per
decode step. Nothing in the current step consumes it — the next reader is the
next decode step's same layer — so this PR forks it onto the Indexer's
existing aux stream right after the top-k kernel and joins it in the same
layer's MLA forward once core sparse attention has been enqueued. The copy
then overlaps with the layer's heaviest decode work instead of delaying it.

Design points:

  • Same-layer fork/join. Every fork is matched with a join inside a single
    forward, which CUDA graph capture requires (cudaStreamEndCapture rejects
    unjoined forks), and the join restores ordering before the next layer
    overwrites the shared topk_indices_buffer rows the copy reads. No
    cross-layer or cross-step event state is needed.
  • Stable-address buffers only. Source and destination are persistent
    graph-pool buffers; the captured copy stays valid on every replay and no
    record_stream bookkeeping is needed.
  • Gated on do_multi_stream(), matching maybe_execute_in_parallel
    policy: the fork engages inside CUDA graph capture (where replay makes the
    stream/event host overhead free); eager execution keeps the original inline
    copy byte-for-byte.
  • Covers both consumers: the DSA (V3.2) path in forward_dsa_attn and the
    DeepSeek-V4 path in forward_impl_with_deepseek_v4; DSA "shared" indexer
    layers (indexer is None) are unaffected.

This also benefits the upcoming GVR top-k e2e wiring (#16420), which consumes
the same heuristic_prev_topk feedback loop.

Test Coverage

  • Pattern-level CUDA graph smoke (standalone): capture with the same-layer
    join succeeds; replayed feedback values are step-correct across replays
    (write@replay N is read@replay N+1); capture without the join fails
    with cudaErrorStreamCaptureUnjoined — confirming the join placement is
    load-bearing, not defensive.
  • Existing e2e coverage: test_fp8_blockscale[heuristic_topk_mtp1]
    (TestDeepSeekV32, cuda_graph=True) exercises the forked path end-to-end
    on Blackwell.

Performance Evidence (measured)

Two instruments, mutually consistent: a per-iteration nsys A/B that resolves
the effect (statistically significant), and an end-to-end TPOT A/B that
correctly reads zero — the effect size (~0.2% of TPOT) is below what e2e
timing can resolve.

Correction (2026-08-12): an earlier revision of this section reported
e2e TPOT reductions of +2–4% (MTP=0/1/2). Those numbers are retracted as
measurement artifacts: their baseline absolute TPOTs are inconsistent with
later same-node reruns, and their magnitude is ~10–20× above the physical
ceiling of this change (per-iteration bound below). The evidence that stands
is the nsys per-iteration A/B plus an e2e null result consistent with it.

nsys per-iteration A/B (mechanism + magnitude)

DeepSeek-V4 Pro FP4, 8xB200, TEP8, BS=1, ISL 64K (real-data prompt), OSL 2048,
MTP=0, enable_heuristic_topk: true, CUDA graphs ON. Arms differ only in this
PR's files on the same C++ build; ABBA run order (PR r1 → base r1 → base r2 →
PR r2); nsys window = decode iters 500–550; per-iteration times taken from a
once-per-iteration anchor kernel (49 deltas per run).

per-iter decode base this PR delta
10% trimmed mean 10.6501 ms 10.6351 ms −15.0 µs/iter
median 10.6488 ms 10.6335 ms −15.3 µs
min (floor) 10.600 ms 10.575 ms −25 µs

Mann-Whitney U z = −4.60 (p ≈ 4e-6); all four runs direction-consistent.

Mechanism, from the same traces: in the base arm 0/300 feedback copies overlap
any other-stream work — the copy is a serial node between the top-k kernel and
the index transform, with ~1.5 µs node-dispatch gaps on each edge. With this
PR, 300/300 copies overlap core sparse attention on the aux stream, and the
join introduces no main-stream bubble.

−15 µs/iter over 30 indexer layers ≈ 0.5 µs/layer removed, i.e. roughly the
copy-kernel duration leaving the critical path (the per-edge dispatch gaps are
not recovered — they re-materialize on the surviving top-k→transform edge).
This bounds the effect at 0.14–0.23% of TPOT for this config, and ≤ ~0.4%
for any realistic config (more indexer layers / MTP raise the per-iter µs, but
iteration time grows alongside).

e2e TPOT A/B (null result, consistent with the bound)

DeepSeek-V3.2-Exp FP4, 8xB200, TEP8, BS=1, MTP=3, same paired interleaved
protocol (one SWE-bench prompt, ISL ~68.7K, within-node pairs, TTFT-matched),
10 warm pairs: paired mean TPOT reduction −0.42% (mean-TPOT) / −0.16%
(median-TPOT), SE ≈ 0.7%
— statistically zero. This is the expected reading:
the predicted effect for this config is +0.18–0.36% (≈64 copies/iter ×
~0.5–0.8 µs on a ~5.6 ms TPOT), and resolving it against the observed per-pair
σ ≈ 2.2–2.4% would take on the order of 250–1400 pairs. The e2e instrument
cannot see this change; the per-iteration instrument does.

What this PR is (and isn't)

A structural win with a small, rigorously measured latency gain: it removes a
serial copy node from every indexer layer's decode critical path (proven
off-path in traces), at zero cost to the eager path, reusing the per-layer
fork/join pattern already shipped for maybe_execute_in_parallel. It is not
an e2e-visible speedup on its own; its value compounds with anything that
lengthens the per-layer overlap window or shortens iteration time.

PR Checklist

  • Please check this after reviewing the above items as appropriate for this PR.

Dev Engineer Review

  • Added auxiliary-stream fork/join handling for heuristic TopK copy in DSA and DeepSeek-V4 paths.
  • Preserved synchronous copying when do_multi_stream() is disabled.
  • Preserved CUDA graph capture and persistent graph-pool buffer usage.
  • Added Indexer.maybe_join_prev_topk_copy() with pending-state cleanup.
  • The DSA and DeepSeek-V4 join points occur after core attention enqueue, which preserves stream ordering.
  • Shared DSA indexer layers remain unchanged.
  • nsys per-iteration A/B shows a statistically significant −15 µs/iter (0.14–0.23% of TPOT) on DeepSeek-V4 Pro BS=1; e2e TPOT A/B reads statistical zero, consistent with the sub-0.4% predicted effect size (earlier +2–4% e2e figures retracted as artifacts).
  • No configuration or test-list changes were identified.
  • Three CI runs failed. The failures require investigation before merge.

QA Engineer Review

No test changes.

@longcheng-nv
longcheng-nv force-pushed the perf/prevtopk-aux-stream-copy branch 2 times, most recently from 767b036 to 90c4b5c Compare August 6, 2026 03:03
@longcheng-nv
longcheng-nv marked this pull request as ready for review August 6, 2026 03:03
@longcheng-nv
longcheng-nv requested a review from a team as a code owner August 6, 2026 03:03
@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change adds asynchronous heuristic TopK write-back coordination to Indexer. DSA and DeepSeek-V4 sparse attention paths join pending copies after scheduling core attention work.

Changes

DSA TopK write-back

Layer / File(s) Summary
Asynchronous TopK copy coordination
tensorrt_llm/_torch/attention_backend/sparse/dsa/indexer.py
Indexer adds CUDA events and pending state. Decode write-back uses aux_stream when multi-stream execution is enabled and retains synchronous copying otherwise. A join method clears pending state after synchronization.
Sparse attention forward-path synchronization
tensorrt_llm/_torch/attention_backend/sparse/dsa/module.py, tensorrt_llm/_torch/attention_backend/sparse/deepseek_v4/module.py
Both sparse attention paths call maybe_join_prev_topk_copy() after core attention scheduling when an indexer is present.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: qijune, kris1025

Sequence Diagram(s)

sequenceDiagram
  participant SparseAttention
  participant Indexer
  participant aux_stream
  participant CUDA_events
  SparseAttention->>Indexer: Schedule heuristic TopK write-back
  Indexer->>aux_stream: Fork asynchronous copy
  aux_stream->>CUDA_events: Record copy completion
  SparseAttention->>Indexer: maybe_join_prev_topk_copy()
  Indexer->>CUDA_events: Wait for pending copy
  CUDA_events-->>Indexer: Signal completion
  Indexer-->>SparseAttention: Clear pending state
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes overlapping the DSA heuristic previous TopK write-back on the auxiliary stream.
Description check ✅ Passed The description includes the required change rationale, implementation details, test coverage, performance evidence, and checklist.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64192 [ run ] triggered by Bot. Commit: 90c4b5c Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64192 [ run ] completed with state FAILURE. Commit: 90c4b5c
/LLM/main/L0_MergeRequest_PR pipeline #52105 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64243 [ run ] triggered by Bot. Commit: 90c4b5c Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64243 [ run ] completed with state FAILURE. Commit: 90c4b5c
/LLM/main/L0_MergeRequest_PR pipeline #52149 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@longcheng-nv
longcheng-nv force-pushed the perf/prevtopk-aux-stream-copy branch from 90c4b5c to 4d90b7b Compare August 7, 2026 01:32
@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64444 [ run ] triggered by Bot. Commit: 4d90b7b Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64444 [ run ] completed with state FAILURE. Commit: 4d90b7b
/LLM/main/L0_MergeRequest_PR pipeline #52320 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

…ream

The per-layer heuristic top-k feedback copy (this step's decode top-k
-> next step's pre_idx hint) is a strided gather sitting on the main
stream's critical path, once per indexer layer per decode step. Nothing
in the current step consumes it, so fork it onto the Indexer's existing
aux stream right after the top-k kernel and join it in the same layer's
MLA forward once core sparse attention is enqueued -- the copy overlaps
with the layer's heaviest decode work.

Same-layer fork/join keeps CUDA graph capture free of unjoined forks
(cudaStreamEndCapture rejects them) and restores ordering before the
next layer overwrites the shared topk_indices_buffer rows the copy
reads. Source and destination are persistent stable-address buffers, so
replays stay valid with no record_stream bookkeeping.

The fork engages only under do_multi_stream() (i.e. inside CUDA graph
capture, where replay makes the stream/event host overhead free); eager
execution keeps the original inline copy unchanged.

Validated with a pattern-level CUDA graph smoke test: capture with the
join succeeds, replayed feedback values are step-correct, and capture
without the join fails with cudaErrorStreamCaptureUnjoined.

Ported onto the sparse-attention framework refactor (NVIDIA#12733): the
Indexer changes moved from sparse/dsa.py to sparse/dsa/indexer.py, and
the two MLA join sites moved from modules/mla.py to
sparse/dsa/module.py (_forward_dsa_attn) and
sparse/deepseek_v4/module.py (forward_sparse_attn).

Made-with: Claude Code (Fable 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
@longcheng-nv
longcheng-nv force-pushed the perf/prevtopk-aux-stream-copy branch from 4d90b7b to 52d3f9b Compare August 8, 2026 07:37
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

Rebased/ported onto the sparse-attention framework refactor (#12733), new head 52d3f9b: the Indexer fork moved from sparse/dsa.py to sparse/dsa/indexer.py (single site now covers both V3.2 and the DeepseekV4Indexer subclass), and the two same-layer join sites moved from modules/mla.py to sparse/dsa/module.py::_forward_dsa_attn and sparse/deepseek_v4/module.py::forward_sparse_attn. Logic is unchanged (diff-of-diffs is the import split only). This also picks up #17416, which fixes the test_on_update_kv_lens_rebuilds_stale_map stub failure that hit the previous CI run on main-side code.

@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64772 [ run ] triggered by Bot. Commit: 52d3f9b Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64772 [ run ] completed with state FAILURE. Commit: 52d3f9b
/LLM/main/L0_MergeRequest_PR pipeline #52616 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@longcheng-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #65523 [ run ] triggered by Bot. Commit: 52d3f9b Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #65523 [ run ] completed with state SUCCESS. Commit: 52d3f9b
/LLM/main/L0_MergeRequest_PR pipeline #53259 completed with status: 'SUCCESS'

CI Report

Link to invocation

metadata.heuristic_prev_topk[local_layer, :num_generations].copy_(last_mtp_topk)
prev_topk_dst = metadata.heuristic_prev_topk[local_layer, :num_generations]
if do_multi_stream() and self.aux_stream is not None:
# Fork the write-back onto the aux stream so the strided

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.

Please simplify comments from agent. :)


Called by the owning MLA layer after this layer's core sparse
attention has been enqueued, so the copy forked in
sparse_attn_indexer overlaps with it. Joining within the same

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.

Same here, first line is enough.

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.

3 participants