4.2. 模型推理

TCIM支持通过C++ API或Python API推理模型。

4.2.1. 模型输入输出数据

4.2.1.1. 内存分配

模型推理前,需要为模型的输入和输出数据分配内存。TCIM支持将输入输出数据存放在主机内存上或后摩设备内存上。

4.2.1.1.1. 主机内存上存放数据

可通过下面方式为输入、输出数据分配内存:

  • TCIM管理内存,用户无需管理内存:

    • 申请存放输入或输出数据的内存,并创建输入或输出tensor:调用 Tensor::CreateHostTensor (C++)接口,并使用该接口参数的默认取值,即 size0ptrnullptr

    • 申请存放输入或输出数据的内存,基于已有tensor创建一个具有相同tensor信息的tensor:通过 Tensor::Clone (C++)或 tcim_lite.runtime.Tensor.clone (Python)接口完成。该方式下,用户无需管理主机内存,由TCIM来管理主机内存。

  • 用户管理内存:调用 Tensor::CreateHostTensor (C++)接口,并根据已申请的主机内存设置 sizeptr 参数。该方式由用户管理主机内存。

4.2.1.1.2. 后摩设备内存上存放数据

可通过下面方式为输入、输出数据分配内存。用户无需管理后摩设备内存,由TCIM来管理后摩设备内存。

  • 申请存放输入或输出数据的内存,并创建输入或输出tensor:

    • 申请默认内存大小:调用 Tensor::CreateDeviceTensor (C++)接口,并使用该接口参数的默认取值,即 size0

    • 申请指定内存大小:调用 Tensor::CreateDeviceTensor (C++)接口,并设置 size 参数。

  • 申请存放输入或输出数据的内存,基于已有tensor创建一个具有相同tensor信息的tensor:通过 Tensor::Clone (C++)或 tcim_lite.runtime.Tensor.clone (Python)完成。

4.2.1.2. 设置模型输入数据

模型推理是在后摩设备上完成的。因此,模型推理前,需要先将模型推理所需的输入数据存放到后摩设备内存中。可调用 Module::SetInput (C++)或 tcim_lite.runtime.Module.set_input (Python)接口拷贝输入数据。

默认情况下,模型加载时,TCIM内部会在后摩设备上对输入数据预分配一块内存(dummy tensors除外),用于存放推理所需的输入数据。

  • 如果输入数据存放在主机内存,调用 Module::SetInput (C++)或 tcim_lite.runtime.Module.set_input (Python)接口时,TCIM会自动将数据转换为后摩设备内存格式,并拷贝到预分配的后摩设备内存上。

  • 如果输入数据已存放在后摩设备内存,调用 Module::SetInput (C++)或 tcim_lite.runtime.Module.set_input (Python)接口时,数据无需再拷贝到预先分配的内存上,而直接使用已存放数据的内存。因此,预先分配的内存被替代,进而预先分配的内存被释放。

4.2.1.3. 获取模型推理后输出数据

模型推理后,默认情况下,输出数据保存在后摩设备的内存上。可调用 Module::GetOutput (C++)或 tcim_lite.runtime.Module.get_output (Python)接口获取输出数据并到存到主机或后摩设备上。

  • 如果将输出数据保存在主机内存,则调用 Module::GetOutput (C++)或 tcim_lite.runtime.Module.get_output (Python)接口时,TCIM会自动将数据转换为主机内存格式,并拷贝到主机内存。

    • 主机端为连续存储格式:需先调用 Tensor::CreateHostTensor(dev.tensorInfo.AsContiguous()) (C++)在主机端创建连续存储的 tensor。

    • 主机端为非连续存储格式:需先调用 Tensor::CreateHostTensor(dev.tensorInfo) (C++)在主机端创建与后摩设备端内存布局一致的tensor。当调用 Module::GetOutput (C++)或 tcim_lite.runtime.Module.get_output (Python)接口获取输出数据到主机内存时,由于主机端内存布局和后摩设备端一致,因此无效填充数据(padding)也会一并拷贝。

  • 如果输出数据仍想保存在后摩设备内存,则可直接获取,后续可用于:

    • 设置其他模型的输入或输出:调用 Module::SetInput (C++)或 tcim_lite.runtime.Module.set_input (Python)接口,或 Module::SetOutput (C++)或 tcim_lite.runtime.Module.set_dev_output (Python)接口。

    • 将数据拷贝到另一个tensor:调用 Tensor::CopyTo (C++)或 tcim_lite.runtime.Tensor.copy_to (Python)接口。

4.2.1.4. 获取模型输入数据

可通过 Module::GetInput (C++)或 tcim_lite.runtime.Module.get_dev_input (Python)接口获取模型输入数据。

获取的模型输入数据可用于:

  • 设置其他模型的输入或输出:调用 Module::SetInput (C++)或 tcim_lite.runtime.Module.set_input (Python)接口,或 Module::SetOutput (C++)或 tcim_lite.runtime.Module.set_dev_output (Python)接口。

  • 将数据拷贝到另一个tensor:调用 Tensor::CopyTo (C++)或 tcim_lite.runtime.Tensor.copy_to (Python)接口。

可用于大语言模型中kvcache数据的传递。

4.2.1.5. 设置模型输出数据

仅支持设置保存在后摩设备内存上的输出数据。可通过 Module::SetOutput (C++)或 tcim_lite.runtime.Module.set_dev_output (Python)接口设置模型输出数据。

默认情况下,在模型加载时,TCIM内部会在后摩设备上对输出数据预分配一块内存(dummy tensors除外),用于存放推理后的输出数据。由于设置的输出数据已存放在后摩设备上,因此,无需再将数据拷贝到预分配的内存上,进而预先分配的内存被释放。

该接口常用于模型推理流水线应用场景。

4.2.1.6. 数据类型转换

当创建用于存放模型输入输出数据的tensor时,可通过 TensorInfo::AsType (C++)或 TensorInfo.astype (Python)接口,将创建的tensor数据类型设置为用户所需的类型。

若模型数据的类型与创建的 tensor 数据类型不匹配,用户必须在调用相关接口获取或设置模型输入输出数据后,调用 Tensor::AsType (C++)或 Tensor.astype (Python) 接口,将数据类型转换为tensor所需的类型,以确保正确的计算和存储。

示例如下:

C++示例

// ...
// Get output information
std::map<std::string, tcim::Tensor> output_map;
int output_num = module.GetOutputNum();
for (int idx = 0; idx < output_num; idx++) {
  auto output_name = module.GetOutputName(idx);
  // Get output information with updated data type to float32
  auto output_info = module.GetOutputInfo(output_name).AsContiguous().AsType(tcim::FLOAT32);
  // Create a tensor on host
  auto output_tensor = tcim::Tensor::CreateHostTensor(output_info);
  output_map.insert(std::pair<std::string, tcim::Tensor>(output_name, output_tensor));
}
// Model inference
// ...
// Get output data
for (auto& output : output_map) {
  // Get output tensor
  auto output_tensor = module.GetOutput(output.first);
  // Cast output tensor to float32
  output_tensor.CastTo(output.second);
}
// ...

Python示例

# ...
outputs = []
output_num = module.get_num_outputs()
for id in range(0, output_num):
    output_name = module.get_output_name(id)
    # Get output tensor information with updated data type to float32
    output_info = module.get_output_info(output_name).ascontiguous().astype(np.float32)
    # Get the output data and cast the output data to float32 data type
    output_data = module.get_output(output_name).astype(np.float32).numpy()
    outputs.append(output_data)
# ...

4.2.2. 注意事项

模型推理注意事项如下:

  • 在使用 C++ 接口推理模型时,如果 ModuleTensorWeightManagerStream 类对象为全局变量,必须在 main 函数结束前显示析构这些对象。

  • 不支持创建子进程。使用Python API推理模型时,import tcim_lite 后不可再创建子进程。

4.2.4. 使用C++ API推理模型

C++ API通过 tcim_runtime.htcim_status.h 头文件访问。

例如,一个简单的应用开头可能为:

#include "tcim_runtime.h"
#include "tcim_status.h"

下面以模型输入数据保存在后摩设备侧为例,介绍如何推理ResNet50模型。

执行下面步骤推理模型:

  1. 调用 Module::LoadFromFile API加载二进制模型文件,并生成runtime模型。示例如下,resnet50.hmm 为加载的二进制模型文件:

    auto module = tcim::Module::LoadFromFile("tcim_resnet50.hmm");
    
  2. 准备模型输入。调用 tcim::Module::GetInputNum 获取输入数据的数量。对每个输入:

    1. 调用 Module::GetInputName 获取输入名称。

    2. 调用 Module::GetInputInfo 获取输入信息,并调用 TensorInfo::AsContiguous 创建 TensorInfo 对象,将张量的内存布局更改为连续。

    3. 调用 Tensor::CreateHostTensor 为输入数据分配后摩设备内存。

    4. 定义一个 input_map,出入输入名称与输入数据的对应关系。

    示例如下:

    std::map<std::string, tcim::Tensor> input_map;
    // Get the total number of inputs
    int input_num = module.GetInputNum();
    std::cout << "Count of Input: " << input_num << std::endl;
    // For each input:
    for (int idx = 0; idx < input_num; idx++) {
      // Get the name of the input
      auto input_name = module.GetInputName(idx);
      // Get input data information
      auto input_info = module.GetInputInfo(input_name).AsContiguous();
      std::cout << "Input[" << input_name << "] " << input_info << std::endl;
      // Allocate memory on host CPU for storing input data
      auto input_tensor = tcim::Tensor::CreateHostTensor(input_info);
      // Create a map between input name and input tensor
      input_map.insert(std::pair<std::string, tcim::Tensor>(input_name, input_tensor));
    }
    
  3. 图像预处理。示例将 cat.png 图片调整为:

    • 图像格式从BGR转为YUV420SP。

    • 图像大小调整为 224 x 224 x 3。

    cv::Mat img_rgb;
    cv::Mat img_yuv;
    cv::Mat img_norm;
    // Load the image
    img_rgb = cv::imread("../data/cat.png");
    // Define normalization parameters for ImageNet
    const float mean[3] = {123.675f, 116.28f, 103.53f};
    const float std[3] = {58.395f, 57.12f, 57.375f};
    // Convert BGR to RGB, resize to 224x224 (standard for ResNet)
    cv::cvtColor(img_rgb, img_rgb, cv::COLOR_BGR2RGB);
    cv::resize(img_rgb, img_rgb, {224, 224});
    // Convert to float32 and normalize
    img_rgb.convertTo(img_norm, CV_32FC3);
    std::vector<cv::Mat> channels;
    cv::split(img_norm, channels);
    for (int i = 0; i < 3; ++i) {
      channels[i] = (channels[i] - mean[i]) / std[i];
    }
    // Convert from HWC (Height-Width-Channel) to CHW (Channel-Height-Width)
    for (auto& ch : channels) {
      ch = ch.reshape(1, 1);
    }
    cv::vconcat(channels, img_norm);
    // Calculate image size in bytes
    size_t img_bytes = img_norm.total() * img_norm.elemSize();
    LOG_INFO("img_bytes: {}", img_bytes);
    
  4. 准备模型输出。调用 Module::GetOutputNum 获取输出数据数量。对每个输出:

    1. 调用 Module::GetOutputName 获取输出名称。

    2. 调用 Module::GetOutputInfo 获取输出信息。

    3. 调用 Tensor::CreateHostTensor 为输出数据分配后摩设备内存,并调用 TensorInfo::AsContiguous 创建 TensorInfo 对象,将张量的内存布局更改为连续。

    4. 定义 output_map,插入输出名称与输出数据的对应关系。

    示例如下:

    // Create a map to store output data
    std::map<std::string, tcim::Tensor> output_map;
    // Get total number of outputs
    int output_num = module.GetOutputNum();
    std::cout << "Count of Output: " << output_num << std::endl;
    //For each output:
    for (int idx = 0; idx < output_num; idx++) {
      // Get the name of the output
      auto output_name = module.GetOutputName(idx);
      // Get the information of the output
      auto output_info = module.GetOutputInfo(output_name).AsContiguous();
      std::cout << "Output[" << output_name << "] " << output_info << std::endl;
      // Allocate memory on Houmo device for storing output data
      auto output_tensor = tcim::Tensor::CreateHostTensor(output_info);
      // Insert the output name and tensor into the output map
      output_map.insert(std::pair<std::string, tcim::Tensor>(output_name, output_tensor));
    }
    
  5. 设置输入数据到后摩设备内存中。

    1. 调用 Tensor::CreateDeviceTensor 在后摩设备端分配内存。

    2. 调用 Tensor::CopyTo 将输入数据从主机的拷贝到设备端。

    3. 调用 Module::SetInput 设置模型输入。

    示例如下:

    // Loop through each key-value pair in the input_map
    for (const auto& input : input_map) {
    
      // Allocate memory on Houmo device for storing input data
      auto device_tensor = tcim::Tensor::CreateDeviceTensor(input_info, input_info.MemSize());
      input.second.CopyTo(device_tensor);
      // Set each input with the key-value pair from the input_map
      module.SetInput(input.first, device_tensor);
    }
    
  6. 分别调用 Module::RunModule::Sync 推理和同步模型。示例如下:

    module.Run();
    module.Sync();
    
  7. 调用 Module::GetOutput 获取推理输出数据。示例如下:

    // Loop through each key-value pair in the output_map
    for (auto& output : output_map) {
      // Get each output with the key-value pair from the output_map
      module.GetOutput(output.first, output.second);
    }
    

4.2.5. 使用Python API推理模型

Python API可通过 tcim_lite 库访问:

import tcim_lite as tcim

用户无需自行申请推理计算所需的输入输出内存,默认使用主机内存。执行下面步骤使用Python API推理模型:

  1. 调用 load API加载二进制模型文件。示例如下,tcim_resnet50.hmm 为加载的二进制模型文件:

    module = tcim.runtime.load(tcim_resnet50.hmm)
    
  2. 模型预处理。示例将 cat.png 图像调整为:

    • 图像格式从BGR转为RGB。

    • 图像大小调整为 224 x 224。

    • 按通道进行标准化处理。

    • 数据变为 NCHW 格式。

    • 转换为 float16 类型以匹配模型输入要求。

    # Load the image
    input_data = cv2.imread("../../data/cat.jpg")
    # Convert image format from BGR to RGB
    input_data = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB)
    # Resize the image to (224, 224)
    input_data = cv2.resize(input_data, (224, 224))
    # Define the mean values for each RGB channel
    mean_arr = np.array([123.675, 116.28, 103.53])
    # Define the standard deviation values for each RGB channel
    std_arr = np.array([58.395, 57.12, 57.375])
    # Normalize the image
    image_norm = (image_rgb - mean_arr) / std_arr
    # Change the data layout from HWC to CHW
    image_norm = np.transpose(image_norm, (2, 0, 1))
    # Add a batch dimension
    image_norm = np.expand_dims(image_norm, axis=0)
    # Convert the data type to float16 to match model input requirements
    input_data = image_norm.astype(np.float16)
    
  3. 分别调用 get_input_nameget_input_info API获取输入tensor的名字和信息,再调用 set_input API设置模型输入。示例如下:

    input_num = module.get_num_inputs()
    for id in range(0, input_num):
        input_name = module.get_input_name(id)
        input_info = module.get_input_info(input_name).ascontiguous()
        module.set_input(input_name, input_data)
    
  4. 分别调用 runsync 推理和同步模型。

    module.run()
    module.sync()
    
  5. 准备模型输出,并获取输出数据。调用 get_num_outputs 获取输出数据的数量。对每个输出:

    1. 调用 get_output_name 获取输出名称。

    2. 调用 get_output_info 获取输出信息。

    3. 调用 get_output 获取输出数据。

    4. 调用 dequant 将输出数据反量化为float32类型tensor。

    5. 调用 numpy 获取反量化后tensor数据,并设置为输出数据。

    result_check = True
    # Get the totoal number of outputs
    output_num = module.get_num_outputs()
    # For each output:
    for id in range(0, output_num):
        # Get the output name
        output_name = module.get_output_name(id)
        # Get the information about output data
        output_info = module.get_output_info(output_name).ascontiguous().astype(np.float32)
        print("output[{}] shape = {}, dtype = {}, format = {}".format(output_name, output_info.shape, output_info.dtype, output_info.format.name))
        # Get the output data
        output_data = module.get_output(output_name).dequant().numpy()
    

有关API详细说明,参看《后摩大道® M50 TCIM开发者手册》。