想象你是一家 AI 公司的技术总监,请来了一位金牌旅行规划师——唐导。公司每个月的任务,就是把一批固定景点(计算任务)都玩一遍,但这些景点分布在不同城市(硬件)里:有的在步行街,有的在盘山公路,有的在高铁新城。唐导要做的,是针对每一个城市为这批景点设计最优游览路线:先逛哪个、坐什么车、每站停多久、几路人马并行出发。路线怎么设计,直接决定你一天能玩完几个景点——对应到程序上,就是代码跑得快不快、功耗高不高。 AutoTVM 和 Ansor 就是这样一套“自动路线规划系统”。AutoTVM 像一位有经验模板的老师傅,靠经典路线库快速出方案;Ansor 像一个不背模板的年轻探险家,从白纸开始自动设计新路线。两者都通过“搜索 + 预测 + 实测”的方式,为每个硬件后端自动找到最优的计算方案,让 AI 代码在不同芯片上跑出接近手工调优的性能。本文就带你走进这套系统,看看自动调度到底在搜索什么、如何预测、又如何落地。
学习自动调度,你将理解 AI 编译器如何用“搜索 + 预测”替代手工优化,这对从事推理部署、芯片软件栈和性能优化工作的从业者,是实打实的核心竞争力。
可视化演示
展示AutoTVM/Ansor自动调度核心机制:搜索空间演示模板化参数空间与候选schedule采样;代价模型展示ML预测器替代硬件实测的性能预测流程;Ansor改进展示无模板sketch-注释自动构建搜索空间与进化搜索协同优化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
搜索空间与候选采样 模板参数→候选schedule→实测 Search 模板化调度定义可调参数搜索空间
调度是什么
想象你找金牌旅行规划师“唐导”帮忙设计一天的旅行。景点清单是固定的——故宫、长城、王府井,先看哪个、坐公交还是打车、每个景点待多久、中午在哪里吃饭,唐导的每一个决定都构成一套“游览执行方案”。只要清单不变,无论怎么走,你最终看到的景点都一样;但不同的走法,体力和时间的消耗却天差地别。
AI 编译器里的“调度”(schedule)就是这个道理。计算图(相当于景点清单)规定了必须做哪些计算,而调度决定了这些计算具体怎么执行:循环是串行还是并行?数据是逐元素处理还是分块搬进缓存?数组是按行访存还是按列访存?TVM 把这种“在不改变计算结果的前提下,重新安排计算执行方式”的动作统称为调度。
TVM 提供了一组调度的“基本动作”,就像路线规划里的微操:Split 把一个大循环拆成内外两层循环,Fuse 把相邻循环合并,Reorder 调整循环顺序,Vectorize 把标量计算打包成向量指令,Parallel 把循环分给多个线程并行执行。一个完整的调度方案就是这些基本动作的组合:
唐导不会凭感觉拍板,他会把这套“微操”组合记录成一份路书草稿,供后续反复修改和比选。调度方案也是如此——它本身是搜索的起点,而所有可能的方案合在一起,就构成了下一章要讲的“搜索空间”。
- 循环长度 N待处理数据的规模,例如 1024 个元素的数组遍历。
- 分块大小 B每个小块处理的元素个数,是搜索中要调的“旋钮”。
- 向上取整 ceil当 N 不能被 B 整除时,最后一块不满也要算一块。
- 分块总数结果表示一个循环被切成的块数,块数直接影响缓存复用和并行粒度。
搜索空间定义
唐导手头有一批“经验模板”:比如“故宫经典一日游”模板,包含路线骨架,但有些位置留了旋钮——几点出发(7 点、8 点、9 点)、在故宫待多久(2 小时、3 小时、4 小时)。把每个旋钮转到不同档位,就得到一条具体路线。所有档位组合出来的路线加起来,就是一个“备选路线池”。
在 AutoTVM 里,专家会用调度模板(template)描述一个算子的“路线骨架”,并在骨架里留下可调的位置,称为旋钮(knob):分块大小取哪几个值、循环展开几路、是否并行。每一组旋钮取值都是一个具体的调度配置(config)。所有配置的集合,就是搜索空间(search space)。
搜索空间的大小等于各旋钮取值数的乘积。旋钮越多、每档取值越多,空间就会爆炸式增长:
空间太大,搜索就像在没有地图的星球上找绿洲。因此生产实践中必须做空间裁剪,删除明显不合理的取值组合。唐导也会先淘汰“凌晨三点出发”这类匪夷所思的选项,把精力留给高价值的候选路线。
- 空间大小竖线括起来的集合大小,表示候选调度方案的总数。
- 旋钮总数模板中可调节参数(如分块大小、展开因子)的个数。
- 第 k 个旋钮的档位数第 k 个旋钮能取多少个离散值。
- 全排列连乘把每个旋钮的档位数乘起来,得到整个空间的规模。
特征提取方法
唐导认识一位“线路点评师”,人家不可能把每条候选路线都亲自走一遍,于是唐导把每条路线浓缩成一张属性卡:总里程、换乘次数、步行比例、沿途餐饮密度。点评师只看卡片,就能预估这条路线的体验分。
自动调度也面临同样的困难:代价模型要预测成千上万个调度的性能,不可能每个都先跑到硬件上测一遍。于是系统先从调度方案中提取特征向量(feature vector),把程序结构、循环嵌套深度、访存密度、并行粒度等信息浓缩成一组数值。
特征设计直接决定预测质量。经典做法包括:统计各类计算操作的个数、统计内存访问次数(访存密度)、记录循环层级的融合与拆分方式、记录向量化位宽等。这些特征拼接成一个固定维度的向量,喂给代价模型:
属性卡片做得好,点评师才能一眼分出高下;特征设计得好,代价模型才能在没跑过的情况下排出优劣。接下来我们看看点评师(代价模型)如何工作,以及唐导如何利用它快速逼近最优路线。
- 特征向量粗体 f 表示一组数值的集合,代表一个调度的“属性卡片”。
- 各维特征每一维对应一个统计量,如乘法次数、访存次数、并行层数。
- 维度数量特征的总个数,维度太少信息不足,太多则引入噪声。
- 拼接与转置把所有特征拼成列向量,方便与模型权重做矩阵运算。
代价模型预测
点评师的本事不是天生的:过去一年他吃过几百家餐厅、走过几百条路线,每一趟的“实际体验分”都记在小本子上。新路线来了,他靠这些经验做类比,给出预估分。
代价模型(cost model)就是用历史测量数据训练出来的回归模型。AutoTVM 早期使用基于 MLP 或 XGBoost 的模型,输入是调度的特征向量,输出是预测的执行时间;Ansor 则训练带样本权重的回归模型,重点拟合高质量样本。预测流程可以概括为:从搜索空间中取出一个配置 → 提取特征 → 送入代价模型 → 得到预估性能。
为了保持准确,系统会在真实硬件上跑一部分采样点,把实测结果加入训练集,迭代更新模型,形成“测量—学习—预测—再测量”的闭环。底层抽象的数学形式如下:
点评师的预估不一定百分百准,但足够用来指导搜索方向;代价模型用“近似正确”换取了“快速排序上万方案”的能力。唐导终于不用实际走完每条路线再做决定,规划时间被大大压缩。
- 预测时延带帽子的 t 表示模型预估出来的执行时间,不是真实测量值。
- 权重向量与特征向量每个特征对应一个权重,表示该特征对性能的影响程度。
- 内积运算对应位上权重和特征相乘再求和,本质是“加权打分”。
\ \cdot \ - 偏置项当所有特征为零时的基础时延,相当于点评师的“起评分”。
搜索算法策略
有了备选路线池和点评师,就差一个“怎么逛”的策略。唐导的做法是:先随机挑几条路线试水,然后重点在分高的路线附近微调;偶尔故意跳到一个陌生方案,防止被局部的“好路线”困住。
这就是模拟退火(simulated annealing)的思路。AutoTVM 内置多种搜索策略:随机采样、模拟退火、遗传算法等。搜索中反复执行“生成新配置 → 代价模型打分 → 决定是否接受”,直到满足迭代预算或收敛条件。
模拟退火的特点是“温度”控制:高温阶段敢于接受更差的方案,扩大探索范围;温度逐渐降低后,只接受更优方案,向局部最优收敛。接受较差方案的概率由 Metropolis 准则给出:
温度从高到低,就像唐导先“广撒网”收集情报,再“锁定目标”精雕细琢。AutoTVM 还配合分阶段搜索:先淘汰明显差的配置,再对幸存者做完整评估,进一步压缩搜索成本。
- 接受概率系统愿意“将就”一个更差方案的可能性。
- 性能变化量新方案与当前方案在代价模型打分上的差值,正数表示变差。
- 当前温度随搜索进行逐渐下降的调度参数,控制探索与收敛的平衡。
- 指数衰减温度高时指数接近 1 敢乱跳,温度低时指数趋近于 0 只爬山。
AutoTVM工作流
唐导接到一个新客户,完整流程是这样的:先确认心愿单(计算图),套用经典模板(调度模板),给模板设置一堆旋钮(搜索空间),让点评师批量打分(代价模型),同时抽几趟真正试跑(硬件实测),最后把得分最高的路线写成详细路书(代码生成)。
AutoTVM 的调优分为几个阶段:tuning(调优)阶段利用模板枚举配置,通过“测量 + 模型预测”寻找最优 schedule;找到后进入代码生成阶段,将最优配置应用到底层代码,产出可在目标硬件上高效运行的算子。整个过程的核心,可以用一个简洁的表达式概括——在所有候选调度中,选代价模型预测最优秀的那一个:
于是,从“模板、搜索空间、特征、代价模型、搜索算法”到“代码生成”,一条完整的流水线把手工调优经验变成了可复用的自动化系统。唐导从此不必亲力亲为,流水线替他完成大部分规划工作。
- 最优调度带星号的 s 表示最终选中的调度方案,是搜索的“冠军路线”。
- 候选集合调度 s 属于搜索空间 S,表示遍历范围内的每个配置。
- 求最大arg max 表示取出使后面表达式取最大值的那个 s,而非最大值本身。
- 代价模型打分PredictCost 是代价模型的调用,输入配置、输出预测性能分。
Ansor自动生成
老牌规划师唐导靠经验模板吃饭,但年轻的“安索”(Ansor)觉得模板限制了想象力——总有客户要去冷门小众的景点,老模板套不上。安索干脆不背模板,拿一张白纸,按几条基本构图规则(草图 sketch)现场画路线,再用“进化试错”反复打磨。
Ansor 的核心创新是“自动生成搜索空间”:先用草图(sketch)机制枚举程序的粗粒度结构(循环怎么拆分、怎么融合、并行放在哪一层),再用进化搜索与模拟退火组合出细粒度的填充参数(分块大小、向量化宽度)。全程不需要专家手写模板,搜索空间的边界由机器自动划定。
Ansor 的代价模型同样遵循“学习—预测—再测量”的闭环,但训练时对高质量候选分配更高权重,使模型更关注搜索热点区域,其训练损失为加权均方误差:
用白纸画路线,好处是不受老模板束缚,坏处是画出的路线可能奇怪。Ansor 用一遍遍跑真机测量来纠偏,最终在多个硬件后端的算子性能上普遍追平或打败 AutoTVM,证明了“自动生成搜索空间”这条路走得通。
- 样本数训练集中已经真实测过的调度方案个数。
- 样本权重第 k 个样本的重要程度,高质量候选的权重更高。
- 预测与实测的差模型预估值减去硬件实测值,残差越小说明越准。
- 加权均方先平方消除正负、再按权重平均,得到整体误差。
跨硬件应用
唐导的客户千奇百怪:徒步的学生、自驾的家庭、骑行的背包客。他们去的是同一批景点(同一个算子),但最优路线完全不同——自驾讨厌停车难,徒步怕绕路,骑行怕陡坡。唐导从不拿同一份路书糊弄所有人。
这就是 AutoTVM/Ansor 的价值所在:同样一个矩阵乘算子,在 x86 CPU、NVIDIA GPU、昇腾 NPU 上,最优调度方案可能完全不同——并行粒度、缓存分块、向量宽度各有差异。自动调度系统会针对每个后端重新搜索,自动生成该硬件上的高性能代码,不再需要工程师为每个硬件手工调优。
收益可以量化:以朴素实现为基准,自动调度通常带来数倍到数十倍的性能提升,同时大幅缩短算子库的部署周期:
往更大的图景看,自动调度正在重新定义 AI 编译器的分工:工程师不再为每个算子手写优化,而是定义问题、配置资源、验证结果。跨硬件的自动化优化,让“一份模型跑遍所有设备”从口号变成了每天都在发生的现实。
- 朴素实现时延未做任何优化(或仅默认编译)的算子执行时间。
- 调优后时延经过自动调度搜索并生成代码后的执行时间。
- 比值两个时延相除,衡量优化带来的相对收益。
- 加速比最终输出的指标,3 表示同任务快了 3 倍。
AI 应用场景
芯片厂商为新 NPU 自动生成算子库
阶段:算子自动调优与代码生成
解决的问题:新芯片上市前需要大量手工调优的算子库,每款芯片从零写优化代码要耗费数月。AutoTVM 把算子库生成变成一条“自动调度流水线”,如同唐导为一座陌生城市批量规划路线,无需每趟亲自踩点。
具体案例:某 IP 厂商在发布 AI 加速器 SDK 时接入 AutoTVM 后端,对 50 个常用算子自动搜索 schedule。在 x86 基准上对比跑分,多数算子在一天内就达到手工优化 80%—120% 的性能,算子库交付周期从季度缩短到周级。
云厂商异构集群的推理服务提速
阶段:推理部署优化
解决的问题:云上同时有 CPU、GPU、ARM 等机型,一份模型要为每类机器分别优化。手工调优成本高且不可复用;Ansor 自动为每类机型生成专属调度,如同唐导为不同客户各设计一条路线而非一套走天下。
具体案例:某推理平台使用 Ansor 对 ResNet-50 在 4 种云实例上重新调度,整体推理时延降低 15%—45%,且无需为每类机型手写算子。后续新机型上线时,只需重跑一遍自动调优流程。
边缘场景的 Transformer 模型加速
阶段:模型编译优化
解决的问题:Transformer 算子在边缘设备上性能差,手工调优排错慢、周期长。自动调度把“算子级优化”变成可重复的自动化过程,快速适配移动端与嵌入式硬件。
具体案例:某团队用 AutoTVM 对量化后的语言模型进行算子自动调优,在手机上把单 token 生成延迟降低约 30%,同时把跨设备适配时间从两周压缩到两天,加快了边缘产品的上市节奏。
Python 演示
import mathimport random def measure(tile_m, tile_n, M=128, N=128): # 模拟一次真机测量:给定分块尺寸,返回执行时延 tiles = math.ceil(M / tile_m) * math.ceil(N / tile_n) reuse = min(tile_m, 16) * min(tile_n, 16) / 256.0 penalty = max(0, tile_m - 16) * 0.5 + max(0, tile_n - 16) * 0.5 return tiles * (2.0 - reuse) + penalty TILES = [2, 4, 8, 16, 32, 64] def random_config(): # 随机生成一个调度起点 return random.choice(TILES), random.choice(TILES) def neighbors(cfg): # 在当前旋钮档位附近正负一档生成相邻配置 tm, tn = cfg out = [] for delta in (-1, 1): i = TILES.index(tm) + delta if 0 <= i < len(TILES): out.append((TILES[i], tn)) j = TILES.index(tn) + delta if 0 <= j < len(TILES): out.append((tm, TILES[j])) return out def simulated_annealing(max_iter=300): # 模拟退火搜索最优分块 cur = random_config() cost = measure(*cur) best_cfg, best_cost = cur, cost t0 = 5.0 for step in range(max_iter): t = t0 * (1.0 - step / max_iter) nxt = random.choice(neighbors(cur)) nxt_cost = measure(*nxt) if nxt_cost < cost or random.random() < math.exp(-(nxt_cost - cost) / t): cur, cost = nxt, nxt_cost if cost < best_cost: best_cfg, best_cost = cur, cost return best_cfg, best_cost if __name__ == "__main__": cfg, cost = simulated_annealing(300) naive = measure(2, 2) print("best config:", cfg) print("best cost:", round(cost, 3)) print("tuning speedup:", round(naive / cost, 2))
def measure(tile_m, tile_n, M=128, N=128):模拟一次真机测量,等价于把配置放到硬件上跑一遍。tiles = math.ceil(M / tile_m) * math.ceil(N / tile_n)计算分块个数,分块越小块数越多、调度开销越大。penalty = max(0, tile_m - 16) * 0.5 + max(0, tile_n - 16) * 0.5超过缓存容量的过大分块也要扣分。def neighbors(cfg):在当前旋钮档位附近正负一档生成相邻配置,即“路线微调”。if nxt_cost < cost or random.random() < math.exp(-(nxt_cost - cost) / t):Metropolis 准则:更优必接受,较差方案按概率接受。best_cfg, best_cost = cur, cost记录当前搜索路径上遇到的最优解。naive = measure(2, 2)用默认小分块作为朴素基线。print("tuning speedup:", round(naive / cost, 2))输出自动调度相对基线的加速比。
常见误区
误区AutoTVM/Ansor 既然是“自动”调度,那工程师只需要点一下按钮,搜索、模板、特征、模型全都自己长出来,我什么都不用管。
正解AutoTVM 仍然依赖专家模板定义搜索空间;Ansor 虽然自动生成搜索空间,但目标硬件怎么接、测量平台怎么搭、结果怎么验证,仍需要人来把关。好比 GPS 会自动规划路线,但出发前你得告诉它目的地,车也得有人开。
为何常见:“自动”二字容易让人联想到全自动驾驶,忽略了工程系统中的人机分工。
误区反正有自动搜索,干脆把所有旋钮取值全放开,让空间大到上亿,总觉得方案多了就一定能找到世界纪录。
正解空间越大,搜索效率越差,代价模型需要的样本越多,调优时间越长。AutoTVM 实际会做空间裁剪,Ansor 也通过草图机制限制结构数量。就像环球旅行不能把地球上所有公路都列入备选,否则计划一辈子都做不完。
为何常见:直观上觉得“选择多 = 更好”,没意识到搜索有预算限制,更大的空间意味着更稀疏的采样。
误区先随机跑几百个样本把代价模型训练好,然后让它一路预测到搜索结束,不再补充新数据。
正解AutoTVM/Ansor 都是“边测量边更新”:搜索过程中对真正跑过的点做实测,把结果加回训练集,让代价模型在重点区域越来越准。这就像点评师吃过一次米其林后,对同类餐厅的评论才更靠谱。
为何常见:把机器学习当“离线一次性任务”,忽略了主动学习在搜索闭环中的作用。
误区在 GPU 上调出的最优 schedule 是“金牌路线”,直接搬到手机上也应该同样快。
正解调度的好坏高度依赖硬件:缓存大小、核数、向量位宽都影响最优解。换一个后端必须重新调优,这正是 AutoTVM 跨硬件自动化的价值所在。就像雪地越野车的路线,放到城市跑车上未必快。
为何常见:把“最优”当成绝对属性,忘了性能评价函数与硬件强绑定。
面试题
Q1. 自动调度的核心目标是什么?
- A.手工编写更多汇编代码
- B.自动搜索最优计算方案← 正确答案
- C.减少算子的数学复杂度
- D.提高训练数据的规模
答案:B。自动调度就是自动搜索最优执行方案(schedule),而不是手工写汇编、改公式复杂度或扩数据。好比唐导自动规划路线,而不是亲手把路修一遍。选项 B 准确描述了这一目标。
Q2. AutoTVM 的搜索空间主要来自哪里?
- A.随机生成的完整程序
- B.深度学习模型的权重
- C.专家设计的调度模板← 正确答案
- D.硬件厂商提供的驱动
答案:C。AutoTVM 的搜索空间来自专家设计的调度模板(含旋钮),Ansor 才自动生成搜索空间。随机程序、模型权重和硬件驱动都不是搜索空间来源。好比唐导的备选路线池来自经验模板,而不是随便乱走。
Q3. 代价模型在自动调度中起什么作用?
- A.直接生成可执行二进制
- B.替代真实硬件测量环节
- C.预测调度方案的性能← 正确答案
- D.压缩计算图的结构
答案:C。代价模型输入特征向量、输出预测性能,用来在搜索中快速筛选配置。它不是编译器、不做真机测量、也不改图。如同点评师对路线打分,而不是亲自开车或修路。
Q4. 下列哪项属于典型的调度原语?
- A.Split 分块拆分循环← 正确答案
- B.ReLU 激活计算单元
- C.Softmax 归一化函数
- D.Adam 梯度优化器
答案:A。Split/Fuse/Reorder/Vectorize/Parallel 是调度原语;ReLU、Softmax 是算子,Adam 是优化器。容易混淆的是“算子”(做什么计算)和“调度”(怎么做计算)。分块拆分循环正是调度原语。
Q5. 模拟退火中温度参数的作用是什么?
- A.控制接受较差解的概率← 正确答案
- B.决定计算图划分层数
- C.影响特征提取的维度
- D.调节代价模型的权重
答案:A。模拟退火用温度控制接受较差解的概率:T 高则概率大、偏向全局探索,T 低则概率小、偏向爬山收敛。温度不决定图划分、特征维度或模型权重。就像唐导前期大胆乱逛、后期谨慎微调。
Q6. 特征提取的目的是什么?
- A.把调度方案转成数值数据← 正确答案
- B.直接执行调度方案
- C.删除计算图中的冗余节点
- D.提高硬件的时钟频率
答案:A。特征提取是把调度方案(循环结构、访存密度、并行粒度等)转成数值向量,供代价模型预测。不是为了执行方案、删节点或提频。好比把路线浓缩成属性卡片给点评师打分。
Q7. Ansor 相比 AutoTVM 的核心创新是什么?
- A.自动生成搜索空间去模板化← 正确答案
- B.使用手工汇编编写核心算子
- C.完全不需要代价模型干预支持
- D.只支持单个硬件后端平台
答案:A。Ansor 的核心是自动生成搜索空间(草图 + 进化),无需手写模板;AutoTVM 则依赖专家模板。Ansor 仍然使用代价模型、也支持多后端。就像年轻探险家白纸作画,而不是靠老师的模板库。
Q8. 常见的自动调度搜索算法包括哪些?
- A.模拟退火搜索算法← 正确答案
- B.遗传进化搜索算法← 正确答案
- C.全连接神经网络
- D.随机采样配合评估← 正确答案
答案:ABD。常见的搜索策略有模拟退火、遗传/进化算法、随机采样配合评估;“全连接神经网络”是代价模型的可能实现,不是搜索算法本身。搜索算法负责“逛路线池”,代价模型负责“打分”。
Q9. 关于代价模型的说法正确的有哪些?
- A.输入是调度特征向量← 正确答案
- B.输出是预测性能指标← 正确答案
- C.训练数据来自历史测量← 正确答案
- D.训练完成后固定不变
答案:ABC。代价模型输入特征向量、输出预测性能、训练数据来自历史实测,并且会随搜索不断补充样本迭代更新。训练完成固定不变恰恰是常见错误认知,应该是“边测量边更新”。
Q10. AutoTVM 自动调度流程包含哪些环节?
- A.定义搜索空间环节← 正确答案
- B.特征提取与建模层← 正确答案
- C.目标硬件实测位置← 正确答案
- D.手工编写所有内核
答案:ABC。自动调度流程包含定义搜索空间、特征提取与建模、目标硬件实测等环节;“手工编写所有内核”正是这套系统要消除的工作。整个过程就像唐导按“模板→打分→实测→定稿”的流水线出路线。