02 - AgentENV 拆解
前置:01 - 训练环境为什么不是运行时沙箱 里的三处约束。读过 执行沙箱 · 03 - E2B 与快照机制 会更快看懂快照那一节。
本篇 回答:一个能支撑 150 万镜像、冷启动 50 毫秒的环境平台,内部是怎么搭的?每一处设计分别解决 01 篇提出的哪个问题?
本篇会用到的词:
| 词 | 意思 |
|---|---|
| Firecracker | AWS 开源的 microVM 管理器(★36,156,Apache-2.0),去掉了传统虚拟机绝大多数设备模拟,所以启动快、内存开销小 |
| overlaybd | containerd 旗下的分层块设备格式(★396,Apache-2.0)。和 OverlayFS 的区别是:它分层的是块设备而不是文件系统,所以可以直接挂给虚拟机当磁盘 |
| LSMT | Log Structured Merge Tree,overlaybd 的镜像格式。底下是若干只读层,最上面一个可写层,写操作一律追加到可写层 |
| ublk | Linux 内核 6.8+ 提供的机制,允许在用户态实现一个块设备(表现为 /dev/ublkbN)。它是 overlaybd 能变成虚拟机磁盘的那座桥 |
| io_uring | Linux 的异步 IO 接口,一次系统调用可以提交一批 IO 请求,避免了传统同步 IO 的频繁陷入内核 |
| 写时复制(COW) | 多个副本共享同一份底层数据,谁要改就先复制自己那一份再改。fork 出成千上万个环境却不会成倍占存储,靠的就是它 |
| 内存气球(ballooning) | 让虚拟机把暂时用不到的内存还给宿主机的机制。它是「空闲环境很便宜」这句话的实现手段 |
| 内存超分 | 分配给所有虚拟机的内存总量超过宿主机物理内存。只有当大部分虚拟机同时空闲时才成立 |
一、它是什么
kvcache-ai/AgentENV(简称 AENV),Rust 写的,MIT,2026-07-23 建仓,实测当天 ★3,240。开发方是 kvcache-ai —— 也就是做 KTransformers 和 Mooncake 的那个团队。
它的定位一句话:大规模跑 Agent 环境的分布式平台。官方 README 里写明了它的第一个生产用户:
AgentENV (AENV) is a platform for running agent environments at scale, powering agentic RL training for Kimi K3.
Kimi K3 是 Moonshot 2026 年 7 月发布的开放权重模型(2.8T 总参数、104B 激活、1M 上下文)。也就是说,这不是一个演示项目,是一个前沿模型训练时真在用的环境层。
官方给出的四条核心能力,正好对上 01 篇那三处约束:
| 官方说法 | 回应的是哪条约束 |
|---|---|
| 按需加载 OCI 镜像,生产中扩到 150 万镜像,本地盘只当有界缓存 | 镜像分发:几百种镜像 × 几百台机器没法预热 |
| 快照支撑的环境,启动或恢复 < 50 ms,暂停 < 100 ms | 生命周期短,起停开销成主要成本 |
| 原生快照与 fork,一个运行中的环境可分叉成多个独立沙箱 | reset 语义缺失 |
| ublk 高性能 IO + 共享宿主页缓存,内存气球实现生产中 9.6 倍内存超分 | 空闲成本:环境闲着但内存占着 |
上面的 150 万镜像与 9.6 倍超分两个数字,README 标注来源为 Kimi K3 技术报告与生产实践,本文按官方声明转述,未自行复现。
二、单节点:一次请求怎么走
沙箱内部还跑着一个 envd 守护进程,负责执行命令、流式返回输出、上报健康状态 —— 这和 执行沙箱 04 篇里讲的「宿主不直接伸进沙箱,只通过协议说话」是同一个结构。
三、存储:为什么分层的是块设备而不是文件系统
这是整套设计里最需要想明白的一处。
容器用的是 OverlayFS,分层发生在文件系统这一层:底下若干只读层,上面一个可写层,合并成一个目录树。但虚拟机要的不是目录树,是一块裸磁盘。你不能把 OverlayFS 直接塞给 Firecracker 当 /dev/vda。
overlaybd 的做法是把同一套「分层 + 写时复制」的思路下沉到块设备:
3.1 LSMT 格式与读路径
每一层是一个文件,内部结构是「头尾信息 + 一张段映射表」。段映射表的每一项 16 字节,位打包存着这一段在虚拟磁盘上的偏移、长度、在物理文件里的位置。
读一个块的时候,自上而下逐层查这张表,第一个命 中的层提供数据;上层没映射到的范围落到下层。写操作一律追加到最上面那个可写层。
3.2 ublk:把镜像变成虚拟机能挂的磁盘
overlaybd 是用户态的一堆文件,Firecracker 要的是 /dev/xxx。中间这一步靠 ublk:
应用在 VM 里读 /dev/vda
↓ Firecracker 转成 virtio 请求
↓ 宿主上表现为对 /dev/ublkbN 的块 IO
↓ 内核 ublk 驱动把请求丢进一块 mmap 的描述符数组
↓ 用户态的 uvm-ublk-daemon 从 io_uring 取出请求
↓ 交给 overlaybd 按 3.1 的路径查层、读数据、返回
几个值得注意的工程选择:
- 每队列一个工作线程,各自持有线程本地的 io_uring 实例,用 MPSC 通道提交,避免跨线程加锁
- 内核 6.8+ 上用
AutoRegBuffer走稀疏缓冲表实现零拷贝,老内核回落到传统分配 - ublk 设备统一由一个独立守护进程
uvm-ublk-daemon管理,节点服务只通过 Unix socket 指挥它 —— 把 io_uring 控制权和生命周期编排分在两个进程里
这也解释了 README 里那条硬性前提:Linux 内核 6.8 以上。
四、快照与 fork:reset 是怎么变便宜的
01 篇 2.3 节说过,训练环境要的是「每条 rollout 从同一起点开始」。AgentENV 把快照做成了系统里的基础原语,官方文档的说法是:其他一切都建在它上面。
- 模板就是快照。构建一个模板 = 提交一个快照,模板 ID 只是指向它的别名
- 启动沙箱就是从快照恢复
- 运行中的沙箱可以再产出新快照,用于后续复用或分叉
暂停一个沙箱时发生的事情,比「把内存 dump 到文件」精细得多:
① Firecracker 生成一个仅含状态的差分快照
② AgentENV 向 Firecracker 查询「脏页 / 已存在页」的范围
—— 只读这些范围,没碰过的内存不用存
③ 用 process_vm_readv 直接把选中的内存读出来
④ 直接写成一个 overlaybd 内存层,堆叠在上一次快照的层之上
关键在第 ②③ 步:内存快照也是分层的、增量的。一个跑久了的沙箱反复暂停恢复,每次只落新脏的那部分,所以官方能声称「即使磁盘改动很大,快照也在 100 毫秒内完成」。
磁盘侧的暂停路径叫 create_snapshot_and_restack():把当前可写层封存成一个新的只读层,再在上面开一个空的可写层。原来的数据一个字节都不用搬。
五、内存快照的共享:一处很漂亮的设计
恢复时,AgentENV 不用 userfaultfd(内核 6.8 之前的常见做法),而是从内存层堆叠出一个只读 ublk 设备,作为 BackendType::File 交给 Firecracker。Firecracker 把这个块设备 mmap 进来,写第一次的时候写时复制到匿名内存 —— 底层设备永远不被修改。
不被修改,就意味着可以共享:
六、按需加载:本地盘只是缓存,不是仓库
这是回应「几百种镜像没法预热」那条约束的部分。
配置上只有两个后端选项 —— POSIX 共享文件系统或 S3 兼容对象存储:
# 快照仓库放在共享存储上,本地盘只做有界缓存
[snapshot]
repository_backend = "oss" # 或 "posix_fs"
[backend.oss]
endpoint = "YOUR_ENDPOINT"
bucket = "YOUR_BUCKET"
cache_max_size_gb = 100 # 本地缓存上限:满了就淘汰冷数据
[image.cache.remote_blocks]
max_size_gb = 100 # 远端块的本地缓存上限,同样是有界的
思路很简单,但对训练场景是决定性的:镜像总量可以远超单机磁盘容量若干个数量级,因为每台机器只保留自己正在用的热数据,冷的自动淘汰。150 万镜像这个数字就是这么来的 —— 不是每台机器都存 150 万,而是集群整体能寻址这么多,且不需要提前把哪一台预热好。
官方在文档里给了一条容易被忽略的前提:存储网络至少 1 Gbps,强烈建议 10 Gbps 以上。 按需加载把磁盘容量问题转成了网络带宽问题。
七、空闲很便宜:内存气球与超分
训练时环境的空闲比例很高 —— 模型在生成下一个动作时,环境就在那儿等着。
AgentENV 的应对是内存气球:把虚拟机里可回收的内存还给宿主机,需要时再要回来。官方声称生产中做到 9.6 倍内存超分,并且强调这个比例是在「环境跑得越久、彼此差异越大」的条件下维持的 —— 这句话的潜台词是,超分最难维持的恰恰是长时间运行之后。
配合 README 里那组数字:启动或恢复 < 50 ms、暂停 < 100 ms。暂停快,才敢频繁暂停;频繁暂停,超分才有意义。 这三件事是一个整体。
八、多节点:网关 + 调度器
九、E2B 兼容:一个务实的选择
AgentENV 暴露的是一套 E2B 兼容的 HTTP API。把 E2B_API_URL 指过来,现有的 E2B Python / TypeScript SDK 代码一行不改就能用。
这一步的意义不在技术,在迁移成本:执行沙箱 03 篇讲过 E2B 已经是这个领域事实上的接口参考,兼容它等于免费获得整个 SDK 生态。自建方案对托管方案做 API 兼容,是这两年基础设施领域一个反复出现的模式(网关 08 篇里阿里云 AI 网关和 Higress 的关系也是同一回事,只是方向相反)。
十、它的边界
写文档最容易失衡的地方是只讲好处,这里明确列出适用边界:
| 限制 | 说明 |
|---|---|
| Linux 内核 6.8+ | ublk 的零拷贝路径依赖它。老内核上要么不可用,要么退回传统缓冲区 |
需要 /dev/kvm | 很多云主机默认不给嵌套虚拟化。官方提供 PVM 部署方案作为退路,但那是另一条路径了 |
| 传输不加密 | README 明确警告:AgentENV 认证 API 请求但不加密流量,必须跑在可信网络里或在反向代理上终结 HTTPS |
| 对存储网络有硬要求 | 按需加载把容量问题换成了带宽问题,至少 1 Gbps,建议 10 Gbps 以上 |
| 很新 | 2026-07-23 建仓,本文实测时不足一个月。生产验证目前主要来自单一用户(Kimi K3 的训练) |
| 沙箱与节点绑定 | 控制面很薄的代价:节点挂了,其上沙箱不自动迁移 |
最后一条值得展开:「给一个前沿模型训练用过」既是最强的背书,也是最大的未知数。 它证明这套设计在那一种负载下成立,但不保证在你的负载下成立 —— 尤其是任务镜像形态、单条 rollout 时长、并发曲线这三样如果差得远,结论未必能搬。
下一篇 → 03 - 环境接口标准:AgentENV 解决的是「环境怎么跑起来」,而「训练框架怎么跟环境对话」是另一个正在打架的问题。
← 回到 专题索引 · Agent Infra 板块总览