Skip to content

[Fix] Align GLM-5.3-Flash tool/boundary rendering with the official chat template - #2181

Closed
ShilohYu wants to merge 3 commits into
InternLM:mainfrom
ShilohYu:fix/glm53-chat-template-align
Closed

ShilohYu wants to merge 3 commits into
InternLM:mainfrom
ShilohYu:fix/glm53-chat-template-align

Conversation

@ShilohYu

@ShilohYu ShilohYu commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

背景

接续 #2180。2026-10-15 的 GLM-5.3-Flash 训推 chat template 一致性对比(SFT v2 样例集 vs checkpoint 内官方 chat_template.jinja,SHA-256 34d5ee66…)报了 11 个不一致 + 5 个异常。本 PR 修复其中剩余的渲染层与数据层差异(M01 的 enable_thinking 已由 #2180 显式拒绝):

编号 问题 修复
M02/M03 assistant→system / 连续 assistant 之间多插入 `< endoftext
M04/M05 分散/聚合 tool 结果未按 tool_calls 调用序重排 实现官方 can_sort 规则:块内结果 id 全部存在、唯一、且匹配唯一 tool_call id 时按调用序输出,任一不满足保持消息顺序
M06 结构化 output 被 Python 字符串化 经 visible_text 渲染可见内容
M07 output 内嵌套 tool_reference 未展开 展开匹配工具定义为 <tool_response><tools>…</tools></tool_response>
M08–M10 tool/assistant 媒体被渲染成 <reminder> 降级文本或字符串化 渲染层输出官方三段式标记;数据层按脏样本丢弃(见下)
M11 system 媒体未被收集,样本静默走纯文本路径 数据层按脏样本丢弃
E01 content 列表混排纯字符串时 collector TypeError 字符串按文本块跳过(与官方 jinja visible_text 一致)
E02 同一记录混合图片+视频 NotImplementedError cache 警告并丢弃、runtime 由上游换无 loss 占位样本,不再中断(见"脏数据不中断")

E03–E05 音频属对比脚本覆盖限制,渲染层占位符本就一致,不改代码。

改动

渲染层 xtuner/v1/data_proto/messages/glm53_chat.py(GLM-5.2 官方模板没有排序/嵌套引用/结构化 output 逻辑,故全部实现留在 glm53 本地,不改共享 glm52 helper):

  • 新增 _render_tool_block:连续 tool 消息块按官方 can_sort 规则重排或保持原序
  • 新增 _render_tool_response / _tool_references_to_response:结构化 output 与嵌套引用渲染
  • glm53 本地 _assistant_content_and_reasoning:assistant 媒体渲染占位标记而非 reminder
  • <|endoftext|> 仅追加在最终 assistant 后;slowspeed golden 对连续 assistant 前缀与 system/assistant 边界的 EOS 做对称处理

数据层:

  • base_mllm_tokenize_fn.py:collector 跳过纯字符串 content 块;新增 collect_media 钩子
  • glm53_vl_tokenize_fn.py:override collect_media,非 user/pretrain 角色的图片/视频按脏样本处理("完整接通或显式拒绝"中选择了拒绝接通;接通工具链媒体是更大的独立工作)

脏数据不中断(commit 2)

数据集 cache 构建在 worker 子进程中运行 tokenize_fn(jsonl.py 的 tokenize_worker 没有异常捕获):任何逃逸异常会杀死 worker,主进程永远阻塞在结果队列上——整条训练启动挂住。本 PR 把所有单条脏数据路径统一到既有损坏样本协议(cache:log warning + num_tokens=0,打包前过滤;runtime:抛出由上游换成无 loss 占位样本):

  • 混合图片+视频样本:cache 警告+丢弃(原为 cache 也逃逸的 NotImplementedError)
  • GLM-5.3 非 user/pretrain 角色媒体拒绝:同一路由
  • 图片/视频 cache 几何与元数据计算加保护网:RuntimeError/KeyError/ValueError/IndexError → 警告+丢弃;AssertionError 故意不捕获,配置错误(如 enable_thinking=False)必须继续暴露,不能静默掏空数据集
  • collector 的单样本 assert(image_wh/video_wh 形状或数量不匹配)改为 RuntimeError,进入既有 cache 丢弃通道
  • glm53 视频 cache 缺 origin_video_length/origin_fps 元数据:RuntimeError(丢弃)替代 assert(挂住)

已知残余风险:tokenize_worker 本身仍无异常转发,未覆盖到的异常类型(如 OSError)在 cache 构建期仍可能挂住;作为后续工作另行评估。

测试

共享环境 pt29_glm2(transformers 5.17.0,GLM_5_3_FLASH_PATH 指向共享盘四层 checkpoint,其 chat_template.jinja 与 25B 字节一致):

  • test_glm53_chat.py / test_glm53_vl_tokenize_fn.py / test_glm53_vl_collator.py:64 passed(11 个报告渲染样例 + 3 个 can_sort 回退样例逐字符对齐官方 jinja 且 fast/slow parity;边界 EOS/loss、tool 结果不监督;混合媒体与媒体角色的丢弃语义;字符串 content 块;TestGlm53DirtyDataDoesNotHalt 覆盖坏 image_wh、缺视频元数据、配置错误不吞没)
  • 共享 collector/base 改动回归:test_glm52_openai_tokenize_fn.py + test_qwen3_vl_tokenize_fn.py + test_qwen35_vl_tokenize_fn.py 32 passed
  • 额外模拟 cache worker 场景验证 5 类脏样本(混合媒体/system 媒体/坏 wh/缺元数据/wh 数量不匹配)全部 num_tokens=0 丢弃、无异常逃逸
  • Ruff check/format 通过;完整 pre-commit 因共享环境 pip mirror 不可达未执行(沿用此前会话的处置)

Note on length caches:tool 块、结构化 output、中间 EOS 的渲染文本变化会影响既有长度缓存中此类样本的 num_tokens,请按 #2180 的同样做法手动删除一次旧长度缓存。

…hat template

Render layer (xtuner/v1/data_proto/messages/glm53_chat.py):
- Render contiguous tool blocks in the preceding assistant's tool_calls order
  when the official can_sort rule holds (all result ids present, unique within
  the block, matching a unique tool_call id); otherwise keep message order.
- Render structured {"output": ...} tool results through visible_text and
  expand nested tool_reference lists into <tool_response><tools>...</tools>
  </tool_response>, instead of Python-stringifying them.
- Render assistant/tool media with the GLM-5.3 placeholder markers via local
  _assistant_content_and_reasoning/_render_tool_response (the shared glm52
  helpers degrade media to <reminder> text, which GLM-5.3 never emits).
- Append the training <|endoftext|> only after the final assistant message;
  the official template never emits EOS mid-conversation, so assistant->system
  and assistant->assistant boundaries no longer get one (the slowspeed golden
  drops the truncated render's trailing EOS accordingly and un-appends the
  prefix EOS for consecutive assistants).

Data layer:
- collect_image_video_paths_and_extra skips plain-string content parts instead
  of crashing on c["type"] (strings are text per the official template).
- BaseMLLMTokenizeFunction gains a collect_media hook; Glm53VLTokenizeFunction
  overrides it to reject image/video in non-user/pretrain roles with
  NotImplementedError in both cache and runtime (the base __call__ no longer
  downgrades NotImplementedError into a cache drop), instead of silently
  training samples whose placeholder markers are never expanded.

Tests: 11 render-contract cases + 3 can_sort fallback cases from the 2026-10-15
template-consistency failure report (text equality with the checkpoint's
official chat_template.jinja plus fast/slow parity), boundary EOS/loss
semantics, tool-result supervision, media-role rejection and string content
parts. 65 passed in the GLM53 dataset suites; glm52/qwen3_vl/qwen35_vl
regression 32 passed.

Note on length caches: rendered text changes for tool blocks, structured tool
outputs and mid-conversation EOS, so cached lengths computed by older code can
be stale for such samples; delete the old length cache once, as before.
The dataset cache builder runs tokenize_fn in worker processes
(jsonl.py tokenize_worker) with no exception handling: any escaping
exception kills the worker and the main process blocks forever on the
result queue. Route every per-sample dirty-data condition through the
existing damaged-sample protocol (log warning + num_tokens=0 in cache,
filtered before packing; an upstream inert placeholder at runtime):

- BaseMLLMTokenizeFunction.__call__: mixed image+video samples now log
  a warning and are discarded in cache instead of raising
  NotImplementedError (which escaped even in cache state); the
  media-role rejection from collect_media overrides (e.g. GLM-5.3
  non-user/pretrain media) takes the same route via
  _discard_damaged_sample.
- Image/video cache geometry/metadata calculation is wrapped:
  RuntimeError/KeyError/ValueError/IndexError -> warning + drop.
  AssertionError is deliberately NOT caught, so config errors (e.g.
  enable_thinking=False) still surface instead of silently emptying
  the dataset.
- collect_image_video_paths_and_extra: per-sample asserts (image_wh /
  video_wh shape or count mismatch) become RuntimeError so they hit the
  existing cache drop channel instead of killing the worker.
- glm53 video cache: missing origin_video_length/origin_fps metadata
  raises RuntimeError (dropped with a warning) instead of an assert
  that escaped the cache worker.

Tests: mixed-media and media-role contract tests updated to the discard
semantics (cache warns + drops with num_tokens=0, runtime raises for
the inert placeholder, media is never loaded); new
TestGlm53DirtyDataDoesNotHalt covers bad image_wh, missing video
metadata, and that config errors still raise in cache. GLM53 dataset
suites 64 passed; glm52/qwen3_vl/qwen35_vl regression 32 passed; ruff
check/format clean.
…ignment

- docformatter --wrap-descriptions 119 rewraps (no behavior change)
- _is_tool_reference_list becomes a TypeGuard so the narrowed value can be
  passed to _tool_references_to_response (refs typed as list[Mapping])
- collect_media accumulates role names into a set[str] via str(role) so
  sorted() sees a homogeneous comparable type
@ShilohYu

Copy link
Copy Markdown
Collaborator Author

本 PR 已按飞书失败报告的维度拆分为 7 个独立小 PR(全部 base=main,按依赖序合并;层叠 PR 的增量 diff 见各 body 的 compare 链接),便于分人并行 review:

  1. [Fix] Append GLM-5.3-Flash training EOS only after the final assistant #2190 EOS 边界(M02/M03)
  2. [Fix] Render GLM-5.3-Flash tool result blocks in tool_calls order #2191 tool 结果块按 tool_calls 顺序重排(M04/M05)
  3. [Fix] Cover GLM-5.3-Flash structured tool outputs and reject string tool_call arguments #2192 结构化 tool output 渲染 + 嵌套 tool_reference 展开(M06/M07)
  4. [Fix] Render GLM-5.3-Flash assistant media as placeholder markers #2193 assistant 媒体三段式占位标记(M08–M10)
  5. [Feature] Collect GLM-5.3-Flash media from all message roles #2194 数据层拒绝非 user/pretrain 角色媒体,丢弃不中断(M11)
  6. [Fix] Tolerate plain-string content parts in multimodal sample collection #2195 collector 跳过纯字符串 content 块(E01)
  7. [Fix] Discard dirty GLM-5.3-Flash multimodal samples instead of halting training #2196 混合图+视频丢弃不中断 + assert 改 RuntimeError 保护网(E02)

拆分无损性已验证:#2193 的两个 chat 文件、#2196 的三个数据文件均与 618d3c4 逐字节一致;7 个 PR 全部合入后整树与 618d3c4 等价。本 PR 关闭,后续 review/合并请走上述系列。

@ShilohYu ShilohYu closed this Oct 16, 2026
@ShilohYu
ShilohYu deleted the fix/glm53-chat-template-align branch October 26, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant