Repository navigation
[TRTLLM-17262][refactor] Move the disaggregated service types and latency logger out of responses_utils - #21510
Draft
JunyiXu-nv wants to merge 13 commits into
Draft
JunyiXu-nv wants to merge 13 commits into
JunyiXu-nv wants to merge 13 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>
…lm/serve/_base/msgspec_route.py Generated move: _msgpack_decoder, _MsgspecRequest and _MsgspecRoute leave tensorrt_llm/serve/openai_server.py byte for byte; the module imports _MsgspecRoute back for its app router and keeps no re-export (the three names are private). The two disaggregated test importers now import the new module. OpenAIServer.health, .version and .get_model are removed here and replaced by the system router added in the following commits. tensorrt_llm/serve/_base/msgspec_route.py joins legacy-files.txt (its code comes from a legacy-formatted file), and the generated lint configs are regenerated with scripts/legacy_utils.py gen-configs. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…ve the system endpoints through it New structure: tensorrt_llm/serve/_base/ (shared serving infrastructure) and tensorrt_llm/serve/api/ (endpoint packages, one router plus one serving class each; the package __init__ imports nothing so routers load lazily, by role). serve/_base/routing.py provides make_router(), which always uses the msgpack-decoding route class, and attach_routers(), which imports and attaches only the routers whose RouterSpec matches the server role. The first endpoint package, serve/api/ops/system/, serves /health, /version and /v1/models for every role. OpenAIServer builds a SystemServing with the injected _health_probe (today's probe body, SIGINT side effect included) and attaches the routers before the role dispatch; the 12 add_api_route lines for the three paths leave the four role tables. Same routes, methods, route classes, bodies and conflict-relevant order: the recorded route reference is unchanged. The layering tests gain the new packages' contracts, the route-snapshot failure message points new endpoints at the router pattern, and the serve agent docs describe where a new endpoint goes. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…w serve packages tensorrt_llm/serve/_base/ and tensorrt_llm/serve/api/ops/ join the multi-GPU trigger list: the msgpack route class decodes the disaggregated frontend's worker requests, and /health is the disaggregated readiness probe, so changes under these directories keep running the multi-GPU stages that exercise that path. The three new modules join the strict mypy file set, and the type-check pre-commit hook now also triggers on changes under tensorrt_llm/serve/ and tensorrt_llm/commands/serve_cli/, so edits to typed serving modules get local type checking; the hook still checks only the declared file set. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
… tests that build routes by hand Two multi-frontend tests build an OpenAIServer without its constructor and call register_routes() themselves. Since /health, /version and /v1/models are attached through the system router by the constructor, those tests no longer saw the three routes: one asserted that /version stays local on an attached frontend (404), the other reported the three per-frontend-safe entries as removed. Both helpers now set the serving object and attach the routers before the hand registration, in the constructor's order, and the route enumeration walks included routers, which FastAPI 0.139 keeps as objects without a path in app.routes. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…n the OpenEngine metrics test The HTTP-enablement case builds a real OpenAIServer through its constructor and looked for /prometheus/metrics by reading .path off every entry of app.routes. Since the constructor attaches the system router, FastAPI 0.139 keeps an included-router object there that has no .path, and the child process failed with AttributeError. The test now matches a GET /prometheus/metrics request against the routes with the public route-matching API, which works on both ends of the pinned FastAPI range. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…d name The check that endpoint packages under tensorrt_llm/serve/api do not import each other read the package off the from-module alone, so `from tensorrt_llm.serve import api` inside an endpoint package passed and `from tensorrt_llm.serve.api.ops import system` inside ops/system failed. It now reads the package off <module>.<name> per imported name, as the layering contracts do, and a synthetic-edge test pins both forms. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…ency logger out of responses_utils tensorrt_llm/serve/responses_utils.py is the Responses API implementation, but the disaggregated client, service, coordinator and router imported it only for three small service types and a latency logger, and so loaded the tool parsers, web_search and the harmony adapter at import time. These definitions move, verbatim, into the shared serving package: tensorrt_llm.serve.responses_utils.ResponseHooks -> tensorrt_llm.serve._base.service_types tensorrt_llm.serve.responses_utils.done_generator -> tensorrt_llm.serve._base.service_types tensorrt_llm.serve.responses_utils.UCompletionResponseOrGenerator -> tensorrt_llm.serve._base.service_types tensorrt_llm.serve.responses_utils.PeriodicLatencyLogger -> tensorrt_llm.serve._base.latency_logger The old module keeps no re-export: no code outside the repository imports these names from it, and every importer in the repository is rewritten to the new modules (openai_client, openai_disagg_service, openai_service, the function-level imports in router and disagg_coordinator, and the two tests under tests/unittest/disaggregated/). The imports that responses_utils.py no longer needs (ABC, abstractmethod, UCompletionRequest, UCompletionResponse) go with them. The two new modules take the formatting group of the file their code comes from: they are registered in legacy-files.txt and the derived lint configs are regenerated with scripts/legacy_utils.py gen-configs. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…tils in the disaggregated client and server tensorrt_llm/serve/openai_client.py and tensorrt_llm/serve/openai_disagg_server.py took get_steady_clock_now_in_seconds from the re-export in tensorrt_llm/serve/responses_utils.py; they now import it from tensorrt_llm._utils, where it is defined, so that nothing under the disaggregated client needs the Responses implementation for the clock. The re-export in responses_utils.py itself is unchanged. openai_client still binds the clock as a module-level name, so the tests that patch tensorrt_llm.serve.openai_client.get_steady_clock_now_in_seconds keep intercepting it. The disaggregated server also imports ResponseHooks from tensorrt_llm.serve._base.service_types, the module that defines it, instead of through openai_disagg_service. The copyright year of tensorrt_llm/serve/openai_service.py, whose import this change edits, is brought to 2026. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
… the Responses implementation A layering contract in tests/unittest/serve/test_layering.py forbids tensorrt_llm.serve.responses_utils as an import of openai_client, openai_service, openai_disagg_service, disagg_coordinator and router, at module level and inside functions. Its one allowance is responses_done_generator in openai_disagg_service, which leaves when the Responses stream code shared with the disaggregated frontend moves to tensorrt_llm/serve/_base/responses_stream.py; the allowance only shrinks. tests/unittest/serve/test_disagg_imports.py imports each of those modules in a fresh interpreter and checks that the Responses implementation is not loaded at all, with a positive control that the probe reports it when it is imported. The openai_disagg_service case is a strict expected failure for the same responses_done_generator import. The file is collected through the existing unittest/serve entry of the CPU test list and carries the cpu_only marker. The expected failure is limited to the import-isolation assertion: an import crash of the module raises CalledProcessError and fails the test. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
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 #21448 ([TRTLLM-17261][refactor] Add the trtllm-serve router skeleton and move /health, /version and /v1/models into ops.system), which is itself 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 those PRs and disappear from this diff when they merge; this PR's own change is the last 3 commits.
@coderabbitai summary
Description
tensorrt_llm/serve/responses_utils.pyis the Responses API implementation, but four disaggregated-serving modules imported it only for three small service types, a latency logger and a re-exported clock function, and so loaded the tool parsers,web_searchand the harmony adapter although the disaggregated frontend does not serve/v1/responses. This PR movesResponseHooks,done_generatorandUCompletionResponseOrGeneratorintotensorrt_llm/serve/_base/service_types.pyandPeriodicLatencyLoggerintotensorrt_llm/serve/_base/latency_logger.py, generated as a verbatim move (check below), rewrites every importer in the repository, and makesopenai_client.pyandopenai_disagg_server.pyimportget_steady_clock_now_in_secondsfromtensorrt_llm._utils, where it is defined (the re-export inresponses_utils.pystays). The disaggregated server also importsResponseHooksfrom the module that defines it instead of throughopenai_disagg_service.Part of the trtllm-serve refactor (epic TRTLLM-17056, task TRTLLM-17262: the small splits that the Responses, request-trace and disaggregated-entry-point changes from the large-scale-serving branches land into when they go upstream). Builds on #21448 (
tensorrt_llm/serve/_base/) and #21406 (the layering-contract test). It must merge before the shared Responses stream code moves totensorrt_llm/serve/_base/responses_stream.pyand before the Responses endpoint package move, so that those land next to these types instead of intoresponses_utils.py.The old module keeps no re-export: no code outside the repository imports these four names from
responses_utils(GitHub code search; every hit is a copy of this repository), and every importer in the repository now imports the new modules:openai_client,openai_disagg_service,openai_service, the function-level imports inrouteranddisagg_coordinator, and the two tests undertests/unittest/disaggregated/. An open PR that imports one of them fromresponses_utilsgets anImportErrorafter this merges and re-applies its import on the new path; a function-level import does so when the function first runs.Two guards pin the result (
tests/unittest/serve/): a layering contract forbidstensorrt_llm.serve.responses_utilsas an import of the disaggregated client, service, coordinator and router, at module level and inside functions; andtest_disagg_imports.pyimports each of them in a fresh interpreter and checks that the Responses implementation is not loaded at all, with a positive control. Both carry one allowance forresponses_done_generator, whichopenai_disagg_servicestill takes fromresponses_utilssince #19883 (Responses on disaggregated serving); it leaves with that helper when the shared Responses stream code moves toserve/_base/responses_stream.py, and the allowance only shrinks (the fresh-interpreter case is a strict expected failure until then).Static import graph (module-level imports; "direct" names what is still imported from
responses_utils):serve.openai_disagg_serverServerArrivalTimeMiddleware,guard_responses_stream,stream_error_eventguard_responses_stream,stream_error_event(from #19883; they leave with the Responses stream helpers)serve.openai_clientserve.openai_serviceserve.openai_disagg_serviceresponses_done_generator(from #19883)serve.disagg_coordinatoropenai_clientserve.routerserve._base.service_types,serve._base.latency_loggerCoordination with open PRs: this PR moves
ResponseHooks,done_generatorandUCompletionResponseOrGeneratorout oftensorrt_llm/serve/responses_utils.pyintotensorrt_llm.serve._base.service_types, andPeriodicLatencyLoggerintotensorrt_llm.serve._base.latency_logger, with no re-export at the old path. Open PRs that import any of them fromresponses_utilsmust retarget their import to the new module: #21442 (Split the disaggregated server's entry-point wrapper; addsUCompletionResponseOrGeneratorto theresponses_utilsimport ofopenai_disagg_server.py), #21447 (Shared middleware package; edits the same import block ofopenai_disagg_server.py,tests/unittest/disaggregated/test_disagg_openai_client.py,tests/unittest/serve/test_layering.pyandlegacy-files.txt) and #21377 (Muse Spark Open modeling_v2 TEP target; adds a function-levelPeriodicLatencyLoggerimport fromresponses_utilsinrouter.py, which would fail when that code first runs). Whichever of each pair lands second re-applies its import edits on the new path. With #21447 the disaggregated server also stops importingServerArrivalTimeMiddlewarefromresponses_utils; whichever of this PR and #21447 lands second addsopenai_disagg_serverto the two guards once its lastresponses_utilsimport is gone. The same applies to #21060, #21388 and #21452, which rewrite that import block, and to the draft #16402, which addsResponseHooksandUCompletionResponseOrGeneratorimports.Behavior
No behavior change. Every moved definition is byte-identical (check below); only import statements change. The function-level imports of
PeriodicLatencyLoggerinrouter.pyanddisagg_coordinator.pystay inside their functions, so nothing runs at a different time.openai_clientstill bindsget_steady_clock_now_in_secondsas a module-level name, so the tests that patchtensorrt_llm.serve.openai_client.get_steady_clock_now_in_secondskeep intercepting it (verified with the real clock temporarily raising: the three patching tests pass, 111 unpatched callers fail).Moves
Pure-move check
Moves without a shim, "old path → new path" for the release notes (no code outside the repository imports these names from the old module):
No kept shim and no shim identity rows. The
get_steady_clock_now_in_secondsre-export inresponses_utils.pyis unchanged (not a moved name). History:git log -C --follow tensorrt_llm/serve/_base/service_types.pyandgit blame -C -C tensorrt_llm/serve/_base/latency_logger.py.CI bookkeeping
tensorrt_llm/serve/_base/entry from [TRTLLM-17261][refactor] Add the trtllm-serve router skeleton and move /health, /version and /v1/models into ops.system #21448 covers both new modules; the edited importers are already listed)/tensorrt_llm/servecovers the new files;/tests/unittestcovers the new test until the serve test-directory rules land)tensorrt_llm/serve/_base/latency_logger.pyandtensorrt_llm/serve/_base/service_types.pyadded (both Group B: their code comes from the Group B fileresponses_utils.py); derived configs regenerated withscripts/legacy_utils.py gen-configs(ruff-legacy.toml, the managed blocks ofpyproject.tomland.pre-commit-config.yaml);ruff-legacy-baseline.json: none (the moved code carries no recorded violation at the new paths;responses_utils.pyonly improves). The stale entriesgen-configswarns about predate this PR and are left alone.tests/unittest/serve/test_disagg_imports.pyis collected through the existingunittest/serveentry oftests/integration/test_lists/test-db/l0_cpu.ymland carriespytest.mark.cpu_only.Test Coverage
CPU checks run from the branch (no GPU on the development host; GPU tests are left to CI):
4 definitions moved verbatim; 0 differences; shim exports 0 names; the same result with the merge base withmainas the base commit.*.py(module-level and function-levelfrom tensorrt_llm.serve.responses_utils import ...) prints nothing;git grepforresponses_utils.<moved name>in tests prints nothing;import tensorrt_llm.serve.responses_utilsbinds none of the four names (no re-export).openai_client,openai_service,disagg_coordinator,router,_base.service_types,_base.latency_loggerdo not importresponses_utils;openai_disagg_server(ServerArrivalTimeMiddleware,guard_responses_stream,stream_error_event) andopenai_disagg_service(responses_done_generator) still do, as described above.pytest -m cpu_only tests/unittest/serve/test_layering.py -q: 9 passed (the new contract and its one allowance included; the stale-allowance check passes).pytest -m cpu_only tests/unittest/serve/test_disagg_imports.py -q -rxX: 5 passed, 1 xfailed (openai_disagg_service, strict) in 2:43; the positive control passes.pytest -m cpu_only tests/unittest/serve tests/unittest/disaggregated -q: 1528 passed, 1 skipped, 3 xfailed, 144 deselected, 3 subtests passed in 7:03.tensorrt_llm._utils.get_steady_clock_now_in_secondstemporarily raising, the three tests that patchtensorrt_llm.serve.openai_client.get_steady_clock_now_in_seconds(test_request_metrics_normalize_frontend_clock_domain,test_streaming_metrics_normalize_frontend_clock_domain,test_refused_frontend_port_falls_back_to_shared_port) still pass and 111 unpatched callers in the same file fail; the temporary edit is not committed.LLM_MODELS_ROOT=... PYTHONPATH=<abs>/tests:<abs> python3 -m test_common.serve.cpu_tests(one pytest process per CPU test-list entry, as the CPU stage runs them): 66 pytest processes (every kept entry ran; the runner reports the first non-zero code): 4199 passed, 1 xfailed (the declaredopenai_disagg_servicecase), 30 deselected, 2 failed, both unrelated to this change and not reproducible as regressions:grpc/openengine/test_frontend_lifecycle.py::test_frontend_group_cleanup[shutdown]timed out (90 s lifecycle wait) on a loaded host and passes in isolation on this branch (1 passed in 49.65s; the OpenEngine frontend imports none of the moved names), andllmapi/apps/test_render_standalone.py::TestCpuOnlyStartup::test_the_launcher_loads_a_cuda_stub_only_when_the_driver_is_missingfails withtrtllm-render is not installedidentically on the dependency branch (the console script is absent in this source-overlay environment).pytest tests/unittest/api_stability -q: 98 passed in 8.16s (no file undertests/unittest/api_stability/references*/changes).pre-commit run --files <every changed file>twice: the first pass applied isort/autoflake/ruff import ordering and removed the four now-unused imports fromresponses_utils.py; the second pass changed nothing;python scripts/legacy_utils.py check-configsexits 0 (its stale-entry hints predate this PR).tests/integration/test_lists/test-db/l0_cpu.ymllistsunittest/serve(directory entry;scripts/test_to_stage_mapping.py --tests "unittest/serve"names the CPU-Generic stages);grep -c "pytest.mark.cpu_only" tests/unittest/serve/test_disagg_imports.pyis 1.postprocess_handlers.pyimports onlyResponsesStreamingProcessorandcreate_response_non_storefromresponses_utils).GPU coverage (CI):
disaggregated/test_disaggregated.py::test_disaggregated_single_gpuand::test_disaggregated_perf_metrics(l0_a10.yml) exerciseResponseHooksthrough the perf-metrics hooks; the multi-GPU stages are added becauseopenai_client.py,openai_disagg_server.py,openai_disagg_service.py,responses_utils.pyandrouter.pyare in the multi-GPU file list, and they 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