Repository navigation
[None][fix] Kimi-K3 on the Responses API: apply its request options and history rules through its serving extension - #21805
Draft
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>
…on the current main The disaggregated frontend now registers its routes with KvHintsRoute (NVIDIA#20269), which strips kv_hints from request bodies unless the server trusts its ingress. Regenerate the route reference: the six disaggregated configurations record KvHintsRoute where they recorded APIRoute; their route sets and order are unchanged. The route-class invariant, which a regenerated reference cannot hide, now requires KvHintsRoute on every API route of the disaggregated frontend and APIRoute on the coordinator app. Dropping the KV-hint policy from the frontend fails it even after a regeneration, and both apps still reject the aggregated server's msgpack route class. The object.__new__ baseline records the sites on main at the merge base: test_kv_hints.py builds one server that way (NVIDIA#20269), and test_harmony_parsing.py no longer builds any (NVIDIA#21176). The harmony parsing tests are now registered as a whole file (NVIDIA#21411), so the node-id entry that the CPU-selection test used as its example is gone. The node-id check now runs select_entries on a one-entry list of its own, so it no longer depends on which node-id entries l0_cpu.yml holds. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…ail on silent regressions The route snapshot now requires every configuration other than the three invalid ones to build, so a configuration that starts failing at construction cannot pass through a regenerated reference. The object.__new__ ratchet resolves 'from ... import <server class> as <name>' aliases and records each baseline site as its enclosing function or class plus the server class, so swapping one site for another in the same file no longer passes. The baseline keeps the same 52 sites in the same 26 files. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…r-snapshot-tests Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…r-snapshot-tests Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…r-snapshot-tests Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…r-snapshot-tests Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
The embedding server now sizes its encode batcher from the encoder executor's max_batch_size, max_num_tokens and max_seq_len instead of reading them from encoder_executor.model_engine. Give FakeEncoderExecutor the same attributes so the EMBEDDING role builds again; the real EncoderExecutor no longer has a model_engine. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…quest handling Add three hooks to ServingExtension for the Responses API path, with defaults that reproduce the generic behavior: - apply_responses_extensions(request): the Responses counterpart of apply_chat_extensions. request_preprocess calls it first, before the sampling parameters and the prompt are built, for relayed prompts too. - replay_stored_turn(message): how one stored message of a previous_response_id conversation is replayed. The default keeps a message as stored and replays a reasoning message as its tool calls only. - prepare_responses_history(messages): adapts the converted chat messages before they are rendered and stored. The default returns them unchanged. chat_messages_from_responses_input and _create_input_messages take the extension; _create_input_tokens resolves it from the render resources, and request_preprocess resolves it the same way (gpt-oss on the Harmony path). The KV-aware router renders a Responses request the way the worker does: it runs the model's Responses hooks on a copy of the request, converts the history with the same extension, and renders the rebuilt chat request with the model's tool serialization but without the chat request hook (render_prepared_chat). A hook that rejects the request leaves routing on the request as sent; the worker answers the rejection. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com> (cherry picked from commit da5340f)
…nd history rules through its serving extension A Responses request for a kimi_k3 checkpoint used to be prepared like any other model's: the generic reasoning_effort / thinking kwargs its template ignores, no tool_choice or response_format control messages, no sampling policy, and a replayed conversation the K3 renderer could not bind (a tool result returned in several items shifted every later result's index; a result without a call failed the whole turn). KimiK3ServingExtension now implements the Responses hooks: - apply_responses_extensions: reasoning.effort maps onto K3's thinking_effort (minimal/low -> low, medium/high -> high, xhigh/max -> max; "none" turns thinking off), tool_choice "required"/"none" with tools declared and text.format json_object/json_schema become the kwargs the template reads, explicit chat_template_kwargs win, and the TRTLLM_KIMI_PARAM_POLICY sampling policy applies to temperature and top_p. The policy is the one implementation chat uses; it now checks only the sampling fields a request type declares, so chat behavior and messages are unchanged. - prepare_responses_history: within a run of tool results each call keeps one result message (segments joined in order), and a result no call claims is replayed as a user note after its run, so the rest of the run still binds by id. The Responses handler no longer reports tool_choice="required" as unenforced for a model whose template enforces it through its extension. The serve test factory can build a kimi_k3 server (the composite checkpoint config, which is what resolves to that model type). Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com> (cherry picked from commit 7ad9f9117ec44c658761aec70f86c3af49d8890f) (cherry picked from commit 5cad4ebd840b5d484b24988fd743cdb0c131fedf) (cherry picked from commit c73f779eca5c0093ce39aff9b4f039a02d388979)
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) and on #21804 ([None][feat] Responses: serving-extension hooks for model-specific request handling): the first commits below belong to those PRs and disappear from this diff when they merge; this PR's own change is the last commit.
Description
A Responses request (
/v1/responses) for a Kimi-K3 checkpoint was prepared like any other model's: the genericreasoning_effort/thinkingchat-template kwargs, which the K3 template ignores, notool_choiceorresponse_formatcontrol messages, no sampling-parameter policy, and a replayed conversation the K3 renderer could not always bind. Seen in production agent traffic with Kimi-K3 behind a Responses client that replays its whole history every turn: a tool result returned in several items shifted the index of every later result in the turn, and a single result whose call was no longer in the history failed every later turn of the session with a 400.KimiK3ServingExtensionnow implements the Responses hooks ofServingExtension, so every Kimi-K3 rule lives in the extension and the Responses path keeps no model-name branch:apply_responses_extensions:reasoning.effortmaps onto K3'sthinking_effort(minimal/low -> low, medium/high -> high, xhigh/max -> max, each the nearest K3 level at or above the one asked for;"none"turns thinking off; no effort leaves the template default).tool_choice"required"/"none"with tools declared andtext.formatjson_object/json_schema(with its schema) become the kwargs the template reads. Explicit clientchat_template_kwargswin over derived values, as on chat. The same kwargs steer the kimi_k3 reasoning parser's initial channel, so the parser starts in the channel the prompt opened. TheTRTLLM_KIMI_PARAM_POLICYsampling policy (off by default) applies to the fields this endpoint has:top_punset or 1.0 is pinned to 0.95, other values and an out-of-rangetemperatureare rejected with a 400.prepare_responses_history: within a run of tool results each call keeps one result message (the segments of a split result are joined in order; the K3 renderer numbers results by counting messages), and a result no call before it claims is replayed as a user note carrying its output, after its run, so the rest of the run still binds by id. An assistant turn without calls ends the binding, as in the renderer. Nothing a client sent is dropped, and the input conversation is not mutated.On the Responses surface
mediumtherefore meansthinking_effort=high(andminimal->low,xhigh->max), as the Kimi-K3 deployment this comes from did; the chat surface keeps leaving those values to the template default. No chat behavior changes in this PR.The sampling policy is the single implementation chat already uses (
enforce_kimi_param_policy), generalized to check only the sampling fields a request type declares: a chat request has them all, a Responses request onlytemperatureandtop_p. Chat behavior and messages are unchanged (the existing chat policy tests pass untouched, and the new tests assert the chat messages verbatim).replay_stored_turnis left on its default for Kimi-K3: the store writer never puts reasoning and text in one stored message, so the "strip only the reasoning" rule and the default replay produce the same conversation on this branch; #21058 makes that rule the generic one.The handler no longer reports
tool_choice="required"as "accepted but not enforced" for a model whose extension enforces it through its template (required_tool_choice_mode() == "template") when tools are declared; other models, grammar-mode extensions (whose grammar the Responses path does not attach) andrequiredwithout tools are reported as before.The serve test factory can build a
kimi_k3server: its fake checkpoint is the composite (text + vision) config, which is the shape that resolves to thekimi_k3model type.Behavior change: for
model_type == "kimi_k3"only. Responses requests now render K3's thinking-effort, tool-choice and response-format control messages from the request fields, replay split or orphan tool results as described above, and (only withTRTLLM_KIMI_PARAM_POLICY=1) pintop_pand reject out-of-policy sampling values. Other models are unchanged. The KV-aware router follows: with the hooks PR it runs the same Responses hooks on a copy of the request, so its routing ids equal the worker's prompt ids. Measured with a probe template mirroring the K3 renderer's control-message placement (32-token routing blocks; before = this change on a router that rendered without the hooks, after = this branch):Interactions with open PRs: #21058 changes the generic stored-turn rule (see above). #21479 edits
serialize_tool, not touched here.Test Coverage
New
tests/unittest/serve/test_responses_kimi_k3.py(collected through theunittest/serveentry ofl0_cpu.yml), CPU only. The handler tests build akimi_k3server withisolated_serve_envand give it a probe chat template on a byte-level tokenizer, so the prompt the fake engine receives shows every kwarg and message the renderer was handed:"none"(prompt and parser: the reply is a message, not reasoning; streaming and not);tool_choicerequired/none with tools, auto, and required without tools;text.formatjson_schema (schema in the prompt), json_object, text; explicitchat_template_kwargswinning;top_p(unset and 1.0), rejecting a temperature of 1.5 before any engine call, and staying off by default;tool_choice="required"warning: absent for K3 with tools, present for K3 without tools, for a generic model and for a grammar-mode extension (captured with a handler on the TRT-LLM logger);previous_response_id;Verification (CPU host; a test-only plugin declares the C++ op the compiled artifacts predate): the new file (69 passed);
tests/unittest/serve(#21406'sobject.__new__ratchet and route snapshot included),test_kimi_serve_extensions.py,test_serving_extensions.py,test_render_callers.py,test_responses_input_preprocess.py,test_responses_store_replay.py,test_responses_usage.py,test_responses_serving_extensions.py,test_responses_streaming_events.py,api_stability/test_serve_routes.pyand the two Responses routing tests ofdisaggregated/test_router.py(690 passed together); the route snapshot regenerator reports no differences. Mutation gate: sixteen planted inverses of the change (a wrong effort level,"none"ignored,tool_choicederived without tools, the schema replaced, derived values beating explicit kwargs, the policy not applied, the policy reading undeclared fields, no merge, no orphan notes, a lost separator, a note before its run, the warning not suppressed, the warning suppressed without tools, call ids surviving an assistant turn without calls, the warning suppressed for grammar mode, the merge mutating its input) each turn the new tests red. Hypothesis properties over generated conversations and parameters (no result text lost, every remaining result binds by id within its run, one kept result per call then the run's notes between the same non-tool messages, idempotence, the policy agreeing between chat and Responses on the shared fields, effort never derived lower than asked,tool_choicederived only with tools, exactly one newline between joined segments) pass; the mutations on the paths they state break them, the handler-warning mutations and the schema value are caught by the example tests. Rendering through the real K3 checkpoint renderer needs the checkpoint files and is left to a GPU end-to-end run.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)
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.
Reviewers by CODEOWNERS: trt-llm-runtime-devs (
tensorrt_llm/serve/), trt-llm-devs (tests/unittest/serve/,tests/test_common/).tensorrt_llm/serve/openai_server.pyandtensorrt_llm/serve/extensions/are in the multi-GPU trigger list ofjenkins/L0_MergeRequest.groovy, so this PR needs theci: full pre-merge approvedlabel to run the full pre-merge pipeline.🤖 Generated with Claude Code