diff --git a/docs/source/developer-guide/telemetry.md b/docs/source/developer-guide/telemetry.md index 90d9f3b9c7d8..184266f2beb9 100644 --- a/docs/source/developer-guide/telemetry.md +++ b/docs/source/developer-guide/telemetry.md @@ -28,7 +28,7 @@ unset or when the safety sanitizer rejects the runtime value. ### `TorchLlmArgs` -270 captured fields. +271 captured fields. | Captured key | Annotation | Kind | Converter | Allowed values | |--------------|------------|------|-----------|----------------| @@ -152,7 +152,7 @@ unset or when the safety sanitizer rejects the runtime value. | `max_stats_len` | `` | `value` | | | | `mm_encoder_only` | `` | `value` | | | | `moe_cluster_parallel_size` | `Optional[int]` | `value` | | | -| `moe_config.backend` | `Literal['AUTO', 'CUTLASS', 'CUTEDSL', 'WIDEEP', 'TRTLLM', 'DEEPGEMM', 'DENSEGEMM', 'VANILLA', 'TRITON', 'MARLIN', 'MEGAMOE_DEEPGEMM']` | `categorical` | | `AUTO`, `CUTLASS`, `CUTEDSL`, `WIDEEP`, `TRTLLM`, `DEEPGEMM`, `DENSEGEMM`, `VANILLA`, `TRITON`, `MARLIN`, `MEGAMOE_DEEPGEMM` | +| `moe_config.backend` | `Literal['AUTO', 'CUTLASS', 'CUTEDSL', 'WIDEEP', 'TRTLLM', 'DEEPGEMM', 'DENSEGEMM', 'VANILLA', 'TRITON', 'MARLIN', 'MEGAMOE_DEEPGEMM', 'MEGAMOE_CUTEDSL']` | `categorical` | | `AUTO`, `CUTLASS`, `CUTEDSL`, `WIDEEP`, `TRTLLM`, `DEEPGEMM`, `DENSEGEMM`, `VANILLA`, `TRITON`, `MARLIN`, `MEGAMOE_DEEPGEMM`, `MEGAMOE_CUTEDSL` | | `moe_config.disable_finalize_fusion` | `` | `value` | | | | `moe_config.max_num_tokens` | `Optional[int]` | `value` | | | | `moe_config.use_low_precision_moe_combine` | `` | `value` | | | @@ -192,7 +192,7 @@ unset or when the safety sanitizer rejects the runtime value. | `ray_placement_config.defer_workers_init` | `` | `value` | | | | `ray_placement_config.per_worker_gpu_share` | `Optional[float]` | `value` | | | | `ray_placement_config.placement_bundle_indices` | `Optional[List[List[int]]]` | `value` | | | -| `reasoning_parser` | `Optional[str]` | `categorical` | allowlist | `auto`, `deepseek-r1`, `laguna`, `qwen3`, `qwen3_5`, `minimax_m2`, `minimax_m2_append_think`, `nano-v3`, `gemma4`, `kimi_k2`, `kimi_k25` | +| `reasoning_parser` | `Optional[str]` | `categorical` | allowlist | `auto`, `deepseek-r1`, `poolside_v1`, `laguna`, `qwen3`, `qwen3_5`, `minimax_m2`, `minimax_m2_append_think`, `nano-v3`, `gemma4`, `kimi_k2`, `kimi_k25` | | `reorder_policy_config.policy_args.agent_inflight_seq_num` | `` | `value` | | | | `reorder_policy_config.policy_args.agent_percentage` | `` | `value` | | | | `reorder_policy_config.policy_name` | `Optional[Literal['AgentTree']]` | `categorical` | | `AgentTree` | @@ -243,6 +243,7 @@ unset or when the safety sanitizer rejects the runtime value. | `sparse_attention_config.window_size` | `` | `value` | | | | `speculative_config.acceptance_rate_threshold` | `Optional[float]` | `value` | | | | `speculative_config.acceptance_rate_window_size` | `Optional[Annotated[int, Ge(ge=0)]]` | `value` | | | +| `speculative_config.advanced_sampling_mode` | `` | `categorical` | | `full`, `no_topk`, `no_topp`, `no_topk_no_topp` | | `speculative_config.allow_advanced_sampling` | `` | `value` | | | | `speculative_config.begin_thinking_phase_token` | `` | `value` | | | | `speculative_config.block_size` | `Optional[Annotated[int, Gt(gt=0)]]` | `value` | | | diff --git a/tensorrt_llm/llmapi/llm_args.py b/tensorrt_llm/llmapi/llm_args.py index cbb9f1ec554e..d6952032633b 100644 --- a/tensorrt_llm/llmapi/llm_args.py +++ b/tensorrt_llm/llmapi/llm_args.py @@ -4473,8 +4473,9 @@ class BaseLlmArgs(StrictBaseModel): default=None, description="The parser to separate reasoning content from output.", status="prototype", - telemetry=TelemetryField.categorical('auto', 'deepseek-r1', 'laguna', - 'qwen3', 'qwen3_5', 'minimax_m2', + telemetry=TelemetryField.categorical('auto', 'deepseek-r1', + 'poolside_v1', 'laguna', 'qwen3', + 'qwen3_5', 'minimax_m2', 'minimax_m2_append_think', 'nano-v3', 'gemma4', 'kimi_k2', 'kimi_k25')) diff --git a/tensorrt_llm/llmapi/reasoning_parser.py b/tensorrt_llm/llmapi/reasoning_parser.py index ccc81e2e05e2..342e2de9b99c 100644 --- a/tensorrt_llm/llmapi/reasoning_parser.py +++ b/tensorrt_llm/llmapi/reasoning_parser.py @@ -109,7 +109,6 @@ def parse_delta(self, delta_text: str) -> ReasoningParserResult: @register_reasoning_parser("deepseek-r1", reasoning_at_start=True) -@register_reasoning_parser("laguna") @register_reasoning_parser("qwen3") # Qwen3.5 (and forced-thinking Qwen3 variants) use a chat template that # pre-injects `\n` into the assistant prompt prefix, so the model @@ -247,6 +246,28 @@ def finish(self) -> ReasoningParserResult: return self._parser.finish() +@register_reasoning_parser("poolside_v1") +@register_reasoning_parser("laguna") +class PoolsideV1ReasoningParser(DeepSeekV4ReasoningParser): + """Poolside parser selected by thinking-mode chat template kwargs. + + Same behavior as Deepseek-V4's, except that reasoning defaults to true + to respect the official documentation. + """ + + def __init__( + self, + *, + chat_template_kwargs: Optional[dict[str, Any]] = None, + ) -> None: + chat_template_kwargs = dict(chat_template_kwargs or {}) + if chat_template_kwargs.get( + "thinking") is None and chat_template_kwargs.get( + "enable_thinking") is None: + chat_template_kwargs["enable_thinking"] = True + super().__init__(chat_template_kwargs=chat_template_kwargs) + + @register_reasoning_parser("minimax_m3") class MiniMaxM3ReasoningParser(DeepSeekR1Parser): """Reasoning parser for MiniMax-M3. @@ -302,7 +323,7 @@ def parse(self, text: str) -> ReasoningParserResult: "qwen3_next": "qwen3", "deepseek_v3": "deepseek-r1", "deepseek_v32": "deepseek-r1", - "laguna": "laguna", + "laguna": "poolside_v1", "deepseek_v4": "deepseek_v4", "nemotron_h": "nemotron-v3", "nemotron_h_puzzle": "nemotron-v3", diff --git a/tensorrt_llm/usage/llm_args_golden_manifest.json b/tensorrt_llm/usage/llm_args_golden_manifest.json index 42a8dd01cb3c..896e8e764855 100644 --- a/tensorrt_llm/usage/llm_args_golden_manifest.json +++ b/tensorrt_llm/usage/llm_args_golden_manifest.json @@ -1237,6 +1237,7 @@ "allowed_values": [ "auto", "deepseek-r1", + "poolside_v1", "laguna", "qwen3", "qwen3_5", diff --git a/tests/unittest/api_stability/references/trtllm_serve_cli.yaml b/tests/unittest/api_stability/references/trtllm_serve_cli.yaml index c3430712d780..7818de3dfd36 100644 --- a/tests/unittest/api_stability/references/trtllm_serve_cli.yaml +++ b/tests/unittest/api_stability/references/trtllm_serve_cli.yaml @@ -368,7 +368,7 @@ commands: flags: - "--post_processor_hook" reasoning_parser: - type: Choice(['auto', 'deepseek-r1', 'deepseek_v4', 'gemma4', 'kimi_k2', 'kimi_k25', 'laguna', 'minimax_m2', 'minimax_m2_append_think', 'minimax_m3', 'nano-v3', 'nemotron-v3', 'qwen3', 'qwen3_5']) + type: Choice(['auto', 'deepseek-r1', 'deepseek_v4', 'gemma4', 'kimi_k2', 'kimi_k25', 'laguna', 'minimax_m2', 'minimax_m2_append_think', 'minimax_m3', 'nano-v3', 'nemotron-v3', 'poolside_v1', 'qwen3', 'qwen3_5']) default: null status: prototype required: false diff --git a/tests/unittest/llmapi/test_reasoning_parser.py b/tests/unittest/llmapi/test_reasoning_parser.py index 68543c247a5f..eb94e4c26ae1 100644 --- a/tests/unittest/llmapi/test_reasoning_parser.py +++ b/tests/unittest/llmapi/test_reasoning_parser.py @@ -19,6 +19,7 @@ import pytest from tensorrt_llm.llmapi.reasoning_parser import (NemotronV3ReasoningParser, + PoolsideV1ReasoningParser, ReasoningParserFactory, resolve_auto_reasoning_parser) @@ -88,6 +89,48 @@ def test_deepseek_v4_reasoning_parser_streams_when_thinking(): for result in results] == ["hid", "den", ""] +@pytest.mark.parametrize("chat_template_kwargs", [{ + "thinking": True +}, { + "enable_thinking": True +}, None, {}, { + "random_key": "random_value" +}]) +def test_poolside_v1_reasoning_parser_extracts_when_thinking( + chat_template_kwargs: dict): + reasoning_parser = ReasoningParserFactory.create_reasoning_parser( + "poolside_v1", chat_template_kwargs) + + result = reasoning_parser.parse(f"hidden{R1_END}visible") + + assert result.content == "visible" + assert result.reasoning_content == "hidden" + + +@pytest.mark.parametrize("chat_template_kwargs", [{"enable_thinking": False}]) +def test_poolside_v1_reasoning_parser_extracts_when_not_thinking( + chat_template_kwargs: dict): + reasoning_parser = ReasoningParserFactory.create_reasoning_parser( + "poolside_v1", chat_template_kwargs) + + result = reasoning_parser.parse(f"visible") + + assert result.content == "visible" + assert result.reasoning_content == "" + + +def test_poolside_v1_reasoning_parser_streams_when_thinking(): + reasoning_parser = ReasoningParserFactory.create_reasoning_parser( + "poolside_v1", {"enable_thinking": True}) + + deltas = ["hid", f"den{R1_END}visible", " tail"] + results = [reasoning_parser.parse_delta(delta) for delta in deltas] + + assert [result.content for result in results] == ["", "visible", " tail"] + assert [result.reasoning_content + for result in results] == ["hid", "den", ""] + + TOOL_START = "<|tool_calls_section_begin|>" @@ -278,32 +321,30 @@ def test_qwen3_reasoning_parser_stream(delta_texts: list, content: list, @pytest.mark.parametrize(("text", "content", "reasoning_context"), [ - ("abc", "c", "b"), - ("ab", "b", "a"), - ("a", "", "a"), - ("a", "a", ""), - ("", "", ""), + ("hiddenvisible", "visible", "hidden"), + ("visible", "visible", ""), + ("", "", ""), ]) -def test_laguna_reasoning_parser(text: str, content: str, - reasoning_context: str): - reasoning_parser = ReasoningParserFactory.create_reasoning_parser("laguna") +def test_poolside_v1_reasoning_parser(text: str, content: str, + reasoning_context: str): + reasoning_parser = ReasoningParserFactory.create_reasoning_parser( + "poolside_v1") result = reasoning_parser.parse(text) assert result.content == content assert result.reasoning_content == reasoning_context @pytest.mark.parametrize(("delta_texts", "content", "reasoning_context"), [ - (["a", "lr", "b"], ["", "r", "b"], ["a", "l", ""]), - (["ab"], ["", "b"], ["", "a"]), - (["ab"], ["", "b"], ["a", ""]), - (["", "ab"], ["", "b"], ["", "a"]), - (["a", "b"], ["", "b"], ["a", ""]), - (["ab"], ["", "", "b" - ], ["a", "", ""]), + (["a", "lr", "b"], ["", "r", "b"], ["a", "l", ""]), + (["ab"], ["", "b"], ["a", ""]), + (["", "ab"], ["", "b"], ["", "a"]), + (["a", "b"], ["", "b"], ["a", ""]), + (["ab"], ["", "", "b"], ["a", "", ""]), ]) -def test_laguna_reasoning_parser_stream(delta_texts: list, content: list, - reasoning_context: list): - reasoning_parser = ReasoningParserFactory.create_reasoning_parser("laguna") +def test_poolside_v1_reasoning_parser_stream(delta_texts: list, content: list, + reasoning_context: list): + reasoning_parser = ReasoningParserFactory.create_reasoning_parser( + "poolside_v1") for i, delta_text in enumerate(delta_texts): result = reasoning_parser.parse_delta(delta_text) assert result.content == content[i] @@ -681,7 +722,7 @@ def test_auto_detect_laguna(tmp_path): _write_config(model_dir, "laguna") result = resolve_auto_reasoning_parser(model_dir) - assert result == "laguna" + assert result == "poolside_v1" @pytest.mark.parametrize("model_type", ["nemotron_h", "nemotron_h_puzzle"]) @@ -695,6 +736,14 @@ def test_auto_detect_nemotron_h(tmp_path, model_type): assert result == "nemotron-v3" +def test_poolside_v1_alias_same_parser(): + """'Poolside_v1' and its alias 'laguna' resolve to the same parser.""" + Poolside_v1 = ReasoningParserFactory.create_reasoning_parser("poolside_v1") + laguna = ReasoningParserFactory.create_reasoning_parser("laguna") + assert isinstance(Poolside_v1, PoolsideV1ReasoningParser) + assert isinstance(laguna, PoolsideV1ReasoningParser) + + def test_nemotron_v3_alias_same_parser(): """'nemotron-v3' and the legacy 'nano-v3' resolve to the same parser.""" nemotron = ReasoningParserFactory.create_reasoning_parser("nemotron-v3")