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  

附加资源

更多算子支持和性能调优功能将很快添加。