[None][perf] disagg: optionally drop message history from generation request - #15366
Merged
lfr-0531 merged 1 commit intoJun 23, 2026
Conversation
lancelly
force-pushed
the
perf/disagg-gen-strip-message-history
branch
from
June 15, 2026 10:16
9e8d2ae to
f644e19
Compare
…request In disaggregated serving the orchestrator forwards each generation_only request to the generation worker carrying BOTH the full chat `messages` history AND the `prompt_token_ids` produced by the context server. The generation worker uses prompt_token_ids directly (it skips apply_chat_template) and only reads the last message for postprocessing, so the full conversation in `messages` is redundant on the gen side. At high concurrency the generation worker's single OpenAI-server process is GIL/event-loop bound on JSON-parsing + pydantic-validating that large request body; the messages history is ~40% of it. When the generation ingress is the binding constraint (the real, un-throttled operating point), trimming messages to the last turn measurably raises throughput and lowers TTFT (DSv4-Pro 6p1d c2048: req/s +2.4%, TTFT p50 -18% / p95 -6%). Tools are preserved (still used for tool-parser / guided decoding). Gated behind a new disagg config `gen_strip_message_history` (default off). It is only safe for text-only, non-harmony deployments: harmony/ gpt_oss generation workers ignore prompt_token_ids and re-tokenize the messages, and multimodal models carry image/audio parts in the history. Model type is fixed per disagg deployment, so the operator opts in via config rather than per-request detection. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
lancelly
force-pushed
the
perf/disagg-gen-strip-message-history
branch
from
June 17, 2026 05:26
f644e19 to
130b558
Compare
lancelly
marked this pull request as ready for review
June 17, 2026 05:45
Collaborator
Author
|
/bot run --disable-fail-fast |
Collaborator
|
PR_Github #54771 [ run ] triggered by Bot. Commit: |
Collaborator
|
PR_Github #54771 [ run ] completed with state |
lfr-0531
approved these changes
Jun 23, 2026
lfr-0531
pushed a commit
to lfr-0531/TensorRT-LLM
that referenced
this pull request
Jun 26, 2026
…request (NVIDIA#15366) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com>
lancelly
added a commit
to lancelly/TensorRT-LLM
that referenced
this pull request
Jun 29, 2026
…t_token_ids
In disaggregated serving the orchestrator is a single asyncio event loop that,
per request, handles the context server's prompt_token_ids (~40k ints for long
agentic prompts) three times on that one loop: json.loads of the ctx response,
pydantic-validate into ChatCompletionResponse, then model_dump_json re-serialize
onto the generation request. At high concurrency this is the binding host
bottleneck and sits on the TTFT critical path.
Add an opt-in fast path, config-gated via DisaggServerConfig.gen_tokids_ctxbytes
(default off, like gen_strip_message_history): the orchestrator instructs context
workers (via DisaggregatedParams.return_prompt_token_ids_b64 on the context
request) to serialize prompt_token_ids into the context response as a
base64-encoded int32 buffer (one JSON string). The orchestrator then relays that
string verbatim onto the generation request without materializing the int list on
its event loop; the generation worker decodes it back. The encode runs on the N
context workers instead of the one orchestrator loop. Off => byte identical to
baseline.
Measured (DeepSeek-V4-Pro 6p1d, GB300, AA-RWLT coding-agent):
- c2048 (stable, GPU-bound): TTFT p50 -57% (2.95s->1.28s), p95 -41%.
- c2600 (orchestrator-bound): +74% mean completions; raises the
sustainable-concurrency-before-collapse ceiling.
Builds on NVIDIA#15366 (body-strip) and is the disagg-HTTP-wire analogue of NVIDIA#15134
(executor IPC int32 bytes). Safe for text-only, non-harmony deployments.
Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
lancelly
added a commit
to lancelly/TensorRT-LLM
that referenced
this pull request
Jun 29, 2026
…t_token_ids
In disaggregated serving the orchestrator is a single asyncio event loop that,
per request, handles the context server's prompt_token_ids (~40k ints for long
agentic prompts) three times on that one loop: json.loads of the ctx response,
pydantic-validate into ChatCompletionResponse, then model_dump_json re-serialize
onto the generation request. At high concurrency this is the binding host
bottleneck and sits on the TTFT critical path.
Add an opt-in fast path, config-gated via DisaggServerConfig.gen_tokids_ctxbytes
(default off, like gen_strip_message_history): the orchestrator instructs context
workers (via DisaggregatedParams.return_prompt_token_ids_b64 on the context
request) to serialize prompt_token_ids into the context response as a
base64-encoded int32 buffer (one JSON string). The orchestrator then relays that
string verbatim onto the generation request without materializing the int list on
its event loop; the generation worker decodes it back. The encode runs on the N
context workers instead of the one orchestrator loop. Off => byte identical to
baseline.
Measured (DeepSeek-V4-Pro 6p1d, GB300, AA-RWLT coding-agent):
- c2048 (stable, GPU-bound): TTFT p50 -57% (2.95s->1.28s), p95 -41%.
- c2600 (orchestrator-bound): +74% mean completions; raises the
sustainable-concurrency-before-collapse ceiling.
Builds on NVIDIA#15366 (body-strip) and is the disagg-HTTP-wire analogue of NVIDIA#15134
(executor IPC int32 bytes). Safe for text-only, non-harmony deployments.
Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
lancelly
added a commit
to lancelly/TensorRT-LLM
that referenced
this pull request
Jun 29, 2026
…t_token_ids
In disaggregated serving the orchestrator is a single asyncio event loop that,
per request, handles the context server's prompt_token_ids (~40k ints for long
agentic prompts) three times on that one loop: json.loads of the ctx response,
pydantic-validate into ChatCompletionResponse, then model_dump_json re-serialize
onto the generation request. At high concurrency this is the binding host
bottleneck and sits on the TTFT critical path.
Add an opt-in fast path, config-gated via DisaggServerConfig.gen_tokids_ctxbytes
(default off, like gen_strip_message_history): the orchestrator instructs context
workers (via DisaggregatedParams.return_prompt_token_ids_b64 on the context
request) to serialize prompt_token_ids into the context response as a
base64-encoded int32 buffer (one JSON string). The orchestrator then relays that
string verbatim onto the generation request without materializing the int list on
its event loop; the generation worker decodes it back. The encode runs on the N
context workers instead of the one orchestrator loop. Off => byte identical to
baseline.
Measured (DeepSeek-V4-Pro 6p1d, GB300, AA-RWLT coding-agent):
- c2048 (stable, GPU-bound): TTFT p50 -57% (2.95s->1.28s), p95 -41%.
- c2600 (orchestrator-bound): +74% mean completions; raises the
sustainable-concurrency-before-collapse ceiling.
Builds on NVIDIA#15366 (body-strip) and is the disagg-HTTP-wire analogue of NVIDIA#15134
(executor IPC int32 bytes). Safe for text-only, non-harmony deployments.
Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
Shixiaowei02
pushed a commit
to lfr-0531/TensorRT-LLM
that referenced
this pull request
Jun 29, 2026
…request (NVIDIA#15366) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com>
lancelly
added a commit
to lfr-0531/TensorRT-LLM
that referenced
this pull request
Jun 29, 2026
…request (NVIDIA#15366) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com> Signed-off-by: Liao Lanyu <108499334+lancelly@users.noreply.github.com>
Shixiaowei02
pushed a commit
to lfr-0531/TensorRT-LLM
that referenced
this pull request
Jun 30, 2026
…request (NVIDIA#15366) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com> Signed-off-by: Liao Lanyu <108499334+lancelly@users.noreply.github.com>
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
In disaggregated serving the orchestrator forwards each
generation_onlyrequest to thegeneration worker carrying both the full chat
messageshistory and theprompt_token_idsproduced by the context server. The generation worker usesprompt_token_idsdirectly (it skipsapply_chat_template) and only reads the last messagefor postprocessing, so the full conversation in
messagesis redundant on the gen side.At high concurrency the gen worker's single OpenAI-server process is GIL/event-loop bound on
JSON-parsing + pydantic-validating that large request body; the
messageshistory is ~40% ofit. Trimming
messagesto the last turn — when the gen ingress is the binding constraint —measurably raises throughput and lowers TTFT.
Change
gen_strip_message_history(default off) onDisaggServerConfig,wired through
extract_disagg_cfgas an explicit parameter (not**kwargs, so it is notinadvertently inherited into the per-server configs).
OpenAIDisaggregatedService._get_gen_requesttrimsmessages→messages[-1:]for
generation_onlyChatCompletionRequests (only whenprompt_token_idsis set). Toolsare preserved (still used for tool-parser / guided decoding).
Enable via the disagg YAML top level (same level as
hostname/schedule_style):Why config-gated (not unconditional)
A code audit found two GEN-side hazards an unconditional strip would hit on other model families:
chat_harmonyignoresprompt_token_idsand always re-tokenizesrequest.messages(openai_to_harmony_tokens); stripping → only the last turn is rendered →token/KV mismatch → corrupt output.
parse_chat_messages_coroutinesextractsmm_datafrommessages; strippingdrops history image/audio parts.
Both are per-model properties and a disagg cluster serves a single model, so a default-off config
flag (an operator assertion "text-only + non-harmony deployment") covers them with zero blast
radius. The standard
openai_chatpath is unaffected: it consumesprompt_token_ids, skipsapply_chat_template, and only readsmessages[-1](get_role()/last_message_content).Performance (DeepSeek-V4-Pro, 6p1d DEP4×6 + DEP8 GEN, MTP3, GB300, c2048, metrics-off)
gen_strip_message_historyThe gain appears at the un-throttled operating point where the gen-serving JSON-parse GIL is the
binding ingress constraint; it is absorbed when the system is decode-bound (closed-loop workload).
Test
extract_disagg_cfgparses the new top-level key ontoDisaggServerConfig.gen_strip_message_historyand does not leak it into the per-server configs; default (unset) =
False.