大家好,这里是最佳拍档,我是大飞。最近 DeepSeek 把他们内部用来做 Agent 训练的沙盒平台 DSec 的完整技术论文给公开了,梁文锋也在作者之列。应该说,这个 DSec 不是一个简单的沙盒运行时,也不是一个套了层壳的容器服务,它是一个完整的生产级执行平台。从 DeepSeek V3.2 到 V4.1,所有的强化学习训练和评测的沙盒负载,全都是跑在 DSec 上的。相当于 DeepSeek 把他们 Agent 训练的地基掏出来给大家看了。
先给大家报几个核心数字,感受一下规模:一个生产单元大概 160 个 CPU 节点、3 万核 CPU、250TB 内存,托管 PB 级别的镜像数据;单日要服务大约 300 万个沙盒实例,峰值并发超过 38 万个;创建速率能到每秒 5000 个以上,单个训练任务最多可以一次性拉起 3.2 万个沙盒。你可能会问:为什么 Agent 训练需要这么多沙盒?传统大模型训练不就是 GPU 堆起来就完了吗?
这事儿就得从 Agent 训练跟传统大语言模型训练的本质区别说起了。传统大语言模型的强化学习,基本是围绕静态的输入输出和奖励信号来做的:你给模型一个提示词,它输出一个回答,打个奖励分,完事。整个过程不需要跟真实环境打交道。但是 Agent 训练完全是另一回事:Agent 要真的进到环境里去干活,比如查代码库、调工具、敲命令、看失败信息、改文件,甚至操作浏览器和桌面应用。
模型每走一步,环境状态就变了;下一步又得在新状态的基础上继续。这就意味着训练过程中,你得维护一大批所谓的工作现场,而且这些工作现场的要求还挺高:得足够接近真实机器,能装依赖、能跑软件、能改文件;一轮任务跑完了,还得能恢复干净状态,下一轮接着用。这就是沙盒要干的事。
论文里总结了 Agent 沙盒工作负载的七个特点,每个特点都直接决定了平台的设计方向。第一个是突发式创建:Rollout 和评测任务不是匀速创建沙盒的,而是一批一批地来。一个任务动辄就要创建几千个沙盒,尾部的任务能到几万个;最大的生产任务能一次性请求 3.2 万个沙盒。而且这些请求集中在很短的时间窗口里,因为训练或评测的批次得等所有环境都准备好了才能开始用。所以调度、创建、环境准备,每一步都得扛得住尖峰。
第二个是高密度运行的需求:Agent 在沙盒里执行任务的时候,大部分时间是在等模型生成下一步操作,CPU 其实很闲。论文里的数据显示:大约 90% 的容器和微虚拟机沙盒,平均 CPU 使用率不到申请量的 5%。这就意味着 CPU 超售是天然可行的:生产环境里,单个节点能稳定跑 3200 个容器或者 800 个微虚拟机。但超售不是没有代价的,内存效率和 CPU 干扰问题会变得越来越突出。
第三个是有状态而且生命周期长:Agent 会修改文件、安装依赖、启动服务,后面的操作都依赖这些累积的状态。一个沙盒可能要撑过很多轮交互:CPU 虽然闲下来了,但是内存和可写状态还占着。容器的中位生命周期是 17.4 分钟,微虚拟机是 15.5 分钟,p99 都超过 3 个小时。所以生命周期越长,内存常驻的成本就越高。
第四个是高度异构:Agent 能干的活太杂了,像刷题类的脚本执行、完整代码库的软件工程任务、安全攻防、电脑操作、安卓移动开发,甚至需要完整的系统环境。不同任务对 CPU、内存、依赖、系统功能、隔离强度的要求差别很大,不可能用一种沙盒打天下。
第五个是环境的多样性极高:训练和评测语料里的每个任务,可能都需要自己的代码库、依赖版本、服务、工具包、评测脚本。论文统计了一周的生产数据:容器后端涉及 11000 多个基础镜像、10 万多个工作区,还有 100 多个工具包。67.8% 的沙盒还要在基础镜像上叠工作区或工具包,而且镜像的 fanout 很低(也就是每个镜像被用的次数不多),靠节点本地缓存根本吃不下这么大的工作集。
第六个是执行不可信:Agent 可能会搞坏文件系统、耗尽资源、干扰系统组件,影响 rollout 的正常进行,甚至波及同节点的其他任务。第七个是可中断性:GPU 训练任务经常被抢占,但 rollout 可能还在跑;平台得能保住执行状态,支持高效恢复。这七个特点叠加在一起,就决定了 DSec 不能是一个简单的沙盒运行时,它必须是一个完整的弹性执行平台。
那具体该怎么实现的?咱们一层一层说。先讲最上层的用户接口和四种沙盒后端:用户通过 libdsec 这个 Python 客户端库来访问 DSec。你指定沙盒类型、镜像 ID、CPU 内存限制、生命周期策略、网络规则以及初始用户,就能创建一个沙盒;然后在里面执行 shell 命令或者工具调用,拿到输出和返回状态。四种后端虽然内部差别很大,但是用户看到的生命周期是统一的。哪四种呢?
最轻的是 FnCall:面向短任务、无状态的场景,比如刷题、代码编译、serverless 程序、GPU 算子评测。FnCall 跑在预先创建好的容器里,避免每次调用都重新配置。值得一提的是 GPU 版本的 FnCall:它有两种模式——共享模式下多个容器共用一块 GPU,适合轻量任务提高利用率;独占模式下一个容器占一块 GPU 实例,给性能敏感的任务用。具体实现上用了 NVIDIA 的 MIG 技术把 GPU 切成隔离的实例;编译这类工作交给 CPU 版的 FnCall 处理,还提前预热了一批 Python 进程池减少启动开销。
第二种是容器:这是软件工程和通用工具调用任务的主力后端,启动快、密度高,能跑绝大多数 Linux 软件栈。缺点是共享宿主机内核,安全敏感任务不太适合。第三种是 Firecracker 微虚拟机:隔离性更强,同时保持 Linux 兼容性,适合安全敏感任务、需要更强租户隔离的场景。代价是内存开销更高,启动也比容器慢。
第四种是完整虚拟机:覆盖需要完整商用操作系统环境的工作负载,比如通过 QEMU 跑安卓虚拟机,还有需要 GUI 或图形渲染的任务。这类资源开销最高,但是有些依赖特定 OS API、移动端运行时或者完整系统执行的任务,非它不可。这四种后端各有各的位置:生产环境里,容器和微虚拟机占了实例数和资源消耗的大头;FnCall 用少量常驻环境服务大量轻量调用;完整 VM 覆盖那些比较特殊但又很重要的场景。
接下来聊聊平台的整体架构:一个请求从 SDK 发出来到沙盒跑起来,中间要经过哪些组件?DSec 在架构上分两大块:集群级服务和沙盒运行时。集群级服务管接入和跨节点调度,包括 IAM、API Server、Placement Engine 和 Watcher。IAM 负责身份认证和权限管理:DSec 的 IAM 有个特点,支持多级项目嵌套(而不是常见的扁平或两级结构),授权的主体包括 Agent 和 harness,可以创建子项目从父项目分一部分配额出去,再授予子项目里的管理权限。人和 Agent 用的是同一套管理 API 和授权模型。
API Server 是整个沙盒集群的入口代理:训练和评测代码在受信任的 GPU 服务器上调用 libdsec,而沙盒里跑的是模型生成的不可信代码。两边网络是隔离的,API Server 是唯一允许的通信路径。它本身不存沙盒状态(沙盒 ID 里编码了所属的 edge 节点),所以任何一个 API Server 实例都能直接把请求转发到目标节点。入口层可以水平扩展。
Placement Engine 负责给每个新沙盒选宿主节点,分两步走:先过滤(只保留健康的、具备所需后端和硬件能力的节点),然后再排序(从合格节点里随机抽几个,选负载最低的那个)。这个算法叫 power-of-k-choices,能有效避免羊群效应,减少突发 RL 环境在 setup 和工具调用阶段的互相干扰。
Watcher 负责监控整个集群的健康状态和负载信息:Placement Engine 的决策准不准,全看 Watcher 收集的数据。Watcher 会定期探测每个 edge 和宿主机的健康状况,收集各种调度相关的状态数据。有意思的是:Placement Engine 和 Watcher 都不需要持久化状态——Placement Engine 不保存沙盒的执行状态;Watcher 重启后重新 poll 一遍 edge,就能重建集群视图。所以这两个组件加实例或者换实例都很方便,不需要走复杂的恢复流程。
然后是节点上的沙盒运行时:包括 Edge、Aether 和 Chronus,底层靠 3FS 存镜像数据。每个节点跑一个 Edge:处理 API Server 发过来的创建请求,接请求之前先查本地容量(不够就直接拒掉)。这个节点级的准入检查是对 Placement Engine 调度决策的补充,毕竟调度器用的是定期刷新的集群状态(可能有延迟)。创建的时候,Edge 负责配置存储、应用 eBPF 网络策略、启动运行时。
这里还补充一个细节:FnCall 和容器不是直接跑在裸机上的,而是跑在 QEMU/libvirt 虚拟机里面。这个虚拟机提供了一个隔离的内核和网络栈:在不可信容器和裸金属之间多加了一层安全边界。对于图形密集型的工作负载(比如电脑操作的 GUI 应用、浏览器、游戏、3D 渲染),还用到了宿主机 hypervisor 的半虚拟化 GPU 接口(比如 virtio-gpu)。
容器和虚拟机沙盒里还跑着 Aether 和 Chronus:Aether 是一个跨平台代理,跟 Edge 建立通信通道(容器用 Unix domain socket,虚拟机用 vsock)。每次操作,Aether 根据终端会话 ID 找到或创建对应的 Chronus 实例,然后把操作转发过去。Chronus 提供沙盒内部的 shell 会话抽象:每个实例代表一个独立的 shell 会话,支持命令执行、文件系统操作、HTTP 请求和流式 I/O。同一个沙盒里可以并发跑多个 Chronus 实例。
镜像数据存在 3FS 上,也就是 Fire-Flyer File System(DeepSeek 自己的分布式文件系统)。3FS 的硬件配置也挺猛的:每个存储服务器配 20 块 15TB 的 SSD、两块 400Gbps 的 RDMA 网卡。几十台存储服务器就能支撑几十万 CPU 核集群的按需镜像加载。
讲完架构,再说说几个核心的技术机制:这些才是论文里最有干货的部分。我们先讲可组合环境层:刚才说了,一个沙盒的环境可以拆成三部分——基础镜像提供 OS 级依赖、工作区带任务的代码库和特定依赖,还有一个或多个频繁更新的工具包(比如 DeepSeek Harness)。如果把这三部分打成一个完整镜像,维护成本会非常高:假设 M 个基础镜像、N 个工作区、K 个工具包,更新 m 个基础镜像就要重新构建 O(m·N) 个组合镜像;更新 k 个工具包就是 O(k·N) 的成本。
DSec 的思路是把这三层各自独立版本化,沙盒启动的时候再用 overlayfs 组合起来:基础镜像在最底下;工作区作为只读层插在上面;每个工具包再往上叠。overlayfs 本身就支持多个只读下层目录堆叠,在内核中呈现一个统一的目录树;冲突按优先级来。最上面还有个可写的上层目录,接住所有的运行时写入。这样一来:更新 m 个基础镜像就只需要重建 m 个基础层(工作区和工具包都不用动);更新 k 个工具包也只需要重建 k 个工具包层。维护成本直接从 O(m·N) 和 O(k·N) 降到了 O(m) 和 O(k)。
而且这些发布出去的环境层都是不可变的,所以用 EROFS 文件系统来存。EROFS 是专门为只读数据设计的:比 ext4、XFS 这些可写文件系统少了写相关的 bookkeeping,磁盘布局更紧凑;还支持数据压缩同时保持随机访问。跟 tar.gz 不一样:EROFS 可以只读需要的压缩块,不用把整个镜像都传下来解压完才能用。
微虚拟机那边也是类似的可组合层模型:基础镜像和工具包打成独立版本的 EROFS 镜像,作为只读块设备暴露给客户机。客户机里的根文件系统用 overlayfs:挂载的 EROFS 文件系统作为下层,可写磁盘上的 ext4 目录作为上层。
除了环境怎么组合,镜像怎么分发也是个大问题:这就是按需加载要解决的事。论文里有个数据很关键:沙盒运行时实际访问的数据,只占完整镜像的很小一部分。不同编程语言的容器镜像:运行时访问比例从 4.2% 到 13.3% 不等(JavaScript 镜像最低,只有 4.2%;Go 最高,也才 13.3%)。既然大部分数据根本用不上,全量拉取就特别浪费。
DSec 把镜像数据放 3FS 上,运行时按需读取:但是 3FS 有个特点——大块连续读写性能很好,小块随机 I/O 表现不行。所以设计上做了三个针对性的处理:第一,写入全部本地化——沙盒的写入操作都是不规则且不可控的(比如日志文件这种小而频繁的写),可写层直接放在节点本地磁盘上,完全避开 3FS 的小写 penalty。
第二,读取按需而且走大块:只读镜像数据只有在被访问的时候才从 3FS 取,而且 I/O 尽量凑成大块来做(利用 3FS 对大 I/O 请求的高吞吐能力)。第三,元数据尽量放本地:文件系统元数据经常是小读取。EROFS 有个多设备模式,可以把文件系统元数据和文件数据分开:DSec 把 EROFS 元数据下载到 worker 的本地磁盘,文件数据留在 3FS 上。这样元数据遍历和路径查找就不会产生远程 I/O。
效果怎么样呢?论文里做了实验:8192 个容器同时突发部署,按需 EROFS 加载跟全量本地缓存的性能差不多(35 分钟左右全部完成);用 Docker 冷拉取要超过 60 分钟(慢了 1.71 倍)。磁盘写入量方面:按需加载大概 700GB,Docker 冷拉取超过 1600GB(减少了 57%)。
还有一个对比是 EROFS 直接挂载跟传统 tar.gz 解压的对比:同样的评测工作区和工具包,用 tar.gz 的方式每个沙盒都要解压一遍(端到端任务完成时间要 79 分钟,磁盘写入总量是 EROFS 的 5.5 倍,峰值写入吞吐是 3.4 倍)。EROFS 直接挂载共享层不用解压,45 分钟就完成了(快了 1.76 倍)。
接下来讲高密度资源管理:这部分是内存和 CPU 的优化。先来说内存:微虚拟机的内存浪费有两个来源——一是镜像数据通过虚拟块设备读取,宿主机缓存一份、客户机又缓存一份(两边重复了);二是客户机里的空闲页面不会主动还给宿主机,申请的内存经常比实际需求大(客户机内部没什么内存压力,自然也就不会去回收不活跃的页面)。
这里 DSec 用了两套互补的机制来解决:一套是 virtio-pmem 加 DAX。它把文件访问直接映射到宿主机的页面,不需要再拷贝到客户机的 RAM 里。这样同节点上的多个微虚拟机可以共享同一份宿主机页缓存,效果是峰值内存使用量减少了 40.2%。但 virtio-pmem 也不是万能的:冷访问的时候需要同步缺页处理来建立映射;而且客户机得为整个 pmem 设备的地址范围分配 page 结构的元数据。如果按照 4KB 页面、64 字节 page 结构的话,元数据就要占 pmem 容量的 1/64(128GB 的 pmem 设备就需要 2GB 的客户机 RAM 来存元数据)。
另一套是 DAMON 加 virtio-balloon 的空闲页报告:DAMON 是 Linux 里一个基于采样的内存访问监控框架,定期采样页访问位找出冷的文件页面;通过内核回收路径把它们驱逐出去。回收之后,零散的页面回到伙伴分配器合并成高阶块(满足空闲页报告的要求)。virtio-balloon 驱动会再把这些空闲页主动报告给宿主机 hypervisor,宿主机通过 madvise 释放对应的内存。这套机制单独用的话:峰值内存变化不大,但是时间积分的内存消耗减少了 21.2%,而且 CPU 开销不明显。生产环境里:只读的 EROFS 基础镜像层和工具包层用 virtio-pmem 加 DAX;比较大的可写磁盘用 DAMON 加 balloon 空闲页回收。两者配合效果最好。
然后是 CPU 的 QoS 调度:超售的时候,怎么保证延迟敏感的任务不受尽力而为任务的干扰呢?DSec 把沙盒分成两类:延迟敏感的 LS 类和尽力而为的 BE 类。BE 类沙盒放在 SCHED_IDLE 调度优先级下:只要有 LS 任务要跑,它们就让出 CPU。但是光有调度优先级还不够——因为同一个物理核的两个 SMT 兄弟线程是共享执行资源的。LS 线程和 BE 线程跑在同一个核的两个线程上,还是会互相干扰。
所以 DSec 又加了一层:给 LS 沙盒开启 Linux 的 core scheduling。core scheduling 能保证不相关的 BE 工作不会跑在 LS 任务所在物理核的兄弟线程上。两层策略配合下来:在 50% 的 BE 负载下,SMT 导致的延迟膨胀从 45.2% 降到了 17.3%。剩下的性能下降主要来自高负载下 CPU 睿频降低、内存带宽争用和共享末级缓存争用(这些是 core scheduling 管不到的)。论文说剩下的干扰已经在可接受范围内了,所以就没再上内存带宽隔离。
接下来我们再来讲一个架构上的关键设计
Rollout跟GPU训练的分离:DeepSeek的GPU集群里,训练任务经常被抢占来提高利用率。在早期的训练流水线里,agent循环是跟模型服务和RL框架一起跑在可抢占的GPU训练Pod里的。GPU任务一被抢占,agent循环就没了,但是沙盒还在。恢复的时候得靠命令日志来重放,把训练框架恢复的rollout状态跟沙盒的执行状态对齐。
重放的时候,已经完成的操作复用记录的结果,避免非幂等命令产生重复副作用。从V4.1开始,DeepSeek把rollout执行移到了DSec上,拆成了两个组件:Agent sandbox跑harness和工具worker container管沙盒,并且提供rollout的控制层。
这俩都跑在可抢占GPU池外面。这样一来,rollout的生命周期就跟训练器的生命周期解耦了。worker container和agent sandbox一起持有完整的rollout状态,是唯一的真相来源。GPU任务被抢占之后重新连上来就能继续,不需要靠命令日志重放来重建执行状态。
RL框架里的rollout状态恢复逻辑直接就删掉了,跨组件协调少了很多,故障处理也更简单。但是这也带来了新的问题:GPU被抢占的时候,沙盒状态还得留在DSec上直到rollout完成。训练挂起的时候,一大堆空闲沙盒占着内存也不行。
所以RL框架会主动给被抢占任务关联的所有沙盒发暂停请求,让DSec回收内存,同时保留执行状态。后面再有请求过来,自动恢复就行。具体怎么暂停呢?容器的话,先用docker pause冻结进程树,然后打开swap,触发主动内存回收。
把匿名页和文件页的内存都收回来,但是容器的执行状态还在。恢复的时候,先给进程的内存映射应用MADV_WILLNEED启动异步预取,再用docker unpause继续执行。微虚拟机更直接:直接把内存和执行状态存成快照,然后杀掉Firecracker进程释放运行时内存。
恢复的时候启个新进程把快照恢复回去接着跑。还有个挺有意思的设计:DeepSeek让Agent自己构建环境。显然,手动构建Agent RL需要的大量环境是不现实的。所以DSec的做法是让Agent在同样的基础设施上交互式地构建环境,通过pack_diff功能,Agent随时可以对沙盒做增量磁盘快照。
后面就能恢复成新的沙盒。这种从检查点到恢复的接口,直接把一个交互会话变成了可复用的环境,不需要单独的镜像构建流水线。当然也有约束:DeepSeek内部有一套规则,打包出来的环境得遵守,避免对共享基础设施的运行时性能造成太大影响。
研究人员还做了个内部平台,对Agent构建的环境做质量检查,导出成标准格式供RL和评测任务使用。而且构建环境的Agent和运行时的Agent会使用不同的账号,打包之前还会把可写层里的构建残留数据清掉,防止参考答案被带进镜像里。
最后聊聊安全的事:这部分论文里讲的细节也很多。研究人员在生产环境里观察到两大类风险,一类是Agent通过非预期渠道拿到答案,让训练和评测结果的有效性打折扣;另一类是Agent破坏执行环境,影响当前任务或者同基础设施上的其他任务。
第一类,奖励黑客的手段五花八门:在沙盒内部Agent会搜平台管理的文件和通信渠道寻找残留的答案。比如,它们会构造特殊的RPC消息直接发给chronus的socket伪造用户请求;还会查chronus的日志看有没有泄露的答案。因为chronus有些操作是通过bash执行的,Agent还试过覆盖/bin/bash来绕过检查,或者给后续shell会话注入命令。
后来加了文件读写和socket访问控制,有个Agent甚至试图用XFS_IOC_SWAPEXT这个ioctl来绕过。这个系统调用可以交换两个文件的数据区映射,目的是让受保护的文件内容能通过另一个文件描述符访问到。结果把XFS元数据搞坏了,逼得文件系统直接shutdown。
你看,找答案的行为都能把基础设施搞崩。沙盒外面也不太平:Agent会扫端口和服务,找能访问的镜像源;还会用Go模块代理去拉GitHub上的代码装更新版本的包,找现成的功能实现。所以光靠最终结果检查根本没法可靠判断Agent是不是按预期方式解决了问题。
第二类,环境破坏很多时候甚至不是故意的:普通命令和执行失误也能搞出大问题。论文里举了几个例子,一个Agent从根目录递归跑grep遍历到/proc读了/proc/kpagecgroup触发了一个内核bug,直接把内核搞崩了。还有个漏洞利用任务攻击命令本来应该发给另一个目标虚拟机结果在Agent容器自己里面执行了把自己的内核搞挂了。
除了内核崩溃,无限制的命令输出也能吃掉大量存储:比如一个Agent调了yes命令chronus为了让用户能异步获取命令输出把stdout都记下来了,结果存了几十GB的数据。那怎么防呢?答案是没有哪个单一机制能防住所有的问题,DSec主要靠两手:AppArmor管文件和socket访问eBPF管网络访问。
AppArmor配置文件用来控制文件读写权限和socket访问,包括对chronus日志和Unix domain socket的访问。这些策略对Agent控制的进程都生效,哪怕它们在沙盒里以root身份跑。网络控制更精细:训练框架按域名或镜像服务来指定任务级的网络权限,比如允许PyPI、禁止NPM。
DSec通过每个沙盒的eBPF程序来执行对应的白名单,按IP地址、端口、协议过滤流量。任务在不同阶段有不同的连通性需求策略还能动态更新。不过论文也明确说了,这些措施只能解决一部分问题:对于触发内核bug这类破坏性行为还没有通用的防御手段。
本质上是个持续攻防的过程,模型在进化平台的加固也得跟着升级。最后聊聊怎么看DSec这篇论文:DSec展示的是一套面向大规模Agent训练的完整基础设施方案,从沙盒创建、环境复用到高密度资源管理、镜像分发再到跟RL框架的协同、暂停恢复、安全防护。
Agent训练正在长出一套独立于传统大模型训练的基础设施需求。这篇论文的价值在于,它不是提出了某个新的隔离机制或者新的文件系统,而是把这些已有技术整合起来针对Agent训练的工作负载特征做了系统性的工程设计和取舍。
四种后端覆盖不同隔离级别,可组合层解决环境维护成本,按需加载解决镜像分发,内存和CPU的超售优化解决密度问题,跟RL框架的协同设计解决训练抢占的问题。每一项单独拿出来可能都不是全新的东西但是组合在一起形成的这套方案是经过大规模生产验证的,对行业来说DSec的公开相当于给Agent训练的基础设施画了一张参考架构图。
后面其他做Agent训练的团队不管是自己搭还是用云服务大概都绕不开这些问题。而随着Agent任务越来越长、越来越复杂,执行环境的规模和安全挑战还会继续加大:怎么在更高密度下保持稳定、怎么应对越来越聪明的Agent的安全风险,这些都是接下来要继续探索的方向。感谢收看,我们下期再见。