vLLM: reasoning not split from content when streaming (non-streaming works)

#6
by mmssix - opened

When serving MiniMax-M3.1-Flash with vLLM and --reasoning-parser minimax_m3, non-streaming responses split reasoning from content correctly, but streaming responses do not: the whole <mm:think>...</mm:think> block arrives in delta.content and delta.reasoning stays empty.

Environment

  • Image: vllm/vllm-openai:minimax-m31-flash (digest sha256:86f582dae25ea92802353727164ae4eb03b37b18baf9119b805ffaec2a7ef07c, vLLM 0.1.dev23341+g8744dd2fb)
  • Weights: this repo at main (54e4e69), chat_template.jinja unmodified
  • 4x RTX PRO 6000 Blackwell (sm120), TP4

Relevant serve args:

--tensor-parallel-size 4 --block-size 128 --disable-hybrid-kv-cache-manager
--kv-cache-dtype fp8 --attention-config '{"indexer_kv_dtype":"fp8"}'
--reasoning-parser minimax_m3 --enable-auto-tool-choice --tool-call-parser minimax_m3

Repro

Same prompt both ways: {"messages":[{"role":"user","content":"What is 17*23? Answer briefly."}]}

Non-streaming (correct):

reasoning: "17*23 = 391."
content:   "391"

Streaming ("stream": true), concatenated deltas:

reasoning: ""
content:   "<mm:think>17*23 = 391.</mm:think>391"

The first content delta is literally "<mm:think>".

Reproduced directly against the vLLM server, with no proxy in between.

What we tried

Request Streaming result
default (adaptive thinking) not split, <mm:think>...</mm:think> in content
"chat_template_kwargs": {"thinking_mode": "enabled"} not split
thinking_mode: enabled + "reasoning_effort": "high" not split, and content begins with a stray </mm:think>

vllm/reasoning/minimax_m3_reasoning_parser.py has not changed upstream since 2026-08-31, so a newer nightly does not seem to address it.

Is this a known issue with the vLLM integration, or is there a serving flag or chat-template kwarg we are missing for streaming? Happy to test a fix.

Side note for other sm120 (RTX PRO 6000 / consumer Blackwell) users

The vLLM recipe targets SM100. On sm120:

  • --kv-cache-dtype nvfp4 and indexer_kv_dtype: nvfp4 are rejected (SM100/SM103/SM107 only), so we use fp8.
  • The DSpark draft needs an explicit attention_backend. Left on auto it picks FlashInfer, which has no 128-token page and fails with No common block size for 128. FLASH_ATTN cannot hold an fp8 KV cache on sm120, so we use "attention_backend":"FLASH_ATTN","kv_cache_dtype":"bfloat16" for the draft.

With that it loads and runs well: ~240 tok/s single-stream decode, DSpark mean acceptance length ~3.05 with 3 speculative tokens.

Sign up or log in to comment