高效优化需要建立一个具有明确参数的搜索空间和定义清晰的成本模型。尽管这些要素提供了基本结构,但搜索的实际执行——即自动调优会话——才是最终实现性能提升的地方。自动调优会话就像一个实验循环。编译器迭代地生成候选内核,测量它们在目标硬件上的性能,并利用这些反馈来优化其搜索策略。调优会话的架构调优会话并非一个单一的整体过程。它涉及到多个独立组件的协作:任务调度器、代码构建器、运行器和测量回调函数。理解这些部分如何互相配合,对于调试性能问题或为新型硬件配置调优器非常必要。在典型配置中,运行编译器的机器(宿主机)可能与执行代码的机器(目标机)不同。这在边缘AI中很常见,你可以在高性能工作站上编译,但为手机或嵌入式加速器进行调优。调优器依赖RPC(远程过程调用)机制向设备发送编译好的构件,并接收计时数据。以下图表说明了自动调优会话期间的循环数据流。优化器生成配置,这些配置被编译并发送给硬件运行器。执行指标随后被反馈以更新成本模型。digraph G { rankdir=TB; node [fontname="Helvetica", shape=box, style=filled, color="#dee2e6", fillcolor="#f8f9fa"]; edge [fontname="Helvetica", color="#868e96"]; subgraph cluster_host { label = "宿主机"; color = "#adb5bd"; fontcolor = "#495057"; style = dashed; Optimizer [label="优化器/策略\n(XGBoost/遗传算法)", fillcolor="#d0bfff"]; Builder [label="代码构建器\n(LLVM/CUDA)", fillcolor="#a5d8ff"]; CostModel [label="成本模型", fillcolor="#ffc9c9"]; } subgraph cluster_target { label = "目标设备"; color = "#adb5bd"; fontcolor = "#495057"; style = dashed; Runner [label="硬件运行器\n(RPC 服务器)", fillcolor="#96f2d7"]; } Optimizer -> Builder [label=" 1. 调度配置"]; Builder -> Runner [label=" 2. 上传二进制文件"]; Runner -> CostModel [label=" 3. 执行时间"]; CostModel -> Optimizer [label=" 4. 更新权重"]; {rank=same; Optimizer; CostModel} }提取调优任务深度学习模型包含数十或数百个算子。然而,并非每个算子都需要调优。许多操作,例如Reshape或ReLU,受内存限制,与Conv2d或MatMul等受计算限制的操作相比,优化空间有限。会话的第一步是任务提取。编译器遍历计算图,识别匹配特定模式的子图,这些模式通常是重度算术运算符及其周围的逐元素操作(由于算子融合)。每个独特的子图结构都成为一个“调优任务”。如果一个模型十次使用相同的卷积层结构,调优器只需优化该内核一次并重用调度。我们通常定义一个调优配置对象,其规定了目标硬件的限制。例如,为GPU调优时,任务必须知晓每个块的最大线程数和共享内存限制。配置搜索策略识别出任务后,您必须选择一个搜索策略。这决定了调优器如何在上一节定义的搜索空间中进行。网格搜索: 穷尽搜索空间中的每个有效配置。由于平铺和矢量化参数的组合爆炸,这对于现代神经网络来说通常不切实际。随机搜索: 从空间中均匀采样配置。这可作为基准,但通常无法在合理时间内找到最佳性能。基于模型的搜索(贝叶斯/XGBoost): 行业生产标准。调优器训练一个替代模型(成本模型)来预测候选调度的排名。它平衡了探索(尝试空间中不确定性高的部分)和利用(优化当前最佳候选方案的变化)。一个典型的Python伪代码配置可能如下所示:tuning_option = { "n_trials": 2000, # Maximum number of configurations to test "early_stopping": 600, # Stop if no improvement after N trials "measure_option": { "builder": LocalBuilder(), "runner": RPCRunner( key="android_device", host="0.0.0.0", port=9190, number=5, # Average 5 runs per config repeat=1 # Measure the loop 1 time ), }, "tuner": "xgb", # Use XGBoost-based tuner }执行调优循环当tune命令被调用时,循环开始。自动调优器的控制台输出通常非常详细,实时显示指标。正确解释这些数据非常必要。您通常会看到诸如GFLOPS(每秒十亿次浮点运算)或延迟(以毫秒为单位)之类的指标。目标是最大化GFLOPS或最小化延迟。在会话初期,由于调优器随机采样空间来初始化成本模型,性能通常较低。随着成本模型获取数据(训练样本),调优器开始给出更好的候选方案,您应该会观察到性能的显著上升趋势。最终,曲线会趋于平稳。这表示调优器可能已找到该特定算子的硬件限制,或者陷入了局部最优。下图显示了矩阵乘法算子的典型收敛曲线。蓝色点代表每次试验结果,橙色线记录了迄今为止找到的最佳性能。请注意,在成本模型学习过程中,前50次试验性能迅速提升。{ "layout": { "title": "调优收敛:ResNet-50 Conv2d 层", "xaxis": { "title": "试验次数", "showgrid": true, "gridcolor": "#e9ecef" }, "yaxis": { "title": "吞吐量 (GFLOPS)", "showgrid": true, "gridcolor": "#e9ecef" }, "plot_bgcolor": "white", "showlegend": true }, "data": [ { "type": "scatter", "mode": "markers", "name": "试验结果", "marker": { "color": "#4dabf7", "size": 6, "opacity": 0.6 }, "x": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], "y": [15, 18, 22, 45, 60, 58, 72, 70, 85, 88, 87, 92, 91, 93, 93] }, { "type": "scatter", "mode": "lines", "name": "当前最佳", "line": { "color": "#fd7e14", "width": 3 }, "x": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], "y": [15, 18, 22, 45, 60, 60, 72, 72, 85, 88, 88, 92, 92, 93, 93] } ] }处理调优产物调优会话的即时输出并非编译好的二进制文件,而是调优日志。这通常是一个JSON或文本文件,其中包含每次尝试的配置及其相应的执行时间历史。这个日志文件是一项宝贵的资源。它有效地作为数据库,将算子签名(输入形状和数据类型)映射到其最优循环调度参数。检查: 您可以手动检查表现最佳的配置,以了解编译器“学习”到了什么。例如,您可能会发现,对于某个特定的GPU,调优器始终选择$16 \times 16$而不是$32 \times 32$的瓦片大小,以避免寄存器溢出。迁移学习: 如果您有一个来自类似硬件设备的调优日志,您可以使用它来为新会话的成本模型提供初始数据。这显著减少了在新设备上找到良好调度所需的时间。应用最佳调度会话的最后阶段是优化模型的编译。这与标准编译流程不同,因为编译器现在拥有外部的真值来源。当您调用高级构建函数(例如TVM中的relay.build或torch.compile)时,您将调优日志作为上下文管理器或参数提供。当编译器降低计算图时,它会为每个算子查询日志。如果找到匹配的条目,编译器会绕过其默认启发式算法,并应用最优配置中定义的特定循环转换、平铺因子、矢量化长度和展开因子。如果未找到特定算子的条目(可能是调优会话中断或算子被排除),编译器将回退到默认调度。这确保了模型仍能运行,尽管这些特定层不会受益于优化提升。生成的机器代码是针对图中计算密集型部分的高度调优内核与其余部分的通用实现的混合体。这个优化后的二进制文件可以部署,与未优化的基准相比,它提供了更低的延迟和更好的能效。