CANN 执行提供程序
华为昇腾计算架构(CANN)是一种面向 AI 场景的异构计算架构,提供多层编程接口,帮助用户快速构建基于昇腾平台的 AI 应用和服务。
使用适用于 ONNX Runtime 的 CANN 执行提供程序,可以帮助您在华为昇腾硬件上加速 ONNX 模型。
适用于 ONNX Runtime 的 CANN 执行提供程序(EP)由华为开发。
目录
安装
已发布包含 CANN EP 的 ONNX Runtime 预构建二进制文件,但目前仅支持 Python,请参考 onnxruntime-cann。
要求
请参考下表了解 ONNX Runtime 推理包对官方 CANN 包的依赖关系。
ONNX Runtime | CANN |
---|---|
v1.18.0 | 8.0.0 |
v1.19.0 | 8.0.0 |
v1.20.0 | 8.0.0 |
构建
有关构建说明,请参阅构建页面。
配置选项
CANN 执行提供程序支持以下配置选项。
device_id
设备 ID。
默认值:0
npu_mem_limit
设备内存区域的字节大小限制。此大小限制仅适用于执行提供程序的内存区域。总设备内存使用量可能更高。
arena_extend_strategy
扩展设备内存区域的策略。
值 | 描述 |
---|---|
kNextPowerOfTwo | 后续扩展以更大的量(乘以二的幂)进行扩展 |
kSameAsRequested | 按请求的量进行扩展 |
默认值: kNextPowerOfTwo
enable_cann_graph
是否使用图推理引擎加速性能。推荐设置为 true。如果为 false,则将回退到单算子推理引擎。
默认值: true
dump_graphs
是否将子图导出为 ONNX 格式,以便分析子图分割情况。
默认值: false
dump_om_model
是否将昇腾 AI 处理器离线模型导出为 .om 文件。
默认值: true
precision_mode
算子的精度模式。
值 | 描述 |
---|---|
force_fp32/cube_fp16in_fp32out | 根据算子实现,首先转换为 float32 |
force_fp16 | 当同时支持 float16 和 float32 时,转换为 float16 |
allow_fp32_to_fp16 | 当不支持 float32 时,转换为 float16 |
must_keep_origin_dtype | 保持原样 |
allow_mix_precision/allow_mix_precision_fp16 | 混合精度模式 |
默认值: force_fp16
op_select_impl_mode
CANN 中的一些内置算子具有高精度和高性能的实现。
值 | 描述 |
---|---|
high_precision | 以高精度为目标 |
high_performance | 以高性能为目标 |
默认值: high_performance
optypelist_for_implmode
列举使用 op_select_impl_mode 参数指定模式的算子列表。
支持的算子如下
- Pooling
- SoftmaxV2
- LRN
- ROIAlign
默认值: None
性能调优
IO 绑定
应利用I/O 绑定功能,以避免输入和输出复制带来的开销。
- Python
import numpy as np
import onnxruntime as ort
providers = [
(
"CANNExecutionProvider",
{
"device_id": 0,
"arena_extend_strategy": "kNextPowerOfTwo",
"npu_mem_limit": 2 * 1024 * 1024 * 1024,
"enable_cann_graph": True,
},
),
"CPUExecutionProvider",
]
model_path = '<path to model>'
options = ort.SessionOptions()
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL
options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
session = ort.InferenceSession(model_path, sess_options=options, providers=providers)
x = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=np.int64)
x_ortvalue = ort.OrtValue.ortvalue_from_numpy(x, "cann", 0)
io_binding = sess.io_binding()
io_binding.bind_ortvalue_input(name="input", ortvalue=x_ortvalue)
io_binding.bind_output("output", "cann")
sess.run_with_iobinding(io_binding)
return io_binding.get_outputs()[0].numpy()
- C/C++(未来)
示例
目前,用户可以在 CANN EP 上使用 C/C++ 和 Python API。
Python
import onnxruntime as ort
model_path = '<path to model>'
options = ort.SessionOptions()
providers = [
(
"CANNExecutionProvider",
{
"device_id": 0,
"arena_extend_strategy": "kNextPowerOfTwo",
"npu_mem_limit": 2 * 1024 * 1024 * 1024,
"op_select_impl_mode": "high_performance",
"optypelist_for_implmode": "Gelu",
"enable_cann_graph": True
},
),
"CPUExecutionProvider",
]
session = ort.InferenceSession(model_path, sess_options=options, providers=providers)
C/C++
注意:本示例以 resnet50_Opset16.onnx 为例展示模型推理。您需要根据您的需求修改 model_path 以及 input_prepare() 和 output_postprocess() 函数。
#include <iostream>
#include <vector>
#include "onnxruntime_cxx_api.h"
// path of model, Change to user's own model path
const char* model_path = "./onnx/resnet50_Opset16.onnx";
/**
* @brief Input data preparation provided by user.
*
* @param num_input_nodes The number of model input nodes.
* @return A collection of input data.
*/
std::vector<std::vector<float>> input_prepare(size_t num_input_nodes) {
std::vector<std::vector<float>> input_datas;
input_datas.reserve(num_input_nodes);
constexpr size_t input_data_size = 3 * 224 * 224;
std::vector<float> input_data(input_data_size);
// initialize input data with values in [0.0, 1.0]
for (unsigned int i = 0; i < input_data_size; i++)
input_data[i] = (float)i / (input_data_size + 1);
input_datas.push_back(input_data);
return input_datas;
}
/**
* @brief Model output data processing logic(For User updates).
*
* @param output_tensors The results of the model output.
*/
void output_postprocess(std::vector<Ort::Value>& output_tensors) {
auto floatarr = output_tensors.front().GetTensorMutableData<float>();
for (int i = 0; i < 5; i++) {
std::cout << "Score for class [" << i << "] = " << floatarr[i] << '\n';
}
std::cout << "Done!" << std::endl;
}
/**
* @brief The main functions for model inference.
*
* The complete model inference process, which generally does not need to be
* changed here
*/
void inference() {
const auto& api = Ort::GetApi();
Ort::Env env(ORT_LOGGING_LEVEL_WARNING);
// Enable cann graph in cann provider option.
OrtCANNProviderOptions* cann_options = nullptr;
api.CreateCANNProviderOptions(&cann_options);
// Configurations of EP
std::vector<const char*> keys{
"device_id",
"npu_mem_limit",
"arena_extend_strategy",
"enable_cann_graph"};
std::vector<const char*> values{"0", "4294967296", "kNextPowerOfTwo", "1"};
api.UpdateCANNProviderOptions(
cann_options, keys.data(), values.data(), keys.size());
// Convert to general session options
Ort::SessionOptions session_options;
api.SessionOptionsAppendExecutionProvider_CANN(
static_cast<OrtSessionOptions*>(session_options), cann_options);
Ort::Session session(env, model_path, session_options);
Ort::AllocatorWithDefaultOptions allocator;
// Input Process
const size_t num_input_nodes = session.GetInputCount();
std::vector<const char*> input_node_names;
std::vector<Ort::AllocatedStringPtr> input_names_ptr;
input_node_names.reserve(num_input_nodes);
input_names_ptr.reserve(num_input_nodes);
std::vector<std::vector<int64_t>> input_node_shapes;
std::cout << num_input_nodes << std::endl;
for (size_t i = 0; i < num_input_nodes; i++) {
auto input_name = session.GetInputNameAllocated(i, allocator);
input_node_names.push_back(input_name.get());
input_names_ptr.push_back(std::move(input_name));
auto type_info = session.GetInputTypeInfo(i);
auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
input_node_shapes.push_back(tensor_info.GetShape());
}
// Output Process
const size_t num_output_nodes = session.GetOutputCount();
std::vector<const char*> output_node_names;
std::vector<Ort::AllocatedStringPtr> output_names_ptr;
output_names_ptr.reserve(num_input_nodes);
output_node_names.reserve(num_output_nodes);
for (size_t i = 0; i < num_output_nodes; i++) {
auto output_name = session.GetOutputNameAllocated(i, allocator);
output_node_names.push_back(output_name.get());
output_names_ptr.push_back(std::move(output_name));
}
// User need to generate input date according to real situation.
std::vector<std::vector<float>> input_datas = input_prepare(num_input_nodes);
auto memory_info = Ort::MemoryInfo::CreateCpu(
OrtAllocatorType::OrtArenaAllocator, OrtMemTypeDefault);
std::vector<Ort::Value> input_tensors;
input_tensors.reserve(num_input_nodes);
for (size_t i = 0; i < input_node_shapes.size(); i++) {
auto input_tensor = Ort::Value::CreateTensor<float>(
memory_info,
input_datas[i].data(),
input_datas[i].size(),
input_node_shapes[i].data(),
input_node_shapes[i].size());
input_tensors.push_back(std::move(input_tensor));
}
auto output_tensors = session.Run(
Ort::RunOptions{nullptr},
input_node_names.data(),
input_tensors.data(),
num_input_nodes,
output_node_names.data(),
output_node_names.size());
// Processing of out_tensor
output_postprocess(output_tensors);
}
int main(int argc, char* argv[]) {
inference();
return 0;
}
支持的算子
在单算子推理模式下,CANN 执行提供程序支持以下算子。
算子 | 注意 |
---|---|
ai.onnx:Abs | |
ai.onnx:Add | |
ai.onnx:AveragePool | 仅支持 2D 池化。 |
ai.onnx:BatchNormalization | |
ai.onnx:Cast | |
ai.onnx:Ceil | |
ai.onnx:Conv | 仅支持 2D 卷积。 权重和偏置应为常量。 |
ai.onnx:Cos | |
ai.onnx:Div | |
ai.onnx:Dropout | |
ai.onnx:Exp | |
ai.onnx:Erf | |
ai.onnx:Flatten | |
ai.onnx:Floor | |
ai.onnx:Gemm | |
ai.onnx:GlobalAveragePool | |
ai.onnx:GlobalMaxPool | |
ai.onnx:Identity | |
ai.onnx:Log | |
ai.onnx:MatMul | |
ai.onnx:MaxPool | 仅支持 2D 池化。 |
ai.onnx:Mul | |
ai.onnx:Neg | |
ai.onnx:Reciprocal | |
ai.onnx:Relu | |
ai.onnx:Reshape | |
ai.onnx:Round | |
ai.onnx:Sin | |
ai.onnx:Sqrt | |
ai.onnx:Sub | |
ai.onnx:Transpose |
附加资源
更多算子支持和性能调优功能将很快添加。