cap2:1000分类的ResNet的TensorRT部署指南(python版)
《TensorRT全流程部署指南》专栏文章目录:
- cap1:TensorRT介绍及CUDA环境安装
- cap2:1000分类的ResNet的TensorRT部署指南(python版)
- cap3:自定义数据集训练ResNet的TensorRT部署指南(python版)
- cap4:YoloV5的TensorRT部署指南(python版)
…
文章目录
- 1、保存pytorch模型
- 1.1 获取pth模型
- 1.2 建立标杆
- 2、导出ONNX
- 2.1 导出模型
- 2.2 验证模型
- 2.3 可视化模型结构
- 3、环境搭建
- 3.1 TensorRT的安装
- 3.2 安装pycuda
- 4、转换TensorRT引擎
- 4.1 使用trtexec工具完成序列化
- 4.2 使用python的API进行转换
- 5、推理
- 5.1 推理代码
- 5.2 结果对比
- 5.3 图片推理
- 6、多batchsize情况
- 6.1 onnx导出设置
- 6.2 TensorRT序列化设置
- 6.3 推理设置
- 常见问题
在深度学习模型的实际应用中,训练只是第一步,如何将模型高效部署到生产环境才是关键。本文将以经典的ResNet18模型(1000分类)为例,手把手带你走完从模型权重获取、优化到使用TensorRT实现高性能推理的完整流程。无论你是刚接触模型部署的新手,还是希望优化推理性能的开发者,本文都将为你提供清晰、实用的指导,助你快速掌握工业级模型部署的核心技能!
环境:ubuntu20.04+4070Ti
1、保存pytorch模型
1.1 获取pth模型
首先,需要获取一个PyTorch模型。以ResNet18为例,可以直接使用torchvision中提供的预训练模型,该模型默认支持1000分类。以下为示例代码。在实际应用中,可以在ResNet18的基础上进行微调,或者从头训练模型,最终保存为.pth格式的权重文件即可。
默认情况下,ResNet18 的输入是一个 4D 张量,形状为 (batch_size, 3, 224, 224),输出是输出形状:(batch_size, 1000)。一般使用单batch,所以输入为(1,3,224,224),输出为(1,1000)。
import torch
import torchvision.models as models# 加载预训练的 ResNet18 模型
resnet18 = models.resnet18(pretrained=True)
# 保存模型为 .pth 文件
torch.save(resnet18.state_dict(), 'resnet18.pth')
print("ResNet18 模型已成功导出为 resnet18.pth")# 建立标杆
resnet18.eval()
# 创建一个全部值为 127 的输入张量 (batch_size=1, channels=3, height=224, width=224)
input_tensor = torch.full((1, 3, 224, 224), 127.0) # 注意:值为浮点数 127.0
# 运行推理
with torch.no_grad(): # 禁用梯度计算output = resnet18(input_tensor)
print("输出张量形状:", output.shape) # 输出: torch.Size([1, 1000])
print("输出张量部分值:", output[0, :5]) # 打印前 5 个类别的分数
# 11.5977, 23.6635, -19.3974, -45.7438, -55.7060
1.2 建立标杆
在上面的代码中,后半段是建立标杆。这里建立标杆是为了验证后续转换精度。同一个卷积在不同的框架中实现方式可能略有不同,那么模型从这个框架转换到另一个框架,其性能不能保证100%不变,肯定是存在一定的变化的。比如给定图片在pytorch中推理,得分为0.95,在tensorrt部署后可能得分为0.946或者0.96都有可能。造成这种原因是多方面的,最常见的是两种:1)算子本身实现方式不一样;2)图像预处理不一样。比如缩放有很多种算法,使用python的cv2进行缩放,或者使用c++自己实现的缩放,甚至其他库实现的缩放,3种算法的最终矩阵不可能完全一致,轻微的像素值差异都会导致最终结果的变化。
这里使用值全部为127的(1,3,224,224)进行推理,得到结果。在onnx和tensorrt部署后我们也可以使用值全部为127的输入进行推理,看看结果和这里的结果是否一样,如果差距很大说明转换过程中出现了问题,需要及时排查。
2、导出ONNX
2.1 导出模型
ONNX是一个中间件,比如下图只要各深度学习框架支持onnx,那么就能够实现互通,大大方便框架的维护和使用者的部署。像本文就是pytorch–>onnx–>tensorrt实现最终的模型转换和部署。
pytorch提供了onnx转换API,可以轻松转换:
import torch
import torchvision.models as models# 1. 加载模型结构,所谓的结构由模型架构+模型参数组成。
resnet18 = models.resnet18(pretrained=False) # 不加载预训练权重
weight_path = "resnet18.pth" # 替换为你的 .pth 文件路径
resnet18.load_state_dict(torch.load(weight_path))# 2. 将模型设置为评估模式
resnet18.eval()# 3. 创建一个虚拟输入张量 (batch_size=1, channels=3, height=224, width=224)
dummy_input = torch.randn(1, 3, 224, 224)# 5. 定义导出的 ONNX 文件名
onnx_filename = "resnet18.onnx"# 6. 导出模型为 ONNX 格式
torch.onnx.export(resnet18, # 要导出的模型dummy_input, # 虚拟输入onnx_filename, # 导出的 ONNX 文件名export_params=True, # 导出模型参数(权重)opset_version=11, # ONNX 算子集版本(推荐使用 11 或更高版本)do_constant_folding=True, # 是否优化常量折叠input_names=["input"], # 输入节点的名称output_names=["output"], # 输出节点的名称
)print(f"模型已成功从 {weight_path} 加载权重并导出为 {onnx_filename}")
2.2 验证模型
导出成功后,我们可以验证一下onnx模型是否正常。可以使用onnxruntime推理引擎简单推理一下:
import onnx
import onnxruntime as ort
import numpy as np# 1. 用checker方法检查模型是否有效
onnx_model = onnx.load("resnet18.onnx")
onnx.checker.check_model(onnx_model) # 检查模型是否有效,如果无效会报错# 2. 用模拟推理的方式检查模型是否有效
ort_session = ort.InferenceSession("resnet18.onnx")# 3. 准备输入数据
input_name = ort_session.get_inputs()[0].name
input_data = np.full((1, 3, 224, 224),127,np.float32)# 4. 运行推理
outputs = ort_session.run(None, {input_name: input_data})# 5. 打印输出形状
print(outputs[0][0][:10]) # 展示前10个类别的输出:11.597546 23.663443 -19.39737 -45.74385 -55.70603
print(outputs[0].shape) # 输出: (1, 1000)
我们可以看到onnx模拟推理的输出和pytorch的输出几乎一样,至少保证了小数点后3位。
2.3 可视化模型结构
Netron 是神经网络、深度学习和机器学习模型的查看器。Netron 支持 ONNX、TensorFlow Lite、Core ML、Keras、Caffe、Darknet、PyTorch、TensorFlow.js、Safetensors 和 NumPy。在这里我们使用该工具可视化resnet18.onnx模型结构。netron提供了在线版本(也有桌面应用版本),在网页中直接打开模型自动上传后就可以显示了。这里我们可视化resnet18.onnx的结果如图所示,展示了输入输出两个节点的信息。
3、环境搭建
TensorRT在Nvidia显卡上优化并推理,所以必然需要先配置好显卡驱动、CUDA以及cuDNN等。
这部分的教程可以参考:cap1:TensorRT介绍及CUDA环境安装中的4、基础环境配置
3.1 TensorRT的安装
首先去NVIDIA TensorRT Download选择一个版本,比如TensorRT8。我是Ubuntu20,但是选择TAR包,所以选择Linux_X86_64+TAR package下载。
下载并解压后,有多个目录,其中:
- bin:有trtexec可执行文件,通过这个工具可以实现序列化转换
- include和lib:是C++开发用的头文件和库文件
- python:python的whl文件,通过pip安装
在python中根据python版本安装对应的whl文件,比如我的是python3.10则pip install tensorrt-8.6.1-cp310-none-linux_x86_64.whl
3.2 安装pycuda
TensorRT是在nvidia显卡上推理,要求直接输入输出都在GPU显存空间中。所以会使用CUDA进行相关操作,pycude就是python操作CUDA的库。
安装方式非常简单:pip install pycuda
4、转换TensorRT引擎
这一步主要完成红框内的工作,就是将onnx模型序列化为TensorRT的engine模型。在这个过程中会自动根据设备的特异性进行优化,该过程十分缓慢。但是完成后会保存为本地文件,下次可以直接加载使用,不用再次序列化。
这个过程可以使用基于python的API完成,或者直接使用trtexec工具。
4.1 使用trtexec工具完成序列化
在第3节中下载了TensorRT包,在bin中有trtexec工具。打开终端进入trtexec工具所在文件夹就可以使用该命令工具了。
转换命令为:
trtexec --onnx=resnet18.onnx --saveEngine=resnet18.engine --fp16
onnx指定onnx模型路径,saveEngine知道转换后engine模保存路径,fp16表示使用FP16,这个可以大大提高推理速度。不加–fp16则默认使用FLOAT32
转换完成后如图,最后会出现PASSED结果,此时就得到engine模型。
4.2 使用python的API进行转换
使用python的API进行转换具有比较固定的流程,只不过可以根据需求比如动态输入等进行相应的设置。最终结果和trtexec转换没有什么区别。
不管什么方式转换得到的engine都可以被python或者c++拿去部署使用,但是要注意:1)版本对应:即转换使用的TensorRT版本和部署推理使用的版本需要一致;2)不支持跨设备:即在哪种设备上转换的就只能在哪种设备上使用,因为转换时根据设备特异性进行了优化。
import tensorrt as trt
# import pycuda.driver as cuda
# import pycuda.autoinit# TensorRT 需要一个 日志对象,用于输出 WARNING 级别的日志,帮助调试问题
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)def build_engine(onnx_file_path, engine_file_path):with trt.Builder(TRT_LOGGER) as builder, \builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) as network, \trt.OnnxParser(network, TRT_LOGGER) as parser:# config 配置 TensorRT 编译选项builder_config = builder.create_builder_config()builder_config.set_flag(trt.BuilderFlag.FP16) # 设置FP16加速(如果 GPU 支持),提高计算速度并减少显存占用。注释则默认使用FP32builder_config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 设置最大工作空间(1GB),用于存储中间 Tensor 和计算资源# 读取 ONNX 文件with open(onnx_file_path, 'rb') as model:if not parser.parse(model.read()):for error in range(parser.num_errors):print(parser.get_error(error))return None# 构建 TensorRT 引擎serialized_engine = builder.build_serialized_network(network, builder_config)# 开始反序列化为可执行引擎runtime = trt.Runtime(TRT_LOGGER)engine = runtime.deserialize_cuda_engine(serialized_engine)# 序列化并保存引擎with open(engine_file_path, 'wb') as f:f.write(engine.serialize())print(f"Saved TensorRT engine to {engine_file_path}")if __name__ == "__main__":onnx_file = "resnet18.onnx"engine_file = "resnet18.engine"build_engine(onnx_file, engine_file)
通过这种方式,我们也成功得到的engine模型。
5、推理
5.1 推理代码
推理的代码十分简单,推理的流程图如下。TensorRT的推理是在GPU上完成,所以推理只能从GPU的显存获取数据,推理的直接输出也是在显存中。所以输入数据需要从RAM(cpu端,或者host端)复制到GPU(设备端)中去。推理后需要从GPU复制到cpu后,才能使用numpy或者其他库进行操作。
为什么要分配显存空间,因为CUDA程序只能读取位于显存上的数据,而一般程序读取得数据都位于RAM。所以我们需要先在CUDA上用cuda.mem_alloc
为模型推理的输入和输出开辟足够的空间,然后将python读取的图像数据用cuda.memcpy_htod_async
复制到CUDA显存中去,推理时直接从显存读取数据。推理结束后,输出放在显存上,再使用cuda.memcpy_dtoh_async
将结果从显存复制到RAM中。同理接收数据时,需要再RAM中开辟足够的空间接收从显存来的数据,所以使用output = np.empty((1, 25200, 85), dtype=np.float32)
开辟了足够大小的空间。
完整的推理代码如下所示:
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit # cuda初始化
import numpy as npTRT_LOGGER = trt.Logger(trt.Logger.WARNING)def load_engine(engine_file_path):"""从engine模型反序列化:param engine_file_path::return:""""""加载 TensorRT 引擎"""with open(engine_file_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:return runtime.deserialize_cuda_engine(f.read())def allocate_buffers(engine):"""给模型直接输入和输出分配CUDA缓存区:param engine::return:"""inputs, outputs, bindings = [], [], []stream = cuda.Stream()for binding in engine: # 遍历模型engine中的所有输入输出节点size = trt.volume(engine.get_tensor_shape(binding)) * np.dtype(np.float32).itemsize # 602112device_mem = cuda.mem_alloc(size) # 分配size大小的空间,用于储存该节点的数据engine.get_tensor_mode(binding)if engine.get_tensor_mode(binding) == trt.TensorIOMode.INPUT:inputs.append(device_mem) # 如果是输入节点则将该空间地址存入inputselif engine.get_tensor_mode(binding) == trt.TensorIOMode.OUTPUT:outputs.append(device_mem) # 如果是输出节点则将该空间地址存入outputsbindings.append(int(device_mem))return inputs, outputs, bindings, streamdef infer(engine, image):"""执行 TensorRT 推理"""context = engine.create_execution_context() # 创建推理的上下文环境inputs, outputs, bindings, stream = allocate_buffers(engine) # 初始化分配CUDA显存空间# 将输入数据拷贝到 GPUcuda.memcpy_htod_async(inputs[0], image, stream) # image是cpu的ram数据,需要拷贝到GPU,才能被tensorrt推理使用# 执行推理context.execute_async_v2(bindings, stream.handle, None) # 推理# 从 GPU 拷贝输出结果output = np.empty((1, 1000), dtype=np.float32) # 初始化一个cpu的ram空间,方便从gpu接受输出数据cuda.memcpy_dtoh_async(output, outputs[0], stream) # 将位于gpu上的输出数据复制到cpu上,方便对齐操作stream.synchronize() # 同步,gpu是并行的,这里相当于等待同步一下return output # 最后将输出结果返回if __name__ == "__main__":engine_path = "resnet18.engine"image_path = "test.jpg" # 替换成你的图片路径print("Loading TensorRT engine...")engine = load_engine(engine_path)print("Preprocessing image...")input_data = np.full((1, 3, 224, 224),127,np.float32)print("Running inference...")output = infer(engine, input_data)print("Inference completed! Top-5 predictions:")print(output[0][:5])
在实际使用过程中,下面可以在最开始执行一次即可,创建的上下文环境可以重复使用,分配的显存空间也可以重复使用,这样就降低了这两个命令的耗时。
context = engine.create_execution_context() # 创建推理的上下文环境
inputs, outputs, bindings, stream = allocate_buffers(engine) # 初始化分配CUDA显存空间
最终我们可以将infer
函数改写为类:
class InferClas:def __init__(self,engine):self.context = engine.create_execution_context() # 创建推理的上下文环境self.inputs, self.outputs, self.bindings, self.stream = allocate_buffers(engine) # 初始化分配CUDA显存空间def infer(self,image):# 将输入数据拷贝到 GPUcuda.memcpy_htod_async(self.inputs[0], image, self.stream) # image是cpu的ram数据,需要拷贝到GPU,才能被tensorrt推理使用# 执行推理self.context.execute_async_v2(self.bindings, self.stream.handle, None) # 推理# 从 GPU 拷贝输出结果output = np.empty((1, 1000), dtype=np.float32) # 初始化一个cpu的ram空间,方便从gpu接受输出数据cuda.memcpy_dtoh_async(output, self.outputs[0], self.stream) # 将位于gpu上的输出数据复制到cpu上,方便对齐操作self.stream.synchronize() # 同步,gpu是并行的,这里相当于等待同步一下return output # 最后将输出结果返回# 推理的调用
inferclas=InferClas(engine) # 初始化一次
for i in range(10): # 后续的执行都只需要调用infer函数,提高每帧的推理速度output = inferclas.infer(input_data)
5.2 结果对比
至此,我们完成了python的tensorrt部署。我们来看一下模型的直接输出情况。
# fp16:10.5 23.53125 -20.0625 -45.84375 -55.75
# fp32:11.597782 23.66346 -19.397268 -45.743862 -55.706043
我们还是以13224*224的全部为127的模拟图像作为输入,并测试resnet18在pytorch、onnx、TensorRT(FP32)和TensorRT(FP16)下的直接输出和速度比较。
速度统计需求如下,因为第一次推理存在冷启动过程,耗时增大,故舍弃,只看最后10次的时间均值。设备为i7-12700KF+4070Ti
# pytorch
with torch.no_grad(): # 禁用梯度计算for i in range(11):t1=time.time()output = resnet18(input_tensor)print(time.time()-t1)# onnx
for i in range(11):t1=time.time()outputs = ort_session.run(None, {input_name: input_data})print(time.time()-t1)#TensorRT
inferclas=InferClas(engine)
for i in range(11):t1=time.time()output = inferclas.infer(input_data)print(time.time()-t1)
模型 | 输出 | 耗时(ms) | FPS |
---|---|---|---|
pytorch | 11.5977, 23.6635, -19.3974, -45.7438, -55.7060 | 9 | 167 |
onnx | 11.5977, 23.6634, -19.3974, -45.7438, -55.7060 | 4.4 | 227 |
TensorRT(FP32) | 11.5977, 23.6634, -19.3972, -45.7438, -55.7060 | 0.6 | 1666 |
TensorRT(FP16) | 10.5000, 23.5313, -20.0625, -45.84375, -55.750 | 0.2 | 5000 |
可以看到TensorRT部署后推理速度有了极大的提高,能够达到上千帧。
但是注意,根据上面的流程图,每一帧的推理还包含其他步骤,所以实际帧率会低。在预处理部分也会影响到帧率,如何更高效率进行预处理也是模型部署的关键一环,此处不再细谈。
5.3 图片推理
图片推理和推理代码一样,只不过需要对图像进行预处理操作,完整代码如下。get_input
函数的功能:读取一张图片,缩放到224*224,归一化到0~1,再Normalize,最后改变维度得到(1,3,224,224)。
import timeimport tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit # cuda初始化
import numpy as np
import sys
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)def load_engine(engine_file_path):"""从engine模型反序列化:param engine_file_path::return:""""""加载 TensorRT 引擎"""with open(engine_file_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:return runtime.deserialize_cuda_engine(f.read())def allocate_buffers(engine):"""给模型直接输入和输出分配CUDA缓存区:param engine::return:"""inputs, outputs, bindings = [], [], []stream = cuda.Stream()for binding in engine: # 遍历模型engine中的所有输入输出节点size = trt.volume(engine.get_tensor_shape(binding)) * np.dtype(np.float32).itemsize # 602112device_mem = cuda.mem_alloc(size) # 分配size大小的空间,用于储存该节点的数据engine.get_tensor_mode(binding)if engine.get_tensor_mode(binding) == trt.TensorIOMode.INPUT:inputs.append(device_mem) # 如果是输入节点则将该空间地址存入inputselif engine.get_tensor_mode(binding) == trt.TensorIOMode.OUTPUT:outputs.append(device_mem) # 如果是输出节点则将该空间地址存入outputsbindings.append(int(device_mem))return inputs, outputs, bindings, streamclass InferClas:def __init__(self,engine):self.context = engine.create_execution_context() # 创建推理的上下文环境self.inputs, self.outputs, self.bindings, self.stream = allocate_buffers(engine) # 初始化分配CUDA显存空间def infer(self,image):# 将输入数据拷贝到 GPUcuda.memcpy_htod_async(self.inputs[0], image, self.stream) # image是cpu的ram数据,需要拷贝到GPU,才能被tensorrt推理使用# 执行推理self.context.execute_async_v2(self.bindings, self.stream.handle, None) # 推理# 从 GPU 拷贝输出结果output = np.empty((1, 1000), dtype=np.float32) # 初始化一个cpu的ram空间,方便从gpu接受输出数据cuda.memcpy_dtoh_async(output, self.outputs[0], self.stream) # 将位于gpu上的输出数据复制到cpu上,方便对齐操作self.stream.synchronize() # 同步,gpu是并行的,这里相当于等待同步一下return output # 最后将输出结果返回def get_input(img_path):"""读取一张图片,缩放到224*224,归一化到0~1,再Normalize,最后改变维度得到(1,3,224,224):param img_path::return:"""import cv2# 1. 使用 OpenCV 读取图像并缩放到 224x224img = cv2.imread(img_path)img_resized = cv2.resize(img, (224, 224))# 2. 使用 NumPy 将图像转为张量格式 (C, H, W) -> (3, 224, 224)# 转换为 [0, 1] 范围内img_normalized = img_resized.astype(np.float32) / 255.0 # 将像素值从 [0, 255] 转换到 [0, 1]# 3. Normalize 操作:使用 ImageNet 均值和标准差mean = [0.485, 0.456, 0.406]std = [0.229, 0.224, 0.225]img_normalized = (img_normalized - mean) / std # 归一化# 4. 改变维度从 (H, W, C) -> (1, C, H, W)img_tensor = np.transpose(img_normalized, (2, 0, 1)) # 转换通道到最前面img_tensor = np.expand_dims(img_tensor, axis=0) # 添加 batch 维度,变为 (1, 3, 224, 224)# 5. 确保数组是连续的,memcpy_htod_async要求的img_tensor=img_tensor.astype(np.float32)img_tensor = np.ascontiguousarray(img_tensor)return img_tensordef get_class_name(idx):# 加载ImageNet类别标签import requests# 下载ImageNet类别标签labels_url = "https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json"labels_map = requests.get(labels_url).json()# 将类别索引映射到类别名称labels = [labels_map[str(i)][1] for i in range(len(labels_map))]# 输出预测结果print(f"Predicted class id: {idx} Predicted class: {labels[idx]}")if __name__ == "__main__":engine_path = "resnet18_fp16.engine"print("Loading TensorRT engine...")engine = load_engine(engine_path)print("Preprocessing image...")input_data = get_input("rabbit2.jpeg")print("Running inference...")inferclas=InferClas(engine)output = inferclas.infer(input_data)predicted_idx=np.argmax(output)get_class_name(predicted_idx) # 从googleapi获取imagenet标签进行转换,可能存在网络不通畅的问题
对于下面的图片,推理结果为:Predicted class id: 332 Predicted class: Angora,即安哥拉兔
6、多batchsize情况
以上介绍的都是输入为(1,3,224,224)的情况,batchsize为1。如果希望推理时batchsize大于1,则需要另外处理:1)onnx环节;2)TensorRT序列化环节;3)推理处理环节;
多batchsize实际上就是动态设置batchsize维度,希望这个维度是可以变动的,而不是固定为1。
6.1 onnx导出设置
onnx导出时,只需要添加dynamic_axes参数,这个参数以字典形式设置输入节点input
和输出节点output
的0轴(即batchsize轴)为动态轴。后面的“batch_size”只是描述信息。注意这里的输入和输出需要同时设置。
dummy_input = torch.randn(1, 3, 224, 224)
# 6. 导出模型为 ONNX 格式
torch.onnx.export(resnet18, # 要导出的模型dummy_input, # 虚拟输入onnx_filename, # 导出的 ONNX 文件名export_params=True, # 导出模型参数(权重)opset_version=11, # ONNX 算子集版本(推荐使用 11 或更高版本)do_constant_folding=True, # 是否优化常量折叠input_names=["input"], # 输入节点的名称output_names=["output"], # 输出节点的名称dynamic_axes={ # 动态轴(支持动态 batch_size)"input": {0: "batch_size"},"output": {0: "batch_size"},},
)
6.2 TensorRT序列化设置
TensorRT序列化设置也非常简单,实际上就是在builder_config中添加一个profile。profile也是一个序列化的配置参数,功能就是为输入节点设置形状信息。
# profile配置 用于配置动态形状输入的优化参数。是config配置的一个子项,最终需要添加到config中
profile = builder.create_optimization_profile()
profile.set_shape("input",(10, 3, 224, 224), # 设置最小 shape(10, 3, 224, 224), # 设置最优 shape(10, 3, 224, 224)) # 设置最大 shape
builder_config.add_optimization_profile(profile)
profile.set_shape()有四个参数,第一个参数是节点名字,你是为哪个节点设置形状(这里的input在onnx导出时参数input_names所设置的)。第二个参数是这个节点接受的最小形状。第三个参数是这个节点接受的最佳形状。第四个参数是这个节点接受的最大形状。
从这里可以看出,如果我设置profile.set_shape("foo", (2, 3, 100, 200), (2, 3, 150, 250), (2, 3, 200, 300))
表明,foo这个节点固定了batchsize=3,但是宽度允许在100~200,高度允许在200~300。注意相应的在onnx导出时就需要在dynamic_axes将宽和高设置为动态轴。
在文本,如果已知batchsize一定为10,输入形状也不变就是(3,224,224),故最小最优最大全部设置为期望的形状。
添加了这个profile,再正常序列化即可。
6.3 推理设置
推理设置就涉及到2个地方。
第一,需要一个(10,3,224,224)的输入。可以将10帧图片预处理后堆叠为(10,3,224,224)的形状作为输入。这里使用np模拟:
input_data = np.full((10, 3, 224, 224),127,np.float32)
# input_data.shape=(10,3,224,224)
第二,修改输出拷贝
class InferClas:def __init__(self,engine):self.context = engine.create_execution_context()self.inputs, self.outputs, self.bindings, self.stream = allocate_buffers(engine) def infer(self,image):cuda.memcpy_htod_async(self.inputs[0], image, self.stream) self.context.execute_async_v2(self.bindings, self.stream.handle, None)# 之前batchsize=1,所以是np.empty((1, 1000)),但是现在batchsize=10,就需要np.empty((10, 1000)的空间来接收输出。# self.outputs[0]中数据量是10*1000,如果output只开辟了1*1000的空间,则最终只有输出的前1000个数据,是不完整的output = np.empty((10, 1000), dtype=np.float32) # 初始化一个cpu的ram空间,方便从gpu接受输出数据cuda.memcpy_dtoh_async(output, self.outputs[0], self.stream)self.stream.synchronize()# ......
input_data = np.full((10, 3, 224, 224),127,np.float32)
inferclas=InferClas(engine)
output = inferclas.infer(input_data)
print(output.shape) # (10,1000)
至此,多batchsize的需求处理完毕,其他如宽高等动态尺寸需求同理。
常见问题
[network.cpp::operator()::3212] Error Code 4: Internal Error (input: kMIN dimensions in profile 0 are [10,3,224,224] but input has static dimensions [1,3,224,224].):onnx导出时没有设置动态轴,在TensorRT序列化时设置的输入形状和onnx的静态输入形状不一致
相关文章:

cap2:1000分类的ResNet的TensorRT部署指南(python版)
《TensorRT全流程部署指南》专栏文章目录: cap1:TensorRT介绍及CUDA环境安装cap2:1000分类的ResNet的TensorRT部署指南(python版)cap3:自定义数据集训练ResNet的TensorRT部署指南(python版&…...
每日一题——把数字翻译成字符串
把数字翻译成字符串 题目描述示例示例1示例2 题解动态规划代码实现复杂度分析 总结 题目描述 有一种将字母编码成数字的方式:‘a’->1, ‘b’->2, … , ‘z’->26。 现在给一串数字,返回有多少种可能的译码结果。 数据范围:字符串…...

我们来学HTTP/TCP -- 三次握手?
三次握手 题记三次呼叫结语 题记 来,我们来演示下川普王和普京帝会面了 哎呦!你好你好,握手…哎嗨!侬好侬好,握手…欧嘿呦玛斯,握手… 抓狂啊!作孽啊!!! 不说人话啊! 关键的是,“三…...

多媒体软件安全与授权新范例,用 CodeMeter 实现安全、高效的软件许可管理
背景概述 Reason Studios 成立于 1994 年,总部位于瑞典斯德哥尔摩,是全球领先的音乐制作软件开发商。凭借创新的软件产品和行业标准技术,如 ReWire 和 REX 文件格式,Reason Studios 为全球专业音乐人和业余爱好者提供了一系列高质…...
SQL复习
SQL复习 MySQL MySQL MySQL有什么特点? MySQL 不支持全外连接。 安装 数据类型 MySQL中的数据类型分为哪些? MySQL中的数据类型主要分为三大类:数值类型、字符串类型、日期时间类型。 其中, 数值类型又分为七种:T…...
红队视角出发的k8s敏感信息收集——日志与监控系统
针对 Kubernetes 日志与监控系统 的详细攻击视角分析,聚焦 集群审计日志 和 Prometheus/Grafana 暴露 的潜在风险及利用方法 攻击链示例 1. 攻击者通过容器逃逸进入 Pod → 2. 发现未认证的 Prometheus 服务 → 3. 查询环境变量标签获取数据库密码 → 4. 通过审…...
Flask中获取请求参数的一些方式总结
在 Flask 中,可以从 request 对象中获取各种类型的参数。以下是全面整理的获取参数的方式及示例代码。 1. 获取 URL 查询参数(Query String Parameters) URL 中的查询参数通过 ?keyvalue&key2value2 的形式传递,使用 reques…...
架构——LVS负载均衡主要模式及其原理、服务水平、优缺点
LVS(Linux Virtual Server)是一款高性能的开源负载均衡软件,支持多种负载均衡模式。以下是其主要模式及其原理、服务水平、优缺点: 1. NAT 模式(Network Address Translation) 原理: 请求流程…...
【漫话机器学习系列】093.代价函数和损失函数(Cost and Loss Functions)
代价函数和损失函数(Cost and Loss Functions)详解 1. 引言 在机器学习和深度学习领域,代价函数(Cost Function)和损失函数(Loss Function)是核心概念,它们决定了模型的优化方向。…...
Android 13 上通过修改 AOSP 拦截 SystemUI 音量调节事件
定位关键代码SystemUI 的音量调节逻辑主要集中在以下类中: VolumeDialogController.java:负责与 AudioService 交互。 VolumeDialogImpl.java:处理 UI 交互事件(如按钮点击)。 PhoneWindowManager.java:处理物理按键事件(如音量键)。 拦截音量调节事件 以 VolumeDialog…...
SQL与数据库程序设计
1.1986年,10月美国国家标准局颁布了SQL语言的美国标准,称为SQL86 2.SQL(Structured Query Language)又称为结构化查询语言 3.建立索引的主要目的是加快查找的速度 4.在基本表上建立一个或者多个索引 5. 一个基本表是最多只能建立一个聚簇索引 6.CAL…...

大模型Deepseek的使用_基于阿里云百炼和Chatbox
目录 前言1. 云服务商2. ChatBox参考 前言 上篇博文中探索了(本地)部署大语言模型,适合微调、数据高隐私性等场景。随着Deepseek-R1的发布,大语言模型的可及性得到极大提升,应用场景不断增加,对高可用的方…...
AlmaLinux9.5安装samba实现与Windows文件共享 笔记250214
Fedora41安装samba实现与Windows文件共享 笔记241202 步骤: 安装samba, 并启用 smb , nmb 服务 切换阿里源(可选) ### 备份 /etc/yum.repos.d 文件夹 tempUri/etc/yum.repos.d ; sudo cp -a $tempUri $tempUri.$(date %0y%0m%0d%0H%0M%0Sns%0N).bak sed -e s|^mirrorli…...
二〇二四年终总结
写在前面 简单总结一下告诉自己,曾经活着 不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树 原本应该 24 年年中的时候写 23 年年终的总结,但是一直拖着&…...

学习sql的资源:sql练习平台;在线编辑器,免费sql教程,免费sql书籍
sql练习平台 LeetCode力扣(部分免费) 题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 HackerRank 解决 SQL |黑客排名 SQLPad SQL、Python 和 R 面试问题 StrataScratch StrataScratch - Analytical Questions 数据雷mur Free SQL Tutorial for Data Analysts …...

宝塔和docker的区别
宝塔面板 vs Docker:区别、场景与互补使用指南 eepseek回答宝塔和docker的区别 好的,我现在需要回答用户关于宝塔面板和Docker之间的区别的问题。首先,我需要明确这两个工具各自的定义、功能以及它们适用的场景,这样才能准确比较…...
机器学习--实现多元线性回归
机器学习—实现多元线性回归 本节顺延机器学习--线性回归中的内容,进一步讨论多元函数的回归问题 y ′ h ( x ) w ⊤ ∙ x b y^{\prime}h(x)w^\top\bullet xb y′h(x)w⊤∙xb 其中, w T ⋅ x 就是 W 1 X 1 w 2 X 2 w 3 X 3 ⋯ w N X N \text{其中,}w^\math…...
【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter2-HTML 中的 JavaScript
二、HTML 中的 JavaScript 将 JavaScript 插入 HTML 的主要方法是使用<script>元素。 <script>元素有下列 8 个属性。 async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部…...
【人工智能】释放数据潜能:使用Featuretools进行自动化特征工程
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 特征工程是机器学习流程中至关重要的一步,它直接影响模型的性能。然而,手动特征工程既耗时又需要领域专业知识。Featuretools是一个强大的…...
算法——对比A*算法与IDA*算法
A*算法与IDA*算法详细解析 1. A*算法 核心思想: A*算法是一种启发式搜索算法,结合了Dijkstra算法的最短路径保证和贪心最佳优先搜索的高效导向性。其核心是评估函数 ( f(n) g(n) h(n) ),其中: ( g(n) ): 从起点到当前节点 ( …...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...