TVM:使用 Auto-scheduling 来优化算子

在本教程中,我们将展示 TVM 的 Auto-scheduling 功能如何在无需编写自定义模板的情况下找到最佳 schedule。

与基于模板的 AutoTVM 依赖手动模板定义搜索空间不同,auto-scheduler 不需要任何模板。 用户只需编写计算声明,无需任何调度命令或模板。 auto-scheduler 可以自动生成一个大的搜索空间,并在该空间中找到一个好的 schedule。

我们在本教程中同样使用矩阵乘法作为示例。

import osimport numpy as np
import tvm
from tvm import te, auto_scheduler

定义矩阵乘法

首先,我们定义一个带有偏置的矩阵乘法。 请注意,这使用了 TVM 张量表达式语言中可用的标准操作。 主要区别在于在函数定义的开始使用了 auto_sceduler 装饰器。 该函数应返回输入/输出张量列表。 从这些张量中,自动调度器可以获得整个计算图。

@auto_scheduler.register_workload  # Note the auto_scheduler decorator
def matmul_add(N, L, M, dtype):A = te.placeholder((N, L), name="A", dtype=dtype)B = te.placeholder((L, M), name="B", dtype=dtype)C = te.placeholder((N, M), name="C", dtype=dtype)k = te.reduce_axis((0, L), name="k")matmul = te.compute((N, M),lambda i, j: te.sum(A[i, k] * B[k, j], axis=k),name="matmul",attrs={"layout_free_placeholders": [B]},  # enable automatic layout transform for tensor B)out = te.compute((N, M), lambda i, j: matmul[i, j] + C[i, j], name="out")return [A, B, C, out]

创建搜索任务

定义函数后,我们现在可以创建供 auto_scheduler 搜索的任务。 我们指定此矩阵乘法的特定参数,在本例中为 1024x1024 大小的方阵的乘法。 然后我们创建一个搜索任务,其中 N=L=M=1024 ,数据类型为 ”float32”。

target = tvm.target.Target("llvm")
N = L = M = 1024
task = tvm.auto_scheduler.SearchTask(func=matmul_add, args=(N, L, M, "float32"), target=target)# Inspect the computational graph
print("Computational DAG:")
print(task.compute_dag)

注意:自定义 target 可以提高性能

为了让 TVM 充分利用特定硬件平台,您需要手动指定 CPU 功能。 例如: - 将下面的“llvm”替换为“llvm -mcpu=core-avx2”以启用 AVX2 - 将下面的“llvm”替换为“llvm -mcpu=skylake-avx512”以启用 AVX-512

此处输出:

Computational DAG:
A = PLACEHOLDER [1024, 1024]
B = PLACEHOLDER [1024, 1024]
matmul(i, j) += (A[i, k]*B[k, j])
C = PLACEHOLDER [1024, 1024]
out(i, j) = (matmul[i, j] + C[i, j])

为 Auto-Scheduler 设置参数

接下来,我们为自动调度程序设置参数。

  • num_measure_trials 是我们在搜索过程中可以使用的测量试验次数。 为了快速演示,我们在本教程中仅进行了 10 次试验。 在实践中,1000 是一个很好的搜索收敛值。 您可以根据您的时间预算进行更多试验。

  • 此外,我们使用 RecordToFile 将测量记录记录到文件 matmul.json 中。 测量记录可用于最佳查询历史记录、恢复搜索以及稍后进行更多分析。

  • 有关更多参数,请参阅 auto_scheduler.TuningOptions

log_file = "matmul.json"
tune_option = auto_scheduler.TuningOptions(num_measure_trials=10,measure_callbacks=[auto_scheduler.RecordToFile(log_file)],verbose=2,
)

运行搜索

现在我们准备好所有输入。 很简单,不是吗? 我们可以开始搜索并让自动调度程序发挥它的魔力。 经过一些测量试验后,我们可以从日志文件中加载最佳计划并应用它。

# Run auto-tuning (search)
task.tune(tune_option)
# Apply the best schedule
sch, args = task.apply_best(log_file)

检查优化过的 Schedule

我们可以在 auto-scheduling 后降低(lower)schedule 以查看 IR。 auto-schduling 程序正确执行优化,包括多级平铺、布局转换、并行化、矢量化、展开和算子融合。

print("Lowered TIR:")
print(tvm.lower(sch, args, simple_mode=True))

此处输出:

Lowered TIR:
primfn(A_1: handle, B_1: handle, C_1: handle, out_1: handle) -> ()attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}buffers = {out: Buffer(out_2: Pointer(float32), float32, [1024, 1024], []),A: Buffer(A_2: Pointer(float32), float32, [1024, 1024], []),C: Buffer(C_2: Pointer(float32), float32, [1024, 1024], []),B: Buffer(B_2: Pointer(float32), float32, [1024, 1024], [])}buffer_map = {A_1: A, B_1: B, C_1: C, out_1: out} {allocate(auto_scheduler_layout_transform: Pointer(global float32), float32, [1048576]), storage_scope = global {for (ax0.ax1.fused.ax2.fused: int32, 0, 128) "parallel" {for (ax4: int32, 0, 256) {for (ax6: int32, 0, 4) {for (ax7: int32, 0, 8) {auto_scheduler_layout_transform[((((ax0.ax1.fused.ax2.fused*8192) + (ax4*32)) + (ax6*8)) + ax7)] = (float32*)B_2[((((ax4*4096) + (ax6*1024)) + (ax0.ax1.fused.ax2.fused*8)) + ax7)]}}}}for (i.outer.outer.j.outer.outer.fused: int32, 0, 16384) "parallel" {allocate(matmul: Pointer(global float32x8), float32x8, [4]), storage_scope = global;for (i.outer.inner: int32, 0, 2) {matmul[ramp(0, 1, 8)] = broadcast(0f32, 8)matmul[ramp(8, 1, 8)] = broadcast(0f32, 8)matmul[ramp(16, 1, 8)] = broadcast(0f32, 8)matmul[ramp(24, 1, 8)] = broadcast(0f32, 8)for (k.outer: int32, 0, 256) {for (k.inner: int32, 0, 4) {matmul[ramp(0, 1, 8)] = ((float32x8*)matmul[ramp(0, 1, 8)] + (broadcast((float32*)A_2[((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (k.outer*4)) + k.inner)], 8)*(float32x8*)auto_scheduler_layout_transform[ramp((((floormod(i.outer.outer.j.outer.outer.fused, 128)*8192) + (k.outer*32)) + (k.inner*8)), 1, 8)]))matmul[ramp(8, 1, 8)] = ((float32x8*)matmul[ramp(8, 1, 8)] + (broadcast((float32*)A_2[(((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (k.outer*4)) + k.inner) + 1024)], 8)*(float32x8*)auto_scheduler_layout_transform[ramp((((floormod(i.outer.outer.j.outer.outer.fused, 128)*8192) + (k.outer*32)) + (k.inner*8)), 1, 8)]))matmul[ramp(16, 1, 8)] = ((float32x8*)matmul[ramp(16, 1, 8)] + (broadcast((float32*)A_2[(((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (k.outer*4)) + k.inner) + 2048)], 8)*(float32x8*)auto_scheduler_layout_transform[ramp((((floormod(i.outer.outer.j.outer.outer.fused, 128)*8192) + (k.outer*32)) + (k.inner*8)), 1, 8)]))matmul[ramp(24, 1, 8)] = ((float32x8*)matmul[ramp(24, 1, 8)] + (broadcast((float32*)A_2[(((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (k.outer*4)) + k.inner) + 3072)], 8)*(float32x8*)auto_scheduler_layout_transform[ramp((((floormod(i.outer.outer.j.outer.outer.fused, 128)*8192) + (k.outer*32)) + (k.inner*8)), 1, 8)]))}}for (i.inner: int32, 0, 4) {out_2[ramp(((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (i.inner*1024)) + (floormod(i.outer.outer.j.outer.outer.fused, 128)*8)), 1, 8)] = ((float32x8*)matmul[ramp((i.inner*8), 1, 8)] + (float32x8*)C_2[ramp(((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (i.inner*1024)) + (floormod(i.outer.outer.j.outer.outer.fused, 128)*8)), 1, 8)])}}}}
}

检查正确性并评估性能

我们构建二进制文件并检查其正确性和性能。

func = tvm.build(sch, args, target)
a_np = np.random.uniform(size=(N, L)).astype(np.float32)
b_np = np.random.uniform(size=(L, M)).astype(np.float32)
c_np = np.random.uniform(size=(N, M)).astype(np.float32)
out_np = a_np.dot(b_np) + c_npdev = tvm.cpu()
a_tvm = tvm.nd.array(a_np, device=dev)
b_tvm = tvm.nd.array(b_np, device=dev)
c_tvm = tvm.nd.array(c_np, device=dev)
out_tvm = tvm.nd.empty(out_np.shape, device=dev)
func(a_tvm, b_tvm, c_tvm, out_tvm)# Check results
np.testing.assert_allclose(out_np, out_tvm.numpy(), rtol=1e-3)# Evaluate execution time.
evaluator = func.time_evaluator(func.entry_name, dev, min_repeat_ms=500)
print("Execution time of this operator: %.3f ms"% (np.median(evaluator(a_tvm, b_tvm, c_tvm, out_tvm).results) * 1000)
)

此处输出:

Execution time of this operator: 45.418 ms

使用记录文件

在搜索过程中,所有的测量记录都被记录到记录文件“matmul.json”中。 测量记录可用于重新应用搜索结果、恢复搜索和执行其他分析。

这是一个示例,我们从文件加载最佳 schedule,并打印等效的 Python schedule API。 这可用于调试和学习 auto-scheduling 程序的行为。

print("Equivalent python schedule:")
print(task.print_best(log_file))

此处输出:

Equivalent python schedule:
matmul_i, matmul_j, matmul_k = tuple(matmul.op.axis) + tuple(matmul.op.reduce_axis)
out_i, out_j = tuple(out.op.axis) + tuple(out.op.reduce_axis)
matmul_i_o_i, matmul_i_i = s[matmul].split(matmul_i, factor=4)
matmul_i_o_o_i, matmul_i_o_i = s[matmul].split(matmul_i_o_i, factor=1)
matmul_i_o_o_o, matmul_i_o_o_i = s[matmul].split(matmul_i_o_o_i, factor=2)
matmul_j_o_i, matmul_j_i = s[matmul].split(matmul_j, factor=8)
matmul_j_o_o_i, matmul_j_o_i = s[matmul].split(matmul_j_o_i, factor=1)
matmul_j_o_o_o, matmul_j_o_o_i = s[matmul].split(matmul_j_o_o_i, factor=1)
matmul_k_o, matmul_k_i = s[matmul].split(matmul_k, factor=4)
s[matmul].reorder(matmul_i_o_o_o, matmul_j_o_o_o, matmul_i_o_o_i, matmul_j_o_o_i, matmul_k_o, matmul_i_o_i, matmul_j_o_i, matmul_k_i, matmul_i_i, matmul_j_i)
out_i_o_i, out_i_i = s[out].split(out_i, factor=4)
out_i_o_o, out_i_o_i = s[out].split(out_i_o_i, factor=2)
out_j_o_i, out_j_i = s[out].split(out_j, factor=8)
out_j_o_o, out_j_o_i = s[out].split(out_j_o_i, factor=1)
s[out].reorder(out_i_o_o, out_j_o_o, out_i_o_i, out_j_o_i, out_i_i, out_j_i)
s[matmul].compute_at(s[out], out_j_o_i)
out_i_o_o_j_o_o_fused = s[out].fuse(out_i_o_o, out_j_o_o)
s[out].parallel(out_i_o_o_j_o_o_fused)
s[matmul].pragma(matmul_i_o_o_o, "auto_unroll_max_step", 8)
s[matmul].pragma(matmul_i_o_o_o, "unroll_explicit", True)
s[matmul].vectorize(matmul_j_i)
s[out].vectorize(out_j_i)

一个更复杂的例子是恢复搜索。 在这种情况下,我们需要自己创建搜索策略和成本模型,并通过日志文件恢复搜索策略和成本模型的状态。 在下面的示例中,我们恢复状态并再进行 5 次试验。

def resume_search(task, log_file):print("Resume search:")cost_model = auto_scheduler.XGBModel()cost_model.update_from_file(log_file)search_policy = auto_scheduler.SketchPolicy(task, cost_model, init_search_callbacks=[auto_scheduler.PreloadMeasuredStates(log_file)])tune_option = auto_scheduler.TuningOptions(num_measure_trials=5, measure_callbacks=[auto_scheduler.RecordToFile(log_file)])task.tune(tune_option, search_policy=search_policy)resume_search(task, log_file)

此处输出:

Resume search:
/usr/local/lib/python3.6/dist-packages/xgboost/training.py:17: UserWarning: Old style callback is deprecated.  See: https://xgboost.readthedocs.io/en/latest/python/callbacks.htmlwarnings.warn(f'Old style callback is deprecated.  See: {link}', UserWarning)

总结

在本教程中,我们展示了如何使用 TVM Auto-Scheduler 自动优化矩阵乘法,而无需指定搜索模板。 它结束了一系列从张量表达式 (TE) 语言开始的示例,这些示例演示了 TVM 如何优化计算操作。

Ref:

https://tvm.apache.org/docs/tutorial/auto_scheduler_matmul_x86.html

TVM:使用 Auto-scheduling 来优化算子相关推荐

  1. TVM:使用 Schedule 模板和 AutoTVM 来优化算子

    TVM:使用 Schedule 模板和 AutoTVM 来优化算子 在本文中,我们将介绍如何使用 TVM 张量表达式(Tensor Expression,TE)语言编写 Schedule 模板,Aut ...

  2. TVM在ARM GPU上优化移动深度学习

    TVM在ARM GPU上优化移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与在台式机平台上所做的类似,在移动设备中使用GPU可以提高推理速度和能源效率.但是,大 ...

  3. 使用Auto TensorCore CodeGen优化Matmul

    使用Auto TensorCore CodeGen优化Matmul 本文将演示如何使用TVM Auto TensorCore CodeGen在Volta / Turing GPU上编写高性能matmu ...

  4. SystemML大规模机器学习,优化算子融合方案的研究

    SystemML大规模机器学习,优化算子融合方案的研究 摘要 许多大规模机器学习(ML)系统允许通过线性代数程序指定定制的ML算法,然后自动生成有效的执行计划.在这种情况下,优化的机会融合基本算子的熔 ...

  5. TVM: Deep Learning模型的优化编译器(强烈推荐, 附踩坑记录)

    本文作者是阿莱克西斯,原载于知乎,雷锋网(公众号:雷锋网)获得授权转载. (前排提醒,本文的人文内容部分稍稍带有艺术加工,请保持一定的幽默感进行阅读) 关注我最近想法的同学应该知道我最近都在把玩 TV ...

  6. TVM:使用Tensor Expression (TE)来处理算子

    TVM:使用Tensor Expression (TE)来处理算子 在本教程中,我们将聚焦于在 TVM 中使用张量表达式(TE)来定义张量计算和实现循环优化.TE用纯函数语言描述张量计算(即每个表达式 ...

  7. TVM:一种自动端到端优化的深度学习编译器

    TVM: An Automated End-to-End Optimizing Compiler for Deep Learning 提出背景 ​ 现有的 DL 框架依赖于计算图 IR 来实现优化,比 ...

  8. 【TVM帮助文档学习】使用张量表达式处理算子

    本文翻译自Working with Operators Using Tensor Expression - tvm 0.9.dev0 documentation 在本教程中,我们将把注意力转向TVM如 ...

  9. 在Relay中注册新TVM算子

    在Relay中注册新TVM算子 在本文件中,将介绍在Relay中注册新TVM算子所需的步骤.将以添加累积算子的PR为例.PR本身建立在另一个PR的基础上,该PR添加了一个累积和运算. 注册新算子需要几 ...

最新文章

  1. cisco 交换机镜像
  2. git版本分支和分支、分支和主分支切换
  3. H5+Mui文件配置 vue-resource基本使用方法
  4. 文件夹快速访问工具-Default Folder X
  5. 论搜索方法,低效的你简直在浪费生命(二)
  6. C++ 基类和派生类的virtual虚析构函数
  7. kubeadm安装k8s测试环境
  8. JSP-tomcat设置编码格式 配置utf-8(以防网页框以及网页显示的时候中文乱码)
  9. 度盘高速下载器,比超级VIP还要快,推荐给大家
  10. 【历史上的今天】1 月 5 日:正则表达式的发明人出生;英特尔发布酷睿系列;Microsoft Bob 诞生
  11. 2022年,你还要做开源软件么?
  12. Lattice LSTM
  13. 程序员接私单操作流程。
  14. 电脑钢琴模拟器(初学WINDOW库)
  15. 2021-12-19 《聪明的投资者》学习笔记-15.积极型投资者的股票选择--7个标准。低市盈率,价格低于净流动资产
  16. 原麦格纳亚洲区总裁布鲁诺兰伯特出任宝沃汽车全球总裁
  17. 方根法公式_方根的简易算法
  18. Python 的turtle模块讲座
  19. 13. 谈谈 Redis 的过期策略
  20. modern cmake的概念剖析

热门文章

  1. linux 当前用户执行定时任务
  2. (vue基础试炼_07)Vue实例生命周期函数
  3. mysql数据库的总结
  4. Springboot部署到Tomcat,可以不带项目名进行访问
  5. 捕获和抛出异常(Ctrl+Alt+T)
  6. mysql的每隔1分钟定时_简单易用,spring boot集成quartz,实现分布式定时任务
  7. Python random 模块 - Python零基础入门教程
  8. BugkuCTF-Crypto题杰斐逊
  9. matlab 打开软件报错,matlab程序错误,提示如下【弄了半夜还是不行】 - 数学 - 小木虫 - 学术 科研 互动社区...
  10. 关于计算机哪些学校好,计算机哪些学校好