Repository navigation
[TRTLLM-17262][refactor] Split the disaggregated server's entry-point wrapper into prepare, run, finish and serialize steps - #21442
Draft
JunyiXu-nv wants to merge 6 commits into
Conversation
…apshots and a CPU test seam for trtllm-serve Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
… baselines at the merge base The route reference is generated from the live apps; the object.__new__ baseline and the gRPC import allowance record what exists on main today and only shrink. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…n the snapshot matrix An attached frontend of a multi-frontend server registers ten launcher forwarding routes before the local table; seven share a method and path with local handlers and three use a path parameter that matches every batch id, so registration order decides which handler serves them. The new configuration records all of it, a live invariant pins the forwarding-before-local order independently of the reference, and the route-class failure message now says why the msgpack route class matters. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
- AGENTS.md: the CPU test command sets LLM_MODELS_ROOT, which the runner requires, and the stage-mapping note says the mapper matches list-entry text, so a file listed through its directory maps by the directory. - check_contracts matches each from-import as <module>.<name>, so 'from pkg import sub' and 'from .. import sub' count as imports of the submodule; new test for both forms and for the allowance entry. - The VisualGen /v1/models test asserts the 200 status. - The AGENTS.md command check also recognizes commands that start with LLM_MODELS_ROOT=. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…s outside-subclass contract Every OpenAI request on the disaggregated frontend, and every Anthropic /v1/messages request, goes through OpenAIDisaggServer._wrap_entry_point. Before reorganizing that closure, pin what it does today: - tests/unittest/disaggregated/test_disagg_entry_point.py pins the per-request counters, the chat-template 400, the conversation identity from the headers, the serialization of streaming and non-streaming responses (including the guarded streamed Responses relay and model_dump(by_alias=True)), the error mapping through _handle_exception, and the single disaggregated_request trace span. - tests/unittest/disaggregated/test_disagg_server_contract.py pins the surface NeMo-RL's OpenAIDisaggServerAdaptor subclass relies on: the constructor (config, req_timeout_secs=..., coordinator_url=None), self.app with every route registered when __init__ returns, __call__(host, port, sockets=None), _wrap_entry_point, _handle_exception and _perf_metrics_collector.http_exceptions. - The OpenAIDisaggServer class docstring and tensorrt_llm/serve/AGENTS.md name that surface, so a change to it is agreed with the NeMo-RL owners first. Both files are CPU-only and collected by the unittest/disaggregated entry of tests/integration/test_lists/test-db/l0_cpu.yml. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…into prepare, run, finish and serialize steps No behavior change. The body of the closure OpenAIDisaggServer builds in _wrap_entry_point becomes a composition of five methods, so that later changes (disconnect watching, in-stream errors, request trace) land in the step they belong to instead of growing one closure: - _prepare_request: the request counters, the chat-template permission (HTTPException 400), the conversation identity, the per-request hooks - _run_entry_point: the call into the disaggregated service - _finish_request: success accounting (total_responses) - _finish_failed_request: every failure, through _handle_exception while the exception is being handled (its traceback logging needs that) - _serialize_response: StreamingResponse with the Responses stream guard, or JSONResponse(content=...model_dump(by_alias=True)), as before The trace span stays on the wrapper that FastAPI calls; the steps open no spans of their own. The statements keep their order inside the same try block: the counters run before the template check, total_responses before serialization, and a serialization failure still reaches _handle_exception. Some step parameters (raw_req of _run_entry_point, req and hooks of the finish steps) are not read yet; they are the inputs the known upcoming changes to each step need, so those changes will not have to alter the signatures again. The characterization tests and the subclass contract tests of the previous commit pass unchanged on both sides of the split. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
This was referenced Nov 3, 2026
Draft
This branch has not been deployed
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.
Stacked on #21406 ([TRTLLM-17261][test] Add route, middleware-order and shim-identity snapshots and a CPU test seam for trtllm-serve): the first commits below belong to that PR and disappear from this diff when it merges; this PR's own change is the last 2 commits.
@coderabbitai summary
Description
Every OpenAI request on the disaggregated frontend, and every Anthropic
/v1/messagesrequest, goes through one closure,OpenAIDisaggServer._wrap_entry_point(tensorrt_llm/serve/openai_disagg_server.py). Upcoming disaggregated-serving work (client-disconnect watching, in-stream errors for streamed Responses, request tracing) all lands in that closure; splitting it first means each of those changes lands in a step of its own instead of growing one closure. This PR splits the closure, with no behavior change, into a composition of five methods:_prepare_requesttotal_requests,stream_requests/nonstream_requests; chat-template permission; conversation identity (mutates the request);RawRequestResponseHooks(queue-latency histogram, perf records)HTTPException(400)for a forbidden template_run_entry_pointOpenAIDisaggregatedServiceaiohttp.ClientResponseError,CppExecutorError,UnusedChatTemplateKwargsError, ...)_finish_requesttotal_responses_finish_failed_request_handle_exception, sohttp_exceptionsorinternal_errors, the SIGINT onCppExecutorError, the context-length envelopeHTTPException, or returnsJSONResponse/None_serialize_responseStreamingResponse(with the Responses stream guard) /JSONResponse(content=...model_dump(by_alias=True))for the OpenAI routes_wrap_entry_pointdisaggregated_requesttrace span, FastAPI's request modelWhere in-flight changes to this closure land after the split:
model_dump(by_alias=True); both moved into_serialize_responseunchanged._finish_failed_request, which already receives the request._run_entry_point(the disconnect watch; the method already receives the raw request) and_finish_failed_request(the cancellation / 499 branch). "Cancel paired GEN on CTX failure or cancellation" ([TRTLLM-17083][feat] Cancel paired GEN on CTX failure or cancellation #20491, merged) landed in the client/service layer and does not touch this wrapper._prepare_request; stream early-start work such as "Close the Mooncake store feature gap" ([None][feat] Close the Mooncake store feature gap: async loads, attention DP, speculative prefill and KV connector support in KVCacheManagerV2 #21195, draft) lands in_run_entry_point/_serialize_response.await entry_point(...)becomes the body of_run_entry_point, itsStreamingResponsecleanup wrapper goes into_serialize_response, and itsexcept asyncio.CancelledErrorbranch goes into the wrapper ahead ofexcept Exception, with the cancellation handling in_finish_failed_request. It is the only open PR that adds a disconnect/deadline watch here, so it has to be re-applied onto these steps.anthropic_messages, the wrapper's caller, which this PR does not change.Some step parameters are not read yet (
raw_reqof_run_entry_point,reqandhooksof the finish steps): they are the inputs the known upcoming changes to each step need, so those changes will not have to alter the signatures again.OpenAIDisaggServeris subclassed outside this repository: NVIDIA NeMo-RL'sOpenAIDisaggServerAdaptorconstructs it as(config, req_timeout_secs=..., coordinator_url=None), adds middleware toself.appright after__init__, and overrides__call__(host, port, sockets=None),_wrap_entry_pointand_handle_exception(counting on_perf_metrics_collector.http_exceptions). This PR keeps that whole surface and pins it three ways: a contract test (tests/unittest/disaggregated/test_disagg_server_contract.py, which builds a subclass of the same shape), the newOpenAIDisaggServerclass docstring, and atensorrt_llm/serve/AGENTS.mdbullet. Changing any item of that surface needs the NeMo-RL owners' agreement first; the tests' failure messages say so.Part of the trtllm-serve refactor (epic TRTLLM-17056, task TRTLLM-17262). Builds on the test seam of #21406 (the server factory the new tests build the disaggregated server with).
Behavior
No behavior change. The wrapper keeps the same trace span, the same
tryblock and the same statement order: the counters run before the template check,total_responsesbefore serialization, and a serialization failure still reaches_handle_exception, which is called while the exception is being handled (its traceback logging needs that). The commit structure separates the pin from the change: the first of the two commits adds the characterization tests, the contract test, the class docstring and theAGENTS.mdbullet against the unsplit closure; the second performs the split. Both test files pass identically on both commits.CI bookkeeping
openai_disagg_server.pychanges, so the multi-GPU stages run and need theci: full pre-merge approvedlabel)pytest.mark.cpu_only) and collected by the existingunittest/disaggregatedentry oftests/integration/test_lists/test-db/l0_cpu.ymlTest Coverage
New, both collected by the
unittest/disaggregatedentry ofl0_cpu.ymland run on the tests-only commit and on the final commit with identical results:tests/unittest/disaggregated/test_disagg_entry_point.py(13 tests): per-request counters for non-streaming, streaming, Anthropic and Responses requests; chat-template 400 before any worker; conversation identity from the headers; upstream error status kept; the context-length worker envelope;Noneafter an engine fatal error (SIGINT recorded); unexpected errors as 500; serialization failures reaching the failure path; exactly onedisaggregated_requestspan per request; the streamed Responses relay guarded;text.format.schemaserialized under its wire name (by_alias).tests/unittest/disaggregated/test_disagg_server_contract.py(6 tests): the NeMo-RL subclass surface above, exercised through a subclass of the same shape.Commands run locally (CPU devel container, no GPU):
pytest -m cpu_only tests/unittest/disaggregated/test_disagg_entry_point.py tests/unittest/disaggregated/test_disagg_server_contract.py -q— 19 passed on the tests-only commit and on the final commit.pytest -m cpu_only tests/unittest/disaggregated -q— 1415 passed, 7 failed on this branch; the same run on the base commit gives 1402 passed, 9 failed, and every failure on this branch also fails on the base (4TestEtcdClusterStoragecases that spawn a realetcdon a port shared with other test processes on the box, 3test_router.pycases that needLLM_MODELS_ROOT, which that run did not set; they pass 4/4 when rerun with it); no new failure. (8TestHttpClusterStorageerrors in one run were port-18000 collisions with another test process on the shared box; the class passes 8/8 when rerun alone.)LLM_MODELS_ROOT=... PYTHONPATH=tests python3 -m test_common.serve.cpu_tests(every serve CPU test, one pytest process per test-db entry, 66 entries) — all entries green except the 4 pre-existingTestEtcdClusterStoragecases above andunittest/grpc/openengine/test_frontend_lifecycle.py::test_frontend_group_cleanup[launcher-death], a process-lifecycle test unrelated to this change (no gRPC file is touched) that passes 8/8 when rerun alone. The route, middleware-order, shim-identity, layering andobject.__new__ratchet checks are among the green entries.pytest -m cpu_only tests/unittest/llmapi/apps/test_anthropic_routes.py tests/unittest/llmapi/apps/test_context_length_error.py -q— 58 passed.pytest tests/unittest/api_stability -q— 98 passed.pytest -m cpu_only tests/unittest/serve/test_agents_md.py -q— 6 passed (the newAGENTS.mdbullet names only real paths).python scripts/legacy_utils.py lint-precommit tensorrt_llm/serve/openai_disagg_server.py— no new violations.pre-commit run --files <changed files>— clean on the second run._handle_exceptionthrough the class instead ofselffailstest_every_failure_reaches_the_overridden_handle_exceptionwith the NeMo-RL message.GPU coverage is left to CI: the single-GPU disaggregated block of
l0_a10.yml(in particulartest_disaggregated_single_gpu,test_disaggregated_perf_metrics,test_disaggregated_chat_completion_tool_calls) and the multi-GPU disaggregated stages;openai_disagg_server.pyis on the multi-GPU trigger list, so those stages need theci: full pre-merge approvedlabel.PR Checklist
Please review the following before submitting your PR:
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-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin 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