Prhub

#2202 fix(tito): prevent DeepSeek V4 system-tail mismatch

原始 PR 作者 guapisolo 合并时间 2026-08-05 11:37 文件变更 3 提交数 1 评论 0 代码增减 +4 / -1

执行摘要

DeepSeek V4 TITO 禁止 system 尾追加,修复会话不匹配

PR body 报告 DeepSeek V4 Stage 3 出现 0.109375 到 0.1875 的 assistant-text mismatch 率而不是 0。根因有三点:DeepSeekV4TITOTokenizer.FIXED_TEMPLATE 继承了 mid-session system 支持;encoding_dsv4.encode_messages 在 system 尾后省略 assistant opener;compute_session_mismatch 用 canonical </think> 重渲染与采样 token 比较,于是产生随机性不匹配。

值得快速浏览:这是一个典型的"一行配置修复系统性随机故障"的案例,展示了如何通过收窄能力边界而非改写校验语义来根治问题。重点关注 allowed_append_roles 的设计(与 Qwen3.5/Minimax 家族复用同一模式)以及通过 E2E 阈值保留漂移可见性的思路。不需要精读大量代码。

讨论亮点

本 PR 只有一条审批评论(Shi-Dong 回复 "LGTM"),没有实质技术讨论。PR body 中作者自述的 review 焦点有两个:一是核对 DeepSeekV4TITOTokenizer.FIXED_TEMPLATE.allowed_append_roles 与编码器 generation-ready tail 一致;二是确认 assistant_text_threshold=0.05 只存在于 DeepSeek V4 E2E 配置中,不会影响其他模型。

实现拆解

  1. 变更入口:miles/utils/chat_template_utils/tito_tokenizer.py 中 DeepSeekV4TITOTokenizer.FIXED_TEMPLATE 新增 allowed_append_roles=frozenset({"tool", "user", "assistant"}),把可追加角色从默认的全角色集合收窄为编码器 generation-ready 的边界。
  2. 核心逻辑:该约束使 TITO 增量 tokenization 不再在 mid-session system 之后追加消息,从而避免 encoding_dsv4 省略 assistant opener 导致的 roundtrip 不一致;由于没有修改 parser 或 comparator 语义,其他模型家族不受影响。
  3. 测试配套:tests/fast/utils/chat_template_utils/test_fixed_templates.py 将 DeepSeekV4TITOTokenizer 加入 test_restricted_fixed_template_excludes_mid_session_system 参数化列表,与 Qwen35、Minimax M25/M27 一同断言角色集合。
  4. E2E 配套:tests/e2e/sglang/test_session_server_multi_role/test_deepseekv4.py 为 CONFIG 增加 assistant_text_threshold=0.05,让未来编码器行为漂移仍能被阈值暴露,而不是静默失败。
  5. 无其他配置、schema 或部署配套改动。
文件 模块 状态 重要度
miles/utils/chat_template_utils/tito_tokenizer.py 会话分词 modified 4.75
tests/fast/utils/chat_template_utils/test_fixed_templates.py 模板测试 modified 3.35
tests/e2e/sglang/test_session_server_multi_role/test_deepseekv4.py V4 E2E modified 3.28

关键符号

test_restricted_fixed_template_excludes_mid_session_system test_deepseekv4

关键源码片段

miles/utils/chat_template_utils/tito_tokenizer.py core-logic

核心修复点:为 DeepSeekV4TITOTokenizer 的 FIXED_TEMPLATE 增加 allowed_append_roles 限制,排除 mid-session system 追加,从根源解决 encoding_dsv4 不产生 assistant opener 导致的 TITO mismatch。

class DeepSeekV4TITOTokenizer(TITOTokenizer):
    """DeepSeek V4 — official encoder via sglang's ``encoding_dsv4``.
​
    Like V3.2, V4 ships no jinja chat_template; miles' ``apply_chat_template``
    routes any V4 tokenizer to the ``chat_template_utils.deepseek`` bridge, and
    TITO incremental tokenization rides that same bridge to stay byte-aligned
    with what the runtime serves.
    """
​
    reasoning_parser = "deepseek-v4"
    tool_call_parser = "deepseekv4"
    chat_template_kwarg_aliases = _DEEPSEEK_MODE_KWARG_ALIASES
​
    FIXED_TEMPLATE = FixedTemplate(
        template=None,
        extra_kwargs={"drop_thinking": False},
        # 关键修复:限制中间会话可追加的角色。
        # encoding_dsv4 在 system 消息之后不会追加 assistant 开启标记,
        # 若允许追加 system 会导致 TITO 会话无法 roundtrip,
        # 出现 0.1 到 0.19 的 assistant-text mismatch。
        allowed_append_roles=frozenset({"tool", "user", "assistant"}),
    )
​
    _DEFAULT_ASSISTANT_START = "<|Assistant|>"
​
    def __init__(
        self,
        tokenizer: Any,
        chat_template_kwargs: dict[str, Any] | None = None,
        assistant_start_str: str | None = None,
    ):
        super().__init__(
            tokenizer,
            chat_template_kwargs=chat_template_kwargs,
            assistant_start_str=assistant_start_str or self._DEFAULT_ASSISTANT_START,
            special_token_ids={
                tokenizer.convert_tokens_to_ids("<|User|>"),
                tokenizer.convert_tokens_to_ids("<|Assistant|>"),
            },
        )
        self._assistant_id: int = tokenizer.convert_tokens_to_ids("<|Assistant|>")
        self._think_bracket_ids: set[int] = {
            tokenizer.convert_tokens_to_ids("<think>"),
            tokenizer.convert_tokens_to_ids("</think>"),
        }
        self.trailing_token_ids = frozenset({self._assistant_id} | self._think_bracket_ids)
        # sglang 的 dsv4 parser 仅在请求携带 thinking 时分离 reasoning,
        # 这里显式暴露有效渲染模式,供 session server 转发。
        self.chat_template_kwargs = {
            **self.chat_template_kwargs,
            "thinking": deepseek.V4.render_thinking_enabled(self.chat_template_kwargs),
        }
tests/fast/utils/chat_template_utils/test_fixed_templates.py test-coverage

将 DeepSeekV4TITOTokenizer 纳入参数化测试,验证其 allowed_append_roles 与 Qwen3.5、Minimax M25/M27 一致,防止未来回归。

@pytest.mark.parametrize(
    "tokenizer_cls",
    [
        DeepSeekV4TITOTokenizer,
        Qwen35TITOTokenizer,
        MinimaxM25TITOTokenizer,
        MinimaxM27TITOTokenizer,
    ],
)
def test_restricted_fixed_template_excludes_mid_session_system(tokenizer_cls):
    # 这些模型家族与中间会话追加 system 不兼容,必须与默认的全角色集合区分;
    # 若未来编码器支持 system tail,应先更新此断言再放开角色集合。
    assert tokenizer_cls.FIXED_TEMPLATE.allowed_append_roles == frozenset(
        {"tool", "user", "assistant"}
    )
tests/e2e/sglang/test_session_server_multi_role/test_deepseekv4.py test-coverage

在 DeepSeek V4 E2E 配置中增加 assistant_text_threshold=0.05,保留未来编码器漂移可见性,同时避免对现有合理波动过度敏感。

CONFIG = ModelConfig(
    # 官方 deepseek-ai/DeepSeek-V4-Flash 是 mxfp4 打包的 routed experts,
    # Hopper MoE 内核无法服务;此 FP8 重打包与 scripts/run_deepseek_v4.py 一致,
    # tokenizer 字节级相同。
    model_name="sgl-project/DeepSeek-V4-Flash-FP8",
    reasoning_parser="deepseek-v4",
    tool_call_parser="deepseekv4",
    tito_model="deepseekv4",
    tp_size=4,
    ep_size=4,
    enable_spec=True,
    cycles=2,
    # 修复 system 尾不匹配后保留 0.05 漂移阈值,避免未来编码器行为变化被静默吞掉。
    assistant_text_threshold=0.05,
    # V4 按前序 assistant 的 tool_calls 顺序排序 tool_result 块,
    # sentinel tool_call_id 无法 roundtrip,因此使用通用回滚恢复。
    tool_call_failure_mode="rollback",
)

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 行为依赖编码器:若未来 encoding_dsv4 开始支持 mid-session system 追加,当前限制会导致 V4 会话在 system 后无法追加角色,需同步放宽 allowed_append_roles。
  2. E2E 阈值放宽:assistant_text_threshold=0.05 允许最多 5% 的 assistant-text mismatch,可能掩盖小规模真实回归;但该配置仅存在于 DeepSeek V4 E2E 文件,不会波及其他模型。
  3. 改动集中在 tito_tokenizer.py 的类级常量,Qwen3.5、Minimax M25/M27 已使用相同限制模式,回归概率低。

影响范围限定在 DeepSeek V4 家族的 session-server TITO 校验:修复后 E2E 全部 64 个样本通过,总 TITO mismatch 与 assistant-text mismatch 均为 0.0。对用户而言,消除了 Stage 3 训练中随机的 assistant-text 验证噪音;对系统而言,没有改变模板渲染、parser 或 mismatch 比较逻辑,其他模型家族零影响。团队后续维护 V4 时需注意 allowed_append_roles 与编码器行为保持一致。

依赖编码器行为 E2E 阈值放宽 仅修复 DeepSeek V4

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论