Skip to content

[None][chore] Declare attention runtime-workspace bytes/token as a backend contract - #16432

Draft
eopXD wants to merge 2 commits into
NVIDIA:mainfrom
eopXD:attention-workspace-reservation-contract
Draft

[None][chore] Declare attention runtime-workspace bytes/token as a backend contract#16432
eopXD wants to merge 2 commits into
NVIDIA:mainfrom
eopXD:attention-workspace-reservation-contract

Conversation

@eopXD

@eopXD eopXD commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #16399 (fp8 context-MLA attention-workspace reservation, nvbugs/6368562).

Motivation. #16399 fixes a mid-forward OOM by reserving KV-cache headroom for the fp8
context-MLA attention workspace and capping summed attended KV length in the scheduler. That fix is
correct, but the accounting is threaded imperatively through the estimator
(_util.configure_kv_cache_capacity) and the scheduler (py_executor._cap_context_by_total_kv_len),
keyed on an MLA-specific model-config check. The failure mode it patches is structural, not
MLA-specific: the KV-cache estimator profiles peak memory against an empty cache and hands the rest to
the KV pool, so any attention backend that stages a workspace sized by a runtime quantity the
profiling forward does not drive to its serving maximum (here total_kv_len, decoupled from
max_num_tokens by KV-cache reuse) is under-reserved and can OOM. A future backend would have to
rediscover and re-thread the same estimator + scheduler + cost-rate pieces, and silently re-introduce
the same OOM if it missed one.

Change. Lift the accounting into a declared contract on the attention backend, so future backends
inherit the accounting instead of the OOM:

  • AttentionBackend.runtime_workspace_bytes_per_token(model_config, mapping) -> int (default 0) — a
    backend declares the per-token cost of any workspace it stages whose size scales with such a runtime
    quantity.
  • TrtllmAttention declares the fp8 context-MLA workspace, still sized by the single C++ source of
    truth (AttentionOp::contextMlaWorkspaceBytesPerToken, via nanobind).
  • The estimator and scheduler consume the declaration generically via
    get_attention_workspace_bytes_per_token(); their reservation/cap logic is unchanged. The active
    fp8-MLA path is behavior-identical; the reservation is now tied to the backend that actually
    allocates the buffer.
  • Documented as a contract in ATTENTION_DEVELOPER_GUIDE.md (required reading) — §2.3, §3.2.3, §4.2.

The contract is deliberately a scalar per-token rate, not a typed driver/reservation abstraction:
there is one driving quantity today and the scheduler's cap is specific to it, so a richer type would
be unused scaffolding. A backend with a different driving quantity introduces it then, alongside the
enforcement it needs.

Stacking note

Stacked on #16399 (fork branch nvbugs/6368562-mla-workspace-reserve). Until that merges, the diff
here also includes #16399's changes; will rebase onto main once #16399 lands. Please merge #16399
first.

Test Coverage

  • tests/unittest/_torch/executor/test_mla_workspace_reserve.py — retargeted to the new resolver; the
    non-MLA test now exercises the full resolve-backend path (get_attention_backend → backend
    classmethod → 0). Behavior-preserving refactor; no new runtime path.

PR Checklist

  • PR description clearly explains what and why.
  • PR follows TRT-LLM coding guidelines; pre-commit run locally (isort/yapf/ruff/ruff-format green).
  • Test updated for the refactored resolver (behavior-preserving).
  • No public API changes (the backend method is internal).
  • No new dependencies.

GitHub Bot Help

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

Summary by CodeRabbit

  • New Features

    • Added automatic workspace reservation for FP8 Context-MLA attention.
    • Added scheduling safeguards that defer context requests when workspace capacity would be exceeded.
    • Exposed an API for querying per-token attention workspace requirements.
  • Bug Fixes

    • Prevented potential out-of-memory errors caused by runtime workspace under-reservation.
    • Improved workspace sizing consistency across attention execution paths.
  • Documentation

    • Documented workspace memory-accounting requirements for attention backends.
  • Tests

    • Added coverage for workspace reservation, request trimming, KV reuse, and admission limits.

eopXD and others added 2 commits July 15, 2026 10:36
…pace in KV cache estimation

The fp8 context-MLA K/V dequant workspace scales with the summed attended KV
length (total_kv_len) of the context requests in a forward step. The KV-cache
memory estimator profiles with fresh-prefill dummy requests against an empty
cache, so total_kv_len there is pinned near max_num_tokens and this workspace
sits at its floor. With block reuse at serving time total_kv_len decouples from
max_num_tokens and the workspace can grow far past the profiled floor, but the
estimator has already handed that headroom to the KV pool, causing an OOM
mid-forward (TestKimiK2::test_nvfp4[4gpus], exposed once NVIDIA#14852 sized the
workspace correctly by total_kv_len).

Fix the estimation rather than the memory fraction (a user co-tenancy knob):
- Split the KV budget between the pool (k bytes/token, all layers) and the
  workspace (w bytes/token, one shared layer buffer) at a common token count, so
  max_tokens = budget / (k + w) and the reserved workspace covers exactly
  max_tokens tokens of attended KV.
- Enforce it at admission: the scheduler trims scheduled context requests so
  their summed attended KV length stays within the pool token capacity, always
  keeping at least one request as a forward-progress guard. No-op for
  non-fp8-MLA models.

The per-token workspace cost is a single source of truth in C++
(AttentionOp::contextMlaWorkspaceBytesPerToken), exposed via nanobind, so the
estimator's reserve can never drift from the runtime allocation.

Remove the waive for TestKimiK2::test_nvfp4[4gpus] (nvbugs/6368562) to re-enable
the test.

Co-Authored-By: Yueh-Ting Chen <yueh.ting.chen@gmail.com>
Signed-off-by: Yueh-Ting Chen <yuehtingc@nvidia.com>
…ckend contract

The fp8 context-MLA workspace reservation (nvbugs/6368562) was threaded
imperatively through the KV-cache estimator and the scheduler, keyed on a
model-config check specific to MLA. Any future attention backend that stages a
workspace sized by a runtime quantity the KV-cache profiler under-measures would
have to rediscover and re-thread the same estimator + scheduler + cost-rate
pieces, and silently re-introduce the same mid-forward OOM if it missed one.

Lift the accounting into a declared contract on the attention backend:

- AttentionBackend.runtime_workspace_bytes_per_token(model_config, mapping)
  returns the per-token bytes to reserve for a workspace the backend stages whose
  size scales with a runtime quantity the profiling forward does not drive to its
  serving maximum. Default 0 -- correct for every backend except fp8 context-MLA.
- TrtllmAttention declares the fp8 context-MLA K/V dequant workspace, still sized
  by the single C++ source of truth (contextMlaWorkspaceBytesPerToken).
- The estimator and scheduler consume the declaration generically via
  get_attention_workspace_bytes_per_token(); their reservation/cap logic is
  unchanged. The active fp8-MLA path is behavior-identical; the reservation is
  now tied to the backend that actually allocates the buffer.
- Document the contract in ATTENTION_DEVELOPER_GUIDE.md (required reading) so a
  new backend inherits the accounting instead of the OOM.

The contract is deliberately a scalar per-token rate, not a typed
driver/reservation abstraction: there is one driving quantity today and the
scheduler's cap is specific to it, so a richer type would be unused scaffolding.
A backend with a different driver introduces it then, alongside the enforcement
it needs.

Signed-off-by: Yueh-Ting Chen <yuehtingc@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared FP8 context-MLA workspace estimator, exposes it to Python backends, reserves corresponding KV-cache headroom, and caps scheduled context requests by attended KV length. Tests cover reuse accounting, trimming behavior, disabled caps, and non-MLA configurations.

Changes

FP8 context-MLA workspace accounting

Layer / File(s) Summary
Workspace estimator and binding
cpp/tensorrt_llm/common/attentionOp.*, cpp/tensorrt_llm/nanobind/thop/bindings.cpp
Adds the shared per-token K/V staging-byte estimator, synchronizes runtime buffer sizing with it, and exposes it through nanobind.
Backend workspace contract
tensorrt_llm/_torch/attention_backend/interface.py, tensorrt_llm/_torch/attention_backend/trtllm.py, tensorrt_llm/_torch/modules/ATTENTION_DEVELOPER_GUIDE.md
Adds the backend workspace hook, implements FP8 context-MLA detection and sizing, and documents the memory-accounting contract.
KV-cache capacity reservation
tensorrt_llm/_torch/pyexecutor/_util.py
Queries backend workspace requirements and adjusts KV-cache capacity to reserve runtime workspace headroom.
Context scheduling admission cap
tensorrt_llm/_torch/pyexecutor/py_executor.py, tests/unittest/_torch/executor/test_mla_workspace_reserve.py
Computes reuse-aware attended KV length, trims context requests to the configured cap, and tests the cap and workspace-resolution behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant AttentionBackend
  participant AttentionOp
  participant KvCacheCreator
  participant PyExecutor

  AttentionBackend->>AttentionOp: obtain FP8 context-MLA bytes per token
  AttentionOp-->>AttentionBackend: return K/V staging cost
  KvCacheCreator->>AttentionBackend: resolve workspace reservation
  AttentionBackend-->>KvCacheCreator: return bytes per token
  KvCacheCreator->>PyExecutor: establish KV capacity and attended-KV cap
  PyExecutor-->>PyExecutor: trim context requests exceeding the cap
Loading

Possibly related PRs

Suggested reviewers: sunnyqgg, yihwang-nv, cascade812, pengbowang-nv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the backend-contract workspace byte/token change.
Description check ✅ Passed The description follows the template and covers motivation, change, test coverage, and checklist items.
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.
✨ 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 (3)
cpp/tensorrt_llm/common/attentionOp.h (1)

61-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new public API with Doxygen.

AttentionOp::contextMlaWorkspaceBytesPerToken is a new public interface, but its declaration uses ordinary // comments and does not document its parameters or return value. Use a Doxygen comment here.

As per coding guidelines, use Doxygen comments for new interfaces.

🤖 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 `@cpp/tensorrt_llm/common/attentionOp.h` around lines 61 - 67, Replace the
ordinary comment immediately preceding
AttentionOp::contextMlaWorkspaceBytesPerToken with a Doxygen comment that
documents the method’s purpose, every parameter, and its returned byte count.
Preserve the existing sizing behavior and shared-source-of-truth description.

Source: Coding guidelines

tests/unittest/_torch/executor/test_mla_workspace_reserve.py (1)

67-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Coverage gap: cap-derivation and KV-budget-split logic are untested.

Coverage of _context_attended_kv_len and _cap_context_by_total_kv_len is solid, but two related pieces of new behavior have no test in this file:

  • tensorrt_llm/_torch/pyexecutor/py_executor.py::PyExecutor._get_ctx_mla_kv_len_cap — the _make_executor helper here bypasses it entirely by pre-setting _ctx_mla_kv_len_cap, so the actual blocks_in_primary_pool * tokens_per_block computation and the w > 0 gating are never exercised.
  • tensorrt_llm/_torch/pyexecutor/_util.py::KvCacheCreator.configure_kv_cache_capacity — the new cap = budget / (k + w) reservation-split branch has no unit coverage.

Both would need mocking (kv_cache_manager attributes / get_attention_workspace_bytes_per_token) similar to the pattern already used for _make_executor, so this is a reasonable, low-effort follow-up rather than a blocker — flagging for completeness. As per path instructions, calling out coverage gaps with concrete file names for QA follow-up.

Also applies to: 106-112

🤖 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 `@tests/unittest/_torch/executor/test_mla_workspace_reserve.py` around lines 67
- 71, Add focused tests for the uncovered cap-derivation and KV-budget-split
branches. Extend the `_make_executor`-related tests to exercise
`PyExecutor._get_ctx_mla_kv_len_cap`, including `blocks_in_primary_pool *
tokens_per_block` and the `w > 0` gating, using mocked `kv_cache_manager`
attributes; add `KvCacheCreator.configure_kv_cache_capacity` coverage for the
`cap = budget / (k + w)` reservation split with a mocked
`get_attention_workspace_bytes_per_token`.

Source: Path instructions

tensorrt_llm/_torch/attention_backend/trtllm.py (1)

1296-1299: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reuse the shared MLA predicate here.
tensorrt_llm._torch.pyexecutor.config_utils.is_mla() already checks both kv_lora_rank and qk_rope_head_dim; this guard only checks kv_lora_rank, so a malformed config can drift past the check and hit the later config.qk_rope_head_dim access. Importing the shared helper here is safe.

🤖 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/trtllm.py` around lines 1296 - 1299,
Update the MLA guard in the relevant attention backend method to use the shared
config_utils.is_mla() predicate instead of checking kv_lora_rank directly, and
import that helper. Preserve the existing early return of 0 for non-MLA
configurations while ensuring both required MLA fields are validated before
later qk_rope_head_dim access.
🤖 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/py_executor.py`:
- Around line 5108-5133: Reset the cached _ctx_mla_kv_len_cap in
_maybe_rebalance_kv_pools immediately after a successful mgr.impl.adjust() so
the next _get_ctx_mla_kv_len_cap() recomputes blocks_in_primary_pool *
tokens_per_block using the rebalanced KV pool. Do not invalidate the cache when
adjust fails or is not performed.

---

Nitpick comments:
In `@cpp/tensorrt_llm/common/attentionOp.h`:
- Around line 61-67: Replace the ordinary comment immediately preceding
AttentionOp::contextMlaWorkspaceBytesPerToken with a Doxygen comment that
documents the method’s purpose, every parameter, and its returned byte count.
Preserve the existing sizing behavior and shared-source-of-truth description.

In `@tensorrt_llm/_torch/attention_backend/trtllm.py`:
- Around line 1296-1299: Update the MLA guard in the relevant attention backend
method to use the shared config_utils.is_mla() predicate instead of checking
kv_lora_rank directly, and import that helper. Preserve the existing early
return of 0 for non-MLA configurations while ensuring both required MLA fields
are validated before later qk_rope_head_dim access.

In `@tests/unittest/_torch/executor/test_mla_workspace_reserve.py`:
- Around line 67-71: Add focused tests for the uncovered cap-derivation and
KV-budget-split branches. Extend the `_make_executor`-related tests to exercise
`PyExecutor._get_ctx_mla_kv_len_cap`, including `blocks_in_primary_pool *
tokens_per_block` and the `w > 0` gating, using mocked `kv_cache_manager`
attributes; add `KvCacheCreator.configure_kv_cache_capacity` coverage for the
`cap = budget / (k + w)` reservation split with a mocked
`get_attention_workspace_bytes_per_token`.
🪄 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: 4157f3a6-9cad-4fa5-892c-67e8a22b83bc

📥 Commits

Reviewing files that changed from the base of the PR and between 0846183 and e18a63b.

📒 Files selected for processing (10)
  • cpp/tensorrt_llm/common/attentionOp.cpp
  • cpp/tensorrt_llm/common/attentionOp.h
  • cpp/tensorrt_llm/nanobind/thop/bindings.cpp
  • tensorrt_llm/_torch/attention_backend/interface.py
  • tensorrt_llm/_torch/attention_backend/trtllm.py
  • tensorrt_llm/_torch/modules/ATTENTION_DEVELOPER_GUIDE.md
  • tensorrt_llm/_torch/pyexecutor/_util.py
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tests/integration/test_lists/waives.txt
  • tests/unittest/_torch/executor/test_mla_workspace_reserve.py
💤 Files with no reviewable changes (1)
  • tests/integration/test_lists/waives.txt

Comment on lines +5108 to +5133
def _get_ctx_mla_kv_len_cap(self):
"""Cap on the summed context attended-KV length (total_kv_len) per forward step.

Equals the KV pool's primary-pool token capacity, which is exactly what the estimator reserved the
fp8 context-MLA attention workspace for (`max_tokens = budget / (k + w)`). Returns None (no cap) for
non-fp8-MLA models — those reserved no workspace, so this per-forward constraint must not alter their
scheduling. Computed once and cached.
"""
cap = getattr(self, "_ctx_mla_kv_len_cap", "unset")
if cap != "unset":
return cap
# Lazy import: _util imports py_executor at module scope, so a top-level import here is circular.
from ._util import get_attention_workspace_bytes_per_token
cap = None
w = get_attention_workspace_bytes_per_token(
self.model_engine.model.model_config, self.dist.mapping)
if w > 0:
blocks = getattr(self.kv_cache_manager, "blocks_in_primary_pool",
None)
tokens_per_block = getattr(self.kv_cache_manager,
"tokens_per_block", None)
if blocks and tokens_per_block:
cap = int(blocks) * int(tokens_per_block)
self._ctx_mla_kv_len_cap = cap
return cap

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant methods and nearby context.
python3 - <<'PY'
from pathlib import Path
path = Path("tensorrt_llm/_torch/pyexecutor/py_executor.py")
lines = path.read_text().splitlines()
targets = ["def _get_ctx_mla_kv_len_cap", "def _maybe_rebalance_kv_pools", "def _context_attended_kv_len", "def _cap_context_by_total_kv_len", "def _schedule"]
for t in targets:
    for i, line in enumerate(lines, 1):
        if t in line:
            start = max(1, i - 20)
            end = min(len(lines), i + 80)
            print(f"\n=== {t} @ line {i} ===")
            for j in range(start, end + 1):
                print(f"{j:5d}: {lines[j-1]}")
            break
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 30267


Invalidate the cached context-MLA KV cap after KV pool rebalance. _get_ctx_mla_kv_len_cap() caches blocks_in_primary_pool * tokens_per_block once, but _maybe_rebalance_kv_pools() can change the primary pool via mgr.impl.adjust(). Reset _ctx_mla_kv_len_cap after a successful adjust so scheduling tracks the current capacity.

🤖 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/pyexecutor/py_executor.py` around lines 5108 - 5133,
Reset the cached _ctx_mla_kv_len_cap in _maybe_rebalance_kv_pools immediately
after a successful mgr.impl.adjust() so the next _get_ctx_mla_kv_len_cap()
recomputes blocks_in_primary_pool * tokens_per_block using the rebalanced KV
pool. Do not invalidate the cache when adjust fails or is not performed.

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.

1 participant