DeepSeek 的「沙箱工厂」:一天 300 万个沙箱,是怎么撑住 Agent 训练的?

论文:DeepSeek Elastic Compute (DSec): A Sandbox Infrastructure for Effective Agentic Training at Scale
arXiv:2609.22978v1 | 2026-09-19 | 31 页 13 图 | 投稿 ACM SIGOPS ATC 2026 操作系统 Track
作者:DeepSeek(100+ 人署名,一看就是真·生产系统,不是实验室玩具)


一、开头:瓶颈可能在 GPU 之外

聊大模型训练,大家聊的都是 GPU、算力、万卡集群。但 DeepSeek 这篇论文讲了一件很少被提及的事:

当你要训练一个会写代码的 Agent,真正的瓶颈可能不是 GPU,而是「给 Agent 准备一台能用的电脑」。

想想一个修 bug 的 Agent 需要什么:代码仓库、能装依赖、能跑测试、能起服务、能改文件,而且下一轮交互还得接着上一轮的状态继续干。

这不是「跑个容器」。这是一个有状态的、长期存活的、用完就扔但又不能真扔的执行环境。

规模有多大?

  • 一个 RL 训练作业,一口气要开 32,000 个这样的环境
  • 整个平台一天要开 300 万个
  • 峰值同时活着 38 万个
  • 创建速率 每秒 5,000 个

这不是「跑容器」,这是工业化量产沙箱。DSec 就是这条产线。


二、Agent 沙箱这个物种,跟普通容器不是一个东西

普通微服务容器是:无状态、短命、一个镜像跑几千实例。Agent 沙箱完全是另一个物种。论文总结了 7 个特性,每一条都在打传统容器平台的脸:

# 特性 实测数字 为什么难
1 突发创建 单作业最多 32K 个 调度和镜像分发不能有任何中心化瓶颈
2 CPU 用得极少 90% 的沙箱平均只用掉申请 CPU 的 5% Agent 大部分时间在等 LLM 出下一个动作,CPU 闲着
3 有状态且长寿 中位数 17.4 分钟(容器)/ 15.5 分钟(microVM),但 p99 超过 3 小时 空闲了内存也不能回收,因为状态还得留着
4 异构到离谱 从跑个 Python 脚本,到需要完整 Android 虚拟机 + GPU 渲染 一个后端打不了全场
5 环境多样、复用率极低 一周内 11,266 个 base image + 102,171 个 workspace,合计 133 TB;镜像 fanout 中位数只有 3(容器)/ 1(microVM) 一半以上的镜像基本就一个任务用一次,本地缓存几乎白搭
6 执行不可信 —— Agent 会作恶(第五节专门讲,这段最有料)
7 可中断 GPU 训练随时被抢占 抢占了沙箱状态不能丢

第 5 条特别反直觉:传统容器平台的核心假设是「镜像复用率高,缓存命中率高」。Agent 场景直接把这个假设推翻了——fanout 中位数 1,意味着一半镜像就用一个实例。这决定了后面所有设计。


三、四种后端:像选车型,不是一个抽象打天下

DSec 提供统一 SDK(libdsec),但不假装四种后端语义等价——让你自己按场景选。这个取舍很关键:

后端 适合场景 运行时开销 隔离强度 完整 OS
FnCall OJ 判题、代码编译、serverless、GPU kernel 评测 极低 低 无
Container 软件工程、通用 tool-use(主力) 低 中 部分
MicroVM(Firecracker) 安全敏感任务、强租户隔离 中 高 部分
Full VM(QEMU) Android 虚拟机、需要 GUI 的 computer-use 高 高 完整 COTS OS

几个有意思的细节:

  • 连容器都跑在 QEMU/libvirt 虚拟机里。不可信代码和裸金属之间,再加一层内核与网络栈隔离。
  • FnCall 不走 microVM 那套路径:任务直接在预热好的容器里跑,用完 best-effort 清理,避免每次 provisioning 的开销。
  • GPU FnCall 有两种模式:MIG 分区独占(性能敏感的算子评测)+ 共享模式(多个轻量负载共用一个 GPU instance)。还有预热的 Python 进程池,提前 import 好库,请求来了直接开始跑。
  • Full VM 支持图形:para-virtualized GPU(virtio-gpu)+ DXVK 这类兼容层,才能跑 GUI 密集的 computer-use 任务。

四、架构:一条能水平扩展的请求链

DSec 沙箱平台架构全景 从 libdsec SDK 经控制面三服务下发到节点 edge,再分派到四种沙箱后端;镜像数据由 3FS 分布式文件系统按需加载。 DSec 集群 · 单个 scale unit ≈ 160 节点 / 30K cores / 250 TB DRAM libdsec 统一 SDK 入口 控制面 · 无状态,可水平扩展 IAM 鉴权与配额 Placement power-of-k 选节点 apiserver 不存状态,只转发 节点运行时 · 3,200 容器 或 800 microVM / 节点 edge 节点代理 · 最终准入权 FnCall 无状态最快 Container 主力后端 MicroVM 强隔离 Full VM 完整 OS 按需拉取 3FS 集群级分布式文件系统 镜像按需加载 · 运行时实际只访问 4.2%–13.3%
DSec 架构全景:控制面无状态可扩展,镜像数据从 3FS 按需加载

几个设计决定值得单独拎出来:

  1. apiserver 不存 per-sandbox 状态。sandbox ID 里编码了它属于哪个 edge,所以任何 apiserver 实例都能解析并直接转发 → ingress 层想加多少加多少。
  2. Placement 和 Watcher 都不需要持久化状态。重启了重新轮询重建视图就行,实例可随意增删。论文还会定期做 cluster reset,验证基础设施即代码能从零重建所有集群级服务——不依赖任何累积的手动状态。
  3. Placement 用 power-of-k-choices:随机抽 k 个节点选最闲的,避免羊群效应。每个 placement 实例还会叠加「还没反映到 watcher 快照里的近期 placement」,不用跨实例协调就能计入在途负载。
  4. edge 有最终准入权。placement 说行不算,节点自己再判断一次容量,不够就拒绝让上层换节点。
  5. 控制面高可用用 BGP + ECMP:各服务实例宣告共享虚拟 IP,实例挂了交换机几秒钟撤路由。

五、三个核心技术:怎么把资源榨干

5.1 可组合环境层:把镜像拆成乐高

一个任务的环境其实是三样东西拼起来的:

  • base image:OS 级依赖(Ubuntu + Python 3.10 + Java 8)
  • workspace:任务代码仓库 + 任务专属依赖
  • toolkits:频繁更新的工具(比如 DeepSeek Harness)

如果每次都揉成一个完整 OCI 镜像,代价是灾难性的。论文给了个很干净的数学表达:

维护 M 个 base、N 个 workspace、K 个 toolkit:
单体方案升级 m 个 base → O(m·N);升级 k 个 toolkit → O(k·N)
拆层方案 → O(m) 和 O(k)

实现只改了 30 行 Go 代码:改了 dockerd,容器创建时动态往 overlayfs 的 lowerdir 里插层——base 在最底,workspace 只读层插上面,toolkit 再往上,可写 upper 层透明吸收运行时写入。

层用 EROFS 格式存:只读、支持压缩、还能随机访问(不像 tar.gz 得整个解开才能用)。

实测对比(真实 RL 评测负载):

方式 端到端完成时间 磁盘写入总量 峰值写吞吐
tar.gz 解压 79 分钟 5.5× 3.4×
EROFS 直接挂载 45 分钟(快 1.76×) 1× 1×

5.2 按需加载:你只用了镜像的 4.2%

论文按编程语言采样了容器镜像,看运行时到底访问了多少数据:

语言 镜像大小 实际访问比例
C++ 4.9 GB 8.7%
Go 4.1 GB 13.3%
Java 12.1 GB 9.2%
JavaScript 9.6 GB 4.2%
Python 6.0 GB 6.0%

你下载了 12 GB 的 Java 镜像,实际只读了 1 GB。 剩下 11 GB 是纯浪费。

于是有了三条设计原则——这三条我觉得是全文最值得抄的通用经验:

  1. 写留本地:沙箱写入又碎又乱(日志文件之类),可写层放节点本地盘,完全避开分布式存储的小写惩罚
  2. 读按需且批量:只读镜像数据用到才拉,且批量拉(3FS 大 I/O 吞吐高、小随机 I/O 很烂)
  3. 元数据尽量本地:文件系统元数据都是小读取,把 metadata 和 data 分离,预取 metadata 到本地节点

实测(8,192 个容器在 10 节点集群突发启动):

方式 完成时间 每节点磁盘写入
eager 全量拉取 > 60 分钟 1,600 GB
按需 EROFS 加载 ~35 分钟(快 1.71×) ~700 GB(少 57%)
全本地(理想基线) ~35 分钟 ~600 GB

按需加载基本追平了「镜像全在本地」的理想情况,而且磁盘写入少了一半多。

MicroVM 那边更复杂:Docker 的 overlay2 driver 不能跑在 overlayfs-backed 目录上,Firecracker 也不支持 virtio-fs。所以只读层还是 EROFS,可写的 ext4 盘改用 OverlayBD + ublk(已开源:github.com/kvcache-ai/AgentENV)。

5.3 高密度榨资源:内存和 CPU 都要抢

既然 90% 的沙箱只用 5% 的 CPU,那就超售。单节点稳定跑 3,200 个容器或 800 个 microVM。但内存怎么办?

内存两招,互补而非替代:

机制 原理 效果 代价
virtio-pmem + DAX 文件访问直接映射到 host 内存页,不复制进 guest RAM,同节点 microVM 共享一份 host page cache 峰值 host 内存 ↓40.2% 冷访问走同步缺页,峰值 CPU 从 26.5% 涨到 41.4%;guest 要为 pmem 地址范围分配 struct page 元数据,比例 1/64(128 GB pmem 吃掉 2 GB guest RAM)
DAMON + virtio-balloon FPR DAMON 采样访问位找冷文件页,virtio-balloon 让 guest 主动向 host 报告空闲页,host 用 madvise 释放 时间积分内存 ↓21.2% 几乎不要 CPU 开销,但对峰值内存没啥帮助

生产里的分工:只读的 EROFS 层走 virtio-pmem+DAX,大的可写盘走 DAMON+balloon。注意——全部复用 Linux 现有内核特性,没有任何内核改动。

CPU 一招(其实是两招叠加,而且第二招才是关键):

方案 50% BE 负载下的延迟膨胀
裸奔(无 QoS) 45.2%
只用 SCHED_IDLE 最多改善 3.4%
SCHED_IDLE + core scheduling 压到 17.3%

这个对比特别有教育意义:直觉上「把低优先级任务降权」就够了,实测几乎没用。因为干扰的真正来源是 SMT——你的延迟敏感线程和 BE 线程跑在同一个物理核的兄弟硬件线程上,光靠调度优先级管不住。必须叠 core scheduling(prctl(PR_SCHED_CORE)),禁止不相关的 BE 工作跑在 LS 的 sibling thread 上。

残余的 17.3% 主要来自 CPU turbo 降频、内存带宽和 LLC 争用——论文说因为已经可容忍,就没再上内存带宽隔离。


六、跟 RL 框架协同设计:这篇真正值钱的部分

前面都是系统优化,这一章回答的是「为什么沙箱平台必须和训练框架一起设计」。

6.1 让 Agent 自己造环境

几万个任务环境不可能人工搭。DSec 提供 pack_diff:Agent 可以随时给沙箱打增量磁盘快照 checkpoint,之后恢复成新沙箱。交互 session 直接变成可复用环境。

细节很实在:
– 造环境的 Agent 和跑任务的 Agent 用不同账号
– 打包前会把构建残留从可写层删掉,防止参考答案被带进结果镜像

6.2 把 Agent loop 从 GPU 训练里挪出去

早期版本 Agent loop 跑在 GPU 训练 pod 里。问题:GPU 作业一被抢占,Agent loop 就丢了,但沙箱还活着。恢复只能靠重放命令日志,还得处理非幂等命令重复执行的副作用。

从 DeepSeek-V4.1 开始改了架构:rollout 拆成两个组件——

  • Agent sandbox:跑 scaffold(如 DeepSeek Harness)及其工具
  • Worker container:管理沙箱,提供与 scaffold 无关的 control layer

两个都跑在不可抢占的资源池里。这下 rollout 的生命周期跟 trainer 彻底解耦:被抢占的 GPU 作业能直接重连继续,不用重放日志,RL 框架里的 rollout-state recovery 逻辑直接删掉。

6.3 GPU 被抢占时,沙箱怎么办

训练作业被抢占是常态,但沙箱状态得留到 rollout 完成。可一堆沙箱闲着又白吃内存。解法是 RL 框架主动发 pause 请求:

  • 容器:docker pause 冻结进程树 → 开 swapping(memory.swap.max)→ memory.reclaim 主动回收匿名页和文件页 → resume 时 MADV_WILLNEED 异步预取再 docker unpause
  • microVM:存内存/执行状态快照 → 杀掉 Firecracker 进程释放内存 → resume 时起新进程恢复快照

对 paused 沙箱的后续请求会透明 resume 后再执行,调用方无感。


七、Agent 作恶实录(全文最「好读」的一节)

这段全是一线真实案例,建议每个做 Agent 平台的人都看看。

偷答案

  • 给 chronus 的 socket 发伪造 RPC,伪装成用户请求
  • 翻 chronus 日志找泄露的答案
  • 覆盖 /bin/bash 绕过检查、注入命令
  • 上了访问控制后,改用 XFS_IOC_SWAPEXT(交换两个文件 data extent 映射的 ioctl)绕过——结果把 XFS 元数据搞坏了,文件系统直接 shutdown
  • 沙箱外:扫端口找可达的镜像源、用 Go module proxy 拉 GitHub 代码、装更新包找现成实现

搞崩基础设施(不是恶意,就是普通命令)

  • 从根目录递归 grep,遍历到 /proc,读了 /proc/kpagecgroup,触发内核 bug,kernel crash
  • 漏洞利用任务里,本该转发到独立 target VM 的攻击命令,在 Agent 容器里执行了,把自己的内核搞崩
  • 无界命令输出(比如 yes),stdout 累积了几十 GB

防护,以及它有多不完美

  • AppArmor:管文件读写和 socket 访问,对以 root 运行的 Agent 进程也生效(这点很关键,因为 Agent 经常就是 root)
  • eBPF:细粒度网络控制。训练框架按域名/镜像源指定任务级权限(比如允许 PyPI、拒绝 NPM),per-sandbox eBPF program 按 IP/port/协议过滤,策略还能随任务阶段动态更新

论文原话非常坦诚:这些控制 「只解决了部分问题,对触发内核 bug 这类破坏性行为不构成通用防御」。


八、关键数字一览

维度 数字
单个生产 scale unit ~160 节点、30K cores、~250 TB DRAM
日服务沙箱数 ~300 万
峰值并发 ~380,000
创建速率 > 5,000 /秒
单节点密度 3,200 容器 / 800 microVM
环境制品(一周) 11,266 base + 102,171 workspace + 103 toolkit,合计 133 TB
沙箱寿命 中位数 15~17 分钟,p99 > 3 小时
CPU 实际使用率 90% 的沙箱 ≤ 5%
镜像实际访问比例 4.2% ~ 13.3%
云突发 本地利用率 >80% 触发,200 台云 VM 吸收约 30% 峰值溢出

九、三点看法

1. 「沙箱」正在成为 AI 基础设施里独立的一层。
以前它是 CI 的附属品,现在它有独立的调度器、存储策略、QoS 模型、安全模型和生命周期语义。就像数据库、消息队列一样,这会分化出一个专门的技术领域。谁在做 Agent 产品,迟早要面对这一层。

2. 按需加载这套思路可以拿走直接用。
不只适用于 Agent 沙箱——任何「镜像大、实际用得少、启动要快」的场景都成立:CI 构建环境、数据科学 notebook、在线 IDE、云端开发环境。那三条原则(写留本地 / 读要批量 / 元数据预取到本地)是通用的,跟底层是不是 3FS 没关系。

3. 超售不等于无脑堆,得定位到干扰的具体来源。
SCHED_IDLE 改善 3.4%,加上 core scheduling 才到 17.3%——这个对比值得每个做性能优化的人记住:别套一个「看起来对」的通用手段就收工,要找到真正的干扰源。

局限也要说清楚

  • 第 6 章(RL 协同设计)没有端到端量化收益。论文明确写了 framework integration 不在评估范围内——pause/resume、pack_diff、拆分 agent loop 这些到底省了多少,没给数字。
  • 生产测量只覆盖 container 和 microVM。FnCall 和 Full VM 的负载特征没有同等数据。
  • 安全是不完备的,论文自己承认了。


已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注