ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Moonshot AI + kvcache-ai 开源 AgentENV —— 基于 Firecracker 的智能体强化学习环境扩展平台深度解析

Moonshot AI + kvcache-ai 开源 AgentENV —— 基于 Firecracker 的智能体强化学习环境扩展平台深度解析 一、引言:当 Agent 开始使用计算机2026年7月27日,月之暗面(Moonshot AI)与 kvcache-ai 联合宣布开源 AgentENV(AENV),一个基于 Firecracker 微虚拟机的智能体强化学习(Agentic RL)环境扩展平台,采用 MIT 许可证发布。这一天也是 Kimi K3(2.8万亿参数 MoE 模型)权重及全套训练基础设施开源的日子。AgentENV 正是为 Kimi K3 的 Agentic RL 训练而生的底层执行环境平台。AgentENV 的核心目标可以用一句话概括:为每个 Agent 提供一个独立的、安全的、可快速克隆的 Linux 计算机,让数千台这样的计算机在单台物理服务器上并行运行,并且让闲置的计算机几乎不消耗资源。这不是一个简单的容器管理平台,也不是一个常规的虚拟机编排工具。AgentENV 瞄准的是智能体强化学习中最棘手的基础设施问题——环境隔离、资源密度和状态管理。经过近一年的内部生产验证,AgentENV 已在 70 节点集群上累计运行超过 22.5 万个 Agent 执行环境,CPU 分配-使用比平均达到 27.9 倍,内存分配-使用比平均达到 9.6 倍,环境管理开销相比现有方案降低约一个数量级。本文将从架构设计、核心原语、性能优化、部署实践等多个维度,对 AgentENV 进行深度技术解析。二、Agentic RL 的困境:为什么需要新基础设施2.1 传统 RL 与 Agentic RL 的本质差异传统的强化学习(如 CartPole、Atari 游戏)中,环境是一个 Python 函数或一个静态模拟器:# 传统 RL:环境是内存中的函数obs,reward,done,info=env.step(action)Agentic RL 则完全不同。一个编码 Agent 需要读取代码仓库、修改文件、安装依赖、启动服务、与数据库交互。一个计算机使用 Agent(Computer Use Agent)需要操作浏览器、点击按钮、填写表单。这意味着每一次训练 rollout 都需要一个真实的、完整的、独立的 Linux 执行环境,包含文件系统、网络栈、运行中的进程。2.2 不可能三角:隔离、速度、密度Agentic RL 对执行环境提出了三个同时满足的要求:要求说明传统方案瓶颈强隔离每个 Agent 运行在独立的安全边界内,不能影响宿主机或其他训练任务容器共享宿主机内核,隔离边界薄弱快速启动环境需要在毫秒级创建和恢复,否则训练吞吐量急剧下降完整虚拟机启动需要数秒到数十秒高密度单机需要支撑数百到数千个并发环境每个 VM 固定占用大量内存,无法弹性伸缩容器方案(Docker/containerd):启动快(秒级),资源开销低,但共享宿主机内核。在 Agentic RL 场景中,模型生成的代码可能尝试逃逸容器边界、访问隐藏服务、修改评估逻辑。我们的研究发现,奖励驱动的 Agent 会尝试多种"作弊"行为——如果它们能获得更高的奖励,它们就会这样做。共享内核意味着这些行为无法被彻底阻止。传统虚拟机方案(KVM/QEMU):隔离性强,但启动慢(10-30秒),每个 VM 固定占用数 GB 内存,无法在千级别并发下保持经济性。AgentENV 的解法:使用 Firecracker microVM 提供硬件级隔离,同时通过快照技术实现毫秒级启动和恢复,通过内存气球和页缓存共享实现高密度部署。2.3 镜像多样性的挑战Agent 训练需要的不仅仅是隔离。不同的任务需要不同的操作系统、语言运行时、编译器、包管理器、代码仓库和外部服务。随着任务数量增长,需要的环境镜像集合迅速膨胀,总大小可能远超单个节点的存储容量。┌─────────────────────────────────────────────────────┐ │ Agent 训练环境镜像库 │ ├─────────────────────────────────────────────────────┤ │ base/ubuntu:22.04 (2.1 GB) ── 基础系统 │ │ base/ubuntu:24.04 (2.3 GB) ── 新版本基础系统 │ │ python/coding (3.5 GB) ── Python 编码环境 │ │ python/data-science (4.2 GB) ── 数据科学环境 │ │ nodejs/web-dev (2.8 GB) ── Web 开发环境 │ │ go/compiler (1.9 GB) ── Go 编译环境 │ │ rust/compiler (2.4 GB) ── Rust 编译环境 │ │ java/jvm (3.1 GB) ── JVM 环境 │ │ ... 更多定制化镜像 ... │ │ 总计: 数百 GB ~ 数 TB │ └─────────────────────────────────────────────────────┘传统方案需要在每个节点上预烧所有镜像,这显然不可扩展。三、AgentENV 架构总览3.1 系统架构图┌─────────────────────────────────────────────────────────────────┐ │ AgentENV 系统架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ Client │ │ Gateway │ │ Scheduler │ │ │ │ (E2B SDK) │───▶│ (Multi- │───▶│ (Multi-Node, │ │ │ │ / aenv │ │ Node) │ │ Prototype) │ │ │ │ CLI) │ │ :8080 │ │ :9090 │ │ │ └──────────┘ └──────────────┘ └───────────┬──────────┘ │ │ │ │ │ ┌────────────────────────────────────────────────┴──────────┐ │ │ │ API Server (Axum) │ │ │ │ :8000 / E2B Compatible │ │ │ └────────────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌────────────────────────────┴───────────────────────────────┐ │ │ │ Orchestrator │ │ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Creating│→│ Running │→│ Pausing │→│ Paused │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────┘ └────┬─────┘ └──────────┘ └────┬─────┘ │ │ │ │ │ │ │ │ │ │ ┌─────────────────▼───────────────────────────▼──────┐ │ │ │ │ │ Resuming Snapshotting Forking │ │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌────────────────────────────┴───────────────────────────────┐ │ │ │ Firecracker microVM 池 │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │ Agent N │ │ │ │ │ │ microVM │ │ microVM │ │ microVM │ │ microVM │ │ │ │ │ │ 4vCPU │ │ 2vCPU │ │ 4vCPU │ │ 2vCPU │ │ │ │ │ │ 8GB │ │ 4GB │ │ 8GB │ │ 4GB │ │ │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────┴──────────────┴──────────────┘ │ │ │ │ │ 共享只读页缓存 │ │ │ └───────────────────────┼───────────────────────────────────────┘ │ │ │ │ │ ┌────────────────────────┴──────────────────────────────────────┐ │ │ │ 存储层 (ublk + overlaybd) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ │ │ 只读基础层 │ │ COW 上层 │ │ S3 远程存储 │ │ │ │ │ │ (共享,去重) │ │ (每个VM独立) │ │ (按需加载) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ 快照管理层 (三层) │ │ │ │ L1: 构建器暂存区 → L2: 已提交快照仓库 → L3: 节点本地缓存 │ │ │ └──────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘3.2 核心组件AgentENV 由以下核心组件构成:API Server (Axum):HTTP 入口,验证请求和认证,转发到 Orchestrator。暴露 E2B 兼容的端点,默认监听0.0.0.0:8000,健康检查位于GET /health。Orchestrator:沙箱生命周期状态机,管理 Creating → Running → Pausing → Paused → Resuming → Snapshotting → Forking → Killing 等状态转换。Firecracker microVM:每个沙箱是一个独立的 Firecracker 微虚拟机,拥有自己的 Linux 内核、文件系统和网络命名空间。块设备层 (ublk + overlaybd):用户态块设备,支持写时复制(COW)分层镜像。envd 守护进程:运行在每个 Guest 内部,监听端口 49983,处理命令执行、文件操作和健康检查。反向代理:将 HTTP 和 WebSocket 流量从客户端路由到运行在 microVM 内部的服务。快照管理器:管理三层快照存储(构建器暂存区 → 已提交快照仓库 → 节点本地运行时缓存)。Gateway + Scheduler:多节点控制平面(原型阶段),Gateway 监听 :8080,Scheduler 监听 :9090。四、Firecracker microVM 深度解析4.1 Firecracker 是什么Firecracker 是 AWS 在 2018 年开源的 microVM 虚拟机管理器(VMM),使用 Rust 语言编写,专为无服务器计算和容器化工作负载设计。它基于 Linux KVM(Kernel-based Virtual Machine)实现硬件虚拟化隔离。Firecracker 的核心设计理念是"安全、轻量、最小化"——它只实现运行 Linux 微虚拟机所需的最小功能集,放弃了传统 VMM(如 QEMU)中的设备模拟、图形界面、USB 支持等非必要功能。4.2 Firecracker 的核心隔离机制┌─────────────────────────────────────────────────────────────────┐ │ Firecracker 隔离层级 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. KVM 硬件虚拟化 (CPU 虚拟化 + EPT 页表扩展) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Guest 内核运行在 Ring 0 (非 root 模式) │ │ │ │ 所有特权指令 (HLT/IN/OUT/MSR/CR3 等) 被 KVM 捕获 │ │ │ │ EPT (Extended Page Tables) 隔离 Guest 物理内存 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 2. Jailer 进程隔离 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Linux Namespaces: 每个 microVM 独享 │ │ │ │ ├─ mount namespace (独立挂载树) │ │ │ │ ├─ pid namespace (独立进程号空间) │ │ │ │ ├─ net namespace (独立网络栈) │ │ │ │ ├─ ipc namespace (独立 IPC 资源) │ │ │ │ └─ uts namespace (独立主机名) │ │ │ │ cgroups: 资源限制 (CPU/内存/IOPS) │ │ │ │ chroot: 文件系统隔离 │ │ │ │ seccomp: 系统调用白名单过滤器 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 3. 设备模型 (极简) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ virtio-net: 网络设备 (无 VGA/音频/USB 等) │ │ │ │ virtio-block: 块存储设备 │ │ │ │ virtio-vsock: 宿主机-Guest 通信通道 │ │ │ │ 串口控制台: 仅用于内核日志和调试 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘4.3 与传统容器隔离的对比维度Docker 容器KVM/QEMU VMFirecracker microVM隔离级别进程级(共享内核)硬件级(独立内核)硬件级(独立内核)启动时间100ms-1s10-30s125ms(冷启动)/50ms(快照恢复)内存开销几乎为零512MB+(含 QEMU 进程)~5MB(Firecracker 进程本身)安全边界共享内核 → 系统调用面大完整隔离完整隔离 + 最小攻击面镜像标准OCI (Docker)QCOW2/RAWOCI (通过 overlaybd)每节点密度数百~数千数十数百~数千设备模型无(直接使用宿主机)完整设备模拟最小设备集(virtio 仅 3 类)4.4 Jailer 机制的深入分析Jailer 是 Firecracker 的重要组成部分,负责在启动 microVM 前建立安全边界。AgentENV 继承了 Firecracker 的 Jailer 机制,并在此基础上增加了额外的安全策略。# AgentENV 中 Jailer 配置的伪代码示意# 实际实现使用 Rust,此处用 Python 说明逻辑defconfigure_jailer(microvm_id:str,resources:ResourceSpec):"""配置 Firecracker Jailer 的隔离参数"""jailer_cfg={# 1. 创建独立的命名空间"namespaces":{"mount":True,# 独立挂载树"pid":True,# 独立 PID 空间"net":True,# 独立网络栈"ipc":True,# 独立 IPC 资源"uts":True,# 独立主机名},# 2. cgroups 资源限制"cgroups":{"cpu_quota":resources.cpu_quota_us,# 微秒级 CPU 配额"memory_max":resources.memory_mb,# 最大内存(含气球)"memory_swap_max":0,# 禁用 swap"io_weight":resources.io_weight,# IO 优先级"pids_max":resources.max_pids,# 最大进程数},# 3. seccomp 过滤器(系统调用白名单)"seccomp":{"default_action":"KILL",# 白名单外的系统调用直接杀死进程"allowed_syscalls":[# 仅允许 microVM 运行所需的最小系统调用集"read","write","openat","close","mmap","munmap","mprotect","futex","clock_gettime","nanosleep","ioctl",# 用于 KVM 接口# ... 约 50 个系统调用],},# 4. chroot 到专用目录"chroot_dir":f"/var/lib/aenv/jailer/{microvm_id}/",# 5. 以非 root 用户运行"uid":1000+hash(microvm_id)%60000,"gid":1000+hash(microvm_id)%60000,}returnjailer_cfg五、存储与 I/O 架构:overlaybd + ublk5.1 分层存储架构AgentENV 的存储设计是整篇文章最精妙的部分。它需要解决一个核心矛盾:Agent 训练需要大量不同的镜像(每个任务可能需要不同的工具链、代码库、依赖),但节点的本地磁盘容量有限。解法是按需加载 + 分层存储 + 内容寻址缓存。┌─────────────────────────────────────────────────────────────────┐ │ overlaybd 分层镜像结构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 远程存储 (S3/对象存储) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ docker.io/library/ubuntu:22.04 (原始 OCI 镜像) │ │ │ │ docker.io/library/python:3.12 (原始 OCI 镜像) │ │ │ │ registry.example.com/coding-agent:latest (自定义镜像) │ │ │ └──────────────────────┬───────────────────────────────┘ │ │ │ 按需加载 (on-demand) │ │ ▼ │ │ 本地缓存 (有限容量, 热数据保留, 冷数据淘汰) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ 只读基础层 (内容寻址, 跨沙箱共享) │ │ │ │ │ │ sha256:abc123... │ │ │ │ │ │ sha256:def456... │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ 可写 COW 上层 (每个沙箱独有) │ │ │ │ │ │ sandbox-001: 增量写入 │ │ │ │ │ │ sandbox-002: 增量写入 │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ ublk 用户态块设备驱动 │ │ ▼ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Firecracker microVM │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ │ │ Guest 内核看到的块设备: /dev/vda │ │ │ │ │ │ (virtio-blk 前端 → ublk 后端 → overlaybd 分层) │ │ │ │ │ └────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘5.2 ublk:用户态块设备ublk 是 Linux 内核提供的一种用户态块设备框架。与传统块设备不同,ublk 允许在用户空间实现块设备的 I/O 处理逻辑,而内核只负责转发 I/O 请求。# ublk + overlaybd 的 I/O 流程示意# 实际实现使用 Rust 的 ublk 绑定defhandle_ublk_io(request:UblkRequest):"""处理 Guest 发起的块设备 I/O 请求"""sector=request.sector# 请求的扇区号nr_sectors=request.nr_sectors# 请求的扇区数is_write=request.is_write# 读/写# 1. 将扇区号映射到 overlaybd 层layer_info=overlaybd.lookup(sector,nr_sectors)ifis_write:# 写操作:写入 COW 上层# 如果该扇区首次写入,先复制基础层数据到上层(COW)ifnotcow_layer.has_sector(sector):base_data=base_layer.read(sector,nr_sectors)cow_layer.write(sector,nr_sectors,base_data)# 写入新数据到 COW 上层cow_layer.write(sector,nr_sectors,request.data)# 标记该扇区为脏dirty_bitmap.mark(sector,nr_sectors)else:# 读操作:优先读 COW 上层,未命中则读基础层ifcow_layer.has_sector(sector):data=cow_layer.read(sector,nr_sectors)else:data=base_layer.read(sector,nr_sectors)# 2. 使用 io_uring 进行异步 I/O# 3. 使用 Direct I/O 避免宿主机双重缓存returndata5.3 关键优化:io_uring 与 Direct I/OAgentENV 的块设备层使用了三个关键 I/O 优化:ublk 用户态驱动:避免内核态文件系统和块设备层的额外开销io_uring:Linux 最新的异步 I/O 接口,相比传统 AIO 减少系统调用开销和内存拷贝Direct I/O:绕过宿主机页面缓存,避免 ublk 后端和 Guest 内核的双重缓存// AgentENV 中 ublk 队列处理的 Rust 示意代码// 基于 io_uring 的异步 I/O 处理useio_uring::{IoUring,opcode,types};structUblkQueue{ring:IoUring,sqes:Vecopcode::ReadWrite,/// 处理一批 I/O 请求asyncfnprocess_io_requests(mutself,requests:VecUblkRequest){for(i,req)inrequests.iter().enumerate(){let(buf,offset)=self.get_buffer_and_offset(req);letsqe=ifreq.is_write{opcode::Write::new(types::Fixed::new(self.fd),buf,offset,).build()}else{opcode::Read::new(types::Fixed::new(self.fd),buf,offset,).build()};// 提交到 io_uring 的提交队列unsafe{self.ring.submission().push(sqe).unwrap();}}// 等待完成self.ring.submit_and_wait(requests.len()).unwrap();// 处理完成队列forcqeinself.ring.completion(){letresult=cqe.result();// 处理完成事件self.complete_io(cqe.user_data(),result);}}}六、快照、暂停、恢复与 Fork:RL 训练的核心原语6.1 为什么这些原语对 Agentic RL 如此重要Agentic RL 训练循环与传统的 RL 有一个本质区别:环境状态是昂贵的。在 CartPole 中,重置环境只需要env.reset()一行代码。但在 Agentic RL 中,重置一个环境意味着:重新下载一个代码仓库(可能数百 MB)重新安装依赖(可能数千个包)重新启动服务(数据库、Web 服务器等)重新登录外部系统如果我们每次 rollout 都从头创建环境,训练成本将是灾难性的。AgentENV 通过四个核心原语解决了这个问题:┌─────────────────────────────────────────────────────────────────┐ │ AgentENV 核心原语 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 快照 (Snapshot) ▲ 增量记录内存和文件系统的变更 │ │ 不需要复制整个 VM 镜像 │ │ 100ms 内完成(即使有大量磁盘写入) │ │ 可持久化到 S3 或分布式文件系统 │ │ │ │ 2. 暂停 (Pause) 释放 CPU 和可回收内存 │ │ 100ms 内完成 │ │ TTL 到期自动暂停(默认不删除) │ │ │ │ 3. 恢复 (Resume) ▲ 从快照快速恢复 │ │ 恢复运行中的进程和打开的网络连接 │ │ 50ms 完成 │ │ │ │ 4. Fork (分支) ▲ 从运行中的环境创建 N 个独立子沙箱 │ │ 最多 16 个 │ │ 子沙箱继承文件系统、内存和资源配置 │ │ 写时复制,几乎零额外开销 │ │ │ │ 典型用例: │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 1. 构建环境: 安装依赖、克隆仓库、启动服务 (一次) │ │ │ │ 2. 快照: 保存当前状态 │ │ │ │ 3. Fork × 16: 从该状态创建 16 个独立子沙箱 │ │ │ │ 4. 并行 Rollout: 每个子沙箱尝试不同的策略 │ │ │ │ 5. 收集奖励: 比较各分支的表现 │ │ │ │ 6. 回收: 暂停或删除子沙箱,释放资源 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘6.2 增量快照的实现原理AgentENV 的快照不是全量拷贝,而是增量记录。这通过两个机制实现:内存快照:基于 KVM 的 dirty page tracking 机制。Firecracker 通过 KVM 的KVM_GET_DIRTY_LOGioctl 获取自上次快照以来被修改的内存页,只记录这些脏页的变化。# 增量快照的 Python 示意代码# 实际 AgentENV 使用 Rust 调用 Firecracker APIclassIncrementalSnapshotManager:"""增量快照管理器"""def__init__(self,microvm_id:str):self.microvm_id=microvm_id self.base_snapshot=None# 基础快照self.delta_snapshots=[]# 增量快照链self.last_dirty_bitmap=None# 上次脏页位图asyncdefcreate_snapshot(self,vm:FirecrackerMicroVM):"""创建增量快照"""# 1. 暂停 VM(冻结 vCPU)awaitvm.pause()# 2. 获取自上次快照以来的脏页位图dirty_bitmap=awaitvm.get_dirty_log()ifself.base_snapshotisNone:# 首次快照:记录全量内存mem_snapshot=awaitvm
返回列表