Repository navigation
[#13318][fix] Gracefully fit token budget at prep boundary #15187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
thorjohnsen
merged 39 commits into
NVIDIA:main
from
thorjohnsen:fix/token-budget-prep-fallback
Aug 11, 2026
Merged
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
db521b8
[None][fix] Gracefully fit token budget at prep boundary (#13318)
thorjohnsen 389dffb
[#13318][fix] Re-bin re-chunked context requests in token-budget fall…
thorjohnsen 69c3786
[#13318][fix] Only re-chunk token-budget fallback when chunked prefil…
thorjohnsen 9266f94
[#13318][feat] Make the token-budget fallback an opt-out flag
thorjohnsen 1b0276c
[#13318][fix] Run token-budget fallback before all resource managers
thorjohnsen 84f5094
[#13318][feat] Terminate server on token-budget overshoot when fallba…
thorjohnsen 7acc549
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 1a48dd2
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 1b54bdc
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen d5ced25
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen acdf6d8
[#13318][fix] Keep disagg gen-init requests cost-free in token-budget…
thorjohnsen 608ed30
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen e4b942e
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 0f8bdbc
[#13318][fix] Use correct sparse_attention_config kwarg in cross KV pool
thorjohnsen e284dd5
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 0b12c82
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 7b89d49
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 691ad29
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 2428bb6
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 6ffec45
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 1737174
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 7322cff
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 29a819a
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 8f24266
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 705017d
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 90e3f5d
[#13318][test] Stub enable_attention_dp in fatal-shutdown unit test
thorjohnsen 65f1ff0
[#13318][chore] Add enable_token_budget_fallback to telemetry golden …
thorjohnsen 84292d5
[#13318][fix] Drop the token-budget opt-out flag and its fatal-shutdo…
thorjohnsen 35d98e7
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 9b974fb
[#13318][fix] Drive token-budget fallback from the executor loop
thorjohnsen db2bd31
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 1a2942c
[#13318][fix] Guard resource_manager in _prepare_and_schedule_batch
thorjohnsen 1e3f516
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen dfd0b6e
[#13318][fix] Move token-budget trim after resource preparation
thorjohnsen 5c63043
[#13318][chore] Correct stale comment on enable_chunked_prefill
thorjohnsen 0cf9cdc
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen b026f50
[#13318][fix] Report the trimmed batch to the KV connector
thorjohnsen 32a5f36
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen 9e4b36e
Merge branch 'main' into fix/token-budget-prep-fallback
thorjohnsen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
158 changes: 158 additions & 0 deletions
158
tests/unittest/_torch/executor/test_token_budget_fallback.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| """Unit tests for KVCacheManager._fit_token_budget. | ||
|
|
||
| These exercise the prep-boundary token-budget fallback that defers or | ||
| re-chunks context requests so a scheduled batch cannot overshoot | ||
| ``max_num_tokens`` in the forward pass (GitHub issue #13318). The fallback is | ||
| pure scheduling logic and does not touch the GPU, so the tests build a bare | ||
| KVCacheManager via ``__new__`` and drive the method with lightweight fake | ||
| requests. | ||
| """ | ||
|
|
||
| import unittest | ||
|
|
||
| from tensorrt_llm._torch.pyexecutor.resource_manager import KVCacheManager | ||
| from tensorrt_llm._torch.pyexecutor.scheduler import ScheduledRequests | ||
|
|
||
|
|
||
| class _FakeRequest: | ||
| """Minimal stand-in exposing only the attributes _fit_token_budget reads.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| context_chunk_size=0, | ||
| is_last_context_chunk=True, | ||
| py_beam_width=1, | ||
| py_draft_tokens=None, | ||
| is_disagg_generation_init_state=False, | ||
| mm_bidirectional=False, | ||
| ): | ||
| self.context_chunk_size = context_chunk_size | ||
| self.is_last_context_chunk = is_last_context_chunk | ||
| self.py_beam_width = py_beam_width | ||
| self.py_draft_tokens = py_draft_tokens | ||
| self.is_disagg_generation_init_state = is_disagg_generation_init_state | ||
| self.py_multimodal_data = {"mm_bidirectional_blocks": True} if mm_bidirectional else None | ||
|
|
||
|
|
||
| def _make_manager(max_num_tokens, tokens_per_block): | ||
| # Skip the heavy (GPU-allocating) __init__; the method under test only | ||
| # needs these two attributes plus its own (bound) helper methods. | ||
| mgr = KVCacheManager.__new__(KVCacheManager) | ||
| mgr.max_num_tokens = max_num_tokens | ||
| mgr.tokens_per_block = tokens_per_block | ||
| return mgr | ||
|
|
||
|
|
||
| def _make_batch(context_requests=(), generation_requests=()): | ||
| batch = ScheduledRequests() | ||
| for req in context_requests: | ||
| batch.append_context_request(req) | ||
| batch.generation_requests = list(generation_requests) | ||
| return batch | ||
|
|
||
|
|
||
| class TestFitTokenBudget(unittest.TestCase): | ||
| def test_request_forward_tokens_upper_bound(self): | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
|
|
||
| # Context: chunk size, plus draft tokens only on the last chunk. | ||
| last = _FakeRequest( | ||
| context_chunk_size=10, is_last_context_chunk=True, py_draft_tokens=[1, 2] | ||
| ) | ||
| self.assertEqual(mgr._request_forward_tokens(last, is_context=True), 12) | ||
| mid = _FakeRequest( | ||
| context_chunk_size=10, is_last_context_chunk=False, py_draft_tokens=[1, 2] | ||
| ) | ||
| self.assertEqual(mgr._request_forward_tokens(mid, is_context=True), 10) | ||
|
|
||
| # Generation: (1 + draft) per beam. | ||
| gen = _FakeRequest(py_beam_width=2, py_draft_tokens=[1, 2, 3]) | ||
| self.assertEqual(mgr._request_forward_tokens(gen, is_context=False), 8) | ||
|
|
||
| def test_within_budget_is_noop(self): | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| ctx = _FakeRequest(context_chunk_size=16) | ||
| gen = _FakeRequest(py_beam_width=100) # 100 gen tokens | ||
| batch = _make_batch([ctx], [gen]) | ||
|
|
||
| mgr._fit_token_budget(batch) | ||
|
|
||
| self.assertEqual(batch.num_context_requests, 1) | ||
| self.assertEqual(ctx.context_chunk_size, 16) # untouched | ||
|
|
||
| def test_overshoot_rechunks_context(self): | ||
| # 100 gen tokens leave a 28-token budget; a 64-token last chunk does not | ||
| # fit but can be re-chunked down to a block-aligned 16. | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| ctx = _FakeRequest(context_chunk_size=64, is_last_context_chunk=True) | ||
| gen = _FakeRequest(py_beam_width=100) | ||
| batch = _make_batch([ctx], [gen]) | ||
|
|
||
| mgr._fit_token_budget(batch) | ||
|
|
||
| self.assertEqual(batch.num_context_requests, 1) | ||
| self.assertEqual(ctx.context_chunk_size, 16) # (28 // 16) * 16 | ||
| total = mgr._request_forward_tokens(ctx, is_context=True) + mgr._request_forward_tokens( | ||
| gen, is_context=False | ||
| ) | ||
| self.assertLessEqual(total, mgr.max_num_tokens) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| def test_overshoot_defers_when_cannot_rechunk(self): | ||
| # Only an 8-token budget remains -- smaller than one block -- so the | ||
| # context request cannot be re-chunked and must be deferred entirely. | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| ctx = _FakeRequest(context_chunk_size=64) | ||
| gen = _FakeRequest(py_beam_width=120) # remaining = 8 < tokens_per_block | ||
| batch = _make_batch([ctx], [gen]) | ||
|
|
||
| mgr._fit_token_budget(batch) | ||
|
|
||
| self.assertEqual(batch.num_context_requests, 0) | ||
| self.assertEqual(ctx.context_chunk_size, 64) # not re-chunked | ||
|
|
||
| def test_mm_bidirectional_is_deferred_not_rechunked(self): | ||
| # A re-chunkable budget exists, but splitting a bidirectional MM block | ||
| # would corrupt attention, so the request is deferred whole. | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| ctx = _FakeRequest(context_chunk_size=64, mm_bidirectional=True) | ||
| gen = _FakeRequest(py_beam_width=100) # remaining = 28 | ||
| batch = _make_batch([ctx], [gen]) | ||
|
|
||
| mgr._fit_token_budget(batch) | ||
|
|
||
| self.assertEqual(batch.num_context_requests, 0) | ||
| self.assertEqual(ctx.context_chunk_size, 64) | ||
|
|
||
| def test_defers_all_subsequent_context_requests(self): | ||
| # ctx1 fits; ctx2 overshoots and cannot re-chunk; ctx3 (small) must | ||
| # still be deferred to preserve context-progress ordering. | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| ctx1 = _FakeRequest(context_chunk_size=96) | ||
| ctx2 = _FakeRequest(context_chunk_size=64) | ||
| ctx3 = _FakeRequest(context_chunk_size=16) | ||
| gen = _FakeRequest(py_beam_width=16) # remaining = 112 | ||
| batch = _make_batch([ctx1, ctx2, ctx3], [gen]) | ||
|
|
||
| mgr._fit_token_budget(batch) | ||
|
|
||
| # ctx1 (96) fits into 112; remaining 16. ctx2 (64) doesn't fit and | ||
| # (16 // 16) * 16 == 16 but 16 < 64 so it *could* re-chunk to 16... | ||
| # remaining after is 0, so ctx3 is deferred. | ||
| kept = batch.context_requests | ||
| self.assertIn(ctx1, kept) | ||
| self.assertNotIn(ctx3, kept) | ||
|
|
||
| def test_generation_alone_over_budget_raises(self): | ||
| mgr = _make_manager(max_num_tokens=128, tokens_per_block=16) | ||
| gen = _FakeRequest(py_beam_width=200) | ||
| batch = _make_batch([], [gen]) | ||
|
|
||
| with self.assertRaises(RuntimeError): | ||
| mgr._fit_token_budget(batch) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.