官方例程
在VisualStudio使用NuGet安装Onnx-Runtime.GPU
- 点击项目,管理NuGet程序包
- 点击预览搜索Microsoft.ML.OnnxRuntime.Gpu
- 安装对应版本的Runtime
修改后的代码
使用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的得分,这部分代码截图还是官方原版,因为得到的输出都是一个一维数组,所以这里不影响运行。
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.