【TVM教程】使用自定义调度规则(Sketch Rule)在 CPU 上自动调度稀疏矩阵乘法

Apache TVM是一个深度的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →https://tvm.hyper.ai/

作者:Chengfan Jia

本文介绍如何用 auto-scheduler 来调优 CPU 的稀疏矩阵乘法。

auto-scheduler 旨在自动探索给定计算声明的最佳性能调度。有时需要尝试一些特殊的操作,auto-scheduler 的默认调度规则(Sketch Rule)可能不能很好的支持这些操作,会导致性能不佳。auto-scheduler 目前允许用户提供一个 CustomSketch 来覆盖这些情况。

本教程使用稀疏矩阵乘法作为示例,演示如何实现自定义调度规则,并将其插入 auto-scheduler 的搜索策略。

注意,本教程无法在 Windows 或最新版本的 macOS 上运行。如需运行,请将本教程的主体放在 if __name__ == "__main__": 代码块中。

import os
import numpy as npimport tvm
import tvm.testing
from tvm import te, auto_scheduler, runtime, topi
from tvm.auto_scheduler import _ffi_api
from tvm.topi.utils import get_const_tuple
from tvm.topi.sparse.utils import random_bsr_matrix

定义计算

首先用几个 relu 和 bias 相加来定义一个稀疏 matmul 的计算,该函数返回输入/输出张量列表,auto-scheduler 可以从这些张量中得到整个计算图。

@auto_scheduler.register_workload
def sparse_dense(M, N, K, w_data_shape, w_indices_shape, w_indptr_shape, dtype):X = te.placeholder(shape=(M, K), dtype=dtype)W_data = te.placeholder(shape=w_data_shape, dtype=dtype)W_indices = te.placeholder(shape=w_indices_shape, dtype="int32")W_indptr = te.placeholder(shape=w_indptr_shape, dtype="int32")B = te.placeholder(shape=(M, N), dtype=dtype)out = topi.nn.sparse_dense(topi.nn.relu(X), W_data, W_indices, W_indptr)out = te.compute((M, N), lambda i, j: out[i, j] + B[i, j], name="BiasAdd")out = topi.nn.relu(out)return [X, W_data, W_indices, W_indptr, B, out]

稀疏工作负载(sparse workload)的特殊步骤

在调度调优期间,auto-scheduler 使用随机输入来测试生成的调度的性能。虽然不能直接使用随机数组作为稀疏运算的输入,但「indices」和「indptr」数组对于计算很有用。

为解决这个问题,将它们注册为特殊的 buffer,然后在测试程序时加载。更多详细信息,参阅 tvm.auto_scheduler.measure.py 。

# 定义稀疏计算的基本 shape
M = 128
K = 256
N = 512
BS_R = 16
BS_C = 1
density = 0.6# 用 numpy 生成测试数据
X_np = np.random.randn(M, K).astype("float32")
X_np = np.maximum(np.zeros((M, K), dtype="float32"), X_np) # Relu
W_sp_np = random_bsr_matrix(N, K, BS_R, BS_C, density=density, dtype="float32")
W_np = W_sp_np.todense()
Y_np = X_np @ W_np.T  # 处理矩阵乘法
B_np = np.random.randn(M, N).astype("float32")
Y_np = Y_np + B_np  # Bias add
Y_np = np.maximum(np.zeros((M, N), dtype="float32"), Y_np) # Relu

创建搜索任务

接下来创建一个搜索任务 M=N=K=512,dtype=「float32」。如果你的机器支持 avx 指令,你可以:

  • 将下面的「llvm」替换为「llvm -mcpu=core-avx2」来启用 AVX2
  • 将下面的「llvm」替换为「llvm -mcpu=skylake-avx512」来启用 AVX-512
target = tvm.target.Target("llvm")# 将稀疏数据注册到任务输入
prefix = "sparse_dense_bsr_%d_%d_%d_%d_%d_%d_" % (N,K,BS_R,BS_C,W_sp_np.indices.shape[0],W_sp_np.indptr.shape[0],
)
task = tvm.auto_scheduler.SearchTask(func=sparse_dense,args=(M, N, K, W_sp_np.data.shape, W_sp_np.indices.shape, W_sp_np.indptr.shape, "float32"),target=target,task_inputs={prefix + "W_data": runtime.ndarray.array(W_sp_np.data),prefix + "W_indices": runtime.ndarray.array(W_sp_np.indices),prefix + "W_indptr": runtime.ndarray.array(W_sp_np.indptr),},task_inputs_save_to_file=True,
)# 检查计算图
print("Computational DAG:")
print(task.compute_dag)

输出结果:

Computational DAG:
placeholder = PLACEHOLDER [33]
placeholder = PLACEHOLDER [4916, 16, 1]
placeholder = PLACEHOLDER [4916]
placeholder = PLACEHOLDER [128, 256]
compute(i0, i1) = max(placeholder[i0, i1], 0f)
compute(i, nb_j, j) += (placeholder[(placeholder[nb_j] + elem_idx), j, c]*compute[i, (placeholder[(placeholder[nb_j] + elem_idx)] + c)])
compute(m, n) = compute[m, floordiv(n, 16), floormod(n, 16)]
placeholder = PLACEHOLDER [128, 512]
BiasAdd(i, j) = (compute[i, j] + placeholder[i, j])
compute(i0, i1) = max(BiasAdd[i0, i1], 0f)

为稀疏密集算子(sparse dense op)编写自定义草图(sketch)

在调优之前,需要为稀疏密集操作定义 CustomSketchRule。

CustomSketchRule 由两部分组成:条件函数和应用函数。

  • 条件函数:描述应用此调度规则的时间。例如,通过匹配名称和标签将规则应用于稀疏操作。
  • 应用函数:描述生成初始草图的方式。可以用 auto-scheduler 提供的循环状态 API 来实现。
def meet_condition_func(search_policy, state, stage_id):state = auto_scheduler.loop_state.State(state, search_policy.search_task.compute_dag)if state.stages[stage_id].op.tag in ["sparse_dense_sp_rhs_bsrmm","sparse_dense_sp_rhs_bsrmm_block",]:return auto_scheduler.PreloadCustomSketchRule.APPLY_AND_SKIP_RESTelse:return auto_scheduler.PreloadCustomSketchRule.PASSdef apply_func(search_policy, state, stage_id):ret = []s0 = auto_scheduler.loop_state.State(state, search_policy.search_task.compute_dag)if s0.stages[stage_id].op.tag == "sparse_dense_sp_rhs_bsrmm_block":return [s0.state_object, stage_id - 1]sparse_dense = s0.stages[stage_id].opsparse_dense_block = s0.stages[stage_id - 1].opassert sparse_dense.tag == "sparse_dense_sp_rhs_bsrmm"assert sparse_dense_block.tag == "sparse_dense_sp_rhs_bsrmm_block"# 设置计算块的默认消费者consumer = sparse_dense# 若稀疏密集有单个元素消费者# 可以计算内联稀疏密集输出阶段consumers = _ffi_api.SearchPolicyUtilsGetConsumers(search_policy.search_task, s0.state_object, stage_id)if len(consumers) == 1:consumer_id = int(consumers.items()[0][0])if _ffi_api.SearchPolicyUtilsIsElementwiseMatch(search_policy.search_task, s0.state_object, stage_id, consumer_id):consumer = s0.stages[consumer_id].ops0.compute_inline(sparse_dense)i, nb_j, j, row_offset, c = s0[sparse_dense_block].itersm, n = s0[consumer].itersi0, i1, i2 = s0.split(sparse_dense_block, i, [None, None])m0, m1 = s0.follow_split(consumer, m, len(s0.transform_steps) - 1, 1)j0, j1 = s0.split(sparse_dense_block, nb_j, [None])n0, n1 = s0.follow_split(consumer, n, len(s0.transform_steps) - 1, 1)s0.reorder(sparse_dense_block, [i0, j0, i1, j1, row_offset, i2, j, c])s0.reorder(consumer, [m0, n0, m1, n1])s0.compute_at(sparse_dense_block, consumer, n0)ret.append([s0.state_object, stage_id - 2])return ret

接下来,为插入自定义草图的 auto-scheduler 设置参数。

  • num_measure_trials 是搜索过程中可以使用的测试次数(根据自己的时间预算调整这个参数),为快速演示,本教程只进行了 10 次试验。在实践中,推荐使用 1000 以得到收敛结果。
  • 此外,使用 RecordToFile 将测试记录转储到 sparse_dense.json 文件中,测试记录可用于查询历史最佳、恢复搜索以及以后进行更多分析。
  • 有关更多参数,参见 auto_scheduler.TuningOptions
  • 接下来创建一个 auto_scheduler.SketchPolicy 对象,并将自定义调度规则添加为 init_search_callbacks
log_file = "sparse_dense.json"
tune_option = auto_scheduler.TuningOptions(num_measure_trials=10,measure_callbacks=[auto_scheduler.RecordToFile(log_file)],verbose=2,
)search_policy = auto_scheduler.SketchPolicy(task,program_cost_model=auto_scheduler.XGBModel(),init_search_callbacks=[auto_scheduler.PreloadCustomSketchRule(meet_condition_func, apply_func, "SparseDense")],
)

运行搜索

现在已经准备好所有的输入。接下来开始搜索,经过一些测试后,可以从日志文件中加载最佳调度并应用。

def tune_and_evaluate(tune_option, search_policy):# 运行自动调优(搜索)task.tune(tune_option, search_policy)# 应用最佳 schedulesch, args = task.apply_best(log_file)# 自动调度后对 schedule 降级,来查看 IR。auto-scheduler 正确执行优化,包括多级平铺、布局# 转换、并行化、向量化、展开和算子融合。print("Lowered TIR:")print(tvm.lower(sch, args, simple_mode=True))# 检查正确性并评估性能# 构建二进制文件,并检查其正确性和性能。func = tvm.build(sch, args, target)dev = tvm.cpu()X_tvm = tvm.nd.array(X_np, device=dev)W_data_tvm = tvm.nd.array(W_sp_np.data, device=dev)W_indices_tvm = tvm.nd.array(W_sp_np.indices, device=dev)W_indptr_tvm = tvm.nd.array(W_sp_np.indptr, device=dev)B_tvm = tvm.nd.array(B_np, device=dev)Y_tvm = tvm.nd.empty(Y_np.shape, device=dev)# 检查结果tvm.testing.assert_allclose(Y_np, Y_tvm.numpy(), atol=1e-4, rtol=1e-4)# 评估执行时间。evaluator = func.time_evaluator(func.entry_name, dev, min_repeat_ms=500)print("Execution time of this operator: %.3f ms"% (np.median(evaluator(X_tvm, W_data_tvm, W_indices_tvm, W_indptr_tvm, B_tvm, Y_tvm).results)* 1000))# 注意: 我们不在服务器上运行调优,因为太花时间,
# 去掉下行注释自行运行
# tune_and_evaluate(tune_option, search_policy)

备注
调优结果示例

Lowered TIR:
primfn(placeholder_5: handle, placeholder_6: handle, placeholder_7: handle, placeholder_8: handle, placeholder_9: handle, compute_1: handle) -> ()
attr = {“global_symbol”: “main”, “tir.noalias”: True}
buffers = {placeholder_2: Buffer(placeholder_10: Pointer(float32), float32, [9831, 16, 1], []),
placeholder_4: Buffer(placeholder_11: Pointer(int32), int32, [33], []),
placeholder_3: Buffer(placeholder_12: Pointer(float32), float32, [512, 512], []),
compute: Buffer(compute_2: Pointer(float32), float32, [512, 512], []),
placeholder_1: Buffer(placeholder_13: Pointer(float32), float32, [512, 512], []),
placeholder: Buffer(placeholder_14: Pointer(int32), int32, [9831], [])}
buffer_map = {placeholder_7: placeholder, placeholder_9: placeholder_1, placeholder_6: placeholder_2, compute_1: compute, placeholder_5: placeholder_3, placeholder_8: placeholder_4} {
for (i0.outer.i1.outer.fused: int32, 0, 1024) “parallel” {
attr [compute_3: Pointer(float32)] “storage_scope” = “global”;
allocate(compute_3, float32, [256]) {
for (nb_j.inner: int32, 0, 2) {
for (i.inner.init: int32, 0, 8) {
for (j.init: int32, 0, 16) {
compute_3[(((i.inner.init32) + (nb_j.inner16)) + j.init)] = 0f32
}
}
for (elem_idx: int32, 0, ((int32*)placeholder_11[(((floormod(i0.outer.i1.outer.fused, 16)2) + nb_j.inner) + 1)] - (int32)placeholder_11[((floormod(i0.outer.i1.outer.fused, 16)2) + nb_j.inner)])) {
for (i.inner: int32, 0, 8) {
for (j: int32, 0, 16) {
compute_3[(((i.inner
32) + (nb_j.inner16)) + j)] = ((float32)compute_3[(((i.inner32) + (nb_j.inner16)) + j)] + ((float32*)placeholder_10[((((int32*)placeholder_11[((floormod(i0.outer.i1.outer.fused, 16)2) + nb_j.inner)]16) + (elem_idx16)) + j)]max((float32)placeholder_12[(((floordiv(i0.outer.i1.outer.fused, 16)4096) + (i.inner512)) + (int32)placeholder_14[((int32*)placeholder_11[((floormod(i0.outer.i1.outer.fused, 16)2) + nb_j.inner)] + elem_idx)])], 0f32)))
}
}
}
}
for (i0.inner: int32, 0, 8) {
compute_2[ramp((((floordiv(i0.outer.i1.outer.fused, 16)4096) + (i0.inner512)) + (floormod(i0.outer.i1.outer.fused, 16)32)), 1, 32)] = max(((float32x32)compute_3[ramp((i0.inner
32), 1, 32)] + (float32x32*)placeholder_13[ramp((((floordiv(i0.outer.i1.outer.fused, 16)4096) + (i0.inner512)) + (floormod(i0.outer.i1.outer.fused, 16)*32)), 1, 32)]), broadcast(0f32, 32))
}
}
}
}

下载 Python 源代码:tune_sparse_x86.py
下载 Jupyter Notebook:tune_sparse_x86.ipynb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/35850.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机开发资源分析的实战——以STM32F103C8T6为例子的单片机资源分析

目录 第一点:为什么叫STM32F103C8T6 从资源手册拿到我们的对STM32F103C8T6的资源描述 第二件事情,关心我们的GPIO引脚输出 第三件事情:去找对应外设的说明部分 前言 本文章隶属于项目: Charliechen114514/BetterATK: This is…

《基于Spring Boot+Vue的智慧养老系统的设计与实现》开题报告

个人主页:@大数据蟒行探索者 一、研究背景及国内外研究现状 1.研究背景 根据1982年老龄问题世界大会联合国制定的标准,如果一个国家中超过65岁的老人占全国总人口的7%以上,或者超过60岁的老人占全国总人口的10%以上,那么这个国家将被定义为“老龄化社会”[1]。 随着国…

微软OneNote无法同步解决方案

目录 前言原因UWP特性 解决方案C***h注册表 参考链接 前言 假设有多台Windows电脑,最方便且免费的多设备笔记同步方案就是微软自家的OneNote,使用OneDrive自带的5G云存储。 但是在国内大陆的OneNote,经常会出现无法同步、同步失败&#xff1…

硬件设计抽象级别详解:门级、RTL级、行为级与HLS

硬件设计抽象级别详解:门级、RTL级、行为级与HLS 引言 在数字系统设计领域,硬件描述语言(HDL)提供了多种抽象级别来描述电路功能和结构。从最底层的门级描述到高层的行为级描述,每一种抽象级别都有其特定的用途和优势。理解这些不同级别以及…

WPF程序使用AutoUpdate实现自动更新

AutoUpdate.NET使用 一、AutoUpdater.NET 简介 AutoUpdater.NET 是一个开源库,支持从各种源(如GitHub、FTP、HTTP服务器等)下载并安装更新。它提供了灵活的配置选项,允许开发者根据需求定制更新检查逻辑和用户体验。 二、安装 …

Qwen2-Audio:通义千问音频大模型技术解读

引言:从llm到mlm(audio) 大型语言模型(LLM)的发展日新月异,它们在文本理解、生成、推理等方面展现出惊人的能力。然而,交互模态不仅仅依赖于文字,语音、语调、环境音等听觉信息同样承载着丰富的内容。阿里巴巴通义千问团队,推出了 Qwen-Audio 系列模型,这里我们一起…

问题 | ACOS(X) 与 ACOSD(X)的区别

github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 [TOC](ACOS(X) 与 ACOSD(X)的区别) ACOSD(X) 是反余弦函数,结果以角度形式表示。ACOS(X) 用于计算 X 中每个元素的反余弦值。当 X 为复数时,结…

两款软件助力图片视频去水印及图像编辑

今天给大家分享两款呼声很高的软件,它们都能处理图片和视频去水印相关的问题。其中一款软件在去水印的同时,图像编辑功能也十分出色;另一款软件专注于图片和视频去水印,去除效果好且支持批量处理。下面就来详细了解一下。 Remover…

Hessian矩阵详解与应用

前言 本文隶属于专栏《机器学习数学通关指南》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见《机器学习数学通关指南》 ima 知识库 知识库广场搜索&#…

【软件系统架构】单体架构

一、引言 在软件开发的漫长历程中,架构的选择一直是至关重要的决策。单体架构作为一种经典的架构模式,曾经在许多项目中发挥着不可替代的作用。虽然如今微服务等架构逐渐流行,但理解单体架构对于深入掌握软件架构体系仍然有着重要意义。 二、…

[C++初阶] :从C到C++

目录 C发展史,C语言的特性C新增关键字namespace关键字C语言的命名缺陷(重定义现象)域与指定访问操作符 “::”命名空间域详解namespace std C的输入与输出函数重载什么是重载,重载的几种常见形态重载的作用注意不构成重载的情况 缺省参数1.全…

[快乐学坊management_1] With Cursor | Mysql设计 | 服务接口设计与开发

目录 数据库设计流程 三张表 测试 接口设计 部门管理接口文档 1. 查询所有部门 2. 新增部门 ⭕3. 根据ID查询部门 4. 修改部门 5. 删除部门 (部门分页条件查询) 错误响应示例 接口设计规范 服务端开发 接口开发 数据库设计流程 01 明确业…

实用插件推荐 -------- 一个可以将任意语言(python、C/C++、go、java等)的程序转换为汇编语言的小插件

链接为: Compiler Explorer 界面: 参考自:如何获取虚函数表及内存分析_com的虚函数表怎么寻找-CSDN博客

vue学习八

十七 组件通信方式 1 props 父传子 //父组件 <script setup>//book来源省略import Subview1 from ./Subview1.vue;function updatebook(updatetimes){book.value.updatetimes updatetimes} </script> <template><Subview1 :book"book" :upd…

51单片机的寻址方式(完整)

目录 一、立即数寻址 二、直接寻址 三、寄存器寻址 四、寄存器间接寻址 五、变址寻址 六、位寻址 七、指令寻址 &#xff08;一&#xff09;绝对寻址 &#xff08;二&#xff09;相对寻址 在 51 单片机中&#xff0c;寻址方式是指在执行指令时&#xff0c;CPU 寻找操作…

每日一题:动态规划

如题&#xff08;基础题&#xff09;&#xff1a; 经典的爬楼梯问题&#xff0c;先从递归想起&#xff1b; class Solution { public:int climbStairs(int n) {if(n1)return 1;if(n2)return 2;return climbStairs(n-1)climbStairs(n-2);} }; 之后可以想办法&#xff08;如哈希…

【论文阅读】FairCLIP - 医疗视觉语言学习中的公平性提升

FairCLIP - 医疗视觉语言学习中的公平性提升 1.研究背景与动机2.核心贡献3.方法论细节4.实验结果与洞见5.总结 FairCLIP: Harnessing Fairness in Vision-Language Learning FairCLIP - 医疗视觉语言学习中的公平性提升 Accepted by CVPR2024 github:链接 1.研究背景与动机…

Linux 入门:权限的认识和学习

目录 一.shell命令以及运行原理 二.Linux权限的概念 1.Linux下两种用户 cannot open directory .: Permission denied 问题 2.Linux权限管理 1).是什么 2).为什么&#xff08;权限角色目标权限属性&#xff09; 3).文件访问者的分类&#xff08;角色&#xff09; 4).文…

大语言模型的压缩技术

尽管人们对越来越大的语言模型一直很感兴趣&#xff0c;但MistralAI 向我们表明&#xff0c;规模只是相对而言的&#xff0c;而对边缘计算日益增长的兴趣促使我们使用小型语言获得不错的结果。压缩技术提供了一种替代方法。在本文中&#xff0c;我将解释这些技术&#xff0c;并…

Java高频面试之集合-14

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;为什么 HashMap 的容量是 2 的倍数呢&#xff1f; HashMap的容量被设计为2的幂次&#xff0c;主要基于以下原因&#xff…