Prhub

#50595 [Bugfix][Structured Output] Mask request stop tokens in xgrammar until grammar terminates

原始 PR 作者 yzong-rh 合并时间 2026-08-14 02:07 文件变更 2 提交数 1 评论 6 代码增减 +41 / -14

执行摘要

修复 Harmony 解析器在 xgrammar 下中止 token 处理

修复 xgrammar 结构化输出时,Harmony 解析器因 stop token 处理不当导致的 500 错误和无限生成问题。Issue #51693 中报告了此错误,bbrowning 确认此修复解决了该问题。

值得精读,尤其是理解 Harmony 解析器与 xgrammar 的集成方式,以及测试如何模拟生产路径。

讨论亮点
  • 关于其他后端支持:bbrowning 询问是否其他 grammar 后端也会遇到相同问题,yzong-rh 确认只有 xgrammar 使用 stop_token_ids,其他后端(如 guidance)需要单独修复,且上游可能也需要支持。
  • 确认修复:bbrowning 确认该修复解决了 #51693 的 500 错误。

实现拆解

  1. 更新 Harmony 解析器:在 vllm/parser/harmony.py 中,将 _END_TAG 列表从 ["<|end|>", "<|call|>", ""] 简化为 ["<|end|>", ""],移除 <|call|>,因为 #50595 落地后不再需要,且 xgrammar 在约束下不允许 stop token。
  2. 更新测试:在 tests/parser/test_harmony.py 中,引入 GenerationConfig、VllmConfig、XgrammarBackend 等依赖,新增 gpt_oss_stop_token_ids 和 xgrammar_backend fixture,并将 _assert_structured_outputs_admission 改为使用真实的 xgrammar 后端编译 grammar,通过 encode_output 和 validate_tokens 验证 token 序列,而非旧的 _is_grammar_accept_string。
  3. 调整测试用例:更新 test_adjust_request 等测试,传递新 fixture,确保测试覆盖真实生产路径。
文件 模块 状态 重要度
vllm/parser/harmony.py 解析器 modified 5.67
tests/parser/test_harmony.py 测试 modified 6.01

关键符号

_normalize_recipient adjust_request _assert_structured_outputs_admission

关键源码片段

tests/parser/test_harmony.py test-coverage

测试更新,改用真实 xgrammar 后端,覆盖生产路径。

# tests/parser/test_harmony.py
# 新增 fixture 获取真实模型的 stop token IDs,并构造 xgrammar 后端实例。
@pytest.fixture(scope="module")
def gpt_oss_stop_token_ids() -> set[int]:
    eos_token_id = GenerationConfig.from_pretrained(REASONING_MODEL_NAME).eos_token_id
    if isinstance(eos_token_id, int):
        return {eos_token_id}
    return set(eos_token_id)
​
@pytest.fixture(scope="module")
def xgrammar_backend(gpt_oss_tokenizer) -> XgrammarBackend:
    vllm_config = VllmConfig(
        structured_outputs_config=StructuredOutputsConfig(backend="xgrammar")
    )
    return XgrammarBackend(
        vllm_config,
        tokenizer=gpt_oss_tokenizer,
        vocab_size=len(gpt_oss_tokenizer),
    )
​
# 将语法编译改为使用真实 xgrammar 后端,并传入 stop_token_ids。
# 使用 encode_output 和 validate_tokens 模拟生产路径的 token 验证。
grammar = xgrammar_backend.compile_grammar(
    StructuredOutputOptions.STRUCTURAL_TAG,
    structured_outputs.structural_tag,
    stop_token_ids=stop_token_ids,
)
tokens = encode_output(getattr(cls, sample_name))
accepted = grammar.validate_tokens(tokens)
admitted = accepted == tokens

评论区精华

其他 grammar 后端是否支持 stop_token_ids 设计

bbrowning 提问:我们已将 `stop_token_ids` 传给所有 grammar,但只有 xgrammar 使用,其他 backend 是否会有同样 bug?

结论:yzong-rh 承认其他后端(如 guidance)也需要单独修复,且部分后端不支持 per-request eos_token 覆盖,留下许多 TODO。 · 已解决

修复是否解决具体 issue question

bbrowning 确认本地测试修复了 #51693 的 500 错误。

结论:确认修复有效。 · 已解决

风险与影响

主要风险在于移除 <|call|> 可能影响 Harmony 解析器对 tool call 结束符的处理,但测试覆盖了相关场景。此外,xgrammar 后端依赖 stop_token_ids,但其他后端不支持,可能导致功能不一致。测试中 encode_output 和 validate_tokens 的替换可能影响断言准确性,但已通过本地测试验证。

影响 xgrammar 结构化输出用户,特别是使用 Harmony 模型的用户,修复了 500 错误和无限生成问题。对系统性能无显著影响,但测试逻辑调整可能影响 CI 稳定性。

核心路径变更 其他后端未覆盖 测试逻辑调整

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论