DVPP + AIPP 加速数据预处理

背景:基于华为昇腾Atlas800T A2服务实现目标检测推理任务,要求使用ACL加载OM实现,数据预处理使用DVPP模块以加速预处理时间。

yolov8 原生推理接口主要包含以下预处理操作

  1. resize(1200012000 -> 40964096) #实际业务场景需要添加一层resize操作
  2. BGR→RGB 通道转换
  3. BHWC→BCHW 维度转换
  4. 转为连续内存数组
  5. 转换为 PyTorch Tensor
  6. 移动到指定设备(CPU/CUDA)
  7. uint8→fp16/32 类型转换
  8. 像素值归一化 (0-255 → 0.0-1.0)

下面给出完整的预处理方案,将 8 个步骤合理分配到 DVPP、AIPP 和 OM 推理三个环节

预处理步骤分工对照

# 原始步骤 (CPU/OpenCV) 迁移至 说明
1 Resize 12000→4096 DVPP 硬件缩放,零 CPU 开销
2 BGR→RGB AIPP CSC 色域转换覆盖
3 BHWC→BCHW AIPP input_format 自动完成
4 连续内存 DVPP 输出本身连续
5 → PyTorch Tensor 消除 OM 推理不需要
6 移动到设备 消除 全程 Device 内存
7 uint8→fp16/32 AIPP 隐式类型提升
8 /255 归一化 AIPP var_reci_chn=1/255
  1. AIPP 配置文件
    aipp.cfg
aipp_op {
    aipp_mode : static
    related_input_rank : 0
    # ---- 输入描述(对接 DVPP Resize 的输出)----
    input_format : YUV420SP_U8        # DVPP 解码/缩放 的标准输出格式
    src_image_size_w : 4096
    src_image_size_h : 4096

    # ----BGRRGBYUVRGB CSC 直接输出 RGB 通道序 ----
    csc_switch : true
    rbuv_swap_switch : false          # 输出 RGB;若模型需要 BGR 则设 true
    # BT.601 标准转换矩阵
    matrix_r0c0 : 298
    matrix_r0c1 : 0
    matrix_r0c2 : 409
    matrix_r1c0 : 298
    matrix_r1c1 : -100
    matrix_r1c2 : -208
    matrix_r2c0 : 298
    matrix_r2c1 : 516
    matrix_r2c2 : 0
    input_bias_0 : 16
    input_bias_1 : 128
    input_bias_2 : 128
    # ----归一化:(x - 0) × (1/255)[0.0, 1.0] ----
    min_chn_0 : 0
    min_chn_1 : 0
    min_chn_2 : 0
    var_reci_chn_0 : 0.00392157      # 1/255
    var_reci_chn_1 : 0.00392157
    var_reci_chn_2 : 0.00392157
}

AIPP 内部流水线顺序:输入 → CSC(②) → 归一化(⑧) → 类型转换(⑦) → 输出(NCHW=③),全在 AI Core 上零拷贝执行。

  1. ATC 模型转换
atc --model=model.onnx 
--framework=5 
--output=model_aipp 
--soc_version=Ascend310P3 
--input_shape="input:1,3,4096,4096" 
--insert_op_conf=aipp.cfg 
--input_format=NCHW
  1. 推理代码
import acl
import numpy as np
import time

# ============ 常量 ============
DEVICE_ID = 0
MODEL_PATH = "model_aipp.om"
IMG_PATH   = "test_resized.png"       # JPEG 编码文件
RESIZE_W, RESIZE_H = 4096, 4096
ACL_MEM_MALLOC_NORMAL_ONLY = 2
ACL_MEMCPY_HOST_TO_DEVICE = 1
ACL_MEMCPY_DEVICE_TO_HOST = 2

# ============ ACL 初始化 ============
acl.init()
acl.rt.set_device(DEVICE_ID)
context, _ = acl.rt.create_context(DEVICE_ID)
stream, _  = acl.rt.create_stream()

# ============ 加载 OM 模型 ============
model_id, _ = acl.mdl.load_from_file(MODEL_PATH)
model_desc  = acl.mdl.create_desc()
acl.mdl.get_desc(model_desc, model_id)

# ----------- 准备输出 buffer -----------
output_dataset = acl.mdl.create_dataset()
for i in range(acl.mdl.get_num_outputs(model_desc)):
  out_size = acl.mdl.get_output_size_by_index(model_desc, i)
  out_buf, _ = acl.rt.malloc(out_size, ACL_MEM_MALLOC_NORMAL_ONLY)
  out_data = acl.create_data_buffer(out_buf, out_size)
  acl.mdl.add_dataset_buffer(output_dataset, out_data)

# ============ DVPP 初始化 ============
dvpp_channel_desc = acl.media.dvpp_create_channel_desc()
acl.media.dvpp_create_channel(dvpp_channel_desc)

# ============ ① DVPP Decode:JPEG → YUV420SP ============
# 读取 JPEG 到 Device
with open(IMG_PATH, "rb") as f:
  jpeg_data = f.read()
jpeg_size = len(jpeg_data)

# 阶段1: 读取文件到设备
copy_start = time.time()
jpeg_dev, _ = acl.media.dvpp_malloc(jpeg_size)
acl.rt.memcpy(jpeg_dev, jpeg_size,
            acl.util.bytes_to_ptr(jpeg_data), jpeg_size,
            ACL_MEMCPY_HOST_TO_DEVICE)
copy_end = time.time()
copy_time = copy_end - copy_start
print(f"[阶段1] 读取文件到设备: {copy_time:.4f} 秒")

# 获取 JPEG 图片信息
W_orig, H_orig = 12000, 12000   # 已知尺寸,也可 dvpp_jpeg_get_image_info 查询
# 解码输出 buffer(YUV420SP = W*H*3/2,宽 128 对齐,高 16 对齐)
W_aligned = ((W_orig + 127) // 128) * 128
H_aligned = ((H_orig + 15) // 16) * 16
decode_size = W_aligned * H_aligned * 3 // 2
decode_dev, _ = acl.media.dvpp_malloc(decode_size)

# 配置解码输出图片描述
decode_pic = acl.media.dvpp_create_pic_desc()
acl.media.dvpp_set_pic_desc_data(decode_pic, decode_dev)
acl.media.dvpp_set_pic_desc_size(decode_pic, decode_size)
acl.media.dvpp_set_pic_desc_format(decode_pic, 1)             # 1 = YUV420SP_NV12
acl.media.dvpp_set_pic_desc_width(decode_pic, W_orig)
acl.media.dvpp_set_pic_desc_height(decode_pic, H_orig)
acl.media.dvpp_set_pic_desc_width_stride(decode_pic, W_aligned)
acl.media.dvpp_set_pic_desc_height_stride(decode_pic, H_aligned)

acl.media.dvpp_jpeg_decode_async(dvpp_channel_desc,
                                jpeg_dev, jpeg_size,
                                decode_pic, stream)
acl.rt.synchronize_stream(stream)

# ============ ① DVPP Resize:12000×12000 → 4096×4096 ============
RW_aligned = ((RESIZE_W + 15) // 16) * 16   # 4096 已对齐
RH_aligned = ((RESIZE_H + 1) // 2) * 2      # 4096 已对齐
resize_size = RW_aligned * RH_aligned * 3 // 2
resize_dev, _ = acl.media.dvpp_malloc(resize_size)

resize_input = decode_pic   # 复用解码输出描述

resize_output = acl.media.dvpp_create_pic_desc()
acl.media.dvpp_set_pic_desc_data(resize_output, resize_dev)
acl.media.dvpp_set_pic_desc_size(resize_output, resize_size)
acl.media.dvpp_set_pic_desc_format(resize_output, 1)
acl.media.dvpp_set_pic_desc_width(resize_output, RESIZE_W)
acl.media.dvpp_set_pic_desc_height(resize_output, RESIZE_H)
acl.media.dvpp_set_pic_desc_width_stride(resize_output, RW_aligned)
acl.media.dvpp_set_pic_desc_height_stride(resize_output, RH_aligned)

# 阶段2: DVPP resize操作
resize_cfg = acl.media.dvpp_create_resize_config()
resize_start = time.time()
acl.media.dvpp_vpc_resize_async(dvpp_channel_desc,
                               resize_input, resize_output,
                               resize_cfg, stream)
acl.rt.synchronize_stream(stream)
resize_end = time.time()
resize_time = resize_end - resize_start
print(f"[阶段2] DVPP resize: {resize_time:.4f} 秒 (从 {W_orig}×{H_orig} resize 到 {RESIZE_W}×{RESIZE_H})")

# ============ OM 推理(AIPP 自动执行 ②③⑦⑧) ============
input_dataset = acl.mdl.create_dataset()
in_data = acl.create_data_buffer(resize_dev, resize_size)
acl.mdl.add_dataset_buffer(input_dataset, in_data)

# 阶段3: OM推理
inference_start = time.time()
acl.mdl.execute(model_id, input_dataset, output_dataset)
inference_end = time.time()
inference_time = inference_end - inference_start
print(f"[阶段3] OM 推理: {inference_time:.4f} 秒 (输入大小: {RESIZE_W}×{RESIZE_H})")

# ============ 获取推理结果 ============
out_buf_ptr = acl.get_data_buffer_addr(
  acl.mdl.get_dataset_buffer(output_dataset, 0))
out_size = acl.get_data_buffer_size_v2(
  acl.mdl.get_dataset_buffer(output_dataset, 0))
out_host, _ = acl.rt.malloc_host(out_size)
acl.rt.memcpy(out_host, out_size, out_buf_ptr, out_size,
            ACL_MEMCPY_DEVICE_TO_HOST)
result = np.frombuffer(acl.util.ptr_to_bytes(out_host, out_size),
                     dtype=np.float32)
print("Inference result shape:", result.shape)

# 打印总耗时统计
print("\n" + "="*50)
print("各阶段耗时统计:")
print("="*50)
print(f"[阶段1] 读取文件到设备:   {copy_time:.4f} 秒")
print(f"[阶段2] DVPP resize:      {resize_time:.4f} 秒")
print(f"[阶段3] OM 推理:          {inference_time:.4f} 秒")
total_time = copy_time + resize_time + inference_time
print(f"总耗时:                  {total_time:.4f} 秒")
print("="*50)

# ============ 资源释放 ============
acl.media.dvpp_free(jpeg_dev)
acl.media.dvpp_free(decode_dev)
acl.media.dvpp_free(resize_dev)
acl.media.dvpp_destroy_pic_desc(decode_pic)
acl.media.dvpp_destroy_pic_desc(resize_output)
acl.media.dvpp_destroy_resize_config(resize_cfg)
acl.media.dvpp_destroy_channel(dvpp_channel_desc)
acl.media.dvpp_destroy_channel_desc(dvpp_channel_desc)
acl.mdl.unload(model_id)
acl.rt.destroy_stream(stream)
acl.rt.destroy_context(context)
acl.rt.reset_device(DEVICE_ID)
acl.finalize()


端到端数据总览

在这里插入图片描述

测试

300I Duo卡:在这里插入图片描述
910B4卡:
在这里插入图片描述

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐