[Perf][Parser] Stream Qwen3 tool argument values incrementally (12 KB call: 2.1 s to 11 ms) (#62027)

Signed-off-by: Afonso Ribeiro <afonso.diamantino2@gmail.com>
Co-authored-by: Flora Feng <4florafeng@gmail.com>
This commit is contained in:
Afonso Ribeiro
2026-10-17 18:49:06 +00:00
committed by GitHub
co-authored by Flora Feng
parent bafb263664
commit 5c1c903501
3 changed files with 196 additions and 0 deletions
+5
View File
@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""MiMo's compact XML parameters contain verbatim string values."""
import functools
import json
from dataclasses import replace
@@ -10,6 +11,7 @@ from vllm.parser.qwen3 import (
_PARTIAL_PARAM_RE,
CHATML_TURN_BOUNDARIES,
Qwen3Parser,
_param_value_extender,
qwen3_config,
)
@@ -37,6 +39,9 @@ class MiMoParser(Qwen3Parser):
turn_boundary_tokens=CHATML_TURN_BOUNDARIES,
),
arg_converter=_mimo_arg_converter,
arg_value_extender=functools.partial(
_param_value_extender, trim_newlines=False
),
),
)
super().__init__(tokenizer, tools, **kwargs)
+60
View File
@@ -25,6 +25,7 @@ import regex as re
from vllm.parser.engine.events import EventType
from vllm.parser.engine.parser_engine import ParserEngine
from vllm.parser.engine.parser_engine_config import (
ArgValueExtender,
ParserEngineConfig,
ParserState,
Transition,
@@ -86,6 +87,64 @@ def _qwen3_arg_converter(raw_args: str, partial: bool) -> str:
return json.dumps(params, ensure_ascii=False)
# What ends an open value in ``_PARAM_RE``: a closing tag or the next opener.
_PARAM_VALUE_END_RE = re.compile(r"<\s*/\s*parameter\s*>|<\s*parameter\s*=")
def _param_value_end_window(text: str) -> str:
"""Return the suffix of *text* that may still grow into a value end."""
start = text.rfind("<")
if start == -1:
return ""
tail = text[start:]
return tail if _PARAM_VALUE_END_RE.fullmatch(tail, partial=True) else ""
def _param_value_extender(
raw_args: str, trim_newlines: bool
) -> ArgValueExtender | None:
"""Extend the open trailing ``<parameter=...>`` value of *raw_args*.
The extender streams exactly what the converter would for the same text.
With *trim_newlines* it follows ``_trim_wrapping_newlines``: it drops a
leading newline of the value and holds back a trailing one.
"""
match = _PARTIAL_PARAM_RE.search(_PARAM_RE.sub("", raw_args))
if match is None:
return None
name, value = match.group(1), match.group(2)
# A repeated key keeps its first position, so its value is not last.
if any(m.group(1) == name for m in _PARAM_RE.finditer(raw_args)):
return None
window = _param_value_end_window(value)
started = bool(value)
held = "\n" if trim_newlines and value.endswith("\n") and value != "\n" else ""
def extend(delta: str) -> str | None:
nonlocal window, started, held
text = window + delta
if _PARAM_VALUE_END_RE.search(text):
return None
window = _param_value_end_window(text)
if trim_newlines:
text = held + delta
if not started and text:
started = True
text = text.removeprefix("\n")
held = "\n" if text.endswith("\n") else ""
text = text[: len(text) - len(held)]
else:
text = delta
return json.dumps(text, ensure_ascii=False)[1:-1]
return extend
def _qwen3_arg_value_extender(raw_args: str) -> ArgValueExtender | None:
return _param_value_extender(raw_args, trim_newlines=True)
@functools.cache
def qwen3_config(
thinking: bool = True,
@@ -223,6 +282,7 @@ def qwen3_config(
),
},
arg_converter=_qwen3_arg_converter,
arg_value_extender=_qwen3_arg_value_extender,
stream_arg_deltas=True,
strip_trailing_reasoning_whitespace=False,
tool_args_json=False,