Repository navigation
Conversation
JunyiXu-nv
left a comment
There was a problem hiding this comment.
LGTM. createResult returns nullopt before setMaxSentTokenLen, so those tokens go out with the next response; returning before the move keeps their logprobs with them. Non-empty responses are built exactly as before, and _handle_responses just skips a None response.
|
No actionable comments were generated in the recent review. Walkthrough
ChangesStreaming logprob preservation
Estimated code review effort: 2 (Simple) | ~8 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk:
|
|
Could you please rebase this branch onto the latest upstream |
…no result LlmRequest.create_response moved a streaming request's pending logprobs into the response's copy of py_result before checking whether the native result was empty, and dropped them when it was. The KV cache V2 scheduler can recompute-pause a request in the step that adds its token; the response built for it in that step has no result, so the token is streamed after the re-prefill without its logprob and the proxy fails the request on its logprobs length check. Return before touching py_result when there is no result. AI assistance (Cursor) was used for this change. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: YangHaoooooo <233765393+Hang-get@users.noreply.github.com>
d49b7cb to
67e96e2
Compare
|
Rebased onto current main ( |
Dev Engineer Review
LlmRequest.create_responsenow checks for an empty serialized result before the streaming logprob handoff. When the result is empty, the method returnsNoneandpy_resultkeeps its pending logprobs. The later empty-result check is removed. Non-empty responses follow the same path as before.Two points to note:
Noneeither way, so no response-visible metric changes. Confirm that no side effect in that block is needed for empty results.create_serialized_result()path.No public API or declaration changes.
QA Engineer Review
Test files changed:
tests/unittest/_torch/pyexecutor/test_request_utils.py: addedtest_streaming_logprobs_survive_a_response_without_result.The test runs
LlmRequestdirectly on a streaming request with logprobs. It checks three steps:[4]and logprob[[-0.5]].reset_for_recompute,create_response()returnsNone.[5, 6]and logprobs[-0.25, -0.125].The author reports that the test fails on
mainand passes with this change. These runs used a Python port and import stubs, not a built TensorRT-LLM. Native CI results are not available for this head.No integration test-list change applies. The author reports that the file falls under the
unittest/_torch/pyexecutorentry ofl0_cpu. This was not verified in the test-db lists.Not covered: the rest of
test_request_utils.py, GPU tests, the beam-width greater than 1 path, and end-to-end serving under KV cache pressure.Coverage verdict: needs follow-up. Confirm the test passes in native CI, and add an end-to-end or pressure test if serving behavior must be verified.
Per-File QA Perspective
tensorrt_llm/_torch/pyexecutor/llm_request.py: Verify that a recompute-paused streaming request with logprobs keeps its pending logprobs. Verify that the next response has one logprob for each token. Verify that non-empty responses behave as before.tests/unittest/_torch/pyexecutor/test_request_utils.py: Adds a CPU regression test for the recompute-pause logprob loss. Confirm it runs in CI with real bindings.Description
LlmRequest.create_responsehands a streaming request's pending logprobs to the response: it movespy_result._log_probsinto the response's copy ofpy_resultand gives the request a freshLogProbStorage. It did this before checking the serialized native result, and when that result was empty it returnedNone, so the moved logprobs were dropped.The native result is empty whenever the request is outside the generation states. With the KV cache manager V2 (the default),
KVCacheV2Schedulerrecompute-pauses started generation requests under KV cache pressure. In the overlap loop the victim is usually in the previous batch, so a single iteration does three things for it:_update_requestsappends its in-flight token and logprob._pause_recompute_paused_requestscallsreset_for_recompute, which setsCONTEXT_INITandpy_decoding_iter = 0._handle_responsescallscreate_response()for it.The token itself was not sent, so after the re-prefill it is streamed together with the next token, but only the next token's logprob is left.
GenerationResultBase._handle_sequencethen failsassert len(output.logprobs) == output.length, and the request errors out. Withstream_interval > 1every logprob accumulated since the last emitted response is lost the same way, in the non-overlap loop as well.This change returns
Noneright aftercreate_serialized_result()when the result is empty, so the logprobs stay in the request until a response carries them. Responses that have a result are built exactly as before.Closes #22091.
Test Coverage
test_streaming_logprobs_survive_a_response_without_resultintests/unittest/_torch/pyexecutor/test_request_utils.py(cpu_only, covered by theunittest/_torch/pyexecutorentry ofl0_cpu). A streaming request with logprobs is recompute-paused after its token and logprob were added.create_response()then returnsNone, and after the re-prefill the next response must carry both tokens and both logprobs. The test fails onmain([[-0.125]] != [[-0.25, -0.125]]) and passes with this change.test_python_logprob_response_snapshots(4 passed), with--noconftestagainst a Python port ofLlmRequest::createResult,createSerializedResultandpause()fromllmRequest.cpp/llmRequest.h, with import stubs for the other bindings. In CI the test runs against the real bindings.PyExecutor._handle_responsesthrough the three steps above and passed its responses through pickle intoGenerationResult._handle_response. Onmainthe proxy raisesAssertionError: logprobs length: 2 != output.length: 3; with this change it receives token_ids[10, 11, 12]with logprobs[-0.1, -0.2, -0.3].pre-commit run --fileson both files: all hooks passed; Pyrefly reported no diagnostics.test_request_utils.py, GPU tests, and an end-to-end serving run under KV cache pressure.PR Checklist
AI assistance (Cursor) was used for this change.