tvm cuda后端编译路径

TVM CUDA 编译路径

本文说明在 target=cuda 下,TVM 如何将 Relax 计算图编译为最终可执行代码,以及模型各层如何映射到具体算子实现(TVM 自研 CUDA kernel、cuBLAS 等 BYOC 后端)。


1. 总览:两条并行路径

CUDA 编译不是「一层对应一个 cuBLAS 调用」。实际是 Relax VM 调度 + 多种算子后端 的组合:

Phase 3: 运行时

Phase 2: Codegen(tvm.compile 后半段)

Phase 1: Relax Pipeline(target=cuda 默认)

输入

IRModule
Relax 计算图

Library Dispatch
Sampling / SortScan

LegalizeOps
高层 op → call_tir + PrimFunc

FuseOps + FuseTIR
算子融合

DLight GPU Schedule
thread / shared mem

VM Lowering
内存规划 / shape / alloc

VMCodeGen → 字节码

tirx.build → CUDA kernel .so

VMLink 链接

Relax VirtualMachine

TVM 自研 CUDA kernel

外部库 BYOC
cuBLAS / CUTLASS / cuDNN ...

核心结论:

  • 默认 CUDA pipeline 不会自动使用 cuBLAS;matmul、conv 等走 TVM 自研 CUDA kernel(DLight schedule + tirx.build)。
  • cuBLAS、CUTLASS、cuDNN 等属于 BYOC 可选分支,需在 tvm.compile 前显式插入 partition_for_* + RunCodegen Pass。

2. 编译入口:tvm.compile(mod, target="cuda")

import tvm
from tvm import relax

ex = tvm.compile(mod, target="cuda")
vm = relax.VirtualMachine(ex, tvm.cuda())
out = vm["main"](input_data)

内部分两大部分(python/tvm/relax/vm_build.py):

步骤动作产出
① Relax Pipelinerelax.get_default_pipeline(cuda)(mod)优化后的 IRModule(Relax 函数 + PrimFunc)
② VMCodeGen将 Relax 函数译为 VM 字节码ExecBuilder
③ tirx.build将所有 PrimFunc 编译为 CUDAruntime.Module.cu → PTX / cubin)
④ VMLink链接 VM 字节码 + TIR lib + external_mods最终 Executable

GPU target 且 relax_pipeline="default" 时,自动选用 target 专属 pipeline(含 DLight),而非通用 default_build_pipeline

# vm_build.py 逻辑(简化)
if relax_pipeline == "default" and "gpu" in target.keys:
    relax_pipeline = relax.get_default_pipeline(target)  # cuda → backend.cuda.pipeline

CUDA 默认 pipeline 定义在 python/tvm/relax/backend/cuda/pipeline.py

library_dispatch_passes   # DispatchSampling, DispatchSortScan
+ legalize_passes         # LegalizeOps → FuseOps → FuseTIR → DLight
+ dataflow_lower_passes   # CallTIRRewrite 等
+ finalize_passes         # StaticPlanBlockMemory → VMShapeLower → AttachGlobalSymbol

3. 各阶段详解

3.1 模型导入:层 → Relax 高层算子

Frontend(PyTorch / ONNX / NNModule)将模型层翻译为 平台无关 的 Relax IR:

模型层Relax IR(示意)
nn.LinearR.matmul(x, W) + R.add(..., bias)
nn.ReLUR.nn.relu(x)
nn.Conv2dR.nn.conv2d(x, weight, ...)
nn.LayerNormR.nn.layer_norm(...)
AttentionR.nn.attention(...) 或分解后的 matmul / softmax

此时尚无 CUDA / cuBLAS 概念,仅为高层算子图。

3.2 LegalizeOps:高层算子 → call_tir + PrimFunc

LegalizeOps 通过 register_legalize 规则,将每个 relax.op 降为 TIR PrimFunc:

Relax 算子Legalize 规则文件生成的 PrimFunc 来源
R.matmullegalize_ops/linear_algebra.pyTE 生成 matmul 三重循环
R.nn.relulegalize_ops/nn.pyTOPI topi.nn.relu
R.nn.conv2dlegalize_ops/nn.pyTOPI topi.nn.conv2d
R.nn.softmaxlegalize_ops/nn.pyTOPI topi.nn.softmax
R.add / R.multiplyelementwise 规则TOPI 逐元素算子

Legalize 后 Relax 函数变为:

lv0 = R.call_tir(matmul_primfunc, (x, w), out_sinfo=...)
lv1 = R.call_tir(relu_primfunc, lv0, out_sinfo=...)

IRModule 中同时存在 Relax 函数(调度逻辑)和 PrimFunc(算子实现草稿)。LegalizeOps 不区分 CUDA / CPU,规则共用。

3.3 FuseOps + FuseTIR:算子融合

Pass作用示例
AnnotateTIROpPattern标注 PrimFunc 的 op patternmatmul=Opaque,relu=Elementwise
FuseOps在 DataflowBlock 内合并相邻算子relu 可融入 matmul 后的 epilogue
FuseTIR将多个 PrimFunc 合成一个 fused PrimFuncmatmul+relu → 单个 kernel

融合减少 GPU 内存读写与 kernel launch 次数。

3.4 DLight:GPU Schedule(CUDA 特有)

dl.ApplyDefaultSchedule(
    dl.gpu.Matmul(),
    dl.gpu.GEMV(),
    dl.gpu.Reduction(),
    dl.gpu.GeneralReduction(),
    dl.gpu.Fallback(),
)

给 PrimFunc 添加 thread binding、shared memory tiling、vectorization 等,使 TIR 可被 codegen 为高效 CUDA kernel。没有此步,matmul 等 PrimFunc 只是朴素三重循环,无法正确生成 GPU 代码。

3.5 VM Lowering:内存 + 形状 + 调用形式

Pass作用
CallTIRRewritecall_tir / call_dps_packed 显式 alloc_tensor
StaticPlanBlockMemory静态内存复用,降低峰值显存
RewriteCUDAGraph(可选)插入 CUDA Graph 捕获点
VMShapeLower动态 shape 计算降为 VM builtin
AttachGlobalSymbol为函数附加符号名,供 codegen / 加载

3.6 Codegen:生成可执行代码

VMCodeGen(mod)            → Relax VM 字节码(调度 main 函数)
tirx.build(tir_mod, cuda) → 所有 PrimFunc → CUDA C → nvcc → PTX/cubin → .so
VMLink(...)               → 打包为单一 Executable

最终产物是一个 runtime.Module(Executable),内含:

  • VM 字节码(控制流、算子调用顺序)
  • CUDA kernel 动态库(TVM 自研算子)
  • (可选)external_mods(BYOC 外部库 runtime)

4. 模型层 → 算子实现映射

4.1 默认 CUDA pipeline(无 BYOC)

模型层Relax IRLegalize 后Schedule最终实现
Linear / MatMulR.matmulcall_tir(matmul_pf)DLight gpu.MatmulTVM CUDA kernel
ReLU / GELUR.nn.relucall_tir(relu_pf)FuseTIR 可融入 matmulTVM CUDA kernel(或 fused)
Conv2dR.nn.conv2dcall_tir(conv2d_pf)DLight gpu.FallbackTVM CUDA kernel
SoftmaxR.nn.softmaxcall_tir(softmax_pf)DLight ReductionTVM CUDA kernel
LayerNormR.nn.layer_normcall_tir(ln_pf)DLightTVM CUDA kernel
Add / MulR.addcall_tir(add_pf)Elementwise 融合TVM CUDA kernel
SamplingR.multinomialDispatchSampling专用 PackedFunc

4.2 启用 BYOC 后(需手动插入 Pass)

模型层 / 子图BYOC Pass匹配 Pattern最终实现
MatMul (+bias+relu)partition_for_cublas + RunCodegencublas.matmul_bias_relucuBLAS LtCallCublasLt
高性能 GEMM / Attentionpartition_for_cutlass + RunCodegencutlass.*CUTLASS 预编译 kernel
Conv + BN + ReLUpartition_for_cudnn + RunCodegencudnn.*cuDNN

BYOC 用法(在 compile 前插入):

from tvm.relax.backend.cuda.cublas import partition_for_cublas
from tvm import relax

mod = partition_for_cublas(mod)          # FuseOpsByPattern + Codegen 标注
mod = relax.transform.RunCodegen()(mod)  # → call_dps_packed + external_mods
ex = tvm.compile(mod, target="cuda")     # VMLink 链接 cuBLAS runtime

cuBLAS 符号命名、端到端关联链、call_dps_packed 机制详见 tvm.md §5.5.2


5. 运行时:算子如何被调用

编译后的 ExecutableRelax VM 驱动。VM 不「理解模型层」,只执行字节码中的 call_tir / call_dps_packed 指令;层与实现的映射在编译期 Pass 链中已完成。

vm["main"](input, *weights)
  │
  ├─ VM 字节码解释执行(控制流、shape 计算、内存分配)
  │
  ├─ call_tir(matmul_fused, x, w, out)
  │     → func_pool 查 PrimFunc 对应的 CUDA kernel
  │     → CUDA driver launch grid/block
  │     → TVM 自研 matmul+relu kernel 在 GPU 上执行
  │
  └─ call_dps_packed(ExternFunc("fused_*_cublas0"), x, w, out)   # 若启用 BYOC
        → func_pool 查 external_mods 中的 CublasJSONRuntime
        → CallCublasLt → NVIDIA cuBLAS Lt API

VM 初始化 func pool 时(src/runtime/vm/vm.cc):

  1. kPackedFunc 条目按 symbol 名查找:GetFuncFromImports(name) 遍历 import 链
  2. kVMFunc 条目加载 Relax 函数字节码
  3. TIR kernel 通过 tirx.build 产物的 func table 解析

6. 示例:MLP 完整走一遍

假设 MLP: Linear(784→128) → ReLU → Linear(128→10)target=cuda

Frontend
  fc1: matmul + add
  relu: nn.relu
  fc2: matmul + add

LegalizeOps(平台无关)
  4 个 call_tir + 4 个 PrimFunc(matmul×2, add×2, relu×1)

FuseOps + FuseTIR
  add 可能融入 matmul epilogue;relu 可能融入第一个 matmul 后

DLight gpu.Matmul
  2 个 matmul PrimFunc 获得 GPU schedule

VM Lowering + Codegen
  VM 字节码调度 2~3 个 kernel launch
  tirx.build 生成对应 .cu kernel

运行时
  VM 依次 launch matmul(+bias+relu?) kernel → matmul(+bias) kernel

启用 cuBLAS BYOC 时: 两个 Linear 的 matmul 子图被替换为 call_dps_packed → cuBLAS Lt;ReLU 及未匹配算子仍走 TVM kernel。

完整代码示例:

import tvm
from tvm import relax
from tvm.relax.frontend import nn

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 128)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

mod, params = MLP().export_tvm({"forward": {"x": nn.spec.Tensor(("n", 784), "float32")}})
target = tvm.target.Target("cuda")

# 路径 A:默认(TVM 自研 CUDA kernel)
with target:
    mod = relax.backend.cuda.get_default_pipeline(target)(mod)
ex = tvm.compile(mod, target=target)
vm = relax.VirtualMachine(ex, tvm.cuda())

# 路径 B:cuBLAS BYOC(在 pipeline 之前或之中插入)
from tvm.relax.backend.cuda.cublas import partition_for_cublas

mod, params = MLP().export_tvm({"forward": {"x": nn.spec.Tensor(("n", 784), "float32")}})
mod = partition_for_cublas(mod)
mod = relax.transform.RunCodegen()(mod)
with target:
    mod = relax.backend.cuda.get_default_pipeline(target)(mod)
ex = tvm.compile(mod, target=target)

7. 默认路径 vs BYOC 路径对比

默认 CUDA pipelineBYOC(cuBLAS 等)
触发方式tvm.compile(mod, target="cuda")额外 partition_for_* + RunCodegen
MatMul 实现DLight + tirx.build → CUDA kernelcall_dps_packed → cuBLAS Lt
Conv 实现TOPI + DLight → CUDA kernel(可选)cuDNN BYOC
IR 调用形式R.call_tir(PrimFunc, ...)R.call_dps_packed(ExternFunc(...), ...)
产物VM 字节码 + CUDA .so额外 external_mods 链接进 Executable
适用场景通用、可融合、动态 shape大 GEMM、已知 pattern、库高度优化

两条路径 并行互补:BYOC 匹配的子图走外部库,未匹配的算子仍走 LegalizeOps → FuseTIR → DLight → tirx.build


8. 小结

问题答案
target=cuda 如何编译?Relax Pipeline(Legalize → Fusion → DLight → VM Lowering)+ tirx.build(cuda) + VMLink
默认会用 cuBLAS 吗?不会。默认 matmul / conv 等走 TVM 自研 CUDA kernel
如何启用 cuBLAS?手动 partition_for_cublas + RunCodegen,在 compile 前插入
层如何映射到实现?Frontend → Relax op → LegalizeOps → PrimFunc →(可选 BYOC)→ VM 调用 kernel / 外部库
最终产物是什么?单一 Executable:VM 字节码 + CUDA .so +(可选)external_mods

一句话: target=cuda 决定 TIR 的 schedule 策略(DLight)和 codegen 后端(NVCC / CUDA);模型层通过 LegalizeOps 映射到 PrimFunc,再 codegen 为 GPU kernel;cuBLAS 等外部库是可选 BYOC 分支,需显式开启,不在默认 pipeline 中。


9. 关键源码索引

主题路径
CUDA 默认 pipelinepython/tvm/relax/backend/cuda/pipeline.py
编译入口python/tvm/relax/vm_build.py
Legalize 规则python/tvm/relax/transform/legalize_ops/
cuBLAS pattern / partitionpython/tvm/relax/backend/cuda/cublas.py
RunCodegensrc/relax/transform/run_codegen.cc
cuBLAS codegensrc/relax/backend/contrib/cublas/codegen.cc
cuBLAS runtimesrc/runtime/extra/contrib/cublas/cublas_json_runtime.cc
TIR → CUDA codegensrc/target/codegen.cc
VM 链接src/relax/backend/vm/codegen_vm.ccVMLink
VM 运行时src/runtime/vm/vm.cc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self-motivation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值