NVIDIA CompileIQ 解决了性能工程中最棘手的问题之一:找到为特定工作负载解锁最佳性能的编译器选项。
想象一下,有一个团队花了数周时间在 GPU 上优化 LLM 推理工作流,调整批量大小,量化到 FP8,采用闪存注意力,融合他们所能做的每一个内核。分析器会说没有任何东西需要压缩。
但如果你可以将编译器本身变为可调节的参数,会怎样呢?现在这已经成为可能。NVIDIA CUDA 13.3 的发布包含了 CompileIQ,这是一种由人工智能驱动的编译器自动调优框架,利用进化算法和遗传算法,针对单个工作负载优化 NVIDIA 通用 GPU 编译器。
NVIDIA GPU 编译器应用相同的默认启发式算法 (寄存器分配策略、指令调度决策、循环展开值等)编译的每个内核。这些启发式算法旨在为各种工作负载提供良好的结果。但“全面良好”和“最适合您的工作负载”是两个截然不同的概念。
AI基础设施的竞争格局使得这一差距不容忽视。各团队在开发自定义CUDA、Triton和Helion内核时,都在竭力争取每一个百分点的吞吐量提升。然而,截至目前,仍未找到针对特定工作负载优化代码生成的有效方法。
90% 的问题和机遇
要理解编译器级优化为何如此重要,请思考 GPU 计算在现代 LLM 推理中的实际应用。
在注意力推理内核中,FFN/ MLP 块线性层中的 GEMM 以及 Q、K、V 和输出投影约占总 FLOP 的 70%。扩大的点积注意力、融合注意力和闪光注意力变体占另外 25%。这两个内核系列共同代表了 90% 以上的端到端推理计算。
这并非 AI 推理所独有。在许多应用和算法中,大部分计算时间都花费在代码的相对较小部分上,这意味着这些较小的代码部分对应用性能的影响非常大。正因如此,这些代码部分的性能提升 (即使只是百分比的一小部分) 对整体应用程序性能的提升也是巨大的。
CompileIQ 简介
CompileIQ 是一个由 AI 驱动的编译器自动调优框架,采用进化算法和遗传算法来优化 NVIDIA GPU 编译器,针对单个工作负载进行处理。与为所有工作负载采用统一编译器配置的传统方法不同,CompileIQ 颠覆了这一做法,为每个最关键的内核生成定制化的专用编译器配置。
在幕后,CompileIQ 会探索大量不会通过任何公共编译器标志公开的内部编译器参数:寄存器分配策略、指令调度策略、循环转换等。输出是一个高级控制文件 (ACF) ,编译器通过 – apply-controls 标志接收该文件,从而生成专门针对工作负载优化的内核二进制文件。
以这种方式思考:您的编译器已经能够为您的内核生成更好的代码。它只是不知道哪种内部设置组合会达到那里。CompileIQ 的进化搜索会自动找到该组合。
该团队在耗尽了他们已知的每一个优化杠杆后,都使用了 CompileIQ 的新杠杆,即编译器本身。
您可以使用 pip 将 CompileIQ 安装到您喜欢的 Python 环境中,如下一节所示。领先的 AI 实验室已经在生产环境中使用它来处理对性能要求严苛的工作负载。
只需 4 步即可开始使用
CompileIQ 是一个具有简单工作流程的 Python 包:
- 学习
- 安装
- 定义您的目标
- 运行
pip install compileiq |
CompileIQ 附带适用于 PTXAS 和 NVCC 的编译器搜索空间,可通过 API 自动获取。无需手动下载或配置。
作为开发者,您的工作是定义目标函数:例如,一个可调用的 Python,它获取候选编译器配置,使用它编译您的内核,对结果进行基准测试,并返回分数。如果您可以对您的内核进行基准测试,则可以使用 CompileIQ。
举个例子:
import subprocessfrom compileiq.ciq import Searchfrom compileiq.types import SearchConfiguration# Define your objective: compile with the ACF and measure runtimedef objective(config_blob): with open("config.acf", "wb") as f: f.write(bytes.fromhex(config_blob)) result = subprocess.run([ "ptxas", "-v", "-arch=sm_90a", "--apply-controls", "config.acf", "my_kernel.ptx" ], capture_output=True, text=True) return extract_runtime(result.stdout)# Configure and run the evolutionary searchconfig = SearchConfiguration( pool_size=32, cull_size=24, generations=20, mutate_rate=0.1, problem_type="min", num_objectives=1)search = Search(config, objective)best_acf = search.run() |
以上代码可分为三个不同的部分:
- 定义您的目标:我们定义函数目标,它会在
config_blob中评估要评估的配置,将其保存到磁盘,编译并运行内核,然后提取指标。 - 配置搜索:设置将驱动搜索的参数,例如在一代中要尝试的候选项数量 (
pool_size) 、要运行的代数以及要优化的目标数量。 - 运行搜索并提取最佳候选项。
就是这样。当搜索开始时,CompileIQ 会初始化一系列编译器配置,根据您的目标函数评估每个配置,选择表现最好的配置,应用突变和交叉来生成新的候选配置,并在连续几代中收于最佳 ACF。
您可以在目标函数中定义“更好”对工作负载的含义,然后 CompileIQ 就能找到它。

示例
现在,我们来重点介绍一些您可以尝试的独立示例。GitHub 资源库中有许多示例,我们将在此处演示两个示例。首先是单一目标示例,该示例与 GPU 计算无关,但展示了使用 CompileIQ 的原则。
from compileiq.ciq import Searchfrom compileiq.types import SearchConfigurationimport compileiq.search_spaces.base as ssdef objective(config): score = config["x"] ** 2 + config["y"] return scoredef main(): dna_config = { "x": ss.range(start=1.0, end=20.0, step=0.5), "y": ss.choice([1, 2, 3]), "z": ss.literal("this is a constant", knockout_prob=0.5), } main_config = SearchConfiguration( generations=5, problem_type="min", num_objectives=1, ) tuner = Search( objective_function=objective, search_space=dna_config, search_config=main_config, ) results = tuner.start() print(f"Entire Results Dataframe:\n {results.get_results()}") print(f"Best Result: {results.get_best_result()}")if __name__ == "__main__": main() |
一、目标函数:
def objective(config): score = config["x"] ** 2 + config["y"] return score |
这是一个将 x 乘以平方的简单函数,并将该值添加到 y 中。这是我们要优化的函数:
dna_config = { "x": ss.range(start=1.0, end=20.0, step=0.5), "y": ss.choice([1, 2, 3]), "z": ss.literal("this is a constant", knockout_prob=0.5), |
配置指定允许变量使用的值:对于 x,取值范围为 1.0 到 20.0,步长为 0.5;对于 y,可选值为 1、2 或 3;z 对目标计算无实际作用,用于说明丢弃变量的情况。
main_config = SearchConfiguration( generations=5, problem_type="min", num_objectives=1, ) |
接下来,我们指定搜索配置。在本例中,我们将运行 5 代,我们希望最小化目标函数,并且只分析一个目标。
代码的其余部分用于设置参数和搜索。这是一个非常简单的目标函数,您可以轻松地手动进行计算,但为便于说明,以下是您运行代码时发生的情况。
$ python single_objective.py
🧬 Generation: 5/5|█| [elapsed: 00:00 · eta: 00:00] , 🏆 best_score=3.2500
Entire Results Dataframe:
metadata ... params
0 {"pid": 2562276} ... {'x': 2.5, 'y': 2, 'z': 'this is a constant'}
1 {"pid": 2562276} ... {'x': 8.5, 'y': 3}
2 {"pid": 2562276} ... {'x': 11.0, 'y': 1, 'z': 'this is a constant'}
3 {"pid": 2562276} ... {'x': 19.0, 'y': 2}
4 {"pid": 2562276} ... {'x': 13.5, 'y': 3}
.. ... ... ...
109 {"pid": 2562276} ... {'x': 1.5, 'y': 3, 'z': 'this is a constant'}
124 {"pid": 2562276} ... {'x': 1.0, 'y': 2, 'z': 'this is a constant'}
126 {"pid": 2562276} ... {'x': 2.0, 'y': 1, 'z': 'this is a constant'}
135 {"pid": 2562276} ... {'x': 3.0, 'y': 2, 'z': 'this is a constant'}
138 {"pid": 2562276} ... {'x': 1.5, 'y': 3}
[61 rows x 4 columns]
Best Result: {'metadata': '{"pid": 2562276}', 'generation': 4, 'score_1': 3.0, 'params': {'x': 1.0, 'y': 2, 'z': 'this is a constant'}}
请注意,列出的最佳结果是 x = 1.0 且 y = 2,得分为 3.0。但我们知道最高分出现在 x = 1.0 且 y = 1 的情况下,因此在此例中,CompileIQ 未能找到最优解。由于代数较小(本例中为 5)且搜索过程具有随机性,因此未能找到绝对最优解。然而,如果将生成次数增加至更多,例如 15 次,通常几乎总能找到最佳答案。
我们来看一个示例,该示例用于衡量特定内核的 GPU 性能。在 GitHub 资源库中,有一个使用 NVCC 构建归约核函数的示例。为简洁起见,我们不会在此处包含整个代码,但会显示用于说明概念的片段。
在用于设置搜索的 Python 函数中,我们有如下代码:
# Configure and run search search_space = args.search_space if args.search_space else NvccSearchSpace(version=cuda_version) config = SearchConfiguration( problem_type=ProblemType.MIN, generations=args.generations, pool_size=args.pool_size, ) |
搜索空间配置为将 NvccSearchSpace 用于 CUDA 13.3。您可以看到要优化的问题类型为 MIN,这意味着我们想要找到目标函数的最小值。Generations 和 pool size 是命令行参数,在此 Python 脚本中分别默认为 10 和 15。GPU 内核代码设置为运行归约,然后打印出时间,目标函数 (此处未列出) 本质上是构建和运行核函数,并搜索 Time = 字符串,这是在搜索空间中最小化的值。
假设您位于 compilers/nvcc_example 文件夹中,以下是您运行搜索时的样子。
$ python optimize_reduction.py --arch sm_120 Running baseline... Baseline: 0.777 ms Starting optimization (10 generations, pool=15)... 🧬 Generation: 10/10|█| [elapsed: 09:29 · eta: 00:00] , 🏆 best_score=0.7700, a Baseline: 0.777 ms Optimized: 0.770 ms Speedup: 1.01x Config saved: reduction_best_config.bin Usage: nvcc --apply-controls reduction_best_config.bin -arch=sm_120 ...
通过搜索发现的性能提升幅度大约为 1%,您可以看到,要应用此保存的配置,您只需使用 –-apply-controls 选项并添加刚刚生成的 ACF 即可。
多目标优化和 IP 保护
大多数自动调整工具针对单个指标 (通常是运行时) 进行优化。CompileIQ 更进一步,支持多目标优化,同时探索在运行时、编译时间和功耗等相互竞争的目标之间的权衡。
这一点很重要,因为“尽可能快”并不总是正确的答案。功率受限的数据中心可能会接受边缘运行时间增加,以换取显著降低的功耗。CI/ CD 工作流可能会优先考虑编译时间,以保持快速迭代周期。嵌入式部署可能需要平衡这三者。
CompileIQ 的进化引擎能够计算非支配解的 Pareto frontier,即那些在不牺牲另一个目标的前提下,无法进一步优化任一目标的配置。您的团队可根据自身约束条件,灵活选择最适合的权衡方案,而非受限于单一优化维度。
此功能将 CompileIQ 的适用性扩展到了 LLM 推理之外。在使用 NVIDIA 编译器的任何地方 (科学计算、智能汽车、图像处理、推荐系统) ,CompileIQ 都可以探索默认启发式算法所缺少的优化空间和表面配置。
在 IP 保护方面,CompileIQ 的设计可确保两侧的安全。编译器内部仍然封装在搜索空间和 ACF 中。用户无需担心编译器参数。用户工作负载永远不会离开自己的环境;目标函数在本地运行,并且只生成生成的 ACF。ACF 可以安全地用于版本控制和跨团队共享。

结果和生产采用率
CompileIQ 已在生产工作负载的 GPU 和 CPU 目标上完成验证。例如,如本次 GTC 演讲所示,Meta 在 TritonBench 和 Helion 内核上的性能提升最高可达 15%,如本次 GTC 演讲 所示。
这些收益建立在内核中已经优化的基准之上,而开发者认为这些基准是“完成”的。这些改进是 CompileIQ 发现默认启发式算法永远不会选择的编译器配置的直接结果。
领先的 AI 实验室已经在生产环境中使用 CompileIQ 来处理对性能要求严苛的推理和训练工作负载。它生成的 ACF 是完全可复制和可移植的:只要相同的基准和底层编译器匹配,相同的 ACF 就会在不同部署中生成相同的优化二进制文件。团队将 ACF 与内核源代码一起用于版本控制,使编译器优化成为开发工作流程中经过版本控制、可审查的部分。
轮到您了
编译器搜索空间可用于 PTXAS 和 NVCC。请确定影响最大的内核(GEMM 和注意力是最佳候选),编写基准测试以衡量对您的工作负载关键的性能指标,并运行 CompileIQ。
您可以在 CompileIQ 文档网站查阅文档、API 参考和实用示例。如需帮助或支持,请在 CompileIQ GitHub 仓库中提交问题。
我们需要明确一点:CompileIQ 并不是一个神奇的工具,可以自动将编写不佳的代码转换为高性能代码。要从 CompileIQ 中获得最大值,您需要从合理的高性能代码开始,然后启用最终的编译器启发式调整,以实现最大性能。
但是,如果您的团队已经耗尽了他们已知的所有优化手段,CompileIQ 会为他们提供一个新的手段,即编译器本身。
下载 CompileIQ,查看 GitHub 中的示例,并立即开始优化您的内核。