ONNX使用pytorch导出的网络模型进行推理(C++)

28 篇文章 2 订阅
14 篇文章 0 订阅

官方例程

https://github.com/microsoft/onnxruntime/blob/master/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests.Capi/CXX_Api_Sample.cpp

在VisualStudio使用NuGet安装Onnx-Runtime.GPU

  1. 点击项目,管理NuGet程序包
  2. 点击预览搜索Microsoft.ML.OnnxRuntime.Gpu
  3. 安装对应版本的Runtime
    NuGet安装

修改后的代码

使用pytorch导出的人体关键点模型kp.onnx,输出是16个关键点的heat map, 尺寸是[batch_size, 16, 64, 64]

#include <iostream>
#include <assert.h>
#include <vector>
// 引入runtime库
#include <onnxruntime_cxx_api.h>
// 引入cuda库
#include "cuda_provider_factory.h"
#include <ctime>
int main(int argc, char* argv[])
{
	//设置为VERBOSE,方便控制台输出时看到是使用了cpu还是gpu执行
    Ort::Env env(ORT_LOGGING_LEVEL_VERBOSE, "test");
    Ort::SessionOptions session_options;
    // 使用五个线程执行op,提升速度
    session_options.SetIntraOpNumThreads(5);
    // 第二个参数代表GPU device_id = 0,注释这行就是cpu执行
    OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);
    // ORT_ENABLE_ALL: To Enable All possible opitmizations
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
#ifdef _WIN32
    const wchar_t* model_path = L"kp.onnx";
#else
    const char* model_path = "squeezenet.onnx";
#endif

    printf("Using Onnxruntime C++ API\n");
    Ort::Session session(env, model_path, session_options);

    Ort::AllocatorWithDefaultOptions allocator;
	// 获得模型又多少个输入和输出,一般是指对应网络层的数目
	// 一般输入只有图像的话input_nodes为1
    size_t num_input_nodes = session.GetInputCount();
    // 如果是多输出网络,就会是对应输出的数目
    size_t num_output_nodes = session.GetOutputCount();
    std::vector<const char*> input_node_names(num_input_nodes);
    std::vector<const char*> output_node_names(num_output_nodes);
    std::vector<int64_t> input_node_dims;
    std::vector<int64_t> output_node_dims;
    printf("Number of inputs = %zu\n", num_input_nodes);
    printf("Number of output = %zu\n", num_output_nodes);
	// 迭代所有输出层信息
    for (int i = 0; i < num_output_nodes; i++) {
        char* output_name = session.GetOutputName(i, allocator);
        printf("Output %d : name=%s\n", i, output_name);
        // 将输出层的名称添加到output_node_names这个vector
        output_node_names[i] = output_name;

        Ort::TypeInfo type_info = session.GetOutputTypeInfo(i);
        auto tensor_info = type_info.GetTensorTypeAndShapeInfo();

        ONNXTensorElementDataType type = tensor_info.GetElementType();
        printf("Output %d : type=%d\n", i, type);

        output_node_dims = tensor_info.GetShape();
        printf("Output %d : num_dims=%zu\n", i, output_node_dims.size());
        for (int j = 0; j < output_node_dims.size(); j++)
            printf("Output %d : dim %d=%jd\n", i, j, output_node_dims[j]);
    }
	// 获取所有输入层信息
    for (int i = 0; i < num_input_nodes; i++) {
        char* input_name = session.GetInputName(i, allocator);
        printf("Input %d : name=%s\n", i, input_name);
        input_node_names[i] = input_name;

        Ort::TypeInfo type_info = session.GetInputTypeInfo(i);
        auto tensor_info = type_info.GetTensorTypeAndShapeInfo();

        ONNXTensorElementDataType type = tensor_info.GetElementType();
        printf("Input %d : type=%d\n", i, type);

        input_node_dims = tensor_info.GetShape();
        printf("Input %d : num_dims=%zu\n", i, input_node_dims.size());
        for (int j = 0; j < input_node_dims.size(); j++)
            printf("Input %d : dim %d=%jd\n", i, j, input_node_dims[j]);
    }
	// 输入的总大小(所有像素)
    size_t input_tensor_size = 256 * 256 * 3;
	// 生成假的输入
    std::vector<float> input_tensor_values(input_tensor_size);
    for (unsigned int i = 0; i < input_tensor_size; i++)
        input_tensor_values[i] = (float)i / (input_tensor_size + 1);

    auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    clock_t startTime, endTime;
    // 创建输入tensor
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 4);
    assert(input_tensor.IsTensor());
    startTime = clock();
    // 第二个参数代表输入节点的名称集合
    // 第四个参数1代表输入层的数目
    // 第五个参数代表输出节点的名称集合
    // 最后一个参数代表输出节点的数目
    // 除了第一个节点外,其他参数与原网络对应不上程序就会无法执行
    auto output_tensors = session.Run(Ort::RunOptions{ nullptr }, input_node_names.data(), &input_tensor, 1, output_node_names.data(), num_output_nodes);
    endTime = clock();
    assert(output_tensors.size() == 1 && output_tensors.front().IsTensor());
    // 获取输出输出
    float* floatarr = output_tensors.front().GetTensorMutableData<float>();
	// TODO 因为这里我的输出是个heat map,暂时还没完成这部分后处理功能
	// 计算运行时间
    std::cout << "The run time is:" << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << std::endl;
    printf("Done!\n");
    system("pause");
    return 0;
}

cpu与gpu执行速度对比

设置op现成为1,对两者进行比较

session_options.SetIntraOpNumThreads(5);

硬件配置

  • GPU 2080ti
  • CPU i7-10700KF

GPU

可忽略Score的得分,这部分代码截图还是官方原版,因为得到的输出都是一个一维数组,所以这里不影响运行。

gpu

CPU

CPU

总结

结果竟然cpu速度比gpu快…根据上方红色框可以看到,cpu执行会提示for cpu字眼,gpu执行会提示for cuda字眼。这个问题可能是因为部分op官方还没有用CUDA实现,在cpu上面是做了很多优化,当前网络在cpu上最快能达到30fps。即使使用官方例程还是cpu比gpu快…在pc平台还是用TensorRT吧。

一个官方github的issue的原话,最后一句话是关键。

If you set the session log severity level to VERBOSE, it’ll print which nodes were assigned to GPU and which ones to CPU. Let us know what this prints. It might explain the behavior. We don’t have CUDA implementations for all ops.

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex-Leung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值