一个 Coding Agent 要修复仓库中的 bug。模型生成修改方案,在隔离环境中编辑文件、运行测试,读到错误后再继续修改。这一整段尝试称为一条 rollout,包含模型推理和环境执行。
假设一轮训练有 100 个问题,每个问题生成 8 条独立 rollout,共 800 条。每个问题使用各自的仓库镜像,同一问题的 8 条 rollout 各自维护可写状态,避免互相覆盖代码。这批任务共用到 100 种镜像。
执行并发上限设为 64,训练要收齐全部结果才进行下一次模型更新。同一问题的 8 条结果会一起参与比较,但不要求同时开始。
预热能减少任务开始时的等待,复用能减少反复创建环境的开销,但前者占用空闲资源,后者需要清理任务留下的状态。选择配置时,要看这些开销最终如何影响收齐 800 条结果的时间。
RL 的环境供给约束
模型推理使用 GPU,代码与测试通常在 CPU Sandbox 中执行。环境没有准备好,对应的 rollout 就不能继续。即使前 799 条已经完成,最后一条仍会推迟下一次更新。
这批任务有三种不同的约束:
- 镜像数据量。 100 种镜像需要拉取和缓存多少数据,取决于镜像体积和共享层。64 条任务的并发上限,并不意味着节点只需要缓存 64 种镜像。
- 同一问题的并发。 8 条 rollout 要同时执行,就需要 8 个独立环境。只预热一个,其余任务就要等后续环境就绪。
- 集中创建的负担。 一起申请大量环境,会同时触发镜像拉取、Pod 调度和 Kubernetes API 请求。提高并发可能加重磁盘、网络和 API server 的负担,反而延长等待。
从训练需求到环境供给
训练框架知道一轮要执行哪些问题、每个问题采样几条 rollout,以及何时需要收齐结果。Kubernetes 调度器负责把 Pod 放到节点上,但这些训练需求还需要转成具体的环境准备计划:哪些镜像先准备、每种准备几个、何时补充、任务结束后是否复用。
| 层次 | 负责的事情 |
|---|---|
| RL 框架 | 运行 Agent,收集交互轨迹,计算 reward,决定结果何时参与训练 |
| 环境编排 | 按镜像组织任务,根据资源容量安排预热、领取和复用 |
| 环境生命周期管理 | 创建和释放环境,维持待领取的空闲环境 |
| Kubernetes 与容器运行时 | 将 Pod 调度到节点,启动进程,提供所选运行时的隔离 |
Google 的 GKE Agent Sandbox 工程实践提供了一个例子:agent-sandbox-rl 中的 Fleet 按镜像安排环境,Agent Sandbox 控制器维护预热池(WarmPool),任务通过 Claim 领取环境。一次 rollout 中的多次编辑和测试使用同一个 Sandbox,结束后再释放或重置。1
开始前:安排预热
每种镜像准备几个环境
预热数量可以按执行并发估算,也可以直接为每条 rollout 准备一个环境。前者希望用少量环境持续处理任务,后者希望任务开始时都能立即领取环境。
例如,将 64 的并发预算按各镜像的任务占比分配,并为每种镜像至少准备一个环境,与全量准备相比:
| 准备方式 | 每种镜像提前准备的环境数 | 总预热环境数 |
|---|---|---|
| 按并发预算分配 | 64 × 8 / 800 = 0.64,四舍五入后至少为 1 | 100 |
| 每条 rollout 准备一个 | 8 | 800 |
一次准备 800 个环境,同一时刻却最多运行 64 条 rollout,大部分环境暂时用不上。如果能在当前任务运行期间准备好后续环境,就不必一次预热全部 800 个。
还要留出领取后的补充空间:执行并发限制的是正在运行的任务数,预热池维持的是待领取的环境数。 环境被领取后,控制器会创建新的环境补足空缺。若 100 个池都已补足一个空闲环境,另有 64 个环境正在执行,集群中就有 100 + 64 = 164 个环境,正在删除的旧环境还可能暂时占用资源。
全部放不下,就按窗口推进
除了每种镜像准备几个环境,还可以限制一批处理多少种镜像,这个批次大小称为窗口。例如每批处理 10 种镜像,再选择批次之间如何衔接。
- 全量预热:准备好所有镜像对应的环境,再执行任务,需要一次容纳全部预热环境。
- 分批预热:完成一批后清理其预热环境,再准备下一批,批次之间会有等待。
- 流水线预热:当前批次运行时就准备下一批,用额外的资源换取更短的批次间等待。
这三种方式在 agent-sandbox-rl 中分别对应 naive、sliding 和 pipelined。
若 A 批任务执行 30 秒、B 批准备 20 秒,理想情况下 A 结束时 B 已经就绪;若 B 要准备 60 秒,仍需再等约 30 秒。两批争用磁盘、网络和调度资源时,准备时间还可能变长。
流水线要同时容纳当前批和下一批。每种镜像预热越多,同样的资源预算能容纳的镜像种类就越少。如果既为每条 rollout 准备环境,又提前准备下一批,窗口可能被迫缩小,导致实际执行的任务数达不到并发上限。
减少镜像读取的等待
清理环境不等于清理镜像缓存。前几批用过的镜像可能继续留在节点磁盘上,占用多少空间取决于共享层、节点分布和容器运行时的回收策略。
让相同镜像的环境尽量落到同一节点,可以共享缓存、减少重复拉取。但过度集中也会争用节点资源,因此需要在缓存复用与任务分散之间取舍。Fleet 使用固定的镜像到集群映射,以及节点上的软 Pod 亲和性来改善放置;前者不查询节点缓存,后者也不保证所有环境落到同一节点。
GKE Image Streaming 允许容器按需读取远程镜像内容,同时在后台下载并缓存完整镜像,因此不必等全量下载完才启动。不过,Sandbox 就绪不代表首次测试要用的文件都已缓存;若初始化或测试需要读取大量文件,远程读取仍会造成等待。可以在预热阶段导入常用依赖、完成必要初始化,把这些读取提前。2
执行中:让空闲环境补得上
预热解决第一批任务的等待,后续任务还要依靠预热池持续补充环境。领取请求与补充环境的创建请求都会消耗 API server 的处理能力,需要安排好节奏。
补充节奏有两个可以分别控制的维度:一是环境被领取后,隔多久开始补充;二是开始补充后,每秒最多创建多少个。
若领取只在启动时集中发生,延迟补充可以让 API server 先处理领取请求。但若像 Agent Sandbox 控制器那样,每次观察到池中环境减少都重新计算延迟,持续领取就可能不断推迟补充,直到空闲环境耗尽。此时应使用较小或为零的延迟,配合限速持续补充。
多预热一些环境能应付短时突发;但如果补充长期跟不上领取,空闲环境迟早会耗尽。限速还要看作用范围。Agent Sandbox 按池限速,100 个池同时补充时,总创建请求量仍可能很大。3
结束后:复用与重置
每组复用几个环境,决定能并行多少条 rollout
保留预热池,能让后续任务继续领取空闲环境;复用则是保留刚执行过任务的 Sandbox,重置后让它接着运行下一条。两者减少的是不同的开销。
同一问题的 8 条 rollout,可以各用一个环境并行执行,也可以在一个环境中依次执行,或者分到两个环境中各执行 4 条。复用几个环境,决定了这一组任务能有多少条同时运行。
沿用 100 个问题、每个问题 8 条 rollout、最多 64 并发的例子。假设每条执行 30 秒、相邻任务间重置 2 秒,环境随取随用,不计领取和清理开销,也不发生中途换环境。若复用时每个问题固定占用一个环境,直到该组全部完成:
| 执行方式 | 收齐一个问题的 8 条结果 | 收齐整批 800 条结果 |
|---|---|---|
| 每条使用独立环境 | 8 条同时开始时为 30 秒 | 13 轮 × 30 秒 = 390 秒 |
| 每个问题复用一个环境 | 8 × 30 + 7 × 2 = 254 秒 | 2 轮 × 254 秒 = 508 秒 |
这是简化推算,不是实测。复用时,第一轮同时处理 64 个问题,第二轮只剩 36 个,并发无法一直跑满。复用是否更快,要看省下的创建和领取时间,能否抵消重置与组内串行带来的时间。
窗口还会进一步限制并发:若每批只有 10 种镜像、每种只复用一个环境,就最多同时运行 10 条;每种改用两个环境,则最多运行 20 条。实际并发不超过窗口内镜像种类数 × 每种镜像使用的环境数,也受全局上限和剩余任务数限制。
复用也会改变空闲环境的需求。已领取的环境还能继续执行后续任务时,可以调低预热池目标,避免补出用不上的副本;需要轮换或重置失败时,再补充替代环境。
重置要覆盖任务修改过的状态
每条 rollout 都应从该问题相同的初始状态开始。Agent 除了编辑源码,还可能安装依赖、留下缓存、修改 Git hook 或启动后台进程。这些残留会影响下一条任务,甚至改变 reward。
例如,agent-sandbox-rl 用 Git 恢复仓库文件,清理未跟踪文件、指定临时目录和进程;检查失败时换环境,也允许限制连续复用次数。但 Git 无法恢复已安装的 Python 包等仓库之外的状态。依赖和 Git 配置检查又是可选的,不能仅凭仓库干净就认定环境已经恢复。4
任务只修改仓库文件时,复用较容易;如果还会修改系统配置或安装软件,就需要恢复这些状态,否则应重新创建环境。
环境故障也应与 Agent 解题失败分开处理。前者可能需要重新准备环境并重试,不能直接按解题失败计算 reward。直接丢弃经常启动失败的镜像所对应的任务,会改变训练样本的组成。
45× 测量了什么
Google 报告的平均首次命令耗时 TTFC 从 44–85 秒降到 1.1–8.8 秒,最坏值从 450 秒降到 10 秒以内。测试使用 10 个节点的 gVisor 节点池,并开启 GKE Image Streaming;另一项测试的 Pod 创建数从 18,312 降到 5,869。标题中的 45× 对应最坏 TTFC,并不表示整轮训练加速 45 倍。5
整轮训练还包括 rollout 执行和模型更新,环境准备也可能与执行重叠。比较一次完整批次时,应记录从开始预热到收齐 800 条有效结果的总时间,以及期间占用的环境资源;若环境跨多轮保留,则分别记录首次准备成本和后续每轮耗时。
计时起点尤其容易影响比较。配套 load test 的复用路径从预热完成后计时,普通路径则包含预热,不能直接比较两者的总耗时。测试本身也只模拟任务执行,环境供给的改善能带来多少训练收益,仍取决于真实 rollout 中推理、测试与等待各占多少时间。6
按瓶颈选择编排方式
| 遇到的问题 | 可以尝试的方式 | 需要考虑的成本 |
|---|---|---|
| 资源充足,任务经常等待环境 | 全量预热,增加提前准备的环境数 | 空闲副本占资源,领取后还会补充 |
| 全部环境放不下,准备时间长 | 分批预热,或流水线预热 | 流水线多占一批资源,窗口过小会限制并发 |
| 创建和领取开销大,任务状态容易清理 | 复用环境 | 重置耗时,每组环境数限制组内并发 |
| 任务修改的状态难以恢复 | 每条 rollout 使用独立环境 | 需要反复创建和清理 |
预热、并发和复用的取舍,都与训练何时需要结果有关。这也留下一个值得继续思考的问题:除了镜像和并发数,训练框架是否还应告诉资源编排层,哪些结果需要一起收齐、哪些 rollout 正在阻塞下一次更新?这些信息可能帮助编排层判断,有限的资源优先分给谁,才能更早推进训练。
Footnotes
-
延迟和补充速率分别由
ReplenishDelay与MaxRefillRate控制,零值分别表示不延迟与不限速。WarmPool 控制器:refill 延迟与速率限制。 返回正文 -
Sandbox 复用与重置实现。检查会比较依赖清单、检查进程;安装目录中的文件变化等残留仍可能被遗漏。 返回正文
-
Google Cloud:GKE Agent Sandbox 的 RL 工程实践,2026 年 9 月 29 日。性能数字为发布方报告。 返回正文
-
性能报告与指标口径;Load test 实现。复用路径在
setup()完成后计时,普通策略的计时包含fleet.run();probe 返回 Pod 名称,并可用 sleep 模拟工作。 返回正文