框架模型层

运行过程与结果与NV相同

实现了一个使用tvm库进行矩阵乘法的程序。该程序在设备上执行矩阵乘法运算,并测量其性能。

  • 包含必要的库和头文件,包括运行库和辅助函数
  • 定义矩阵乘法的维度: 设置矩阵 (A) 的大小为 (320* 640),矩阵 (B) 的大小为 (640* 320)。
  • 构建计算图:使用 te.placeholder 定义输入矩阵 (A) 和 (B)。使用 te.compute 定义输出矩阵 (C) 的计算逻辑,利用 te.sum 进行矩阵乘法。
  • 创建调度:使用 te.create_schedule 创建调度,并为 GPU 设置线程和块的调度。使用 s[C].splits[C].bind 将计算任务分配到不同的 GPU 线程和块。
  • 构建和运行函数 build_and_run:编译计算图为可执行的函数,并为输入矩阵分配随机数据。在设备上分配内存,创建 TVM 数组。计算 FLOPs,并在循环中执行矩阵乘法多次以计时。
  • 计算性能指标:计算总运行时间和每秒浮点运算次数 (GFLOPS),并输出结果。
  • 执行代码: 调用 build_and_run 函数在 GPU 上执行矩阵乘法,并打印计算图的简化模式。

代码:

import tvm
from tvm import te
import numpy as np
import time

# 定义矩阵乘法的大小
M = 320
N = 640
K = 320

# 定义矩阵乘法
A = te.placeholder((M, N), name='A')
B = te.placeholder((N, K), name='B')
k = te.reduce_axis((0, N), name='k')
C = te.compute((M, K), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name='C')

# 创建调度
s = te.create_schedule(C.op)

# GPU 线程调度
block_x = te.thread_axis("blockIdx.x")
block_y = te.thread_axis("blockIdx.y")
thread_x = te.thread_axis("threadIdx.x")
thread_y = te.thread_axis("threadIdx.y")

# 为 GPU 添加块和线程的调度
bx, tx = s[C].split(C.op.axis[0], factor=32)
by, ty = s[C].split(C.op.axis[1], factor=32)
s[C].bind(bx, block_x)
s[C].bind(by, block_y)
s[C].bind(tx, thread_x)
s[C].bind(ty, thread_y)

# 定义函数
def build_and_run(target_device="rocm", num_repeats=300):
    # 编译
    target = tvm.target.Target(target_device)
    f = tvm.build(s, [A, B, C], target=target, name='matmul')

    # 创建输入数据
    a_np = np.random.uniform(-1, 1, size=(M, N)).astype(np.float32)
    b_np = np.random.uniform(-1, 1, size=(N, K)).astype(np.float32)
    c_np = np.zeros((M, K), dtype=np.float32)

    # 在设备上分配内存
    dev = tvm.device(target_device, 0)
    a_tvm = tvm.nd.array(a_np, dev)
    b_tvm = tvm.nd.array(b_np, dev)
    c_tvm = tvm.nd.array(c_np, dev)

    # 计算 FLOPs(2 * M * N * K)
    flops = 2 * M * N * K
    
    # 运行并计时
    start_time = time.time()
    for i in range(num_repeats):
        f(a_tvm, b_tvm, c_tvm)
    dev.sync()  # 保证所有计算都已完成
    end_time = time.time()

    # 计算总时间和 GFLOPS
    total_time = end_time - start_time
    gflops = (flops * num_repeats) / (total_time * 1e9)

    # 输出结果
    print(f"Execution on {target_device} completed in {total_time:.4f} seconds for {num_repeats} iterations.")
    print(f"FLOPs: {flops} per matrix multiplication")
    print(f"GFLOPS: {gflops:.2f} GFLOPS")

# 在 GPU 上执行
build_and_run(target_device="rocm")

结果:

Execution on rocm completed in 0.1786 seconds for 300 iterations.
FLOPs: 131072000 per matrix multiplication
GFLOPS: 220.18 GFLOPS

实现了一个使用 TVM 的Auto-scheduling 进行算子优化。

  • 定义一个带有偏置加法的矩阵乘法。这里使用了 TVM 张量表达式语言中的标准操作。区别在于函数定义上方使用了 register_workload 装饰器。该函数应返回输入/输出张量列表。通过这些张量,auto-scheduler 可以得到整个计算图。
  • 定义函数后,可以为 auto_scheduler 创建要搜索的任务。为这个矩阵乘法指定了特定的参数,如这里是两个大小为 1024x1024 的矩阵乘法。然后创建一个 N=L=M=1024 和 dtype="float32" 的搜索任务
  • num_measure_trials 表示搜索过程中可用的测试试验次数。用 RecordToFile 将测试记录记录到文件 matmul.json 中。测试记录可用于查询历史最佳、恢复搜索以及以后进行更多分析。
  • auto-scheduling 完成后,可将 schedule 降级来查看 IR。auto-scheduler 执行合适的优化,包括多级循环切分、布局转换、并行化、向量化、循环展开和算子融合。

代码:

import logging
import sys
import numpy as np
import tvm
from tvm import te
import tvm.testing

from tvm import autotvm
@auto_scheduler.register_workload  # Note the auto_scheduler decorator
def matmul_add(N, L, M, dtype):
    A = te.placeholder((N, L), name="A", dtype=dtype)
    B = te.placeholder((L, M), name="B", dtype=dtype)
    C = te.placeholder((N, M), name="C", dtype=dtype)

    k = te.reduce_axis((0, L), name="k")
    matmul = te.compute(
        (N, M),
        lambda i, j: te.sum(A[i, k] * B[k, j], axis=k),
        name="matmul",
        attrs={"layout_free_placeholders": [B]},  # enable automatic layout transform for tensor B
    )
    out = te.compute((N, M), lambda i, j: matmul[i, j] + C[i, j], name="out")

    return [A, B, C, out]
target = tvm.target.Target("llvm")
N = L = M = 1024
task = tvm.auto_scheduler.SearchTask(func=matmul_add, args=(N, L, M, "float32"), target=target)

# 检查计算图
print("Computational DAG:")
print(task.compute_dag)
log_file = "matmul.json"
tune_option = auto_scheduler.TuningOptions(
    num_measure_trials=10,
    measure_callbacks=[auto_scheduler.RecordToFile(log_file)],
    verbose=2,
)
# 运行 auto-tuning(搜索)
task.tune(tune_option)
# 应用最佳 schedule
sch, args = task.apply_best(log_file)
print("Lowered TIR:")
print(tvm.lower(sch, args, simple_mode=True))

结果:

Computational DAG:
A = PLACEHOLDER [1024, 1024]
B = PLACEHOLDER [1024, 1024]
matmul(i, j) += (A[i, k]*B[k, j])
C = PLACEHOLDER [1024, 1024]
out(i, j) = (matmul[i, j] + C[i, j])
Lowered TIR:
@main = primfn(A_1: handle, B_1: handle, C_1: handle, out_1: handle) -> ()
  attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
  buffers = {A: Buffer(A_2: Pointer(float32), float32, [1048576], []),
             B: Buffer(B_2: Pointer(float32), float32, [1048576], []),
             C: Buffer(C_2: Pointer(float32), float32, [1048576], []),
             out: Buffer(out_2: Pointer(float32), float32, [1048576], [])}
  buffer_map = {A_1: A, B_1: B, C_1: C, out_1: out}
  preflattened_buffer_map = {A_1: A_3: Buffer(A_2, float32, [1024, 1024], []), B_1: B_3: Buffer(B_2, float32, [1024, 1024], []), C_1: C_3: Buffer(C_2, float32, [1024, 1024], []), out_1: out_3: Buffer(out_2, float32, [1024, 1024], [])} {
  allocate(auto_scheduler_layout_transform: Pointer(global float32), float32, [1048576]), storage_scope = global {
    for (ax0.ax1.fused.ax2.fused: int32, 0, 128) "parallel" {
      for (ax4: int32, 0, 256) {
        for (ax6: int32, 0, 4) {
          for (ax7: int32, 0, 8) {
            auto_scheduler_layout_transform_1: Buffer(auto_scheduler_layout_transform, float32, [1048576], [])[((((ax0.ax1.fused.ax2.fused*8192) + (ax4*32)) + (ax6*8)) + ax7)] = B[((((ax4*4096) + (ax6*1024)) + (ax0.ax1.fused.ax2.fused*8)) + ax7)]
          }
        }
      }
    }
    for (i.outer.outer.j.outer.outer.fused: int32, 0, 16384) "parallel" {
      allocate(matmul: Pointer(global float32x8), float32x8, [4]), storage_scope = global;
      for (i.outer.inner: int32, 0, 2) {
        matmul_1: Buffer(matmul, float32x8, [4], [])[0] = broadcast(0f32, 8)
        matmul_1[1] = broadcast(0f32, 8)
        matmul_1[2] = broadcast(0f32, 8)
        matmul_1[3] = broadcast(0f32, 8)
        for (k.outer: int32, 0, 256) {
          for (k.inner: int32, 0, 4) {
            let cse_var_2: int32 = (((floormod(i.outer.outer.j.outer.outer.fused, 128)*8192) + (k.outer*32)) + (k.inner*8))
            let cse_var_1: int32 = ((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (k.outer*4)) + k.inner)
             {
              matmul_1[0] = (matmul_1[0] + (broadcast(A[cse_var_1], 8)*auto_scheduler_layout_transform_1[ramp(cse_var_2, 1, 8)]))
              matmul_1[1] = (matmul_1[1] + (broadcast(A[(cse_var_1 + 1024)], 8)*auto_scheduler_layout_transform_1[ramp(cse_var_2, 1, 8)]))
              matmul_1[2] = (matmul_1[2] + (broadcast(A[(cse_var_1 + 2048)], 8)*auto_scheduler_layout_transform_1[ramp(cse_var_2, 1, 8)]))
              matmul_1[3] = (matmul_1[3] + (broadcast(A[(cse_var_1 + 3072)], 8)*auto_scheduler_layout_transform_1[ramp(cse_var_2, 1, 8)]))
            }
          }
        }
        for (i.inner: int32, 0, 4) {
          let cse_var_3: int32 = ((((floordiv(i.outer.outer.j.outer.outer.fused, 128)*8192) + (i.outer.inner*4096)) + (i.inner*1024)) + (floormod(i.outer.outer.j.outer.outer.fused, 128)*8))
          out[ramp(cse_var_3, 1, 8)] = (matmul_1[i.inner] + C[ramp(cse_var_3, 1, 8)])
        }
      }
    }
  }
}

实现了在 Relay 中定义神经网络,并为 GPU 生成 runtime 库。

  • 使用 Relay 框架定义了 ResNet18 神经网络模型,设定批量大小为 1,图像形状为 (3, 224, 224),输出类别数为 1000。
  • 输出 ResNet18 模型的计算图结构,show_meta_data=False 表示不显示元数据。
  • 设置优化级别为 3(包括算子融合、预计算、布局变换等优化),并指定GPU作为目标设备,编译生成可在 GPU 上执行的库。
  • 随机生成形状为 (1, 3, 224, 224) 的输入数据。创建一个执行模块,并将输入数据设置到模型中,然后运行模型并获取输出结果。输出结果中的前 10 个元素。
  • 使用 TVM 的 utils.tempdir 创建临时目录,并将编译后的计算图、库和参数保存为文件,以便于后续部署时使用。
  • 从保存的文件中加载编译模块,并使用相同的输入数据进行推理,获取输出结果。再次输出推理结果的前 10 个元素。
  • 使用 tvm.testing.assert_allclose 检查重新加载的模块输出与最初输出是否一致,容差设置为 1e-5。
import numpy as np
from tvm import relay
from tvm.relay import testing
import tvm
from tvm import te
from tvm.contrib import graph_executor
import tvm.testing

batch_size = 1
num_class = 1000
image_shape = (3, 224, 224)
data_shape = (batch_size,) + image_shape
out_shape = (batch_size, num_class)

# 获取 ResNet 模型
mod, params = relay.testing.resnet.get_workload(
    num_layers=18, batch_size=batch_size, image_shape=image_shape
)

# 为 AMD GPU (ROCm) 编译
opt_level = 3
target = tvm.target.rocm()  # 改为 rocm 目标
with tvm.transform.PassContext(opt_level=opt_level):
    lib = relay.build(mod, target, params=params)

# 创建图执行器,并在 AMD GPU 上运行该模块
# 创建随机输入
dev = tvm.rocm()  # 改为使用 ROCm 设备
data = np.random.uniform(-1, 1, size=data_shape).astype("float32")

# 创建模块
module = graph_executor.GraphModule(lib["default"](dev))

# 设置输入和参数
module.set_input("data", data)

# 运行
module.run()

# 获取输出
out = module.get_output(0, tvm.nd.empty(out_shape)).numpy()

# 打印输出的前 10 个元素
print(out.flatten()[0:10])

结果:

[0.00089283 0.00103331 0.0009094  0.00102275 0.00108751 0.00106737
 0.00106262 0.00095838 0.00110792 0.00113151]
# 创建随机输入
dev = tvm.rocm()  # 改为 ROCm 设备
data = np.random.uniform(-1, 1, size=data_shape).astype("float32")

# 创建图执行器模块
module = graph_executor.GraphModule(lib["default"](dev))

# 设置输入数据和参数
module.set_input("data", data)

# 在 AMD GPU 上运行
module.run()

# 获取输出
out = module.get_output(0, tvm.nd.empty(out_shape)).numpy()

# 打印输出的前 10 个元素
print(out.flatten()[0:10])

结果:

[0.00089283 0.00103331 0.0009094  0.00102275 0.00108751 0.00106737
 0.00106262 0.00095838 0.00110792 0.00113151]
# 保存和加载编译模块 分别将计算图、库和参数保存到不同文件

from tvm.contrib import utils

temp = utils.tempdir()
path_lib = temp.relpath("deploy_lib.tar")
lib.export_library(path_lib)
print(temp.listdir())

# 重新加载模块
loaded_lib = tvm.runtime.load_module(path_lib)
input_data = tvm.nd.array(data)
module = graph_executor.GraphModule(loaded_lib["default"](dev))
module.run(data=input_data)
out_deploy = module.get_output(0).numpy()

# 打印输出的前十个元素
print(out_deploy.flatten()[0:10])

# 检查来自部署模块的输出和原始输出是否一致
tvm.testing.assert_allclose(out_deploy, out, atol=1e-5)

结果:

['deploy_lib.tar']

[0.00089283 0.00103331 0.0009094  0.00102275 0.00108751 0.00106737
 0.00106262 0.00095838 0.00110792 0.00113151]

实现了将 ONNX 模型编译到 TVM Runtime并使用 TVMC 运行来自编译模块的模型

  • 从指定的 URL 下载图像,并保存为 imagenet_cat.png
  • 使用 PIL 库将下载的图像大小调整为 224x224,以适应标准的图像输入要求(例如 ResNet)。
  • 将图像数据从 HWC(Height-Width-Channel)格式转换为 NCHW(Channel-Height-Width)格式,这是 ONNX 模型的输入格式要求。
  • 根据 ImageNet 的标准化方法,对图像进行归一化处理,减去均值 imagenet_mean 并除以标准差 imagenet_stddev
  • 将图像数据扩展一个维度,以符合神经网络模型所需的 batch 大小格式 (batch, channel, height, width)。
  • 最终将预处理后的图像数据保存为 imagenet_cat.npz,用于后续推理。
  • 从指定的 URL 下载 ImageNet 的类别标签列表,并保存为 synset.txt
  • 从保存的 predictions.npz 文件中加载输出张量,该文件应是神经网络推理后的结果。
  • 使用 softmax 函数将模型的输出转化为概率分布。根据概率分数对输出进行排序,选出排名前 5 的类别,并打印它们的标签及对应的概率。
from tvm.contrib.download import download_testdata
from PIL import Image
import numpy as np

img_url = "https://s3.amazonaws.com/model-server/inputs/kitten.jpg"
img_path = download_testdata(img_url, "imagenet_cat.png", module="data")

# 重设大小为 224x224
resized_image = Image.open(img_path).resize((224, 224))
img_data = np.asarray(resized_image).astype("float32")

# ONNX 需要 NCHW 输入, 因此对数组进行转换
img_data = np.transpose(img_data, (2, 0, 1))

# 根据 ImageNet 进行标准化
imagenet_mean = np.array([0.485, 0.456, 0.406])
imagenet_stddev = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(img_data.shape).astype("float32")
for i in range(img_data.shape[0]):
      norm_img_data[i, :, :] = (img_data[i, :, :] / 255 - imagenet_mean[i]) / imagenet_stddev[i]

# 添加 batch 维度
img_data = np.expand_dims(norm_img_data, axis=0)

# 保存为 .npz(输出 imagenet_cat.npz)
np.savez("imagenet_cat", data=img_data)

import os.path
import numpy as np

from scipy.special import softmax

from tvm.contrib.download import download_testdata

# 下载标签列表
labels_url = "https://s3.amazonaws.com/onnx-model-zoo/synset.txt"
labels_path = download_testdata(labels_url, "synset.txt", module="data")

with open(labels_path, "r") as f:
    labels = [l.rstrip() for l in f]

output_file = "predictions.npz"

# 打开并读入输出张量
if os.path.exists(output_file):
    with np.load(output_file) as data:
        scores = softmax(data["output_0"])
        scores = np.squeeze(scores)
        ranks = np.argsort(scores)[::-1]

        for rank in ranks[0:5]:
            print("class='%s' with probability=%f" % (labels[rank], scores[rank]))

结果:

class='n02123045 tabby, tabby cat' with probability=0.621104
class='n02123159 tiger cat' with probability=0.356378
class='n02124075 Egyptian cat' with probability=0.019712
class='n02129604 tiger, Panthera tigris' with probability=0.001215
class='n04040759 radiator' with probability=0.000262

实现了使用 AutoTVM 在 TVM 中编译和优化 ONNX 模型。

  • 使用 onnx.load() 加载 ONNX 模型。

  • 下载一张图像并将其调整为 224x224 像素,这是 ResNet 等模型的标准输入大小。根据 ImageNet 的标准对图像进行归一化,并调整为 NCHW 格式。

  • 使用 Relay 前端编译模型,并指定目标架构( GPU)。

  • 构建模型并将其转换为图模块以便执行。

  • 使用 TVM 的运行时运行模型以获取预测结果,并使用 softmax 处理结果以获得每个类别的概率。

  • 使用 timeit 测量推理运行时间,并保存优化和未优化模型的结果。

  • 使用 TVM 的 AutoTVM 中的 XGBTuner 启动调优过程。

  • 设置调优选项并在从模型中提取的任务上运行调优。

  • 在调优后,使用在调优过程中找到的最佳配置重新构建模型,并验证优化模型的预测结果。

  • 打印优化模型和未优化模型的性能指标以进行比较。

    import onnx
    from tvm.contrib.download import download_testdata
    from PIL import Image
    import numpy as np
    import tvm.relay as relay
    import tvm
    from tvm.contrib import graph_executor
    
    model_url = (
        "https://github.com/onnx/models/raw/main/"
        "vision/classification/resnet/model/"
        "resnet50-v2-7.onnx"
    )
    
    model_path = download_testdata(model_url, "resnet50-v2-7.onnx", module="onnx")
    onnx_model = onnx.load(model_path)
    
    img_url = "https://s3.amazonaws.com/model-server/inputs/kitten.jpg"
    img_path = download_testdata(img_url, "imagenet_cat.png", module="data")
    
    # 重设大小为 224x224
    
    resized_image = Image.open(img_path).resize((224, 224))
    img_data = np.asarray(resized_image).astype("float32")
    
    # 输入图像是 HWC 布局,而 ONNX 需要 CHW 输入,所以转换数组
    
    img_data = np.transpose(img_data, (2, 0, 1))
    
    # 根据 ImageNet 输入规范进行归一化
    
    imagenet_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))
    imagenet_stddev = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))
    norm_img_data = (img_data / 255 - imagenet_mean) / imagenet_stddev
    
    # 添加 batch 维度,期望 4 维输入:NCHW。
    
    img_data = np.expand_dims(norm_img_data, axis=0)
    
    # 为 numpy 的 RNG 设置 seed,得到一致的结果
    
    np.random.seed(0)
    
    target = "rocm"
    # 可用 Netron 工具检查输入名称
    input_name = "data"
    shape_dict = {input_name: img_data.shape}
    
    mod, params = relay.frontend.from_onnx(onnx_model, shape_dict)
    
    with tvm.transform.PassContext(opt_level=3):
        lib = relay.build(mod, target=target, params=params)
    
    dev = tvm.device(str(target), 0)
    module = graph_executor.GraphModule(lib["default"](dev))
    
    #在 TVM Runtime 执行
    dtype = "float32"
    module.set_input(input_name, img_data)
    module.run()
    output_shape = (1, 1000)
    tvm_output = module.get_output(0, tvm.nd.empty(output_shape)).numpy()
    
    #收集基本性能数据
    import timeit
    timing_number = 10
    timing_repeat = 10
    unoptimized = (
        np.array(timeit.Timer(lambda: module.run()).repeat(repeat=timing_repeat, number=timing_number))
        * 1000
        / timing_number
    )
    unoptimized = {
        "mean": np.mean(unoptimized),
        "median": np.median(unoptimized),
        "std": np.std(unoptimized),
    }
    print(unoptimized)
    

    结果:

    class='n02123045 tabby, tabby cat' with probability=0.621103
    class='n02123159 tiger cat' with probability=0.356379
    class='n02124075 Egyptian cat' with probability=0.019712
    class='n02129604 tiger, Panthera tigris' with probability=0.001215
    class='n04040759 radiator' with probability=0.000262
    
    #调优模型
    import tvm.auto_scheduler as auto_scheduler
    from tvm.autotvm.tuner import XGBTuner
    from tvm import autotvm
    
    logging.basicConfig(level=logging.DEBUG)
    
    number = 10
    repeat = 1
    min_repeat_ms = 100  # 对于 GPU 设置为一个合理值,通常不为 0
    timeout = 10  # 秒
    
    # 创建 TVM 运行器,针对 GPU 不需要 CPU 缓存刷新
    runner = autotvm.LocalRunner(
        number=number,
        repeat=repeat,
        timeout=timeout,
        min_repeat_ms=min_repeat_ms,
        enable_cpu_cache_flush=False,  # GPU 不需要清空 CPU 缓存
    )
    
    # 使用 XGBoost 算法来指导搜索。对于 GPU 推荐 3000-4000 次试验
    tuning_option = {
        "tuner": "xgb",
        "trials": 4000,  # 对于 GPU 调优,推荐更高的试验次数
        "early_stopping": 800,  # 设置一个较大的早停值
        "measure_option": autotvm.measure_option(
            builder=autotvm.LocalBuilder(build_func="default"), 
            runner=runner
        ),
        "tuning_records": "resnet-50-v2-autotuning-gpu.json",  # 记录调优结果的文件
    }
    
    # 设置目标为rocm,表示 GPU
    target = "rocm"
    
    # 从 onnx 模型中提取任务
    tasks = autotvm.task.extract_from_program(mod["main"], target=target, params=params)
    
    # 按顺序调优提取的任务
    for i, task in enumerate(tasks):
        prefix = "[Task %2d/%2d] " % (i + 1, len(tasks))
    
        # 选择 XGBoost 调优器
        tuner = "xgb"
    
        # 创建调优器
        if tuner == "xgb":
            tuner_obj = XGBTuner(task, loss_type="reg")
        else:
            raise ValueError("Invalid tuner: " + tuner)
    
        # 开始调优
        tuner_obj.tune(
            n_trial=min(tuning_option["trials"], len(task.config_space)),
            early_stopping=tuning_option["early_stopping"],
            measure_option=tuning_option["measure_option"],
            callbacks=[
                autotvm.callback.progress_bar(tuning_option["trials"], prefix=prefix),
                autotvm.callback.log_to_file(tuning_option["tuning_records"]),
            ],
        )
    

    结果:

    [Task 25/25]  Current/Best:    0.00/   0.00 GFLOPS | Progress: (0/20) | 0.00 s
    [Task 25/25]  Current/Best:    1.56/   2.93 GFLOPS | Progress: (4/20) | 9.63 s
    [Task 25/25]  Current/Best:    5.65/   7.64 GFLOPS | Progress: (8/20) | 18.43 s
    [Task 25/25]  Current/Best:    5.95/   7.64 GFLOPS | Progress: (12/20) | 29.31 s
    [Task 25/25]  Current/Best:    5.80/   9.36 GFLOPS | Progress: (16/20) | 36.11 s
    [Task 25/25]  Current/Best:    2.94/   9.36 GFLOPS | Progress: (20/20) | 51.33 s
    
    #使用调优数据编译优化模型,获取存储在 resnet-50-v2-autotuning.json(上述调优过程的输出文件)中的调优记录
    with autotvm.apply_history_best(tuning_option["tuning_records"]):
        with tvm.transform.PassContext(opt_level=3, config={}):
            lib = relay.build(mod, target=target, params=params)
    
    dev = tvm.device(str(target), 0)
    module = graph_executor.GraphModule(lib["default"](dev))
    
    #验证优化模型是否运行并产生相同的结果:
    dtype = "float32"
    module.set_input(input_name, img_data)
    module.run()
    output_shape = (1, 1000)
    tvm_output = module.get_output(0, tvm.nd.empty(output_shape)).numpy()
    
    scores = softmax(tvm_output)
    scores = np.squeeze(scores)
    ranks = np.argsort(scores)[::-1]
    for rank in ranks[0:5]:
        print("class='%s' with probability=%f" % (labels[rank], scores[rank]))
    

    结果:

    class='n02123045 tabby, tabby cat' with probability=0.621104
    class='n02123159 tiger cat' with probability=0.356378
    class='n02124075 Egyptian cat' with probability=0.019712
    class='n02129604 tiger, Panthera tigris' with probability=0.001215
    class='n04040759 radiator' with probability=0.000262
    
    #比较调优和未调优的模型,收集与此优化模型相关的一些基本性能数据,并将其与未优化模型进行比较。
    import timeit
    
    timing_number = 10
    timing_repeat = 10
    optimized = (
        np.array(timeit.Timer(lambda: module.run()).repeat(repeat=timing_repeat, number=timing_number))
        * 1000
        / timing_number
    )
    optimized = {"mean": np.mean(optimized), "median": np.median(optimized), "std": np.std(optimized)}
    
    print("optimized: %s" % (optimized))
    print("unoptimized: %s" % (unoptimized))
    

    结果:

    optimized: {'mean': 407.31687583000166, 'median': 407.3377107500164, 'std': 1.692177042688564}
    unoptimized: {'mean': 495.13895513002353, 'median': 494.6680843500417, 'std': 1.3081147373726523}