今天这篇论文「Agentic Auto-Scheduling: An Experimental Study of LLM-Guided Loop Optimization」讲的是一个很具体的问题:LLM 能不能参与编译器优化,让程序跑得更快?
这里的 Loop,指的是程序里的循环结构,比如 for / while 循环,尤其是科学计算、图像处理、机器学习算子里常见的多层嵌套循环。论文提出了一个名为 COMPILOT 的实验框架,把现有 LLM 和编译器放进同一个优化闭环里:LLM 先提出循环结构的变换方案,编译器再检查方案是否合法、能否编译,并实际运行代码,判断性能是提升还是下降。随后,系统把检查和测速结果反馈给 LLM,让它继续调整下一轮方案。
LLM 和编译器的分工
在 COMPILOT 的设定中,LLM 并不直接改写完整代码。它输出的是一组面向编译器的优化指令,比如循环融合、循环交换、并行化、二维/三维 tiling、循环展开(unrolling)、循环偏斜(skewing)等。而真正执行变换、做依赖分析、生成代码和测速的部分,依旧是编译器系统。

图 1:Fig. 1,COMPILOT 框架总览
这张架构图可以按从左到右的顺序理解。
左侧是输入程序、编译 / 运行环境,以及最终生成的优化后程序。输入代码先进入 COMPILOT,中间的 Context Initializer 会把任务说明、目标循环代码、可用变换、输出格式等信息整理成上下文,发送给右侧的 LLM Agent。
LLM Agent 根据这些上下文生成下一轮 schedule,也就是循环结构的变换方案。它的输出会回到中间的 Interaction Loop Handler,由 Response Parser 解析成编译器能执行的指令。
接下来,左侧的编译 / 运行环境会尝试执行这些变换,检查是否合法、能否编译,并实际运行代码得到性能结果。中间的 Feedback Generator 再把格式错误、非法变换、编译失败、运行时间变化等信息整理成反馈,继续发给 LLM,进行下一轮优化。
如上所述,LLM 站在“提出候选方案”的位置上;编译器和运行环境负责验证、执行和测速;COMPILOT 中间这层负责把两边连接起来,让这个过程可以反复迭代。
从多层循环到调度搜索
这篇论文关注的是 loop nest optimization,也就是多层循环结构的优化。
在科学计算、图像处理、机器学习底层算子里,性能瓶颈往往出现在这类循环结构上。多层循环怎么嵌套、哪个维度先跑、要不要分块、能不能并行,都会影响缓存命中、线程利用率和最终运行时间。
这类优化难在搜索空间很大。同一段循环代码,可以有很多种变换方式;同一种变换,也可能因为参数、顺序、硬件环境和输入规模不同,带来完全不同的性能结果。编译器按预设规则自动选择时,未必总能选到合适结果。
COMPILOT 要测试的,就是能不能让 LLM 在这个空间中充当“调度方案生成器”。它会根据目标循环代码提出一组 schedule,也就是循环结构的变换方案。每一组 schedule 都可以理解成一次候选尝试:这次先交换循环顺序,还是先做分块;并行化放在哪一层;tiling 参数该怎么选。
为了让模型进入这个任务,系统会把输入循环、可用变换、输出格式、硬件信息和错误处理规则整理成 prompt。接下来,模型会先读懂目标循环结构,再给出后续的 schedule 候选。

图 2:Fig. 2 展示系统 prompt 结构

图 3:Fig. 3 展示输入给 LLM 的循环代码

图 4:Fig. 4 展示 LLM 对循环的分析
这样看,COMPILOT 做的事情并不神秘:它把多层循环优化拆成一轮轮候选 schedule 的搜索。LLM 负责提出下一次尝试,编译器和运行环境负责判断这次尝试能不能成立、跑起来有没有更快。
3 倍加速是怎么测出来的
论文在 PolyBench/C 上做性能测试。它是编译器优化研究里常用的一组 C 语言基准程序,包含矩阵计算、线性代数、stencil 等循环密集型 kernel,适合用来测试多层循环优化效果。
这个基准包含 30 个 benchmark,论文使用 5 种标准数据规模,所以一共是 150 个 benchmark instance。本次论文的主要实验硬件是一台双路 Intel Xeon E5-2695 v2 机器,总计 48 个线程,128 GB 内存。编译器侧,使用的是 Tiramisu。
这篇论文默认使用 Gemini-2.0-flash 作为主要 LLM,并在每个实例上运行多次,统计中位数加速,再用几何平均聚合整体结果。
论文实验里最核心的两个数字,是 2.66× 和 3.54×。
在单次运行设置下,也就是每个程序最多迭代 30 轮优化,COMPILOT@30 相比原始代码达到 2.66× 几何平均加速。如果允许同一个程序独立运行 5 次,再从 5 次结果里选择最快的一次,也就是 best-of-5 设置,COMPILOT_5@30 的几何平均加速达到 3.54×。
所以,标题里的“跑快 3 倍”需要带上实验前提:这是在 PolyBench/C 这组循环密集型基准上,按照论文统计方式得到的汇总结果。单次运行接近 3 倍,best-of-5 超过 3 倍。

图 5:Fig. 7 COMPILOT@30 在不同 benchmark 和不同输入规模上的加速结果。
如上图所示,从分布上看,这个加速并不平均。有些 benchmark 几乎没有提升,有些在 LARGE / XLARGE 这类数据规模下提升明显。这里的数据规模,指的是同一个程序处理的矩阵、数组或循环迭代范围更大。数据变大后,并行化和 tiling 这类优化更容易摊开开销、改善缓存访问,因此收益也更容易显现。比如 correlation_XLARGE、trmm_XLARGE 等案例里,COMPILOT 找到的 schedule 带来了很高的加速。
所以,这里的 3 倍加速更适合理解为一个受控实验结果:在循环密集型 benchmark 上,LLM 可以帮助编译器搜索更好的 schedule。它不能直接推到普通业务代码上。
更可控的代码优化
这篇论文比较值得注意的一点,是它没有让 LLM 直接生成优化后的 C 代码。
因为直接改代码有一个风险:LLM 可能会写出能编译、甚至能通过一部分测试的代码,但其实它的语义已经被改坏了。对于循环优化来说,这个问题会更敏感,因为很多变换涉及数据依赖关系。看起来只是调整循环顺序,实际可能影响读写顺序。
COMPILOT 的做法相对保守。LLM 输出的是结构化的编译器变换命令,编译器再通过依赖分析判断这些变换是否合法。这样可以把 LLM 的自由度限制在一个更可控的动作空间里。
论文还做了一个对照实验:让 LLM 直接生成变换后的 C 代码。结果显示,这种方式的几何平均加速比标准 COMPILOT 低约 14% 到 16%。不仅如此,一部分初看通过的代码在二次检查中暴露出错误输出或非法变换问题。论文还提到,直接生成完整代码会明显增加 token 消耗,因为输出完整代码比输出简短的变换命令更贵。
多轮反馈带来的差异
COMPILOT 不是只让 LLM 猜一次。每一轮结束后,系统都会把结果反馈给 LLM:这次 schedule 是格式错误、违反依赖、编译失败,还是成功运行;如果成功运行,速度提升了多少。
论文做了一个去掉反馈的消融实验。没有反馈时,LLM 只能持续基于自己的判断提出 schedule。结果在 T=30 时,标准 COMPILOT 的单次运行加速是 2.66×,无反馈版本是 2.01×;best-of-5 场景下,标准版本也明显更高。
这说明反馈本身是系统能工作的关键部分。LLM 会犯错,尤其是在复杂循环依赖关系上。但只要编译器能把错误类型和性能结果进行反馈,LLM 就有机会在后续轮次里避开一些失败模式,继续试探新的组合。

图 6:Fig. 10 展示不同 benchmark 中 runnable / invalid / illegal schedule 的比例
这个结果也提醒我们,COMPILOT 并没有让 LLM 变成一个可靠的编译器专家。根据论文统计,在 30 轮对话范围内,LLM 提出的 schedule 中只有约 36.1% 能最终成功编译并运行,31.4% 是 invalid,32.5% 是 illegal。看得出来,大量候选方案其实是会失败,只是失败被编译器拦住,并转化成下一轮反馈了。
COMPILOT vs Pluto
论文还把 COMPILOT 和 Pluto 这样的 polyhedral optimizer 做了比较。
在 best-of-5、每次 30 轮的设置下,COMPILOT_5@30 相比 Pluto 优化后的代码达到 2.94× 几何平均加速。按论文统计,在 150 个 PolyBench instance 中,COMPILOT 优于 Pluto 的有 119 个,差距在 5% 以内的有 9 个,低于 Pluto 的有 22 个。
这里也需要看实验条件。论文分析认为,COMPILOT 的一部分优势来自实测反馈:如果某个优化方案实际运行后变慢,COMPILOT 有机会在后续迭代中避开它;而 Pluto 这类传统优化器更多依赖静态模型和预设规则,通常不会根据真实运行结果一轮轮调整。
论文也做了一个 “capped Pluto” 对照:如果 Pluto 的优化导致变慢,就退回原始代码。这样一来,COMPILOT 相对 Pluto 的整体优势从 2.94× 降到 1.78×。这个对照说明,COMPILOT 的一部分优势来自实测反馈:当某个优化方案实际跑起来变慢时,系统有机会在后续迭代中避开它;另一部分优势则来自它确实搜索到了更好的 schedule。

图 7:Fig. 11,COMPILOT_5@30 与 Pluto 的对比。红线表示与 Pluto 持平,高于红线表示 COMPILOT_5@30 更快,低于红线表示 Pluto 更快。
不同模型的表现差异
论文还比较了多个 LLM 在同一套流程里的表现,包括 gemini-2.0-flash、gpt-4o、llama3.3、gpt-o3-mini、qwq、qwen2.5-coder、codestral 等。
从结果看,模型之间确实有差异,但差异不完全由“是不是推理模型”或“是不是代码模型”决定。gemini-2.0-flash、gpt-4o、gpt-o3-mini 的表现比较接近;推理模型没有稳定压过非推理模型;专门面向代码的模型,在这个任务里也没有显示出明显优势。
这和 COMPILOT 的任务形式有关。模型需要遵守固定输出格式,理解循环结构,提出高层变换命令,还要根据编译器反馈继续调整。它考验的不只是代码生成能力,还包括结构化输出、规则遵守和多轮反馈利用。

图 8:Table I,不同 LLM 在不同迭代次数下的 COMPILOT@T 几何平均加速
边界与适用场景
整体来看,这篇论文的重点不在于证明 LLM 可以自动改好所有代码,而是把问题收在一个更具体的范围里:循环密集型代码、编译器可验证的变换空间、多轮实测反馈。
在这个范围内,COMPILOT 展示了一种比较清晰的思路:让 LLM 负责提出 schedule,让编译器负责验证、执行和测速。效果来自这个闭环,而不是模型一次性生成一段更快的代码。
所以,这篇论文更适合看作一个有边界的编译优化实验。它不能直接推到普通业务代码上,但对于已有编译器基础设施、又需要反复调度搜索的性能优化场景,提供了一个值得参考的样本。
论文文献
- Agentic Auto-Scheduling: An Experimental Study of LLM-Guided Loop Optimization arxiv.org/pdf/2511.00592