细说TensorRT C++模型部署3

相关文章:

五 线程的具体实现

对 (四) 中提到的三个线程的代码实现. 这是最重要的三个代码片段了.

  • trtPre代码解析:
void InferImpl::trtPre(BaseParam &curParam, Infer *curFunc) {
    // 记录预处理总耗时
    double totalTime;
    std::chrono::system_clock::time_point preTime;
//    auto t = timer.curTimePoint();
//    计算1个batchSize的数据所需的空间大小
    unsigned long mallocSize = this->memory[0] * sizeof(float);
    // count统计推理图片数量,最后一次,可能小于batchSize.imgNums统计预处理图片数量,index是gpuIn的索引,两个显存轮换使用
    int countPre = 0, index = 0, inputSize = this->memory[0] / curParam.batchSize;
    Job job;

    while (workRunning) {
        {
            std::unique_lock<std::mutex> l1(lock1);
            // 队列不为空, 退出等待,. 当标志位workRunning为False时,说明要停止线程运行,也退出等待
            cv_.wait(l1, [&]() { return !qfJobs.empty() || !workRunning; });
//            判断退出等待原因是否是要停止线程
            if (!workRunning) break;
            fJob = qfJobs.front();
            qfJobs.pop();
        }
        // 默认推理的数量为batchSize, 只有最后一次才可能小于batchSize
        job.inferNum = curParam.batchSize;
//        记录待推理的最后一个元素地址
        auto lastElement = &fJob.mats.back();

        // todo 暂时,先在内存中进行图片预处理. gpuMat以后写cuda核函数处理
        for (auto &curMat: fJob.mats) {
            // 记录前处理耗时
            preTime = timer.curTimePoint();
//            调用具体算法自身实现的预处理逻辑
            curFunc->preProcess(curParam, curMat, pinMemoryIn + countPre * inputSize);
            // 将当前正在处理图片的变换参数加入该batch的变换vector中
            job.d2is.push_back({curParam.d2i[0], curParam.d2i[1], curParam.d2i[2], curParam.d2i[3], curParam.d2i[4], curParam.d2i[5]});

            countPre += 1;
            // 不是最后一个元素且数量小于batchSize,继续循环,向pinMemoryIn写入预处理后数据
            if (&curMat != lastElement && countPre < curParam.batchSize) continue;
            // 若是最后一个元素,记录当前需要推理图片数量(可能小于一个batchSize)
            if (&curMat == lastElement) job.inferNum = countPre;

            totalTime += timer.timeCountS(preTime);
            //若全部在gpu上操作,不需要这句复制
            checkRuntime(cudaMemcpyAsync(gpuIn[index], pinMemoryIn, mallocSize, cudaMemcpyHostToDevice, preStream));
            job.inputTensor = gpuIn[index];

            countPre = 0;
//            将内存地址索引指向另一块内存
            index = index >= 1 ? 0 : index + 1;

            {
                std::unique_lock<std::mutex> l1(lock1);
                // false,继续等待. true,不等待,跳出wait. 一旦进入wait,解锁. 退出wait,又加锁 最多2个batch
                cv_.wait(l1, [&]() { return qPreJobs.size() < 2; });
                // 将一个batch待推理数据的显存空间指针保存
                qPreJobs.push(job);
            }

            // 流同步,在通知队列可使用前,确保待推理数据已复制到gpu上,保证在推理时取出就能用
            checkRuntime(cudaStreamSynchronize(preStream));
            cv_.notify_all();
            // 清空保存仿射变换参数,只保留当前推理batch的参数
            std::vector<std::vector<float >>().swap(job.d2is);
        }
    }
    // 结束预处理线程,释放资源
    preFinish = true;
    // 唤醒trt线程,告知预处理线程已结束
    cv_.notify_all();
//    printf("pre   use time: %.2f ms\n", totalTime);
    logInfo("pre   use time: %.3f s", totalTime);
}

在while循环中,如果队列qfJobs不为空, 就提取数据, 之后遍历数据容器内每一项进行preProcess数据预处理.
这里的预处理是指由各具体算法自己实现的预处理, 将处理好的数据存放在内存pinMemoryIn开头的地址中,当处理完一个batchsize数据后, 再向后进行其他步骤. 若python中传入多个batchsize数据,且处理到最后一个数据且不满一个batchsize时, 代码也会向下进行. 此时job.inferNum会单独记录当前待推理数据数量.
数据处理完毕后,会使用cuda函数cudaMemcpyAsync把数据异步复制到GPU上, 然后传入队列qPreJobs, 当流同步后唤醒下一个线程trtInfer.
这里要说明, 在gpu上开辟的空间有两个空间,分别对应gpuIn[0],gpuIn[1],每个空间可以存储一个bachsize处理好的数据, 这样的处理方式比较占用显存, 好处是能使队列qPreJobs中一直有可用数据, 即使推理模块trtInfer速度远快于队列数据生成, 那么也能保证trtProcess一直处于工作状态, 保证效率.

  • trtInfer代码解析:
// 适用于模型推理的通用trt流程
void InferImpl::trtInfer(BaseParam &curParam) {
    // 记录推理耗时
    double totalTime;
//    auto t = timer.curTimePoint();
    int index2 = 0;
//    engine输入输出节点名字, 是把model编译为onnx文件时,在onnx中手动写的输入输出节点名
    const char *inferInputName = curParam.inputName.c_str();
    const char *inferOutputName = curParam.outputName.c_str();

    Job job;
    Out outTrt;

    while (true) {
        {
            std::unique_lock<std::mutex> l1(lock1);
            // 队列不为空, 就说明推理空间图片已准备好,退出等待,继续推理. 当图片都处理完,并且队列为空,要退出等待,因为此时推理工作已完成
            cv_.wait(l1, [&]() { return !qPreJobs.empty() || (preFinish && qPreJobs.empty()); });
//            若图片都处理完且队列空,说明推理结束,直接退出线程
            if (preFinish && qPreJobs.empty()) break;
            job = qPreJobs.front();
            qPreJobs.pop();
        }
        // 消费掉一个元素,通知队列跳出等待,向qJob继续写入一个batch的待推理数据
        cv_.notify_all();

        auto qT1 = timer.curTimePoint();
        // 指定tensor的输入输出地址,然后进行推理
        curParam.context->setTensorAddress(inferInputName, job.inputTensor);
        curParam.context->setTensorAddress(inferOutputName, gpuOut[index2]);
        curParam.context->enqueueV3(inferStream);

        outTrt.inferOut = gpuOut[index2];
        outTrt.d2is = job.d2is;
        outTrt.inferNum = job.inferNum;

        index2 = index2 >= 1 ? 0 : index2 + 1;

        cudaStreamSynchronize(inferStream);
        totalTime += timer.timeCountS(qT1);

        // 流同步后,获取该batchSize推理结果
        {
            std::unique_lock<std::mutex> l2(lock2);
            // false, 表示继续等待. true, 表示不等待,gpuOut内只有两块空间,因此队列长度只能限定为2
            cv_.wait(l2, [&]() { return qPostJobs.size() < 2; });
            qPostJobs.emplace(outTrt);
        }
        cv_.notify_all();
    }
    // 在post后处理线程中判断,所有推理流程是否结束,然后决定是否结束后处理线程
    inferFinish = true;
    //告知post后处理线程,推理线程已结束
    cv_.notify_all();
//    printf("infer use time: %.2f ms\n", totalTime);
    logInfo("infer use time: %.3f s", totalTime);
}

这个函数思路明确, 从预处理队列取数据, 然后指定引擎输入输出节点的地址和数据值, 执行推理, 流同步后将推理结果传入后处理队列qPostJobs中.
该推理模块中也是设计了两个显存块存储推理结果, 存储在qPostJobs中,设计思路在trtPre中有说明.

  • trtPost代码解析:
void InferImpl::trtPost(BaseParam &curParam, Infer *curFunc) {
    // 记录后处理耗时
    double totalTime;
//    auto t = timer.curTimePoint();
//    将推理后数据从从显存拷贝到内存中,计算所需内存大小,ps:其实与占用显存大小一致
    unsigned long mallocSize = this->memory[1] * sizeof(float), singleOutputSize = this->memory[1] / curParam.batchSize;
//    batchBox收集每个batchSize后处理结果,然后汇总到batchBoxes中
    batchBoxesType batchBoxes, batchBox;

//    传入图片总数, 已处理图片数量
    int totalNum = 0, countPost = 0;
    bool flag = true;
    Out outPost;

    while (true) {
        {
            std::unique_lock<std::mutex> l2(lock2);
            // 队列不为空, 就说明图片已推理好,退出等待,进行后处理. 推理结束,并且队列为空,退出等待,因为此时推理工作已完成
            cv_.wait(l2, [&]() { return !qPostJobs.empty() || (inferFinish && qPostJobs.empty()); });
            // 退出推理线程
            if (inferFinish && qPostJobs.empty()) break;
            outPost = qPostJobs.front();
            qPostJobs.pop();
            cv_.notify_all();
//           每次当python传入数据时,totalNum值才会取出一次,获得当前传入图片数量
            if (flag) {
                totalNum = qfJobLength.front();
                qfJobLength.pop();
                flag = false;
            }
        }
        // todo 将engine推理好的结果从gpu转移到内存中处理, 更高效的方式是在在gpu中用cuda核函数处理,but,以后再扩展吧
        cudaMemcpy(pinMemoryOut, outPost.inferOut, mallocSize, cudaMemcpyDeviceToHost);
        // 取回数据的仿射变换量,用于还原预测量在原图尺寸上的位置
        curParam.d2is = outPost.d2is;

        auto qT1 = timer.curTimePoint();
//        记录当前后处理图片数量, 若是单张图片,这个记录没啥用. 若是传入多个batchSize的图片,countPost会起到标识作用
        countPost += outPost.inferNum;
//        调用具体算法自身实现的后处理逻辑
        curFunc->postProcess(curParam, pinMemoryOut, singleOutputSize, outPost.inferNum, batchBox);
        //将每次后处理结果合并到输出vector中
        batchBoxes.insert(batchBoxes.end(), batchBox.begin(), batchBox.end());
        batchBox.clear();
        // 当commit中传入图片处理完时,通过set_value返回所有图片结果. 重新计数, 并返回下一次要输出推理结果的图片数量
        if (totalNum <= countPost) {
//            fJob.batchResult = std::make_shared<std::promise<batchBoxesType>>();
            // 输出解码后的结果,在commit中接收
            fJob.batchResult->set_value(batchBoxes);
            countPost = 0;
            flag = true;
            batchBoxes.clear();
        }
        totalTime += timer.timeCountS(qT1);
    }
//    printf("post  use time: %.2f ms\n", totalTime);
    logInfo("post  use time: %.3f s", totalTime);

}

代码中变量flag是标志位,作用时什么呢?
在commit方法中qfJobLength队列记录了传入数据的量,在trtPost中取出, 当后处理数据量达到totalNum时, 说明传入的数据全部后处理完毕,可以返回出去了.
flag初始为True,当第一次执行trtPost时取出传入数据的数量大小. 当后处理结束时再置为True,等待从python传入的新数据.
设想这样一个场景, python中通过request 请求一次传入66张图片,但batchsize仅设为8,这时需处理9次, 最后一次处理数量为2,当进行到后处理时, flag为True,会获得totalNum=66, 后处理会将每次处理的结果量记录到countPost中, 然后合并到vector变量batchBoxes中. 当totalNum <= countPost时, 说明66张图片全部后处理处理完,通过set_value()返回结果,然后清空计数,重置flag状态.

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值