Prhub

#6305 [data] fix: forward apply_chat_template_kwargs to system prompt measurement

原始 PR 作者 MohammadShahdad 合并时间 2026-05-12 21:24 文件变更 3 提交数 1 评论 4 代码增减 +97 / -7

执行摘要

修复自定义 chat template 参数未转发导致 loss mask 偏移

What does this PR do? Fixes a bug where extract_system_prompt_and_generation and initialize_system_prompt in verl/utils/chat_template.py did not forward apply_chat_template_kwargs to their internal apply_chat_template() calls. This caused the system prompt length to be measured using default kwargs while per-turn tokenization used custom kwargs, resulting in incorrect token stripping and a shifted loss mask. Any SFT run using apply_chat_template_kwargs with a model whose chat template embeds configurable fields in the system prompt (e.g., model_identity on openai/gpt-oss-20b) would trigger the sanity_check assertion error. Bypassing it via ignore_input_ids_mismatch=True would result in training on silently malformed data (shifted loss mask, missing role markers).

建议所有使用 apply_chat_template_kwargs 的 SFT 用户升级此修复。值得关注的设计决策包括参数转发模式和 reviewer 提出的改进建议。可以在后续 PR 中参照 reviewer 建议,迁移到 apply_chat_template wrapper 和字典合并模式以增强健壮性。

讨论亮点

Review 中 gemini-code-assist[bot] 提出了两点高优先级建议:

  • 使用本地 apply_chat_template wrapper 和字典合并模式:建议用模块内定义的 apply_chat_template 函数代替直接调用 tokenizer.apply_chat_template,以保持一致性并支持需特殊处理的模型(如 Qwen3.5)。同时使用 **{**apply_chat_template_kwargs, 'add_generation_prompt': False, 'tokenize': True} 模式避免用户 kwargs 包含 tokenize 等参数时导致 TypeError
  • 在系统提示初始化时使用 processor 代替 tokenizer:建议在 MultiTurnSFTDataset.__init__ 中调用 extract_system_prompt_and_generation 时优先使用 self.processor(当可用时),以保证与 _process_single_message 中 tokenization 逻辑一致,尤其是对多模态模型。

这些建议未被当前 PR 采纳,但审核者 wuxibin89 批准了合并,认为这些增强可以在后续 PR 中完成。

实现拆解

  1. 修改 chat template 核心函数:在 verl/utils/chat_template.py 中,为 initialize_system_promptextract_system_prompt_and_generation 添加 **apply_chat_template_kwargs 参数,并在三个内部的 tokenizer.apply_chat_template() 调用中透传这些参数,确保系统提示长度计算使用与每轮 tokenization 一致的 kwargs。

  2. 调整数据集调用处:在 verl/utils/dataset/multiturn_sft_dataset.py__init__ 方法中,将 self.apply_chat_template_kwargs 作为关键字参数传递给 extract_system_prompt_and_generation,保证初始化时系统提示长度的测量与后续数据处理(_process_single_message)对齐。

  3. 新增测试用例确保回归:在 tests/utils/dataset/test_multiturn_sft_dataset_on_cpu.py 中新增 test_multiturn_sft_dataset_with_chat_template_kwargs 测试函数,使用 openai/gpt-oss-20b 模型并传入 model_identity 参数,验证 assistant 消息内容正确位于 loss mask 为 1 的区域,user 和 system 消息位于 loss mask 为 0 的区域。该测试在修复前因 sanity check 失败而无法通过。

文件 模块 状态 重要度
verl/utils/chat_template.py 模板工具 modified 7.05
verl/utils/dataset/multiturn_sft_dataset.py 数据集 modified 4.9
tests/utils/dataset/test_multiturn_sft_dataset_on_cpu.py 测试覆盖 modified 6.13

关键符号

initialize_system_prompt extract_system_prompt_and_generation test_multiturn_sft_dataset_with_chat_template_kwargs

关键源码片段

verl/utils/chat_template.py core-logic

核心修复文件:修改 initialize_system_prompt 和 extract_system_prompt_and_generation 函数,添加 **apply_chat_template_kwargs 参数并传递给内部的 apply_chat_template 调用,解决系统提示长度测量与每轮 tokenization 参数不一致的问题。

# verl/utils/chat_template.pydef initialize_system_prompt(tokenizer, **apply_chat_template_kwargs) -> list[int]:
    """
    Initialize system prompt tokens for chat templates that support them.
    Args:
        tokenizer: The tokenizer with a chat template
        **apply_chat_template_kwargs: Additional arguments for apply_chat_template
    Returns:
        List of token IDs for the system prompt, or empty list if not supported
    """
    # 注意:将 **apply_chat_template_kwargs 传递给 apply_chat_template,
    # 保证系统提示长度测量与每轮 tokenization 使用相同的自定义参数
    token1 = normalize_token_ids(
        tokenizer.apply_chat_template(
            [{"role": "user", "content": ""}], add_generation_prompt=False, tokenize=True, **apply_chat_template_kwargs
        )
    )
    token2 = normalize_token_ids(
        tokenizer.apply_chat_template(
            [{"role": "user", "content": ""}] * 2,
            add_generation_prompt=False,
            tokenize=True,
            **apply_chat_template_kwargs,
        )
    )
    system_prompt = token1[: -(len(token2) - len(token1))]
    return system_prompt
​
​
def extract_system_prompt_and_generation(tokenizer, **apply_chat_template_kwargs):
    """Extract system prompt and generation prompt tokens."""
    # 三个内部调用都需要转发 kwargs,确保测量一致性
    token1 = normalize_token_ids(
        tokenizer.apply_chat_template(
            [{"role": "user", "content": ""}], add_generation_prompt=False, tokenize=True, **apply_chat_template_kwargs
        )
    )
    token2 = normalize_token_ids(
        tokenizer.apply_chat_template(
            [{"role": "user", "content": ""}] * 2,
            add_generation_prompt=False,
            tokenize=True,
            **apply_chat_template_kwargs,
        )
    )
    system_prompt = token1[: -(len(token2) - len(token1))]
​
    token3 = normalize_token_ids(
        tokenizer.apply_chat_template(
            [{"role": "user", "content": ""}], add_generation_prompt=True, tokenize=True, **apply_chat_template_kwargs
        )
    )
    generate_prompt = token3[len(token1):]
​
    return system_prompt, generate_prompt

评论区精华

使用本地 apply_chat_template wrapper 和字典合并模式避免参数冲突 设计

gemini-code-assist[bot] 建议使用本模块定义的 apply_chat_template wrapper 代替直接调用 tokenizer.apply_chat_template,以保持一致性和支持特殊模型(如 Qwen3.5)。同时建议使用字典合并模式 **{**apply_chat_template_kwargs, 'add_generation_prompt': False, 'tokenize': True} 避免用户 kwargs 中包含重复参数导致 TypeError。

结论:当前 PR 未采纳,仍使用直接调用方式;wuxibin89 审批通过,认为可后续改进。 · unresolved

在系统提示初始化时使用 processor 代替 tokenizer 正确性

gemini-code-assist[bot] 建议在 MultiTurnSFTDataset.__init__ 中调用 extract_system_prompt_and_generation 时使用 self.processor (if available) 代替 self.tokenizer,以保证与 _process_single_message 中使用的 tokenization 一致,特别是对多模态模型。

结论:当前 PR 未修改,仍使用 tokenizer;wuxibin89 审批通过。 · unresolved

风险与影响

  1. 参数冲突风险:当前实现直接将 **apply_chat_template_kwargs 与显式参数(如 tokenize=True)一起展开,如果用户配置的 kwargs 中包含这些键名,会引发 TypeError。建议采用字典合并模式规避。
  2. 多模态兼容性风险:未使用 processor 可能导致多模态模型(如 VLM)在 tokenization 时产生不一致。
  3. 回归风险:核心数据路径变更可能影响未使用自定义 kwargs 的现有流程,但已有测试覆盖基础场景。
  4. 安全风险:无直接安全风险。

影响范围:所有在 SFT 训练中使用 apply_chat_template_kwargs 配置且模型系统提示包含可配置字段(如 model_identity)的用户。修复后训练数据的 loss mask 正确,不再触发断言错误或静默错误。对未使用自定义 kwargs 的用户无影响。影响程度中等,涉及数据正确性而非性能或可用性。无 API 破坏,配置向后兼容。

参数冲突风险 多模态兼容性风险 核心数据路径变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论