在编译器的世界里,每个 AI 算子的运行方式都有成千上万种“做法”:分块取多大、循环怎么交换、向量化用多宽……这些调度参数一经组合,可能出现 \(10^{30}\) 种候选方案,比银河系恒星还多。想象一位叫安师傅的金牌主厨,要在一本厚到没边的“菜谱册”里找出做招牌菜最快的方法。他当然不傻:随手做 20 道菜试一圈,把好吃的两两合并、偶尔撒把盐当成新菜,再试一圈……几十轮下来,那道最好的菜就被“进化”了出来。这正是 Ansor 自动调优的核心——进化搜索(Evolution Search):用代价模型快速预估、用真实测量精准把关,通过选择、交叉、变异一代代逼近最优调度方案,而这一切都发生在有限的预算之内。它取代了传统“每页都试一遍”的网格搜索,成为 TVM 生态中让深度学习模型跑得更快的关键引擎。
AI 从业者一旦理解了进化搜索,就能看懂 Ansor 这类自动调优工具的核心原理,知道编译器如何自动为算子找到最优调度,从而在实际部署中摆脱手工调参的玄学。
目录
可视化演示
展示Ansor进化搜索的种群迭代、交叉变异基因重组,以及代价模型预测与实测反馈机制,揭示自动调优算法核心原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+_captions.evolve+'
搜索空间:调度方案的“宇宙”
安师傅的厨房里挂着一本厚重的《招牌菜做法大全》,每一页都写着一种做法:第 37 页把肉切成 64 像素见方、大火爆炒 2 分钟、出锅前撒一把葱花;第 38 页则切成 128 见方、中火慢炖。食材、刀工、火候、下锅顺序,每一栏都有好几个选项,随意组合就能翻出天文数字的新页。安师傅咽了咽口水:“照我这本册子的厚度,一页页试根本不可能。”
这本“菜谱”在编译器世界里对应着调度方案的搜索空间。给定一个算子(比如矩阵乘法),编译器可以用多种方式生成目标代码:循环分块(tiling)、向量化(vectorization)、并行化(parallelization)、循环重排(reordering)等,每一种叫做一条调度原语(schedule primitive)。每条原语选取不同取值,就构成一个具体的调度方案,即一个程序变体(program variant)。所有变体的集合,就是搜索空间 S。在进化过程中,每个调度方案会被编码成一条紧凑的序列或向量(编码表示),交叉与变异就在这份“基因序列”上操作。
搜索空间的大小可以用简单的乘法估算。假设共有 k 种调度原语,第 i 种原语有 n_i 种可选取值,则总方案数为:
安师傅盯着菜谱册子叹气,但他转念一想:不需要翻完所有页,我只需要找到最优的那一两页——而“进化”能帮我快速逼近它。于是,他开始了第一批实验。
- 原语种类 kk 表示调度原语的种类数,好比菜谱上有几列可调选项(分块、向量化、重排……)
- 每种原语可选值 n_in_i 表示第 i 种原语有多少种合法取值,如分块大小可选 {16, 32, 64} 则 n₁ = 3
- 连乘得到总数N 是所有可能的调度方案总数,因为每列选项彼此独立、自由组合,所以用乘法而非加法
- 组合爆炸直觉即便每种原语只有五六种取值、共 30 种原语,N 也超过 10²⁰,远超任何穷举能力
初始种群:从随机开始的第一代
安师傅没有继续对着菜谱空想,而是挽起袖子,闭着眼从册子里随机抽了 20 页,照着做了 20 道菜,一字排开。有的切块大得离谱,有的火候忽大忽小,卖相难看,但安师傅很满意:“这才叫广撒网,哪个方向都别漏。”
进化搜索的第一步,就是生成这样的“第一批试验品”,称为初始种群(population)。种群中的每个方案是一个个体(individual),它们从搜索空间中随机采样(random sampling)得到。为了保证不遗漏任何可能有希望的区域,采样还要尽量均匀,避免 20 道菜全挤在同一种口味上——这就是多样性(diversity)。
假设种群规模为 P,第 0 代种群写作:
“第一批 20 道菜虽然大多难以下咽,但重要的是它们长满了整个口味地图。”安师傅把试吃结果记在本子上,心里已有了下一锅的灵感——好的方向,慢慢浮出水面。
- 种群记号\(\mathcal{P}_0\) 表示进化的第 0 代种群,也就是安师傅的第一批实验菜
- 个体与空间\(x_i\) 是第 i 个调度方案个体,它属于整个搜索空间 \(\mathcal{S}\)
- 均匀随机采样\(x_i \sim \mathrm{Uniform}(\mathcal{S})\) 表示每个个体独立、均匀地从搜索空间抽取,保证初始覆盖
- 种群规模 PP 是每代保留的个体总数,太小探索不足、太大每代评估开销高,Ansor 中通常取几十到几百
适应度评估:老厨师的直觉与食客的舌头
试菜是有成本的。安师傅的招牌菜工序复杂,一道菜从备料到出锅要 5 分钟,20 道就是 100 分钟,这还不算顾客等菜的不耐烦。聪明的安师傅想了个办法:先凭自己几十年的经验(直觉)快速扫一眼,把明显不行的菜直接划掉;只有少数看着有潜力的菜,才真正做给食客们试吃(实测)。食客的反馈,又反过来修正他的直觉。
这就是代价模型(cost model)与实际测量(measurement)的双轨评估。Ansor 训练一个代价模型 \(f_\theta(x)\) 来预测调度方案 x 的运行性能,用它给种群中所有个体打分排序;排名前 k 的候选才上真机实测。实测获得的真实性能数据会加入训练集,微调模型参数 θ,让“老厨师的直觉”越来越准。
评估流程可以表示为:
“光靠直觉会看走眼,光靠试吃要等到天黑。”安师傅的双轨策略,让每次试吃都花在刀刃上,而他的经验也在一次次反馈中进化——这正是 Ansor 能快速锁定好方案的关键。
- 模型打分\(\hat{f}(x)\) 是代价模型对调度方案 x 的预估性能,θ 为模型参数,训练数据来自历史实测记录
- 排名预筛对种群内所有个体按预测分排序,rank 函数给出每个个体在种群中的名次
- 选取实测集\(\mathcal{M}_t\) 是第 t 代被选中实测的个体集合,只保留预测排名前 k 的候选,其余不下锅
- 反馈修正实测得到的真实耗时 \(y_{\mathrm{meas}}(x)\) 会用于更新模型参数 θ,使预测逐步逼近真实性能
进化算子:交叉、变异与选择
第一轮试吃结束,安师傅对着成绩单琢磨:第 3 道菜的火候堪称完美,第 11 道菜的刀工让人惊艳,可惜它们各自都有短板。他灵机一动,把第 3 道的火候和第 11 道的刀工拼到一道新菜里——这就是“交叉”。偶尔,他还会故意手一抖多撒一把盐,给新菜加点“意外”——这就是“变异”。
进化算子(evolution operator)是驱动种群升级的引擎。选择(selection)负责决定谁有资格当父母:锦标赛选择(tournament selection)从种群中随机抽出几个个体“比武”,适应度最高的胜出。交叉(crossover)把两个父代方案的调度特征拼接成子代。变异(mutation)以一定概率随机扰动某个参数,比如把分块大小从 64 改成 128。精英保留(elitism)则把每代最优个体原封不动地放进下一代,防止好方案失传。子代生成规则可写成:
安师傅的新菜一批批出锅:火候基因、刀工基因在种群中像口碑一样传开,偶尔的“手抖”又带来惊喜。好味道在一代代进化中不断叠加,离那道完美招牌菜越来越近。
- 父代挑选\(x_a\)、\(x_b\) 是经过锦标赛选择选出的优秀父代,它们带着被验证过的“好基因”
- 交叉分支以概率 \(p_c\) 将两个父代的调度片段(如分块大小、向量宽度)重组生成新个体
- 变异分支以概率 \(p_m\) 对单个父代做小幅随机扰动,产生父代没有的新参数组合
- 概率归一\(p_c + p_m < 1\),余下概率直接继承父代,保证种群不会因剧烈变化而失稳
迭代收敛:有限预算内的逼近
安师傅给自己立下规矩:最多试 50 锅,每锅 20 道,预算用光就收工。另外,如果连续几锅都没能做出比之前更好的菜,说明已经接近这道菜的“天花板”,再耗下去也是浪费。他把规则贴在厨房墙上,提醒自己:进化的目的是在有限时间内端出最好的菜,而不是永远试下去。
迭代(iteration)是进化搜索的主循环:评估当前种群 → 选择父代 → 交叉变异生成下一代 → 再评估,如此循环 T 代。整个过程中,算法始终记录着历史最优个体。收敛判定通常看相邻两代最优适应度的提升量 \(\Delta f_t\) 是否跌破阈值 ε,若长期无改善则提前终止。Ansor 的实际运行也受总时长、总测量次数预算约束。最终返回的最优方案为:
50 锅很快用完,安师傅端出那盘集大成之作。它不是菜谱册子里每一页都试过的“理论最好”,却是试过的几千道菜里最棒的一盘——这正是工程世界的聪明活法。
- 历史最优\(x^{*}\) 是所有世代里实测性能最佳的个体,用 arg max 选出这个“历代最强者”
- 世代并集将第 0 代到第 T 代种群合并成一个集合,最优者就藏在这个大集合里
- 收敛判据\(\Delta f_t\) 是相邻两代最优适应度之差,当它小于阈值 ε 时认为搜索趋于饱和,可以收工
- 预算上限T 是最大进化代数,对应编译时的总时间预算,防止搜索无限进行下去
对比优势:为何碾压网格搜索
如果安师傅是个死心眼的“网格搜索”厨师,他会怎么做?把菜谱册子从第 1 页开始,一页一页全部做一遍。以每道 5 分钟算,做完 \(10^{20}\) 道菜需要千亿年。如果是个“随机搜索”厨师,他每天随机抽几页做,做完就忘,永远不知道哪道更好——没有记忆,就没有进步。
网格搜索(grid search)枚举搜索空间中所有组合,开销为完整空间大小;随机搜索(random search)虽然能覆盖一些点,但每轮彼此独立,没有利用已发现的好区域。进化搜索通过选择压力把算力集中在有希望的区域,同时保留一定探索能力,实际评估次数只是种群规模 P 与代数 T 的乘积:
安师傅的进化菜谱最终成了餐厅招牌。同样的思路,在 Ansor 中对 ResNet、BERT 等真实模型做了验证,开发者们发现:曾经需要手工调好几天的算子,进化搜索自动跑几十分钟就能逼近甚至超越人工调优的性能。这就是“聪明试错”对“笨拙穷举”的全面碾压。
- 进化开销\(N_{\mathrm{evo}}\) 是进化搜索实测/评估的个体总数,等于种群规模 P 乘代数 T
- 网格开销\(N_{\mathrm{grid}}\) 是网格搜索需要枚举的全部组合数,即整个搜索空间的大小
- 差距悬殊当搜索空间有 \(10^{20}\) 种方案时,网格搜索要试 \(10^{20}\) 次,而进化搜索可能只要 \(40 \times 30 = 1200\) 次
- 性价比根源进化搜索不做无用功——每代淘汰劣质方案、繁殖优质方案,评估预算花在最值得探索的地方
AI 应用场景
深度学习编译器的算子自动调优
阶段:模型部署优化 / 推理性能调优
解决的问题:手工调优依赖资深工程师的直觉和经验,能覆盖的调度组合极其有限;传统网格搜索面对 \(10^{20}\) 以上的搜索空间完全不可行。进化搜索自动在调度空间中寻找最优方案,把“老师傅经验”变成“算法自动探索”。
具体案例:TVM 编译器对 ResNet-50 的卷积算子做自动调优时,先用代价模型对随机采样的几十个候选打分排序,挑出 top-k 到真实 GPU 上测量,再用实测数据微调模型。经过约 30 轮进化,在十几分钟内找到了比默认实现快 2~3 倍的调度方案,且全程无需人工干预。
数据库查询计划自动优化
阶段:数据查询优化
解决的问题:查询优化器面对表连接顺序、索引选择、并行度设置等参数,同样存在组合爆炸。传统规则优化器依赖手工编写的启发式规则,容易错过反直觉的最优计划。
具体案例:借鉴进化搜索思想,优化器将一条 SQL 查询的执行计划编码为“基因”,随机生成一批候选计划,用统计信息代价模型预筛、抽样执行少量计划得到真实延迟,再交叉、变异产生下一代。数轮迭代后,自动找到比默认优化器快数倍的执行计划,尤其在多表 Join 场景收益显著。
Python 演示
import random # 搜索空间: 每种调度参数的可选值TILE = [16, 32, 64, 128, 256] # 分块大小VEC = [2, 4, 8, 16] # 向量化宽度STAGE = [1, 2, 3, 4] # 流水级数POP_SIZE = 12GENERATIONS = 20MUT_RATE = 0.2CACHE = {} # 测量缓存, 同一方案只实测一次 def random_sample(): # 从搜索空间均匀随机采样一个调度方案 return (random.choice(TILE), random.choice(VEC), random.choice(STAGE)) def cost_model(s): # 模拟代价模型: 预估运行耗时, 越小越好 t, v, st = s return 4000.0 / t + v * 2.0 + st * 3.0 + random.uniform(0, 5) def real_measure(s): # 模拟真实测量: 在预估基础上加噪声波动, 并缓存结果 if s not in CACHE: CACHE[s] = cost_model(s) + random.gauss(0, 3) return CACHE[s] def fitness(s): # 耗时越小适应度越高, 进化搜索按适应度最大化来排 return -real_measure(s) def crossover(a, b): # 均匀交叉: 每个维度随机取自一个父代 return tuple(a[i] if random.random() < 0.5 else b[i] for i in range(3)) def mutate(s): # 变异: 以一定概率扰动每个维度 t, v, st = s if random.random() < MUT_RATE: t = random.choice(TILE) if random.random() < MUT_RATE: v = random.choice(VEC) if random.random() < MUT_RATE: st = random.choice(STAGE) return (t, v, st) def tournament_select(pop, k=3): # 锦标赛选择: 随机挑 k 个, 取适应度最高者 best = None for _ in range(k): ind = random.choice(pop) if best is None or fitness(ind) > fitness(best): best = ind return best def evolve(): # 初始化种群 pop = [random_sample() for _ in range(POP_SIZE)] for gen in range(GENERATIONS): pop.sort(key=fitness, reverse=True) elites = pop[:2] # 精英保留 next_gen = elites[:] while len(next_gen) < POP_SIZE: p1 = tournament_select(pop) p2 = tournament_select(pop) child = mutate(crossover(p1, p2)) next_gen.append(child) pop = next_gen best = max(pop, key=fitness) print('gen', gen, 'best cost %.1f' % -fitness(best), best) return max(pop, key=fitness) if __name__ == '__main__': best = evolve() print('best schedule:', best, 'cost %.1f' % -fitness(best))
CACHE = {}测量缓存池,模拟 Ansor 中“同一调度方案只实测一次”的工程约束,避免重复测量浪费预算pop = [random_sample() for _ in range(POP_SIZE)]初始化种群,从搜索空间均匀随机采样 12 个候选调度pop.sort(key=fitness, reverse=True)按适应度从高到低排序,让优秀方案排在最前elites = pop[:2]精英保留:最优秀的 2 个方案原样进入下一代,防止好基因丢失child = mutate(crossover(p1, p2))先交叉再变异,把两个父代的调度特征重组并引入随机扰动if s not in CACHE:实测前先查缓存,命中则直接返回已测结果,节省昂贵测量开销
常见误区
误区进化搜索本质上是随机瞎试,和随机搜索没区别,试的次数多了总能蒙到一个好方案。
正解随机搜索每轮彼此独立,没有任何记忆;进化搜索每一代都根据适应度筛选父代,优秀方案的特征通过交叉、变异逐代累积,越试越有方向。就像安师傅不是闭眼做菜,而是把上一轮的好火候、好刀工“遗传”给下一代,这是有方向的搜索。用一句话区分:随机搜索是蒙着眼扔飞镖,进化搜索是每次看上一镖落点再调整姿势。
为何常见:变异和随机采样都带有随机性,初学者容易把“用了随机数”和“纯随机搜索”混为一谈,忽略了选择压力这个核心差异。
误区代价模型预测肯定有误差,既然要追求最优性能,那每个候选调度都应该实际运行一遍,以实测为准。
正解实测一次可能需要几秒甚至几分钟,而搜索空间有 \(10^{20}\) 个候选,全测等于没测。Ansor 的做法是:先用代价模型给全部候选打分排序,只把排名前 k 的少数个体送上真机,实测数据再反哺模型,让预测越来越准。这就像主厨不可能让每道菜都经食客全流程品鉴,先用经验筛出有希望的那几道,品鉴才有意义。
为何常见:直觉上“实测=真理”没有错,但忽略了实测的时间成本恰恰是自动调优要解决的核心瓶颈,预算有限才是工程常态。
误区既然一直在进化,多跑几代肯定能找到理论最快的那个调度方案。
正解进化搜索是启发式算法,在有限预算内逼近最优而非保证全局最优。它需要在“探索新区域”和“利用已发现的好区域”之间做折中,可能陷入局部最优。Ansor 的收敛判据和预算控制,本质上是接受“够好”的工程现实——就像安师傅 50 锅预算用完就收工,端出的是“试过的所有菜里最好的一盘”,而不是“全宇宙理论最优的一盘”。
为何常见:受生物进化“适者生存”叙事的影响,人们误以为自然选择也有完美终点,忽略了进化本身是受环境资源约束的不断适应过程。
误区多变异就能多尝试新方案,变异率设得越高,搜索覆盖越广,效率自然越高。
正解变异率过高会破坏已被验证的优秀方案,种群退化成随机游走;过低则种群固化、容易早熟陷入局部最优。工程中一般把变异率设在 0.1~0.3,并配合精英保留维持稳定性。这就像做菜:每次多撒一撮盐是新尝试,但把整罐盐倒进去,连前几代积累的好味道也一并毁掉了。进化搜索要的是“在稳定中探索”,而不是“为了探索而自毁”。
为何常见:把“探索”理解成越多越好,忽略了探索与利用之间的平衡——有效的搜索既要敢尝试新路,也要守住已验证的好成果。
面试题
Q1. 进化搜索中“适应度函数”的作用是?
- A.衡量调度方案的性能优劣← 正确答案
- B.控制在变异时的随机概率
- C.计算搜索空间的大小范围
- D.将调度方案编码为二进制
答案:A。适应度函数是进化搜索的“评分标准”,用来衡量每个调度方案的好坏,数值越高代表性能越好。选项 B 是变异率做的事,C 是搜索空间分析的范畴,D 是编码表示的工作,均与适应度函数无关。好比安师傅试吃后的打分,决定了哪道菜值得“繁殖后代”。
Q2. Ansor 采用代价模型预筛候选的主要作用是?
- A.完全替代真实测量的结果
- B.减少昂贵耗时的实测次数← 正确答案
- C.增大搜索空间的覆盖范围
- D.消除进化过程的所有随机性
答案:B。代价模型用预测分给所有候选排序,只挑排名前 k 的个体去实测,从而把昂贵的真机测量次数压到最小。它不能完全替代实测(预测有误差),也不会改变搜索空间或消除随机性。就像安师傅先用经验筛菜,而不是让每道菜都下锅。
Q3. 交叉操作在进化搜索中的含义是?
- A.随机丢弃种群里的劣质个体
- B.组合两个父代的调度方案片段← 正确答案
- C.对单个方案做小幅随机参数扰动
- D.让最优个体直接原样进入下一代
答案:B。交叉是把两个父代方案的调度特征(如分块大小、向量宽度)重新组合生成子代,相当于把第 3 道菜的火候和第 11 道菜的刀工拼到一盘。A 是淘汰策略,C 是变异,D 是精英保留,三者都不是交叉。
Q4. 精英保留策略的核心思想是?
- A.将最优个体无修改地传给下一代← 正确答案
- B.只用精英个体参与交叉繁殖
- C.淘汰所有新生成的子代个体
- D.实时增大种群的个体数量
答案:A。精英保留保证每代最优的个体原封不动进入下一代,防止交叉变异等操作把好方案破坏掉。它只是“保底”,不是让精英独占繁殖权,更不是淘汰子代或扩大种群。就像安师傅每锅必定留一盘上一轮最受欢迎的菜,不会因为做新菜就把老招牌丢了。
Q5. 为什么网格搜索在 Ansor 的调度空间中不可行?
- A.调度空间中的方案都是相同的
- B.组合数量随参数增加呈指数爆炸← 正确答案
- C.性能测量结果没有任何误差
- D.编译器根本不支持批量测试
答案:B。调度空间的大小是各调度原语可选值的连乘,随参数种类增加呈指数爆炸,常见规模超过 \(10^{20}\)。网格搜索要枚举全部组合,时间上完全不可行。A、C、D 都与事实相反:方案各不相同、测量有噪声、编译器完全支持批量测试。
Q6. 关于变异率设置的正确理解是?
- A.变异率设置得越高搜索效果越好
- B.变异率必须设为零才能保持稳定
- C.过高会破坏优秀解需要折中设置← 正确答案
- D.变异率与最终搜索效率完全无关
答案:C。变异率过高会破坏已被验证的优秀方案,种群退化为随机游走;过低则种群固化、易早熟。工程上通常取 0.1~0.3 的折中值,配合精英保留维持稳定,所以 C 正确。A 忽略了过度变异的破坏性,B 走向另一个极端,D 否认了变异的作用。
Q7. 进化搜索中的“个体”在 Ansor 中指?
- A.一套具体的算子调度方案← 正确答案
- B.一个完整的神经网络模型
- C.一台用于测试的物理机器
- D.一组用于训练的数据样本
答案:A。在 Ansor 的进化搜索里,一个个体就是一份具体的调度方案,包含分块大小、向量宽度等一系列参数取值。种群就是这些调度方案的集合,而不是神经网络模型、测试机器或数据集。好比一道具体的菜,而不是整个餐厅。
Q8. 以下属于进化搜索核心操作的有?
- A.交叉重组两个父代的调度方案← 正确答案
- B.变异扰动单个方案的调度参数← 正确答案
- C.用梯度下降更新整个种群的权重
- D.锦标赛选择出优质的父代个体← 正确答案
答案:ABD。进化搜索的三大核心操作是选择(锦标赛选择)、交叉(重组父代方案)、变异(扰动参数),所以 A、B、D 正确。梯度下降是训练神经网络代价模型时可能用到的优化器,不是进化搜索本身的算子。
Q9. 关于代价模型与实际测量的关系,正确的是?
- A.模型预测用于排序并筛选候测个体← 正确答案
- B.实测数据反馈用于更新模型参数← 正确答案
- C.实测结果完全不存在任何测量噪声
- D.两者结合可压缩高开销的实测次数← 正确答案
答案:ABD。Ansor 用代价模型预测性能并排序,只选前 k 名实测(A),实测数据反哺模型让预测更准(B),两者结合把实测次数从天文数字压缩到几千(D)。实测必然受硬件波动、频率变化等影响而有噪声,C 错误。这就像安师傅的经验筛选 + 食客真实反馈互相促进。
Q10. 哪些手段可以平衡进化搜索的“探索”与“利用”?
- A.设置适当的变异率避免种群固化← 正确答案
- B.精英保留确保已发现优解不丢失← 正确答案
- C.锦标赛选择给优秀个体更多繁殖机会← 正确答案
- D.每代全部重新随机生成种群个体
答案:ABC。适当变异率让种群保持探索新区域的能力(A),精英保留守住已发现的优解、保证利用(B),锦标赛选择把繁殖机会倾斜给优秀个体、强化利用(C)。D 每代全部重新随机生成等于抛弃了所有进化积累,完全退化为随机搜索,谈不上平衡。