Prhub

#2476 fix(dashboard): zero the trainer log-probs the loss masks out

原始 PR 作者 Shi-Dong 合并时间 2026-08-13 01:53 文件变更 3 提交数 1 评论 0 代码增减 +37 / -3

执行摘要

清零 loss 掩码位置的 train log-probs,修复 token 视图误导

PR body 明确指出:dump 对 loss 忽略的位置没有 rollout log-prob(引擎从未生成这些 token,所以存的是 0.0 占位),而 trainer 会给每个位置打分,token 视图直接相减会把真实 log-prob 与占位符比较,在从不训练的 token 上产生 tens of nats 的虚假分歧。更严重的是 lp_diff 热力图是发散色标、按窗口内最大绝对值归一化,单个被掩码的 -30 会把所有真实值压成中性色,导致用于定位 drift 的视图完全失效。

值得精读。本 PR 虽然改动很小,但解决了一个典型的“占位值污染可视化”问题,展示了如何区分“真实零值”与“缺失占位”,并通过保持数组对齐的方式在展示层修复而不影响统计层。_zero_masked 的设计简洁可复用,是 dashboard 数据展示层的一个好范例。

讨论亮点

本 PR 没有公开的 review 讨论线程,Zhichenzzz 直接批准(APPROVED,无评论)。PR body 中作者自行交代了设计取舍:清零只限于 token 视图,因为该视图需要保留每个位置以保持数组对齐;汇总统计量早已按 loss 掩码过滤;ref_log_probs 暂时不做同样处理,因为当前没有视图对它做差分,未来若增加参考模型对比视图需要同样处理。

实现拆解

实现分为 4 步:

  1. 新增掩码清零辅助函数:在 miles/dashboard/dump_reader.py 中新增 _zero_masked(values, mask),用 torch.where(mask, values, zeros_like(values)) 将掩码位置清零但保留数组长度,确保与 token 切片一一对齐。
  2. 改造 tokens() 计算链路:在 DumpReader.tokens() 中先用 _zero_masked(row.log_probs, row.loss_mask > 0) 得到 train_log_probs,再基于它计算 lp_difftrain_log_probs - row.rollout_log_probs),最终返回给前端的 train_log_probslp_diffimp_ratio 都使用这条新链路。
  3. 补充回归测试:在 tests/fast/dashboard/test_dump_reader_views.py 新增 test_tokens_blank_the_trainer_side_where_the_loss_is_masked,覆盖全掩码样本(REMOVED[0])与普通样本,断言掩码位置的 train_log_probs 为 0、lp_diff 等于 -rollout_log_probsref_log_probs 保持原样,且未掩码样本的值不变。
  4. 同步文档:在 docs/user-guide/dashboard.md 的 token 页面说明中新增注意事项,告知用户 loss 忽略的位置两边都读为零,应只依据 loss 覆盖的 token 判断一致性。

该改动刻意只作用于 token 视图,汇总统计量(mean_abs_lp_diffmax_abs_lp_diffmean_imp_ratio)此前已只选择 loss 覆盖的位置,因此不受影响;ref_log_probs 也保持 dump 原样。

文件 模块 状态 重要度
miles/dashboard/dump_reader.py 数据读取 modified 6.8
tests/fast/dashboard/test_dump_reader_views.py 视图测试 modified 5.24
docs/user-guide/dashboard.md 用户文档 modified 1.9

关键符号

_zero_masked tokens test_tokens_blank_the_trainer_side_where_the_loss_is_masked

关键源码片段

miles/dashboard/dump_reader.py core-logic

核心源码变更:新增 `_zero_masked` 辅助函数并改造 `tokens()` 的 `train_log_probs`/`lp_diff` 计算链路,是修复的载体。

# miles/dashboard/dump_reader.py
​
​
def _zero_masked(values: torch.Tensor | None, mask: torch.Tensor) -> torch.Tensor | None:
    """将 loss 掩码位置清零,同时保持数组长度与 token 切片对齐。    token 视图需要保留每一个位置(数组要与 token 一一对应),
    只有汇总统计量才可以真正丢弃被掩码的位置。
    """
    # None 表示该行没有 trainer 数据(如 evaluation 样本),直接透传。
    return None if values is None else torch.where(mask, values, torch.zeros_like(values))
​
​
# 在 DumpReader.tokens() 中,改造后的核心计算链路:
#
# dump 对 loss 忽略的位置(工具输出、被掩码的 turn)没有 rollout log-prob:
# 引擎从未生成这些 token,因此存的是 0.0 占位。trainer 却会给每个位置打分,
# 若直接相减,lp_diff 会在从不参与训练的 token 上报告几十 nats 的差异,
# 并在分发热力图中把真实信号压平。这里把 trainer 侧同样清零。
train_log_probs = None if row is None else _zero_masked(row.log_probs, row.loss_mask > 0)
lp_diff = (
    train_log_probs - row.rollout_log_probs
    if row is not None and train_log_probs is not None and row.rollout_log_probs is not None
    else None
)
# 之后 token 视图返回的 train_log_probs、lp_diff、imp_ratio 都基于上述结果。

评论区精华

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

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

风险与影响

  1. tensor 类型假设_zero_masked 依赖 torch.where,若 row.log_probs 不是 tensor(例如未来 dump 格式变化),会直接报错;当前 TrainRow 数据来自 torch.load,风险较低。
  2. 热力图行为变化:掩码位置全部变为 0 后,lp_diff 热力图的色标窗口会缩小到真实信号的范围,原本“被异常值压平”的症状消失,但用户若未读文档,可能误以为掩码位置“没有数据”;文档已补充说明。
  3. 测试覆盖缺口:作者明确说明本地无法运行 dashboard 测试(sglang/triton 依赖在 macOS 上不可用),新测试依赖 CI 验证;PR 已合入,CI 结果未在材料中给出,存在理论上的回归风险。
  4. 与其他视图的一致性ref_log_probs 未同步处理,若未来新增“参考模型 vs trainer”的差分视图,会重蹈本 PR 修复的误导问题。

用户影响:dashboard 的 token 页面不再显示虚假的几十 nats 分歧,lp_diff 热力图能真实反映 loss 覆盖位置的训练/推理差异,避免用户误判 run 健康状态。系统影响:仅增加一次 torch.where 操作,无性能与存储影响。团队影响:文档同步更新,降低了 dashboard 解读门槛;新增 _zero_masked 成为可复用的掩码处理工具,为后续视图开发提供约定。

热力图尺度行为变化 新测试依赖 CI 验证 ref_log_probs 未同步处理

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论