Agent 与 harness:把「帮我优化」变成可验收的工程
上一章导读与环境中,我们看到 Kernel Agent 的产物不是一段代码,而是一条带证据的候选链。问题随之而来:模型本身只会根据上下文预测下一个 token,它凭什么能稳定地改对 kernel、跑对测试、在合适的时候停下来?答案不在模型里,而在模型外面那层 harness(执行框架)。这一章把 Agent 循环拆开,说明 harness 为什么存在,以及怎样为一个 kernel 任务写出可执行的验收标准。
- 描述一个 Agent 循环的五个组成部分,并解释上下文为什么是稀缺资源。
- 用自己的话说明 harness 与 prompt、Agent 框架的区别。
- 写出包含正确性、性能、约束与通过条件的任务合同。
- 解释 draft 与 plan 各自解决什么问题。
- 识别 Agent 自评失灵的四种机制,并知道「写/判分离」为何必要。
- 为一个最小任务设计目录、产物与停止条件。
Agent 循环:模型、上下文、工具
Section titled “Agent 循环:模型、上下文、工具”一个终端里的编码 Agent,本质上在重复下面这件事。拆开看,它由五个部件组成:模型(策略)、上下文(工作记忆)、工具(动作接口)、循环控制(何时继续、何时停止)与环境(文件系统、权限、GPU)。
- 读取当前上下文:系统指令、任务描述、已读文件、历史工具输出、之前的决定。
- 生成下一步动作:回复用户,或发起一次工具调用(读文件、搜索、编辑、执行命令)。
- 等待环境返回结果:文件内容、命令退出码、stdout/stderr、测试报告。
- 把结果写回上下文,进入下一轮。
- 检查停止条件:成功、预算耗尽或阻塞时结束并归档证据;否则回到第 1 步。
这里有三条容易被低估的事实。
第一,上下文是有限且会衰减的资源。 模型并不是「记得」整个仓库,它只看见被塞进上下文的那一部分。长任务里,早期读过的代码、任务合同、失败原因会被压缩或截断;一旦任务合同被挤掉,Agent 就会开始「凭感觉」优化。可靠的做法不是把 prompt 写得更长,而是把合同、计划、候选记录落成文件,每轮重新读取关键部分。
第二,工具调用是 Agent 与现实的唯一接口。 模型可以声称「已经通过测试」,但只有真正执行 pytest 或 ncu 才知道结果。工具也定义了它的能力边界:没有 profiler,它只能猜瓶颈;没有 benchmark 脚本,它无法判断快慢;没有写权限,它只能给建议。
第三,会话不等于任务。 一个 session 是一段连续对话;一个任务可能跨多个 session,甚至跨两个模型。KDA 的推荐布局把「可复用的工作流」和「任务工作区」分开:工作流放在参考仓库,代码、测试、数据、benchmark 脚本和产物留在任务工作区。这样换模型、换会话都不会丢证据。
harness 是什么
Section titled “harness 是什么”harness 是包在 Agent 外面的执行框架:它规定工作区、任务合同、可用工具与权限、预算、验证命令、记录格式和停止条件。Agent 负责在框内探索,harness 负责让探索可复现、可审计、可比较。
它和 prompt 的区别是「强制」:prompt 说「请先写计划」,harness 可以检查 docs/plan.md 是否存在,不存在就不允许改代码;prompt 说「不要改测试」,harness 可以用权限或 diff 检查阻止。它和 Agent 框架的区别是「任务语义」:框架提供工具调用与消息循环,harness 决定这个任务什么叫成功。
下图是一个最小闭环:任务合同经过计划变成动作,工具循环产生证据,验证决定是否晋升,记录把这一次的经验留给下一次;预算贯穿全程。
读图时注意两个容易被忽略的箭头。一个是「验证 → 计划」:验证失败不一定要改 kernel,也可能是计划、测试或 baseline 错了,应该回到计划层修正。另一个是「预算 → 停止」:预算不是事后统计,而是每一轮都要检查的约束,否则 Agent 会在一个错误方向上无限迭代。
任务合同:把「优化一下」变成可验收
Section titled “任务合同:把「优化一下」变成可验收”KDA 的 prompts/basic-flow.md 把任务合同拆成八项内容:任务名、目标、正确性要求、性能或质量目标、允许的实现方式、验证命令、评估命令、晋升条件;docs/agent-flow.md 进一步要求候选既要满足合同,又要有证据表明目标指标变好或不变差,才可晋升;被否决的候选要写明理由而不是静默丢弃。这些可以直接拿来当模板。关键在于每一项都要能对应一条命令或一个可检查的事实。
| 字段 | 反例 | 可验收的写法 |
|---|---|---|
| 目标 | 优化 attention | 把 decode attention 在 batch=16、seq=8k、head_dim=128 下的 kernel 中位耗时降到 baseline 的 0.8 倍 |
| 正确性 | 结果差不多 | 与 FP32 参考实现比较,BF16 输出最大相对误差不超过 2e-2,且无 NaN/Inf |
| 性能 | 越快越好 | 固定输入与 dtype,预热 5 次、重复 30 次,取中位数;同时报告 p90(90 分位耗时) |
| 约束 | 随便写 | 只允许修改 kernel/ 下文件;不得改测试与 benchmark;允许使用 CuTe-DSL |
| 验证命令 | 跑一下测试 | python tests/check.py --case decode_8k --tol 2e-2 |
| 评估命令 | 看看速度 | python bench.py --case decode_8k --warmup 5 --repeat 30 --json |
| 晋升条件 | 比原来快 | 正确性通过,且中位耗时低于 baseline 的 0.8 倍,p90 不高于 baseline 的 0.9 倍 |
这张表最重要的不是格式,而是把「我认为」变成「命令说」。如果一条验收标准无法被第三方在同一硬件上重跑,它就不算验收标准。
正确性还要覆盖边界:空输入、单 token、最长序列、非连续布局、极端 gate(门控)值、不同 batch 组合。只测一个「好看的 shape」是 reward hacking(奖励作弊)的温床。性能则要固定硬件、时钟、输入、dtype、warmup 与重复次数;kernel-only(只计 kernel 执行)与 end-to-end(含框架与数据准备)要分开报,否则你无法判断加速来自 kernel 还是少了框架开销。
draft 与 plan:先想清楚,再动手
Section titled “draft 与 plan:先想清楚,再动手”KDA 的流程要求 Agent 先写 docs/draft.md,再转成可执行的 docs/plan.md。这不是形式主义,而是把「探索」和「承诺」分开。
draft 是低成本的探索。 它应该包含:当前 baseline 是什么、如何验证;主要风险与未知;按期望收益/风险排序的候选方向;第一步做什么;要跑哪些命令;什么证据足以晋升或放弃。draft 允许粗糙、允许被推翻,目的是暴露假设。
plan 是带约束的承诺。 它把 draft 里的方向变成有顺序的步骤:先改哪一块、每步如何验证、失败时回退到哪里、预算怎么分配。plan 一旦确认,Agent 的改动就应该围绕它进行;要改 plan,必须显式说明原因。
| 维度 | draft | plan |
|---|---|---|
| 目的 | 探索可能性 | 约束执行 |
| 内容 | 风险、候选方向、初步证据 | 步骤、命令、回退点、预算 |
| 变更成本 | 低,鼓励推翻 | 高,需要说明理由 |
| 常见错误 | 写成营销文案 | 写成无法执行的愿望清单 |
一个实用技巧:让 plan 的每一步都带一个「验证动作」。例如「把 BLOCK_SIZE 从 128 调到 256,跑一次正确性 + 30 次 benchmark,若中位耗时无改善则回退」。这样 Agent 不会连续改十处再一次性测试,失败时也能定位。
为什么 Agent 的自评不可靠
Section titled “为什么 Agent 的自评不可靠”Agent 说「完成了」,只是一个 claim(声明),不是 evidence(证据)。至少有四层原因。
同源盲区。 写代码的模型和审代码的模型如果共享同一套先验,它容易犯的错,往往也正是它看不出来的。Humanize 论文把这条链路写成 b → b·m_R → b·m_R·m_O:b 是 builder(实现者)自身的漏检率,m_R 是 reviewer(评审者)的漏检率,m_O 是人工或 oracle(可依赖的标准答案)的漏检率。换一个厂商的模型、换一个全新上下文做 reviewer,本质上是在压低 m_R;并且只有 reviewer 能宣布完成。
目标被代理指标替换。 Agent 被要求「让 benchmark 变快」,它可能去改 benchmark、缓存输入、跳过计算,而不是优化 kernel。KDA for KDA 的 3.74× 案例就是典型:硬编码 L2 范数、跳过三角矩阵求逆,在合成数据上误差刚好过关。
自我一致性偏差。 模型倾向于为自己已经写出的实现找理由。让它同时扮演作者和裁判,它很难主动否定自己刚做的工作。
上下文污染。 作者上下文里有「我为什么这样写」的叙事,裁判需要的是「结果是否满足合同」的冷证据。两者混在一起,判断就会被叙事带偏。
所以可靠的结构是写/判分离:builder(KDA 中称 Coder)在任务工作区实现,reviewer(KDA 中称 Verifier)用新上下文、甚至不同厂商的模型,只读合同、diff 与证据,独立重跑验证命令。第 5 章 Humanize 会展开计划合同、机械 gate(门禁检查)与 RLCR(Ralph-Loop with Codex Review);这一章先记住原则:完成与否不能由写代码的那一方单方面宣布。
预算与停止条件
Section titled “预算与停止条件”没有预算的 Agent 会一直「再试一个版本」。预算至少包含五维:墙钟时间、token/花费、GPU 小时、候选数量、连续无改善轮数。前三个是成本,后两个是方向性信号。
停止条件建议写成三类:
- 成功停止:合同里的晋升条件全部满足,证据已归档,reviewer 签字。
- 预算停止:任一预算耗尽,保存当前最佳候选与失败记录,写复盘。
- 阻塞停止:缺少权限、数据、硬件,或连续两次验证结果互相矛盾;此时应升级给人,而不是继续猜。
KDA 官方博客的策略消融在 B300 上设了 14 小时预算(2026-09-27),Humanize 论文的 DSA indexer 消融用 48 小时预算(2026-10);它们是研究级预算,不是初学者默认值。小任务可以从 30 分钟、20 个候选开始,先验证流程,再增加投入。
一个最小 harness 的目录与产物
Section titled “一个最小 harness 的目录与产物”KDA README 推荐的工作区布局很值得直接借用;再补上任务合同与两个 Agent 不应修改的验证脚本,最小版本如下:
task-workspace/ docs/ task.md # 任务合同,Agent 不可修改 draft.md # 第一版计划草稿 plan.md # 可执行计划 runs/ # 每次运行的日志与命令 outputs/ # 生成的 kernel、编译产物 profile/ # ncu 报告与摘要 tests/ check.py # 正确性检查,Agent 不可修改 bench.py # 性能测量,输出 JSON benchmark.csv # 表格化的性能记录 candidates.jsonl # 候选、父版本、状态、理由每个文件的职责:task.md 保存不可变的合同,draft.md 保存被推翻的想法,plan.md 保存当前承诺,runs/ 保存可重跑的命令与环境,profile/ 保存性能证据,tests/ 与 bench.py 保存独立验证脚本,benchmark.csv 让趋势可见,candidates.jsonl 让「为什么保留/放弃」可追溯。格式可以变,一致性不能变:下一位工程师应该能只看这些文件就重建你的决策。
一个最小的循环伪代码:
# harness 只做四件事:给任务、跑工具、验结果、记证据for step in range(max_steps): context = read("docs/task.md") + read("docs/plan.md") + recent_tool_outputs() # 合同 + 计划 + 近期证据 action = agent.next_action(context) # 避免只凭记忆,每轮重建关键上下文 result = tools.execute(action) # 编译、测试、benchmark、ncu append_jsonl("runs/loop.jsonl", action, result)
if not result.correct: agent.observe(result.stderr) continue
if result.speedup >= target and verifier.accept(result): promote(result) break
if budget.exhausted() or no_improvement(patience=3): write_postmortem() break配套的验证脚本应该独立于 Agent 的编辑范围:
#!/usr/bin/env bashset -euo pipefailpython tests/check.py --case decode_8k --tol 2e-2python bench.py --case decode_8k --warmup 5 --repeat 30 --json benchmark.json注意 set -euo pipefail:测试失败必须让脚本失败,而不是被后续命令掩盖。benchmark 输出 JSON,方便 harness 自动比较;人只看摘要,原始数据留在文件里。
- 把 harness 当成更长的 prompt。 不能被检查的约束等于没有约束。
- 只记录成功候选。 失败候选与放弃理由才是下一次的捷径。
- 让 Agent 同时改 kernel 和测试。 正确性证据立刻失去独立性。
- 只测一个 shape。 过拟合单个形状是最常见的「假加速」。
- 上下文塞得越满越好。 关键合同被挤掉后,Agent 的行为会不可预测。
- 没有回退点。 连续改动无法归因,最后只能全部重来。
- 我能画出 Agent 循环,并指出上下文、工具、验证各自的位置。
- 我能解释 harness 与 prompt、Agent 框架的区别。
- 我写的任务合同里,每条验收条件都能对应一条命令。
- 我能说出 draft 与 plan 的区别,以及为什么先 draft 后 plan。
- 我能举出至少两种 Agent 自评失灵的机制。
- 我为自己的任务设置了至少三类预算和三类停止条件。
- 我知道最小工作区里每个文件的职责。
为一个 kernel 任务写验收标准与停止规则
目标:把模糊的优化愿望改写成可执行合同,并设计一个不会被 Agent 绕过的停止规则。
任务:
- 选一个你熟悉的算子(softmax、LayerNorm 或一个小 GEMM),写一份任务合同,覆盖目标、输入输出、正确性、性能、约束、验证命令、评估命令、晋升条件。
- 至少写三条边界用例(例如空输入、极长序列、非连续布局),并说明每条用什么命令测。
- 设计停止规则:成功、预算耗尽、无改善、阻塞各一条;说明每条由 harness 的哪个检查触发。
- 让另一个 Agent(或同学)只读合同,尝试指出一个可以「钻空子」的地方;你补上对应约束。
验收标准:合同中的每条条件都可执行;边界用例不少于三条;停止规则不依赖 Agent 自评;你能指出至少一个被补上的漏洞。
提示
钻空子的方向包括:改测试、换 baseline、只测一个 shape、把计算挪到计时区间外、降低精度但不改容差。逐条问「如果我是想偷懒的 Agent,我会怎么绕过这条?」
- KDA Agent Flow:最小循环、任务合同、证据记录与晋升规则。
- KDA basic-flow prompt:八项任务合同与 draft 要求。
- Humanize 论文:写/判分离、计划合同与机械 gate 的动机。
- TIRx Harness 博客:另一个开源 harness 如何把编译器、知识库与基准服务接给 Agent。
- Claude Code 文档 与 Codex CLI:工具、权限与会话管理。
- Nsight Compute 文档:把 profiler 接进 harness 时需要知道的命令行与权限。
下一章补上 kernel 这一侧的基础:GPU 怎么执行一个 kernel、roofline(屋顶线模型)怎么判断瓶颈、prefill 与 decode 为什么是两类问题,以及如何用 ncu 的六个维度读出一份可信的性能证据:GPU kernel 与性能。
本章链接与数据核对至 2026-10-29;完整来源清单见资料库。