深度解析 Apache TVM Relay 测试工具集 tvm.relay.testing:网络构建、梯度校验与基准 Workload 全指南
2026/9/23 10:37:54 网站建设 项目流程
  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

tvm.relay.testing是 Apache TVM(本仓库为gh_mirrors/tvm7/tvm)中面向Relay IR 测试与基准(benchmark)的官方工具包,其模块 docstring 即 "Utilities for testing and benchmarks"。本文以 docs/reference/api/python/relay/testing.rst 为主线,深入其背后源码(python/tvm/relay/testing),系统讲解run_opt_passrun_infer_typecheck_grad等通用工具,create_workload参数初始化机制,以及 MLP、ResNet、MobileNet、Inception-V3、SqueezeNet、VGG、DenseNet、DCGAN、LSTM 等经典网络的 Relay 构建 API。读完本文,你将能够:直接用一行get_workload()获得带随机权重的(IRModule, params)用于测试与调优、为自定义 Relay 算子编写数值梯度校验、在仓库测试中复用整套网络构造基建。

一、tvm.relay.testing 的整体定位与包结构

tvm.relay.testing不是一个"单文件工具",而是一个聚合了多种能力的 Python 子包。从 python/tvm/relay/testing/init.py 可以看出,它在导入时即完成以下三方面组织:

  • 经典网络模型模块mlpresnetresnet_3ddqndcganmobilenetlstminception_v3squeezenetvggdensenetyolo_detection
  • 辅助/基础设施模块init(参数初始化与create_workload)、layers(Relay 层 DSL 封装)、temp_op_attr(临时算子属性上下文)、synthetic(合成数据生成)、nat(自然数编码,供 Prelude 测试)、py_converter(Relay 表达式转 Python 解释执行);
  • 顶层再导出create_workloadcount/make_nat_value/make_nat_exprto_python/run_as_python,以及从tvm.relay.transform再导出的gradient,用于配合check_grad做自动微分梯度校验。

也就是说,文档中由 Sphinxautomodule指令渲染出的tvm.relay.testing及其mlp/resnet/dcgan/mobilenet/lstm/inception_v3/squeezenet/vgg/densenet子模块页面,其真实内容全部来自上述源码中的函数 docstring 与公开 API。下文的每一个 API 均可通过import tvm.relay.testing as relay_testing(或from tvm import relayrelay.testing.xxx)直接使用。

二、通用测试工具:Pass 执行、类型推断与梯度校验

2.1run_opt_pass:在 IRModule 上执行单个 Pass

run_opt_pass(expr, opt_pass, import_prelude=False)是测试中高频使用的入口,其实现位于 python/tvm/relay/testing/init.py:

def run_opt_pass(expr, opt_pass, import_prelude=False): assert isinstance(opt_pass, tvm.transform.Pass) mod = tvm.IRModule.from_expr(expr) if import_prelude: Prelude(mod) mod = relay.transform.InferType()(mod) mod = opt_pass(mod) entry = mod["main"] return entry if isinstance(expr, relay.Function) else entry.body

其执行链条为:IRModule.from_expr包装表达式 → 可选导入Prelude(涉及 ADT/列表等高层结构时使用)→ 先运行InferType保证类型完整 → 再运行目标 Pass → 最终取出main函数;若输入是relay.Function返回整个函数,否则返回main.body。由于它要求opt_pass必须是tvm.transform.Pass实例,因此在使用时需注意传入的是 Pass 对象而非函数名,例如run_opt_pass(func, relay.transform.Legalize())

2.2run_infer_type:快速类型推断

def run_infer_type(expr): return run_opt_pass(expr, relay.transform.InferType())

它是run_opt_pass的特化:仅做类型推断,不执行任何改写。在测试动态算子(dynamic op)时极其常用,例如 tests/python/relay/dyn/test_dynamic_op_level10.py 与 tests/python/relay/dyn/test_dynamic_op_level2.py 中均通过zz = run_infer_type(z)来验证动态算子的输出checked_type

2.3check_grad:解析梯度与数值梯度的双侧逼近校验

check_grad是整个工具包中最具"含金量"的测试函数,完整签名如下(源码位置):

def check_grad(func, inputs=None, test_inputs=None, eps=1e-6, atol=1e-5, rtol=1e-3, scale=None, mean=0, mode="higher_order", target_devices=None, executor_kind="debug")

它的工作流程(实现细节):

  1. 对输入函数先后执行InferTypegradient(自动微分,mode参数控制微分模式),再跑Legalize得到反向函数;
  2. 若未提供inputs,则依据参数checked_type用正态随机数生成输入,且默认把标准差scale设为10 * eps,使随机输入与 epsilon 同量级、避免数值精度损失(对应_np_randn_from_type的实现);
  3. test_inputs用于只对部分输入做梯度匹配——这对动态算子中不可微的符号输入尤为重要;未指定时默认等于全部inputs
  4. 遍历target_devices(默认tvm.testing.enabled_targets(),见 python/tvm/testing 相关实现)上的(target, dev),用relay.create_executor(executor_kind, device=dev, target=target)分别求值前后向函数,得到解析梯度;
  5. 对每个测试输入的每个元素做+eps/-eps双侧差分(f(x+eps)-f(x-eps))/(2*eps)得到数值梯度;
  6. 最后用np.testing.assert_allclose(grad, approx_grad, atol=atol, rtol=rtol)比较。

仓库测试中的典型用法(tests/python/relay/dyn/test_dynamic_op_level3.py):

check_grad(run_infer_type(func), inputs=[x_data, y_data], eps=1e-3)

注意check_grad的 docstring 特别提醒:若函数输入类型精度不足(如 float16),该测试可能失败,因此默认atol=1e-5, rtol=1e-3需要针对所选eps与输入量级相应调整。

2.4randcount_ops:随机张量与算子计数

  • rand(dtype, *shape):一行生成指定 dtype 与形状的tvm.nd.array随机张量(源码);
  • count_ops(expr):内部实现一个继承tvm.relay.ExprVisitorOpCounter,重写visit_call统计图中每个 op 的调用次数并返回collections.Counter(源码),适合在 pass 改写前后对比算子数量。

三、基准 Workload 的基石:create_workload 与参数初始化

所有模型模块的get_workload()最终都调用同一个函数create_workload(net, initializer=None, seed=0),其定义位于 python/tvm/relay/testing/init.py:

def create_workload(net, initializer=None, seed=0): mod = tvm.IRModule.from_expr(net) mod = relay.transform.InferType()(mod) shape_dict = {v.name_hint: v.checked_type for v in mod["main"].params} np.random.seed(seed) initializer = initializer if initializer else Xavier() params = {} for k, v in shape_dict.items(): if k == "data": continue init_value = np.zeros(v.concrete_shape).astype(v.dtype) initializer(k, init_value) params[k] = tvm.nd.array(init_value, device=tvm.cpu(0)) return mod, params

关键语义有三点:

  1. 返回值为(mod, params)二元组mod是带类型信息的tvm.IRModuleparams是名字到NDArray的字典,可直接交给relay.build/ VM / AutoTVM 图调优器使用;
  2. data输入不参与初始化:名为"data"的参数被跳过,因为它代表模型输入而非权重;
  3. 初始化器按参数名后缀分派Initializer.__call__依据desc是否以weight/bias/gamma/beta/mean/var结尾,分别调用_init_weight/_init_bias/_init_gamma/_init_beta/_init_mean/_init_var(init.py),默认规则为:bias/beta/mean 置 0,gamma/var 置 1,weight 由子类实现。

包内自带两种权重初始化器:

  • Xavier(rnd_type="uniform", factor_type="avg", magnitude=3):默认初始化器。按 fan_in/fan_out 计算scale = sqrt(magnitude/factor)并在[-scale, scale]均匀采样;其内部还有一个针对 MobileNet 的细节——当参数名含"depthwise"factor = hw_scale,因为深度可分离卷积连接更稀疏(init.py);
  • Constant():把矩阵所有元素置为1/num_elements,使权重和归一为 1(init.py)。

四、layers.py:快速搭建网络的 Layer DSL 封装

多个模型模块复用了 python/tvm/relay/testing/layers.py 中的轻量封装,它们的作用是"自动创建带名字的权重 Var",避免每个模型手写大量relay.var

函数行为
conv2d(data, weight=None, **kwargs)未给weight时自动创建relay.var(name + "_weight")
conv3d(...)3D 卷积同理
conv2d_transpose(...)转置卷积,DCGAN 反卷积上采样使用
batch_norm_infer(...)自动创建_gamma/_beta/_moving_mean/_moving_var四个 Var,并只返回 batch_norm 的第一个输出(归一化结果)
dense_add_bias(data, weight, bias, units, ...)dense+bias_add(axis=-1)组合
conv_kernel_layout(data_layout, is_depthwise=False)布局映射:NCHW→OIHW、NHWC→HWIO;depthwise 时 NHWC→HWOI

这些封装对统一布局处理至关重要:模型可以传layout="NCHW""NHWC",卷积核布局随之切换,从而支持在内存布局偏好不同的后端上构造同一网络。

五、经典 CNN 模型构造 API 详解

5.1mlp:极简多层感知机(MNIST 默认配置)

python/tvm/relay/testing/mlp.py 提供get_net(batch_size, num_classes=10, image_shape=(1, 28, 28), dtype="float32")。网络结构为:batch_flattendense(128)reludense(64)reludense(num_classes)softmaxget_workloadget_net参数完全一致,默认即 MNIST 场景(1×28×28 灰度图、10 类)。

5.2resnet:支持 18/34/50/101/152/200/269 层

python/tvm/relay/testing/resnet.py 实现了 He 等提出的 ResNet 结构。核心 API:

  • residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, data_layout="NCHW", kernel_layout="IOHW"):残差单元,bottle_neck=True时采用 1×1→3×3→1×1 瓶颈结构,dim_match决定 shortcut 是否走 1×1 卷积投影;
  • get_net(batch_size, num_classes, num_layers=50, image_shape=(3,224,224), layout="NCHW", dtype="float32")
  • get_workload(batch_size=1, num_classes=1000, num_layers=18, image_shape=(3,224,224), ...)

实现中有两处值得注意的自动分支逻辑(resnet.py):

  1. 按输入高度分流:当height <= 32(典型如 CIFAR-10 的 32×32)时,卷积核从 7×7/stride 2 降为 3×3/stride 1,且 stage 数为 3、filter 列表变为[16,16,32,64][16,64,128,256]height > 32(如 ImageNet 的 224)时走标准 4-stage 配置,filter 列表为[64,256,512,1024,2048](bottleneck)或[64,64,128,256,512](非 bottleneck);
  2. 按层数校验配置:小图场景仅接受(num_layers-2) % 9 == 0(≥164 层,bottleneck)或(num_layers-2) % 6 == 0(<164 层)的层数,否则抛出ValueError;大图场景则精确匹配 18/34/50/101/152/200/269 的 units 表(如 50 层对应[3,4,6,3],101 层对应[3,4,23,3])。

5.3mobilenet:深度可分离卷积 + 宽度因子 alpha

python/tvm/relay/testing/mobilenet.py 将 NNVM 版 MobileNet 移植到 Relay。mobile_net(num_classes=1000, data_shape=(1,3,224,224), dtype="float32", alpha=1.0, is_shallow=False, layout="NCHW")是底层构造函数:

  • conv_block:标准 Conv+BN+ReLU;
  • separable_conv_block:Depthwise Conv(groups=depthwise_channels,权重形状 NCHW 下为(C,1,kh,kw))+ BN + ReLU,再接 1×1 Pointwise Conv + BN + ReLU,downsample=True时 stride=2;
  • alpha作为宽度因子缩放所有通道数(如int(32*alpha));
  • is_shallow=True时走 8 个 separable block 的浅层版本,否则走 13 个 block 的标准版本。

get_workload(batch_size=1, num_classes=1000, image_shape=(3,224,224), dtype="float32", layout="NCHW")固定使用alpha=1.0, is_shallow=False。仓库中 tests/python/contrib/test_clml/test_compiler.py 即以relay.testing.mobilenet.get_workload(batch_size=1)构造编译输入。

5.4inception_v3:五阶段 Inception 模块

python/tvm/relay/testing/inception_v3.py 面向约 299×299 输入,定义了Inception7A/7B/7C/7D/7E五类模块:

  • Inception7A:1×1、5×5、双层 3×3 三个卷积塔 + 池化塔的 concatenate;
  • Inception7B:首个下采样块,stride 2 卷积与 max-pool 拼接;
  • Inception7C:引入 1×7/7×1 非对称分解(num_d7_*num_q7_*两组);
  • Inception7D:第二下采样块;
  • Inception7E:3×3 分解为 1×3+3×1 的双分支结构。

get_workload(batch_size=1, num_classes=1000, image_shape=(3,299,299), dtype="float32")conv(32, s2) → conv(32) → conv(64) → maxpool → conv(80) → conv(192) → maxpool → mixed(mixed_0..mixed_10)顺序组装(inception_v3.py),最后接 8×8 平均池化、flatten、dense 与 softmax。注意与其它 CNN 不同,此模块的get_net签名中image_shapedtype无默认值,需显式传入。

5.5squeezenet:Fire 模块与 1.0/1.1 双版本

python/tvm/relay/testing/squeezenet.py 以 Fire 模块(squeeze 1×1 + 并行 expand 1×1/3×3)为基础。get_net(batch_size, image_shape, num_classes, version, dtype)中:

  • version="1.0":首层 96 通道 7×7 卷积,fire1-fire8 采用(16,64,64)/(32,128,128)/(48,192,192)/(64,256,256)通道组合;
  • version="1.1":首层 64 通道 3×3 卷积,squeeze 通道保持 16/32/48/64 且在第 2、4、6 个 fire 后插入 max-pool;
  • 二者都会校验版本值,非法值直接AssertionError
  • 尾部统一为dropout(0.5) → 1×1 conv(num_classes) → relu → global_avg_pool2d → flatten → softmax

get_workload(batch_size=1, num_classes=1000, version="1.0", image_shape=(3,224,224), dtype="float32")

5.6vgg:11/13/16/19 层规格表驱动

python/tvm/relay/testing/vgg.py 内置规格表:

vgg_spec = { 11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]), 13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]), 16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]), 19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512]), }

get_feature逐 stage 堆叠 3×3 卷积 + ReLU(可选 BN)+ 2×2 max-pool;get_classifierflatten → fc6(4096) → relu → dropout(0.5) → fc7(4096) → relu → dropout(0.5) → fc8(num_classes)get_net(batch_size, image_shape, num_classes, dtype, num_layers=11, batch_norm=False),层数非法时报ValueErrorget_workload需显式传batch_size

5.7densenet:密集连接块 + 过渡层

python/tvm/relay/testing/densenet.py 实现 DenseNet:

  • _make_dense_layer:BN→ReLU→1×1 Conv(bn_size * growth_rate通道)→BN→ReLU→3×3 Conv(growth_rate通道);
  • _make_dense_block:堆叠多个 dense layer 后沿通道维concatenate
  • _make_transition:1×1 卷积减半通道 + 2×2 平均池化;
  • _make_dense_net(num_init_features, growth_rate, block_config, data_shape, data_dtype, bn_size=4, classes=1000):7×7 卷积 + 3×3 maxpool 起步,按block_config循环构建,最后一个 block 之后是 7×7 平均池化 + flatten + dense。

get_workload(densenet_size=121, classes=1000, batch_size=4, image_shape=(3,224,224), dtype="float32"),注意其默认batch_size=4,与其余模型默认 1 不同。densenet_size会换算为对应的 growth_rate 与 block_config(常见为 121/161/169/201)。

六、生成式与序列模型:DCGAN 生成器与 LSTM

6.1dcgan:仅支持 64×64 输出的反卷积生成器

python/tvm/relay/testing/dcgan.py 基于 Radford 等的 DCGAN 论文实现生成器网络:

  • 输入:(batch_size, random_len)的随机噪声(random_len默认 100);
  • 主干:dense 到4*4*ngf*8通道 → reshape 到 4×4 特征图 → 依次经过deconv2d_bn_relu(4×4 核、stride 2,带 BN+ReLU)把分辨率逐级放大到 8×8 → 16×16 → 32×32 → 64×64,最后deconv2d+tanh输出;
  • deconv2d内部按目标形状自动推导output_paddingadj_y/adj_x),保证输出尺寸精确匹配;
  • 前置断言oshape[-1] == 64 and oshape[-2] == 64只支持 64×64 输出

get_workload(batch_size, oshape=(3,64,64), ngf=128, random_len=100, layout="NCHW", dtype="float32")

6.2lstm:ScopeBuilder 构建的 LSTM Cell 与展开 RNN

python/tvm/relay/testing/lstm.py 提供:

  • lstm_cell(num_hidden, batch_size=1, dtype="float32", name=""):用relay.ScopeBuilder构造单步 cell。输入为(inputs, states, i2h_weight, i2h_bias, h2h_weight, h2h_bias),其中states(h, c)二元组;内部计算i2h = dense(inputs)h2h = dense(h)gates = i2h + h2h,对 4 份num_hidden宽的输出用relay.split(..., 4)切成输入门/遗忘门/候选/输出门,更新next_cnext_h,最终返回(next_h, (next_h, next_c))
  • get_net(iterations, num_hidden, batch_size=1, dtype="float32"):把iterations个 LSTM cell 顺序展开(unroll),每个时间步使用独立的i2h_{i}_weight等权重变量;
  • get_workload(iterations, num_hidden, batch_size=1, dtype="float32"):同样参数,返回(mod, params)

这个模块是理解 Relay 中元组类型、ScopeBuilder 显式 let 绑定、split/astuple 组合的极佳教学样例。

七、在仓库测试与工具链中的真实落地

tvm.relay.testing并非孤立代码,仓库中大量测试与上层工具直接依赖它:

  • AutoTVM 图调优:tests/python/autotvm/test_autotvm_graph_tuner_core.py 中多处使用relay.testing.create_workload(net)把自建网络转为带参数的模块,再交给图调优器;
  • 梯度校验链路:tests/python/relay/dyn/test_dynamic_op_level3.py 用check_grad(run_infer_type(func), inputs=[...], eps=1e-3)验证动态算子的反向传播正确性;
  • AOT / CRT 集成:tests/python/relay/aot/test_crt_aot.py 使用tvm.relay.testing.byoc(BYOC 编译注解工具)构造自定义代码生成场景;
  • 算子属性测试:tests/python/relay/test_pass_legalize.py 借助TempOpAttr临时覆盖算子属性以驱动 pass 行为;
  • Ethos-U 系列测试:tests/python/contrib/test_ethosu 多个用例从tvm.relay.testing导入run_opt_pass,对模型做 pass 预处理后验证硬件相关改写。

这证明tvm.relay.testing实际承担了"模型仓库 + 测试夹具 + 基准数据源"三重角色:既服务于tests/python下数千个用例,也为 apps/benchmark 等上层基准脚本提供统一的网络构造入口。

八、快速上手:一份可直接运行的基准脚本骨架

综合上述 API,一个典型的 benchmark 使用方式如下(以 MobileNet 为例):

from tvm import relay from tvm.relay.testing import mobilenet # 1. 构造 workload:获得带 Xavier 初始化权重的 IRModule 与参数 mod, params = mobilenet.get_workload(batch_size=1, num_classes=1000) # 2. 查看图结构与算子统计 expr = mod["main"] print(expr) # Relay 图文本表示 # 3. 类型推断与 pass 处理 from tvm.relay.testing import run_opt_pass typed = run_opt_pass(expr, relay.transform.InferType()) # 4. 编译到目标后端 target = "llvm" with tvm.transform.PassContext(opt_level=3): lib = relay.build(mod, target=target, params=params)

若需要自建网络并接入同一套工具,只需按get_net的模式返回relay.Function,再调用relay.testing.create_workload(net)即可无缝获得(mod, params)

九、小结

  • 一句话定位tvm.relay.testing是 TVM Relay 的"模型库 + 测试工具包",以get_workload()家族提供十余种经典网络的可编译(IRModule, params),以create_workload/Xavier提供统一参数初始化,以run_opt_pass/run_infer_type/check_grad提供 Pass 测试与数值梯度校验基建。
  • 易踩的坑check_grad对低精度输入敏感、inception_v3.get_net参数无默认值、densenet.get_workload默认 batch_size=4、dcgan仅支持 64×64、resnet的层数/图像高度存在严格组合校验——实际使用前建议核对各模块的 docstring。
  • 延伸阅读:所有 API 的权威签名与说明见 docs/reference/api/python/relay/testing.rst,对应实现可直查 python/tvm/relay/testing 目录下的同名模块文件。
  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询