SEM + RDK S100 部署全流程

:round_pushpin:感谢地平线工具链同学以及地瓜生态同学的大力支持

SEM:RoboOrchardLab/projects/sem/robotwin at master · HorizonRobotics/RoboOrchardLab · GitHub
飞书文档:https://horizonrobotics.feishu.cn/wiki/CT0dwlRDMiHkTNkypBLcYAM7nsb

1. 模型导出

1.1 量化友好修改

针对开源仓库中的 RoboOrchardLab-master/projects/sem/robotwin/onnx_scripts/export_onnx.py 中需要做如下修改,以对量化友好:

  1. 具有物理含义的枚举型输入在不溢出的情况下维持 int8/int16 定点输入:joint_relative_pos 和 timestep
...
data["joint_relative_pos"] = data["joint_relative_pos"].to(torch.int8)
...
timestep = timestep.to(torch.int16)
...
  1. 去除量化不友好的数值,对应修改两处,将float("-inf")-15
  1. 导出 onnx 模型
python3 onnx_scripts/export_onnx.py \
    --config config_sem_robotwin.py \
    --model /your/float_model_dir/ \
    --output_path /your/onnx_model \
    --num_joint 14 \
    --validate

1.2 校准数据准备

RoboOrchardLab-master/projects/sem/robotwin/onnx_scripts/export_onnx.py中添加代码如下,目的为导出 onnx 时 dump 模型部署的部分评测数据作为PTQ量化的校准数据,校准数据需经过前处理,样例代码如下:

def prepare_calib_data(dataset, data_processor, encoder, sample_dataset=True):
    os.makedirs("calibration_data_dir", exist_ok=True)
    for e_name in ENCODER_INPUT_NAMES:
        os.makedirs(os.path.join("calibration_data_dir", e_name), exist_ok=True)
    for d_name in DECODER_INPUT_NAMES:
        os.makedirs(os.path.join("calibration_data_dir", d_name), exist_ok=True)
    
    if sample_dataset:
        new_dataset = []
        # 均匀采样数据集,这里取100份数据作为校准数据(可调整)
        num_samples = 100
        indices = np.arange(0, len(dataset), len(dataset) // num_samples)[:num_samples]
        for idx in indices:
            new_dataset.append(dataset[idx])
        dataset = new_dataset
    for i in range(len(dataset)):
        # 校准数据需经过前处理
        data = data_processor(dataset[i])
        # 对齐onnx导出时的输入信息
        data["joint_relative_pos"] = data["joint_relative_pos"].to(torch.int8)
        data['imgs'] = data['imgs'].squeeze(0)
        data['depths'] = data['depths'].squeeze(0)
        for e_name in ENCODER_INPUT_NAMES:
            np.save(os.path.join("calibration_data_dir", e_name, f"{e_name}_{i}.npy"), data[e_name].cpu().numpy())
        image_feature, robot_feature = encoder(*[data[key] for key in ENCODER_INPUT_NAMES])
        data["image_feature"] = image_feature
        data["robot_feature"] = robot_feature
        data["timestep"] = torch.tensor([999], dtype=torch.int16)
        for d_name in DECODER_INPUT_NAMES:
            np.save(os.path.join("calibration_data_dir", d_name, f"{d_name}_{i}.npy"), data[d_name].cpu().numpy())
        # 不采样的话默认取前200份数据作为校准数据
        if not sample_dataset and i == 199:
            break
    logger.info("prepare calib data done")

def export_onnx(config, model, num_joint, output_path, validate=True):
    # 原代码不变
    prepare_calib_data(dataset, data_processor, encoder)

2. PTQ 量化

2.1 环境准备

  • 建议使用算法工具链标准缴付的 docker(GPU) 环境,会在校准时调用 GPU 加速
docker run -itd \
  --network host \
  --gpus all \
  --shm-size=15g \
  -v {oe_dir_path}:/open_explorer \
  -v {data_path}:/data// \
{docker_image_name}

2.2 校准数据准备

参考 1.2 节

2.3 校准 config 准备

  • 参考:模型量化编译 - OpenExplorer
  • 需要根据实际路径修改校准数据以及 onnx 路径,需要根据实际量化的需要调整量化精度,样例中给到的是全 int 16
# encoder config
calibration_parameters:
  cal_data_dir: ./calibration_data_dir/imgs;./calibration_data_dir/depths;./calibration_data_dir/pixels;./calibration_data_dir/projection_mat_inv;./calibration_data_dir/hist_robot_state;./calibration_data_dir/joint_scale_shift;./calibration_data_dir/joint_relative_pos
  quant_config: {
    "model_config": {
      "all_node_type": "int16"
    }
  }
compiler_parameters:
  advice: 0
  balance_factor: 0
  cache_mode: enable
  cache_path: 'cache'
  compile_mode: latency
  core_num: 1
  jobs: 64
  max_time_per_fc: 0
  optimize_level: O2
input_parameters:
  input_layout_train: NCHW;NCHW;NCHW;NCHW;NCHW;NCHW;NCHW
  input_name: imgs;depths;pixels;projection_mat_inv;hist_robot_state;joint_scale_shift;joint_relative_pos
  input_shape: 3x3x256x320;3x1x256x320;400x1x2;1x3x4x4;1x1x14x8;1x14x2;1x14x14
  input_type_rt: featuremap;featuremap;featuremap;featuremap;featuremap;featuremap;featuremap
  input_type_train: featuremap;featuremap;featuremap;featuremap;featuremap;featuremap;featuremap
  mean_value: ''
  scale_value: ''
  separate_batch: false
  std_value: ''
model_parameters:
  debug_mode: ''
  march: nash-e
  onnx_model: test_onnx_model/encoder.onnx
  output_model_file_prefix: encoder_opt
  output_nodes: ''
  remove_node_name: ''
  remove_node_type: Quantize;Dequantize;Cast;Softmax
  working_dir: ./model_output_calib
# decoder config
calibration_parameters:
  cal_data_dir: ./calibration_data_dir/noisy_action;./calibration_data_dir/image_feature;./calibration_data_dir/robot_feature;./calibration_data_dir/timestep;./calibration_data_dir/joint_relative_pos
  quant_config: {
    "model_config": {
      "all_node_type": "int16"
    }
  }
compiler_parameters:
  advice: 0
  balance_factor: 0
  cache_mode: enable
  cache_path: 'cache'
  compile_mode: latency
  core_num: 1
  jobs: 64
  max_time_per_fc: 0
  optimize_level: O2
input_parameters:
  input_layout_train: NCHW;NCHW;NCHW;NCHW;NCHW
  input_name: noisy_action;image_feature;robot_feature;timestep;joint_relative_pos
  input_shape: 1x64x14x8;1x3x400x256;1x14x1x256;1;1x14x14
  input_type_rt: featuremap;featuremap;featuremap;featuremap;featuremap
  input_type_train: featuremap;featuremap;featuremap;featuremap;featuremap
  mean_value: ''
  scale_value: ''
  separate_batch: false
  std_value: ''
model_parameters:
  debug_mode: ''
  march: nash-e
  onnx_model: test_onnx_model/decoder.onnx
  output_model_file_prefix: decoder_opt_resize
  output_nodes: ''
  remove_node_name: ''
  remove_node_type: Quantize;Dequantize;Cast;Softmax
  working_dir: ./model_output_calib

2.4 模型校准及编译

  • 使用如下指令分别校准及编译对应模型,从而得到 hbm 模型;
  • 输出的 log 会打印中间结果的相似度,也可以从输出的 .log 查看;
  • 模型校准取决于校准的数据量,如果无 GPU 加速,速度会比较慢
hb_compile -c encoder_calib_deploy.yaml
hb_compile -c decoder_calib_deploy.yaml

3. HBM 模型板端部署

  • 在开发机编译完得到 hbm 模型后,需将 hbm 模型手动拷贝至 S100 运行
  • 板端开发使用地瓜提供的 python 接口,可以方便地进行 hbm 模型的读取和推理(链接)

3.1 输入输出量化/反量化

  • 由于 sem 将 encoder 和 decoder 分为两部分导出,因此整体的输入输出量化/反量化流程如下:
  • 实际操作时,需参考接口定义,检查 hbm 模型输入输出的 name、type,根据 scale 进行量化及反量化操作
  • 量化/反量化公式如下:
import numpy as np
# 量化,此处以 int16 为例
x = input_float / scale
x = np.clip(np.round(x), -32768, 32767)
x = x.astype(np.int16)

# 反量化
float_data = int_data * scale

4. 一致性验证

  • 通过 dump GPU 推理的输入和中间输出以及最终输出,再进行 BPU 推理来对比每一步的余弦相似度

5. 真机部署调试