@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ecbfbb8d61
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 5, 08:30 UTC

[Feature] Add auto-detection for reasoning_config when only reasoning_parser is set (#38214)

Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com>

Chauncey committed Apr 10, 2026 at 09:36 UTC ecbfbb8d61cbe1e2dfd63c5a39490e59922fb1b4
10 files changed +110 -16
docs/features/reasoning_outputs.md
+1 -1
@@ -249,7 +249,7 @@ Token counting starts from `reasoning_start_str`. Once the reasoning token count
249 To use this feature:
250
251 - `--reasoning-parser` enables reasoning extraction.
252 -- `--reasoning-config` defines the reasoning boundary tokens (e.g., `reasoning_start_str`, `reasoning_end_str`).
252 +- `--reasoning-config` defines the reasoning boundary tokens (e.g., `reasoning_start_str`, `reasoning_end_str`). If not set, vLLM will attempt to automatically initialize these tokens from the reasoning parser.
253 - `thinking_token_budget` (a sampling parameter) sets the per-request reasoning token limit.
254
255 If `thinking_token_budget` is not specified, no explicit reasoning limit is applied beyond normal generation constraints such as `max_tokens`.
tests/entrypoints/openai/chat_completion/test_thinking_token_budget.py renamed
+28 -3
@@ -24,6 +24,24 @@ def server():
24 "--max-model-len",
25 "2048",
26 "--enforce-eager",
27 + "--gpu-memory-utilization",
28 + "0.4",
29 + "--no-async-scheduling",
30 + ]
31 + with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
32 + yield remote_server
33 +
34 +
35 +@pytest.fixture(scope="module")
36 +def server_with_auto_reasoning_config():
37 + args = [
38 + "--reasoning-parser",
39 + "qwen3",
40 + "--max-model-len",
41 + "2048",
42 + "--enforce-eager",
43 + "--gpu-memory-utilization",
44 + "0.4",
45 "--no-async-scheduling",
46 ]
47 with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
@@ -31,12 +49,18 @@ def server():
49
50
51 @pytest_asyncio.fixture
34 -async def client(server):
35 - async with server.get_async_client() as async_client:
52 +async def client(request, server, server_with_auto_reasoning_config):
53 + server_map = {
54 + "default": server,
55 + "auto_config": server_with_auto_reasoning_config,
56 + }
57 + target_server = server_map[request.param]
58 + async with target_server.get_async_client() as async_client:
59 yield async_client
60
61
62 @pytest.mark.asyncio
63 +@pytest.mark.parametrize("client", ["default", "auto_config"], indirect=True)
64 async def test_thinking_token_budget_mixed_requests(client: openai.AsyncOpenAI):
65 """Test that mixed requests (some with thinking_token_budget, some without)
66 complete successfully without errors."""
@@ -61,6 +85,7 @@ async def test_thinking_token_budget_mixed_requests(client: openai.AsyncOpenAI):
85
86
87 @pytest.mark.asyncio
88 +@pytest.mark.parametrize("client", ["default", "auto_config"], indirect=True)
89 async def test_thinking_token_budget_limits_reasoning(client: openai.AsyncOpenAI):
90 """Test that thinking_token_budget limits the number of reasoning tokens.
91
@@ -82,6 +107,6 @@ async def test_thinking_token_budget_limits_reasoning(client: openai.AsyncOpenAI
107 reasoning_token_count += 1
108
109 assert reasoning_token_count == THINK_BUDGET, (
85 - f"reasoning tokens ({reasoning_token_count}) != "
110 + f"reasoning tokens ({reasoning_token_count}) exceeded "
111 f"thinking_token_budget ({THINK_BUDGET})"
112 )
tests/v1/logits_processors/test_correctness.py
+1
@@ -106,6 +106,7 @@ class MockReasoningConfig:
106
107 reasoning_start_token_ids = [THINK_START_TOKEN_ID]
108 reasoning_end_token_ids = [THINK_END_TOKEN_ID]
109 + enabled = True
110
111
112 def _generate_fake_sampling_metadata(
vllm/config/reasoning.py
+40 -7
@@ -5,6 +5,7 @@ from dataclasses import field
5
6 from vllm.config.model import ModelConfig
7 from vllm.config.utils import config
8 +from vllm.reasoning import ReasoningParserManager
9 from vllm.tokenizers import cached_tokenizer_from_config
10
11
@@ -18,11 +19,11 @@ class ReasoningConfig:
19 `initialize_token_ids` and are not intended to be set directly.
20 """
21
21 - # NOTE: These parameters are temporary, the intent is to derive them
22 - # automatically from the reasoning parser in a future version.
23 - reasoning_start_str: str = "<think>"
22 + reasoning_parser: str = ""
23 + """The name of the ReasoningParser to use for this model."""
24 + reasoning_start_str: str = ""
25 """String that indicates the start of reasoning."""
25 - reasoning_end_str: str = "</think>"
26 + reasoning_end_str: str = ""
27 """String that indicates the end of reasoning content."""
28
29 _reasoning_start_token_ids: list[int] | None = field(
@@ -36,6 +37,16 @@ class ReasoningConfig:
37 """Private backing field for `reasoning_end_token_ids`. Set by
38 `initialize_token_ids`. Not intended to be configured directly."""
39
40 + _enabled: bool = field(default=False, init=False, repr=False)
41 + """Private field indicating whether reasoning token IDs have been initialized.
42 + Set to True by `initialize_token_ids` once token IDs are initialized."""
43 +
44 + @property
45 + def enabled(self) -> bool:
46 + """Returns True if reasoning is enabled (i.e. if token IDs have been
47 + initialized), False otherwise."""
48 + return self._enabled
49 +
50 @property
51 def reasoning_start_token_ids(self) -> list[int] | None:
52 """Token IDs derived from `reasoning_start_str`. Set automatically by
@@ -54,15 +65,36 @@ class ReasoningConfig:
65 self._reasoning_start_token_ids is not None
66 and self._reasoning_end_token_ids is not None
67 ):
57 - return
68 + self._enabled = True
69 + return # Already initialized
70
71 tokenizer = cached_tokenizer_from_config(model_config=model_config)
72 + reasoning_start_str = self.reasoning_start_str
73 + reasoning_end_str = self.reasoning_end_str
74 + if self.reasoning_parser is not None and (
75 + not reasoning_start_str or not reasoning_end_str
76 + ):
77 + parser_cls = ReasoningParserManager.get_reasoning_parser(
78 + self.reasoning_parser
79 + )
80 + reasoning_parser = parser_cls(tokenizer)
81 + start_token = reasoning_parser.reasoning_start_str
82 + if start_token and not reasoning_start_str:
83 + reasoning_start_str = start_token
84
85 + end_token = reasoning_parser.reasoning_end_str
86 + if end_token and not reasoning_end_str:
87 + reasoning_end_str = end_token
88 +
89 + if not reasoning_start_str or not reasoning_end_str:
90 + # If we don't have valid strings to tokenize,
91 + # we can't initialize the token IDs.
92 + return
93 self._reasoning_start_token_ids = tokenizer.encode(
62 - self.reasoning_start_str, add_special_tokens=False
94 + reasoning_start_str, add_special_tokens=False
95 )
96 self._reasoning_end_token_ids = tokenizer.encode(
65 - self.reasoning_end_str, add_special_tokens=False
97 + reasoning_end_str, add_special_tokens=False
98 )
99
100 if not self._reasoning_start_token_ids or not self._reasoning_end_token_ids:
@@ -72,3 +104,4 @@ class ReasoningConfig:
104 f"reasoning_end_str='{self.reasoning_end_str}'. "
105 "Ensure the strings are valid tokens in the model's vocabulary."
106 )
107 + self._enabled = True
vllm/config/vllm.py
+6
@@ -1210,6 +1210,12 @@ class VllmConfig:
1210
1211 if self.reasoning_config is not None and self.model_config is not None:
1212 self.reasoning_config.initialize_token_ids(self.model_config)
1213 + if not self.reasoning_config.enabled:
1214 + logger.warning_once(
1215 + "Auto-initialization of reasoning token IDs failed. "
1216 + "Please check whether your reasoning parser has implemented "
1217 + "the `reasoning_start_str` and `reasoning_end_str`."
1218 + )
1219
1220 # Hybrid KV cache manager (HMA) runtime rules:
1221 # - Explicit enable (--no-disable-kv-cache-manager): error if runtime
vllm/engine/arg_utils.py
+8 -1
@@ -1591,7 +1591,7 @@ class EngineArgs:
1591 self._set_default_max_num_seqs_and_batched_tokens_args(
1592 usage_context, model_config
1593 )
1594 -
1594 + self._set_default_reasoning_config_args()
1595 sliding_window: int | None = None
1596 if not is_interleaved(model_config.hf_text_config):
1597 # Only set CacheConfig.sliding_window if the model is all sliding
@@ -2233,6 +2233,13 @@ class EngineArgs:
2233 )
2234 self.enable_prefix_caching = False
2235
2236 + def _set_default_reasoning_config_args(self):
2237 + if not self.reasoning_parser:
2238 + return
2239 + if self.reasoning_config is None:
2240 + self.reasoning_config = ReasoningConfig()
2241 + self.reasoning_config.reasoning_parser = self.reasoning_parser
2242 +
2243 def _set_default_max_num_seqs_and_batched_tokens_args(
2244 self,
2245 usage_context: UsageContext | None,
vllm/reasoning/abs_reasoning_parsers.py
+14
@@ -39,6 +39,20 @@ class ReasoningParser:
39 # whereas all tokenizers have .get_vocab()
40 return self.model_tokenizer.get_vocab()
41
42 + @property
43 + def reasoning_start_str(self) -> str | None:
44 + """Set `reasoning_start_str` to the strings that delimit
45 + the reasoning block (e.g. `""<seed:think>""` and `"<think>"`).
46 + """
47 + return None
48 +
49 + @property
50 + def reasoning_end_str(self) -> str | None:
51 + """Set `reasoning_end_str` to the strings that delimit
52 + the reasoning block (e.g. `""</seed:think>""` and `"</think>"`).
53 + """
54 + return None
55 +
56 @abstractmethod
57 def is_reasoning_end(self, input_ids: Sequence[int]) -> bool:
58 """
vllm/reasoning/basic_parsers.py
+8
@@ -39,6 +39,14 @@ class BaseThinkingReasoningParser(ReasoningParser):
39 """The token that ends reasoning content."""
40 raise NotImplementedError
41
42 + @property
43 + def reasoning_start_str(self) -> str:
44 + return self.start_token
45 +
46 + @property
47 + def reasoning_end_str(self) -> str:
48 + return self.end_token
49 +
50 def __init__(self, tokenizer: TokenizerLike, *args, **kwargs):
51 super().__init__(tokenizer, *args, **kwargs)
52
vllm/v1/engine/input_processor.py
+3 -3
@@ -98,9 +98,9 @@ class InputProcessor:
98 self.tokenizer,
99 )
100
101 - if (
102 - params.thinking_token_budget is not None
103 - and self.vllm_config.reasoning_config is None
101 + if params.thinking_token_budget is not None and (
102 + self.vllm_config.reasoning_config is None
103 + or not self.vllm_config.reasoning_config.enabled
104 ):
105 raise ValueError(
106 "thinking_token_budget is set but reasoning_config is "
vllm/v1/sample/logits_processor/builtin.py
+1 -1
@@ -301,7 +301,7 @@ class ThinkingTokenBudgetLogitsProcessor(LogitsProcessor):
301 max_num_reqs = vllm_config.scheduler_config.max_num_seqs
302
303 # Check if thinking is enabled
304 - self.is_enabled = reasoning_config is not None
304 + self.is_enabled = reasoning_config is not None and reasoning_config.enabled
305
306 self.reasoning_start_token_ids = getattr(
307 reasoning_config, "reasoning_start_token_ids", []