惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
S
Securelist
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
量子位
博客园 - Franky
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
T
Troy Hunt's Blog
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
小众软件
小众软件
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
P
Privacy International News Feed
Last Week in AI
Last Week in AI
Microsoft Security Blog
Microsoft Security Blog
T
Tailwind CSS Blog
博客园_首页
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
罗磊的独立博客
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
T
Tenable Blog

Пусть этот камень будет более крепким, чем человек

【琐记】烟火与尘埃 【Triton】Triton实现矩阵乘 【LLM推理加速】FlashAttention 【LLM推理加速】PagedAttention 【LLM推理加速】Online Softmax LLM基础知识【1】 Transformer模型 【AI编译】LayerGroup Tiling Tile的疑惑和思考 【AI编译】深度优先的Tile调度,万事大吉? 【AI编译】多级流水线Tile调度策略 【CUDA C++】GPU内存使用【3】 【AI编译】Cache缓存地址映射 【CUDA C++】GPU存储【2】 【CUDA C++】GPU基本介绍【1】 【00】0序章-不受欢迎的来客 【转载】我来了——持续低熵 【Halide】调度优化【2】 【感想】写作进度报告5 【Halide】调度优化【1】 【转载】北大中文男足战报2 【转载】北大中文男足战报1 【AI编译】张量生命周期管理 SystemC 用寄存器同步建模方法 【脉动阵列】脉动阵列类型 【im2col】AScend conv accelerate 【感想】写作进度报告4 【BYOC】TVM添加自定义编译器 ccompiler 【感想】写作进度报告3 【Tengine】推理流程脑图【2】 【Tengine】推理流程脑图【1】 【NCNN】学习ncnn模型转换 【编译器】使用llvm编译自定义语言【3】编译 object 【编译器】使用llvm编译自定义语言【2】转llvm IR 【编译器】使用llvm编译自定义语言【1】构建AST 【AI编译】如何进行内存分配 【感想】写作进度报告2 【AI编译】layer-group之后如何tiling 【AI编译】如何进行layer-group 【量化】连续卷积层首尾量化的可行性 【Gemm】内存对齐 【gemm】Gemm计算加速 【TVM】通过代码学习编译流程【5】FuseOps 【TVM】通过代码学习编译流程【6】CodeGen 【TVM】通过代码学习类【3.5】Pass 【TVM】通过代码学习编译流程【4】BuildRelay 【AI编译】Tiling操作能优化什么时间 【TVM】通过代码学习编译流程【3】模型编译 【TVM】通过代码学习编译流程【2】模型转换 【TVM】通过代码学习编译流程【1】必要知识 【感想】写作进度报告1 【Winograd】卷积加速算法原理及实现 SystemC 等待异步事件解决方案 【TVM】Python脚本实现模型编译和保存 【推理引擎】常见AI推理框架 【3D建模】T110E3卡迪夫蓝调皮肤模型 【TVM】C++部署运行TVM 【推理引擎】NCNN和Tengine量化推理逻辑对比 【3D建模】IS-7攻城锤流纹岩皮肤展示 【TVM】根据例子走通代码库 博客汇总目录 【Im2Col】卷积加速算法【2】NHWC 【Im2Col】卷积加速算法【1】 NCHW openBlas库的安装与简单使用 C语言工程调用Cpp库解决方案 foo Hello World
【BYOC】TVM切分子图
Post author: XianMu@Пусть этот камень будет более крепким, чем ч · 2025-10-06 · via Пусть этот камень будет более крепким, чем человек

# 前言

本篇通过举例说明 TVM 切分子图的方法。

TVM 切分子图,是将整个图中的部分算子拆分出来,包装为一个函数。TVM 把它称为复合函数。切分子图有很多用处,比如算子融合、跨平台优化、做 layergroup 等。TVM 为子图切分提供了好用的工具,本篇文章将结 《【BYOC】TVM 添加自定义编译器 ccompiler》,来介绍一下如何使用 TVM 的工具切分子图,令自定义编译器 ccompiler 支持的子图在 ccompiler 执行,不支持的子图在 CPU 运行的。

参考链接:《【BYOC】TVM 添加自定义编译器 ccompiler》

作为初学者,错误在所难免,还望不吝赐教。

TVM 需要一个注释器,为需要切分的图结构的起止地点标记出来,之后 TVM 就可以帮助将子图切分出来。
例如 TVM 支持 这个 DNNL 的外部编译器,那么可以使用 mod = transform.AnnotateTarget(["dnnl"])(ref_mod) 这个注释器来标记 DNNL 支持的子图结构(TVM 已经提供了 DNNL 的注释器),然后使用 mod = transform.PartitionGraph()(mod) 将子图切分出来。

我们以 “将 ccompiler 支持的子图切分出来” 这个目标为例,介绍这个过程。
ccompiler 只支持 + - * 三个算子,目标是将所有 ccompiler 支持的算子切分成子图,交给 ccompiler 编译和计算,其他算子交给 CPU 计算。
如下图所示:
这个图中出现了一个 / 算子,需要将该算子留下来,其余算子切成子图,交给 ccompiler 进行编译和计算。

含有除法的图结构

原图结构如下:

fn (%x: Tensor[(10, 10), float32], %w0: Tensor[(10, 10), float32], %w1: Tensor[(10, 10), flot32], %w2: Tensor[(10, 10), float32], %w3: Tensor[(10, 10), float32], %w4: Tensor[(10, 10), float32]) {
  %0 = add(%x, %w1);
  %1 = multiply(%x, %w0);
  %2 = subtract(%0, %w2);
  %3 = divide(%1, %2);
  %4 = add(%3, %w3);
  divide(%4, %w4)
}

接下来需要将 ccompiler 支持的子图结构标记出来,标记原理如图所示:

标记子图的起始和终止节点

图示比较清楚:将连续的、支持的算子,用 beginend 隔离出来,形成一个子图。注释器的参考写法、调用例子,可以参考 TVM 官方代码。

添加注释之后的图结构:

def @main(%x: Tensor[(10, 10), float32], %w0: Tensor[(10, 10), float32], %w1: Tensor[(10, 10), flot32], %w2: Tensor[(10, 10), float32], %w3: Tensor[(10, 10), float32], %w4: Tensor[(10, 10), float32]) {
  %0 = annotation.compiler_begin(%x, meta[relay.attrs.CompilerAttrs][0]);
  %1 = annotation.compiler_begin(%w0, meta[relay.attrs.CompilerAttrs][1]);
  %2 = multiply(%0, %1);
  %3 = annotation.compiler_begin(%x, meta[relay.attrs.CompilerAttrs][3]);
  %4 = annotation.compiler_begin(%w1, meta[relay.attrs.CompilerAttrs][4]);
  %5 = add(%3, %4);
  %6 = annotation.compiler_begin(%w2, meta[relay.attrs.CompilerAttrs][5]);
  %7 = subtract(%5, %6);
  %8 = annotation.compiler_end(%2, meta[relay.attrs.CompilerAttrs][2]);
  %9 = annotation.compiler_end(%7, meta[relay.attrs.CompilerAttrs][6]);
  %10 = divide(%8, %9);
  %11 = annotation.compiler_begin(%10, meta[relay.attrs.CompilerAttrs][7]);
  %12 = annotation.compiler_begin(%w3, meta[relay.attrs.CompilerAttrs][8]);
  %13 = add(%11, %12);
  %14 = annotation.compiler_end(%13, meta[relay.attrs.CompilerAttrs][9]);
  divide(%14, %w4)
}

添加注释之后,直接调用 TVM 的切分工具: mod = transform.PartitionGraph()(mod) ,实现子图切分。

切分之后的结构:

def @main(%x: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %w0: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %w1: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %w2: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %w3: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %w4: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */) -> Tensor[(10, 10), float32] {
  %0 = @tvmgen_default_ccompiler_main_0(%x, %w0) /* ty=Tensor[(10, 10), float32] */;
  %1 = @tvmgen_default_ccompiler_main_2(%x, %w1, %w2) /* ty=Tensor[(10, 10), float32] */;
  %2 = divide(%0, %1) /* ty=Tensor[(10, 10), float32] */;
  %3 = @tvmgen_default_ccompiler_main_5(%2, %w3) /* ty=Tensor[(10, 10), float32] */;
  divide(%3, %w4) /* ty=Tensor[(10, 10), float32] */
}
def @tvmgen_default_ccompiler_main_0(%ccompiler_0_i0: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %ccompiler_0_i1: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, Compiler="ccompiler", Primitive=1, Inline=1, global_symbol="tvmgen_default_ccompiler_main_0") -> Tensor[(10, 10), float32] {
  multiply(%ccompiler_0_i0, %ccompiler_0_i1) /* ty=Tensor[(10, 10), float32] */
}
def @tvmgen_default_ccompiler_main_2(%ccompiler_2_i0: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %ccompiler_2_i1: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %ccompiler_2_i2: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, Compiler="ccompiler", Primitive=1, Inline=1, global_symbol="tvmgen_default_ccompiler_main_2") -> Tensor[(10, 10), float32] {
  %4 = add(%ccompiler_2_i0, %ccompiler_2_i1) /* ty=Tensor[(10, 10), float32] */;
  subtract(%4, %ccompiler_2_i2) /* ty=Tensor[(10, 10), float32] */
}
def @tvmgen_default_ccompiler_main_5(%ccompiler_5_i0: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, %ccompiler_5_i1: Tensor[(10, 10), float32] /* ty=Tensor[(10, 10), float32] */, Compiler="ccompiler", Primitive=1, Inline=1, global_symbol="tvmgen_default_ccompiler_main_5") -> Tensor[(10, 10), float32] {
  add(%ccompiler_5_i0, float32] {
  add(%ccompiler_5_i0, %ccompiler_5_i1) /* ty=Tensor[(10, 10), float32] */
}

可以看到三个子图 @tvmgen_default_ccompiler_main_0@tvmgen_default_ccompiler_main_2@tvmgen_default_ccompiler_main_5 用单独的函数进行包装,在主函数中进行调用。

可以通过 TVM 官方例子来学习如何写这个注释器。

# 后记

本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!