18910140161

[TVM学习材料]使用计划模板和AutoTVM优化运算符

顺晟科技

2023-02-23 10:20:56

195

完成TVM中文文档,访问TVM中文站。

作者:郑,克里斯霍格

本教程将展示如何用TVM张量表达式(te)语言编写一个时间表模板,并通过AutoTVM搜索模板以找到最佳时间表。这种自动优化张量计算的过程称为自动调谐。

本课程基于之前的TE写矩阵乘法课程。

自动调谐包括两个步骤:

步骤1:定义搜索空间。

第二步:运行搜索算法来探索这个空间。

通过本教程,您可以了解如何在TVM中执行这两个步骤。以矩阵乘法为例说明了整个工作流程。

评论

请注意,本教程不能在Windows或最新版本的macOS上运行。要运行,请将本教程的主体放在if name=='__main__ '代码块中。安装依赖项要在tvm中使用autotvm包,您需要安装一些额外的依赖项。

pip 3 install-user Psutil XG Boost Cloud Pickle为了让TVM在调优过程中运行更快,建议将Cython作为TVM的FFI。在TVM的根目录下,执行:

pip 3 install-user cythonsudo make cython 3现在让我们看看如何用Python代码实现它。首先导入所需的包:

import logging import sys import numpy as NP import TVM from TVM import TE import TVM。测试#模块调用autotvm `从tvm导入autotvmTE基本矩阵乘法回忆一下用TE做矩阵乘法的基本实现,下面做一些修改。将矩阵乘法放入Python函数定义中。为简单起见,重点关注分割的优化,并将重新排序的块大小设置为一个固定值。

def matmul_basic(N,L,M,dtype): A=te.placeholder((N,L),name='A 'dtype=dtype)B=te . placeholder((L,M),name='B 'dtype=dtype)k=te . reduce _ axis((0,L),name='k') C=te.compute((N,M),lambda i,j: te.sum(A[i,k] * B[k,j],axis=k),Name=' c') s=te .create _ schedule(c . op)# scheduley,x=s [c]。op.axisk=s [c]。op.reduce _ axis [0] yo,yi=s [c]。分裂(y,8) xo。8) s [c]。Reorder (yo,XO,k,yi,xi)返回,[a,b,c]与AutoTVM的矩阵乘法前面的调度代码使用常量“8”作为周期分割因子,但它可能不是最优的。因为最佳周期分段因子取决于真实的硬件环境和输入形状。

如果您希望调度代码可移植到更广泛的输入形状和目标硬件上,最好定义一组候选值,并根据目标硬件上的评估结果选择最佳值。

Autotvm可以为该值定义一个可调参数或“旋钮”。

基本矩阵乘法模板以下示例将演示如何为分割调度操作的块大小创建可调参数集。

#马特穆尔V1:列出候选值@ auto tvm . template(' tutorial/马特穆尔_ v1') # 1。使用decorator defmatmul _ v1 (n,l,m,dtype): a=te。placeholder ((n,L),name=' a 'dtype=dtype)B=te . placeholder((L,M),name='B 'dtype=dtype)K=te . reduce _ axis((0,L),name='k') C=te.compute((N,M),lambda i,j: te.sum(A[i,K] * b [k,j],axis=k),name=' C ')s=te . create _ schedule(C . op)# schedule,X=s [c]。op.axis k=s [c]。op.reduce _ axis [0] # 2。获取配置对象cfg=autotvm.get_config() # 3。定义搜索空间CFG。Define _ Knob ('tile _ y '[1,2,4])。16]) CFG。Define _ Knob ('tile _ x '[1,2,4,8,16]) # 4。Schedule yo,yi=s [c]。Split (y,CFG ['tile _ y']。val) XO,xi=CFG ['tile _ x']。val) s [c]。Reorder (yo,XO,k,yi,)返回,[a,b,c]对前面的调度代码进行四次修改,然后得到一个可调的“模板”。逐一解释这些变化:

使用装饰器将该函数标记为简单模板。获取config对象:把cfg看作这个函数的一个参数,但是我们用另一种方式获取它。cfg参数使该功能不再是一个明确的计划。通过向该函数传递不同的配置,可以得到不同的调度。这种使用配置对象的功能称为模板。为了细化模板函数,可以在单个函数中定义参数搜索空间:

用一组值定义搜索空间。将cfg转换成一个ConfigSpace对象,收集这个函数中所有可调旋钮,然后从中构建一个搜索空间。根据空间中的实体。将cfg转换为ConfigEntity对象,当它转换为ConfigEntity时,所有空间定义都将被忽略。

API(即cfg.define_XXXXX(.)),但它存储了可调旋钮的所有确定值,并根据这些值制定计划。在自动调优的过程中,首先用ConfigSpace对象调用模板构建搜索空间,然后在构建的空间中用不同的ConfigEntity调用模板得到不同的调度。最后,我们将评估不同调度生成的代码,然后选择最佳调度。

定义两个可调旋钮。第一个是tile_y,有五个可能的值。第二个是tile_x,可能值和前者一样。这两个旋钮是独立的,因此它们以25=5x5的大小跨越搜索空间。配置旋钮被传递到分割调度操作,然后可以根据之前在cfg中定义的5x5确定值进行调度。带高级参数API的矩阵乘法模板前面的模板手动列出了konb的所有可能值。它是用于定义空间和显示要搜索的参数空间的最底层API。建议使用另一套更高级的API,可以更简单智能的定义搜索空间。

以下示例使用ConfigSpace.define_split来定义拆分旋钮。它列出了所有可能的方式分裂轴和建设空间。

同时用ConfigSpace.define_reorder对旋钮进行重新排序,用ConfigSpace.define_annotate对扩展、向量化、线程绑定进行注释。当高级API不能满足您的需求时,您可以退而使用底层API。

@ autot VM . template(' tutorial/mat mul ')def mat mul(N,L,M,dtype): A=te.placeholder((N,L),name='A 'dtype=dtype)B=te . placeholder((L,M),name='B 'dtype=dtype)k=te . reduce _ axis((0,L),name='k') C=te.compute((N,M),lambda i,j: te.sum(A[i,k] * B[k,j],axis=k)op.axis k=s [c]。op.reduce _ axis [0] # # # #开始定义空间# # # CFG=autotvm。Get _ config () CFG。Define _ split ('tile _ y 'y,Num _ outputs=2) CFG。define _ split ('tile _ x 'x,num _ outputs=2) # # # #结束定义空格# # # #调度yo,yi=CFG ['tile _ y']。应用(s,c Xi=CFG ['tile _ x']。应用(s,c,x) s [c]。Reorder (yo,XO,k,yi,)returns,[a,b,c]更多关于cfg.define_split的解释在这个模板里,cfg。定义_拆分。

Num_outputs=2)枚举所有可能的组合(用Y的长度将Y轴分为两个轴)。例如,如果y的长度为32,并且您希望从

用因子32把它分成两个轴,那么(外轴长,内轴长)有六个可能的值,分别是(32,1),(16,2),(8,4),(4,

8),(2,16)或(1,32)。这也是tile _ y的六个可能值。

在调度期间,cfg['tile_y']是一个SplitEntity对象。我们将外轴和内轴的长度存储在

在cfg['tile_y']中。size(包含两个元素的元组)。这个模板使用yo,yi=cfg['tile_y']。应用(s,

c,y)来应用它。实际上相当于yo,yi=s [c]。split (y,CFG ['tile _ y']。尺寸[1])或哟,

易=s[C]。split(y,nparts=cfg['tile_y']。大小[0]).

cfg.apply API的优势在于它使得多级拆分(也就是num_outputs=3时)变得更加容易。

步骤2:使用AutoTVM优化矩阵乘法。步骤1中编写的矩阵乘法模板可以参数化分割调度中的块大小。通过第一步,你可以搜索这个参数空间。下一步是选择一个调谐器来指导如何探索太空。

TVM的自动调谐器tuner的任务可以用下面的伪代码来描述:

CT=0而CT max _ number _ of _ Trials:建议一批配置在真实硬件上测量这批配置并得到结果CT=batch _ size调优器可以采用不同的策略来规划下一批配置,包括:

Tvm.autotvm.tuner.RandomTuner:枚举空间Tvm。自动虚拟机。调谐器。GridserchTuner:枚举空间tvm.autotvm.tuner.GATuner:用遗传算法搜索空间tvm.autotvm.tuner.XGBTuner:用基于模型的方法训练一个XGBoost模型进行预测。

并根据预测值选择下一批配置。可以根据空间大小、时间预算和其他因素来选择调谐器。例如,如果您的空间非常小(小于1000),网格搜索调谐器或随机调谐器就足够了。如果你的空间在10 ^ 9的水平(CUDA GPU上conv2d算子的空间大小),XGBoostTuner可以更有效的探索,找到更好的配置。

开始调整下面的例子继续矩阵乘法。首先创建一个调优任务,然后检查初始搜索空间。下面这个例子是一个512x512的矩阵乘法,空间大小是10x10=100。请注意,任务和搜索空间独立于所选的调谐器。

n,L,M=512,512,512任务=自动虚拟机。任务。Create ('tutorial/matmul 'args=(n,L,M,' float 32 '),target=' llvm') print (task。config _ space)输出结果:

Configspace (len=100,space _ map=0 file _ y:split(policy=factors,product=512,num _ outputs=2) len=101 tile _ x:split(policy=factors,product=512,num _ outputs=2)len。由于我们的空间非常小,随机调谐器就可以了。

本教程只做10个实验来演示。其实你可以根据自己的时间预算多做实验。优化结果将记录在日志文件中。该文件可用于选择稍后找到的调谐器的最佳配置。

# Record config(以便将调整日志打印到屏幕上)logging.getlogger ('autotvm ')。set level(logging . debug)logging . get logger(' auto tvm ')。addhandler(logging . stream handler(sys . stdout))评估配置有两个步骤:构建和运行。默认情况下,所有CPU内核都用来编译程序。然后依次评价。为了减少方差,将五次评估的结果进行平均。

measure _ option=autot VM . measure _ option(builder=' local 'runner=autot VM . local runner(number=5))#使用RandomTuner开始调优。记录到matmul.log' file #可以由XGBTuner替换。Tuner=autot VM . Tuner . Random Tuner(task)Tuner . tune(n _ trial=10,measure _ option=measure _ option,callbacks=[autot VM . callback . log _ to _)。

等待设备.设备可用成功获取用于测量的设备!第一名:1 GFLOPS: 8.48/8.48结果:测量结果(costs=(0.0316434228,)error_no=MeasureErrorNo .NO_ERROR,all_cost=0.638512134552002,timestamp=1657225928.6342561)[(' tile _ y '[-1,1],(' tile_x '[-1,256])],None,80No:2 GFLOPS:2.30/8.48 result:measure result(costs=(0.111165478966,)error_noNO错误NO_ERROR,all_cost=2.6911513805389404,timestamp=1657225934.9635096[(' tile _ y '[-1,1],(' tile_x '[-1,4])],None,20No:5 GFLOPS:3.65/11.82 result:measure result(costs=(0.073561817,)error _ no=NONO_ERROR,all_cost=2.5179028511047363,timestamp=1657225938.961955[(' tile _ y '[-1,512],(' tile_x '[-1,4])],None,29 NO:7 GFLOPS:0.87/11.82 result:measure result(costs=(0.3093780240000000n调优完成后,可从日志文件中选择具有最佳评估性能的配置,并用相应参数来编译时间表。快速验证日程安排是否产生了正确的结果,可直接在autotvm.apply_history_best上下文中调用马特穆尔函数,它会用参数查询调度上下文,然后可用相同的参数获取最优配置。

# 从日志文件中应用历史最佳带自动tvm。apply _ history _ best(' mat mul。log’):带tvm。目标。target(' llvm '):s,arg_bufs=matmul(N,L,M,' float32') func=tvm.build(s,arg_bufs)#验证正确性a_np=np.random.uniform(size=(N,L))。astype(NP。float 32)b _ NP=NP。随机的。制服(尺寸=(长,米)).astype(NP。float 32)c _ NP=a _ NP。dot(b _ NP)c _ tvm=tvm。nd。空(c _ NP。形状)函数(tvm。nd。array(a _ NP),tvm.nd.array(b_np),c _ tvm)tvm。测试。assert _ all close(c _ NP,c_tvm.numpy(),rtol=1e-4)输出结果:

完成加载10条记录总结本教程展示了如何构建算子模板,使得TVM能够搜索参数空间,并选择优化的调度配置。为了更深入地了解其工作原理,推荐基于:参考:张量表达式入门tensorexpr_get_started教程中演示的调度操作,向调度添加新的搜索参数。接下来的章节将演示自动计划程序,它是TVM中一种优化常用算子的方法,同时无需用户提供自定义的模板。

随机推荐
我们已经准备好了,你呢?
2024我们与您携手共赢,为您的企业形象保驾护航