找回密码
 立即注册
首页 业界区 业界 【TensorRT 10 C++ inference example】最新版本TensorR ...

【TensorRT 10 C++ inference example】最新版本TensorRT c++ api的推理部署教程

叭遭段 2025-6-2 23:48:48
  TensorRT是英伟达推出的部署框架,我的工作经常需要封装我的AI算法和模型给到桌面软件使用,那么tensorRT对我来说就是不二之选。TensorRT和cuda深度绑定,在c++版本的推理教程中需要使用cuda进行数据的显存绑定,由于10之前的写法比较固定,我自己基于tensorRT和cuda写了一套部署框架,将模型转换和核心推理部分都封装了起来。
  但是最近在一个新项目上,我把cuda升级到了12.4,随着的tensorRT也用上了最新的10.11版本,然后我发现原来的代码报错了,仔细检查发现tensorRT10修改了很多原来的api,我在网上查询了一些方案,大部分都没有解决或者不是很清楚,还有些文章需要付费所以也看不了。后来我发现英伟达自己在tensorRT项目中给了很多sample,但是这些sample有点臃肿,所以在这里我分享一下我的方案。
  【关于如何生成trt的引擎文件我这里就不写了,tensorRT10和之前的版本在这里区别不大,而且tensorRT都会给一个转换的可执行文件。所以我直接从模型推理开始。】
  首先,我将回顾一个tensorRT的推理流程,然后会将一些最新版本和之前版本的区别,最后会给上最新版本推理的示例代码。
  TensorRT首先需要初始化模型引擎和执行会话,这个新旧版本都一样,这里放一下我的代码:
  1. // 核心头文件
  2. #include <NvOnnxParser.h>
  3. #include <NvInfer.h>
  4. #include <cuda_runtime_api.h>
  5. // 首先定义cuda stream
  6. cudaStream_t stream;
  7. // 初始化变量
  8. nvinfer1::IRuntime* model_runtime{ nullptr };
  9. nvinfer1::ICudaEngine* model_engine{ nullptr };
  10. nvinfer1::IExecutionContext* model_context{ nullptr };
  11. // 模型路径
  12. const char* modelFile = deployModel.modelPath.c_str();
  13. // 加载模型
  14. model_runtime = nvinfer1::createInferRuntime(gLogger);
  15. std::ifstream fin(modelFile, std::ios::binary);
  16. std::string modelData = "";
  17. while (fin.peek() != EOF) { // 使用fin.peek()防止文件读取时无限循环
  18.     std::stringstream buffer;
  19.     buffer << fin.rdbuf();
  20.     modelData.append(buffer.str());
  21. }
  22. fin.close();
  23. model_engine = model_runtime->deserializeCudaEngine(modelData.data(), modelData.size());
  24. model_context = model_engine->createExecutionContext();
复制代码
  完成模型的初始化后,接下来我们需要进行数据缓冲的创建,即预先创建好输入输出的数据缓冲,我一般会把每个模型的输入输出的大小写到配置文件里,这样这里就可以直接创建,也可以通过model_engine->getTensorShape(name)进行获取,怎么写都可以,重要的要确定好输入输出的index和shape。
  1. // buffers,这里是一个指针数组,设计多少都可以,一般的模型只有一个输入一个输出的话,设置为2也可以,这里我设置100只是习惯
  2. void* model_buffers[100];
  3. // 计算输入shape,这里的inputShape是[640, 640, 3],inputIndex是0
  4. int tmpInputSize = 1;
  5. for (int j = 0; j < inputShape.size(); j++) {
  6.     tmpInputSize *= inputShape[j];
  7. }
  8. // 这是使用cuda申请对于大小的显存,并和缓存指针绑定
  9. CUDA_CHECK(cudaMalloc(&model_buffers[inputIndex], tmpInputSize * sizeof(float)));
  10. // 同样的操作对output也进行一遍
  11. int tmpOutputSize = 1;
  12. for (int j = 0; j < outputShape.size(); j++) {
  13.     tmpOutputSize *= outputShape[j];
  14. }
  15. CUDA_CHECK(cudaMalloc(&model_buffers[outputIndex], tmpOutputSize * sizeof(float)));
复制代码
  我习惯提前做好这些,这些在新旧版本上都是一致的没有区别,接下来就是模型推理部分了,也是有区别的地方。旧版本使用enqueue、enqueueV2进行推理,而新api是enqueueV3这里的差异导致我研究了半天。
  首先看一下旧版本的推理方式,以enqueueV2为例:
  1. // 分配cuda stream
  2. CUDA_CHECK(cudaStreamCreate(&stream));
  3. // 这里是将真正的输入数据移动到刚刚绑定申请完显存的缓存地址上,inputData就是真正的输入数据
  4. CUDA_CHECK(cudaMemcpyAsync(model_buffers[inputIndex], inputData, inputSize * sizeof(float), cudaMemcpyHostToDevice, stream));
  5. // 接下来就是推理部分
  6. model_context->enqueueV2(model_buffers, stream, nullptr);
  7. // 完成推理将缓存地址中输出数据移动出来
  8. float* outputData = new float[outputSize];
  9. CUDA_CHECK(cudaMemcpyAsync(outputData, model_buffers[outputIndex], outputSize * sizeof(float), cudaMemcpyDeviceToHost, stream));
  10. // 同步stream
  11. CUDA_CHECK(cudaStreamSynchronize(stream));
复制代码
  这里我们可以看到enqueueV2接受了model_buffers作为输入,但是enqueueV3就完全不一样了,他的参数只有一个stream,我看到这个api的时候都懵了,输入怎么办?输出怎么办?于是我看了官方的example,又看了几个教程,才终于搞明白,tensorRT10新增了一个输入输出注册的环节,需要先将buffer地址注册,然后再推理,等于将原来的一步拆分了两步,说实话真的有点脱裤子放屁,没活硬整了。好吧,看一下代码吧。
  1. // 前面都是一样的
  2. CUDA_CHECK(cudaStreamCreate(&stream));
  3. CUDA_CHECK(cudaMemcpyAsync(model_buffers[inputIndex], inputData, inputSize * sizeof(float), cudaMemcpyHostToDevice, stream));
  4. // 这里开始,需要进行输入输出的地址注册
  5. model_context->setInputTensorAddress(model_engine->getIOTensorName(inputIndex), model_buffers[inputIndex]);
  6. model_context->setOutputTensorAddress(model_engine->getIOTensorName(outputIndex), model_buffers[outputIndex]);
  7. // 接下来就是推理部分,这里不需要放缓存了
  8. model_context->enqueueV3(stream);
  9. // 完成推理将缓存地址中输出数据移动出来,后面也是和旧版本一样了
  10. float* outputData = new float[outputSize];
  11. CUDA_CHECK(cudaMemcpyAsync(outputData, model_buffers[outputIndex], outputSize * sizeof(float), cudaMemcpyDeviceToHost, stream));
  12. // 同步stream
  13. CUDA_CHECK(cudaStreamSynchronize(stream));
复制代码
  搞清楚后,其实挺让人无语的,我想看看源码,可惜老黄并没有公开,github上的也只有头文件和sample,但是感觉这个地址注册可能就是脱裤子放屁了一下,但是这样变动sample里又写的很复杂,真的很让人无语。
  好啦,以上就是本次分享的全部,欢迎大家评论区交流!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册