Prhub

#52430 [Bugfix][Gemma4] Align parser enable_thinking default with template

原始 PR 作者 lxy-alexander 合并时间 2026-08-18 20:41 文件变更 3 提交数 3 评论 10 代码增减 +31 / -9

执行摘要

对齐 Gemma4 parser 与模板的 enable_thinking 默认值

Issue #52410 指出 Gemma 4 对省略的 chat_template_kwargs.enable_thinking 存在两套默认值:模板 examples/tool_chat_template_gemma4.jinja 使用 enable_thinking | default(false),文档 docs/features/reasoning_outputs.md 也声明 Gemma 4 reasoning 默认关闭,但 parser vllm/parser/gemma4.py 使用 chat_kwargs.get("enable_thinking", True)。结果是省略该参数时模板渲染出非思考 prompt,parser 却按思考模式初始化,导致新模型 turn 时 is_reasoning_end() 返回错误结果。

值得精读。虽然只有一行核心改动,但它清晰展示了“chat template、文档、parser 三方默认值必须一致”的设计原则,以及如何通过三态 parametrize 测试锁定行为边界。对维护 parser 或 reasoning 功能的开发者有参考价值。

讨论亮点

chaunceyjiang:Could you please provide the execution results of the script examples/reasoning/openai_chat_completion_with_reasoning.py before and after the modification?

作者 lxy-alexander 提供了修改前后示例脚本的输出,两者可见输出一致(均返回 reasoning=None、正常 content),原因是模板本来就渲染非思考 prompt,差异只体现在 parser 内部状态。

lxy-alexander:docs/features/reasoning_outputs.md 显示 Gemma 4 reasoning 默认关闭,examples/tool_chat_template_gemma4.jinja 也使用 enable_thinking | default(false)

作者引用文档和模板证实默认值应为 False,审阅者最终认可并批准(LGTM)。

实现拆解

  1. 修正 parser 默认值:在 vllm/parser/gemma4.pyGemma4Parser.__init__ 中,将 self._thinking_enabled = chat_kwargs.get("enable_thinking", True) 改为 chat_kwargs.get("enable_thinking", False)。这一步使省略参数的行为与模板的 default(false) 对齐,是本次修复的核心。
  2. 重构 parser 单元测试:在 tests/reasoning/test_gemma4_reasoning_parser.py 中,将原来的 test_gemma4_previous_turn_reasoning_is_reasoning_end 替换为 test_gemma4_new_turn_reasoning_end_matches_enable_thinking,使用 pytest.mark.parametrize 覆盖 {}{"enable_thinking": False}{"enable_thinking": True} 三态,分别断言新 turn 时 is_reasoning_end() 的期望值,确保省略与显式 False 行为一致。
  3. 显式化 streaming 测试前提:在 tests/parser/engine/test_gemma4_streaming_reasoning.py 中,为所有依赖思考开启的 fixture 和 parser 构造(如 parseropen_reasoning_parserpre_init_parserplain_parser)显式传入 chat_template_kwargs={"enable_thinking": True},避免默认值变更后测试失去思考模式前提。
  4. 验证:本地执行 tests/reasoning/test_gemma4_reasoning_parser.py 31 项通过,tests/parser/engine/test_gemma4_streaming_reasoning.py 57 项通过;CI 全绿。
文件 模块 状态 重要度
vllm/parser/gemma4.py 解析器 modified 5.29
tests/reasoning/test_gemma4_reasoning_parser.py 解析器测试 modified 5.5
tests/parser/engine/test_gemma4_streaming_reasoning.py 流式解析 modified 4.69

关键符号

Gemma4Parser.__init__ test_gemma4_new_turn_reasoning_end_matches_enable_thinking

关键源码片段

vllm/parser/gemma4.py core-logic

核心修复点:将省略 enable_thinking 时的默认值从 True 改为 False,使 parser 与 chat template 和文档行为对齐,是本次 PR 的关键变更。

class Gemma4Parser(ParserEngine):
    """Gemma4 parser:负责 <|channel> 推理块与 <|tool_call> 工具调用的解析。"""
​
    def __init__(
        self,
        tokenizer: TokenizerLike,
        tools: list[Tool] | None = None,
        **kwargs,
    ) -> None:
        # 从 chat_template_kwargs 中读取 enable_thinking。
        # 关键修复:默认值由 True 改为 False,与模板中的
        # enable_thinking | default(false) 及文档保持一致,
        # 避免“模板渲染非思考 prompt、parser 却按思考模式初始化”的状态错位。
        chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {}
        self._thinking_enabled = chat_kwargs.get("enable_thinking", False)
        super().__init__(
            tokenizer,
            tools,
            parser_engine_config=gemma4_config(),
            **kwargs,
        )
        # 缓存特殊 token id,供后续状态机判定推理结束、工具调用等边界
        vocab = self.vocab
        self._tool_call_token_id: int | None = vocab.get("<|tool_call>")
        self._new_turn_token_id: int | None = vocab.get("<|turn>")
        self._tool_response_token_id: int | None = vocab.get("<|tool_response>")
        self._reasoning_text: str = ""
        self._prefix_stripped: bool = False
        self._is_first_feed: bool = True
tests/reasoning/test_gemma4_reasoning_parser.py test-coverage

新增三态 parametrize 测试,显式覆盖省略、False、True 三种 enable_thinking 情况,验证新 turn 时 is_reasoning_end 的期望行为,是修复正确性的直接保障。

@pytest.mark.parametrize(
    ("chat_template_kwargs", "expected_is_reasoning_end"),
    [
        # 省略 enable_thinking 时,应与显式 False 行为一致(默认 False)
        pytest.param({}, True, id="omitted_enable_thinking"),
        pytest.param({"enable_thinking": False}, True, id="thinking_disabled"),
        # 显式开启思考时,新 turn 之后仍处于推理中,is_reasoning_end 应为 False
        pytest.param({"enable_thinking": True}, False, id="thinking_enabled"),
    ],
)
def test_gemma4_new_turn_reasoning_end_matches_enable_thinking(
    generic_tokenizer,
    chat_template_kwargs,
    expected_is_reasoning_end,
):
    # 构造“第一轮已结束、进入新 turn”的输出:第一轮含 thought 块,
    # 之后是 user 消息与 model 起始标记。
    output = (
        "<|channel>thought\n1st thought<channel|>1st content<turn|>\n"
        "<|turn>user\nThanks<|turn>model\n"
    )
    output_tokens = gemma4_encode_output(generic_tokenizer, output)
    parser = ReasoningParserManager.get_reasoning_parser(parser_name)(
        generic_tokenizer,
        chat_template_kwargs=chat_template_kwargs,
    )
    is_reasoning_end = parser.is_reasoning_end(output_tokens)
    assert is_reasoning_end is expected_is_reasoning_end

评论区精华

要求提供示例脚本执行结果 question

chaunceyjiang 要求提供 examples/reasoning/openai_chat_completion_with_reasoning.py 修改前后的执行结果,以确认实际输出是否变化。

结论:作者提供了前后输出,两者可见输出一致(reasoning=None、content 正常),因为模板已渲染非思考 prompt,差异仅在 parser 内部状态。 · 已解决

默认值对齐依据 question

作者引用 docs/features/reasoning_outputs.md 和 examples/tool_chat_template_gemma4.jinja 说明 Gemma 4 reasoning 默认关闭,parser 应与之对齐。

结论:确认文档与模板均默认 False,parser 默认值改为 False 是对齐而非破坏,审阅者批准。 · 已解决

fork 自动 review 禁用 other

claude[bot] 提示 fork 来源的 PR 自动 review 被禁用,可让维护者手动触发。

结论:无实质影响,后续由维护者 chaunceyjiang 手动审核并批准。 · 已解决

风险与影响

主要风险是默认值行为变更:对省略 enable_thinking 的用户,parser 的 is_reasoning_end() 结果会从“思考开启”变为“思考关闭”。但由于模板本就渲染非思考 prompt,实际线上生成行为不变,这只是修正了解析状态。显式 enable_thinking=True 的路径完全不受影响。另一个潜在风险是依赖旧默认值(True)直接构造 Gemma4Parser 的调用方会观察到行为变化,但这类调用与模板行为不一致,属于被修复的 bug。测试已覆盖三态且 streaming 测试显式设置参数,风险整体较低。

影响范围集中在 Gemma4 推理解析器初始化逻辑,对 vLLM 其他模型和核心路径无影响。对用户而言,省略 enable_thinking 的请求不再出现“prompt 非思考但解析器认为在思考”的状态错位,新 turn 输出分类更准确。对团队而言,消除了模板、文档、parser 三处默认值不一致的隐患,降低后续维护成本。改动量小,无性能与兼容性影响。

默认值行为变更 隐性依赖旧默认值的调用方

关联 Issue

#52410 [Bug]: Gemma4 parser defaults omitted enable_thinking to true while the template defaults false

完整报告

参与讨论