You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CUDA graph in the collector: from full-capture to update-state-only, and what we learned
#2357
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Collector CUDA Graph 复盘:从 Full Capture 到 Update-State-Only,以及为什么停下来
概要
这篇 discussion 回顾 collector CUDA graph 这条线的工作(#2307 → #2311 → #2338 → #2350/#2351)。它是一份工程复盘,而不是把 graph 模式推向生产的决策。
结论可以概括为四点:
因此当前结论是:保留其中协议层面正确的部分(backend preflight、owner boundary classification、receipt 修复),继续推荐 MPS 作为共享执行方式,但不把 update-state graph 作为默认能力或通用能力推广。
起点:为什么要做
早期 Phase A profiling 在标准的 MJWarp / FlashSAC / G1 motion workload 上发现,collector 侧可处理的 overhead 大约为:
主要集中在:
MJWarp 本身已经有固定 shape 的 physics step/reset graph,但这些 graph 不覆盖 Manager-based observation、reward、termination、command、packed reset 和 transition publication。于是自然出现一个问题:Manager update body 能否也用同样的方式捕获。
第一次尝试:Full Collector Graph
第一种设计尝试把 collector tick 里尽可能多的部分都放进 graph:
这条路失败的原因是结构性的,而不是实现细节没写好。
1. Collector tick 不是一个封闭的 device function
K=1 off-policy tick 有一个无法回避的依赖:
Policy inference 和 action publication 都在 env 外。任何把它们包含进 capture 的边界,都必须在 graph 内解决 inference IPC 的顺序关系。这会立刻把 graph 和 uni_rl 的 transport 细节耦合在一起。
2. Reset 是 mask-driven 且由 host 编排
Reset 需要 fixed-width mask、owner transaction、scene 写入和 event application。mask 本身是合法的 device state,但围绕它的编排是 host lifecycle。把它放进 graph,要么把 mask 语义冻结在 capture 时刻,要么引入 CUDA graph 无法表达的数据依赖分支。
3. Validation 和 diagnostics 天然希望对 host 可见
Finite / shape check 被写成 reduction 后,很容易变成 host bool read。在 eager 路径里,这只是性能问题;在 capture 里,它要么变成非法同步,要么变成被冻结的分支。graph-safe 的做法——persistent device verdict、deferred publication、只在失败边界做 host diagnostics——方向是正确的,但意味着必须先改变所有 manager 的 validation contract,然后才能评估 graph 本身。
4. 边界随着真实任务不断变大
每换一个 workload,就会暴露出一条新的动态路径:reset row compaction、event mode、command resample、recorder term、curriculum transition。每一条路径都必须被安全 capture、移到 graph 外,或者被 preflight 拒绝。这个设计最终收敛到的状态是:“capture 这个特定任务恰好不需要的那部分”。
第二次尝试:Update-State-Only Graph
第二种设计把边界收窄到真正属于固定 shape tensor program 的部分:
Physics stepping、reset 编排、inference 和 transition publication 都留在 graph 外。这是正确的边界,也是最终成功 capture 的版本。
这个阶段产出了应该保留的东西
即便最终不推广 graph 模式,下面这些工作仍然有价值:
为 update boundary 建立了独立的 public backend preflight,与 backend physics graph 是否开启无关。physics graph READY 并不能证明 Manager update body 可 capture。
建立了 owner preflight,对不支持的动态语义 fail closed,而不是尝试 capture 后在运行时崩掉。
建立了 persistent carrier contract:update 输入和输出的 address、shape、dtype、device 都固定。
建立了 stream / completion ordering contract:
引入了 deferred finite verdict:validation 结果保存在 device carrier 中,在明确的 completion boundary 再发布。
对 curriculum term 做了 reset-owned classification:global / step-owned curriculum 变化留在 graph 外;只有 reset-owned boundary 可以和 graph body 共存。
在 warmup、capture、replay 周围加入了 allocator invariant check。
run manifest 中加入了 capture identity、state、replay counter、disable reason、RNG state / draw version、carrier layout 等证据。
这份清单是整个尝试中最重要的沉淀。即使最终性能答案是零,这些工作本身也是有意义的。
但为了跑通,仍然需要禁用大量内容
成功 capture 的那次运行并不是生产语义。为了让 motion tracking 达到 graph-ready,测试路径需要:
lower == upper时成立的 command resample 路径motion tracking 本身的
resampling_time_range: [1e9, 1e9]也接近“几乎从不 resample”。因此这次 graph run 实际上是一个接近静态的 kernel 序列。它证明的是:CUDA graph replay 可以加速一个静态化的 Manager body。
它没有证明的是:按生产配置运行时,motion tracking 任务可以被 graph 模式加速。
这个区别比任何单个性能数字都重要。
性能对比实际说明了什么
2×2×2 训练路径对比覆盖了:
测试使用真实训练参数,只缩短 iterations,并且临时禁用了阻塞 graph 的 item。
MPS 本身是更大、更普适的收益:
怎么解读
updates_per_step: 8使 learner work 占主导,Amdahl 定律大幅压低了 graph 的上限。真正的阻塞:开发成本,而不是 capture 报错
最终决定性的问题不是 capture 失败,而是为了让 capture 通过,未来需要持续付出什么。
如果 update-state graph 变成一个受支持的通用路径,那么每一个 reward、observation、command term 都需要回答:
这意味着一个二十行的 reward term,会变成二十行实现,加上一份永久的 graph-compatibility 义务。它还会持续诱导出一些数学上并不正确、只是为了满足 capture 的 shortcut。
lower == upper的 resample 分支正是这一类改动,后来也被正确回退了。这个成本不是一次性的。它会随着每一个新 term、每一次 manager 改动、每一个新任务族重复出现。而换来的收益是恢复生产语义前的 2%–19%,在 MPS SAC 场景下接近零。
这不是一个好的默认基础设施 trade。
保留什么
以下内容与 graph 是否推广无关,应当保留:
step。这是一个真实的 baseline bug,eager 路径同样会命中。SimBackend上;env 代码不应探测 backend 私有状态。不继续携带什么
out=graph-only 机制lower == upper的 command resample shortcut经验教训
1. 边界应该从 ownership 推导,而不是从 profiling 百分比推导
profiling 能告诉我们 update body 很热,但真正可 capture 的边界,是输入输出有单一 owner 且 shape 稳定的那部分。决定 capture 可行性的是 ownership,不是 kernel 数量。
2. 一个需要禁用语义才能完成的 capture,还没有被真正评估
每一个被禁用的 feature 都应该视为一个未测试的交互,而不是简化条件。当这个列表包含 noise、RNG、event 和 validation 时,benchmark 测量的是合成 workload。
3. Fail-closed preflight 值得坚持
preflight 把模糊的 “graph 坏了” 变成了明确原因:不支持的 event mode、非 reset-owned curriculum、active recorder、carrier layout 不稳定。即使最终决定不使用 graph,这种清晰度仍然有价值。
4. 错误传播是实验的一部分
大量诊断时间花在了一个由 daemon thread 吞掉第一错误后引发的次生错误上。根因只是 IPC receipt 的 operation mismatch。如果第一错误能直接暴露,整个调查会大幅缩短。
5. 评估维护模型,而不只是吞吐
性能数字本身不是验收标准。真正的问题是:未来的每一个 term 作者现在必须保证什么?这个永久约束是否值得?在这里,答案是否定的。
6. 优先选择没有作者契约的优化
MPS 在不改变 term 写法的情况下带来了更大、更普适的收益。当两个优化重叠时,没有语义契约的那个应该优先成为默认。
7. workload 依赖应该被预期,而不是当作意外
CUDA graph replay 作为机制是通用的,但端到端收益遵循 Amdahl 定律。被捕获 body 在 iteration 中所占比例、learner update 比例、host orchestration 的多少,共同决定收益上限。单一任务的 benchmark 不够。
当前建议
All reactions