跳至主要内容

M05-2 | 校準:樣本數怎麼影響結果,校準時 BN 會動嗎

Open In Colab

校準資料決定每個 observer 看到的範圍,也就決定 scale。資料太少,範圍估不準。這本 notebook 用 M05-1 的輸出 SQNR 量兩件事:

import copy

import matplotlib.pyplot as plt
import torch
from matplotlib_inline.backend_inline import set_matplotlib_formats

from common.data import calibration_batches, trained_tiny_net
from common.graph_utils import sqnr
from common.models import example_inputs
from common.pt2e import convert_pt2e, export_module, get_xnnpack_quantizer, prepare_pt2e, quiet
from common.viz import table

quiet()
_ = torch.manual_seed(0)
set_matplotlib_formats("svg")
BLUE, INK, MUTED = "#2a78d6", "#0b0b0b", "#52514e"

模型與評估資料都和 M05-1 相同(trained_tiny_net()、calibration_batches(64, seed=9)):

model = trained_tiny_net()
gm = export_module(model, example_inputs())
quantizer, _ = get_xnnpack_quantizer(is_per_channel=True)
(x_eval,) = next(calibration_batches(64, seed=9))  # 固定的評估輸入,和所有校準資料都不同
with torch.no_grad():
    y_ref = model(x_eval)

A. 校準樣本數與 SQNR

每個樣本數 \(n\),用 5 個不同的 seed 產生 5 份校準資料,各做一次 prepare、校準、convert:

SEEDS = (1, 2, 3, 4, 5)
SIZES = (16, 32, 64, 128, 256, 512)

results = {}
for n in SIZES:
    results[n] = []
    for seed in SEEDS:
        prepared = prepare_pt2e(copy.deepcopy(gm), quantizer)
        with torch.no_grad():
            for batch in calibration_batches(n, seed=seed):
                prepared(*batch)
            results[n].append(sqnr(y_ref, convert_pt2e(prepared)(x_eval)))

mean = {n: sum(v) / len(v) for n, v in results.items()}
spread = {n: max(v) - min(v) for n, v in results.items()}
畫圖的程式
fig, ax = plt.subplots(figsize=(6.4, 3.4))
for i, (n, scores) in enumerate(results.items()):
    ax.scatter([n] * len(scores), scores, s=18, color=BLUE, alpha=0.35, linewidths=0,
               label="one calibration set" if i == 0 else None)
ax.plot(SIZES, [mean[n] for n in SIZES], color=BLUE, lw=2, marker="o", ms=5, label="mean of 5 sets")
ax.legend(loc="lower right", frameon=False, labelcolor=MUTED, fontsize=9)
ax.set_xscale("log", base=2)
ax.set_xticks(SIZES, [str(n) for n in SIZES])
ax.set_xlabel("calibration samples", color=MUTED)
ax.set_ylabel("output SQNR (dB)", color=MUTED)
ax.grid(axis="y", color="#e6e5e1", lw=0.8)
ax.spines[["top", "right"]].set_visible(False)
ax.tick_params(colors=MUTED)
plt.show()

table([[n, f"{mean[n]:.2f}", f"{min(results[n]):.2f} ~ {max(results[n]):.2f}", f"{spread[n]:.2f}"] for n in SIZES],
      ["校準樣本數", "平均", "min ~ max", "波動(max − min)"])
校準樣本數 平均 min ~ max 波動(max − min)
16 18.05 13.17 ~ 31.30 18.13
32 20.93 15.28 ~ 31.84 16.55
64 23.50 20.73 ~ 31.60 10.87
128 28.83 26.42 ~ 31.57 5.14
256 31.41 29.67 ~ 34.06 4.39
512 31.44 29.96 ~ 32.78 2.82
assert spread[SIZES[0]] > 2 * spread[SIZES[-1]]  # 樣本少時波動大得多
assert mean[SIZES[0]] < mean[SIZES[-1]]

圖上每個淺色點是一份校準資料的結果,深色線是 5 份的平均:

  • 樣本少時,不只平均較低,更重要的是換一份校準資料結果就差很多。同一個模型、同一個設定,只因為校準資料不同,SQNR 可以差十幾 dB。
  • 樣本多了,點才收攏。所以要看的是波動,不只是一次的結果:只跑一次、剛好抽到好的一份,會高估量化的品質。
  • 這份合成資料同分布、很乾淨;真實資料需要多少樣本,取決於資料本身,這裡的數字不能直接套用。

實務上:樣本數加到波動收斂為止;懷疑量化品質時,先換幾份校準資料重跑,看結果穩不穩。

B. 校準時 BN 會不會影響結果

M04-1 看過,prepare_pt2e 一開始就把 BN 折進 conv。所以校準時圖上已經沒有 BN:

prepared = prepare_pt2e(copy.deepcopy(gm), quantizer)
n_bn = sum("batch_norm" in str(n.target) for n in prepared.graph.nodes)
print("prepare 之後的 batch_norm 數量:", n_bn)
assert n_bn == 0
prepare 之後的 batch_norm 數量: 0

這有什麼影響?比較同一筆樣本「單獨跑」和「和另外 15 筆同一個 batch 跑」的輸出。 對照組是原模型的 train 模式:BN 用當下 batch 的統計,輸出會隨同批的其他樣本改變。

(x,) = next(calibration_batches(16, seed=5))
bn_train = copy.deepcopy(model).train()
with torch.no_grad():
    alone = torch.cat([prepared(x[i : i + 1]) for i in range(len(x))])
    together = prepared(x)
    bn_alone = torch.cat([bn_train(x[i : i + 1]) for i in range(len(x))])
    bn_together = bn_train(x)
d_prepared = float((alone - together).abs().max())
d_bn = float((bn_alone - bn_together).abs().max())
table([["prepare 之後的圖(校準時用的)", f"{d_prepared:.2e}"], ["對照:原模型的 train 模式", f"{d_bn:.2e}"]],
      ["", "單獨跑 vs 和 15 筆同批:最大輸出差"])
單獨跑 vs 和 15 筆同批:最大輸出差
prepare 之後的圖(校準時用的) 2.29e-05
對照:原模型的 train 模式 6.79e+00
assert d_prepared < 1e-3 < d_bn
  • 校準用的圖裡,BN 已經是固定的乘加(用 running stats 折進 conv),這個模型也沒有其他跨 batch 的運算,所以輸出只取決於樣本自己,差別只有浮點誤差。
  • 對照組的輸出隨同批樣本改變。如果校準時 BN 還這樣運作,observer 看到的範圍會跟著 batch 的組成漂移。PTQ 的圖沒有 BN,天生避開了這個問題(前提是 export 時用 eval 模式)。

所以 PTQ 校準時不必擔心 BN,對這個模型而言,校準的 batch size 只影響跑多快。QAT 才有 BN 統計的問題(M06)。

小結

  • 校準樣本少時,結果隨校準資料大幅波動;要看波動,不是只看一次的結果。
  • PTQ 校準用的圖沒有 BN,輸出不受 batch 組成影響。