LabHub
学习 学习路径 课程

AI瘦身失败事件

AI瘦身失败事件

在 LabHub 中继续学习

目标

将手写数字分类器实际转换为 INT8 ONNX,并拒绝使用错误校准数据生成的模型。在 Linux CPU 上测量准确率、延迟和内存;不涉及 MCU、NPU、GPU 或真实设备功耗测量。

为什么重要

即使模型很小,只要输入尺度或校准数据错误,它也可能看似正常运行却给出错误答案。只有分别检查文件大小、准确率、执行内核、延迟和内存,才能为部署决策提供依据。需要具备 Python、NumPy、JSON 基础。模型和依赖项已提供,无需联网安装。

步骤

  1. 写下数字识别器的输入契约——在 contract.json 中以整数保存 features=64、divisor=16、train=1077、calibration=360、test=360。请亲自确认所提供 digits.npz 数组的形状和 ID 数量。不要加入其他键。
  2. 修正把输入除以 255 的做法——实现 preprocess.py 中的 normalize(pixels)。把 uint8 N×64 数组转换为 float32,除以 16 后返回新数组。保持输入形状,不要修改原数组。无需直接执行该文件。
  3. 防止校准数据混入评估样本——在 calibration.json 的 ids 键中保存所提供 ids_calibration 的 360 个整数 ID,不得重复。顺序不限,但不要混入训练或评估 ID。不要混淆行位置与原始 ID。
  4. 给 AI 瘦身——编写并运行 quantize.py,生成 candidate.onnx。先把提供的 fp32.onnx 复制为 source.onnx,再取 calibration.json 中 ID 对应的 x_calibration,经 normalize 转换后由 CalibrationDataReader 读取。输入名称为 pixels,单个样本形状为 1×64。使用 quantize_static 的 QDQ、激活 QInt8、权重 QInt8、per_channel=True。系统会检查三个线性层的整数权重、真实整数内核执行,以及相对 FP32 不超过 0.01 的准确率损失。
  5. 记录数字被误判的方向——实现 metrics.py 中的 summarize(predictions, labels)。接收两个长度相同、非空且只含 0~9 整数的列表,只返回 count、correct、accuracy、confusion。count 和 correct 为整数,accuracy 为 0~1 的比例,confusion 为“真实类别行 × 预测类别列”的 10×10 整数数组。对角线之和等于 correct。
  6. 拒绝体积虽小但变笨的模型——使用同一份 x_test/16,在 CPU 上分别对 FP32、candidate.onnx 和提供的 bad-calibration.onnx 进行推理,与 y_test 比较后编写 regression.json。为 fp32、int8、bad 分别保存第 5 步的指标;reject_bad 写为布尔值,表示“FP32 准确率减去 bad 准确率”是否大于 0.01。字符串 true 不是布尔值。不要凭记忆填写准确率,要记录实际运行结果。
  7. 不要因为体积变小就宣称速度变快——在 benchmark.py 中实现 latency_stats(samples_us, batch_size)。对于正数且有限的时间列表,p50_us 为中位数,p95_us 为 nearest-rank 的第 ceil(0.95×n) 个值,samples_per_second 为 batch_size×n×1e6/时间总和。运行提供的 bench.py --work /root/quantization 生成 benchmark.json。每个模型、每种批大小 1/32 都需要 300 个原始时间、3 轮测量,并包含 threads=1、unit=us/batch、数据与模型 SHA-256,以及来源为 proc/VmHWM 的正数 peak_rss_bytes。汇总统计必须与原始样本一致。
  8. 同时发布依据与限制——在 release.json 中保存 model=candidate.onnx、sha256=当前候选模型哈希、max_file_bytes=32768、max_accuracy_drop=0.01、target_benchmark_required=true、universally_faster=false。不要加入其他键。实际模型必须不超过 32KiB,并再次检查准确率回归、统计数据及其与被测模型的关联。如果更换候选模型,也必须重新生成第 6~7 步的结果。

参考

所有编写和生成的文件都位于 /root/quantization 下。首先执行 mkdir -p /root/quantization,再执行 cd /root/quantization。Python 使用 /opt/onnx-lab/bin/python。脚本执行示例:/opt/onnx-lab/bin/python quantize.py。提供的材料目录为 /opt/lab/quantization。digits.npz 包含 x_train/x_calibration/x_test、y_train/y_calibration/y_test、ids_train/ids_calibration/ids_test 数组。fp32.onnx 是基准模型,bad-calibration.onnx 是必须拒绝的对照模型;reference.json、splits.json、DATA-LICENSE.json 分别记录契约、数据划分和来源。这些二进制材料已经包含在镜像中,不要将它们改写成文本。候选 ONNX 也应由转换器生成。第 7 步命令:/opt/onnx-lab/bin/python /opt/lab/quantization/bench.py --work /root/quantization。benchmark.json 由测量辅助程序生成,不要手工伪造样本。评分只检查数值计算和哈希关联,并不认证测量的真实性。提交的源文件和 JSON 各自必须是小于等于 64KiB 的普通文件;候选模型在检查时必须小于等于 128KiB,最终部署时必须小于等于 32KiB。学生函数会在独立进程中调用,请勿输出日志。需要使用前面步骤的函数时,请参考支持导入的答案和执行脚本。函数检查的单次调用时限为 10 秒。如果实验时间不足,请用“+时间”延长,并在结束前另行保存文件。会话结束后文件不会保留。

写下数字识别器的输入契约

在 contract.json 中以整数保存 features=64、divisor=16、train=1077、calibration=360、test=360。请亲自确认所提供 digits.npz 数组的形状和 ID 数量。不要加入其他键。

使用 np.load(..., allow_pickle=False) 和 data.files 查看数组名称,再通过 shape 和 len 确认契约。

修正把输入除以 255 的做法

实现 preprocess.py 中的 normalize(pixels)。把 uint8 N×64 数组转换为 float32,除以 16 后返回新数组。保持输入形状,不要修改原数组。无需直接执行该文件。

应先转换 dtype,再执行除法。仅仅因为数据是图像就使用 255,会与该模型的输入不符。

防止校准数据混入评估样本

在 calibration.json 的 ids 键中保存所提供 ids_calibration 的 360 个整数 ID,不得重复。顺序不限,但不要混入训练或评估 ID。不要混淆行位置与原始 ID。

可以使用 tolist() 将 NumPy 整数数组转换为能够写入 JSON 的列表。

给 AI 瘦身

编写并运行 quantize.py,生成 candidate.onnx。先把提供的 fp32.onnx 复制为 source.onnx,再取 calibration.json 中 ID 对应的 x_calibration,经 normalize 转换后由 CalibrationDataReader 读取。输入名称为 pixels,单个样本形状为 1×64。使用 quantize_static 的 QDQ、激活 QInt8、权重 QInt8、per_channel=True。系统会检查三个线性层的整数权重、真实整数内核执行,以及相对 FP32 不超过 0.01 的准确率损失。

get_next() 每次返回一个输入字典,结束时返回 None。受保护的原始文件旁无法写入临时文件,因此请使用工作副本。

记录数字被误判的方向

实现 metrics.py 中的 summarize(predictions, labels)。接收两个长度相同、非空且只含 0~9 整数的列表,只返回 count、correct、accuracy、confusion。count 和 correct 为整数,accuracy 为 0~1 的比例,confusion 为“真实类别行 × 预测类别列”的 10×10 整数数组。对角线之和等于 correct。

如果把真实类别 1 预测为 2,则 [1][2] 增加。即使颠倒行列,准确率也相同,因此请检查非对角单元格。

拒绝体积虽小但变笨的模型

使用同一份 x_test/16,在 CPU 上分别对 FP32、candidate.onnx 和提供的 bad-calibration.onnx 进行推理,与 y_test 比较后编写 regression.json。为 fp32、int8、bad 分别保存第 5 步的指标;reject_bad 写为布尔值,表示“FP32 准确率减去 bad 准确率”是否大于 0.01。字符串 true 不是布尔值。不要凭记忆填写准确率,要记录实际运行结果。

使用 ORT CPUExecutionProvider,并将 intra/inter 线程均设为 1;把输出的 argmax(axis=1) 传给 summarize。

不要因为体积变小就宣称速度变快

在 benchmark.py 中实现 latency_stats(samples_us, batch_size)。对于正数且有限的时间列表,p50_us 为中位数,p95_us 为 nearest-rank 的第 ceil(0.95×n) 个值,samples_per_second 为 batch_size×n×1e6/时间总和。运行提供的 bench.py --work /root/quantization 生成 benchmark.json。每个模型、每种批大小 1/32 都需要 300 个原始时间、3 轮测量,并包含 threads=1、unit=us/batch、数据与模型 SHA-256,以及来源为 proc/VmHWM 的正数 peak_rss_bytes。汇总统计必须与原始样本一致。

测量由辅助程序完成,统计函数由你亲自编写。该步骤的执行命令位于参考部分。不要混淆文件大小与整个进程的 HWM。

同时发布依据与限制

在 release.json 中保存 model=candidate.onnx、sha256=当前候选模型哈希、max_file_bytes=32768、max_accuracy_drop=0.01、target_benchmark_required=true、universally_faster=false。不要加入其他键。实际模型必须不超过 32KiB,并再次检查准确率回归、统计数据及其与被测模型的关联。如果更换候选模型,也必须重新生成第 6~7 步的结果。

使用 hashlib.sha256 对模型字节进行哈希。本实验的文件预算不是 RSS 预算,也不要声称已经完成目标设备性能验证。