执行摘要
- 一句话:修复 agentic v1/v2 的 rollout_id 不一致
- 推荐动作:值得精读,因为这是 session v1/v2 parity 设计与后处理语义交汇处的一次小而关键的修正。建议重点理解两点:一是为什么只对 v2 多叶写 rollout_id,二是它与 #2347 如何在同一个守卫上收敛;这两点对后续 agentic rollout 逻辑有直接影响。
功能与动机
PR body 指出 tests/e2e/sglang/test_session_v1_v2_parity.py::test_qwen3_8b_h200_fa3_agentic_v2_drop_retries_matches_v1_training_payload_bitwise 在 main 上稳定复现:AssertionError: sample.rollout_id is not bitwise equal left = None, right = 0。根因是 agentic_tool_call.py 只在 v2 分支把 input.sample.index 写进 sample.rollout_id,v1 分支从不写,而 parity 测试逐字段位级比较训练 payload,两条路径必然冲突。PR body 明确说 rollout id 是样本身份,不是 v2 概念,两条路径应当对齐。
实现拆解
- 变更入口:
miles/rollout/generate_hub/agentic_tool_call.py 的 generate(),在 collect_samples 返回后对 result.samples 做后处理。原始提交尝试把 rollout_id 的推导提升到分支外,让 v1 也能按需戳印,从而直接满足 parity 测试。
- 重定基修正:因 #2347 已把同一条件改为
if use_v2 and len(samples) > 1,guapisolo 将本 PR 调整为相同条件。v2 多叶结果需要共享 rollout id 以标识样本归属;v1 与 v2 单例结果保持 None,从而保留普通后处理(样本裁剪、动态全局 batch 元数据),避免触发 compact 判定。
- 配套验证:依赖现有测试,
tests/fast/rollout/generate_hub/test_agentic_v2.py 的 7 个用例与 ruff check agentic_tool_call.py 通过;PR body 记录 CI lane 从 4/5 提升到 5/5 通过,证明问题被真实修复而非被跳过。本次没有新增或修改测试文件。
关键文件:
miles/rollout/generate_hub/agentic_tool_call.py(模块 样本生成;类别 source;类型 core-logic;符号 generate): 唯一改动文件。generate() 中 v2 分支原无条件对每个样本写 rollout_id,导致 v1/v2 parity 位级断言失败;将条件收窄为 v2 多叶场景后,单例语义与 v1 一致。
关键符号:generate
关键源码片段
miles/rollout/generate_hub/agentic_tool_call.py
唯一改动文件。generate() 中 v2 分支原无条件对每个样本写 rollout_id,导致 v1/v2 parity 位级断言失败;将条件收窄为 v2 多叶场景后,单例语义与 v1 一致。
# miles/rollout/generate_hub/agentic_tool_call.py —— generate() 中样本收集完成后的核心处理
samples = result.samples
# rollout id 只对 v2 多叶结果做共享标注:多叶展开需要用 id 标识样本归属。
# v1 与 v2 单例结果一律保持 None,避免后处理误判为 compact rollout,
# 从而保住样本裁剪、动态全局 batch 元数据等普通路径。
if use_v2 and len(samples) > 1:
# FIXME: handle sample index issues.
rollout_id = input.sample.rollout_id if input.sample.rollout_id is not None else input.sample.index
assert rollout_id is not None, "v2 agentic samples require input Sample.rollout_id or Sample.index"
for sample in samples:
sample.rollout_id = rollout_id
# v1 路径:agent 元数据由驱动侧合并;v2 的元数据已随 wire 返回,无需在此叠加。
if not use_v2:
for s in samples:
s.metadata.update(agent_metadata or {})
评论区精华
guapisolo 在 issue 评论中澄清最终行为:本分支对 v1 与 v2 单例结果保持 rollout_id=None,仅通过 if use_v2 and len(samples) > 1 为 v2 多叶结果分配共享 rollout id,并说明这样可以保留样本裁剪与动态全局 batch 元数据等普通后处理。此外 guapisolo 的批准意见为 Do changes to align with session server v2 design,表示该条件与 session server v2 的设计意图一致。PR body 也预判了与 #2347 的 rebase 冲突,最终两个意图兼容并收敛到同一条件。
- rollout_id 戳印语义:两条路径都打还是仅 v2 多叶打 (design): 采用仅 v2 多叶打 rollout_id,保留 v1 与 v2 单例的 None;PR 描述已过时,以评论记录为准。
- 与 #2347 的同一个守卫改动冲突 (other): 本 PR 最终头版本与该条件收敛一致,两个意图兼容;审阅人批准意见表示与 session server v2 设计对齐。
- parity 测试与 CI 验证 (testing): 问题被真实修复;快速验证未重跑 e2e parity,依赖 CI 覆盖。
风险与影响
- 风险:行为风险:v2 单例不再无条件获得 rollout_id,依赖该字段的下游(如 dashboard、训练数据转换)需要确认对单例路径没有隐含假设;现有测试通过但不代表所有消费方都被覆盖。测试覆盖:本次没有新增断言,e2e parity 验证依赖 CI;若未来 parity 测试扩展到多叶场景,v1 多叶仍无 rollout_id,可能再次暴露不一致。跨 PR 协调:#2347 与本 PR 改同一守卫,时序上已通过 rebase 解决,后续改动需注意保持单一职责。整体是单行核心逻辑变更,风险面较小但处于 rollout 主路径。
- 影响:影响范围覆盖 agentic rollout 的 session v1/v2 样本生成路径,以及依赖
sample.rollout_id 的训练数据转换与 parity 校验。对用户而言,修复了训练 payload 中 rollout_id 不一致导致的确定性失败,且不会让 v1 承接到新的失败模式;对团队而言,CI 恢复绿色,同时明确了 v2 多叶与单例在 rollout_id 上的语义边界。该改动为单行条件收窄,无接口或配置变更。
- 风险标记:核心路径变更, 缺少新增测试, 跨 PR 交互
关联脉络
- PR #2129 test(session): validate H200 v1/v2 agentic parity: 该 PR 添加了位级 parity 测试,把 v1/v2 的 sample.rollout_id 差异变成确定性失败,是本次修复的直接触发点。
- PR #2368 fix(rollout): group session v2 leaf samples: 该 PR 引入 v2-only 的 sample.rollout_id 戳印逻辑,是根因来源;本 PR 收窄同一逻辑。
- PR #2347 (标题未提供,见 PR body 提及的同一守卫改动): PR body 与最终提交都对同一守卫
if use_v2 and len(samples) > 1 有改动,两个意图最终收敛为相同条件,存在 rebase 交互。
参与讨论