Prhub

#45588 [Frontend] Replace legacy Gemma4 parsers with engine-based implementation

原始 PR 作者 bbrowning 合并时间 2026-06-16 05:34 文件变更 20 提交数 6 评论 16 代码增减 +2808 / -1332

执行摘要

Gemma4 解析器迁移至统一引擎框架

Gemma4 模型原有的 reasoning 和 tool 解析器是独立编写的,处理 spec decoding、流式推理/工具调用边界时有诸多 bug。PR 将其迁移到统一的 ParserEngine 框架(最初为 Qwen3 设计),以解决这些问题并清理代码。PR 描述提到:'This cleans up a multitude of issues with Gemma4 models (including DiffusionGemma) when used with spec decoding, stream_interval > 1, and general handling of reasoning / tool call boundaries, especially in streaming scenarios.'

值得精读,特别是 _parse_gemma4_args 的流式部分和状态机配置。展示了如何将复杂手写解析器迁移到统一引擎框架的设计模式。

讨论亮点

仅在 qwen3.py 中一行变更触发讨论:sfeng33 询问 (ParserState.CONTENT, "TOOL_START") 增加 EventType.REASONING_END 是否 intended。bbrowning 确认是该 PR 对 Qwen3 的修复,处理模型在工具响应后立即输出工具调用而无推理时的边缘情况,并指出测试由新的 tool-after-tool-response Scenario 覆盖。

实现拆解

  1. 创建 vllm/parser/gemma4.py,基于 ParserEngineConfig 定义状态机和配置。包括 _GEMMA4_MODEL_DROP_TOKENS、channel/tool 常量、_parse_gemma4_args 函数解析自定义参数格式,以及 Gemma4Parser 类继承 ParserEngine。
  2. 删除旧的 vllm/tool_parsers/gemma4_tool_parser.py(896行)和 vllm/reasoning/gemma4_reasoning_parser.py(225行),不再使用。
  3. 新增 vllm/tool_parsers/gemma4_engine_tool_parser.py 和 vllm/reasoning/gemma4_engine_reasoning_parser.py,作为注册适配器,指向新的 gemma4 配置。
  4. 在 vllm/parser/engine/parser_engine.py 中添加 _preprocess_feed 逻辑以支持新的 hold-back 恢复。
  5. 修改 vllm/parser/qwen3.py 中的 CONTENT→TOOL_START 转换,增加 REASONING_END 事件,防止工具调用紧跟工具响应时缺少推理结束信号。
  6. 增加/修改大量测试:tests/parser/engine/test_gemma4_streaming_reasoning.py(1201行新增)、test_token_id_scanner.py(652行修改)、trace_builder.py(113行新增 Gemma4 段构建)、test_replay.py、test_gemma4_tool_parser.py 等,覆盖各类流式边界情况。
文件 模块 状态 重要度
vllm/parser/gemma4.py 解析器 added 9.25
vllm/tool_parsers/gemma4_tool_parser.py 工具解析器 removed 9.25
vllm/reasoning/gemma4_reasoning_parser.py 推理解析器 removed 9.2
tests/parser/engine/test_gemma4_streaming_reasoning.py 测试 added 8.42
vllm/parser/engine/parser_engine.py 引擎核心 modified 7.3
vllm/tool_parsers/gemma4_engine_tool_parser.py 工具解析器 added 7.01
vllm/reasoning/gemma4_engine_reasoning_parser.py 推理解析器 added 6.3

关键符号

_strip_partial_delim _parse_gemma4_args _parse_gemma4_array _gemma4_arg_converter Gemma4Parser Gemma4ToolParser Gemma4ReasoningParser

关键源码片段

vllm/parser/gemma4.py core-logic

新的单一状态机解析器,取代旧的两个解析器,是核心变更。

# 常量定义
CHANNEL_START = "<|channel>"
CHANNEL_END = "<channel|>"
TOOL_CALL_START = "<|tool_call>"
TOOL_CALL_END = "<tool_call|>"
STRING_DELIM = '<|"|>'
_DELIM_LEN = len(STRING_DELIM)# 部分定界符后缀列表,用于流式截断
_PARTIAL_DELIM_SUFFIXES = tuple(
    STRING_DELIM[:k] for k in range(len(STRING_DELIM), 0, -1)
)
​
​
def _strip_partial_delim(value: str) -> str:
    """Strip a trailing partial ``STRING_DELIM`` prefix from *value*.
    Prevents partial delimiters from leaking into the streamed JSON diff.
    """
    for suffix in _PARTIAL_DELIM_SUFFIXES:
        if value.endswith(suffix):
            return value[: -len(suffix)]
    return value
​
​
def _parse_gemma4_args(args_str: str, *, partial: bool = False) -> dict:
    """Parse Gemma4's custom key:value format into a Python dict.
    Format examples::
        location:<|"|>Tokyo<|"|>
        count:42,flag:true
    Args:
        args_str: The raw Gemma4 argument string.
        partial: When True (streaming), bare values at end are omitted.
    """
    if not args_str or not args_str.strip():
        return {}
​
    result: dict = {}
    i = 0
    n = len(args_str)
​
    while i < n:
        # 跳过空白和逗号
        while i < n and args_str[i] in (" ", ",", "\n", "\t"):
            i += 1
        if i >= n:
            break
​
        # 解析键(无引号,遇到 ':' 结束)
        key_start = i
        while i < n and args_str[i] != ":":
            i += 1
        if i >= n:
            break
        key = args_str[key_start:i].strip()
        # 修复 #44715:如果键被 <|"|> 包围,去除定界符
        if key.startswith(STRING_DELIM) and key.endswith(STRING_DELIM):
            key = key[_DELIM_LEN:-_DELIM_LEN]
        i += 1 # 跳过 ':'
​
        # 值解析继续(字符串、嵌套对象、数组等)
        # ... 完整实现见头文件

评论区精华

qwen3.py transition 变更意图 正确性

sfeng33 询问 `(ParserState.CONTENT, "TOOL_START")` 增加 `EventType.REASONING_END` 是否 intended。

结论:bbrowning 确认是修复工具调用紧跟工具响应时缺少推理结束信号的边缘情况,测试由新的 `tool-after-tool-response` Scenario 覆盖。 · 已解决

风险与影响

1) 旧解析器完全删除,若出现回归无法快速切换回旧路径,需要依赖测试覆盖。
2) qwen3.py 的 transition 变更影响所有使用 Qwen3 引擎的模型,虽然修复了边缘情况,但可能引入新的顺序问题。
3) Gemma4 自定义参数格式解析器重新实现,流式部分兼容性需充分验证。
4) 测试虽多但模拟 tokenizer 可能遗漏真实 tokenizer 行为差异。

对用户透明(无需配置变更),但显著改善 Gemma4 在流式、spec decoding 和 MTP 下的解析正确性,特别是工具调用密集场景。内部架构统一到引擎框架,方便未来扩展。对开发团队,后续模型解析器可参考此模式。影响范围为 Gemma4 用户,特别是使用 spec decoding 和流式工具调用的用户。

旧解析器删除后回滚困难 核心引擎变更影响其他模型 复杂流式状态机潜在边缘情况 模拟 tokenizer 测试覆盖不足

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论