一个 agent 手里有预算,为什么没把预算铺到不同基座、不同后训练方法的比较上?arXiv:2611.03551 那篇用 15 个模型、八套 coding-agent 配置跑企业 text-to-SQL 的交付阶段,最后只有 25% 的 run 把验收条件全过完。我盯着这个数字,没有先怪模型能力,先想到 loop 的结构。论文把这条列成瓶颈之一——underused compute budgets / insufficient comparison across base models and post-training methods——但它没接着往下挖。这篇我们只挖这一层:写一个最小能跑的 agent loop,再给它加一个跨步计划,跑一遍看差别。
第一步,搭最朴素的采样循环。管它是哪个框架封的,最里面都长这样:
from collections import Counter
ACTIONS = [("base_model", "A"), ("base_model", "B"), ("base_model", "C"),
("post_train", "sft"), ("post_train", "lora")]
def execute(action):
# 模拟一次训练配方执行:只返回这一步覆盖到的维度
return {"covered": action[0]}
class NaiveAgent:
def __init__(self, budget):
self.budget = budget
self.trace = []
def propose(self):
if not self.trace:
return ACTIONS[0]
# 没有计划,只从近期上下文里挑一个最常出现的动作
counts = Counter(a for a, _ in self.trace)
return counts.most_common(1)[0][0]
def run(self):
while self.budget > 0:
a = self.propose()
r = execute(a)
self.trace.append((a, r))
self.budget -= 1
return self.trace
for a, r in NaiveAgent(6).run():
print(a, r["covered"])
跑出来是这样:
('base_model', 'A') base_model
('base_model', 'A') base_model
('base_model', 'A') base_model
('base_model', 'A') base_model
('base_model', 'A') base_model
('base_model', 'A') base_model
预算 6 个单位全花了,但全花在同一个动作上,post_train 维度一次都没碰到。这个版本还不够狠:真实 loop 里 propose 不是 Counter,多半是 LLM 按 P(action|context) 采样。采样温度调低,它更会重复局部看起来成功的动作;调高也只是乱跳,不会自动去补没跑过的格子。两种情况都指向同一个结构问题——loop 的每一步没有“还剩哪些组合没跑”的状态。它只有上下文,上下文里没有跨步计划,propose 想负责全局搜索也拿不到全局信息。
第二步,把缺的东西补上:一个独立于上下文的跨步计划。
BASES = ["A", "B", "C"]
POSTS = ["sft", "lora"]
class PlannedAgent:
def __init__(self, budget):
self.budget = budget
self.trace = []
self.remaining = [(b, p) for b in BASES for p in POSTS]
def propose(self):
return self.remaining.pop(0) if self.remaining else None
def run(self):
while self.budget > 0:
step = self.propose()
if step is None:
break
self.trace.append(step)
self.budget -= 1
return self.trace
for action in PlannedAgent(6).run():
print(action)
输出:
('A', 'sft')
('A', 'lora')
('B', 'sft')
('B', 'lora')
('C', 'sft')
('C', 'lora')
同样 6 个预算单位,这个 loop 把所有六个格子都踩了一遍。差别不在模型,在 self.plan:一个显式的、独立于上下文的跨步状态。真实的后训练配方比这个格子复杂得多——数据、步数、硬件、验证集——但计划的结构一样:先把要比较的维度做成笛卡尔积,每走一步就划掉一个还没覆盖的格子,直到预算跑完或格子全被覆盖。没有这个结构,预算花不出去不是错觉,是控制流里没有地方可以花。
到这一步就清楚了:所谓“交付阶段只有 25% 的 run 满足所有 acceptance criteria”,很可能不全是模型执行能力不行。八套 coding-agent 配置,如果每套的 loop 都缺这个跨步计划,它们会在少数看起来能过的配置上耗掉预算,却没跑够足以比较不同基座/不同后训练方法的组合;到头来验收标准一全量查,就漏。论文作者在补救建议里写的那条 explicit experiment-environment specification,覆盖 hardware、budget scope、production checks,正是这个计划结构在评测侧的样子。他们没把它叫 agent loop,但往回对,它恰好补的是我们上面 self.plan 缺的那个位置。
有人可能会说,给 agent 加个“先列出所有组合”的 prompt 不就行了。那是把计划交给采样,仍然不牢靠。LLM 可以在第一步输出一个漂亮的计划文本,但后面的执行 loop 如果不把计划变成每个 step 能查的硬状态,计划就只是上下文里一段话,过了几十步就被新的执行结果稀释掉,proposal 又会回到局部采样。真正的跨步计划必须是一个独立的数据结构,不是一段 prompt。
这里顺便纠正一个容易想岔的地方:预算用不满不一定是物理 GPU 闲着。上面的 NaiveAgent 预算也是花完的,但它花在了重复动作上。论文里那条瓶颈写的是 underused compute budgets,我一开始也以为是指有的 run 提前停了、机器空转;再看八套配置只有 25% 通过验收这个数字,更可能的是预算被低信息量的动作占满,真正该花的对照实验没排上。严格说,“用不满”应该是“没用在刀刃上”。这个区别决定修法不一样——如果只是机器空转,加个重试就完了;如果是计划缺失,得改 loop 的结构。
想再深一层,有一个入口可以直接动手:把上面 PlannedAgent 的 self.remaining 换成一个真实的后训练配方,比如固定数据、固定 eval,只换 base model 和 post-training method,然后给每个格子加一条 acceptance criteria,看 coverage 从多少爬到多少。爬不动的地方就是计划粒度太粗,不够覆盖生产检查。翻开源码这步,等人把 forward-deployed agent 的 harness 放出来再对照;没放出来之前,上面这两个最小 loop 就是机制的骨架。这块地我据过,往下还有的是。
