- 编译器
- 深度学习
- 模型优化
【免费下载链接】tvm
Open deep learning compiler stack for cpu, gpu and specialized accelerators
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_pass、run_infer_type、check_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 可以看出,它在导入时即完成以下三方面组织:
- 经典网络模型模块:
mlp、resnet、resnet_3d、dqn、dcgan、mobilenet、lstm、inception_v3、squeezenet、vgg、densenet、yolo_detection; - 辅助/基础设施模块:
init(参数初始化与create_workload)、layers(Relay 层 DSL 封装)、temp_op_attr(临时算子属性上下文)、synthetic(合成数据生成)、nat(自然数编码,供 Prelude 测试)、py_converter(Relay 表达式转 Python 解释执行); - 顶层再导出:
create_workload、count/make_nat_value/make_nat_expr、to_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 relay后relay.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")它的工作流程(实现细节):
- 对输入函数先后执行
InferType与gradient(自动微分,mode参数控制微分模式),再跑Legalize得到反向函数; - 若未提供
inputs,则依据参数checked_type用正态随机数生成输入,且默认把标准差scale设为10 * eps,使随机输入与 epsilon 同量级、避免数值精度损失(对应_np_randn_from_type的实现); test_inputs用于只对部分输入做梯度匹配——这对动态算子中不可微的符号输入尤为重要;未指定时默认等于全部inputs;- 遍历
target_devices(默认tvm.testing.enabled_targets(),见 python/tvm/testing 相关实现)上的(target, dev),用relay.create_executor(executor_kind, device=dev, target=target)分别求值前后向函数,得到解析梯度; - 对每个测试输入的每个元素做
+eps/-eps双侧差分(f(x+eps)-f(x-eps))/(2*eps)得到数值梯度; - 最后用
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.4rand与count_ops:随机张量与算子计数
rand(dtype, *shape):一行生成指定 dtype 与形状的tvm.nd.array随机张量(源码);count_ops(expr):内部实现一个继承tvm.relay.ExprVisitor的OpCounter,重写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关键语义有三点:
- 返回值为
(mod, params)二元组:mod是带类型信息的tvm.IRModule,params是名字到NDArray的字典,可直接交给relay.build/ VM / AutoTVM 图调优器使用; data输入不参与初始化:名为"data"的参数被跳过,因为它代表模型输入而非权重;- 初始化器按参数名后缀分派:
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_flatten→dense(128)→relu→dense(64)→relu→dense(num_classes)→softmax。get_workload与get_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):
- 按输入高度分流:当
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); - 按层数校验配置:小图场景仅接受
(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_shape与dtype无默认值,需显式传入。
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_classifier为flatten → 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),层数非法时报ValueError;get_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_padding(adj_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_c与next_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
相关推荐
终极指南:如何使用图神经网络基准测试工具评估GNN性能
终极指南:如何使用图神经网络基准测试工具评估GNN性能 图神经网络(GNN)已成为处理图结构数据的强大工具,但选择合适的GNN模型和参数配置常常让研究者和开发者
MXNet Python 测试工具库 mxnet.test_utils 完全指南:数值校验、梯度验证与测试数据生成
MXNet Python 测试工具库 mxnet.test_utils 完全指南:数值校验、梯度验证与测试数据生成 导读 mxnet.test_utils 是
人工智能深度学习机器学习2025年最全面的网络基准测试工具深度解析:ECSSpeed如何解决跨国网络测速痛点
2025年最全面的网络基准测试工具深度解析:ECSSpeed如何解决跨国网络测速痛点 你是否还在为选择合适的网络测试工具而烦恼?面对复杂的服务器节点配置、频繁的
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考