当前位置: 首页 > article >正文

从零开始 TensorRT(3)Python 篇:解析 ONNX、PyTorch TensorRT 接口

前言

学习资料:
TensorRT 源码示例
官方文档:Working With TensorRT Using The Python API
官方文档:TensorRT Python
官方文档:CUDA Python
B站视频教程
视频配套代码 cookbook

示例:解析 ONNX 模型

参考源码:cookbook → 04-BuildEngineByONNXParser → pyTorch-ONNX-TensorRT

源码

  cookbook 中自定义了一个网络并在 MNIST 数据集上进行训练,然后保存为 ONNX 并用 TensorRT 读取生成引擎并进行推理。这里对代码进行了简化,直接用 Pytorch 提供的训练好的 ResNet18,存为 ONNX 然后推理。
  测试数据使用了 TensorRT 中自带的数据,./TensorRT-8.6.1.6/data/resnet50 下有4张图片以及标签 class_labels.txt。将图像复制到 ./data/images 下,标签复制到 ./data 下即可。

import os
import numpy as np
from PIL import Image

import torch
import torchvision.models as models
import torchvision.transforms as transforms

import tensorrt as trt
from cuda import cudart

bUseFP16Mode = False
bUseINT8Mode = True

h, w = 224, 224
dataPath = 'data/images'
imgFiles = [os.path.join(dataPath, f) for f in os.listdir(dataPath)]
labelFile = 'data/class_labels.txt'
onnxFile = 'resnet18.onnx'
if bUseFP16Mode:
    trtFile = 'fp16.plan'
elif bUseINT8Mode:
    trtFile = 'int8.plan'
else:
    trtFile = 'fp32.plan'

batch_size = len(imgFiles)
with open(labelFile, 'r') as f:
    label = np.array(f.readlines())

# 准备数据
transform = transforms.Compose([
    transforms.Resize((h, w)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])


def load_images(image_paths):
    images = [Image.open(image_path) for image_path in image_paths]
    tensors = [transform(image).unsqueeze(0) for image in images]
    res = torch.cat(tensors, dim=0)
    return res


input_tensor = load_images(imgFiles).cuda()

weights = models.ResNet18_Weights.DEFAULT
model = models.resnet18(weights=weights, progress=False).eval().cuda()

result = model(input_tensor)

print(f'PyTorch results: {label[result.argmax(dim=1).cpu()]}')

# 导出 ONNX
torch.onnx.export(
    model,
    torch.randn(1, 3, h, w, device='cuda'),
    onnxFile,
    input_names=['x'],
    output_names=['y'],
    do_constant_folding=True,
    verbose=True,
    keep_initializers_as_inputs=True,
    opset_version=12,
    dynamic_axes={'x': {0: 'nBatchSize'}, 'y': {0: 'nBatchSize'}}
)


# INT8 模式校准器
class MyCalibrator(trt.IInt8EntropyCalibrator2):

    def __init__(self, data_path, n_calibration, input_shape, cache_file):
        trt.IInt8EntropyCalibrator2.__init__(self)
        self.imageList = [os.path.join(data_path, f) for f in os.listdir(data_path)]
        self.nCalibration = n_calibration
        self.shape = input_shape
        self.bufferSize = trt.volume(input_shape) * trt.float32.itemsize
        self.cacheFile = cache_file
        _, self.dIn = cudart.cudaMalloc(self.bufferSize)
        self.oneBatch = self.batch_generator()

    def __del__(self):
        cudart.cudaFree(self.dIn)

    def batch_generator(self):
        for i in range(self.nCalibration):
            print("> calibration %d" % i)
            sub_images = np.random.choice(self.imageList, self.shape[0], replace=False)
            yield np.ascontiguousarray(load_images(sub_images).numpy())

    def get_batch_size(self):  # necessary API
        return self.shape[0]

    def get_batch(self, nameList=None, inputNodeName=None):  # necessary API
        try:
            data = next(self.oneBatch)
            cudart.cudaMemcpy(self.dIn, data.ctypes.data, self.bufferSize, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
            return [int(self.dIn)]
        except StopIteration:
            return None

    def read_calibration_cache(self):  # necessary API
        if os.path.exists(self.cacheFile):
            print("Succeed finding cache file: %s" % self.cacheFile)
            with open(self.cacheFile, "rb") as f:
                cache = f.read()
                return cache
        else:
            print("Failed finding int8 cache!")
            return

    def write_calibration_cache(self, cache):  # necessary API
        with open(self.cacheFile, "wb") as f:
            f.write(cache)
        print("Succeed saving int8 cache!")
        return


# 构建期
logger = trt.Logger(trt.Logger.ERROR)
builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
profile = builder.create_optimization_profile()
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)
if bUseFP16Mode:
    config.set_flag(trt.BuilderFlag.FP16)
if bUseINT8Mode:
    config.set_flag(trt.BuilderFlag.INT8)
    config.int8_calibrator = MyCalibrator(dataPath, 1, (4, 3, h, w), 'int8.cache')

# 加载 ONNX
parser = trt.OnnxParser(network, logger)
with open(onnxFile, "rb") as model:
    if not parser.parse(model.read()):
        print("Failed parsing .onnx file!")
        for error in range(parser.num_errors):
            print(parser.get_error(error))
        exit()
    print("Succeeded parsing .onnx file!")

inputTensor = network.get_input(0)
profile.set_shape(inputTensor.name, [1, 3, h, w], [4, 3, h, w], [8, 3, h, w])
config.add_optimization_profile(profile)
# 生成序列化网络
engineString = builder.build_serialized_network(network, config)
with open(trtFile, "wb") as f:
    f.write(engineString)

# 运行期
engine = trt.Runtime(logger).deserialize_cuda_engine(engineString)
nIO = engine.num_io_tensors
lTensorName = [engine.get_tensor_name(i) for i in range(nIO)]
nInput = [engine.get_tensor_mode(lTensorName[i]) for i in range(nIO)].count(trt.TensorIOMode.INPUT)

context = engine.create_execution_context()
context.set_input_shape(lTensorName[0], [batch_size, 3, h, w])

inputHost = np.ascontiguousarray(input_tensor.cpu().numpy())
outputHost = np.empty(
    context.get_tensor_shape(lTensorName[1]),
    dtype=trt.nptype(engine.get_tensor_dtype(lTensorName[1]))
)

_, inputDevice = cudart.cudaMalloc(inputHost.nbytes)
_, outputDevice = cudart.cudaMalloc(outputHost.nbytes)
context.set_tensor_address(lTensorName[0], inputDevice)
context.set_tensor_address(lTensorName[1], outputDevice)

cudart.cudaMemcpy(inputDevice, inputHost.ctypes.data, inputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
context.execute_async_v3(0)
cudart.cudaMemcpy(outputHost.ctypes.data, outputDevice, outputHost.nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)

print(f'TensorRT results: {label[outputHost.argmax(axis=1)]}')

cudart.cudaFree(inputDevice)
cudart.cudaFree(outputDevice)

代码解析

  代码整体流程就是加载 PyTorch 提供的预训练模型 ResNet-18 并在几张 ImageNet 数据上进行推理,然后把模型保存为 ONNX,最后用 TensorRT 中的 Parser 加载模型进行推理。
  PyTorch 模型的存储路径为 /home/xxx/.cache/torch/hub/checkpoints
  比较陌生的部分在于导出 ONNX 所使用的 API torch.onnx.export(),以及使用 INT8 模式时需要额外编写一个校准器。

(1)导出 ONNX 模型:官方文档
部分参数解释:
do_constant_folding=True 是否进行常量折叠
verbose=True 是否打印详细信息
keep_initializers_as_inputs=True 是否将模型参数作为输入,个人理解是当设为 True 时,模型的参数是不固定的,是输入的一部分,这样可以加载不同参数的模型。而设为 False 时,模型的参数被固定了,但会更有利于优化加速。
dynamic_axes 设定动态轴

(2)INT8 模式下的校准器
config.int8_calibrator = MyCalibrator(dataPath, 1, (4, 3, h, w), 'int8.cache')

  INT8 模式需要确定每个权重张量的量化范围,以便在量化时保持模型的精度。此处算是作弊了,用推理的数据作为校准数据,并且校准时也把 4 张图像作为一个 batch 输入,从而得到的结果相同。若设为 config.int8_calibrator = MyCalibrator(dataPath, 5, (1, 3, h, w), 'int8.cache') 会发现结果有所不同。

  input_shape 对应了输入形状和 batch size。校准器的作用就是从输入的数据集中随机挑选 batch size 个样本,循环校准 n_calibration 次。

踩坑

  最初仅使用 Numpy 做数据预处理:

image = Image.open(imgFile).resize((224, 224))
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
image = (np.array(image, dtype=np.float32) / 255 - mean) / std
image = np.expand_dims(image.transpose((2, 0, 1)), axis=0)
input_tensor = torch.from_numpy(image).cuda()

  报错:RuntimeError: Input type (torch.cuda.DoubleTensor) and weight type (torch.cuda.FloatTensor) should be the same
  原因是第 4 行做标准化计算时,会把数据类型自动转变为 float64。若使用 torch.FloatTensor(image) 可解决报错,但是在 TensorRT 推理时使用 inputHost = np.ascontiguousarray(image) 会导致推理结果不同但没有报错,必须使用一样的 float32 数据。这里因为在 Pytorch 上推理了,后续用 input_tensor.cpu().numpy() 很安全,但实际使用会跳过这一步直接在 TensorRT 上推理,要小心数据类型问题。

示例:PyTorch 框架内 TensorRT 接口

参考源码:cookbook → 06-UseFrameworkTRT → Torch-TensorRT
PyTorch 官方示例

源码

  省略加载数据部分,与上个示例相同。测试时发现启用 TorchScript 会让推理速度加快很多。

import torch_tensorrt


TorchScript = True
if TorchScript:
    model = torch.jit.trace(model, torch.randn(batch_size, 3, h, w, device="cuda"))
    
optimized_model = torch_tensorrt.compile(
    model,
    inputs=[torch.randn((batch_size, 3, h, w)).float().cuda()],
    enabled_precisions=torch.float,
    debug=True,
)

optimized_result = optimized_model(input_tensor)
print(f'Torch TensorRT results: {label[optimized_result.argmax(dim=1).cpu()]}')

http://www.kler.cn/a/232130.html

相关文章:

  • .Net Core根据文件名称自动注入服务
  • candence : 通孔焊盘、插装器件封装绘制
  • ubuntu16.04配置网卡
  • 双子数(枚举素数)
  • 【计算机毕设】无查重 基于python豆瓣电影评论舆情数据可视化系统(完整系统源码+数据库+开发笔记+详细部署教程)✅
  • [Python学习日记-67] 封装
  • vue使用Mars3d弹框嵌套video视频/实时视频(flv) 使用jessibuca.js播放器
  • CTFshow web(php命令执行 45-49)
  • k8s 部署java应用 基于ingress+jar包
  • Flask基础学习
  • 高仿原神官网UI 纯html源码
  • 【C++11】统一初始化 和 initializer_list
  • 微信小程序新手入门教程四:样式设计
  • 自然语言处理(NLP)——使用Rasa创建聊天机器人
  • 测试管理_利用python连接禅道数据库并自动统计bug数据到钉钉群
  • 输入:123 输出321
  • 【大厂AI课学习笔记】【1.5 AI技术领域】(7)图像分割
  • 分布式存储中常见的容错机制:多副本、纠删码(RS、LRC、SHEC)
  • 修复 Java 异常 Java.IO.NotSerializableException
  • c入门第十篇——指针入门
  • vue.js基于springboot的实验室设备管理系统10345
  • 51单片机之LED灯模块篇
  • 牛客网SQL264:查询每个日期新用户的次日留存率
  • Flink流式数据倾斜
  • 如何在 Debian 11 上安装 Python 3 并设置编程环境
  • 2023年哪个前端框架用的最多?