目录
- 前言
- 1. inference案例
- 总结
前言
杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。
本次课程学习 tensorRT 基础-实现模型的推理过程
课程大纲可看下面的思维导图
1. inference案例
tensorRT 推理的案例代码如下所示:
void inference(){
// ------------------------------ 1. 准备模型并加载 ----------------------------
TRTLogger logger;
auto engine_data = load_file("engine.trtmodel");
// 执行推理前,需要创建一个推理的runtime接口实例。与builer一样,runtime需要logger:
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
// 将模型从读取到engine_data中,则可以对其进行反序列化以获得engine
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(engine_data.data(), engine_data.size());
if(engine == nullptr){
printf("Deserialize cuda engine failed.\n");
runtime->destroy();
return;
}
nvinfer1::IExecutionContext* execution_context = engine->createExecutionContext();
cudaStream_t stream = nullptr;
// 创建CUDA流,以确定这个batch的推理是独立的
cudaStreamCreate(&stream);
/*
Network definition:
image
|
linear (fully connected) input = 3, output = 2, bias = True w=[[1.0, 2.0, 0.5], [0.1, 0.2, 0.5]], b=[0.3, 0.8]
|
sigmoid
|
prob
*/
// ------------------------------ 2. 准备好要推理的数据并搬运到GPU ----------------------------
float input_data_host[] = {1, 2, 3};
float* input_data_device = nullptr;
float output_data_host[2];
float* output_data_device = nullptr;
cudaMalloc(&input_data_device, sizeof(input_data_host));
cudaMalloc(&output_data_device, sizeof(output_data_host));
cudaMemcpyAsync(input_data_device, input_data_host, sizeof(input_data_host), cudaMemcpyHostToDevice, stream);
// 用一个指针数组指定input和output在gpu中的指针。
float* bindings[] = {input_data_device, output_data_device};
// ------------------------------ 3. 推理并将结果搬运回CPU ----------------------------
bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr);
cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
printf("output_data_host = %f, %f\n", output_data_host[0], output_data_host[1]);
// ------------------------------ 4. 释放内存 ----------------------------
printf("Clean memory\n");
cudaStreamDestroy(stream);
execution_context->destroy();
engine->destroy();
runtime->destroy();
// ------------------------------ 5. 手动推理进行验证 ----------------------------
const int num_input = 3;
const int num_output = 2;
float layer1_weight_values[] = {1.0, 2.0, 0.5, 0.1, 0.2, 0.5};
float layer1_bias_values[] = {0.3, 0.8};
printf("手动验证计算结果:\n");
for(int io = 0; io < num_output; ++io){
float output_host = layer1_bias_values[io];
for(int ii = 0; ii < num_input; ++ii){
output_host += layer1_weight_values[io * num_input + ii] * input_data_host[ii];
}
// sigmoid
float prob = 1 / (1 + exp(-output_host));
printf("output_prob[%d] = %f\n", io, prob);
}
}
我们加载的模型文件是上节课编译好的,整个代码可分为以下几个部分:
1.准备模型并加载
我们首先创建了一个 TensorRT 的 IRuntime
实例,用于运行推理。接着,我们通过 load_file
函数从本地读取一个序列化的 TensorRT 模型,然后使用 IRuntime
的 deserializeCudaEngine
方法将其反序列化为 ICudaEngine
。如果反序列化失败,则释放 IRuntime
实例防止内存泄露
2.创建执行上下文和 CUDA 流
ICudaEngine
的 createExecutionContext
方法用于创建一个执行上下文,然后我们创建了一个 CUDA 流用于异步管理
3.准备输入数据并将其移动到 GPU
首先在 host 中创建输入数据,然后分配 GPU 内存并将数据从主机复制到 GPU 上,最后创建一个指向输入和输出数据的指针数组
4.推理并将结果移动回 CPU
IExecutionContext
的 enqueueV2
方法用于启动异步推理,然后将推理结果从 GPU 复制回 主机内存,并同步 CUDA 流以确保复制完成。
5.释放内存
释放 stream、execution、engine、runtime
6.手动推理验证
为了验证 TRT 推理的正确性,又手动计算了一次推理结果,然后将其与 TRT 的结果进行比较
运行效果如下:
从结果来看 tensorRT 推理的结果和验证的结果一致,可知整个推理过程没问题
关于代码的重点提炼:
1. bindings 是 tensorRT 对输入输出张量的描述,bindings = input_tensor + output_tensor。比如 input 有 a,output 有 b,c,d,那么 bindings = [a,b,c,d],binding[0] = a,binding[2] = c。此时看到 engine->getBindingDimensions(0) 你得知道获取的是什么
2. enqueueV2 是异步推理,加入到 stream 队列等待执行。输入的 bindings 则是 tensors 的指针(注意是 device pointer),其 shape 对应于编译时指定的输入输出的 shape
3. createExecutionContext 可以执行多次,允许一个引擎创建多个执行上下文,不过看看就好,别当真😂
关于推理的知识点有:(from 杜老师)
执行推理的步骤:
1. 准备模型并加载
2. 创建 runtime:
createInferRuntime(logger)
3. 使用运行时时,以下步骤:
- 3.1. 反序列化创建 engine,得为 engine 提供数据:
runtime->deserializeCudaEngine(modelData, modelSize)
,其中modelData
包含的是 input 和 output 的名字,形状,大小和数据类型class ModelData(object): INPUT_NAME = "data" INPUT_SHAPE = (1, 1, 28, 28) // [B, C, H, W] OUTPUT_NAME = "prob" OUTPUT_SIZE = 10 DTYPE = trt.float32
- 3.2. 从 engine 创建执行上下文:
engine->createExecutionContext()
4. 创建 CUDA 流
cudaStreamCreate(&stream)
:
- 4.1. CUDA 编程中流是组织异步工作的一种方式,创建流来确定 batch 推理的独立
- 4.2. 为每个独立 batch 使用 IExecutionContext(3.2中已经创建),并为每个独立批次使用 cudaStreamCreate 创建 CUDA 流
5. 数据准备:
- 5.1. 在 host 上声明 input 和 output 数组大小,搬运到 gpu 上
- 5.2. 要执行 inference,必须用一个指针数组指定 input 和 output 在 gpu 中的指针
- 5.3. 推理并将 output 搬运回 cpu
6. 启动所有工作后,与所有流同步以等待结果
cudaStreamSynchronize
7. 按照与创建相反的顺序释放内存
总结
本节课程主要学习了利用 TRT-CPP 的 API 来进行模型推理,主要步骤包括:准备模型并加载、创建执行上下文和 CUDA 流、准备输入数据并将其移动到 GPU、推理并将结果移动回 CPU、释放内存。目前是通过调用 API 接口来推理模型,后面将会学习利用 onnxparser 解析 onnx 创建并推理模型。