3. MacaRT C++ API

3.1. 部署ONNX模型

MacaRT支持使用C++ API将ONNX模型部署到曦云系列GPU上,并完成推理。

3.1.1. 创建Session

操作步骤

  1. 配置日志对象 Ort::Env

    #include <onnxruntime_cxx_api.h>
    std::string ep_name = "MacaEP";
    Ort::Env env(OrtLoggingLevel::ORT_LOGGING_LEVEL_INFO, ep_name.c_str());
    
  2. SessionOption 中添加MacaEP的信息。

    OrtMACAProviderOptions maca_options;
    maca_options.device_id=0;
    Ort::SessionOptions sessionOptions;
    sessionOptions.AppendExecutionProvider_MACA(maca_options);
    

    备注

    • SessionOption 用于在创建Session时,指定MacaRT所使用的EP信息。

    • SessionOption 的详细信息,可参考ONNX Runtime相关文档

  3. 创建Session对象。

    Ort::Session session(env, your_onnx_model_path, sessionOptions);
    

3.1.2. 获取模型图的输入输出信息

操作步骤

  1. 执行以下命令,获取模型图的输入输出信息。

    Ort::AllocatorWithDefaultOptions allocator;
    std::vector<const char*> inputNames, outputNames;
    for ( size_t i=0; i< session.GetInputCount(); i++){
       inputNames.push_back(session.GetInputName(i , allocator));
    }
    for ( size_t i=0; i< session.GetOutputCount(); i++){
       outputNames.push_back(session.GetOutputName(i , allocator));
    }
    

3.1.3. 构建模型输入Tensors

操作步骤

  1. 准备输入数据。假设提供的模型输入数据和数据长度是通过以下变量名称提供,并且输入数据处于可分页内存上。

    void* input_data[inputNames.size()]; // input data ptr
    size_t input_data_len[inputNames.size()]; // input data len
    
  2. 创建 MemoryInfo,用于标识输入数据所在的设备信息。

    Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator,OrtMemType::OrtMemTypeDefault);
    
  3. 创建输入Tensors。假设模型的输入形状是固定的。

    std::vector<Ort::Value> inputTensors;
    for(size_t i=0; i<inputNames.size(); i++){
       // get input node data type
       Ort::TypeInfo inputTypeInfo = session.GetInputTypeInfo(i);
       auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
       ONNXTensorElementDataType inputType = inputTensorInfo.GetElementType();
       // get input node data length
       std::vector<int64_t> inputDims=inputTensorInfo.GetShape();
       inputTensors.push_back(Ort::Value::CreateTensor(memoryInfo,input_data[i],input_data_len[i],inputDims.data(),inputDims.size(),inputType));
    }
    

3.1.4. 获取模型输出Tensors

操作步骤

  1. 执行以下命令,通过同步执行的方式,获取模型的输出Tensors。在不指定设备信息的情况下,输出Tensors中的数据默认位于可分页内存上。

    auto ouput = session.Run(Ort::RunOptions{nullptr},inputNames.data(), inputTensors.data(),inputNames.size(), ouputNames.data(), outputNames.size());
    mcStreamSynchronize(stream);
    

3.2. 绑定输入输出设备

有多个模型且模型间存在数据拷贝时,绑定输入输出内存信息,可帮助减少模型之间不必要的输入输出数据拷贝。MacaRT支持将模型的输入或输出绑定到:

  • 两种Host端的内存页面:可分页内存(Pageable Memory)和固页内存(Pinned Memory)

  • 一种Device端的内存信息:曦云系列GPU显存(Video Memory)

推荐使用固页内存存储模型输入输出数据,可以提高数据传输效率。

3.2.1. 绑定输入输出数据到可分页内存上

具体操作步骤,参见 3.1 部署ONNX模型

3.2.2. 绑定输入输出数据到固页内存上

操作步骤

  1. 准备输入数据。假设提供的模型输入数据和数据长度是通过以下变量名称提供,并且输入数据处于固页内存上。

    void* input_data[inputNames.size()]; // input data ptr
    size_t input_data_len[inputNames.size()]; // input data len
    
  2. 创建 MemoryInfo,用于标识输入数据所在的设备信息。

    constexpr const char* MACA_PINNED_STR= "MacaPinned";
    Ort:MemoryInfo memoryInfo = Ort::MemoryInfo(MACA_PINNED_STR, OrtAllocatorType::OrtDeviceAllocator,0, OrtMemType::OrtMemTypeCPUOutput);
    
  3. 创建输入Tensors。假设模型的输入形状是固定的。

    std::vector<Ort::Value> inputTensors;
    for(size_t i=0; i<inputNames.size(); i++){
       // get input node data type
       Ort::TypeInfo inputTypeInfo = session.GetInputTypeInfo(i);
       auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
       ONNXTensorElementDataType inputType = inputTensorInfo.GetElementType();
       // get input node data length
       std::vector<int64_t> inputDims=inputTensorInfo.GetShape();
       inputTensors.push_back(Ort::Value::CreateTensor(memoryInfo,input_data[i],input_data_len[i],inputDims.data(),inputDims.size(),inputType));
    }
    
  4. 执行以下命令,使用IOBinding来获取模型输出。

    Ort::IoBinding ioBinding(session);
    for(size_t i=0; i< inputNames.size(), i++){
       ioBinding.BindInput(inputNames[i],inputTensors[i]);
    }
    for(size_t i=0; i< outputNames.size(), i++){
       ioBinding.BindOutput(outputNames[i],memoryInfo);
    }
    session.Run(Ort::RunOptions{nullptr},ioBinding);
    auto outputs=ioBinding.GetOutputValues();
    

3.2.3. 绑定输入输出数据到显存上

操作步骤

  1. 准备输入数据。假设提供的模型输入数据和数据长度是通过以下变量名称提供,并且输入数据处于曦云系列GPU显存上。

    void* input_batch_data[inputNames.size()]; // input data ptr
    size_t input_batch_data_len[inputNames.size()]; // input data len
    
  2. 创建MemoryInfo,用于标识输入数据所在的内存信息。

    constexpr const char* MACA_STR= "Maca";
    Ort:MemoryInfo memoryInfo = Ort::MemoryInfo(MACA_STR, OrtAllocatorType::OrtDeviceAllocator, gpu_id, OrtMemType::OrtMemTypeDefault);
    
  3. 创建输入Tensors。假设模型的输入形状是固定的。

    std::vector<Ort::Value> inputTensors;
    for(size_t i=0; i<inputNames.size(); i++){
       // get input node data type
       Ort::TypeInfo inputTypeInfo = session.GetInputTypeInfo(i);
       auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
       ONNXTensorElementDataType inputType = inputTensorInfo.GetElementType();
       // get input node data length
       std::vector<int64_t> inputDims=inputTensorInfo.GetShape();
       inputTensors.push_back(Ort::Value::CreateTensor(memoryInfo,input_data[i], input_data_len[i], inputDims.data(), inputDims.size(),inputType));
    }
    
  4. 执行以下命令,使用IOBinding来获取模型输出。

    Ort::IoBinding ioBinding(session);
    for(size_t i=0; i< inputNames.size(), i++){
       ioBinding.BindInput(inputNames[i],inputTensors[i]);
    }
    for(size_t i=0; i< outputNames.size(), i++){
       ioBinding.BindOutput(outputNames[i],memoryInfo);
    }
    session.Run(Ort::RunOptions{nullptr},ioBinding);
    auto outputs=ioBinding.GetOutputValues();
    

3.3. 动态Batch推理

MacaRT支持ONNX Runtime的动态Batch推理功能,在指定推理后端为MacaEP时,使用 BatchSize 值为-1的模型。

推理原理

推理原理如图 3.1 所示,其中input_i_j代表第i组Batch数据,模型的第j个输入,M为模型结构需要的输入个数,N为输入的Batch数目,K为模型结构的输出个数。 input_i包含了input_0_i到input_N_i的所有输入数据。

../_images/image5.png

图 3.1 动态Batch推理原理图

操作步骤

  1. 准备输入数据。假设提供的模型输入数据和数据长度是通过以下变量名称提供,input_data包含了Batch为N的输入数据,且输入数据位于可分页内存上。

    void* input_data[inputNames.size()];
    size_t input_data_len[inputNames.size()];
    
  2. 创建MemoryInfo,用于标识输入数据所在的设备信息。

    Ort::MemoryInfo memoryInfo =Ort::MemoryInfo::CreateCpu(
    OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
    
  3. 创建输入Tensors。

    std::vector<Ort::Value> inputTensors;
    for(size_t i=0; i<inputNames.size(); i++){
       // get input node data type
       Ort::TypeInfo inputTypeInfo = session.GetInputTypeInfo(i);
       auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
       ONNXTensorElementDataType inputType = inputTensorInfo.GetElementType();
       // get input node data length
       std::vector<int64_t> inputDims=inputTensorInfo.GetShape();
       inputDims[0]=N;
       inputTensors.push_back(Ort::Value::CreateTensor(memoryInfo,input_data[i], input_data_len[i], inputDims.data(), inputDims.size(),inputType));
    }
    
  4. 支持使用以下两种方式获取模型输出。

    // session run
    auto ouput = session.Run(Ort::RunOptions{nullptr},inputNames.data(),
    inputTensors.data(),inputNames.size(),
    ouputNames.data(),outputNames.size());
    

    // iobinding
    Ort::IoBinding ioBinding(session);
    for(size_t i=0; i< inputNames.size(), i++){
       ioBinding.BindInput(inputNames[i],inputTensors[i]);
    }
    for(size_t i=0; i< outputNames.size(), i++){
       ioBinding.BindOutput(outputNames[i],memoryInfo);
    }
    session.Run(Ort::RunOptions{nullptr},ioBinding);
    auto outputs = ioBinding.GetOutputValues();
    

3.4. 提升曦云系列GPU推理性能

使用MacaRT将ONNX模型部署到曦云系列GPU上时,MacaEP配置信息 OrtMACAProviderOptions 会直接影响MacaRT的推理速度。 OrtMACAProviderOptions 支持的配置选项参见表 3.1

表 3.1 OrtMACAProviderOptions支持的配置选项

选项

说明

device_id

配置所使用的设备号

gpu_mem_limit

MacaRT使用的最大显存量

arena_extend_strategy

显存增长策略

default_memory_arena_cfg

内存管理配置

3.4.1. 设备ID

当有多个 GPU 时,需通过配置 gpu_id 来管理。通过在 OrtMACAProviderOptions 中配置 device_id,可以指定所使用的 gpu_id,默认为0。

OrtMACAProviderOptions maca_options;
maca_options.device_id = your_gpu_id;

3.4.2. MacaRT显存管理

MacaRT提供BFCarena显存管理策略,可以有效地使用显存,避免显存重复申请和显存碎片。有关BFCarena的更多信息,可参考ONNX Runtime相关文档

有以下两种方法可以配置MacaEP显存管理策略:

  • 直接使用 OrtMACAProviderOptions 配置。

    OrtMACAProviderOptions maca_options;
    maca_options.gpu_mem_limit=SIZE_MAX;
    maca_options.arena_extend_strategy=0;
    
  • 创建一个 OrtArenaCfg 对象。

    auto cfg = Ort::ArenaCfg(SIZE_MAX,0,-1,-1);
    OrtMACAProviderOptions maca_options;
    maca_options.default_memory_arena_cfg=cfg.release();
    

3.5. 自定义算子

MacaRT支持用户定义非官方的ONNX算子进行推理。

操作步骤

  1. 使用C++的API构建自定义算子库。

  2. 通过使用C++ API或Python API将自定义算子库注册到 SessionOptions 对应的MacaEP。

  3. 加载包含自定义算子的模型进行推理。

上述步骤的具体实现细节,可以参考ONNX Runtime官方文档中关于Cuda EP自定义算子的实现步骤,MacaEP保持基本一致。 也在 /opt/maca-ai/onnxruntime-maca/custom_op_test中提供了完整的实现样例。

以下介绍MacaEP自定义算子的不同之处。

3.5.1. Domain的定义

将自定义算子注册到MacaEP的后端时, Domain 必须设置为 custom.op,即:

static const char* c_OpDomain="custom.op";

3.5.2. Kernel输入输出的数据排布

在构建自定义算子库时,在MacaRT中新增接口用于指定Kernel需要的输入输出数据排布。

OrtDataLayout GetInputDataLayout(size_t index); //指定输入的数据排布
OrtDataLayout GetOutputDataLayout(size_t index); //指定输出的数据排布

当前MacaEP的自定义算子支持以下三种数据排布:

typedef enum OrtDataLayout{
   NCHW,
   NHWC8,
   NHWC16,
}OrtDataLayout;

需要注意的是:

  • 在默认情况下,所有自定义算子Kernel的输入输出都是NCHW格式。

  • 模型的输入数据排布必须是NCHW格式,MacaEP会根据Kernel需求自动进行数据排布的转换。

  • 模型的输出会被MacaEP自动转换为NCHW格式的排布。