系列导读:上一篇讲了INT8量化的原理和校准集选择,这一篇聚焦"量化后精度不达标怎么办"——用 accuracy_analysis 做逐层诊断、可视化精度分布、定位问题算子,最终通过混合量化把精度拉回来。这是量化调试的完整闭环,工程上最有含金量的一篇。


一、量化调试的整体思路

量化后精度下降时,很多人的第一反应是"换个更好的校准集"或者"不量化了"。这两种做法都过于粗糙。正确的姿势是:

量化后精度下降
      │
      ▼
Step1:定量评估——掉了多少?哪个指标掉了?
      │
      ▼
Step2:逐层诊断——哪几层量化误差最大?
      │
      ▼
Step3:归因分析——是校准集问题?算法问题?还是特定算子问题?
      │
      ▼
Step4:针对性修复——换算法 / 混合量化 / 补充校准集
      │
      ▼
Step5:回归验证——修复后整体精度是否恢复?有无引入新问题?

本篇按这五步展开,每一步都有具体代码和判断标准。


二、Step1:定量评估精度损失

2.1 分类模型:Top-1 / Top-5 准确率对比

# eval_classification.py
from rknn.api import RKNN
import numpy as np
import os
from PIL import Image

def preprocess(img_path, input_size=224):
    img = Image.open(img_path).convert("RGB").resize((input_size, input_size))
    return np.array(img, dtype=np.uint8)[np.newaxis]  # shape: (1, H, W, 3)

def evaluate_topk(rknn, img_dir, label_file, k=5):
    with open(label_file) as f:
        labels = {line.split()[0]: int(line.split()[1]) for line in f}

    top1_correct = top5_correct = total = 0

    for fname, gt_label in labels.items():
        img_path = os.path.join(img_dir, fname)
        if not os.path.exists(img_path):
            continue

        inp = preprocess(img_path)
        outputs = rknn.inference(inputs=[inp])
        probs = outputs[0][0]

        top5 = np.argsort(probs)[::-1][:5]
        if top5[0] == gt_label:
            top1_correct += 1
        if gt_label in top5:
            top5_correct += 1
        total += 1

    return top1_correct / total, top5_correct / total

# ── 评估 FP 模型(PC 端模拟,不量化)──
rknn_fp = RKNN()
rknn_fp.load_onnx("model.onnx")
rknn_fp.build(do_quantization=False)
rknn_fp.init_runtime()
top1_fp, top5_fp = evaluate_topk(rknn_fp, "./val_images", "./val_labels.txt")
print(f"FP 精度  Top-1: {top1_fp:.4f}  Top-5: {top5_fp:.4f}")
rknn_fp.release()

# ── 评估 INT8 量化模型 ──
rknn_int8 = RKNN()
rknn_int8.load_rknn("model.rknn")
rknn_int8.init_runtime()
top1_int8, top5_int8 = evaluate_topk(rknn_int8, "./val_images", "./val_labels.txt")
print(f"INT8 精度 Top-1: {top1_int8:.4f}  Top-5: {top5_int8:.4f}")
print(f"精度损失  Top-1: {(top1_fp - top1_int8):.4f}")
rknn_int8.release()

2.2 检测模型:mAP 对比

检测模型精度评估需要结合 COCO 格式标注,通常借助 pycocotools

# 核心流程(以 YOLOv8 为例)
# 1. 遍历验证集,每张图片跑 RKNN 推理
# 2. 后处理(NMS)得到检测框
# 3. 转换为 COCO 格式的预测结果 JSON
# 4. 用 pycocotools 计算 mAP@0.5 和 mAP@0.5:0.95

from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval

coco_gt = COCO("./annotations/instances_val2017.json")
coco_dt = coco_gt.loadRes("./predictions_int8.json")

coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
coco_eval.evaluate()
coco_eval.accumulate()
coco_eval.summarize()
# 输出:mAP@0.5, mAP@0.5:0.95 等指标

💡 快速评估技巧:如果没有完整验证集,可以用 100-200 张有标注的图片做快速估算,不用跑完整 COCO 5000 张,开发阶段够用。


三、Step2:accuracy_analysis 逐层诊断

3.1 完整诊断脚本

# run_accuracy_analysis.py
from rknn.api import RKNN
import numpy as np
from PIL import Image

def prepare_input(img_path, size=224):
    img = Image.open(img_path).convert("RGB").resize((size, size))
    arr = np.array(img, dtype=np.float32)
    # 不做归一化,因为 pass_through=0 时 SDK 内部处理
    return arr[np.newaxis]  # (1, H, W, 3)

rknn = RKNN(verbose=False)
rknn.config(
    mean_values=[[123.675, 116.28, 103.53]],
    std_values=[[58.395, 57.12, 57.375]],
    target_platform="rk3588",
    quantized_dtype="asymmetric_quantized-8",
    quantized_algorithm="mmse",
)
rknn.load_onnx("model.onnx")
rknn.build(do_quantization=True, dataset="./dataset.txt")

# 准备测试输入(建议用几张有代表性的图片)
test_input = prepare_input("./test_sample.jpg")

# 运行逐层精度分析
# 结果输出到 ./snapshot 目录
rknn.accuracy_analysis(
    inputs=[test_input],
    output_dir="./snapshot",
    target=None          # None = PC 端模拟分析,不需要连板子
)

rknn.release()
print("✅ 精度分析完成,查看 ./snapshot 目录")

3.2 解读 snapshot 输出文件

分析完成后,./snapshot 目录下有以下文件:

snapshot/
├── model_float/           # 各层 FP 输出的 npy 文件
├── model_quantized/       # 各层 INT8 量化后输出的 npy 文件
└── accuracy_analysis.csv  # 核心报告文件 ← 重点看这里

accuracy_analysis.csv 的关键列:

列名 含义 判断标准
layer_name 算子/层名称
cos_similarity 余弦相似度(FP vs INT8) >0.99 正常,<0.95 需关注
mean_error 平均绝对误差 越小越好
error_rate 相对误差率 <5% 可接受
target_dtype 该层当前量化类型 float16 / int8

3.3 用 Python 快速可视化精度分布

# visualize_accuracy.py
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 支持中文

df = pd.read_csv("./snapshot/accuracy_analysis.csv")

# 绘制逐层余弦相似度曲线
fig, axes = plt.subplots(2, 1, figsize=(16, 10))

# 图1:余弦相似度
axes[0].plot(df["cos_similarity"].values, color="#2196F3", linewidth=1.5)
axes[0].axhline(y=0.99, color="green", linestyle="--", label="阈值 0.99")
axes[0].axhline(y=0.95, color="red", linestyle="--", label="警戒线 0.95")
axes[0].set_title("逐层余弦相似度(越接近1越好)")
axes[0].set_ylabel("Cosine Similarity")
axes[0].legend()
axes[0].set_ylim(0.8, 1.01)

# 图2:标记问题层(cos_sim < 0.95)
problem_layers = df[df["cos_similarity"] < 0.95]
colors = ["red" if s < 0.95 else "#90CAF9" for s in df["cos_similarity"]]
axes[1].bar(range(len(df)), df["cos_similarity"].values, color=colors, width=1.0)
axes[1].axhline(y=0.95, color="red", linestyle="--")
axes[1].set_title(f"量化精度分布(红色为问题层,共 {len(problem_layers)} 层)")
axes[1].set_ylabel("Cosine Similarity")

plt.tight_layout()
plt.savefig("accuracy_analysis.png", dpi=150)
print(f"⚠️ 发现 {len(problem_layers)} 个问题层:")
print(problem_layers[["layer_name", "cos_similarity"]].to_string())

四、Step3:归因分析——三类典型问题模式

拿到逐层数据后,根据问题层的分布位置来判断根本原因:

模式一:误差在浅层就出现,且逐层累积放大

Layer 1:  cos=0.9998  ← 正常
Layer 2:  cos=0.9995  ← 正常
Layer 3:  cos=0.9721  ← 开始出问题
Layer 4:  cos=0.9203  ← 累积放大
...
Layer 20: cos=0.7841  ← 严重失真

原因:前几层的激活值分布异常(比如有极端大值或极端小值),导致 scale 计算不准,误差逐层叠加。

解决:检查校准集,确认是否覆盖了激活值极端情况;或对第3层单独做混合量化(保留FP16)。

模式二:误差只在某几个孤立层出现

Layer 1-10:  cos ≈ 0.999  ← 全部正常
Layer 11:    cos = 0.871  ← 突然跌落(孤立问题层)
Layer 12-20: cos ≈ 0.996  ← 恢复正常

原因:该层特有的激活值分布极不均匀(可能是残差连接输出、BatchNorm 等),导致量化范围统计失准。

解决:对该孤立层单独做混合量化,通常就能解决。

模式三:误差在靠近输出层的位置出现

Layer 1-18:  cos ≈ 0.999  ← 全部正常
Layer 19:    cos = 0.934  ← 输出层前
Layer 20:    cos = 0.891  ← 最终输出

原因:输出层附近的激活值范围往往比中间层更宽(分类的 logit、检测的坐标回归值等),INT8 的 256 个台阶不够细。

解决:让最后 1-2 层保持 FP16,性能影响极小但精度提升显著。


五、Step4:混合量化完整操作流程

5.1 两阶段混合量化 API

# hybrid_quantization.py
from rknn.api import RKNN

rknn = RKNN(verbose=False)
rknn.config(
    mean_values=[[123.675, 116.28, 103.53]],
    std_values=[[58.395, 57.12, 57.375]],
    target_platform="rk3588",
    quantized_dtype="asymmetric_quantized-8",
    quantized_algorithm="mmse",
)
rknn.load_onnx("model.onnx")

# ── 第一阶段:生成量化配置文件 ──
rknn.hybrid_quantization_step1(
    dataset="./dataset.txt",
    proposal=True,                          # 自动提出问题层建议
    proposal_dataset="./dataset.txt"
)
# 执行后生成三个文件:
# model.quantization.json  ← 模型结构描述
# model.quantization.data  ← 量化数据
# model.quantization.cfg   ← 量化配置 ← 这里手动编辑

rknn.release()

5.2 手动编辑量化配置文件

打开 model.quantization.cfg,找到问题层对应的条目:

{
    "layer_name": "layer3/conv/Conv",
    "dtype": "int8"           ← 将此行改为 "float16"
},
{
    "layer_name": "fc/MatMul",
    "dtype": "int8"           ← 输出层也改为 "float16"
}

5.3 第二阶段:生成混合量化模型

# hybrid_quantization_step2.py
from rknn.api import RKNN

rknn = RKNN(verbose=False)
rknn.config(
    mean_values=[[123.675, 116.28, 103.53]],
    std_values=[[58.395, 57.12, 57.375]],
    target_platform="rk3588",
)

# ── 第二阶段:按修改后的 cfg 生成最终模型 ──
rknn.hybrid_quantization_step2(
    model_input="model.quantization.json",
    data_input="model.quantization.data",
    model_quantization_cfg="model.quantization.cfg",
    export_rknn_model_path="model_hybrid.rknn"   # 输出混合量化模型
)

rknn.release()
print("✅ 混合量化模型生成:model_hybrid.rknn")

5.4 混合量化的性能代价

混合量化后,FP16 层的推理时间会比 INT8 层慢,但通常影响有限:

配置 推理耗时(YOLOv8n) mAP@0.5
纯 INT8 4.5ms 35.8%
最后2层 FP16 5.1ms 36.9%
最后5层 FP16 6.8ms 37.1%

经验规律:把问题层改为 FP16 通常能以 10-20% 的延迟代价换回 80% 以上的精度损失。精度恢复性价比最高的往往是"只改最后 1-2 层"。


六、Step5:回归验证

混合量化后,不能只看目标层的精度,要做完整的回归验证:

# regression_test.py
# 对比三个版本的整体精度,确认修复有效且无副作用

models = {
    "FP(基准)":       "model_fp.rknn",     # do_quantization=False
    "INT8 纯量化":      "model_int8.rknn",
    "INT8 混合量化":    "model_hybrid.rknn",
}

results = {}
for name, path in models.items():
    rknn = RKNN()
    rknn.load_rknn(path)
    rknn.init_runtime()
    top1, top5 = evaluate_topk(rknn, "./val_images", "./val_labels.txt")
    results[name] = {"Top-1": top1, "Top-5": top5}
    rknn.release()

print("\n===== 回归验证结果 =====")
for name, r in results.items():
    print(f"{name:20s}  Top-1: {r['Top-1']:.4f}  Top-5: {r['Top-5']:.4f}")

七、常用调试命令速查

# 查看 .rknn 模型的基本信息(输入输出、算子列表)
python3 -c "
from rknn.api import RKNN
rknn = RKNN()
rknn.load_rknn('model.rknn')
rknn.list_support_info()
rknn.release()
"

# 查看 snapshot 中哪些层 cos_similarity < 0.95
python3 -c "
import pandas as pd
df = pd.read_csv('./snapshot/accuracy_analysis.csv')
bad = df[df['cos_similarity'] < 0.95]
print(bad[['layer_name','cos_similarity','mean_error']].to_string())
"

# 对比两层的输出分布差异
python3 -c "
import numpy as np
fp  = np.load('./snapshot/model_float/layer_11_output.npy')
q   = np.load('./snapshot/model_quantized/layer_11_output.npy')
print('FP  均值/方差:', fp.mean(), fp.std())
print('INT8 均值/方差:', q.mean(), q.std())
print('最大绝对误差:', abs(fp - q).max())
"

八、精度调试决策流程(完整版)

量化后精度下降
      │
      ▼
掉点 < 2%? ──是──→ 换 mmse 算法 ──→ 满足要求则部署
      │
     否
      ▼
运行 accuracy_analysis
      │
      ├─ 浅层就出现误差累积 ──→ 检查/扩充校准集
      │                          对第一个问题层做混合量化
      │
      ├─ 孤立层出现误差 ──────→ 仅对该层设为 FP16
      │
      └─ 输出层附近误差 ──────→ 最后 1-2 层设为 FP16
                  │
                  ▼
            运行回归验证
                  │
            精度满足要求?
                  │
                  ├─ 是 → 部署
                  └─ 否 → 逐步扩大 FP16 覆盖层数
                           仍不满足 → 重新采集校准集

九、总结与下篇预告

本篇完整覆盖了量化调试的五步闭环:

  • 用 Top-1/mAP 定量评估精度损失
  • accuracy_analysis 逐层定位问题
  • 识别三类典型误差模式并归因
  • 用两阶段混合量化 API 针对性修复
  • 回归验证确认修复效果

至此,量化三部曲(原理→实战→调试)全部完成,你已经具备了处理任意模型量化精度问题的完整能力。

下一篇(系列第 7 篇)进入性能优化主题——多线程异步推理:如何利用 RK3588S 三核 NPU 做多模型并发,让总吞吐量真正达到 6 TOPS。


本系列文章列表(持续更新)


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐