[None][perf] disagg: skip per-chunk JSON parse on streaming usage rewrite - #15042
Conversation
7ab5ff8 to
641f858
Compare
…rite When rewriting a disaggregated streaming response's usage from the context server, _rewrite_usage_sse_event_from_ctx ran json.loads on EVERY relayed SSE chunk, even though a top-level usage object only appears on the final chunk (and only when the client requested usage). On the orchestrator's single event loop this full JSON parse of every relayed output token is ~5% of one core at high concurrency (~15k tok/s). Add a byte pre-filter: a top-level usage field cannot exist unless the chunk bytes contain "usage", so skip the splitlines + json.loads for chunks that don't. The event is returned verbatim on the fast path (identical to the prior no-usage behavior), so the rewrite is never dropped; a rare token whose content is literally "usage" just re-parses harmlessly. Micro-bench: 3.46us -> 0.25us per chunk (~5%% -> ~0.4%% of one core). Existing streaming usage-rewrite unit test (test_send_disagg_request) covers both paths and passes. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
641f858 to
2ce78b4
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #52691 [ run ] triggered by Bot. Commit: |
|
PR_Github #52691 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #52721 [ run ] triggered by Bot. Commit: |
|
PR_Github #52721 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #52768 [ run ] triggered by Bot. Commit: |
|
PR_Github #52768 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #52822 [ run ] triggered by Bot. Commit: |
|
PR_Github #52822 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #52848 [ run ] triggered by Bot. Commit: |
|
PR_Github #52848 [ run ] completed with state
|
peihu-nv
left a comment
There was a problem hiding this comment.
CI failure looks to be flaky test. Not due to the PR
…rite (NVIDIA#15042) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com>
…VIDIA#15042 NVIDIA#15042 ("skip per-chunk JSON parse on streaming usage rewrite") was a 5-line optimization on feat/deepseek_v4, layered on the method-based usage-rewrite machinery introduced by NVIDIA#13620. When this PR carved NVIDIA#15042 onto main, that machinery did not exist there: main's NVIDIA#14177 ("Fix disaggregated usage propagation") had refactored NVIDIA#13620 into postprocess_handlers (rewrite_usage_info_from_ctx, operating on parsed chunk objects). The carve folded the obsolete pre-refactor machinery back in to make the patch apply. The result is dead code: _rewrite_disagg_usage (the chain root) has no caller, so the whole _ctx_usage_for_client / _rewrite_usage_* / _rewrite_streaming_usage_from_ctx subtree is unreachable, and NVIDIA#15042's json.loads-skip optimization sits inside it (a no-op). main already rewrites disagg usage via postprocess_handlers and relays streaming bytes untouched, so the optimized per-chunk-parse path no longer exists. Remove the dead methods and the now-orphaned imports (json, AsyncIterator, PromptTokensDetails). No behavior change: the live usage path is unaffected. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…rite (NVIDIA#15042) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Signed-off-by: Fanrong Li <lfr-0531@users.noreply.github.com>
…VIDIA#15042 NVIDIA#15042 ("skip per-chunk JSON parse on streaming usage rewrite") was a 5-line optimization on feat/deepseek_v4, layered on the method-based usage-rewrite machinery introduced by NVIDIA#13620. When this PR carved NVIDIA#15042 onto main, that machinery did not exist there: main's NVIDIA#14177 ("Fix disaggregated usage propagation") had refactored NVIDIA#13620 into postprocess_handlers (rewrite_usage_info_from_ctx, operating on parsed chunk objects). The carve folded the obsolete pre-refactor machinery back in to make the patch apply. The result is dead code: _rewrite_disagg_usage (the chain root) has no caller, so the whole _ctx_usage_for_client / _rewrite_usage_* / _rewrite_streaming_usage_from_ctx subtree is unreachable, and NVIDIA#15042's json.loads-skip optimization sits inside it (a no-op). main already rewrites disagg usage via postprocess_handlers and relays streaming bytes untouched, so the optimized per-chunk-parse path no longer exists. Remove the dead methods and the now-orphaned imports (json, AsyncIterator, PromptTokensDetails). No behavior change: the live usage path is unaffected. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…rite (NVIDIA#15042) 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>
…VIDIA#15042 NVIDIA#15042 ("skip per-chunk JSON parse on streaming usage rewrite") was a 5-line optimization on feat/deepseek_v4, layered on the method-based usage-rewrite machinery introduced by NVIDIA#13620. When this PR carved NVIDIA#15042 onto main, that machinery did not exist there: main's NVIDIA#14177 ("Fix disaggregated usage propagation") had refactored NVIDIA#13620 into postprocess_handlers (rewrite_usage_info_from_ctx, operating on parsed chunk objects). The carve folded the obsolete pre-refactor machinery back in to make the patch apply. The result is dead code: _rewrite_disagg_usage (the chain root) has no caller, so the whole _ctx_usage_for_client / _rewrite_usage_* / _rewrite_streaming_usage_from_ctx subtree is unreachable, and NVIDIA#15042's json.loads-skip optimization sits inside it (a no-op). main already rewrites disagg usage via postprocess_handlers and relays streaming bytes untouched, so the optimized per-chunk-parse path no longer exists. Remove the dead methods and the now-orphaned imports (json, AsyncIterator, PromptTokensDetails). No behavior change: the live usage path is unaffected. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…rite (NVIDIA#15042) 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>
…VIDIA#15042 NVIDIA#15042 ("skip per-chunk JSON parse on streaming usage rewrite") was a 5-line optimization on feat/deepseek_v4, layered on the method-based usage-rewrite machinery introduced by NVIDIA#13620. When this PR carved NVIDIA#15042 onto main, that machinery did not exist there: main's NVIDIA#14177 ("Fix disaggregated usage propagation") had refactored NVIDIA#13620 into postprocess_handlers (rewrite_usage_info_from_ctx, operating on parsed chunk objects). The carve folded the obsolete pre-refactor machinery back in to make the patch apply. The result is dead code: _rewrite_disagg_usage (the chain root) has no caller, so the whole _ctx_usage_for_client / _rewrite_usage_* / _rewrite_streaming_usage_from_ctx subtree is unreachable, and NVIDIA#15042's json.loads-skip optimization sits inside it (a no-op). main already rewrites disagg usage via postprocess_handlers and relays streaming bytes untouched, so the optimized per-chunk-parse path no longer exists. Remove the dead methods and the now-orphaned imports (json, AsyncIterator, PromptTokensDetails). No behavior change: the live usage path is unaffected. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
@coderabbitai summary
Description
In disaggregated serving the orchestrator relays the generation server's streaming
SSE response back to the client, and rewrites the
usageobject so the reportedprompt_tokens/cached_tokensreflect the context server (where prefillactually happened) rather than the generation server.
_rewrite_usage_sse_event_from_ctxwas invoked for every relayed SSE chunk andran
splitlines()+json.loads()on each one — but a top-levelusageobjectonly appears on the final chunk (and only when the client requested usage).
So for essentially every streamed output token the orchestrator did a full JSON
parse just to discover "no usage here, nothing to rewrite". On the orchestrator's
single asyncio event loop this is pure per-token overhead that serializes all
concurrent streams: ~5% of one core at high concurrency (~15k relayed tok/s).
This PR adds a cheap byte pre-filter before the parse:
A top-level
usagefield cannot exist unless the chunk bytes contain"usage",so chunks without it skip the
splitlines()+json.loads()entirely and arereturned verbatim — byte-for-byte identical to the previous no-usage behavior.
The rewrite is never dropped (when a
usagefield exists,"usage"is present inthe bytes, so the chunk still takes the full path); a rare token whose content is
literally
"usage"simply falls through and re-parses harmlessly (the nestedmatch is not a top-level key, so it returns unchanged).
Measured (
json.dumps-shaped chat.completion.chunk, in-container aarch64):_rewrite_usage_sse_event_from_ctxdrops from 3.46 µs → 0.25 µs per chunk(~5% → ~0.4% of one core at 15.4k tok/s). Behavior is unchanged; this only avoids
work on chunks that have nothing to rewrite.