Skip to content

[Performance]: Streamed responses search the whole output text for stop strings on every response #22020

Open

Description

@Hang-get

Proposal to improve performance

DetokenizedGenerationResultBase._handle_response checks string stop
sequences after it detokenizes a response. On the streaming path it runs
stop_reason in beam_output.text (and then find) over the whole accumulated
text, for every stop string, on every response. With the default
stream_interval=1 that is one full-text search per generated token, so a
streamed request with stop strings spends time quadratic in its output
length. The search runs on the proxy's asyncio thread (the trtllm-serve
event loop when num_postprocess_workers=0, the default) or in a postprocess
worker, in series with the response handling of every other request.

Incremental detokenization only appends to text, and earlier responses
already searched the text before _last_text_len, so the first match must end
in the newly appended part. Starting the search len(stop) - 1 characters
before that point returns the same index as a full search. The full search
would stay where the whole output is decoded again from its tokens
(non-streaming, beam search, a trimmed stop word) and when a post-processor
hook is configured, because the hook can rewrite the last chunk after it was
searched.

CPU benchmark

Intel Xeon Platinum 8352Y, CPython 3.12.3, transformers 5.5.4, tokenizers
0.22.2, one pinned core. The benchmark drives the repository's
DetokenizedGenerationResultBase._handle_response with the Qwen3-0.6B
tokenizer (HF incremental detokenization), one response per generated token,
on English text from docs/source. The stop strings do not occur, so every
response is searched, as happens until a stop string appears. One warm-up
stream, then five runs per row; medians. Stop strings: ["</answer>"] and
["Question:", "</s>", "<|im_end|>"].

Output tokens (chars) stop Whole stream, main With the change Per response, last 1,000 responses
8,192 (28K) none 91.8 ms 90.4 ms 11.4 -> 11.4 us
8,192 (28K) 1 string 109.4 ms 100.1 ms 15.3 -> 12.2 us
8,192 (28K) 3 strings 151.5 ms 105.7 ms 25.6 -> 13.0 us
16,384 (58K) none 202.9 ms 204.8 ms 12.9 -> 13.1 us
16,384 (58K) 1 string 461.4 ms 218.8 ms 53.0 -> 13.9 us
16,384 (58K) 3 strings 796.0 ms 231.4 ms 98.1 -> 15.0 us
32,768 (125K) none 465.2 ms 451.6 ms 16.7 -> 16.6 us
32,768 (125K) 1 string 1767.0 ms 475.6 ms 102.2 -> 17.6 us
32,768 (125K) 3 strings 3356.5 ms 513.8 ms 214.4 -> 18.6 us

At 32K output tokens the change saves 1.3 s (one string) to 2.8 s (three
strings) of CPU per streamed request. Near 16K tokens each response
carries an extra 39-83 us; across 64 concurrent streams that is 2.5-5.3 ms of
serial work per decode step on the thread that also serves HTTP. Requests
without stop strings are unaffected.

26,000 randomized streams (both incremental detokenization backends, chunk
sizes 1-6, n > 1, beam search, non-streaming, token-level stop-word trims,
post-processor hooks, empty stop strings, multibyte byte-fallback tokens)
produce identical text deltas, final text, finish reasons and stop reasons on
main and with the change.

Current environment

  • OS: Linux x86_64

  • Python: 3.12.3

  • CPU: Intel Xeon Platinum 8352Y

  • GPU: not used

  • Base: current main at 70fe2b2a6

  • I searched open and closed issues and pull requests, including recent
    stop-string, streaming, detokenization and LLM API result-path work.

AI assistance (Cursor) was used to investigate and prepare this report.

Activity

  1. added
    General perf<NV>Broad performance issues not specific to a particular component
    on Nov 10, 2026
  2. added a commit that references this issue on Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    General perf<NV>Broad performance issues not specific to a particular component

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions