TensorRT 调用onnx后的批量处理(下)

上一篇学习了tensorrt转onnx后如何使用python API进行批量化的处理,这篇文章的目的是将python API翻译成c++ API并测试下其时间消耗。
代码如下:

#include "argsParser.h"
#include "buffers.h"
#include "common.h"
#include "logger.h"
#include "parserOnnxConfig.h"
#include "NvInfer.h"
#include <cuda_runtime_api.h>
#include <random>
#include <iostream>
#include <vector>

using namespace nvinfer1;
template <typename T>
using SampleUniquePtr = std::unique_ptr<T, samplesCommon::InferDeleter>;
const std::string gSampleName = "TensorRT.sample_dynamic_reshape";
const int BatchSize = 32;

ICudaEngine* loadEngine(const std::string& engine, int DLACore, std::ostream& err)
{
    std::ifstream engineFile(engine, std::ios::binary);
    if (!engineFile)
    {
        err << "Error opening engine file: " << engine << std::endl;
        return nullptr;
    }

    engineFile.seekg(0, engineFile.end);
    long int fsize = engineFile.tellg();
    engineFile.seekg(0, engineFile.beg);

    std::vector<char> engineData(fsize);
    engineFile.read(engineData.data(), fsize);
    if (!engineFile)
    {
        err << "Error loading engine file: " << engine << std::endl;
        return nullptr;
    }

    auto runtime{createInferRuntime(gLogger.getTRTLogger())};
    if (DLACore != -1)
    {
        runtime->setDLACore(DLACore);
    }

    return runtime->deserializeCudaEngine(engineData.data(), fsize, nullptr);
}

void get_binding_idxs(ICudaEngine* engine, int profile_idx, 
        std::vector<int>& input_binding_idxs, std::vector<int>& output_binding_idxs)
{
    int num_bindings_per_profile = engine->getNbBindings()/engine->getNbOptimizationProfiles();
    int start_binding = profile_idx * num_bindings_per_profile;
    int end_binding = start_binding + num_bindings_per_profile;
    std::cout<<"Engine/Binding Metadata\n";
    std::cout<<"\tNumber of optimization profiles: "<<engine->getNbOptimizationProfiles()<<"\n";
    std::cout<<"\tNumber of bindings per profile: "<< num_bindings_per_profile<<"\n";
    std::cout<<"\tFirst binding for profile 0: "<< start_binding<<"\n";
    std::cout<<"\tLast binding for profile 0: "<<end_binding-1<<"\n";
    // 拆分input_idx和output_idx
    for(int i=start_binding; i<end_binding; ++i)
    {
        if(engine->bindingIsInput(i))
            input_binding_idxs.push_back(i);
        else
            output_binding_idxs.push_back(i);
    }
}

float* get_random_inputs(ICudaEngine* engine, SampleUniquePtr<IExecutionContext>& context,
    std::vector<int>& input_binding_idxs)
{
    std::cout<<"Read imgs from: "<<"\n";
    std::vector<float> fileData(BatchSize*3*224*224);
    for(int i=0; i<BatchSize*3*224*224; ++i)
        fileData[i] = 1-float(rand()%255/255.0);
    return fileData.data();
}

void setuo_binding_shapes(ICudaEngine* engine, IExecutionContext* context)
{}

int main(int argc, char** argv)
{
    std::string engineFile = "../resnet18_dynamic_fp16.engine";
    
    auto sampleTest = gLogger.defineTest(gSampleName, argc, argv); 
    gLogger.reportTestStart(sampleTest);

    nvinfer1::ICudaEngine* engine = loadEngine(engineFile, 1, gLogError); // 加载cuda Engine
    std::cout<<"CudaEngine: "<< engineFile <<" has been deserialized successful!\n";
    // 由cudaEngine创建 ExecuteContext, 该context可以重复使用
    auto context = SampleUniquePtr<IExecutionContext>(engine->createExecutionContext());
    context->setOptimizationProfile(0);  
    std::cout<<"Activate Optimization Profile: 0\n";
    // 从engine中获得输入输出的idx,这部分其实可以手工指定
    std::vector<int> input_binding_idxs, output_binding_idxs;
    get_binding_idxs(engine, context->getOptimizationProfile(), input_binding_idxs, output_binding_idxs);
    std::vector<const char*> input_names;
    for(int i: input_binding_idxs)
        input_names.push_back(engine->getBindingName(i));
    // 生成随机变量
    //给输入分配空间,并拷贝到cuda上
    std::vector<void *> buffers(2);
    float* host_inputs = get_random_inputs(engine, context, input_binding_idxs);
    CHECK(cudaMalloc(&buffers[input_binding_idxs[0]], BatchSize*3*224*224*sizeof(float)));
    cudaStream_t stream;
    CHECK(cudaStreamCreate(&stream));
    CHECK(cudaMemcpyAsync(buffers[input_binding_idxs[0]], host_inputs, BatchSize*3*224*224*sizeof(float),
          cudaMemcpyHostToDevice, stream));
    std::cout<<"Input Metadata"<<"\n";
    std::cout<<"\tNumber of Inputs: "<<input_binding_idxs.size()<<"\n";
    // std::cout<<"\tInput Bindings for Profile 0: "<<input_binding_idxs<<"\n";
    // std::cout<<"\tInput names: "<<input_names[0]<<"\n";
    // 由输入大小推断输出大小,并分配空间,这里我们直接直接指定大小
    context->setBindingDimensions(input_binding_idxs[0], Dims4{BatchSize, 3, 224, 224});
    if(!context->allInputDimensionsSpecified())
        std::cout<<"Some input dimension is not specified!\n";
    CHECK(cudaMalloc(&buffers[output_binding_idxs[0]], BatchSize* 1000 * sizeof(float)));

    typedef std::chrono::high_resolution_clock Time;
    typedef std::chrono::duration<double, std::ratio<1, 1000>> ms;
    typedef std::chrono::duration<float> fsec;
    double total = 0.0;
    auto t0 = Time::now();
    for(int i=0; i<1000; ++i)
    {
        bool status = context->executeV2(buffers.data());
        if(!status) std::cout<<"Something is wrong in inference!\n";
    }
    auto t1 = Time::now();
    fsec fs = t1 - t0;
    ms d = std::chrono::duration_cast<ms>(fs);
    total += d.count();
    std::cout<<"Running time of 1000 Batch is: "<<total/1000<<" ms\n";
    std::cout<<"Running time of 1000 image is: "<<total/BatchSize/1000<<" ms\n";

    float output[BatchSize*1000];
    CHECK(cudaMemcpyAsync(output, buffers[output_binding_idxs[0]], BatchSize * 1000 * sizeof(float),
     cudaMemcpyDeviceToHost, stream));
    cudaStreamSynchronize(stream);

    cudaStreamDestroy(stream);
    CHECK(cudaFree(buffers[input_binding_idxs[0]]));
    CHECK(cudaFree(buffers[output_binding_idxs[0]]));

    return gLogger.reportPass(sampleTest);

}

使用 trtexec工具由onnx模型转换成 engine 的方法见上篇。

TensorRT的C++ API查询 NVIDIA/TensorRT/C++ API

在V100上测试ResNet18分别使用TensorRT和Libtorch推理1000次获得的每帧平均处理时间(ms)

batchsize 1 2 4 8 16 24 32
TensorRT(FP32) 1.69 0.91 0.52 0.44 0.36 0.36 0.35
TensorRT(FP16) 1.26 0.64 0.34 0.19 0.11 0.09 0.09
libtorch(FP32) 1.81 1.06 0.71 0.51 0.40 0.36 0.34
libtorch(FP16) 0.43 0.15 0.08 0.04 0.03 0.03 0.02
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335