4.3.tensorRT基础(1)-实现模型的推理过程

目录

    • 前言
    • 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 模型,然后使用 IRuntimedeserializeCudaEngine 方法将其反序列化为 ICudaEngine。如果反序列化失败,则释放 IRuntime 实例防止内存泄露

2.创建执行上下文和 CUDA 流

ICudaEnginecreateExecutionContext 方法用于创建一个执行上下文,然后我们创建了一个 CUDA 流用于异步管理

3.准备输入数据并将其移动到 GPU

首先在 host 中创建输入数据,然后分配 GPU 内存并将数据从主机复制到 GPU 上,最后创建一个指向输入和输出数据的指针数组

4.推理并将结果移动回 CPU

IExecutionContextenqueueV2 方法用于启动异步推理,然后将推理结果从 GPU 复制回 主机内存,并同步 CUDA 流以确保复制完成。

5.释放内存

释放 stream、execution、engine、runtime

6.手动推理验证

为了验证 TRT 推理的正确性,又手动计算了一次推理结果,然后将其与 TRT 的结果进行比较

运行效果如下:

4.3.tensorRT基础(1)-实现模型的推理过程_第1张图片

图1-1 inference案例运行效果

从结果来看 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 创建并推理模型。

你可能感兴趣的:(模型部署,tensorRT,CUDA,高性能)