Agent 运行时与工具执行(Agent Runtime & Tool Execution)
所属:01-fundamentals / 06-agent-infra
面试权重:★★★
来源:《深入理解 AI Infra:量化分析与系统设计》第 11 章「资源调度与运行环境」(李博杰,github.com/bojieli/ai-infra-book),另引第 3.2.4 节「分支、工具等待与状态驻留」。书中数字均带实验条件与算式,非本仓库测量
相关知识点:../04-agent-architecture/tool-use.md(调用层的并行工具调用:前提、3×RTT→1×RTT、call id 配对)、../04-agent-architecture/09-coding-agent.md(并发声明与级联中止、故障四层、熔断阈值)、07-distributed-inference.md§6(实例级的启动积压与部分故障恢复)、gateway-routing.md(模型调用层的限流 / 熔断 / 降级)、latency-cost.md(延迟与成本口径)、../../02-interview/design/07-single-agent.md(六道刹车与循环终止)、../../02-interview/design/05-routing-gateway.md§4.7(网关自身的高可用四条)、../00-toolchain.md(沙箱与隔离执行层的点名表:E2B / Modal / Daytona / Firecracker / gVisor / microsandbox / Docker,以及「自建隔离还是买托管」的判据 —— 本篇讲四种隔离强度本身,那篇点名)
一句话#
工具执行不是「调个函数」—— 它要在一片共享资源上同时容纳很多任务的沙箱、给它们分 CPU 和内存、决定谁先跑、以及失败了从哪接着跑。这一层要回答四个问题:并发容量够不够(先撞内存还是 CPU)、隔离边界画在哪、环境什么时候销毁、以及「做完了但确认丢了」怎么不重做。
为什么重要(面试角度)#
- 「Agent 高并发」的答案通常止步于「加副本 + 限流」。 能往下说清 瓶颈常常不在算力而在沙箱内存、环境驻留时间就是内存成本、抢占要先算保存代价的人,立刻和背概念的人分层。
- 它有一批能当场推的量。 资源需求率、Little 定律、暂停是否划算的不等式、提前准备的命中率公式 —— 这些都是可推导的,不是形容词。
- 它专治一个高频错误答案:「环境一直留着最省事」。 数据反过来:把 2 GiB 环境改成每轮重建,平台平均内存从 600 GiB 降到 180 GiB,代价只是每秒多创建 30 个环境。
- 它把「高可用」从「重试三次」推进到持久性语义。 只保证答案已生成 ≠ 保证可恢复;
write()返回 ≠fsync()成功;「没做完」和「做完了但确认丢了」是两种失败,处理完全不同。 - 和你的项目接得上:QueWI 的网关做的是模型调用层的重试 / 熔断 / 降级;这一篇是工具执行层的资源与恢复。同一场面试里,能说清这两层是两回事,就是加分。
⚠️ 边界声明:本篇是《AI Infra》第 11 章的知识梳理,不是你的实战经验。你没有运维过多租户沙箱平台、没做过 microVM 隔离或抢占调度。面试用「这套账我会这么算」的口径讲,不要说成「我们线上就是这么部署的」。
核心内容#
1. 先算需求:为什么 Agent 平台先撞的常常是内存,不是 CPU#
单任务的时间线决定了平台的需求。一轮 Agent 循环是「模型生成工具参数 → 工具执行 → 结果回灌」;模型那 9 秒里工具不占 CPU,但环境(进程 + 内存)通常还占着 —— 执行停下来并不自动腾出已占的空间。
把它放大到平台,只需要一个关系式:
书里的教学平台:每秒到达 10 项任务,每项 3 轮、每轮模型 9 秒 + 工具 1 秒。
| 资源 | 怎么算出来 | 需求 | 容量 | 占用率 |
|---|---|---|---|---|
| CPU 核 | 秒工具时间 | 平均 30 核 | 48 核 | 62.5% |
| 模型并发调用 | 平均 270 次 | 320 槽 | 约 84% | |
| 环境内存 | 每任务 60 GiB·秒 → | 平均 600 GiB | 358 GiB | 约 168% ❌ |
内存是唯一超容量的那一项,而 CPU 还空着 18 核 —— 加 CPU 核解决不了这个问题。环境数量也可以直接用 Little 定律求:
每个 2 GiB → 600 GiB。358 GiB 最多同时保留 178 个环境,每个要占 30 秒,所以平台每秒最多开始约 5.9 项任务,其余只能排队。
一个反直觉的推论:把模型调用从 9 秒延长到 12 秒,CPU 需求一点没变(仍是 30 核),内存需求却涨到 780 GiB,模型并发也涨到 360 次、超过 320 的容量。因为任务变长了,环境要保留更久。模型变慢会推高工具平台的内存需求 —— 这一条几乎没有人的第一反应会答对。
生产规模上超卖是常态。 DeepSeek 的 DSec 沙箱平台按一周运行记录:约九成容器和 microVM 平均只用到所申请 CPU 的 5% 以内 —— 因为 agent 装依赖、启服务、改的文件在模型生成期间一直留着,CPU 大多闲置。所以平台超卖(各沙箱申请的 CPU 加起来远超物理核数),单个节点曾稳定跑 3,200 个容器或 800 个 microVM。密度再往上提,卡住的两件事是 ① 等待期间一直占用的内存,② 挤在同一物理核上的任务相互拖慢。
错开启动能换内存、但要付时间。 一组四进程 CPU 工具实验:同时启动 vs 错开启动,累计 CPU 时间几乎一样(0.29 s vs 0.27 s),但 RSS 峰值从约 320 MiB 降到 80 MiB,整组完成时间从约 0.10 s 拉到 0.54 s。内存峰值和完成时间在这里是一对可交换的量。
2. 隔离边界:四种强度,以及「沙箱要防的不只是别的沙箱」#
工具调用最终要由普通程序执行,运行环境提供操作系统接口、文件系统、内存、网络与资源限制。隔离强度是一根连续的轴,代价随强度上升:
| 隔离方式 | 边界的实质 | 代价 |
|---|---|---|
| 进程 | 独立地址空间 + 权限;共享宿主内核 | 最轻,启动最快 |
| 容器 | 再加命名空间(进程号、网络等视图)+ 文件系统视图 + 资源控制;仍共享宿主内核 | 中等,密度高 |
| microVM | 自带内核,经虚拟硬件与宿主隔离;精简虚拟设备降低开销 | 启动与每环境开销更大 |
| 完整虚拟机 | 完整商用操作系统、可做图形渲染 | 最重 |
Firecracker 是这一档的代表(AWS 为无服务器计算开发,2018 年开源,团队 2020 年在 NSDI 发论文讨论短时任务下的隔离 / 启动 / 内存开销权衡);E2B 用 Firecracker microVM 跑工具环境,模板由容器镜像构建 —— 容器镜像决定「环境里有哪些文件和依赖」,microVM 决定「这些程序怎么和宿主隔离」,两者是正交的。
DSec 提供四类后端按任务选用:判题 / 编译这类短小无状态任务放预先创建、反复使用的容器;代码仓库级任务用容器(快、密度高);安全相关用 microVM;要完整 OS(如 Android)或图形渲染用完整虚拟机。注意容器本身也跑在 QEMU 虚拟机里 —— 因为容器共享宿主内核,外面再套一层更安全。
沙箱要防三个方向,不只是「沙箱之间」。
| 方向 | 手段 |
|---|---|
| 沙箱 ↔ 沙箱 | 虚拟化 + 资源配额 |
| 沙箱内的 agent ↔ 平台自己的代理进程 | AppArmor(按程序限制可访问的文件与 socket)—— agent 以 root 运行也无法越权 |
| 沙箱 ↔ 外部网络 | eBPF 逐包检查地址 / 端口 / 协议,只放行本任务白名单内的软件源 |
为什么中间那一层必须存在?因为 RL 训练里的 agent 会主动找捷径(DSec 记录到的做法):向沙箱内平台代理进程的 socket 发伪造请求、翻查代理日志找残留的答案、改写 /bin/bash 以篡改后续命令。也有无意造成的破坏:从根目录递归 grep 读到 /proc 下某文件触发内核缺陷整机崩溃;执行 yes 命令让平台逐字记录输出、攒下数十 GB。这些手段只能堵住已经发现的捷径;单看最终输出,判断不了 agent 是否按预期完成了任务。
3. 环境生命周期:留、暂停、还是每轮重建#
这是本篇最"值钱"的一节,因为它是一个可以当场算的取舍,而不是经验之谈。四种做法:
| 做法 | 做法 | 代价 |
|---|---|---|
| 始终驻留 | 任务期间环境不释放 | 内存按整个任务时长计费 |
| 暂停 / 恢复 | 等待期间保存状态、释放内存,用时载回 | 保存耗时随内存容量增长 |
| 每轮重建 | 只保文件(已持久化),进程重起 | 每次要付创建工作 |
| 提前准备 | 在模型生成期间就把下一轮环境备好 | 预测错误要取消重来 |
① 暂停什么时候才划算 —— 有不等式。 设释放内存 GiB、空闲 秒、每 GiB·秒价格 、保存与恢复的新增成本 :
E2B 文档口径下暂停每 GiB 约需 4 秒、恢复约 1 秒。于是 2 GiB 环境保存要 8 秒 —— 而书里的模型等待只有 9 秒,整个间隔都被保存和恢复占满,占用仍是 18 GiB·秒,一点没省。换成 512 MiB 环境,约 2 秒保存完,9 秒间隔能释放 6 秒,才开始有收益。间隔短于「保存 + 恢复」之和时,暂停还会把下一次工具执行往后推。
反过来,长间隔下暂停几乎是免费的。 RL 训练里 GPU 作业被抢占、到恢复通常要一到三小时:DSec 上一个作业最多可申请 32K 个沙箱,按 2 GiB 算合计约 70 TB(超过一个集群单元约 250 TB 内存的 1/4),但保存加恢复仍只要 9 秒左右,暂停两小时,内存空出来的时间约为保存恢复期间的 800 倍。所以作业一被抢占就主动暂停全部沙箱。
② 每轮重建能省多少内存 —— 有账可算。 前提是下一轮要用的状态已经全部写入持久存储。此时工作目录和结果已落盘,每轮只需在「准备 2 秒 + 执行 1 秒」时保留环境:
平台平均内存 600 GiB → 180 GiB,代价是每秒创建 30 个环境、准备平均多用 3 个 CPU 核(30 → 33)。Firecracker 论文报告单主机每秒最多创建 150 个 microVM,每秒 30 个仍有余量。用少量创建开销换回 420 GiB 的等待期驻留。
但这条路的边界要说清楚:它成立的前提是「一轮结束后,下一轮需要的状态能全部写进持久存储」。模型装了依赖、起了数据库或后台服务时就不成立 —— 只保存文件,重建出来的环境和原来的并不相同。这时只能走「环境保留 + 压缩驻留成本」那条路。判据一句话:轮间状态能否完整落盘。
③ 提前准备能省多少等待 —— 有命中率公式。 设准备需 秒、提前量 秒、预测正确的概率 ,则从发出调用到环境就绪的平均等待为
取 秒、:不提前等 2 秒;提前 1 秒 → 1.25 秒;提前 2 秒 → 0.5 秒;提前 3 秒仍是 0.5 秒,却让正确分支多保留 1 秒就绪状态,对 2 GiB 环境每次多占 GiB·秒。结论:能在调用到达前准备好就够了,更早只会白占内存。 本地工具重放印证了这一点 —— 从「提前 50 ms」改成「模型等待一开始就准备」,累计 CPU 时间几乎不变(约 0.26–0.28 秒),累计内存占用却从约 0.0193 涨到 0.467 GiB·秒,约 24 倍。
4. 队列、优先级与抢占:突发、共用物理核、以及抢占的账#
先分清是哪一层的并发。 「并行工具调用」(一轮里同时发多个 call)属于调用层 —— 前提、收益和坑见
../04-agent-architecture/tool-use.md;哪些工具允许并发、失败时怎么级联中止,见../04-agent-architecture/09-coding-agent.md。本节讲的是执行层:这些调用落到共享资源池上之后,排队、分配与抢占怎么发生。
排队时间不由总工作量决定,而由到达方式决定。 十项各需 1 秒的工作:每隔 1 秒到达 → 每项零排队;十项同时在第 0 秒到达 → 执行同样只要 10 秒,但各项等待依次为 0…9 秒、平均 4.5 秒。所以从「平均资源需求」推到「实际响应时间」,必须知道请求是否集中到达。
书里的突发算例:48 核全空时同时来 80 次各 1 秒的调用 → 前 48 项立即执行、其余 32 项等 1 秒,平均等待 0.4 秒、全部 2 秒内完成;加到两台(96 核)→ 1 秒内完成。但同一批调用还需要足够的沙箱内存:80 个已准备环境要 160 GiB,而「始终驻留」方案本身要 600 GiB、早就超了;改成每轮重建后平均只占 180 GiB,剩下的 178 GiB 正好够这 80 个环境。减少等待期驻留,才腾得出内存给突发调用,闲着的 CPU 也才用得上。
调度器怎么把突发摊到多台节点。 DSec 峰值创建速率超过每秒 5,000 个沙箱,摊到 160 个节点约每节点每秒 31 个。难点是调度器看到的节点负载是定期汇总的、天然过时,若每次都挑"当前最空闲"的节点,同一批请求会一起挤到同一台。三项措施:① power-of-k-choices —— 每次只随机抽几个节点、选其中最闲的,同时做的决定自然就分散;② 调度实例把刚放下、还没汇总进来的沙箱补记到本地视图;③ 最终接不接收由节点自己检查本地容量决定,不够就拒绝、调度器换一个。全局视图可以过时,不超容量由节点自查保证 —— 而且调度器和汇总都不需要持久状态,实例可以随时增减替换。
优先级:硬件上不一定分得开。 延迟敏感(LS)任务与尽力而为(BE)任务超卖共用 CPU 时,把 BE 放进 Linux 的 SCHED_IDLE 调度类,理应「LS 能跑就让」。但开了超线程(SMT)后,一个物理核有两个硬件线程、共用执行单元和一级缓存,调度器按硬件线程分配,LS 和 BE 仍可能落在同一个物理核的两个线程上。DSec 实测:BE 占到节点容量一半时,LS 任务每步耗时增加约 45%,单靠 SCHED_IDLE 几乎没有改善;再给 LS 开 core scheduling(同一物理核的硬件线程只跑同一组任务)降到约 17%。剩下的干扰来自整机高负载降频、共用内存带宽与末级缓存。所以书里的教学平台直接关掉超线程 —— 换来干净隔离,代价是可调度逻辑核少一半。
抢占的账要算两笔。 设在线任务最多等 2 秒,而离线作业保存状态要 30 秒:这组资源即使可抢占,也要 30 秒后才交还,所以平台必须另行预留能在 2 秒内分配给在线任务的资源。第二笔是未保存的进度:已跑 20 秒、恢复需 2 秒的作业被抢占,重做多少取决于上一个 checkpoint 之后积累了多少工作。调度器既要看新任务能提前多久开始,也要计入保存 / 恢复开销和原作业因此增加的延迟。
公平性也要有判据。 两项任务各得两张 GPU 看似份额相同,但若一种卡做同样工作要两倍时间,进度就不同。主导资源公平(DRF) 取每个任务在各类资源中占比最高的那类作为主导资源、按主导资源份额分配;Gavel、Pollux 等进一步考虑不同加速器的处理能力或训练进度。「按资源份额」「按推进速度」「限制最长等待」是三个不同的公平目标,要先说清选哪个。
5. 限流与路由:缓存亲和性把「排队」和「命中」绑在一起#
这一层和 07-distributed-inference.md §5 讲的是同一件事的两个尺度(那边是实例路由,这边是供给与额度的约束)。
缓存只有请求打到「保存了该前缀的那个后端」才复用,所以选后端同时决定命中率和排队时间 —— 有时值得为了命中多等一会。书里的算例:后端 A 排队 200 ms、命中后处理前缀 50 ms;后端 B 排队 20 ms、未命中要 500 ms;共同网络 50 ms。A 用 300 ms,B 用 570 ms,A 领先 270 ms —— 命中省下 450 ms 处理时间、多花 180 ms 排队,净赚 270 ms。当 A 的排队涨到 470 ms 时两者打平。判据是比总时间,不是比谁有命中标记。
额度的约束要分清是哪一种。 每次调用从 9 秒缩到 6 秒是靠把每副本的并发从 32 降到 16 换来的:并发调用数从 270 降到 180,但 10 个副本只剩 160 个并发槽、装不下 180,得加到 12 个副本;每调用占的 GPU 时间反而从 涨到 B200·秒。按量 API 通常分别限制请求数和 token 数,输入或输出变长时可能 token 额度先于请求数额度用完。所以「限流够不够」要分别看请求排队时间、并发调用数、token 处理能力三项,找出真正卡住任务完成速度的那一个。
6. 失败恢复:先分清「没做完」与「做完了但确认丢了」#
这是「高可用」在 Agent 语境下真正的难点,也是与 ../04-agent-architecture/tool-use.md、../04-agent-architecture/09-coding-agent.md 的分工处:那边讲循环怎么不卡死,这里讲任务怎么从故障中接着跑。
第一件事:恢复不能靠猜,要靠查询。 控制器没收到测试完成的确认时,无法区分「测试没跑完」和「跑完了但确认消息丢了」。正确动作不是重试,而是先查询已持久化的结果、核对操作标识与输入文件版本:结果在就继续,不在才判断能不能重试。关键是这个查询本身要幂等。 消息去重和「实际执行两次」是两回事 —— 同一份测试结果被传送两次,去重就够了;同一项测试实际执行两次,已经烧了两份资源,而这要靠持久化结果 + 目标系统的幂等协议来避免,不是靠消息层去重。
第二件事:报告「完成」时到底保证了什么 —— 持久性语义。 操作系统早就区分「写入完成」和「已经持久化」:write() 返回时数据可能还在内核页缓存里,要确保落盘得调 fsync() 并检查成功。Agent 运行时同样要明确规定:向调用者报告完成时,只保证答案已生成,还是保证消息、工具结果、恢复位置都已保存?而且只对聊天日志 fsync() 还不够 —— 恢复所需的信息可能分散在日志、工作目录和外部数据库里,控制器还要核对三处是否一致。更麻烦的是**「保存成功」还有不同等级**:写入本地页缓存 / 完成本地 fsync() / 收到云端持久化确认,提供的保证并不相同。
第三件事:保存频率是一笔显式的权衡。 100 个请求、每个 200 ms,每次提交固定开销 8 ms、每条记录 2 ms:
| 保存方式 | 保存共用时 | 无故障总用时 | 故障后最多重做 |
|---|---|---|---|
| 每请求提交一次 | ms | 21 s | 0.2 s |
| 每 10 个请求提交 | ms | 20.28 s | 2 s |
每 10 个一起提交省 0.72 秒,但恰好赶在提交前故障就要重做 2 秒。结论:比较保存方式时,除了任务完成时间,还要记录「已返回但未保存」的请求数,并核对重启后实际能恢复到哪。
异步上传有一个隐蔽的缺口。 每轮先写本地、再后台异步上传(失败不重试、不中止执行):三轮记录本地都全,但第二轮上传失败、第三轮成功 —— 本地历史完整,云端却只连续完整到第一轮。此时若换台机器仅凭云端恢复,就会撞上这个缺口。所以云端不能只记「收到的最后一轮」,还要记「从起点连续保存到了哪一轮」;不能因为第三轮上传成功就把连续位置从第一轮改成第三轮。要确保历史完整,必须补齐第二轮,或用完整本地副本重建并核对。删除旧日志同理:删前两轮之前,先保存一份能恢复到第二轮结束的完整状态,并把状态与轮次一起提交;否则即使剩余日志读完也该返回 incomplete。
第四件事:把一轮做成一个事务。 借用数据库的做法,把一轮「模型决策 — 工具执行 — 保存结果」当整体提交:这一轮的模型消息、工具结果、相关文件版本都保存成功后才记「已提交」,重启后从最后一次完整提交接着跑。这正是 Agent 事务(agent transaction) 研究的方向 —— 在 Agent 执行中实现数据库的 ACID。但要补一句硬边界:如果这一轮还调了外部服务,仅在本地记「已提交」不能保证外部操作也恰好执行一次,对方必须支持操作标识、幂等接口或补偿处理。
第五件事:单位成本要按「成功任务」算,还要能算恢复树。 只看单次调用价格算不出完成一个任务花了多少钱:
十项任务共花 1 美元、五项成功 → 每完成一项实际支付 0.2 美元;只统计成功那五项的 0.5 美元会得到 0.1 美元、正好漏掉一半支出。书里的恢复树算例(首次 10 秒 0.010 美元、80% 直接成功;12% 进局部修复、条件成功率 60%;其余升级到更强模型):总成本约 14.6 美元、约 997 项成功;每项通过测试约 0.0146 美元、按时通过约 0.0153 美元;而只做首次尝试只要 0.0125 美元、但成功率只有 80%。恢复把成功率从 80% 提到约 99.7%(其中约 95.0% 按时),代价是单位完成成本上升 —— 该不该付,取决于业务对「提交按时成功率」的门槛,而不是单次价格。
常见面试问法#
Q:要做 Agent 平台的高并发,你会怎么规划容量? A:先算三类资源的平均需求,找最先撞线的那一个。 用 分别算 CPU、模型并发、沙箱内存。教学平台的算例里 CPU 只用 62.5%、模型并发约 84%、内存却到 168% —— 卡住的是环境内存,而且 358 GiB 最多留 178 个 2 GiB 环境、每秒只能开始约 5.9 项任务。加 CPU 核解决不了。还要记住一个反直觉推论:模型响应变慢会推高工具平台的内存需求(9 s → 12 s,CPU 需求不变,内存 600 → 780 GiB),因为任务变长、环境要留更久。
Q:工具环境应该一直留着,还是每轮重建? A:看「轮间状态能不能完整写进持久存储」。 能落盘 → 每轮重建,把「准备 + 执行」期间才保留环境:平均内存 600 → 180 GiB,代价是每秒多创建 30 个环境、多用 3 个 CPU 核(Firecracker 单主机上限是每秒 150 个)。但模型装了依赖、起了数据库或后台服务时就不成立 —— 只保存文件、重建出来的环境与原来不同,这时只能保留环境、靠按需加载 / 只读页共享 / 冷页回收来压驻留成本。中间态是暂停:判据是 。 2 GiB 环境保存要 8 秒,而等待只有 9 秒 → 一点没省;RL 抢占要等一到三小时 → 暂停两小时空出的内存时间是保存恢复期的约 800 倍,几乎免费。
Q:沙箱要隔离什么?
A:三个方向,不只是「沙箱之间」。 ① 沙箱 ↔ 沙箱:虚拟化 + 资源配额,强度按任务选(进程 / 容器 / microVM / 完整 VM,越强越慢)。② 沙箱内 agent ↔ 平台自己的代理进程:AppArmor 限制可访问的文件与 socket,agent 以 root 运行也无法越权。③ 沙箱 ↔ 外部网络:eBPF 逐包白名单。为什么中间那层是重点:RL 训练里的 agent 会主动找捷径 —— 向代理 socket 发伪造请求、翻代理日志找答案、改 /bin/bash 篡改后续命令;也有无意的破坏(递归 grep 读到 /proc 触发内核缺陷整机崩、yes 命令攒下数十 GB)。最后要承认:这些只堵得住已经发现的捷径,单看输出判断不了 agent 是否按预期完成。
Q:模型调用失败 / 超时,你怎么恢复?
A:先分清是「没做完」还是「做完了但确认丢了」—— 这两种失败处理完全不同。 没收到确认不代表没执行;正确动作是查询已持久化的结果、核对操作标识与输入文件版本,而不是直接重试。只有确认结果不存在、且操作幂等(或目标系统支持操作标识 / 补偿)才重试。「消息去重」和「实际执行两次」是两回事 —— 前者靠消息层,后者只能靠持久化结果 + 幂等协议。再往上要定义持久性语义:报告完成时保证的是「答案已生成」还是「消息 / 工具结果 / 恢复位置都已保存」?只对聊天日志 fsync() 不够,恢复信息可能分散在日志、工作目录、外部数据库三处,要核对一致。
Q:抢占一个正在跑的任务,代价是什么?
A:两笔账。 ① 交还延迟:离线任务保存状态可能要 30 秒,而在线任务只肯等 2 秒 —— 所以光设优先级没用,必须另行预留能在 2 秒内给出的资源。② 未保存进度:已跑 20 秒、恢复需 2 秒的作业,被抢占后要重做多少,取决于上一个 checkpoint 之后的积累。调度器要同时看「新任务提前多久开始」「保存 / 恢复开销」「原作业增加的延迟」。另外提醒:设了优先级硬件上不一定分得开 —— 开 SMT 时 LS 与 BE 会共用一个物理核的两个硬件线程,DSec 测到 BE 占一半容量时 LS 每步慢约 45%,单靠 SCHED_IDLE 几乎没改善,加 core scheduling 才降到约 17%。
Q:Agent 平台的成本怎么算才不会被问穿? A:分母用「成功任务数」,不是「调用次数」;分子是全部支出,失败尝试也计入。 。十项花 1 美元、五项成功 → 每项 0.2 美元;只算成功那五项会得到 0.1 美元、漏掉一半支出。进一步还要用恢复树把各分支概率和成本乘起来:恢复策略把成功率从 80% 提到约 99.7%,但每项成本从 0.0125 升到约 0.0146(按时 0.0153) —— 值不值取决于业务对按时成功率的门槛,不是单次价格。
易错点#
| 易错说法 | 问题 | 应该说 |
|---|---|---|
| 「并发上不去就加 CPU / 加副本」 | 找错了瓶颈 | 教学平台 CPU 只占 62.5%、内存到 168%;环境内存才是瓶颈,加 CPU 核无用 |
| 「环境一直留着最省事」 | 忽略驻留时间就是成本 | 每轮重建把平均内存 600 → 180 GiB,代价仅每秒多创建 30 个环境 |
| 「暂停一下总能省内存」 | 漏了保存代价随容量增长 | 2 GiB 环境保存约 8 秒,9 秒间隔毫无节省;判据是 |
| 「模型变慢只影响延迟」 | 漏了对工具平台的反作用 | 9 s → 12 s:CPU 需求不变,内存 600 → 780 GiB,环境要留更久 |
| 「环境提前准备好就多准备点」 | 白占内存 | :提前量超过 不再降低等待,只增加空闲驻留(实测可达 24 倍) |
| 「设了优先级就能保证 LS 不被打扰」 | 忘了 SMT | BE 占一半容量时 LS 每步 +45%,SCHED_IDLE 几乎无效;core scheduling 才降到 +17% |
| 「抢占就是把资源抢过来」 | 漏了交还与重做 | 保存可能要 30 秒(对方只等 2 秒);未保存进度按上个 checkpoint 之后算 |
| 「重试一下就恢复了」 | 混淆了两种失败 | 先查持久化结果与操作标识;「没做完」和「做完了但确认丢了」处理不同,后者重试会产生重复副作用 |
| 「消息去重就防住重复执行了」 | 方向错 | 去重只防「同一结果传两次」;实际执行两次要靠持久化结果 + 幂等协议 |
| 「保存越频繁越好」 | 忽略了正常路径开销 | 每请求提交:正常 21 s / 最多重做 0.2 s;每 10 个提交:20.28 s / 最多重做 2 s |
| 「异步上传失败无所谓,后面成功就行」 | 有一个隐蔽缺口 | 第二轮上传失败、第三轮成功 → 云端只连续完整到第一轮;要记「连续保存位置」,不能只看最后一轮 |
| 「本轮记了已提交,外部操作就一定只执行一次」 | 越过信任边界 | 只在本地记「已提交」保证不了外部服务恰好执行一次,要靠操作标识 / 幂等接口 / 补偿 |
| 「优化了单次调用价格,成本就降了」 | 分母错了 | 分母是成功任务数、分子是全部支出(含失败尝试);恢复提高成功率但抬高单位成本 |
| 把本章讲成「我们线上就这么搭的」 | 触碰仓库硬规矩 | 这是书里的推导与实测,非一手经验;用「这套账我会这么算」的口径讲 |
自测#
1. 一个代码修复平台:每秒到达 10 项任务,每项 3 轮,每轮模型调用 9 秒、工具执行 1 秒;工具主机 48 核 / 约 358 GiB 内存;模型服务 10 个副本、共 320 个并发槽。三项资源各自够不够?该先动哪一项?
答案
先用一个关系式把三项都算出来:。
- CPU:每任务累计 3 秒工具时间 → 秒/秒,即平均 30 核; ✅
- 模型并发:每任务 3 次 × 9 秒 → 平均 次; ✅(已接近上限)
- 环境内存:每任务占环境 30 秒、每个 2 GiB → 每任务 60 GiB·秒; GiB; ❌
内存是唯一超容量的一项,也是先动的一项。 358 GiB 最多同时保留 个环境(书中口径取整为 178),每个占 30 秒,所以每秒最多开始约 5.9 项任务,其余排队。注意此时 CPU 还空着 18 核 —— 加 CPU 核完全解决不了这个问题。
下一步不是加机器,是改环境策略。 每轮重建环境(只保文件、进程重起)把每任务累计占用从 60 降到 GiB·秒,平台平均内存 600 → 180 GiB,进入容量内;代价是每秒创建 30 个环境、平均多用 3 个 CPU 核(30 → 33)。前提是轮间状态能完整落盘,否则这条不成立。
顺带记住那个反直觉推论:若模型调用从 9 秒延长到 12 秒,CPU 需求完全不变(仍 30 核),但内存需求涨到 780 GiB、模型并发涨到 360 次(超过 320 个槽)。模型变慢会同时推高工具平台的内存需求和自身的并发压力。
2. 客户说「我们的 Agent 沙箱平台 CPU 利用率很低,但一到高峰就建不出来新环境」。你怎么解释,给什么方案?
答案
先纠正前提:CPU 利用率低和生产级沙箱平台是同一个事实的两面,不矛盾。 DSec 按一周运行记录,约九成容器和 microVM 平均只用到所申请 CPU 的 5% 以内 —— 因为 agent 装依赖、起服务、改的文件在模型生成期间一直留着,CPU 大部分时间闲着,内存却一直被占。所以平台本来就超卖(申请的 CPU 加起来远超物理核数),单节点曾稳定跑 3,200 个容器或 800 个 microVM。
「建不出新环境」的瓶颈是密度上限,而密度上限由两件事决定:① 等待期间一直占用的内存,② 挤在同一物理核上的任务相互拖慢。
三条对症方案,按性价比排:
- 压每次创建的驻留量。 不做「整份复制模板」,而用按需加载 + 只读页共享 + 冷页回收。书里 100 个环境的算例:整份复制每环境约 2,052 MiB(合计约 200 GiB);只加载访问到的内容约 516 MiB(约 50 GiB);再共享只读内容、只留脏页约 132 MiB(约 13 GiB)。同一批任务要加载到内存的数据可以减少约 150 GiB。 DSec 还让只读镜像层走 virtio-pmem + DAX(microVM 直接映射宿主缓存,不再各存一份),内存峰值再降约四成。
- 让调度器把突发摊开。 难点是负载视图天然过时,都挑"最闲"的节点会让同批请求挤到一台。用 power-of-k-choices(随机抽几个选最闲的)+ 本地补记刚放下的沙箱 + 节点自查容量决定接收与否。全局视图可以过时,不超容量由节点保证,且调度器不需要持久状态。
- 如果轮间状态能完整落盘,直接改成每轮重建环境 —— 用创建开销换驻留内存,这是一条判据清楚的路(判据就是「能不能完整落盘」)。
要提醒的是:如果不便重启环境(模型装了依赖、起了后台服务),第 3 条不适用,就只能靠 1 和 2 压驻留。另外 DSec 还观察到提前整份拉取镜像反而更慢 —— 按需加载完成整批任务的时间与镜像全部预缓存在本地时差不多,先整份拉取要慢约 1.7 倍(镜像 4–12 GB,运行时其实只读到 4%–13%)。
3. 一次工具调用超时了,你不知道它是「没执行」还是「执行了但结果丢了」。直接重试有什么问题?应该怎么设计?
答案
直接重试的问题是:它把「不确定」当成了「没发生」。 如果操作其实已经执行(比如已经付款、已经创建了资源、已经发了消息),重试会让同一件事发生两次,而这类操作的代价不可逆。
正确顺序是「先查询,再决定重试」:
- 查持久化结果,核对操作标识与输入文件版本。有结果就接续,不重试。
- 没有结果时,判断该操作是否幂等 —— 幂等指「以同一操作标识重复调用,最终业务效果与调用一次相同」。只读查询或幂等操作重试不成问题;付款 / 创建资源 / 发消息这类不幂等的操作,需要目标系统支持操作标识、事务或补偿才敢重试。
- 区分两种"重复":同一份结果被传送两次 → 消息层去重就够了(累计 CPU 时间不变);同一项测试实际执行两次 → 已经烧了两份资源(3 CPU·秒 → 6 CPU·秒),只能靠持久化结果 + 幂等协议避免。
再往上,要把持久性语义定义清楚。 报告「完成」时保证的是什么?只保证答案已生成,还是保证消息、工具结果和恢复位置都已保存?只对聊天日志 fsync() 不够 —— 恢复所需信息可能分别在工作目录、日志和外部数据库里,控制器还要核对三处一致。而且「保存成功」本身有等级:写入本地页缓存 / 完成本地 fsync() / 收到云端持久化确认,能扛的故障完全不同(本地 fsync() 能扛进程重启,扛不住整台机器 + 磁盘不可用)。
最后一条硬边界:把一轮「模型决策 — 工具执行 — 保存结果」当事务提交(这是 Agent 事务 / ACID 的方向)能保证本地一致,但保证不了外部服务恰好执行一次 —— 那一侧必须靠操作标识 / 幂等接口 / 补偿处理,仅本地记「已提交」不行。
4. 你要给平台的「保存频率」定一个策略:每个请求保存一次,还是攒一批再保存?怎么论证?
答案
把它当成一次显式的权衡,而不是"越频繁越安全"。 用书里的算例:100 个请求依次执行、每个 200 ms;每次持久化提交固定开销 8 ms,每写一条记录 2 ms;提交期间暂停处理请求。
| 保存方式 | 保存共用时 | 无故障总用时 | 故障后最多重做 |
|---|---|---|---|
| 每个请求提交一次 | ms | 21 s | 0.2 s |
| 每 10 个请求提交一次 | ms | 20.28 s | 2 s |
每 10 个一起提交,正常路径省 0.72 秒,但恰好赶在提交前故障就要重做 2 秒的计算。 所以论证要说三件事,而不是两件:
- 正常路径的开销(完成时间)
- 故障后最多重做多少(这是上面那张表里最容易漏的一列)
- 「已返回但未保存」的请求数,并核对重启后实际能恢复到哪 —— 这一条是校验,不是估计
再加一条异步优化的陷阱:如果改成「每轮先存本地、再后台异步上传、上传失败不重试也不中止」,三轮里第二轮上传失败、第三轮成功,本地历史完整、云端却只连续完整到第一轮。若此时换机器仅凭云端恢复,就撞上缺口。所以云端要记的不是「收到的最后一轮」,而是「从起点连续保存到了哪一轮」;更严格的做法是补齐缺口轮次,或用完整本地副本重建并核对。
删日志同理:要删前两轮,必须先保存一份能恢复到第二轮结束的完整状态、并把它与轮次一起提交;否则即使剩余日志都读完,也应返回 incomplete。
5. 「我们的 Agent 任务成功率只有 80%,能不能靠重试提上去?成本会不会爆炸?」你怎么答?
答案
能提上去,但要算恢复树,不能只算单次价格。 用书里的算例:首次尝试 10 秒、0.010 美元,80% 直接成功;12% 进局部修复(再加 0.006 美元 / 4 秒,条件成功率 60%,其余升级);其余走升级到更强模型(加 0.030 美元 / 8 秒,条件成功率 98%)。
结果:总成功率 约 99.7%,其中约 95.0% 在前 20 秒内完成(先修复再升级那条路径要 22 秒、超期)。每项成功任务的平均成本从只做首次的 美元升到约 0.0146 美元(按时通过 0.0153 美元)。
几个必须说清的判断:
- 分母是成功任务数,分子是全部支出。 失败尝试也烧了模型和工具资源,不能从总支出里扣。十项花 1 美元、五项成功就是每项 0.2 美元;只统计成功那五项会得到 0.1 美元,正好漏掉一半。
- 成本和成功率是两条轴。 恢复策略同时提高了完成比例和单位成本。该不该付,取决于业务对「提交按时成功率」的门槛(例子里若要求 ≥95% 按时成功,就必须付这笔钱)。
- 超期的成功也是成本。 约 4.7% 的提交要到第 22 秒才完成 —— 花了资源却不能计入按时完成数。所以如果只缩短评价期限,执行过程不变,只是"按时成功"更少;如果限制重试次数,则会真的少执行步骤,支出和成功率同时变。
- 要区分「哪一类失败」。 程序结果不对 → 要修程序;工具进程因资源不足中止 → 要恢复执行。两类失败的后续成功率不同,在恢复树里应该是不同节点,不能共用一个概率。