Skip to content

[TRTLLM-14138][perf] Make FlashInfer decode plans sync-free with host-built page tables - #16073

Merged
yuxianq merged 3 commits into
NVIDIA:mainfrom
kaiyux:perf/flashinfer-sync-free-plan-20260707
Jul 13, 2026
Merged

[TRTLLM-14138][perf] Make FlashInfer decode plans sync-free with host-built page tables#16073
yuxianq merged 3 commits into
NVIDIA:mainfrom
kaiyux:perf/flashinfer-sync-free-plan-20260707

Conversation

@kaiyux

@kaiyux kaiyux commented Jul 7, 2026

Copy link
Copy Markdown
Member

Description

FlashInfer attention metadata preparation performed per-step GPU arithmetic and synchronizations, and BatchDecodeWithPagedKVCacheWrapper.plan() triggered flashinfer's per-request host rebuild loop — one stream sync plus one scalar D2H copy per generation request per plan. This PR makes the decode planning path sync-free by building everything on the host:

  • Build all page-table metadata (kv lens, block counts, decode/prefill/mixed indptrs, last-page lens) with numpy on the host and stage it through pinned tensors for async H2D copies, removing per-step GPU arithmetic and syncs from prepare().
  • Add KVCacheManagerV2.get_batch_cache_indices_flat(): gathers only the live block-table widths straight into a pinned CPU tensor with one vectorized transform, instead of materializing padded per-request Python lists. get_batch_cache_indices keeps its trimmed-list semantics for non-flat callers.
  • Retain host copies of the decode indptr and per-pool flat page indices, and pre-build persistent trtllm-gen decode block tables on the host, so plan() no longer triggers flashinfer's per-request rebuild loop. Buffers are allocated once at capacity under CUDA graphs so captured decode kernels keep reading stable addresses.
  • Vectorize the VSWA decode block-table refresh (one gather + where instead of a per-request slice-copy loop).

No kernel behavior change: the planning inputs are identical to before, they are just produced on the host without device round-trips.

Split out of #16033 (2 of 3, together with #16072 and #16074); this PR is independent of the other two.

Test Coverage

  • The touched paths are exercised by the existing _torch attention and executor test suites (FlashInfer backend, VSWA, and KV-cache manager coverage).
  • Perf: measured standalone on top of main (045705139d) on 1x B200 (Gemma-4-31B-IT-NVFP4, ISL 1024 / OSL 128, 1024 prompts, concurrency 1024, trtllm-serve + benchmark_serving):
Metric main (045705139d) this PR delta
Output throughput 1331.8 tok/s 1421.5 tok/s +6.7%
Median TTFT 49.45 s 45.64 s -7.7%
Median TPOT 141.0 ms 128.2 ms -9.1%
P99 TPOT 221.4 ms 198.6 ms -10.3%
Median E2EL 67.41 s 62.07 s -7.9%

Together with #16072 and #16074, the three PRs compose to the +30.9% measured in #16033.

PR Checklist

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

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

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

🤖 Generated with Claude Code

@kaiyux
kaiyux requested review from a team as code owners July 7, 2026 16:28
@kaiyux
kaiyux requested a review from yuxianq July 7, 2026 16:28
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes rework FlashInfer attention metadata preparation and decode planning to move computations from device-synchronous operations to host-side, pinned-memory-staged buffers with single async H2D transfers. A new flat batched index-building helper is added to KVCacheManagerV2 to support vectorized host-side decode block-table construction.

Changes

FlashInfer decode planning sync reduction

Layer / File(s) Summary
Pinned host staging utility
tensorrt_llm/_torch/attention_backend/flashinfer.py
Adds numpy import and _staging_int32() helper to stage host int32 arrays through pinned memory for async H2D copies, reordering pinning-related imports.
KV cache manager flat batched index builder
tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py
Caches index_scales as plain ints and adds get_batch_cache_indices_flat() to build a single pinned, scaled, concatenated int32 tensor of per-request block indices.
Persistent host/device buffer setup
tensorrt_llm/_torch/attention_backend/flashinfer.py
Extends FlashInferAttentionMetadata with per-pool host page-index storage and stable device block-table buffers, and computes _decode_block_tables_width from VSWA pool sizes.
Decode block table construction helpers
tensorrt_llm/_torch/attention_backend/flashinfer.py
Adds _get_paged_kv_indices() and _build_decode_block_tables() to build TRTLLM-gen decode block tables on the host and transfer them via a single async H2D copy.
prepare() host-side KV length computation
tensorrt_llm/_torch/attention_backend/flashinfer.py
Reworks cached token length and KV length/page count computation to use pinned host staging and NumPy instead of device-synchronous operations.
VSWA prepare() handling and mixed indptr
tensorrt_llm/_torch/attention_backend/flashinfer.py
Populates per-pool host/device page-index buffers for VSWA and reworks mixed decode/prefill paged_kv_indptr construction to use host staging instead of CUDA cumsum.
CUDA-graph decode metadata refresh
tensorrt_llm/_torch/attention_backend/flashinfer.py
Replaces per-request block-table rebuild loop with vectorized gather/zero-pad construction and host-staged kv_lens_buffer updates during CUDA-graph warmup.
decode_plan() integration of host buffers
tensorrt_llm/_torch/attention_backend/flashinfer.py
Updates decode_plan() to reuse retained host indptr and pass host-built block_tables into decode_wrapper.plan() for the TRTLLM-gen backend.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Prepare as FlashInferAttentionMetadata.prepare()
    participant KVManager as KVCacheManagerV2
    participant HostStage as Host staging (_staging_int32)
    participant Device as GPU device buffers
    participant DecodePlan as decode_plan()/decode_wrapper.plan()

    Prepare->>KVManager: get_batch_cache_indices_flat(request_ids, num_blocks)
    KVManager-->>Prepare: pinned int32 tensor of scaled indices
    Prepare->>HostStage: stage cached token lengths, KV lengths, indptr on host
    HostStage->>Device: async H2D copy into stable buffers
    Prepare->>Prepare: retain _host_pool_indices, _paged_kv_indptr_decode_host
    DecodePlan->>Prepare: _build_decode_block_tables()
    Prepare->>Device: gather pool buffer rows, write to persistent block-table buffer
    Prepare-->>DecodePlan: block_tables (or None to trigger fallback)
    DecodePlan->>DecodePlan: decode_wrapper.plan(block_tables=block_tables)
Loading

Suggested reviewers: pengbowang-nv, lancelly, yuxianq, chang-l

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 is concise and accurately summarizes the main FlashInfer decode-planning change.
Description check ✅ Passed The description follows the template and includes a clear summary, test coverage, and checklist items.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tensorrt_llm/_torch/attention_backend/flashinfer.py (2)

1242-1244: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Stage decode_blocks through pinned memory before moving to CUDA.

This CUDA-graph refresh path still builds nb from a pageable CPU tensor, so non_blocking=True may not avoid a host-side stall. Consider adding an int64 staging helper or using a pinned CPU tensor here.

Suggested fix
-                nb = torch.tensor(decode_blocks[:rows],
-                                  dtype=torch.int64).to(device=bt.device,
-                                                        non_blocking=True)
+                nb_host = maybe_pin_memory(
+                    torch.tensor(decode_blocks[:rows], dtype=torch.int64))
+                nb = nb_host.to(device=bt.device, non_blocking=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/attention_backend/flashinfer.py` around lines 1242 -
1244, The CUDA-graph refresh path in flashinfer.py builds nb from a pageable CPU
tensor, so the non_blocking transfer may still stall on the host. Update the
decode_blocks handling in the refresh logic to stage the int64 data through
pinned memory before moving it to bt.device, either by reusing an existing
pinned staging helper or by creating the CPU tensor with pinned memory first.
Keep the fix localized around the nb construction so the CUDA transfer remains
truly asynchronous.

876-883: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Pin the fallback page-index tensor before async H2D copies.

The flat path returns a staged/pinned CPU tensor, but the fallback creates a pageable torch.tensor; the later copy_(..., non_blocking=True) can still serialize H2D for managers without get_batch_cache_indices_flat().

Suggested fix
-        return torch.tensor(indices_list, dtype=torch.int32)
+        return maybe_pin_memory(torch.tensor(indices_list, dtype=torch.int32))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/attention_backend/flashinfer.py` around lines 876 - 883,
The fallback path in flashinfer attention backend returns a pageable page-index
tensor, unlike the flat path that stages pinned CPU memory, so the later
non_blocking H2D copy may serialize. In the fallback branch that builds the
tensor from get_batch_cache_indices, make sure the returned tensor is pinned
before it is used for async copy, matching the behavior of the flat-path code
paths in flashinfer.py.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py`:
- Around line 2860-2878: The batched page-index transform in the cache-index
path is doing arithmetic in int32 via the temporary buffer from the request-ids
loop, so `out * scale // div_factor` can overflow before `BAD_PAGE_INDEX` is
masked out. Update the logic in `kv_cache_manager_v2.py` to perform the
multiply/divide in int64 for the `out` array (after it is filled from
`get_base_page_indices` and before `np.copyto`), then cast the results back to
the original int32 buffer so the batched behavior matches the old per-element
Python loop.

---

Nitpick comments:
In `@tensorrt_llm/_torch/attention_backend/flashinfer.py`:
- Around line 1242-1244: The CUDA-graph refresh path in flashinfer.py builds nb
from a pageable CPU tensor, so the non_blocking transfer may still stall on the
host. Update the decode_blocks handling in the refresh logic to stage the int64
data through pinned memory before moving it to bt.device, either by reusing an
existing pinned staging helper or by creating the CPU tensor with pinned memory
first. Keep the fix localized around the nb construction so the CUDA transfer
remains truly asynchronous.
- Around line 876-883: The fallback path in flashinfer attention backend returns
a pageable page-index tensor, unlike the flat path that stages pinned CPU
memory, so the later non_blocking H2D copy may serialize. In the fallback branch
that builds the tensor from get_batch_cache_indices, make sure the returned
tensor is pinned before it is used for async copy, matching the behavior of the
flat-path code paths in flashinfer.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: da1effb6-0dd0-44b3-8e32-654516df56a6

📥 Commits

Reviewing files that changed from the base of the PR and between 409b696 and 86bbdb8.

📒 Files selected for processing (2)
  • tensorrt_llm/_torch/attention_backend/flashinfer.py
  • tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py

Comment thread tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py
@kaiyux kaiyux changed the title [None][perf] Make FlashInfer decode plans sync-free with host-built page tables [TRTLLM-14138][perf] Make FlashInfer decode plans sync-free with host-built page tables Jul 8, 2026
@kaiyux

kaiyux commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58127 [ run ] triggered by Bot. Commit: 86bbdb8 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58127 [ run ] completed with state SUCCESS. Commit: 86bbdb8
/LLM/main/L0_MergeRequest_PR pipeline #46784 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

@kaiyux
kaiyux force-pushed the perf/flashinfer-sync-free-plan-20260707 branch from 86bbdb8 to df4bb35 Compare July 8, 2026 05:45
@kaiyux

kaiyux commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58163 [ run ] triggered by Bot. Commit: df4bb35 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58163 [ run ] completed with state SUCCESS. Commit: df4bb35
/LLM/main/L0_MergeRequest_PR pipeline #46813 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

@kaiyux

kaiyux commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58334 [ run ] triggered by Bot. Commit: df4bb35 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58334 [ run ] completed with state SUCCESS. Commit: df4bb35
/LLM/main/L0_MergeRequest_PR pipeline #46963 completed with status: 'SUCCESS'
Pipeline passed with automatic retried tests. Check the rerun report for details.

CI Report

Link to invocation

@nvpohanh
nvpohanh requested a review from yizhang-nv July 9, 2026 06:29
@nvpohanh

nvpohanh commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

[by Codex] @yizhang-nv Could you review this PR? Thanks!

Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated
Comment thread tensorrt_llm/_torch/attention_backend/flashinfer.py Outdated

@yizhang-nv yizhang-nv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM on KVCM side

kaiyux added 2 commits July 9, 2026 23:50
…age tables

Remove per-step GPU arithmetic, stream syncs, and scalar D2H copies from
the FlashInfer attention metadata preparation path:

- Build all page-table metadata (kv lens, block counts, decode/prefill/
  mixed indptrs, last-page lens) with numpy on the host and stage it
  through pinned tensors for async H2D copies.
- Add KVCacheManagerV2.get_batch_cache_indices_flat(): gather only the
  live block-table widths straight into a pinned CPU tensor with one
  vectorized transform instead of materializing padded per-request
  Python lists. get_batch_cache_indices keeps its trimmed-list
  semantics for non-flat callers.
- Retain host copies of the decode indptr and per-pool flat page
  indices, and pre-build persistent trtllm-gen decode block tables on
  the host, so BatchDecodeWithPagedKVCacheWrapper.plan() no longer
  triggers flashinfer's per-request rebuild loop (one stream sync plus
  one scalar D2H per generation request per plan). Buffers are
  allocated once at capacity under CUDA graphs so captured decode
  kernels keep reading stable addresses.
- Vectorize the VSWA decode block-table refresh (one gather + where
  instead of a per-request slice-copy loop).

Signed-off-by: Kaiyu Xie <26294424+kaiyux@users.noreply.github.com>
…plans

- Store the persistent trtllm-gen decode block tables (device + pinned
  host) on FlashInferWrappers instead of two PlanParams-keyed dicts;
  _plan_with_params now creates the container once per plan_params and
  mutates it in place across replans so the device buffer address stays
  stable for CUDA graphs.
- Replace the per-pool host-indices lookup in _build_decode_block_tables
  with a _host_paged_kv_indices mirror kept in lockstep with
  _paged_kv_indices at the prepare fill, VSWA swap, and restore sites.
- Add KVCacheManager.get_batch_cache_indices_flat (V1) matching the V2
  interface and drop the getattr feature-detection fallback.
- Replace the dead indptr fallback in decode_plan with an assert; access
  decode_wrapper._backend directly.
- Hoist the max_n capacity guard and flatten the CUDA-graph no-realloc
  guard in _build_decode_block_tables.
- Naming: _host_paged_kv_indptr_decode, _max_num_blocks, block_tables/
  host_block_tables, and full variable names in the VSWA block-table
  refresh.

Signed-off-by: Kaiyu Xie <26294424+kaiyux@users.noreply.github.com>
@kaiyux
kaiyux force-pushed the perf/flashinfer-sync-free-plan-20260707 branch from df4bb35 to b5be365 Compare July 10, 2026 07:50
Rename _staging_int32 to _to_int32_tensor and make it an inner
function of FlashInferAttentionMetadata.prepare, its only user,
per review feedback.

Signed-off-by: Kaiyu Xie <26294424+kaiyux@users.noreply.github.com>
@kaiyux

kaiyux commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@yuxianq Thanks a lot for the very detailed review! All the suggestions should've been addressed, and I've replied some of the questions, can you help take a look again when having a chance?

@kaiyux

kaiyux commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58656 [ run ] triggered by Bot. Commit: 2760524 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58656 [ run ] completed with state SUCCESS. Commit: 2760524
/LLM/main/L0_MergeRequest_PR pipeline #47242 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

@kaiyux

kaiyux commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58727 [ run ] triggered by Bot. Commit: 2760524 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58727 [ run ] completed with state SUCCESS. Commit: 2760524
/LLM/main/L0_MergeRequest_PR pipeline #47310 completed with status: 'SUCCESS'

CI Report

Link to invocation

@yuxianq
yuxianq merged commit 4a86ff5 into NVIDIA:main Jul 13, 2026
8 checks passed
@kaiyux
kaiyux deleted the perf/flashinfer-sync-free-plan-20260707 branch July 13, 2026 04:21
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.

5 participants