跳到主要内容

Token 预算是调度问题,而非提示词问题

· 阅读需 11 分钟
Tian Pan
Software Engineer

当一个智能体(agent)给出的回答比上周差时,第一直觉往往是归咎于提示词(prompt)。有人会重新编写系统指令,删减几句话,增加一个示例,然后发布。有时这会有所帮助。但通常这毫无作用,因为提示词从来就不是问题所在。问题在于,一个冗长的工具调用结果静默地消耗了 18,000 个 token,将实际的任务指令推到了上下文窗口中关注度较低的中部位置,让模型在一个 70% 都是噪音的记录上进行推理。

这不仅是措辞问题,更是资源分配问题。资源分配在系统工程中有一个专属名称:调度(scheduling)。上下文窗口是一种固定大小的资源,多个消费者在其中竞争,而目前大多数智能体技术栈“调度”它的方式就像 1960 年代的批处理系统调度内存一样——先到先得,直到用完为止。

这种视角的转变至关重要,因为它改变了你构建产品的方式。如果 token 使用量是一个提示词问题,解决方法是编辑。如果它是一个调度问题,解决方法则是一个分配器(allocator):一个为每个消费者分配预算、强制执行上限、决定在压力下驱逐哪些内容并使整个过程可观测的组件。操作系统在几十年前就为内存解决了这个问题,而智能体框架正在以艰难的方式重新发现它。

上下文窗口就是 RAM,而没人写过内存管理器

思考一下在单次推理调用中,究竟有哪些内容在竞争空间:系统提示词、工具和函数定义(schemas)、从向量数据库检索到的片段、正在进行的对话历史、之前步骤的工具输出、少样本(few-shot)示例、子智能体的草稿本(scratchpad)推理。每一个都是一个在申请内存的进程,而上下文窗口就是它们共享的 RAM。

在真实的操作系统中,没有进程可以无限制地调用 malloc()。有一个内存管理器负责跟踪分配、强制执行限制,并在压力增大时将内容换出(page out)。在大多数智能体代码中,并没有对应的机制。检索器根据 top_k 获取尽可能多的片段。工具返回 API 返回的所有内容。历史记录每轮增加一轮。没有任何机制在它们之间进行仲裁。“分配策略”仅仅取决于这些字符串被拼接在一起的顺序。

这种方式在出问题之前一直有效。出问题的转折点很少是硬性的上下文限制错误——现代窗口足够大,通常都有空间。转折点在于质量。Chroma 的上下文腐烂研究测试了 18 个前沿模型,发现随着输入长度的增长,每个模型的表现都会下降,即使相关信息在技术上确实存在。性能遵循 U 型曲线:窗口开头和结尾的内容准确率最高,而困在中间的内容准确率会下降 30% 以上。上下文窗口并非均匀资源。Token 具有位置价值,忽略位置的分配器正在白白丢掉正确性。

因此,这种失败是无声的。你不会收到异常报错。你只会得到一个稍差的答案,接着又是一个,而仪表盘仍然显示绿色,因为技术上没有任何东西崩溃。这就是资源未受管理的典型特征。

各组件的预算上限

调度器做的第一件事就是限制每个消费者的上限。你需要提前决定每个组件可以占用的窗口最大份额,并强制执行。

对于一个智能体,一个可行的初始分配方案大致如下:系统指令占用 10–15%,工具定义占用 15–20%,检索到的知识占用 30–40%,其余部分预留给对话历史和模型自身的推理。具体的数字并不如上限的存在本身重要。上限将无限制的 malloc() 转换为固定分配,这意味着一个组件再也不会因意外而让其他组件“饿死”。

最紧迫需要上限的组件是工具输出。在某项已发布的上下文工程基准测试中,48,400 个总 token 中有 30,400 个仅来自工具结果——而且其中 40% 到 60% 的 token 可以在不产生可衡量性能损失的情况下移除。工具输出是智能体系统的内存泄漏。API 返回了一个包含 200 行的 JSON 块,智能体只需要其中三个字段,而剩下的 197 行现在留在窗口中,降低了后续每一步的质量。生产环境的工具链已经开始直接处理这个问题:Claude Code 会根据预算计算每个工具结果的 token 数,并将超大的结果持久化到磁盘而不是内联,专门捕捉那些虽然未超过字节限制但 token 密度极高的输出。

工具输出的上限不应该只是个礼貌的建议。它应该是一个硬性的截断或转移(truncate-or-offload)边界,在结果进入窗口之前就应用。任何超过上限的内容都会被存入磁盘、生成摘要或进入后续的检索调用——默认情况下绝不会直接放入提示词。

优先级与驱逐:压力之下哪些内容被换出

上限处理稳态,驱逐(eviction)处理危机。当所有需求的总和超过窗口时,必须有一些内容离开,而这种“离开”应该是一个决策,而不是字符串顺序导致的意外。

为每个组件分配一个优先级,反映其对当前步骤的重要性。系统指令和活动任务描述是不可驱逐的——它们是内核;如果它们消失了,一切都无法运行。智能体正在积极使用的工具定义具有高优先级。最近的对话轮次是中等优先级。旧的工具输出和陈旧的检索片段是低优先级——它们在三步之前很有用,现在大多是噪音。

驱逐随后从底部开始。生产环境工具链中的滑动窗口策略正是这样做的:当使用量超过阈值时,丢弃最旧的 30% 消息,然后以 10% 的增量继续丢弃,直到回到预算之内。这就是换名后的页面置换算法。值得改进的一点是,新鲜度只是价值的一个粗略代理。一个确立了关键事实的旧工具结果可能比最近一轮的客套话更重要。一个好的驱逐器会根据优先级和相关性进行评分,而不仅仅是根据时间——这更接近带有提示的 LRU(最近最少使用),而非纯粹的 FIFO(先进先出)。

至关重要的一点是,驱逐不是删除。被换出的内存帧会存入磁盘,并可以被重新调入(faulted back in)。智能体对应的做法是总结一个被驱逐的块,将完整版本带上句柄存储在外部,并在后续步骤需要时允许智能体重新检索。智能体应该能够重新调入自己的上下文。这种特性——将内容驱逐到可恢复的地方,而不是虚无中——正是调度器与断头台的区别。

饥饿故障模式

每种调度系统都有其特征性的故障,对于 Token 预算来说,这种故障就是“饥饿”:一个贪婪的消费者占用了过多的资源,导致另一个消费者无法取得进展。

具体表现为:冗长的工具结果挤占了推理预算。智能体调用了一个工具,该工具返回了 18,000 个 Token。从技术上讲,这些 Token 都在“上下文”中,所以不会报错。但模型现在对实际推理步骤剩下的有效注意力(Attention)要少得多,任务指令也已经漂移到了 U 型曲线中低注意力的中间部分。模型并不比昨天笨,它只是被“饿坏了”。它被要求在工具结果未消耗掉的区区几百个 Token 的注意力空间内进行思考。

这正是调度器存在的意义——防止这种 Bug。操作系统不会允许单个进程消耗掉所有 RAM 并卡死其他进程,它拥有各种限制以及一套根据优先级强制执行公平性的调度器。在智能体领域,对应的方案是推理预算预留:在上下文窗口中划出一块保证不受检索和工具输出干扰的区域,无论它们多么想要这块空间。你预留它的方式就像实时系统为关键任务预留 CPU 一样。这种预留是确保模型始终有思考空间的保证。

让上下文压力变得可观测

Token 预算被当作提示词问题的最深层原因在于,压力是不可见的。你无法像在 top 命令中看到 CPU 那样看到上下文窗口。因此,性能下降被诊断为“模型最近似乎变差了”,然后团队就开始反复调试提示词。

调度器通过发送遥测数据来解决这个问题。仪表盘上需要显示的指标并不复杂:

  • 每个组件的利用率 —— 在当前步骤中,每个消费者相对于其上限实际占用了多少窗口空间。
  • 驱逐事件 —— 哪些内容被移出了缓存,何时发生的,以及是从哪个优先级层级移出的。
  • 推理余量 —— 在扣除固定成本后,每一步给模型留下了多少 Token。
  • 饥饿标志 —— 任何单个工具结果超过窗口(例如)25% 的步骤。

有了这四个数字,“智能体感觉变差了”就变成了“在周二更改 Schema 之后,工具输出利用率从 35% 跃升至 70%,推理余量现在经常低于 5%”。这是一个可以诊断的故障。前者只是感觉。可观测性能将无声的质量衰减转化为图表,让人们在账单或支持工单爆发之前就能采取行动。

这里还有成本维度。LLM API 在每次调用时都会对整个上下文计费,因此一个任由历史记录无节制增长的智能体循环会导致成本呈二次方复合增长——一个运行 20 步的任务消耗的 Token 可能比初步估计的多 10 倍以上。一个能够封顶和驱逐内容的调度器不仅是在保护答案质量,它也是让发票金额保持线性增长的控制面板。

从分配器开始,而不是提示词

实际的做法是停止为了应对质量回退而修改提示词,转而构建一个尽可能精简的分配器(Allocator)。它不需要多么复杂就能超越目前大多数技术栈(它们通常什么都没有)。

一个最小可行性 Token 调度器只需做四件事:它为每个上下文组件分配预算上限。它在组装前强制执行这些上限,对超出的内容进行截断或转移。当总需求超过窗口大小时,它按优先级驱逐内容,并将驱逐的内容发送到可恢复的地方。并且它记录利用率,使压力可见。这只需几百行代码,它作为真正的仲裁层存在于组件和模型之间。

这种思维转变才是重点。“修剪提示词”是将上下文窗口视为一个待编辑的文档。而将其视为受调度的资源——具有上限、优先级、驱逐机制、预留空间和遥测数据——才是将其视为它的本质:它是拥有众多竞争消费者且在你自己构建一个管理器之前处于无人管理状态的共享内存。操作系统在半个世纪前就吸取了这个教训。现在的智能体系统规模已经大到必须也吸取这个教训,而构建分配器的团队将不再通过“看茶叶占卜”的方式来调试他们的上下文窗口。

References:Let's stay in touch and Follow me for more thoughts and updates