Elasticsearch 执行多样化的负载,包括持续的高强度索引构建和实时搜索与分析。要在所有方面提供卓越性能,需要广泛覆盖的同时,深入代码库理解每种负载的优化机会。传统上,人类注意力是这一过程的瓶颈;根本没有足够的工程时间来审视每一个热点代码路径,以在一个庞大且不断变化的表面积上寻找低效之处。
然而,随着编码智能体的快速发展,性能优化已成为可以半自动化处理的任务。与许多软件工程挑战不同,代码优化提供了一个廉价且客观的验证器。如果你让AI模型使代码更快,最终会有一个硬数字告诉你确切发生了什么,并附有分析工具解释原因。这使得性能成为自动化的完美候选,前提是你能真正信任这些数字。
如果你只是将一个编码智能体指向一个基准测试,通常会得到低信噪比的结果:一些落在环境方差范围内的胜利,或者由热节流而非更好的代码引起的变化。为了捕获真正对Elasticsearch用户有益的优化,我们必须弥合“原则上可验证”与“实践中已验证”之间的差距。我们构建了一个高度可信的测量循环:一个自动化框架,它假设智能体有很大概率会出错,但能够在它正确时可靠地捕捉并证明它,然后引导它下一步该往哪里看。
一旦机制到位,结果不言自明。通过让这个框架在代码库中自由运行,我们已经开始在整个堆栈中发现有意义的优化成果。在本文的第二部分,我们将深入探讨它目前发现的一些示例,包括 Elasticsearch 查询语言(ES|QL)中的字符串转换低效问题、对 NEON 向量点积实现的改进,以及我们正在使用的 gzip 库的升级机会。在本部分中,我们将看看我们做出的设计选择,以及它们与更广泛的自动化框架开发主题的关系。
任何软件工程问题的第一步都是识别正确的高级组件。我们做了一个架构选择,结果证明对这个非常有用:将“理解机会存在于何处”与“进行代码修改的循环”分开。智能体从一个真实负载开始,但只用它来挖掘信息,以确定在哪里寻找性能改进。在这个阶段,它被指示要广泛探索,考虑一系列与性能相关的信号。一旦它发现并分类了热点,智能体就会读取它们周围的代码上下文,以理解优化机会。我们使用一个单独的任务将排名后的热点列表压缩成可让循环在几分钟内迭代的产物:一个我们证明能在实际运行机制中锻炼热点路径的微基准测试。最后,我们使用一个“提议者-验证者”循环实际修改代码库以改进基准测试性能。这最后会交由验证来评估对真实负载的影响。我们的CLI(atune)提供了此过程所需的工具,其余大部分由一组特定任务的指令自动化完成。
为了提供上下文,我们的高级架构如下所示。粉色框代表人类,青色框代表智能体。有三种任务类型,一个骨架循环,和一个仲裁者。

探索任务分析一个真实负载并生成排名后的机会;人类将其中一个提升为利用任务,该任务针对批准的微基准测试进行迭代并提交每个接受的实验;对真实负载的验证运行在人类审查并打开PR之前守护结果。在没有基准测试覆盖热点路径的地方,基准测试任务会编写一个,人类批准后将其注册到注册表中。性能图谱为每个任务提供信息,并累积每个任务学到的东西。
三个属性使一个任务非常适合自主工作,值得明确说明,因为它们提供了一个可用于评估自动化候选者的检查清单。你需要:
性能为你提供了所有这三者。基准测试提供了裁决,分析器提供了梯度,而一个被拒绝的补丁消耗的是墙钟时间而不是正确性。更改被回滚,原因被记录。生活继续。关于前两点,我们逐渐认为梯度比裁决更重要。“这变快了”是二元的,而分析结果则暗示了下一步该尝试什么。智能体可以根据丰富的引导信号(越丰富越好)生成下一个假设,而不是在列表中苦苦搜索。
一个有用的思维模型是:CLI是智能体的感知器官。这改变了你设计每个命令的方式。与其暴露一个能力,不如设计它来返回对当前任务相关问题的一个清晰简洁的答案,并在相关时提供一个模型可以推理的解释,而不是它必须解析和解释的原始数据。这些是智能体据此行动的信号,我们最终为我们的框架设计了以下信号:
信号 | 它回答的问题 |
|---|---|
分面分解的宏观性能数据 | 在代码的哪个地方消耗了真实负载的时间,按查询类型分类? |
在相同捕获中的分配和锁采样 | 成本是CPU周期、垃圾回收还是资源竞争? |
成本构成分类 | 这是范围内的计算、其他产品代码、GC、JIT开销还是停顿线程? |
输入形状检测 | 负载实际上向这段代码提供了什么? |
统计裁决 | 这个改变有帮助吗,在这个测量的噪声水平下? |
分配率比较 | 新代码是否最终分配了更多内存? |
解释的汇编代码 | 为什么会发生那个结果? |
端到端A/B防护,带差异性能归属 | 是否有任何东西看起来坏了,是我们造成的吗? |
环境检查 | 这台机器现在是否适合进行测量? |
上游重复搜索 | 是否已经有人报告或解决了这个问题? |
其中有四个信号值得深入探讨,因为在每种情况下,工具都编码了智能体原本需要手动做出的判断。
分面分解是最清晰的例子。混合的CPU份额掩盖了广度:如果你对一个混合查询负载做性能分析,分组哈希映射插入和百分位数草图更新都可能显示为总运行时间的个位数百分比,看起来相当。它们根本不可比,因为哈希映射插入几乎每个聚合查询都要付出代价,而草图更新只有在有人请求百分位数时才会付出代价。所以分析器将每个命名的查询分面作为独立的比赛运行,并且智能体记录的每个机会都带有一个广度字段(通用、广泛或狭窄),并根据余量×可处理性×广度进行排名。一个通用的3%胜过一个狭窄的10%。将排名函数放在工具中,可以防止每次运行都必须重新发现它。
成本构成分类充当路由器。它不会向智能体提供平坦的顶级帧名称列表,而是将每个采样的堆栈分类到"范围内计算"、"其他产品计算"、"GC"、"JIT和安全点开销"、"不在CPU上等待"和"停顿线程"。每个类别都暗示了不同类型的调查。如果GC超过约15%,真正的目标是分配率,而CPU顶级帧会主动误导你,因为它们显示对象被回收的地方而不是创建的地方。如果JIT和安全点开销超过约25%,你看到的是一个上限而不是机会,因为没有范围内代码改变能够移动它。如果线程被停顿且核心利用率低,这是一个并发问题,CPU火焰图完全不是正确的工具。我们将这个映射写成表格放入操作手册,这样模型(即使是便宜的模型)也能像有经验的工程师一样读取性能数据,而不是直接跳到顶级帧。不过,分类只是形成假设的先验,而不是证据的替代品,所以智能体在提议实验时仍然必须引用具体的帧。
解释的汇编代码是我们最初没有提供的工具,但它绝对值得一席之地。它有助于回答"为什么",这个问题能解锁下一个假设。火焰图告诉你时间花在哪里;它们很少告诉你为什么一个改变让事情变得更糟。因此 atune asm 短暂运行基准测试,让JIT为一个热方法打印汇编,捕获工作树差异的两侧,将每个缩减为最终的C2编译,标准化地址,然后进行差异比较。单单差异可能仍然是4000行的aarch64,所以在其上还有一个解释层:每个助记符的增量、净指令计数、实际捕获的编译层,以及一个向量化信号,它计算每一侧的向量寄存器引用,并在它们被消除、减半或从https://en.wikipedia.org/wiki/Advanced_Vector_Extensions (AVX) 缩小到https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions (SSE) 宽度时发出警告。然后操作手册将助记符模式映射到原因:
差异中的模式 | 可能的原因 |
|---|---|
b.eq/b.ne 增加,csel 减少 | 新的不可预测分支 |
针对栈指针的 str/ldr 簇 | 编译器寄存器不足 |
NEON 加载被标量比较替换 | 向量路径退化 |
在一次实验中,智能体将两个https://en.wikipedia.org/wiki/Single_instruction,_multiple_data掩码提取合并为一个,基准测试退化了26%。向量化警告在大约10秒内解释了原因。没有这个工具,智能体会遇到死胡同,并且对机器没有有效的模型;有了它,它就有了一个修正的模型和几个新想法。
第四个信号与其说是一个单一工具,不如说是一种习惯;仪器检查自身。核心利用率通过两种独立方式推导(样本密度和进程采样),因此两者可以进行比较。如果未分类的桶超过预算,分类就会被拒绝,理由是如果无法说明自身样本的分解就不应该被推理。当捕获的编译不是稳态编译时,汇编捕获会发出警告。上游重复搜索仅限于只读命令,并且这个限制由测试通过grep源代码来强制执行,这样未来的编辑无法悄悄重新引入提交任何东西的能力。这些之所以存在,是因为一个可能自信地出错的工具比一个根本不在的工具更糟糕。
一个小的设计点值得强调,作为良好回报实践的一个具体实例。atune compare 返回0表示改进,1表示无变化,2表示退化,3表示错误,循环根据这个代码进行分支。这意味着无需解析,也无需对裁决内容产生歧义。而且,没有标记花费在解释文本上。
如果你从我们的CLI设计中只带走一件事,那应该是更广泛的设计原则。在一般设置中,有趣的不是我们发现的对理解性能有用的单个信号,而是CLI正在捕获和打包一个有经验性能工程师的判断,将其变成返回答案而不是原始数据的工具。对智能体所负责的问题结构性施加良好判断可以改善结果。正确的CLI与指令一样重要。此外,通过返回决策和摘要而不是数据的工具节省了标记。这意味着比较裁决而不是原始的JMH输出;一个位于4000行汇编差异之上的分诊摘要。
构建一个你负担得起咨询的验证器与构建一个你信任的验证器是两个不同的问题。这覆盖了可负担性部分。或者,如果你喜欢格言,真实负载是真理所在之处,也是迭代消亡之地。一个宏观性能数据需要45到60分钟,一次端到端验证运行需要数小时。但一个微基准测试只需要几分钟。这就是探索-然后-利用分割有效的原因;你对真实负载进行一次广泛探索,然后交给一个你可以在几分钟内迭代的微基准测试。
问题是,只有当微基准测试在真实的操作机制(即正确的基数 and 正确的数据分布)下锻炼热点路径时,交接才是可靠的。如果你搞错了,你的快速循环会快速旋转但方向错误。在我们最终确定交接流程之前,我们看到一些情况,智能体在关键分布恰好使其受益的基准测试上接受了更改,只有端到端运行才发现了问题。
两个阶段之间的交接是结构化的而非随意的。探索任务的主要交付物是一组机会记录,每条记录都包含一个利用任务被允许编辑的作用域路径、一个余量估计、一个分类(常数因子、结构性、分配或并发),以及它将依赖的基准测试(如果没有则显式标记为"无基准覆盖")。每条记录还包含一个狭窄的测试模式,以便正确性门保持廉价。测试模式字段的存在是因为一个特定事件;一个探索任务省略了它,结果利用任务在每个实验上都运行了非常重的测试套件。修复方法是更改上游工件,而不是在下游添加指令。这是我们反复使用的模式:尽可能早地做出并记录决策,而不是每次都重新推导。
在基准覆盖确实缺失的地方,一个专门的基准测试任务会编写一个,并且这个新基准测试必须通过有效性检查,然后才能被任何东西依赖。其中两个是机械性的:基准测试的热自时间中有多大比例来自实际出现在生产性能数据中的帧,以及参数是否落在我们测量的输入形状之内。第三个是智能体必须逐一证明的清单。它的存在是为了避免 JIT 得到一个比生产环境更简单的世界。
然后人类将其批准到一个哈希固定的注册表中。在此之前,它是惰性的,因为任务设置会拒绝任何引用未批准基准测试的任务。我们认为这个批准是系统中最强的大门,并且是故意放置的。一个被批准的基准测试可以在所有未来任务中决定接受或拒绝,所以它是我们要求人类在工件上签名而不是在决策上签名的唯一地方。
在所有这一切之下,是一个反馈机制的层次结构,具有这样的特性:每一级都比下一级更便宜、更弱,并且便宜的层级仅用于拒绝。
层级 | 成本 | 角色 |
|---|---|---|
探测 | ~20–60秒 | 方向正确吗?永远无法接受 |
代码生成捕获 | ~4分钟 | 为什么会那样? |
筛选 | ~5–15分钟 | 廉价的统计过滤器 |
确认 | ~20–60分钟 | 接受决策 |
端到端 | 数小时 | 回归防护,咨询性 |
接受与拒绝之间的不对称在这里发挥了真正的作用。一个探测是单个配对分支,而接受谓词需要一个完整的确认运行,并附带匹配的校准记录。智能体非常擅长讲述可信的故事;事实上,它们是在许多由LLM和人类评判的任务上训练的,所以有说服力是积极奖励的。我们不希望智能体通过有说服力地将一个廉价信号提升为决策。
对于这个任务基准测试,墙钟时间是一个真实的成本考虑。一次确认运行可能需要一个小时。所以在选择设置时必须仔细权衡所有涉及的成本。一个四个假设中命中一个的模型通常比一个十个中命中一个的更便宜模型的端到端指标要好。在长时间运行的循环中降低模型规格的通常直觉在这里恰恰相反。当你的循环结果不确定且消耗的代价远不止标记时,你很可能发现自己处于相同的情况。
它更快了吗? 是一个统计问题。所以我们让接受谓词成为代码而不是判断,并将其放在智能体无法绕过的地方。接受决策中有四个想法,在每种情况下,我们拒绝的替代方案与我们所做的选择一样有信息量。
每个JMH分支收敛到其均值,裁决来自精确的双尾Mann-Whitney U检验,α=0.05,加上一个基于自举的置信区间,每侧三到五个分支均值。原因是分支内的迭代共享JIT和堆状态,因此是自相关的,将它们视为独立样本会无中生有地制造显著性。我们拒绝比较单次运行得分(纯噪声)和迭代级别的t检验(可能自信地出错)。为自举设置种子意味着重新运行会精确重现裁决,因为智能体需要能够区分结果发生了变化和结果从未稳定过。
筛选(三个分支,可选地针对参数子集)仅用于在大约10分钟内杀死坏假设,而不是一小时。接受决策本身来自一个确认运行,它使用一个“存储-翻转”机制背靠背测量候选和基线。在不稳定的环境中,只有当两侧在相同条件下运行时,热漂移和后台漂移才会相互抵消,所以数小时前的基线实际上是不同的实验。
一个任务将接受的最小效应大小必须清除该特定基准测试在该特定机器上的A/A校准变异系数,并且确认运行和比较在没有匹配的校准记录时都拒绝继续。笔记本电脑上的1-2%改进与噪声无异,并且由于噪声水平因基准测试和JDK而异,你选择的任何全局常数都会在一些地方过于宽松,在另一些地方过于严格。
一个参数组合只有在p < α、效应清除校准水平、置信区间排除零时才被视为改进。整体接受要求没有任何东西退化(主基准测试和防护测试都没有退化),并且至少有一个主要组合得到改进。头号数字是每个组合加速比的几何均值,它始终为正并且可以在实验之间组合,因此任务的累积改进是一个有意义的数字,而不是不可比百分比的累加。
防护测试值得单独说明,因为它们回答的问题与主基准测试不同:不是“这变快了吗?”,而是“在它变快的同时,什么不能变慢?”任务定义出于这个原因将它们单独列出,它们通常是更改不针对的操作和输入机制。如果你正在优化哈希表的插入吞吐量,迭代是一个防护测试,冲突密集的键分布也是如此。一个通过弱化哈希函数来改善常见情况的更改,在均匀分布的键上看起来不错,同时灾难性地降低更具对抗性的输入。我们知道这是因为发生过这种情况;冲突分布从接受我们早期实验之一的矩阵中缺失了,该任务现在带有一条注释,告诉未来的读者再也不要为哈希质量敏感的范围删除它。虽然防护测试在每个确认上消耗墙钟时间,但它们也揭示了边缘情况退化,从长远来看,省略它们可能代价更高。
运行时也不是唯一需要保护的东西。确认运行还捕获两侧的标准化分配率,并标记任何通过增加15%以上额外垃圾来换取速度的更改。这个建议性的而非阻塞性的,因为有时候权衡是正确的。然而,这种退化如果纯粹基于时间的接受规则会很乐意通过,但可能会向有经验的性能工程师发出红旗,他们能更好地理解调用上下文。
端到端门槛是一个不同的统计问题:小样本,高噪声,以及一个非常强的先验,即我们应该基于微基准测试结果接受。我们的第一个设计将接受和拒绝等同对待,它产生了多个明显虚假的拒绝,所以重新设计是单边的且默认开放。只有当中间退化超过阈值且每个候选重复都比每个基线重复慢时,一个操作才会被标记。这个全分离标准是该样本量下的非参数单边检验,并且它对偶尔会误导我们的单一异常重复很稳健。然后一个标记还必须通过差分CPU火焰图得到证实,只有任务自身范围内CPU份额的上升才被视为真实。接近命中的情况会被报告以提高透明度,但不会触发分类,并且没有任何东西会被自动拒绝;标记是请求人类注意而不是裁决。我们还有一个最终的后备措施,即我们已经在每日基础上对Elasticsearch运行的大规模性能测试套件。
单边门槛的经验教训可以超越基准测试。在有强烈先验理由接受的情况下,一个噪声门槛应该是单边的且默认开放。噪声信号上的对称阈值不仅会损失真正的胜利,还会教会循环不信任自己的仪器,这是一个更昂贵的失败。
探索和利用可能看起来像一个活动的两个阶段,但它们有不同的输入(宏观负载 vs. 固定范围)和不同输出(排名后的机会 vs. 提交)。它们也有不同的失败模式,这意味着它们需要不同的权限。我们将分割作为任务的一等属性,这让我们可以强制执行;一个探索任务字面上不能提交。基线确定、比较、检查点和验证CLI都拒绝探索任务,基准测试只允许探测,并且每个探测差异总是被恢复。一个广泛、推测性的调查是安全的,因为它所做的任何事情都不能编辑代码。
一个不错的好处是提示聚焦。每种类型完整阅读一本操作手册,其他手册明确不加载。如果你试图写一份同时涵盖“在哪里找到余量”和“在这个范围内落地一个经过验证的胜利”的文档,你会得到一样都不好的东西,因为良好探索的指令(跟随性能分析,扩大网络,偏好广泛调查)几乎与良好利用的指令相反(一次一个假设,最小差异,永远不要扩大范围)。
会话是短暂的,但你能从中学到的东西不是,所以框架积累了三个持久资产,加上一个从它们派生的可丢弃视图。这些是我们尝试过的代码更改的日志、代码性能的数据库、框架失败时的事后分析,以及由于会话可以停止和恢复,还有一个会话摘要。它们协同工作的关键在于一个清晰的归属规则,规定哪种事实放在哪里。
日志记录了我们尝试和测量的内容。它是仅追加的,每个任务一个文件,每个实验一条记录,并且在代码编辑之前写入。拒绝记录带有一条前向注释,形式为“不要重试X,因为Y”,这可能是最高价值的一行,因为它阻止下一个会话重新推导死胡同。记录还携带环境和驱动模型,这意味着假设命中率在不同模型之间可比较。
知识库记录了代码如何工作以及它的表现。它是一个按区域摘要的索引集合,每个摘要都带有编写时所针对的提交标记,并且每本操作手册以一个维护步骤结束,该步骤附加了运行过程中发现的任何持久事实。因为JDK的性能特性也时不时变化,例如,一个新的Vector API可能在AArch64上更高效地实现向量掩码,来自性能数据的结果也标记了它们适用的JVM版本。
事后分析记录了智能体过去犯的错误,它们按照症状而不是日期索引。当数字看起来不对时,会话实际想问的问题是“我以前见过这种错误形状吗?”,而按时间顺序的列表无法回答。所以条目看起来像“验证在与你的差异结构无关的操作上失败”,或“筛选报告没有匹配的组合”。
保持日志和知识库分离听起来学究气,但并非如此,因为没有规则,两者都会变成日记,有膨胀上下文窗口或在上下文中丢失关键信息的趋势。
可丢弃的状态是会话移交,我们的建议是永远不要手动编写它,并尽可能避免要求模型提供会话摘要。我们的框架从日志机械地重新生成一页摘要,因此恢复的会话不会花费时间和标记来重建任务已到达的位置。因为它是推导出来的而不是编写的,它不会像手动维护的内容那样偏离记录。这也是为什么它不是审计追踪的一部分;因为从日志重新生成它的成本很低。
机械会话移交指向两个教训,它们原来是同一个教训。每次一个人出现在循环中,都是摩擦源和错误机会。每次你伸手拿模型时,问问代码是否能做同样的工作。在一个主要前提是将委托给模型的项目中,提倡这点似乎很奇怪,但一旦你有一个模型在手,这个习惯很容易养成。判断是昂贵的,无论它位于何处,所以人和模型都必须凭优点赢得自己的位置。
另外两点对于持久的智能体记忆至关重要。第一是知识会腐烂,所以你必须对其进行lint。Elasticsearch的主分支每天都在变化,这意味着知识库的文件引用会过时,所以一个linter检查它们。另一个linter检查面向智能体文档中引用的每个命令、标志和路径是否实际存在,每个事后分析是否从索引链接,并且每个任务类型都有一本操作手册,并且它作为测试套件的一部分运行。将为智能体编写的散文视为可测试工件是它保持真实的理由。第二是加载纪律是记忆的一半。指令是加载索引,然后加载与任务范围匹配的一两个摘要;永远不要批量加载其余部分。你负担不起读取的内存就不是内存。
Elasticsearch有数百万行代码,对这样大小的代码库进行无范围的“让它更快”运行是不可审查且不可证伪的。它也很昂贵。反过来,一个验证器只保护它能看到的东西,所以我们必须将同样的边界应用于它可以更改的东西。结果是一个任务在编辑任何代码之前就固定了其目标、允许的路径、基准测试、阈值和停止条件,并且在运行期间智能体不能更改其中任何一项。

人类拥有安全边界,在编辑任何代码之前定义范围、阈值和停止条件,并且拥有跨信任边界的每次交接。智能体会话拥有判断:读取性能数据,一次形成一个假设,在范围内编辑。atune CLI拥有机械强制执行:范围检查、正确性门、统计、校准、停止条件、环境检查和信号。持久状态是记录:一个固定在基础提交上的工作树、一个仅追加的日志和生成的报告。
包含发生在两层。第一层是粗粒度的且与任务无关;它是一个单一的静态权限文件,让会话可以编辑每个任务的工作树(包括Elasticsearch的本地分支、所有日志条目和CLI工件)和知识库,并拒绝原始的Elasticsearch克隆、任务定义、框架配置和审计追踪。第二层是精确且按任务来的:一个在每次构建前对跟踪和未跟踪文件运行的git级范围检查,因此一个超出范围的编辑在被基准测试或提交之前就被拒绝了。
两层包含有一个很好的推论。将第一层扩展到整个工作树没有成本,因为没有任何超出范围的东西能通过第二层。粗粒度包含加精确范围胜过试图让单一机制做两项工作,这是我们首先尝试的,结果留下了每次新任务都需要编辑的权限文件。
停止条件是机械的。有一个最大实验次数和连续拒绝次数,以及一个累积改进目标,一旦其中一个触发,提议新实验就会被拒绝。人类可以覆盖;智能体不能。这种不对称使得门独立于驱动它们的模型。
测试是只添加的。新的测试文件随检查点一起提交,修改或删除现有测试会被范围检查阻止。这通过构造而不是指令关闭了整个问题空间中最诱人的快捷方式,这似乎是处理你本来必须不断要求智能体不要采取的快捷方式的正确方式。
框架存在于它优化的代码之外。Elasticsearch克隆是一个单独的、git忽略的目录,每个任务得到一个固定在基础提交上的工作树。收益叠加:主题仓库保持纯净且可向上游合并,与框架相关的任何内容都不会泄漏到PR中,审计追踪独立于每天变化的代码库版本化。此外,任务相互隔离,也独立于任何开发者检出。定位一个较新的Elasticsearch意味着创建一个新任务而不是重新指向一个旧任务,因为该任务的数字与其基础绑定。这也意味着框架原则上可以重新定位,因为Elasticsearch特定的部分是配置、知识库和基准测试注册表,而不是架构。
我们确定的规则是:智能体运行循环,而人类拥有每个跨信任边界的步骤。即创建工作、批准测量仪器、发布分支或在仓库之外操作。这些都不在智能体的允许列表中,每个都有值得说明的理由:
人类行动如何在工作流中呈现很重要。生成的报告和会话移交都会打印当前到期的人类行动,在它们到期的时候,而不是让它们从操作手册中推断出来。
我们故意不对手动流程的永久性采取立场。新技术的正确监督程度是一个经验问题,我们宁愿测量它也不愿争论它。我们从一个相对较高的监督程度开始,因为这是出错的便宜方向(一个你从未需要的门比一个你发货的回归更容易移除),并且因为框架使问题可回答。每个门都是命名的、记录在案的转换,因此随着时间的推移,我们可以看到哪些门曾经改变了结果,哪些只造成了摩擦。总之,先测量,然后改进。
框架设计的很多内容并非来自初始设计文档。信号集、排名函数、任务的形状和操作手册规则的精确措辞都来自观察一次运行出错。如果有一条能泛化的建议,那就是在你准备好之前就使用这个东西,并为你自己的失望进行测量。
最清晰的例子是我们现在称为“不信任令人惊讶的结果”的规则。一次验证运行报告每个操作都退化了,最坏的一个退化了14.8%。它错了两次。一个目标操作模式匹配了一个完全不同的代码路径,而一个来自早期运行的陈旧输出目录与新输出目录一起被读取。给了一个连贯的故事,智能体接受了它并恢复了一个实际上是好的更改。
那次事件后,写入操作手册的不是“小心”。而是一个三步检查,在对令人惊讶的裁决采取行动之前运行:
与此同时,还有另一个重要规则:如果智能体认为框架有bug,它不能在运行中途修复,因为中途的框架更改会使该运行中的所有结果不可比较。它应该记录证据并停止。
改进框架本身就是一个值得描述的循环。要求模型审查自己的转录和框架文档,并提议规则本身,通常效果很好。它善于发现自己的指令在哪里模棱两可,这种方式很难通过自己重读指令来重现。使输出有用的是有地方让它落地:操作手册中的简洁规则、日期化事后分析中的叙述、症状索引行以及保持引用诚实的linter。
克制原来是同一纪律的一部分。设计文档有一个明确列出我们故意没有构建的扩展点列表,因为这些需求仍然是推测性的。这是我们强加给优化循环的同一“不要猜测,等待证据”规则,应用于我们自己。
其中一些主题并非特定于性能工作或Elasticsearch。
可验证的工作是当前的前沿。同样的见解推动了带可验证奖励的强化学习,这也是AlphaEvolve的基础。智能体持续擅长的任务是那些带有廉价预言(测试、编译器、基准测试)的任务,而有趣的举动不是找到更多这样的领域,而是为缺乏预言的领域制造预言。性能是一个有启发性的案例,恰恰因为预言在某些意义上是显而易见的,但构建它仍然花费了大部分工程工作。
仲裁者模式也可以泛化。将易错优化器与机械强制执行分开,与沙盒执行和策略引擎的形状相同:智能体中的判断,代码中的不变性。实际结果是系统的安全性不依赖于驱动它的模型。较弱的模型浪费基准测试时间,但不能破坏代码或接受虚假的胜利。
古德哈特定律是任何使用智能体的优化任务的长期对手,并且有一个值得阅读的形式化处理。智能体精确优化你告诉它的东西,所以两层基准测试结构、仅添加测试、基准测试批准注册表、对抗性守卫分布以及一个使时间构建机械上不可能的标记文件都是一个设计主题的不同着装工具即上下文工程。共识正在从“暴露一切”转向少数精心设计的返回摘要的工具:渐进式披露、自文档化接口以及返回裁决而非原始数据的原则。
记忆正在成为架构。规则文件、按任务区分的操作手册、持久化的知识库和仅追加的日志形成了一个层次结构,它们拥有不同的生命周期、所有者以及加载规则,而困难的部分在于淘汰和过时,而非存储;因此有了那些 linter。
最后,人机协同是一个旋钮而非开关,因此它应处于何种位置,需要按领域进行测量,而非断言。
该框架的设计是一套关于自主性能工作所需条件的假设,而框架的构建正是为了让我们能够测试这些假设。第二部分就是这次测试:我们通过它提起的前四个 PR、它们的收益、每个收益需要多少假设、哪些门实际上捕捉到了问题,以及框架在哪些地方妨碍了自身。这包括那些未通过端到端验证的更改,因为与往常一样,被拒绝的经验同样具有信息量。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。