Lecture 15 · 智能体的工具使用:反馈、接口与推理系统 · Agentic Tool Use

Efficient ML Inference at Scale · Lecture 15。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:Stanford CS349E / EE339 Efficient ML Inference at Scale(Autumn 2026),原课第 17 次课 · Thu Nov 19 · Agentic Tool Use(Fredrik Kjolstad)。 依据说明:这次课没有指定阅读材料,课程网站也没有 slides。本讲以公开文献为主线:S. Yao et al., ReAct(ICLR 2023);J. Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering(NeurIPS 2024);KernelBench(第 14 讲)的迭代优化实验;R. Abhyankar et al., InferCept: Efficient Intercept Support for Augmented Large Language Model Inference(ICML 2024);H. Li et al., Continuum: Efficient and Robust Multi-Turn LLM Agent Scheduling with KV Cache Time-to-Live(2025–2026)。前三篇和 InferCept 用的是全文,Continuum 只用了摘要。内容的取舍是我定的,不代表课堂上实际讲了什么。 配套代码 Code:code/lecture15/(numpy;一个模块 + 5 个脚本,最长的约 15 秒) 先修:第 14 讲(智能体写内核、评估器)、第 7 讲(KV cache 管理、前缀缓存、Mooncake)、第 5 讲(服务模拟器 StepModel)、第 10 讲(分块矩阵乘法、掩码、同步)。 预计阅读时间:2.5–3 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标 Learning goals

学完这一讲,你应该能够:

  1. 描述智能体的”思考 → 调用工具 → 观察”循环(ReAct),说出一个写内核的智能体需要哪些工具、各返回什么。
  2. 用信息论衡量一种反馈有多”细”:一次工具调用能把不确定性减少多少比特;解释为什么”只说对错”的测试不够,以及”下一步调用哪个工具”是信息和时间的权衡。
  3. 用 SWE-agent 的原则设计工具接口:动作简单、反馈有信息量但简洁、有护栏;会算一个多轮会话的上下文和 prefill 账,知道前缀缓存和历史截断之间的冲突。
  4. 比较重复采样、单线迭代、树搜索这几种”脚手架”,说出用多轮强化学习训练工具使用时奖励怎么设计。
  5. 从推理系统的角度描述智能体的负载(多轮、输入远多于输出、频繁暂停),用 InferCept 的公式比较”保留 / 丢掉重算 / 换到 CPU”三种处理,理解 TTL 与租雪板问题,并读懂一个服务模拟的结果。

0.2 本讲地图 Map

§1  从一次调用到一个循环                                       ← 代码 01
§2  反馈有多少信息                                             ← 代码 01、02
§3  智能体-计算机接口与上下文                                   ← 代码 03
§4  脚手架、预算与训练
§5  推理系统的视角:工具调用期间的 KV cache                     ← 代码 04、05
§6  工具使用的安全与可靠性
§7–11  小结、常见误区、练习(含答案)、延伸阅读、下一讲预告

0.3 本讲的代码

agent_lab.py 有四部分:

  • A. 一个有 bug 的内核和六种工具:用 numpy 逐块模拟一个 32×32×32 分块的矩阵乘法内核,可以注入 7 种真实的 bug(第 10 讲的掩码、网格、同步、累加精度、下标);工具有:只说对错的测试、给出错误模式的详细测试、用构造的输入(单位矩阵)测试、跑两次看是否确定、memcheck(越界)、racecheck(竞争),每种有一个假设的耗时。贝叶斯更新、信息增益、挑工具的策略。
  • B. 上下文的账:真实的编译器输出、错误清单 vs 摘要的 token 数;多轮会话的上下文、prefill(有 / 没有前缀缓存)。
  • C. InferCept 的浪费公式:保留 / 丢掉重算 / 分块重算 / 换出,盈亏平衡点。
  • D. 服务模拟器:很多个智能体会话跑在一块 H100 上(Llama-3.1-8B),7 种 KV 处理策略。每步时间用第 5 讲的 StepModel。

0.4 记号 Notation

符号含义
轮 turn智能体的一次”生成 → 调用工具 → 拿到观察”
观察 observation工具返回、追加进上下文的内容
HH香农熵(比特):H=−∑plog⁡2pH = -\sum p \log_2 p
信息增益H(先验)−E观察[H(后验)]H(\text{先验}) - \mathbb{E}_{\text{观察}}[H(\text{后验})]:一次工具调用平均减少多少不确定性
拦截 interceptionInferCept 的说法:生成被工具调用打断(也包括等人回复)
TINTT_\text{INT}一次拦截(工具调用)的时长
CC、CotherC_\text{other}被拦截请求的上下文长度;其他正在运行的请求的上下文长度之和(token)
MM每个 token 的 KV 字节数(Llama-3.1-8B、BF16:128 KiB)
Tfwd(n)T_\text{fwd}(n)一次前向处理 n 个 token 的时间

1. 从一次调用到一个循环 · From one call to a loop

代码:01_agent_loop.py

1.1 什么是”智能体”

在这门课里,智能体 = 一个 LLM + 一组工具 + 一个循环。每一轮,模型读上下文,决定调用哪个工具、传什么参数;系统执行工具,把结果追加回上下文;如此往复,直到模型认为任务完成。

工具对模型来说是一个有签名的函数:名字、参数(通常用 JSON Schema 描述)、返回值的格式。模型输出一段结构化的”调用”,由外面的程序真正去执行——编译代码、跑测试、查文档、读文件。各家 API 都支持这种”函数调用”;Anthropic 在 2024 年 11 月发布的 MCP(Model Context Protocol)是一个开放协议,让工具以统一的方式接到不同的模型和应用上。

1.2 ReAct:思考和行动交替

ReAct(Yao et al., ICLR 2023)的格式很简单:Thought(为什么做下一步)、Action(调用工具)、Observation(工具的结果)交替出现。两件事一起做的好处:

  • 只想不做(思维链)的推理没有接地气,容易幻觉、错误累积;ReAct 在 HotpotQA、Fever 上让模型通过一个简单的维基百科 API 查事实,克服了这类问题。
  • 只做不想,模型不知道自己在干什么、什么时候该调整计划;在 ALFWorld 和 WebShop 两个交互式任务上,ReAct 只用一两个示例就比模仿学习、强化学习的方法高出 34% 和 10%(绝对成功率)。

Toolformer(Schick et al. 2023)走的是另一条路:让模型自监督地学会在文本里插入 API 调用(计算器、问答、搜索、翻译、日历)——只保留那些”让后面的文本更好预测”的调用。今天的智能体大多结合两者:训练出会调用工具的模型,再在推理时用 ReAct 式的循环。

1.3 一个写内核的智能体的工具箱

工具返回什么典型耗时(量级)
编译(nvcc / Triton)成功,或者错误信息(行号、原因)几秒到一分钟(PyTorch 的 CUDA 扩展编译常要几十秒)
测试对 / 错;好的接口还会给出哪里错、错多少秒
计时运行时间(第 14 讲 §4 的那些陷阱都在这里)秒
profiler(Nsight Compute)每个内核的吞吐、占用率、瓶颈几十秒
sanitizer(compute-sanitizer)越界读写、共享内存竞争、未初始化的读让程序慢一两个数量级
文档 / 规格 / 代码搜索文本亚秒

01 脚本用一个”有 bug 的分块矩阵乘法”把这些工具具体化。7 种 bug 都是第 10 讲里真会犯的错:K 方向最后一块没加掩码、K 循环少算一块、网格大小向下取整、写回越界、少一次 __syncthreads、FP16 累加、B 的下标写反。每种 bug 在三种形状上(128³ 都是块的整数倍;100×70×90 都不是;64×64×4096 的 K 很长)留下的痕迹:

bug只测对错(128³ / 100×70×90 / K=4096)详细测试(100×70×90)其他工具
K 尾巴没掩码对 / 错 / 对几乎全错跑两次不同;memcheck:越界读
K 循环少一块错 / 错 / 错几乎全错—
网格向下取整对 / 错 / 对最后 4 行、最后 6 列错跑两次不同(未初始化的显存)
写回越界对 / 错 / 对每行开头 26 列被写成 0memcheck:越界写
少一次同步错 / (常常)对 / 错—跑两次不同;racecheck:竞争
FP16 累加错 / 错 / 错零星的小误差—
B 下标写反错 / 错 / 错几乎全错单位矩阵输入时对

最重要的一行观察:在”整齐”的形状上,三种和尾巴有关的 bug 完全看不出来——而很多人(和模型)写测试时正好只用这种形状。


2. 反馈有多少信息 · How much information is in feedback

代码:01_agent_loop.py、02_feedback_information.py

2.1 用比特衡量

假设智能体知道 bug 是 8 种情况之一(含”没有 bug”),一开始同样可能:不确定性是 log⁡28=3\log_2 8 = 3 比特。一次工具调用之后,按贝叶斯公式更新”是哪个 bug”的概率;信息增益是不确定性的期望减少量:

IG(a)=H(先验)−∑oP(o) H(后验∣o).\text{IG}(a) = H(\text{先验}) - \sum_{o} P(o)\, H(\text{后验} \mid o).

一个只说 PASS / FAIL 的测试只有两种结果,所以一次最多 1 比特。02 演示 1 的结果(从均匀先验出发):

动作信息量(比特)耗时每秒比特
详细测试 · 100×70×902.093 s0.70
详细测试 · 128³1.753 s0.58
memcheck · 100×70×901.0640 s0.027
只测对错 · 128³1.003 s0.33
只测对错 · 100×70×900.573 s0.19
racecheck(任一形状)0.54120 s0.005

(构造输入的测试、跑两次在中间。)同样是”测试”,给出错误模式(哪些行列、误差多大)的那一种带来两倍以上的信息;同样是只测对错,换个形状信息量也不同——它决定了”哪些 bug 在这里会出错”。

2.2 下一步调用哪个工具

有了信息量和耗时,“下一步做什么”就是一个决策问题。01 演示 2 的轨迹(真正的 bug 是”写回越界”):

[1] Thought: 还剩 8 个可能;test_verbose·edge 信息量 2.09 比特、3 s,每秒 0.70 比特,最划算。
    Action: test_verbose(shape=edge)
    Observation: FAIL:2492 / 7000 个元素超出容差(35.6%);几乎整列都错的列 0–25;最大误差 36.6
[2] Thought: 还剩 2 个(store_overflow、missing_sync);test·nice 每秒 0.130 比特,最划算。
    Action: test(shape=nice)   Observation: PASS
[3] Thought: store_overflow 的概率 1.000。修法:写回 C 时加 n < N 的掩码。
    Action: edit + compile
[4] 复查:三种形状都 PASS;memcheck 没有越界。

两次诊断、两次编译、四次复查,假设的工具时间合计 105 秒。这里的”思考”是一个固定的规则(维护概率分布、挑每秒信息量最大的工具),不是语言模型;但它把一个好的工程师调试时做的事情显式化了:先用便宜、信息多的工具缩小范围,再用贵的工具确认。

02 演示 2 比较了七种策略(8 个 bug × 10 个随机种子):

策略找到 bug平均调用次数平均工具时间
P1只跑 128³ 的对错测试0%8.024 s
P2对错测试轮流用三种形状22%6.720 s
P3详细测试轮流用三种形状74%3.310 s
P4信息量最大(所有工具)100%2.16 s
P5每秒信息量最大(所有工具)100%2.16 s
P6每秒信息量最大(不用 sanitizer)100%2.16 s
P7每秒信息量最大(不用 sanitizer、也不用构造的输入)75%1.85 s

几个结论:

  1. 反馈越细,需要的调用越少:只说对错时 8 步都找不到;详细测试 74%。
  2. 工具集决定了能发现什么:P7 永远分不开”K 循环少一块”和”B 下标写反”——在随机输入上它们一模一样(几乎全错、误差大),修法却完全不同。喂一个单位矩阵(B 转置之后还是它自己)一下就分开了。构造的输入、边界输入是人写测试时会想到、随机测试很难碰到的东西(第 14 讲 §2.3 的 eps bug 也是这样)。
  3. P4 和 P5 在这里一样,因为信息最多的工具恰好也最便宜;如果最有信息的工具很贵,两者会分开——“每秒信息量”是更合理的目标。

2.3 和 KernelBench 的对应

KernelBench 的迭代优化实验(第 14 讲 §2.5)喂回三种东西:上一次的代码 G、编译和执行的结果 E、profiler 的输出 P。DeepSeek-R1 在 Level 2 上的 fast_1 从 36% 升到 72%(G+E+P);10 轮以内 90% 以上的题都能写出能运行的内核,剩下的几乎都是”结果不对”——论文的解释是”正确性的反馈远没有执行失败的信息具体”。用这一节的语言说:**编译错误带着行号和原因,信息量大;“不等于参考”至多 1 比特。**把”不对”变成”哪里不对、错多少、什么模式”,是工具接口设计里最便宜的一步。


3. 智能体-计算机接口与上下文 · The agent-computer interface

代码:03_context_and_interface.py

3.1 SWE-agent:接口是为模型设计的

SWE-agent(Yang et al., NeurIPS 2024)提出”智能体-计算机接口”(ACI):LLM 智能体是一类新的”用户”,有它自己的长处和短处,应该给它设计专门的接口,就像给人设计 IDE 一样。用 GPT-4 Turbo,SWE-agent 在 SWE-bench 的 2,294 道题上解决了 12.47%,此前最好的非交互式方法是 3.8%;在 300 道题的 Lite 子集上是 18.0%,比只给一个 Linux shell 的基线多 10.7 个百分点。他们总结了四条原则:

  1. 动作要简单、好懂:bash 命令有几十个选项;少数几个选项、文档简短的命令更好用。
  2. 动作要紧凑、高效:重要的操作(浏览、编辑文件)应该一步完成,而不是要组合很多步。
  3. 反馈要有信息量,但要简洁:告诉智能体环境的状态和它刚做的事情的效果,不要多余的细节。
  4. 护栏减少错误的传播:例如编辑时自动跑语法检查,有错就拒绝这次编辑并说明原因。

他们的消融(SWE-bench Lite,解决率):

改动解决率
完整的 SWE-agent18.0%
编辑命令不带语法检查15.0%
没有编辑命令10.3%
搜索结果逐条翻看(而不是摘要)12.0%
没有搜索命令15.7%
文件查看器一次显示 30 行 / 整个文件(而不是 100 行)14.3% / 12.7%
保留全部历史(而不是只留最近 5 次观察)15.0%

逐条翻看的搜索比根本没有搜索还差:模型会把每一条结果都看一遍,耗尽预算和上下文。

3.2 工具输出有多长

03 演示 1 量了几个真实的工具输出(token 数按”英文 4 个字符一个、汉字一个字一个”估计):

工具输出约多少 token
clang 编译一段 20 行的错 C 代码(-Wall -Wextra),完整输出297
只留 error 行26
写回越界的测试,每个错误元素一行(2,492 行)25,099
同一个测试的模式摘要37
B 下标写反的测试,每个错误元素一行(16,316 行)171,391
同一个测试的模式摘要36

一个”老实地把所有东西都打印出来”的测试工具,一次输出就可能超过很多模型的整个上下文窗口;而摘要只要几十个 token,信息还更有用(§2)。真实的 CUDA 扩展编译出错时,输出常常是这个 C 例子的几十倍(模板实例化、头文件链、ninja 的日志)。

3.3 一个会话的上下文账

每一轮,LLM 要把整个上下文再读一遍(prefill)。设系统提示 + 任务 4,000 token、30 轮,每轮模型输出 6,000(带思维链)或 1,500(只留代码和摘要)token,工具输出 4,000(详细)或 120(精简)token(03 演示 2,单价是假设的,只用来比较):

配置最后一轮的上下文超过 128kprefill(无缓存)prefill(前缀缓存)费用(缓存,美元)
思维链全留 + 详细输出294,000第 14 轮4.47 M0.29 M4.83
思维链全留 + 精简输出181,480第 22 轮2.78 M0.18 M4.02
只留摘要 + 详细输出163,500第 24 轮2.51 M0.16 M1.87
只留摘要 + 精简输出50,980—0.82 M0.05 M1.06

两点:

  • 没有前缀缓存,prefill 随轮数平方增长:第 t 轮要读 P+t⋅aP + t \cdot a 个 token,30 轮加起来约 30P+a⋅30⋅29/230P + a \cdot 30 \cdot 29 / 2。按 Llama-3.1-70B、H100、50% MFU 算,最”啰嗦”的配置一个会话要约 1 个 GPU·小时的 prefill;有了前缀缓存(第 7 讲),每轮只算新增的部分,变成线性。
  • 智能体的负载是”输入远多于输出”的:每轮输出几千 token,却要读几万到十几万 token 的上下文。Kevin-32B 在多轮强化学习里也因此不在后面的轮次里带完整的思维链,只带模型写的简短摘要。

3.4 截断历史 vs 前缀缓存

SWE-agent 的经验是只留最近 5 次观察(更早的换成一行占位符)效果更好。可从推理系统的角度:

历史的管理最后一轮的上下文prefill(前缀缓存)缓存命中
全部历史163,5000.16 M93%
只留最近 5 次观察67,7880.69 M47%
只留最近 2 次观察55,8240.31 M69%

上下文短了 2.4 倍,要 prefill 的 token 却多了 4 倍:每一轮都要把”刚刚变旧”的那次观察换成占位符,从那个位置往后的内容都变了,前缀缓存只能用到它之前。上下文管理的策略要和推理引擎一起设计:例如攒到一定长度一次性把一大段旧历史换成摘要(之后又是只追加的),而不是每轮都改中间。

3.5 护栏

护栏的意义是在昂贵的步骤之前拦住便宜就能发现的错误:编辑前的语法检查(SWE-agent:有 18.0%、没有 15.0%);第 14 讲 §6.6 的”所有分支的配置都能启动”的静态检查;编译前的类型检查;测试和 sanitizer 的超时(KernelBench 对超时和死锁只返回一句”你的内核执行超时”)。每一道护栏都把一次”白跑的工具调用”变成一条精确的反馈。


4. 脚手架、预算与训练 · Scaffolds, budgets and training

4.1 三种脚手架

同样的模型、同样的工具,怎样组织调用?

  • 重复采样(并行、独立):一次生成 k 个候选,挑最好的。需要一个能自动判分的评估器(第 14 讲);适合”一次就对的概率不低”的任务。
  • 单线迭代(顺序):每一轮带着上一轮的代码和反馈改进。KernelBench 表 2 在同样 10 次调用的预算下比较:6 个”模型 × 级别”的组合里,迭代优化在 5 个上好于重复采样;但它依赖模型会不会用反馈——DeepSeek-R1 能持续利用执行和 profiler 的反馈,DeepSeek-V3 和 Llama-3.1-70B 不一定。
  • 树搜索 / 进化(并行 + 顺序):METR 的 KernelAgent 同时跑 8 个尝试,每个新尝试从”按加速比抽中的之前的尝试”或参考实现出发,每题最多 300 次;AlphaEvolve(第 14 讲 §5)用程序数据库和岛屿保持多样性。

4.2 预算

智能体的每一步都有代价:LLM 的 token(输入大头,见 §3.3)、工具的时间(编译几十秒、profiler 更久)、评估用的 GPU。METR 的 KernelAgent 每题每个模型约 20 美元,他们估计 o3-mini 写出的内核平均在 H100 上跑 27 小时就能把这笔钱赚回来。决定”还要不要再试一次”,就是在比较下一次尝试的期望收益和它的成本;§2 的”每秒信息量”是同一个思路在诊断问题上的版本。

4.3 用强化学习学会用工具

Kevin-32B(第 14 讲 §7)把”调用工具 → 读反馈 → 改代码”做成多轮强化学习:每一轮把编译错误或运行时间喂回去;一个回答的奖励是它和之后各轮得分的折扣和(折扣 0.4),这样”给后面的改进打好基础”的一步也能拿到奖励;为了控制上下文,后面的轮次只带模型写的摘要。训练中出现的作弊(照抄参考、try/except 退回 PyTorch、继承参考类)靠更严格的格式检查压住——奖励就是评估器,第 14 讲 §3 的每一条都适用。


5. 推理系统的视角:工具调用期间的 KV cache · The serving view

代码:04_kv_during_tool_calls.py、05_agent_serving_sim.py

5.1 智能体负载长什么样

从推理引擎看,一个智能体会话是一串请求:每一轮的上下文是上一轮的超集(前缀复用),输入远多于输出,而且两轮之间有一段暂停——等工具、等人。InferCept 测了六类”增强”(表 1,平均值):

类型每次拦截的时长每个请求的拦截次数拦截时的上下文
算术(计算器)0.09 ms3.751,422
问答(查维基百科)0.69 s2.521,846
虚拟环境(ALFWorld)0.09 s28.182,185
聊天(等人回复,估计)28.6 s4.45753
生成图片(部分估计)20.03 s6.911,247
文字转语音(部分估计)17.24 s6.911,251

时长跨了五个数量级。写内核的智能体落在中间:读文件亚秒、跑测试几秒、编译和 profiler 几十秒(我们的模拟用的是一个假设的分布:读文件 40%、平均 0.3 s;编译 + 测试 30%、25 s;只跑测试 15%、4 s;profiler 15%、15 s)。

程序级的调度(把一个会话的多次调用当成一个整体来排队)是 CS349D 第 13 讲的主题(Parrot、Autellix);这一节只看暂停期间 KV cache 的去留。

5.2 三种做法与 InferCept 的浪费公式

今天的引擎把每次工具调用当成”请求结束”:KV 丢掉,工具回来后作为一个新请求,整个上下文重新 prefill。InferCept 把三种做法的代价统一成”浪费了多少显存 × 时间”:

  • 丢掉重算(Discard):重算期间这 CC 个 token 的显存被占着却没产出新 token;而且重算让这一次迭代变长,其他请求(CotherC_\text{other} 个 token 的 KV)都在等: Wdiscard=Tfwd(C)⋅(C+Cother)⋅M.W_\text{discard} = T_\text{fwd}(C) \cdot (C + C_\text{other}) \cdot M.
  • 保留(Preserve):暂停期间 KV 一直占着显存:Wpreserve=TINT⋅C⋅MW_\text{preserve} = T_\text{INT} \cdot C \cdot M。
  • 换出(Swap,同步):换出和换入各一次,期间整批请求都在等:Wswap=2 Tswap(C)⋅(C+Cother)⋅MW_\text{swap} = 2\, T_\text{swap}(C) \cdot (C + C_\text{other}) \cdot M。

InferCept 在混合负载上测到:Discard 有 37–40% 的前向时间花在重算上;Preserve 让”近一半的显存被暂停的请求占着”的时间超过 60%;朴素的同步 Swap 浪费 26% 的 GPU 资源、超过 25% 的时间在等传输。

04 演示 1 按这些公式算了一遍(Llama-3.1-8B、H100、CotherC_\text{other} = 100k token):毫秒级的工具,保留几乎不浪费;几十秒的工具,保留白占几十秒的显存。按 PCIe 5.0(有效 50 GB/s)算,8B 模型每 token 只有 128 KiB,12k 个 token 换出一次只要 31 ms(重算要 368 ms),所以连同步换出在大多数情况下都是最省的——带宽、模型大小、同时换的请求数一变,结论就变。

5.3 盈亏平衡与租雪板问题

保留和丢掉的盈亏平衡点:

T∗=Tfwd(C) (C+Cother)C.T^* = \frac{T_\text{fwd}(C)\,(C + C_\text{other})}{C}.

工具调用短于 T∗T^* 就该保留。04 演示 2:上下文 4,000、其他请求 50,000 时 T∗≈1.5T^* \approx 1.5 s;其他请求越多 T∗T^* 越长(重算拖慢的人越多)。

问题是事先不知道工具要等多久。这正是经典的租雪板问题:每天租(保留,按时间付)还是一次买下(丢掉重算,一次性付 DD),可你不知道要滑几天。确定性的最优策略是”租到累计的租金等于买价,再买”——TTL = T∗T^*:先保留 T∗T^*,没回来就丢。它最坏只比”事先知道时长的先知”多花一倍(竞争比 2);随机化的 TTL 能降到 e/(e−1)≈1.58e/(e-1) \approx 1.58。

04 演示 3(上下文 12k,工具时长按上面的分布抽 20,000 次):

策略平均浪费 / 先知单次最坏 / 先知
总是保留4.9118.3
总是丢掉重算1.6092.1
TTL = T∗T^*(3.4 s,不看分布)1.862.00
TTL = 0.6 s(按这个分布挑的最优值)1.227.1

T∗T^* 给的是最坏情况的保证;平均意义上它不一定最好(这个分布里 54% 的调用都超过 T∗T^*,TTL 在它们身上付了两倍)。知道分布就能做得更好:这个分布有两个峰,TTL 设成 0.6 s 时快的保留、慢的马上丢。所以 InferCept 按工具类型离线测时长;没有测量时,它用”已经等了多久”(T^INT=tnow−tcall\hat T_\text{INT} = t_\text{now} - t_\text{call})当估计——等得越久越该放弃,和 TTL 是同一个思路——论文报告达到了先知 93% 的性能。

5.4 InferCept 与 Continuum

InferCept 把三种做法都改进了,再动态组合:

  • 按层流水的换出:换第 i+2i+2 层的同时传第 i+1i+1 层,第 ii 层的显存已经给别人用;把一个序列的换入换出拆到好几个迭代里,每次只换”能藏在计算后面”的量(swap budget:一次迭代的时间 × PCIe 带宽 / MM。04 演示 4:批 64 的 decode 迭代约 19 ms,能藏下约 7,400 个 token)。
  • 分块重算:利用 decode 用不满的算力(第 5 讲),每个迭代只重算一块,不让整批等。
  • 最小浪费的调度:每个被拦截的请求取 min⁡(Wpreserve,Wchunked)\min(W_\text{preserve}, W_\text{chunked}),按浪费从大到小分配换出的预算,其余的保留或分块重算。

在 GPT-J-6B、Vicuna-13B、Llama3-70B 上,相对 vLLM 能承受 1.6–2 倍的请求速率(同样的归一化延迟),每秒完成的请求多一倍以上。

Continuum(Berkeley 等,2025–2026)关注多轮的智能体负载(SWE-bench、BFCL、OpenHands):预测工具调用的时长,给产生工具调用的请求的 KV 设一个 TTL——按”重算或重新加载的代价”和”被挤出显存后的排队延迟”来定;TTL 到了还没回来就释放;配合程序级的先来先服务调度。摘要报告平均作业完成时间改善 8 倍以上。它点出了 InferCept 公式漏掉的一项:排队。

5.5 我们的模拟

05 脚本:很多个智能体会话同时跑在一块 H100 上(Llama-3.1-8B,KV 的容量约 427k token = 55.9 GB);每个会话 3,000 token 的提示、12 轮、每轮生成 150 token、工具输出 600 token;会话结束马上开始下一个(闭环)。每轮在工具上等约 10.5 秒,一个会话大约 115 秒在等工具——大部分时间它的 KV 是冷的。7 种策略(每小时完成的会话数):

策略32 个会话96 个会话160 个会话
丢掉重算6411,073882
前缀缓存(释放但可被挤掉)7671,541904
一直保留7673420
TTL = T∗T^*(InferCept 公式)7671,2710
TTL,有人排队就不保留7671,422896
同步换到 CPU7451,7782,164
异步换到 CPU7632,0633,010
  • 显存够时(32 个会话),保留、前缀缓存、两种 TTL 完全一样;丢掉重算少 16%,它的 prefill 里 88% 都在重算。“一直保留”平均有 24.9 GB 显存被暂停的会话占着——将近一半,和 InferCept 的观察一致。
  • 显存不够时(96、160 个),“一直保留”掉下悬崖:显存被暂停的会话钉满,排在队首的会话进不来,引擎经常空转,直到什么都跑不动时才被迫丢掉一些。按 InferCept 公式算的 TTL 也被拖累:其他请求越多,T∗T^* 越长,越倾向于保留——可那正是显存最紧的时候。把”排队”算进来(有人排队就不保留)好一些,但仍不如从不保留、只留可被挤掉的缓存。
  • 换到 CPU 在 PCIe 5.0 的带宽下最好:CPU 内存相当于一块大得多的 KV 存储。带宽降到 1/4 时异步的几乎不受影响(2,063 → 2,030),同步的掉到 1,112;降到 1/16 时异步的也撑不住(616,不如前缀缓存的 1,541)。

演示 4 把工具全换成 0.3 秒的快工具:48 个会话时,保留(2,588)和 TTL(2,804)明显好于前缀缓存(1,915)——缓存刚被挤掉,它的主人 0.3 秒后就回来了,只好整个重算,这正是”短拦截应该保留”;64 个会话时 KV 总量超过显存,保留又崩了(230)。

没有一种”保留 / 丢掉”的规则在所有负载下都对:工具时长的分布、显存的余量、PCIe 的带宽一起决定答案。这就是 InferCept(按请求算浪费、动态选择)和 Continuum(预测时长、把排队的代价算进 TTL)在做的事,也是第 7 讲 Mooncake”把 KV 当成可以在 HBM、DRAM、SSD 之间搬动的数据”的延续。(模拟的简化:每步时间是 roofline 模型;每轮的显存一次预留;先来先服务、不抢占正在运行的会话;不模拟会话之间的前缀共享。)


6. 工具使用的安全与可靠性 · Safety and reliability

智能体执行的是模型生成的代码,调用的是有副作用的工具:

  • 沙箱:在隔离的容器 / 虚拟机里编译和运行,限制文件系统、网络和设备的访问;每次评估用干净的环境(第 14 讲:评估器本身也会被攻击)。
  • 资源限制:超时、内存上限、GPU 时间配额;死循环和死锁要能被杀掉,并给出一条清楚的反馈。
  • 工具的输出是不可信的输入:网页、文件、日志里的文字可能包含”指令”(提示词注入)。智能体应把它们当数据,不当命令;有副作用的动作(提交代码、发消息、删除文件)要另外确认。
  • 权限最小化:只给完成任务需要的工具和权限;记录每一次调用,便于审计和复盘。

7. 小结 · Summary

  1. 智能体 = LLM + 工具 + 循环。ReAct 让思考和行动交替;工具是有签名的函数,模型输出结构化的调用,系统执行、把观察写回上下文。
  2. 反馈的信息量可以用比特衡量。只说对错的测试一次至多 1 比特;给出错误模式的测试多一倍以上;构造的输入、不整齐的形状、sanitizer 回答别的工具回答不了的问题。“下一步调用哪个工具”是信息和时间的权衡;工具集决定了能发现什么。
  3. 接口是为模型设计的(SWE-agent):动作简单紧凑、反馈有信息量但简洁、有护栏。原样打印的工具输出可以比整个上下文窗口还长;摘要几十个 token 就够。
  4. 上下文的账:没有前缀缓存时 prefill 随轮数平方增长;智能体负载输入远多于输出;“只留最近几次观察”会让前缀缓存失效——上下文管理要和推理引擎一起设计。
  5. 脚手架:重复采样、单线迭代、树搜索 / 进化,各有适用的场合;用多轮强化学习训练工具使用时,奖励就是评估器。
  6. 工具调用期间的 KV:保留、丢掉重算、换出各有代价(InferCept 的公式);保留和丢掉的取舍是租雪板问题,TTL = T∗T^* 保证不超过先知的两倍,知道时长分布能做得更好;显存紧张时还要算排队(Continuum)。模拟里:显存够时”保留”最好,不够时它掉下悬崖;PCIe 够快时换到 CPU 最好。

8. 常见误区 · Common pitfalls

  • “测试通过了,内核就是对的。” 在 128³ 这种整齐的形状上,三种尾巴相关的 bug 完全看不出来;要用不整齐的形状、构造的输入、sanitizer。
  • “工具输出越详细越好。” 详细不等于有信息量:16,000 行的错误清单(17 万 token)不如一行模式摘要。上下文是有限、要付钱的资源。
  • “智能体只是一个模型的事。” 同样的模型,换一套工具和接口,成功率差很多(SWE-agent 的消融、§2 的 P1–P7)。
  • “截断历史一定省钱。” 每轮改中间的内容会让前缀缓存失效:上下文短了,prefill 反而多了。
  • “智能体负载和聊天差不多。” 它输入远多于输出、轮数多、有大量暂停;每轮重新 prefill 整个历史的代价随轮数平方增长。
  • “工具调用时保留 KV 总是对的。” 显存够时是;显存不够时,钉住的 KV 让排队的请求进不来,吞吐会掉下悬崖。
  • “按 InferCept 公式算出的 T 就是最优的 TTL。”* T∗T^* 只保证最坏情况不超过 2 倍;知道时长分布能做得更好;公式也没算排队的代价。
  • “换到 CPU 总是最好。” 依赖 PCIe 带宽、同时换的请求数、模型的 KV 大小和 CPU 内存;同步地换会让整个引擎停下来等。

9. 练习 · Exercises

练习 1(一次测试的信息量) 8 个同样可能的 bug,一个测试在其中 3 个上 FAIL、5 个上 PASS(确定性的)。它的信息增益是多少比特?

答案:后验熵:FAIL 时 log⁡23\log_2 3,PASS 时 log⁡25\log_2 5。信息增益 =3−(38log⁡23+58log⁡25)=H(3/8)≈0.954= 3 - (\tfrac38 \log_2 3 + \tfrac58 \log_2 5) = H(3/8) \approx 0.954 比特(一个两结果的测试,信息增益就是结果分布的熵)。

练习 2(每秒信息量) 工具 A 2 比特、3 秒;工具 B 3 比特、120 秒。按”每秒信息量”该先用哪个?什么情况下应该先用 B?

答案:A 0.67 比特/秒,B 0.025 比特/秒,先用 A。如果 A 能带来的信息已经用完(剩下的不确定性只有 B 能分开,例如竞争),或者工具的耗时不是瓶颈(比如可以并行调用),才轮到 B。

练习 3(下界) 8 个同样可能的 bug,只用”对 / 错”的测试至少要几次?如果一个测试有 4 种可能的结果呢?

答案:至少 ⌈3/1⌉=3\lceil 3 / 1 \rceil = 3 次;4 种结果时一次至多 2 比特,至少 ⌈3/2⌉=2\lceil 3/2 \rceil = 2 次。这都是下界,前提是每次都能把剩下的候选平均分开。

练习 4(另一种分开 bug 的办法) 不用单位矩阵,能不能从随机输入的误差矩阵里分开”K 循环少一块”和”B 下标写反”?提示:看误差矩阵的秩。

答案:少算最后一块时,误差是 −A:,K−32:KBK−32:K,:-A_{:,K-32:K} B_{K-32:K,:},秩至多 32;下标写反时误差是 A(B′−B)A(B' - B),几乎满秩。在 128³ 上实测秩分别是 32 和 126。一个报告”误差矩阵的秩”的测试工具就能分开它们——好的工具接口把调试经验变成一条反馈。

练习 5(上下文的账) 提示 PP 个 token,之后每轮新增 aa 个 token,共 TT 轮。没有前缀缓存时总共 prefill 多少?有前缀缓存时呢?代入 P=4000P = 4000、a=10,000a = 10{,}000、T=30T = 30。

答案:无缓存 ∑t=0T−1(P+ta)=TP+a T(T−1)/2=120,000+4,350,000=4.47\sum_{t=0}^{T-1}(P + ta) = TP + a\,T(T-1)/2 = 120{,}000 + 4{,}350{,}000 = 4.47 M;有缓存约 P+(T−1)a=294,000P + (T-1)a = 294{,}000。和 03 演示 2 的第一行一致。

练习 6(KV 有多大、搬多久) Llama-3.1-8B、BF16,12,000 个 token 的 KV 多大?在 50 GB/s 的 PCIe 上单向要多久?和重算一遍比呢?

答案:12,000×2×32×8×128×2=1.5712{,}000 \times 2 \times 32 \times 8 \times 128 \times 2 = 1.57 GB;31 ms;04 脚本里 Tfwd(12k)≈0.37T_\text{fwd}(12\text{k}) \approx 0.37 s,搬比算快 10 倍以上。

练习 7(盈亏平衡) C=4000C = 4000、Cother=50,000C_\text{other} = 50{,}000、Tfwd(4000)=0.109T_\text{fwd}(4000) = 0.109 s。T∗T^* 是多少?一次 0.7 秒的问答该保留还是丢掉?

答案:T∗=0.109×54,000/4,000≈1.47T^* = 0.109 \times 54{,}000 / 4{,}000 \approx 1.47 s;0.7 s < T∗T^*,保留。

练习 8(为什么是 T*) 如果把 TTL 设成 T∗/2T^*/2 或 2T∗2T^*,最坏情况相对先知是几倍?

答案:TTL = T∗/2T^*/2:时长刚超过 T∗/2T^*/2 时付 D/2+D=1.5DD/2 + D = 1.5D,先知付约 D/2D/2,3 倍。TTL = 2T∗2T^*:时长刚超过 2T∗2T^* 时付 2D+D=3D2D + D = 3D,先知付 DD,也是 3 倍。T∗T^* 是让最坏情况最小的选择(2 倍)。

练习 9(暂停的会话占多少显存) 32 个会话,每个会话从开始到结束约 132 秒(05 演示 2),其中约 115 秒在等工具;平均上下文按 7,500 token 算。“一直保留”时,平均有多少显存被暂停的会话占着?

答案:约 32×115132×7,500×128 KiB≈2732 \times \frac{115}{132} \times 7{,}500 \times 128\,\text{KiB} \approx 27 GB;05 演示 2 量到 24.9 GB(暂停发生在每一轮结束时,前几轮的上下文比平均短,所以实际略小)。显存给 KV 的只有约 56 GB,将近一半。

练习 10(截断让缓存失效) 每轮模型输出 1,500、工具输出 4,000 token,只留最近 5 次观察。粗略估计每一轮要 prefill 多少 token?和全部历史比呢?

答案:刚变旧的那次观察之后还有约 5 轮的内容要重新 prefill:约 5×(1,500+4,000)≈27,5005 \times (1{,}500 + 4{,}000) \approx 27{,}500 token;全部历史只要新增的 5,5005{,}500。03 演示 3 里 30 轮合计 0.69 M vs 0.16 M,平均每轮约 23k vs 5k。

练习 11(swap budget) 一次 decode 迭代 19 ms,PCIe 有效带宽 50 GB/s,每 token 128 KiB。每次迭代能”藏住”多少 token 的换入换出?换回一个 12k token 的会话要几个迭代?

答案:0.019×50×109/131,072≈7,3000.019 \times 50 \times 10^9 / 131{,}072 \approx 7{,}300 token;12k 需要 2 个迭代。

练习 12(值不值得) 一个智能体花 20 美元写出一个内核,能让一块 H100 的某个负载快 10%。H100 每小时按 2 美元算,这个内核要跑多久才回本?

答案:每小时省 0.1×2=0.20.1 \times 2 = 0.2 美元,20/0.2=10020 / 0.2 = 100 小时。(METR 用真实的数字估计 o3-mini 的内核约 27 个 H100·小时回本;只是量级上的对照。)


10. 延伸阅读 · Further reading

智能体与工具

  • S. Yao, J. Zhao, D. Yu, N. Du, I. Shafran, K. Narasimhan, Y. Cao, ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023. https://arxiv.org/abs/2210.03629
  • T. Schick et al., Toolformer: Language Models Can Teach Themselves to Use Tools, NeurIPS 2023. https://arxiv.org/abs/2302.04761
  • J. Yang, C. E. Jimenez, A. Wettig, K. Lieret, S. Yao, K. Narasimhan, O. Press, SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering, NeurIPS 2024. https://arxiv.org/abs/2405.15793
  • Anthropic, Model Context Protocol(2024-11)的规范与文档。
  • KernelBench 论文 §5.1(迭代优化与反馈)、Cognition 的 Kevin-32B 博客、METR 的 Measuring Automated Kernel Engineering(第 14 讲的延伸阅读)。

推理系统

  • R. Abhyankar, Z. He, V. Srivatsa, H. Zhang, Y. Zhang, InferCept: Efficient Intercept Support for Augmented Large Language Model Inference, ICML 2024. https://arxiv.org/abs/2402.01869
  • H. Li et al., Continuum: Efficient and Robust Multi-Turn LLM Agent Scheduling with KV Cache Time-to-Live, 2025–2026. https://arxiv.org/abs/2511.02230
  • A. R. Karlin, M. S. Manasse, L. Rudolph, D. D. Sleator, Competitive Snoopy Caching, 1988(租雪板问题与竞争分析的经典来源之一)。

本仓库

  • CS349D 第 13 讲:智能体工作流的程序级调度(Parrot、Autellix)。
  • 第 7 讲:PagedAttention、前缀缓存、Mooncake 的分层 KV 存储。
  • 第 14 讲:评估器、作弊与测量的陷阱——智能体的”奖励”从哪里来。

11. 下一讲预告 · Next lecture

最后一讲把整门课串起来:一个 token 从模型、精度、切分、服务系统、KV 管理、硬件、内核、编译器一路走下来,每一层的瓶颈和对策是什么;然后用前面各讲的模块搭一个端到端的部署规划器——给定模型、硬件、SLO 和负载,选精度、张量并行度、是否解耦、要几块 GPU——并用第 5 讲的模拟器检验它;最后讲怎样做一个推理系统的课程项目(选题、基线、测量、写报告)和这个领域的开放问题。

→ 第 16 讲:总结与课程项目

← 返回课程目录