定义:时间/空间复杂度更低,即算法复杂度低,保证计算的同时内存消耗低,耗时短.
TensorRT密集型操作更友好,使用TensorRT实现高性能深度学习推理
要点:
imread
继承自nvinfer1::ILogger
定义logger类
实例化logger,作为全局日志打印的东西
构建模型编译器
创建网络 创建编译配置
auto builder = nvinfer1::createInferBuilder(logger);
auto network = builder->createNetworkV2(1);//其中1表示显示批处理
auto config = builder->createBuilderConfig();
配置网络参数, 配置最大的batchsize,意味着推理所指定的batch参数不能超过这个
builder->setMaxBatchSize(1);
配置工作空间的大小, 每个节点不用自己管理内存空间,不用自己去cudaMalloc内存, 使得所有节点均把workspace当做内存池,重复使用,使得内存
config->setMaxWorkspaceSize(1 << 30);
默认情况下,使用的是FP32推理,如果希望使用FP16,可以设置这个flags
builder->platformHasFastFp16(); //这个函数告诉你,当前显卡是否具有fp16加速的能力
builder->platformHasFastInt8(); //这个函数告诉你,当前显卡是否具有int8的加速能力
如果要使用int8,则需要做精度标定,模型量化内容,把你的权重变为int8格式,计算乘法。减少浮点数乘法操作,用整数(int8)来替代
构建网络结构;
自定义网络结构,并赋值权重
采用nvidia提供的nvonnxparser
自行编译nvonnxparser
使用构建好的网络,编译引擎
auto engine = builder->buildEngineWithConfig(*network, *config)
序列化模型为数据,并储存为文件
auto host_memory = engine->serialize(); save_to_file("04.cnn.trtmodel", host_memory->data(), host_memory->size());
回收内存
可实现转engine模型
cd /TensorRT-7.2.3.4/bin
例:04.tensorRT.cnn.cpp
cudaSetDevice(0);
cudaStreamCreate(&stream);
auto model_data = load_from_file("04.cnn.trtmodel");
auto runtime = nvinfer1::createInferRuntime(logger);
auto engine = runtime->deserializeCudaEngine(model_data.data(), model_data.size());
auto context = engine->createExecutionContext();
int nbindings = engine->getNbBindings();
// 分配输入的设备空间
float* input_device_image = nullptr;
size_t input_device_image_bytes = sizeof(float) * input_host_image.size();
cudaMalloc(&input_device_image, input_device_image_bytes);
// 创建流,并异步的方式复制输入数据到设备
cudaStream_t stream = nullptr;
cudaStreamCreate(&stream);
cudaMemcpyAsync(input_device_image, input_host_image.data(), input_device_image_bytes, cudaMemcpyHostToDevice, stream);
// 分配输出的设备空间
float* output_device = nullptr;
size_t output_device_bytes = 1000 * sizeof(float);
cudaMalloc(&output_device, output_device_bytes);
void* bindings[] = {input_device_image, output_device};
context->enqueueV2(1, bindings, stream, nullptr);
vector output_predict(1000);
cudaMemcpyAsync(output_predict.data(), output_device, output_device_bytes, cudaMemcpyDeviceToHost, stream);
enqueue的第四个参数inputConsumed,是通知input_device可以被修改的事件指针
如果在这里cudaEventSynchronize(inputConsumed);,在这句同步以后,input_device就可以被修改干别的事情
cudaStreamSynchronize(stream);
onnx框架,依赖自protobuf做序列化解析文件, nvonnxparser解析器,libnvonnxparser.so。
如果解析器与pytorch的onnx版本不匹配时就会导致莫名的错误、 如果解析器与pytorch的protobuf版本不匹配,也会导致错误,无法加载nvonnxparser解析器,
nvidia开源,所以可以直接拿来编译使用。 https://github.com/onnx/onnx-tensorrt
但是nvonnxparser解析器,自身也有版本问题,他的版本需要配合tensorRT版本一起使用
nvonnxparser这个解析器,不同版本有不同的坑,这个与动态batchsize有关。 比较好的搭配,是https://github.com/onnx/onnx-tensorrt/tree/6.0 版本
onnx框架,依赖自protobuf做序列化解析文件。
Protobuf通过onnx-ml.proto编译得到onnx-ml.pb.h和onnx-ml.pb.cc或onnx_ml_pb2.py
onnx-ml.pb.cc的代码操作onnx模型文件,实现增删改
onnx-ml.proto描述onnx文件如何组成,结构
下载特定的protobuf,这里我用的是3.11.4
下载onnx,保留其proto协议文件,生成pb.h、pb.cpp,只使用这几个即可,不需要onnx全部,Protocol Buffers,为了解决任何语言之间的数据序列化反序列化工作
下载onnx-tensorrt,配合onnx、protobuf等一起加入到项目进行编译,此时nvonnxparser可以替换为
项目内的源代码,那么任何错误都可以在源代码中进行调试
查看节点:model.graph.node
删除节点:model.graph.node.remove(item)
先修改输入输出
添加节点:onnx.helper.make_node(name, op_type, inputs, outputs, axes)
onnx拼接:
n.name = f'pre/{n.name}'
对于任何用到shape、size返回值的参数时,例如:tensor.view(tensor.size(0), -1)这类操作,避免直接使用tensor.size的返回值,而是加上int转换,tensor.view(int(tensor.size(0)), -1),断开跟踪
对于nn.Upsample或nn.functional.interpolate函数,使用scale_factor指定倍率,而不是使用size参数指定大小
对于reshape、view操作时,-1的指定请放到batch维度。其他维度可以计算出来即可。batch维度禁止指定为大于-1的明确数字
torch.onnx.export指定dynamic_axes参数,并且只指定batch维度,禁止其他动态
使用opset_version=11,不要低于11
避免使用inplace操作,例如y[…, 0:2] = y[…, 0:2] * 2 - 0.5
尽量少的出现5个维度,例如ShuffleNet Module,可以考虑合并wh避免出现5维
尽量把让后处理部分在onnx模型中实现,降低后处理复杂度
简化过程的复杂度,去掉gather、shape类的节点,很多时候,部分不这么改看似也是可以但是需求复杂后,依旧存在各类问题。按照说的这么修改,基本总能成。做了这些,就不需要使用onnx-simplifer了
利用int8乘法替换float32乘法实现性能加速, 对计算过程提高4倍加速
**量化模式:**1.PTQ训练后量化;2.QAT量化感知训练:TensorRT8.0后版本提供,一般在训练框架中进行
**目的:**使确定编码所用参数是否合适
采用KL散度衡量两个分布之间的差异。
细节:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6dpur23-1680900703022)(TensorRT.assets/image-20221116145759444-16685818867501.png)]
左边非对称量化,右边对称量化
动态对称量化:onnx
动态非对称量化算法
能够更好的捕捉到权重分布,不友好,用的不多
静态对称量化算法:TensorRT
PTQ训练后量化
Int8量化步骤:
配置setFlag nvinfer1::BuilderFlag::kINT8
实现Int8EntropyCalibrator类并继承自IInt8EntropyCalibrator2
实例化Int8EntropyCalibrator并且设置到config.setInt8Calibrator
Int8EntropyCalibrator的作用,是读取并预处理图像数据作为输入
QAT感知量化训练:
量化感知训练钱需要将BN层融合到conv层中
一般都是在训练框架中去做,比如Pytorch
**作用:**实现tensorrt不支持的算子。例如HWish
通过官方插件:
编译官方plugin库,将生成的libnvinfer_plugin.so.7
替换成原本的.so
文件。
编写流程
python实现导出onnx
继承自TRTPlugin
SetupPlugin()
主要实现enqueue
RegisterPlugin(): onnxplugin.hpp中,
目的:降低tensorrt使用门槛和集成难度,避免重复代码,关注业务逻辑,而非复杂细节。
资源获取即初始化
目的:创建资源时初始化,哪里分配哪里释放,再配合接口模式
原则:
**目的:**实现内存管理,维度管理,偏移量计算,索引计算,CPU/GPU互相自动拷贝。
内存拷贝:
目的:实现onnx到engine的转换封装,int8封装,
目的:实现tensorRT引擎的推理管理,自动关联引擎的输入和输出,管理上下文,插件
RAII+接口模式的封装
目的:封装插件的细节,序列化,反序列化,creator,tensor,weight.
简单的plugin
protocol通过onnx-ml.proto编译得到onnx-ml.pb.h和onnx-ml.pb.cc
onnx-ml.pb.cc的代码操作onnx模型文件,实现增删改
onnx-ml.proto描述onnx文件如何组成,结构
其中builtin_op_importers.cpp对应onnx的插件
等价与tensorRT8x
封装后的代码
common
python:
session = onnxruntime.InferenceSession("workspace/yolov5s.onnx", providers=["CPUExecutionProvider"])
pred = session.run(["output"], {"images": image_input})[0]
c++:
intel在cpu上推理加速引擎
promise:
future:
condition_variable:
时间:
#include
auto startTime = std::chrono::high_resolution_clock::now();
context->enqueueV2(&buffers[0], stream, nullptr);
cudaStreamSynchronize(stream);
auto endTime = std::chrono::high_resolution_clock::now();
float totalTime = std::chrono::duration(endTime - startTime).count();
CUDA耗时:
计算CUDA事件之间的耗时
cudaEvent_t start, end;
cudaEventCreate(&start);
cudaEventCreate(&end);
cudaEventRecord(start, stream); // 开始
context->enqueueV2(&buffers[0], stream, nullptr); // cuda事件
cudaEventRecord(end, stream); // 结束
cudaEventSynchronize(end); // 异步
float totalTime;
cudaEventElapsedTime(&totalTime, start, end); // 总耗时
Slice节点,提示slice is out of input range
原因:Slice节点,是由yolo中Focus层导出所生成,生成时,其ends值【通常是-1】给定为极大的整数值,导致两者不兼容
解决方案:
model.model[-1].export = True,指定为导出模式
Gather的错误,While parsing node number 97 [Gather]:
原因:依旧是PyTorch和TensorRT和Onnx之间没有统一的原因。要么他修改了新版本,要么你修改了新版本,反正就不一起改
解决方案:
出现的第一个场景是:Resize节点
出现的第二个场景是:Reshape和Transpose节点
维度问题,onnx中和pytorch导出可以是5个维度,但是tensorRT显示是4个维度
推理过程中反序列化报错
Serialization (Serialization assertion creator failed.Cannot deserialize plugin since corresponding IPluginCreator not found in Plugin Registry)
原因:为了使用TensorRT的插件,libnvinfer_plugin.so库必须被加载,所有插件必须通过调用initLibNvinferPlugins注册.
解决:初始化并登记所有TensorRT的plugins到Plugin Registry.添加initLibNvinferPlugin()
#include
auto startTime = std::chorno::high_resolution_clock::now();
auto endTime = std::chorno::high_resolution_clock::now();
float totalTime= std::chorno::duration(endTime - startTime).count();
time.time()
计时单位为秒预处理未进行cuda加速。
采用不带nms的onnx转FP32精度的TensorRT的engine模型推理一张图片需要2ms左右,采用CUDA后处理部分需要35ms左右,如何对nms进行提速?
11.tensorrt推理后:采用size_t类型输入到模型中,float类型输出,当将gpu推理的结果复制到cpu上时耗时4000ms。
解决办法:
nms,输出为[concatoutput_dim, 7],其中onnx转tensorrt文件后,输出维度为[100, 7],即每次推理会输出100X7个框。如何改成输出[框的个数,7],即输出维度第一维也为动态。
time.time()
计时单位为秒预处理未进行cuda加速。
采用不带nms的onnx转FP32精度的TensorRT的engine模型推理一张图片需要2ms左右,采用CUDA后处理部分需要35ms左右,如何对nms进行提速?
11.tensorrt推理后:采用size_t类型输入到模型中,float类型输出,当将gpu推理的结果复制到cpu上时耗时4000ms。
解决办法:
使用更快的数据传输方式:可以尝试使用更快的数据传输方式,如CUDA的异步内存拷贝,或者使用更快的设备之间的数据传输方式,如PCIe Gen3 x16或NVLink等。
减小数据传输量:可以尝试减小数据传输量,如使用更小的数据类型(如半精度浮点数或整数量化)或仅传输需要的数据(如仅传输置信度最高的检测框)。
对推理过程进行优化:可以尝试优化推理过程中的计算流程,如使用TensorRT的优化算法、减少计算图中的计算节点数量等,以减少GPU计算时间。
使用更快的CPU:可以尝试使用更快的CPU,以加快将数据从GPU传输到CPU的速度。