拉萨市网站建设_网站建设公司_导航菜单_seo优化
2026/1/16 15:58:19 网站建设 项目流程

1. inference案例:推理全流程详解

voidinference(){// ------------------------------ 1. 准备模型并加载 ----------------------------TRTLogger logger;autoengine_data=load_file("engine.trtmodel");// 执行推理前,需要创建一个推理的runtime接口实例。与builer一样,runtime需要logger:nvinfer1::IRuntime*runtime=nvinfer1::createInferRuntime(logger);// 将模型从读取到engine_data中,则可以对其进行反序列化以获得enginenvinfer1::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 ----------------------------floatinput_data_host[]={1,2,3};float*input_data_device=nullptr;floatoutput_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 ----------------------------boolsuccess=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. 手动推理进行验证 ----------------------------constintnum_input=3;constintnum_output=2;floatlayer1_weight_values[]={1.0,2.0,0.5,0.1,0.2,0.5};floatlayer1_bias_values[]={0.3,0.8};printf("手动验证计算结果:\n");for(intio=0;io<num_output;++io){floatoutput_host=layer1_bias_values[io];for(intii=0;ii<num_input;++ii){output_host+=layer1_weight_values[io*num_input+ii]*input_data_host[ii];}// sigmoidfloatprob=1/(1+exp(-output_host));printf("output_prob[%d] = %f\n",io,prob);}}

先明确推理的核心目标:加载上一节课保存的engine.trtmodel,输入[1,2,3],让TRT算出输出,再手动计算验证结果是否一致。

// 辅助函数:读取二进制文件(比如engine.trtmodel)到内存#include<fstream>#include<vector>usingnamespacestd;vector<unsignedchar>load_file(conststring&file){ifstreamin(file,ios::in|ios::binary);if(!in.is_open())return{};in.seekg(0,ios::end);size_t length=in.tellg();vector<unsignedchar>data;if(length>0){in.seekg(0,ios::beg);data.resize(length);in.read((char*)&data[0],length);}in.close();returndata;}

推理代码逐段拆解(6大步骤)

原代码的推理流程分为6个核心步骤,我们逐段讲透,补充每个步骤的“为什么”和“怎么来的”。

步骤1:准备模型并加载(搬出造好的机器)
voidinference(){// ------------------------------ 1. 准备模型并加载 ----------------------------TRTLogger logger;// 日志对象:记录推理过程中的警告/错误(必须有)autoengine_data=load_file("engine.trtmodel");// 读取保存的模型文件到内存// 1.1 创建推理运行时(Runtime):相当于“机器的启动器”,负责加载和运行enginenvinfer1::IRuntime*runtime=nvinfer1::createInferRuntime(logger);// 1.2 反序列化engine:把二进制的模型文件还原成可运行的“机器本体”nvinfer1::ICudaEngine*engine=runtime->deserializeCudaEngine(engine_data.data(),engine_data.size());if(engine==nullptr){// 反序列化失败(比如模型文件损坏、TRT版本不匹配)printf("Deserialize cuda engine failed.\n");runtime->destroy();// 及时释放资源,避免内存泄漏return;}

通俗解读

  • IRuntime:机器的“启动器”——只有通过它,才能把硬盘里的engine.trtmodel(二进制文件)变成内存中可运行的ICudaEngine
  • deserializeCudaEngine:“组装机器”的过程——把二进制的“零件图纸”还原成能干活的“机器”;
  • 常见失败原因:模型文件损坏、编译模型的TRT版本和推理的TRT版本不一致(比如用TRT8.6编译,用TRT8.2推理)、GPU架构不匹配。
步骤2:创建执行上下文和CUDA流(给机器装操作面板+流水线)
// 2.1 创建执行上下文(ExecutionContext):相当于“机器的操作面板”nvinfer1::IExecutionContext*execution_context=engine->createExecutionContext();// 2.2 创建CUDA流(Stream):相当于“独立的流水线”,异步处理推理任务cudaStream_t stream=nullptr;cudaStreamCreate(&stream);

通俗解读

  • IExecutionContext:一个engine(机器)可以创建多个execution_context(操作面板)——比如一台机器有多个操作面板,能同时给不同的工人用(支持多批次并行推理);
  • CUDA流
    • 没有流的话,所有操作(数据拷贝、推理)都是“同步的”:等上一个做完才能做下一个;
    • 有流的话,操作是“异步的”:把任务丢到流里,CPU不用等GPU做完,能继续干别的——比如拷贝数据的同时准备下一批数据,提高效率;
    • cudaStreamCreate(&stream):创建一条空的流水线。
步骤3:准备输入数据并搬运到GPU(给机器喂原材料)
/* 回顾上一节课的网络结构: 输入(3维) → 全连接层(2维) → Sigmoid → 输出(2维) */// 3.1 主机(CPU)上准备输入数据:原材料(比如[1,2,3])floatinput_data_host[]={1,2,3};float*input_data_device=nullptr;// GPU上的输入数据指针(空)// 3.2 主机(CPU)上准备输出缓存:装成品的空盒子floatoutput_data_host[2];float*output_data_device=nullptr;// GPU上的输出数据指针(空)// 3.3 给GPU分配内存:在GPU上造“原材料仓库”和“成品仓库”cudaMalloc(&input_data_device,sizeof(input_data_host));cudaMalloc(&output_data_device,sizeof(output_data_host));// 3.4 异步拷贝数据:把CPU的原材料搬到GPU的仓库(丢到流里,异步执行)cudaMemcpyAsync(input_data_device,input_data_host,sizeof(input_data_host),cudaMemcpyHostToDevice,stream);// 3.5 定义bindings(绑定):告诉机器“原材料仓库”和“成品仓库”的地址// bindings顺序:必须和构建模型时addInput、markOutput的顺序一致!float*bindings[]={input_data_device,output_data_device};

关键细节(新手必看)

  1. bindings是推理的核心——相当于机器的“输入输出接口”:
    • 构建模型时,我们先addInput("image", ...)(第一个接口),再markOutput(...)(第二个接口);
    • 所以bindings[0]必须是输入的GPU指针,bindings[1]必须是输出的GPU指针;
    • 如果有多个输入/输出(比如2个输入、3个输出),bindings顺序要严格对应addInputmarkOutput的顺序,否则推理结果错乱或报错。
  2. cudaMemcpyAsyncvscudaMemcpy
    • cudaMemcpy:同步拷贝——CPU等GPU拷贝完才继续;
    • cudaMemcpyAsync:异步拷贝——把拷贝任务丢到流里,CPU立刻继续执行下一行代码,效率更高;
    • 最后一个参数stream:指定拷贝任务丢到哪条流水线。
步骤4:推理并将结果搬运回CPU(机器干活,取出成品)
// 4.1 启动异步推理:按下机器的“启动键”,把任务丢到流里boolsuccess=execution_context->enqueueV2((void**)bindings,stream,nullptr);// 4.2 异步拷贝结果:把GPU成品仓库的东西搬回CPU的空盒子(丢到流里)cudaMemcpyAsync(output_data_host,output_data_device,sizeof(output_data_host),cudaMemcpyDeviceToHost,stream);// 4.3 同步流:等待流里的所有任务(拷贝输入、推理、拷贝输出)都做完cudaStreamSynchronize(stream);// 4.4 打印推理结果printf("TensorRT推理结果:output_data_host = %f, %f\n",output_data_host[0],output_data_host[1]);

通俗解读

  • enqueueV2:“启动机器”的核心接口(V2对应显性batch size,上一节课用的createNetworkV2(1));
    • 参数1:(void**)bindings——输入输出的GPU指针数组(必须转成void**类型);
    • 参数2:stream——把推理任务丢到这条流水线;
    • 参数3:nullptr——暂时不用(是针对多线程的同步参数);
  • cudaStreamSynchronize(stream):“等流水线干完活”——如果不加这行,CPU可能在GPU还没算完的时候就打印结果,拿到的是随机值;
  • 为什么要异步+同步:既利用异步提高效率,又保证结果能正确拿到。
步骤5:释放内存(用完机器,清理现场)
// 5.1 打印提示printf("Clean memory\n");// 5.2 释放资源:按“创建顺序倒序”释放,避免内存泄漏cudaStreamDestroy(stream);// 销毁流水线execution_context->destroy();// 销毁操作面板engine->destroy();// 销毁机器本体runtime->destroy();// 销毁启动器

关键规则

  • TRT的资源(runtime、engine、context)必须手动destroy(),不像C++普通对象会自动析构;
  • CUDA的流必须cudaStreamDestroy,GPU内存如果是cudaMalloc分配的,也要cudaFree(原代码里漏了,补充修正:
    // 补充:释放GPU内存(原代码漏了,新手一定要加)cudaFree(input_data_device);cudaFree(output_data_device);
  • 释放顺序:后创建的先释放(流→上下文→engine→runtime)。
步骤6:手动推理验证(核对机器干活的结果是否正确)

为了确认TRT的推理结果没错,我们手动复现“全连接+Sigmoid”的计算过程:

// 6.1 定义和上一节课一致的权重、偏置constintnum_input=3;constintnum_output=2;floatlayer1_weight_values[]={1.0,2.0,0.5,0.1,0.2,0.5};// 2个输出×3个输入floatlayer1_bias_values[]={0.3,0.8};// 6.2 手动计算printf("\n手动验证计算结果:\n");for(intio=0;io<num_output;++io){// 遍历2个输出神经元// 第一步:计算全连接层输出 = 偏置 + Σ(权重×输入)floatoutput_host=layer1_bias_values[io];// 先加偏置for(intii=0;ii<num_input;++ii){// 遍历3个输入// 权重索引:io×num_input + ii → 第io个输出神经元的第ii个输入权重output_host+=layer1_weight_values[io*num_input+ii]*input_data_host[ii];}// 第二步:Sigmoid激活 = 1 / (1 + e^(-x))floatprob=1/(1+exp(-output_host));printf("output_prob[%d] = %f\n",io,prob);}}

手动计算过程(代入数值)
我们把输入[1,2,3]代入,一步步算,验证和TRT结果一致:

  • 第一个输出神经元(io=0):
    全连接:0.3 + (1.0×1) + (2.0×2) + (0.5×3) = 0.3 + 1 + 4 + 1.5 = 6.8;
    Sigmoid:1/(1+e^(-6.8)) ≈ 1/(1+0.0011) ≈ 0.9989;
  • 第二个输出神经元(io=1):
    全连接:0.8 + (0.1×1) + (0.2×2) + (0.5×3) = 0.8 + 0.1 + 0.4 + 1.5 = 2.8;
    Sigmoid:1/(1+e^(-2.8)) ≈ 1/(1+0.0608) ≈ 0.9427;
  • 最终TRT输出的两个值就是这两个结果,说明推理正确。

完整运行效果(补充)

运行代码后,控制台会输出:

TensorRT推理结果:output_data_host = 0.998897, 0.942676 手动验证计算结果: output_prob[0] = 0.998897 output_prob[1] = 0.942676

两者结果完全一致,证明TRT推理流程正确。

补充:新手必知的关键知识点

1. bindings的顺序是“命脉”

  • 构建模型时:addInput的顺序是第0个binding,markOutput的顺序是第1个binding;
  • 推理时:bindings[0]必须是输入的GPU指针,bindings[1]必须是输出的GPU指针;
  • 如何确认顺序/维度:可以用engine的接口查询(新手调试用):
    // 打印binding的名称和维度(调试用)for(inti=0;i<engine->getNbBindings();i++){printf("binding[%d] name: %s, shape: ",i,engine->getBindingName(i));autodims=engine->getBindingDimensions(i);printf("(%d, %d, %d, %d)\n",dims.d[0],dims.d[1],dims.d[2],dims.d[3]);}

2. 异步推理的核心逻辑

  • 流(stream)是“独立的任务队列”:不同流的任务互不干扰;
  • 异步操作(cudaMemcpyAsyncenqueueV2)只是“把任务加入队列”,不会等待执行;
  • 必须用cudaStreamSynchronizecudaDeviceSynchronize等待任务完成,否则会拿到错误结果。

3. 一个engine可以创建多个上下文

  • 比如:engine->createExecutionContext()可以调用多次,得到多个execution_context
  • 用途:支持多线程并行推理——每个线程用一个上下文,共用一个engine(节省内存);
  • 注意:每个上下文需要独立的bindings和流,避免冲突。

4. 常见推理报错原因

报错现象常见原因
反序列化engine失败TRT版本不匹配、模型文件损坏、GPU架构不匹配
enqueueV2返回falsebindings顺序错、GPU内存不足、维度不匹配
结果是随机值没同步流、bindings指针是CPU指针(不是GPU)
内存泄漏没调用destroy()/cudaFree()/cudaStreamDestroy

总结:推理核心步骤与关键点

核心步骤(6步走)

  1. 加载模型load_file读二进制文件 →createInferRuntimedeserializeCudaEngine
  2. 创建上下文和流createExecutionContextcudaStreamCreate
  3. 数据准备:CPU准备数据 →cudaMalloc分配GPU内存 →cudaMemcpyAsync拷贝到GPU;
  4. 推理+结果拷贝enqueueV2启动推理 →cudaMemcpyAsync拷贝结果 →cudaStreamSynchronize同步;
  5. 释放资源:按倒序destroy TRT资源、销毁流、释放GPU内存;
  6. 验证结果:手动复现计算逻辑,确认推理正确。

关键要点

  1. bindings顺序必须和构建模型时的输入输出顺序一致,且必须是GPU指针;
  2. 异步操作(Async)要配合cudaStreamSynchronize使用,否则结果错误;
  3. TRT资源(runtime/engine/context)必须手动destroy(),CUDA资源要手动释放;
  4. 推理用的TRT版本、GPU架构要和编译模型时一致,否则反序列化失败。

是TensorRT部署的“通用模板”——不管是简单的全连接模型,还是复杂的YOLO模型,推理的核心步骤都是这6步,只是输入输出的维度、网络结构不同而已。吃透这个案例,你就掌握了TensorRT推理的核心逻辑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询