论文: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 任务。
四、架构:一条能水平扩展的请求链
几个设计决定值得单独拎出来:
- apiserver 不存 per-sandbox 状态。sandbox ID 里编码了它属于哪个 edge,所以任何 apiserver 实例都能解析并直接转发 → ingress 层想加多少加多少。
- Placement 和 Watcher 都不需要持久化状态。重启了重新轮询重建视图就行,实例可随意增删。论文还会定期做 cluster reset,验证基础设施即代码能从零重建所有集群级服务——不依赖任何累积的手动状态。
- Placement 用 power-of-k-choices:随机抽 k 个节点选最闲的,避免羊群效应。每个 placement 实例还会叠加「还没反映到 watcher 快照里的近期 placement」,不用跨实例协调就能计入在途负载。
- edge 有最终准入权。placement 说行不算,节点自己再判断一次容量,不够就拒绝让上层换节点。
- 控制面高可用用 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 是纯浪费。
于是有了三条设计原则——这三条我觉得是全文最值得抄的通用经验:
- 写留本地:沙箱写入又碎又乱(日志文件之类),可写层放节点本地盘,完全避开分布式存储的小写惩罚
- 读按需且批量:只读镜像数据用到才拉,且批量拉(3FS 大 I/O 吞吐高、小随机 I/O 很烂)
- 元数据尽量本地:文件系统元数据都是小读取,把 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 的负载特征没有同等数据。
- 安全是不完备的,论文自己承认了。
发表回复