
Agent 训练这件事真正跑过大规模任务的人都有一个共识模型本身的训练框架再强只要沙箱调度这一层没设计好整个集群的吞吐就会被拖垮。DeepSeek 的 DSec 这套东西核心要解决的就是当你要同时拉起成千上万个 Agent 实例、每个实例都要在隔离环境里执行代码、调用工具、读写文件的时候怎么让沙箱的创建、镜像的分发、以及任务中断后的状态恢复这三件事不成为瓶颈。我最近花了不少时间研究这套机制的设计思路也结合自己在 Agent 训练基础设施上踩过的坑把这里面的关键问题拆开聊一聊。1. 为什么 Agent 训练的沙箱层和传统训练完全不是一回事1.1 传统训练任务的资源模型 vs Agent 训练的资源模型如果你之前做的是常规的模型训练脑子里对资源的认知大概是这样的一批 GPU 被固定分配给一个训练任务数据从存储里流式读取整个任务的生命周期是确定的——启动、训练、保存 checkpoint、结束。资源是静态划分的任务之间互不干扰调度器只需要做一件事把任务塞进合适的机器里。Agent 训练完全不是这个逻辑。一个 Agent 在执行任务的过程中会不断地产生新的子任务它可能要执行一段 Python 代码来验证某个假设可能要调用一个外部工具去查询数据可能要在文件系统里创建临时文件来保存中间结果。每一个这样的动作都需要一个隔离的执行环境。而且这个环境的生命周期是不确定的——有的可能几秒钟就结束了有的可能要跑几分钟甚至更久。这就意味着沙箱不再是一个任务一个容器的粗粒度分配而是变成了一个动作一个沙箱的细粒度调度。你面对的不再是几十个训练任务而是每秒可能产生数百甚至数千个沙箱创建请求。这个量级的变化直接把调度问题从分配机器变成了管理高频短生命周期资源。1.2 沙箱创建延迟为什么是 Agent 训练的隐形杀手我做过一个粗略的测算。假设你的 Agent 训练任务里每个 Agent 每完成一个推理步骤平均需要创建一个沙箱一个中等规模的训练任务有 5000 个并发 Agent每个 Agent 每秒产生 0.5 个沙箱请求。那么你每秒需要处理的沙箱创建请求就是 2500 个。如果每个沙箱的创建延迟是 200 毫秒——这已经算是比较快的了——那么你需要的并发处理能力就是 2500 × 0.2 500 个并发创建槽位。但如果创建延迟涨到 2 秒呢你需要 5000 个并发槽位。这中间的差距不是线性的因为并发槽位本身会消耗内存和 CPU槽位越多单机能承载的沙箱密度就越低你就需要更多的机器成本直接翻倍。更关键的是Agent 的训练过程是串行的——Agent 必须等沙箱创建完成、代码执行完毕、结果返回之后才能进行下一步推理。沙箱创建的延迟直接叠加到了每个 Agent 的每一步推理上。如果一步推理本身只需要 100 毫秒但沙箱创建花了 2 秒那你的 GPU 利用率就会被拉到极低。GPU 在等沙箱不是在算东西。1.3 DSec 要解决的核心矛盾DSec 的设计目标说白了就是在三个约束之间找平衡隔离性要够强不同 Agent 之间不能互相干扰、创建速度要够快不能拖慢训练吞吐、资源开销要够低不能因为沙箱本身吃掉太多资源。这三个约束天然是矛盾的。你要强隔离就得用虚拟机或者至少是独立的内核命名空间创建就慢。你要创建快就得用轻量级容器甚至进程级隔离隔离性就打折扣。你要资源开销低就得做资源共享但资源共享又会影响隔离性。DSec 的思路不是在这三个维度上取一个折中点而是通过分层设计让不同的场景走不同的路径。对于需要强隔离的高风险操作走重量级沙箱对于轻量级的代码执行走快速路径。同时通过镜像预热和状态快照来把创建延迟压到最低。2. 沙箱调度的分层架构与调度策略2.1 三层沙箱模型的设计逻辑DSec 把沙箱分成了三个层级每一层的隔离强度、创建速度、资源开销都不一样。这个分层不是随便切的而是根据 Agent 实际操作的风险等级来划分的。第一层是进程级沙箱。这一层用的是 Linux 的 namespace 和 cgroup 做隔离本质上是在同一台机器上跑多个受限制的进程。创建速度极快通常在 10 毫秒以内资源开销也很低。适合什么场景呢适合那些纯计算的、不涉及外部网络调用、不涉及敏感文件操作的代码执行。比如 Agent 要算一个数学公式、要跑一段数据处理的逻辑这种场景用进程级沙箱就够了。第二层是容器级沙箱。这一层用的是容器运行时有独立的文件系统视图、独立的网络命名空间。创建速度在 100 到 500 毫秒之间资源开销中等。适合需要文件系统隔离、需要独立网络环境的场景。比如 Agent 要安装一个 Python 包、要访问一个特定的网络端点、要在隔离的文件系统里做实验。第三层是虚拟机级沙箱。这一层用的是轻量级虚拟机有独立的内核。创建速度在秒级资源开销最大。适合什么场景呢适合那些需要执行不可信代码、需要强安全隔离的场景。比如 Agent 要从外部获取一段代码来执行或者要模拟一个完整的操作系统环境。这个分层的核心价值在于它让调度器可以根据任务的实际需求选择最合适的沙箱类型而不是一刀切地用最重的方案。在实际运行中大部分 Agent 操作其实都是轻量级的走第一层就够了。只有少数高风险操作才需要走到第三层。2.2 调度器如何决定一个请求走哪条路径调度决策的逻辑不是简单的看任务类型而是综合了多个维度的信息。我梳理了一下大概有这么几个判断依据代码来源可信度如果代码是 Agent 自己生成的且经过了静态安全检查可以走轻量级路径。如果代码来自外部输入或者未经检查必须走重量级路径。资源需求预估根据代码的静态分析预估它需要多少 CPU、内存、磁盘。如果需求很小走进程级如果需求中等走容器级如果需求很大或者不确定走虚拟机级。网络访问需求如果代码需要访问网络至少要走容器级因为进程级沙箱通常不提供独立的网络命名空间。文件系统操作范围如果代码只需要读写临时目录进程级就够如果需要挂载特定的文件系统或者需要持久化存储走容器级。历史执行记录如果同一个 Agent 之前的操作都是安全的可以适当放宽隔离级别如果之前有过异常行为提升隔离级别。这个决策过程需要在毫秒级完成所以不能做太复杂的分析。DSec 的做法是维护一个轻量级的规则引擎把上述判断编码成一系列快速的布尔判断和查表操作。2.3 调度队列的分级与优先级管理沙箱创建请求不是一视同仁的。有的请求来自正在等待的 Agent延迟直接影响训练吞吐有的请求是预创建的可以慢慢来。DSec 把调度队列分成了几个优先级优先级请求类型延迟要求处理策略P0阻塞式请求Agent 正在等待 50ms专用资源池预留槽位P1半阻塞式Agent 即将需要 200ms共享资源池优先调度P2预创建请求提前准备 2s后台队列空闲时处理P3批量预创建为后续任务准备无硬性要求低优先级可被抢占P0 级别的请求是最关键的。这些请求对应的 Agent 已经暂停了推理在等沙箱就绪。如果这类请求被阻塞GPU 就在空转。DSec 为 P0 请求预留了专用的资源槽位确保它们不会被 P1、P2 的请求挤占。P1 请求是那些 Agent 即将需要但当前还在做其他事情的。比如 Agent 正在等待上一个沙箱的结果但调度器已经知道它下一步需要一个新的沙箱。这类请求可以稍微等一等但不能等太久。P2 和 P3 是预创建请求。调度器根据历史模式预测接下来可能需要什么样的沙箱提前创建好放在池子里。这样当 P0 请求到来时如果池子里有匹配的沙箱直接分配就行不需要从头创建。2.4 资源池的弹性伸缩与过载保护资源池的大小不是固定的。DSec 会根据实时的请求速率和队列深度动态调整资源池的规模。调整的逻辑大概是这样的当 P0 队列的平均等待时间超过阈值比如 30 毫秒时触发扩容。扩容的方式有两种一是增加单机上的沙箱密度在资源允许的情况下多跑几个沙箱二是增加参与调度的机器数量。前者更快但受限于单机资源后者更慢但上限更高。当 P0 队列持续为空且 P1 队列也很短时触发缩容。缩容不是直接杀掉沙箱而是把空闲的沙箱标记为可回收等它们自然结束或者被新的请求复用。过载保护是另一个关键机制。当请求速率超过系统处理能力时不能简单地让队列无限增长否则延迟会爆炸。DSec 的做法是分级降级首先降低 P2、P3 请求的处理速率把资源让给 P0、P1如果还不够就对 P1 请求做限流让部分 Agent 稍微等一等最后如果 P0 请求也开始积压就触发告警并拒绝新的训练任务提交。3. 镜像加载的加速机制与缓存策略3.1 镜像分层与按需加载Agent 训练用的镜像和普通的应用镜像不太一样。普通的应用镜像可能几百 MB 到几个 GB启动一次用很久。Agent 训练用的镜像往往需要包含多种运行时环境——Python、Node.js、各种工具链——体积可能更大但每个沙箱的生命周期可能只有几秒钟。如果每次创建沙箱都要完整加载一个几 GB 的镜像那创建延迟根本压不下来。DSec 的做法是把镜像做细粒度的分层然后按需加载。具体来说镜像被分成了几个层基础层操作系统的最小集合包含基本的 shell、核心工具。这一层是所有沙箱共享的只需要加载一次。运行时层Python、Node.js 等运行时环境。这一层根据沙箱类型按需加载比如纯 Python 执行的沙箱只需要 Python 运行时层。依赖层常用的第三方库和工具。这一层也是按需加载而且做了更细的拆分比如数据科学相关的库单独一层Web 相关的库单独一层。任务层特定任务需要的额外文件和数据。这一层是每个沙箱独有的必须单独加载。按需加载的核心是沙箱启动时只加载基础层和必要的运行时层依赖层和任务层在沙箱运行过程中按需加载。比如 Agent 的代码一开始只用了标准库那依赖层就不需要加载当代码 import 了一个第三方库时再触发对应层的加载。3.2 镜像预取与本地缓存按需加载虽然减少了初始加载量但引入了运行时的加载延迟。如果 Agent 的代码在运行过程中突然需要加载一个很大的依赖层那这个加载时间就会阻塞代码执行。DSec 的解决方案是镜像预取。调度器会根据历史执行记录预测某个 Agent 接下来可能需要哪些依赖层提前把这些层预取到本地缓存。预取的触发时机是在沙箱创建的同时后台异步进行不阻塞沙箱的启动。本地缓存的管理用的是 LRU 加优先级的策略。常用的层保留在缓存里不常用的层在缓存空间不足时被淘汰。但淘汰不是简单的 LRU而是考虑了层的加载成本和预测使用频率。加载成本高且预测使用频率高的层即使最近没被使用也会保留在缓存里。这里有一个实操中的经验缓存的大小不是越大越好。我见过有人把缓存配得很大结果发现缓存命中率并没有明显提升反而因为缓存管理本身的开销索引维护、淘汰决策拖慢了整体性能。比较合理的做法是缓存大小设置为常用层总大小的 1.5 到 2 倍留出一定的余量应对突发需求但不要过度。3.3 镜像分发网络的优化在大规模集群里镜像分发本身就是一个网络问题。如果所有节点都从中心存储拉取镜像层中心存储的带宽会成为瓶颈。DSec 用的是 P2P 加分层的分发策略。P2P 的部分节点之间可以互相分享已经下载的镜像层。一个新节点需要某个层时先检查本地有没有没有的话从邻近的节点拉取而不是直接去中心存储。这样可以大幅减少中心存储的压力。分层的部分不同优先级的层走不同的分发通道。基础层和常用的运行时层走高速通道保证快速可用不常用的依赖层走普通通道可以慢一点。还有一个细节是压缩。镜像层在传输时是压缩的但压缩算法和压缩级别会影响解压时间。DSec 用的是 zstd 压缩在压缩率和解压速度之间取了一个比较好的平衡。实测下来zstd 级别 3 的压缩率接近 gzip 级别 6但解压速度快了将近一倍。3.4 镜像版本管理与一致性保证Agent 训练中镜像的版本管理比普通应用更复杂。因为不同的 Agent 可能依赖不同版本的运行时和库而且这些版本可能在训练过程中动态变化。DSec 的做法是给每个镜像层打上内容哈希作为版本标识而不是用可变的标签。这样不同节点上的同一层可以确保是完全一致的。同时调度器维护一个版本映射表记录每个 Agent 任务需要哪些层的哪些版本。当某个层需要更新时不是直接替换而是创建一个新的层版本同时保留旧版本。新的沙箱创建请求使用新版本已经在运行的沙箱继续使用旧版本。这样可以避免版本更新对正在运行的任务造成影响。4. 状态恢复当沙箱意外终止时怎么办4.1 Agent 训练中状态丢失的代价Agent 训练和普通训练任务有一个很大的区别普通训练任务的 checkpoint 是定期保存的即使中断了从上一个 checkpoint 恢复就行损失的是几分钟的计算。但 Agent 训练中一个 Agent 的状态是高度动态的——它可能已经完成了十几步推理积累了大量的中间结果和上下文如果这时候沙箱挂了这些状态全部丢失Agent 需要从头开始。更糟糕的是Agent 的训练过程往往是有依赖的。Agent 在第 5 步做出的决策可能依赖于第 3 步的执行结果。如果第 3 步的状态丢了第 5 步的决策就失去了依据。这不是简单地重跑第 3 步就能解决的因为 Agent 的决策过程可能涉及随机性重跑的结果可能不一样。所以状态恢复在 Agent 训练中不是一个锦上添花的功能而是必须有的基础能力。4.2 状态快照的粒度与时机DSec 的状态恢复机制基于快照。但快照的粒度和时机需要仔细设计。如果快照太频繁开销太大如果快照太少恢复时丢失的状态太多。DSec 用的是增量快照加关键点全量快照的混合策略。增量快照记录的是自上次快照以来的状态变化开销小可以频繁做。关键点全量快照是在 Agent 完成一个重要步骤后做的完整状态保存开销大但恢复时更可靠。关键点的判定标准是什么呢根据我的理解大概有这么几个触发条件Agent 完成了一个完整的推理-执行循环且执行结果被验证为有效。Agent 的状态大小超过了某个阈值需要做一次整理。Agent 即将执行一个高风险操作需要先保存当前状态作为回滚点。距离上次全量快照已经过了一定的时间或步骤数。增量快照的频率可以很高比如每完成一个子步骤就做一次。因为增量快照只记录变化部分开销很小。但增量快照的问题是恢复时需要按顺序重放所有增量如果增量太多恢复时间会很长。所以需要定期做全量快照来截断增量链。4.3 快照存储的选型与性能考量快照存储的选择直接影响恢复速度。DSec 用的是分层存储最近的全量快照和增量快照存在本地的高速存储上比如 NVMe SSD保证快速恢复较旧的全量快照存在网络存储上节省本地空间。本地存储的快照有一个保留策略保留最近 N 个全量快照和对应的增量快照。N 的大小取决于本地存储容量和恢复时间要求。如果本地存储大可以多保留几个如果恢复时间要求高也需要多保留几个因为从网络存储恢复比从本地恢复慢得多。这里有一个实操中的坑快照的序列化和反序列化开销往往被低估。Agent 的状态可能包含复杂的嵌套数据结构、大量的字符串、甚至二进制数据。如果序列化格式选得不好序列化和反序列化的时间可能比快照传输的时间还长。DSec 用的是高效的二进制序列化格式比 JSON 快很多但代价是可读性差。调试的时候需要专门的工具来解析。4.4 恢复流程的完整链路当一个沙箱意外终止时恢复流程大概是这样的检测终止调度器通过心跳机制检测到沙箱失联标记该沙箱为异常终止。定位快照根据沙箱 ID 找到最近的全量快照和之后的增量快照。创建新沙箱按照原沙箱的配置创建一个新的沙箱加载相同的镜像层。恢复状态先加载全量快照然后按顺序重放增量快照重建 Agent 的状态。验证状态对恢复后的状态做一致性检查确保没有损坏。重新调度把恢复后的 Agent 重新加入训练队列从上次中断的步骤继续执行。这个流程中最耗时的通常是第 4 步。如果增量快照很多重放时间会很长。所以 DSec 在恢复时会做一个优化如果增量快照的数量超过阈值就直接从最近的全量快照恢复放弃中间的增量。代价是丢失一些状态但恢复速度更快。这个取舍需要根据具体场景来定。5. 实际部署中的调优经验与常见问题5.1 沙箱密度与资源超卖的平衡在单台机器上跑多少个沙箱这个数字需要仔细调。跑得太少资源利用率低跑得太多沙箱之间会争抢资源导致延迟抖动。我的经验是不要按照 CPU 核心数来算沙箱密度而是按照内存来算。因为 Agent 沙箱通常是 IO 密集和内存密集的CPU 反而不是瓶颈。一个沙箱如果分配 512MB 内存一台 64GB 内存的机器理论上可以跑 128 个沙箱但实际建议跑 80 到 100 个留出余量应对突发。CPU 的超卖可以更激进一些。因为大部分沙箱在大部分时间都在等待 IO 或者网络CPU 利用率其实不高。可以把 CPU 的分配比例设成 1:4 甚至 1:8也就是一个核心分配给 4 到 8 个沙箱。但要注意设置 CPU 的权重和上限防止某个沙箱突然跑一个 CPU 密集的任务把其他沙箱饿死。5.2 网络策略对沙箱调度的影响Agent 沙箱的网络需求差异很大。有的沙箱完全不需要网络有的需要访问特定的内部服务有的需要访问外部网络。网络策略的设计直接影响沙箱的调度灵活性。如果所有沙箱都走同一套网络策略那调度器就没法根据网络需求来做优化。比如一个不需要网络的沙箱和一个需要访问外部网络的沙箱如果走同样的网络配置那前者就浪费了网络资源后者可能因为网络配置的限制而变慢。DSec 的做法是给沙箱打上网络标签调度器根据标签把沙箱分配到合适的节点上。不需要网络的沙箱可以分配到网络带宽较小的节点需要外部网络访问的沙箱分配到有专门网络通道的节点。这样既提高了资源利用率又保证了网络需求的满足。5.3 监控指标的选择与告警阈值设定Agent 训练的监控和普通训练不一样。普通训练主要看 GPU 利用率、loss 曲线、吞吐量。Agent 训练除了这些还需要重点关注沙箱相关的指标沙箱创建延迟的 P50、P95、P99P50 反映平均水平P95 和 P99 反映长尾情况。长尾延迟对训练吞吐的影响往往比平均延迟更大。沙箱创建成功率失败的创建请求需要重试重试会进一步加剧资源紧张。沙箱平均生命周期如果生命周期突然变短可能说明有大量沙箱异常终止。镜像层缓存命中率命中率低说明缓存策略需要调整或者镜像分层需要优化。状态恢复频率和恢复耗时恢复频率高说明沙箱稳定性有问题恢复耗时长说明快照策略需要优化。告警阈值的设定需要根据实际运行情况来调。我的建议是先用一段时间的运行数据建立基线然后把告警阈值设在基线的 2 到 3 倍标准差之外。不要一开始就设很紧的阈值否则会被大量的误报淹没。5.4 几个我踩过的坑第一个坑是忽略了沙箱的清理延迟。沙箱终止后资源不是立刻释放的需要经过清理流程——删除文件系统、释放网络端口、回收内存。如果清理流程太慢会导致资源池的实际可用容量小于名义容量。我当时的做法是把清理流程异步化沙箱标记为终止后立即从资源池中移除清理在后台进行。但这样做的风险是如果清理失败资源就泄漏了。所以需要有一个兜底的清理机制定期扫描并清理那些长时间处于待清理状态的沙箱。第二个坑是镜像层版本更新导致的缓存失效。有一次我们更新了一个常用的依赖层结果所有节点上的缓存都失效了大量沙箱创建请求同时去拉取新版本把网络打满了。后来我们改成了灰度更新先在一小部分节点上更新观察一段时间没问题后再逐步扩大范围。同时在新版本层上预热缓存确保大部分节点已经缓存了新版本再全面切换。第三个坑是状态快照的序列化格式不兼容。有一次我们升级了 Agent 框架改变了状态的数据结构但旧的快照还是用旧格式序列化的。恢复的时候反序列化失败导致一批 Agent 无法恢复。后来我们在快照里加了版本号恢复时先检查版本号不兼容的话就走降级恢复流程——从更早的全量快照恢复或者直接重新开始。6. 从 DSec 的设计看 Agent 训练基础设施的演进方向6.1 沙箱调度正在从通用走向专用早期的 Agent 训练基础设施大多是拿通用的容器调度系统改的比如在 Kubernetes 上跑 Agent 沙箱。但通用调度系统的设计目标是长期运行的服务而不是高频短生命周期的沙箱。它们的调度决策周期是秒级甚至分钟级而 Agent 训练需要毫秒级的调度决策。DSec 代表了一种趋势为 Agent 训练专门设计调度系统。这种专用系统不需要支持所有的容器编排功能只需要把沙箱创建、镜像加载、状态恢复这三件事做到极致。功能少了但性能上去了。6.2 镜像加载的瓶颈正在从网络转向存储随着 RDMA 网络和高速缓存的普及镜像加载的网络瓶颈正在缓解。现在的瓶颈更多地在存储端——如何快速地从本地存储中读取镜像层如何高效地管理缓存。NVMe SSD 的随机读取性能比传统的 SATA SSD 高了一个数量级这为镜像加载的进一步加速提供了空间。6.3 状态恢复正在从事后补救走向事前预防DSec 的状态恢复机制虽然强大但恢复本身是有代价的——时间、计算资源、可能的状态丢失。更好的做法是预防沙箱异常终止而不是等终止了再恢复。这需要更完善的健康检查机制、更智能的资源隔离、更主动的故障预测。我在实际使用中的一个体会是状态恢复机制的存在会让人放松对沙箱稳定性的要求。反正挂了能恢复就不那么在意沙箱为什么挂了。但实际上频繁的恢复会严重拖慢训练进度而且恢复过程中的状态丢失可能影响训练效果。所以状态恢复应该是最后一道防线而不是常规操作。6.4 Agent 训练对基础设施提出的新要求Agent 训练和传统训练还有一个根本区别传统训练的任务是同质的所有 GPU 做同样的计算Agent 训练的任务是异质的不同的 Agent 可能在做完全不同的事情需要不同的运行时环境、不同的工具、不同的网络策略。这对基础设施提出了更高的要求调度器需要理解任务的语义而不仅仅是资源需求镜像系统需要支持更灵活的组合和按需加载状态管理需要处理更复杂的数据结构和依赖关系。DSec 在这些方面做了不少探索但这个问题远没有完全解决。我在实际部署中感受最深的一点是Agent 训练的基础设施优化没有银弹。你不能指望通过某一个技术点的突破来解决所有问题。沙箱调度、镜像加载、状态恢复这三件事是相互关联的——沙箱创建快了镜像加载的压力就大了镜像加载快了状态恢复的窗口就小了。需要整体考虑在各个环节之间找到平衡。而且这个平衡点会随着训练规模、任务类型、硬件配置的变化而变化需要持续调优。