TVM教程-----Bring Your Own Codegen
Bring Your Own CodegenTVM 的 Bring Your Own CodegenBYOC框架允许你将模型的部分内容卸载到自定义后端——硬件加速器、推理库或你自己的 kernel——而其余部分仍由 TVM 编译。本教程分为两部分BYOC 如何工作—— 我们先用一个捆绑的、无需硬件的示例 NPU 后端讲解流程再用同一流程驱动真实的生产后端 NVIDIA TensorRT。两者都运行一个小型手写模型以便每一步都清晰可见两者之间唯一变化的是后端而这种对比正是本课要点。部署真实模型—— 随后将其投入实际使用把一个真实的 PyTorchnn.Module从 export 经 TensorRT 跑到 GPU 上。示例 NPU 是一个教学用桩stub其 runtime 会记录 NPU 会做出的调度决策内存层级、执行引擎、融合但不做真实计算因此输出 buffer 保持未初始化。所以在 NPU 相关小节我们只检查 shape不检查数值——它的职责是让每一步 BYOC 都可见没有任何隐藏。TensorRT 则用同一流程做真实计算因此我们会将其结果与参考结果交叉验证。前置条件示例 NPU 小节需要 TVM 以USE_EXAMPLE_NPU_CODEGENON和USE_EXAMPLE_NPU_RUNTIMEON构建TensorRT 小节需要USE_TENSORRT_CODEGENON、USE_TENSORRT_RUNTIMEON和USE_CUDAON外加 CUDA GPU 以及匹配的 TensorRT 安装来自 NVIDIA 的pip install tensorrt包或 TensorRT 压缩包最终部署小节还需要 PyTorch。当其后端不可用时各小节会优雅降级。BYOC 流程概览BYOC 通过四个步骤把自定义后端接入 TVM 的编译流水线注册 pattern—— 描述后端能处理哪些 Relax op 序列。划分图partition—— 将匹配到的算子归组为 composite function。运行 codegen—— 将每个 composite 降到后端表示本教程中两个后端都是 JSON graph。执行—— runtime 将每个 composite 派发到后端。步骤 1 和 2 是纯 Python可在任意环境运行步骤 3 和 4 需要将后端的 codegen 与 runtime 编入 TVM因此下文的 build-and-run 单元都做了可用性守卫。Step 1Import 后端以注册其 pattern导入后端模块会将其 pattern 注册到 TVM 的全局 registry。Pattern 注册与 C 构建无关——只有 codegen 和 runtime 需要把后端编进去——因此我们探测每个后端并据此守卫 build-and-run 单元。importosimporttempfileimportnumpyasnpimporttvmimporttvm.relax.backend.contrib.example_npufromtvmimportrelaxfromtvm.relax.backend.contrib.tensorrtimportpartition_for_tensorrtfromtvm.relax.backend.pattern_registryimportget_patterns_with_prefixfromtvm.relax.transformimportFuseOpsByPattern,MergeCompositeFunctions,RunCodegenfromtvm.scriptimportrelaxasR has_example_npu_codegentvm.get_global_func(relax.ext.example_npu,True)has_example_npu_runtimetvm.get_global_func(runtime.ExampleNPUJSONRuntimeCreate,True)has_example_npuhas_example_npu_codegenandhas_example_npu_runtime has_tensorrt_codegentvm.get_global_func(relax.ext.tensorrt,True)isnotNone_is_trt_runtime_enabledtvm.get_global_func(relax.is_tensorrt_runtime_enabled,True)has_tensorrt(has_tensorrt_codegenand_is_trt_runtime_enabledisnotNoneand_is_trt_runtime_enabled())has_cudatvm.cuda(0).existStep 2定义模型单个卷积后接 ReLU。该模型同时用于两个后端。tvm.script.ir_moduleclassConvReLU:R.functiondefmain(data:R.Tensor((1,3,32,32),float32),weight:R.Tensor((16,3,3,3),float32),)-R.Tensor((1,16,30,30),float32):withR.dataflow():convrelax.op.nn.conv2d(data,weight)outrelax.op.nn.relu(conv)R.output(out)returnoutStep 3为示例 NPU 做 PartitionFuseOpsByPattern将匹配已注册 pattern 的算子归组为 composite functionMergeCompositeFunctions再把绑定到同一后端的相邻 composite 合并为一次外部调用。有两个标志控制划分bind_constantsFalse让权重保持为函数参数从而由 host 管理参数。下文的 TensorRT 做相反选择它把权重绑定为常量因为会将其 bake 进 engine。annotate_codegenTrue用带后端名称标签的函数包装每个匹配到的 composite——RunCodegen依据该标签路由。后续的MergeCompositeFunctions在归组 composite 时也会附上该标签因此下文的partition_for_tensorrt可以不设该标志。示例 NPU 注册了一个融合的conv2d relupattern优先级高于独立的conv2dpattern因此这两个算子会折叠成单个example_npu.conv2d_relu_fusedcomposite——可在打印出的 module 中看到它。npu_patternsget_patterns_with_prefix(example_npu)npu_modFuseOpsByPattern(npu_patterns,bind_constantsFalse,annotate_codegenTrue)(ConvReLU)npu_modMergeCompositeFunctions()(npu_mod)print(After partitioning for the example NPU:)print(npu_mod)After partitioning for the example NPU: # from tvm.script import ir as I # from tvm.script import relax as R I.ir_module class Module: R.function def fused_relax_nn_conv2d_relax_nn_relu_example_npu_example_npu(data: R.Tensor((1, 3, 32, 32), dtypefloat32), weight: R.Tensor((16, 3, 3, 3), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): R.func_attr({Codegen: example_npu}) # from tvm.script import relax as R R.function def local_func(data_1: R.Tensor((1, 3, 32, 32), dtypefloat32), weight_1: R.Tensor((16, 3, 3, 3), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): R.func_attr({Composite: example_npu.conv2d_relu_fused}) conv: R.Tensor((1, 16, 30, 30), dtypefloat32) R.nn.conv2d(data_1, weight_1, strides[1, 1], padding[0, 0, 0, 0], dilation[1, 1], groups1, data_layoutNCHW, kernel_layoutOIHW, out_layoutNCHW, out_dtypeNone) gv: R.Tensor((1, 16, 30, 30), dtypefloat32) R.nn.relu(conv) return gv output: R.Tensor((1, 16, 30, 30), dtypefloat32) local_func(data, weight) return output R.function def main(data: R.Tensor((1, 3, 32, 32), dtypefloat32), weight: R.Tensor((16, 3, 3, 3), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): cls Module with R.dataflow(): gv: R.Tensor((1, 16, 30, 30), dtypefloat32) cls.fused_relax_nn_conv2d_relax_nn_relu_example_npu_example_npu(data, weight) R.output(gv) return gvStep 4在示例 NPU 上 Codegen、build 与运行RunCodegen调用每个被注解的 composite 的后端 codegen将其替换为后端 runtime module此处为 NPU 的 JSON graph随后relax.build编译剩余的 host 侧程序并链接所有内容。由于该 runtime 是一个不做任何计算的桩我们只对输出 shape 做断言——数值是未初始化的。np.random.seed(0)data_npnp.random.randn(1,3,32,32).astype(float32)weight_npnp.random.randn(16,3,3,3).astype(float32)ifhas_example_npu:npu_modRunCodegen()(npu_mod)withtvm.transform.PassContext(opt_level3):npu_execrelax.build(npu_mod,tvm.target.Target(llvm))npu_vmrelax.VirtualMachine(npu_exec,tvm.cpu())npu_outnpu_vm[main](tvm.runtime.tensor(data_np,tvm.cpu()),tvm.runtime.tensor(weight_np,tvm.cpu()))assertnpu_out.numpy().shape(1,16,30,30)print(Example NPU run completed. Output shape:,npu_out.numpy().shape)else:print(Example NPU backend unavailable; skipping its build and run.)Example NPU backend unavailable; skipping its build and run.同一流程打到真实后端TensorRT上面的 Step 1–4 就是全部机制。把它们对准真实后端时变化很少因此不再重复走一遍这里只说明对 NVIDIA TensorRT 有何不同一次调用完成 Partition。partition_for_tensorrt打包了你手写运行的FuseOpsByPatternMergeCompositeFunctions并使用 TensorRT 自己的 pattern 表。权重变为常量bind_constantsTrueTensorRT 会把它们 bake 进所构建的 engine因此在 partition 之前先绑定参数。真实数值。TensorRT 会真正计算因此我们为 CUDA 构建、在 GPU 上运行并与普通的 CPU 构建交叉验证——而不仅仅是 shape。下面的 build-and-run 单元仅在 TensorRT 与 CUDA 可用时执行。在仅 CPU 的文档构建中它们不会产生输出。trt_modrelax.transform.BindParams(main,{weight:weight_np})(ConvReLU)trt_modpartition_for_tensorrt(trt_mod)print(After partition_for_tensorrt:)print(trt_mod)After partition_for_tensorrt: # from tvm.script import ir as I # from tvm.script import relax as R I.ir_module class Module: R.function def fused_relax_nn_conv2d_relax_nn_relu_tensorrt(data: R.Tensor((1, 3, 32, 32), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): R.func_attr({Codegen: tensorrt}) # from tvm.script import relax as R R.function def gv(data_1: R.Tensor((1, 3, 32, 32), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): R.func_attr({Composite: tensorrt.nn.conv2d}) with R.dataflow(): gv_1: R.Tensor((1, 16, 30, 30), dtypefloat32) R.nn.conv2d(data_1, metadata[relax.expr.Constant][0], strides[1, 1], padding[0, 0, 0, 0], dilation[1, 1], groups1, data_layoutNCHW, kernel_layoutOIHW, out_layoutNCHW, out_dtypeNone) R.output(gv_1) return gv_1 lv: R.Tensor((1, 16, 30, 30), dtypefloat32) gv(data) # from tvm.script import relax as R R.function def gv1(lv_1: R.Tensor((1, 16, 30, 30), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): R.func_attr({Composite: tensorrt.nn.relu}) with R.dataflow(): gv_1: R.Tensor((1, 16, 30, 30), dtypefloat32) R.nn.relu(lv_1) R.output(gv_1) return gv_1 gv_1: R.Tensor((1, 16, 30, 30), dtypefloat32) gv1(lv) return gv_1 R.function def main(data: R.Tensor((1, 3, 32, 32), dtypefloat32)) - R.Tensor((1, 16, 30, 30), dtypefloat32): cls Module with R.dataflow(): gv: R.Tensor((1, 16, 30, 30), dtypefloat32) cls.fused_relax_nn_conv2d_relax_nn_relu_tensorrt(data) R.output(gv) return gv # Metadata omitted. Use show_metaTrue in script() method to show it.为 CUDA 构建在 GPU 上运行并与 CPU 参考结果比较。ifhas_tensorrtandhas_cuda:devtvm.cuda(0)withtvm.transform.PassContext(opt_level3):trt_execrelax.build(RunCodegen()(trt_mod),cuda)trt_outrelax.VirtualMachine(trt_exec,dev)[main](tvm.runtime.tensor(data_np,dev)).numpy()cpu_modrelax.transform.LegalizeOps()(relax.transform.BindParams(main,{weight:weight_np})(ConvReLU))cpu_execrelax.build(cpu_mod,llvm)cpu_outrelax.VirtualMachine(cpu_exec,tvm.cpu())[main](tvm.runtime.tensor(data_np,tvm.cpu())).numpy()np.testing.assert_allclose(trt_out,cpu_out,rtol1e-2,atol1e-2)print(TensorRT output shape:,trt_out.shape,- matches the CPU reference.)真实后端还会暴露教学桩没有的旋钮。通过relax.ext.tensorrt.options配置设置use_fp16可让 TensorRT 选用 FP16 kernel用少许精度换取速度流程的其余部分不变。其他选项由环境变量驱动TVM_TENSORRT_USE_INT8启用带 calibration 的 INT8TVM_TENSORRT_MAX_WORKSPACE_SIZE限制 build workspaceTVM_TENSORRT_CACHE_DIR将已构建的 engine 缓存到磁盘以便跨运行复用。ifhas_tensorrtandhas_cuda:fp16_modpartition_for_tensorrt(relax.transform.BindParams(main,{weight:weight_np})(ConvReLU))withtvm.transform.PassContext(opt_level3,config{relax.ext.tensorrt.options:{use_fp16:True}}):fp16_execrelax.build(RunCodegen()(fp16_mod),cuda)fp16_outrelax.VirtualMachine(fp16_exec,tvm.cuda(0))[main](tvm.runtime.tensor(data_np,tvm.cuda(0))).numpy()np.testing.assert_allclose(fp16_out,cpu_out,rtol5e-2,atol5e-2)print(TensorRT FP16 output shape:,fp16_out.shape,- matches within FP16 tolerance.)Example NPU vs TensorRT 一览同一套四步流程两个后端AspectExample NPU教学桩TensorRT真实后端Runtime记录决策不做计算构建并运行 nvinfer engineOutput未初始化检查 shape真实数值与 CPU 交叉验证Weightsbind_constantsFalsebind_constantsTruebake 进去Partition两步 Pass手写partition_for_tensorrt一次调用用 TensorRT 部署 PyTorch 模型上面全部使用手写IRModule以便每个 op 都可见。实践中你从训练好的模型出发。最后一节对真实的 PyTorchnn.Module端到端运行同一套partition_for_tensorrt流程export 它用 PyTorch frontend 导入 Relax权重以常量形式进入——正是 TensorRT bake 进 engine 所需的形式partition为 CUDA 构建并将 GPU 结果与 PyTorch 自身输出对照。除 frontend 导入外唯一差异是导入后的程序以 tuple 返回输出因此对单个结果张量取索引[0]partition-build-run 流程本身不变。本节额外需要 PyTorch。try:importtorchfromtorchimportnn has_torchTrueexceptImportError:has_torchFalseifhas_torchandhas_tensorrtandhas_cuda:fromtvm.relax.frontend.torchimportfrom_exported_programclassSmallConvNet(nn.Module):def__init__(self):super().__init__()self.conv1nn.Conv2d(3,8,3)self.conv2nn.Conv2d(8,16,3)self.poolnn.MaxPool2d(2)defforward(self,x):xtorch.relu(self.conv1(x))xself.pool(x)xtorch.relu(self.conv2(x))returnx torch_modelSmallConvNet().eval()example_inputtorch.randn(1,3,32,32)withtorch.no_grad():torch_reftorch_model(example_input).numpy()exportedtorch.export.export(torch_model,(example_input,))torch_modfrom_exported_program(exported)torch_modpartition_for_tensorrt(torch_mod)print(After importing and partitioning the PyTorch model:)print(torch_mod)torch_devtvm.cuda(0)withtvm.transform.PassContext(opt_level3):torch_execrelax.build(RunCodegen()(torch_mod),cuda)deployedrelax.VirtualMachine(torch_exec,torch_dev)[main](tvm.runtime.tensor(example_input.numpy(),torch_dev))[0].numpy()np.testing.assert_allclose(deployed,torch_ref,rtol1e-2,atol1e-2)print(Deployed PyTorch model on TensorRT; output,deployed.shape,matches PyTorch.)真实部署会构建一次并复用产物。将编译后的 module export 为共享库之后再加载运行——可在全新进程中进行无需 PyTorch也无需重新构建。ifhas_torchandhas_tensorrtandhas_cuda:withtempfile.TemporaryDirectory()astmpdir:lib_pathos.path.join(tmpdir,deployed_trt.so)torch_exec.export_library(lib_path)loadedtvm.runtime.load_module(lib_path)reranrelax.VirtualMachine(loaded,torch_dev)[main](tvm.runtime.tensor(example_input.numpy(),torch_dev))[0].numpy()np.testing.assert_allclose(reran,torch_ref,rtol1e-2,atol1e-2)print(Reloaded the exported library and reran; output,reran.shape,still matches.)真实部署注意事项算子覆盖与回退。TensorRT 只卸载其 pattern 表中的算子见python/tvm/relax/backend/contrib/tensorrt.py任何不支持的算子会留在 host 上。打印划分后的 module查找带Codegen: tensorrt的函数即可看到卸载了什么。动态 shape。Builder 会为动态的 leadingbatch维度设置 optimization profile因此该集成可以服务以 symbolic batch size export 的模型。Engine 构建成本。第一次构建 TensorRT engine 很慢不是卡死。设置TVM_TENSORRT_CACHE_DIR可将已构建的 engine 缓存到磁盘后续运行跳过重建。下一步要以示例 NPU 为起点构建你自己的后端用你的硬件 SDK 调用替换src/runtime/extra/contrib/example_npu/example_npu_runtime.cc中的桩 runtime。在patterns.py中扩展你的硬件支持的算子。若后端需要非 JSON 的序列化格式在src/relax/backend/contrib/下添加 C codegen。参照ExampleNPU.cmake在cmake/modules/contrib/下添加 CMake 模块。若要研究完整的真实后端实现参见 TensorRT 集成python/tvm/relax/backend/contrib/tensorrt.py中的 pattern 表与partition_for_tensorrtsrc/relax/backend/contrib/tensorrt/中的 codegen以及src/runtime/extra/contrib/tensorrt/中的 runtime。

相关新闻

Python办公自动化实战:基于python-docx与docxtpl的公文批量排版解决方案

Python办公自动化实战:基于python-docx与docxtpl的公文批量排版解决方案

1. 项目概述:为什么用Python处理公文排版?如果你在体制内单位、大型企业或者任何需要处理大量正式文档的部门工作过,一定对“公文排版”这四个字深有感触。那不仅仅是调调字体和字号,而是一套严谨到近乎苛刻的格式规范&#xff1a…

2026/11/2 2:17:14 阅读更多 →
2026岳阳黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐

2026岳阳黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐

岳阳黄金白银铂金回收2026实测榜单|公安工商双备案无损测金无折旧费门店 在岳阳这座历史悠久的城市,贵金属回收店铺遍地丛生,行业套路层出不穷,不少市民变现遭遇虚高报价、克扣损耗、未经同意熔金压价等问题。为帮助本地居民规避消…

2026/10/30 11:47:22 阅读更多 →
2026云浮黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐

2026云浮黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐

2026云浮黄金白银铂金回收实测榜单|公安工商双备案无损测金无折旧费门店 云浮黄金回收哪家靠谱|工商公安双备案中检认证实体门店 云浮本地贵金属回收店铺遍地丛生,行业套路层出不穷,不少市民变现遭遇虚高报价、克扣损耗、未经同意…

2026/10/31 12:31:26 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/3 1:09:25 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/2 10:46:11 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/2 10:47:40 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/3 2:27:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/2 10:46:31 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/2 10:46:26 阅读更多 →

日新闻

周新闻

月新闻