4. 动态量化与静态量化对比

任务目标

在RKNN工具链中分别完成YOLOv5s模型的动态量化与静态量化,对比两种方式在模型大小、推理耗时、精度(mAP)三个维度的差异,并给出选择建议。

环境依赖

  • RKNN-Toolkit2(≥1.6.0)
  • Rockchip Linux SDK(或模拟器环境)
  • YOLOv5s.onnx(输入640×640,80类COCO)
  • 校准数据集:COCO val2017中随机200张图片(仅静态量化需要)

核心实现

以下代码完成两种量化rknn模型的生成、推理与精度评估。动态量化直接调用build接口(不提供量化数据集);静态量化通过quantize_dataset传入校准数据。两者共用同一推理与评估函数,保证对比公平。

import os
import cv2
import numpy as np
from rknn.api import RKNN

# ========== 配置 ==========
ONNX_PATH = "yolov5s.onnx"
DYNAMIC_RKNN = "yolov5s_dynamic.rknn"
STATIC_RKNN = "yolov5s_static.rknn"
CALIB_DIR = "calib_imgs/"          # 200张校准图片
TEST_DIR = "test_imgs/"            # 验证图片(含GT标注)
IMG_SIZE = 640
CLASSES = 80
IOU_THRESH = 0.45
CONF_THRESH = 0.25

# ========== 1. 构建RKNN引擎(共用) ==========
def build_engine(platform="rk3588"):
    rknn = RKNN(verbose=False)
    rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform=platform)
    assert rknn.load_onnx(model=ONNX_PATH) == 0, "ONNX加载失败"
    return rknn

# ========== 2. 动态量化(无需校准数据) ==========
def build_dynamic():
    rknn = build_engine()
    # 关键:build时不传dataset,即动态量化
    assert rknn.build(do_quantization=True) == 0, "动态量化build失败"
    rknn.export_rknn(DYNAMIC_RKNN)
    rknn.release()
    print(f"[动态量化] 模型已保存: {DYNAMIC_RKNN}")

# ========== 3. 静态量化(需校准数据) ==========
def build_static():
    rknn = build_engine()
    # 关键:传入dataset参数,即静态量化
    assert rknn.build(do_quantization=True, dataset="./calib_dataset.txt") == 0, "静态量化build失败"
    rknn.export_rknn(STATIC_RKNN)
    rknn.release()
    print(f"[静态量化] 模型已保存: {STATIC_RKNN}")

# ========== 4. 推理与精度评估(共用) ==========
def evaluate(rknn_path, label="量化模型"):
    rknn = RKNN(verbose=False)
    rknn.load_rknn(rknn_path)
    rknn.init_runtime()

    tp, fp, total_gt = 0, 0, 0
    for img_name in os.listdir(TEST_DIR):
        img = cv2.imread(os.path.join(TEST_DIR, img_name))
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
        outputs = rknn.inference(inputs=[img])
        # 此处简化:实际需NMS+box解码,与GT计算mAP
        # 假设已实现yolo_postprocess()返回boxes, scores, classes
        boxes, scores, classes = yolo_postprocess(outputs, CONF_THRESH)
        gt_boxes, gt_classes = load_gt(img_name)   # 读取GT
        tp_i, fp_i = calc_tp_fp(boxes, scores, classes, gt_boxes, gt_classes, IOU_THRESH)
        tp += tp_i; fp += fp_i; total_gt += len(gt_boxes)

    precision = tp / (tp + fp + 1e-6)
    recall = tp / (total_gt + 1e-6)
    ap = (precision + recall) / 2  # 简化mAP,实际应逐类计算
    rknn.release()
    return ap

# ========== 5. 主流程 ==========
if __name__ == "__main__":
    # 生成两种量化模型
    build_dynamic()
    build_static()

    # 对比评估
    ap_dyn = evaluate(DYNAMIC_RKNN, "动态量化")
    ap_sta = evaluate(STATIC_RKNN, "静态量化")

    size_dyn = os.path.getsize(DYNAMIC_RKNN) / 1024
    size_sta = os.path.getsize(STATIC_RKNN) / 1024

    print("\n========== 对比结果 ==========")
    print(f"模型大小: 动态 {size_dyn:.1f}KB | 静态 {size_sta:.1f}KB")
    print(f"mAP(简化): 动态 {ap_dyn:.4f} | 静态 {ap_sta:.4f}")
    print("注:推理耗时需在板端用RKNN Benchmark工具实测")

运行与验证

  1. 准备校准列表:在calib_dataset.txt中逐行写入校准图片路径(仅静态量化需要)。
  2. 执行量化:运行上述脚本,生成yolov5s_dynamic.rknn与yolov5s_static.rknn。
  3. 板端耗时对比:将两个rknn模型分别推送到RK3588开发板,使用rknn_benchmark工具各运行100次取平均耗时。

判定标准

  • 功能通过:脚本无报错,两个rknn文件均成功生成且可被init_runtime()正常加载。
  • 效果差异:静态量化模型大小通常比动态量化小10%~20%(因权重与激活均定点化);静态量化mAP通常比动态量化高1~3个百分点(因动态量化仅权重量化,激活仍为浮点)。
  • 耗时结论:板端实测静态量化推理耗时通常比动态量化低15%~30%(因激活定点化后计算更快)。
选择建议:若追求精度且校准数据易得,优先静态量化;若需快速部署且无校准数据,动态量化是合理降级方案。两者均比FP16模型体积减少约75%,但静态量化精度损失更小。
动态量化 vs 静态量化(RKNN) 动态量化 静态量化 校准数据:不需要 量化范围:仅权重 激活值:保持FP16 模型体积:中等 推理速度:较慢 精度损失:较小 适用场景:快速部署 校准数据:需要200+张 量化范围:权重+激活 激活值:INT8定点 模型体积:更小 推理速度:更快 精度损失:更小 适用场景:正式部署 结论:静态量化在体积、速度、精度三方面均优于动态量化,但需要额外校准数据