INT8・INT4量子化で精度はどう変わる?FashionMNIST 1万枚でfake quantization

実験日: 2026-08-24
環境: RTX 5070 Ti / WSL2 / PyTorch 2.13.0+cu130

量子化はmodelを小さく高速にできる手法として紹介されます。しかし「4 bitならFP32の1/8」という容量計算だけでは、predictionが何枚変わるか分かりません。また、scaleをtensor全体で1個にするかchannelごとに持つかで誤差が変わります。

INT4なら容量は大きく削れますが、predictionが保たれるかは別に確認する必要があります。

前の記事で学習・ONNX exportしたFashionMNIST CNNを使い、weightだけをsymmetric INT8/INT4へ丸めてFP32へ戻すfake quantizationを実行しました。10,000枚で元modelと比較すると、INT8 per-channelは18枚だけpredictionが変化しaccuracy 86.50%を維持しました。INT4 per-tensorは1,026枚変化し81.99%、per-channelなら482枚・85.95%まで回復しました。

INT8は保ち、INT4は方式で差が出た

方式 accuracy FP32から変化 平均logit差 理論parameter容量
FP32 86.50% 0 0 421.3 KiB
INT8 per-tensor 86.55% 38/10,000 0.0369 103.7 KiB
INT8 per-channel 86.50% 18/10,000 0.0235 103.7 KiB
INT4 per-tensor 81.99% 1,026/10,000 0.7579 52.1 KiB
INT4 per-channel 85.95% 482/10,000 0.3331 52.1 KiB
FashionMNIST CNNのINT8・INT4 weight-only fake quantizationで精度、prediction変化、理論容量を比較
FashionMNIST CNNのINT8・INT4 weight-only fake quantizationで精度、prediction変化、理論容量を比較

今回は「fake quantization」

この実験の範囲を最初に明確にします。

weight      INT8またはINT4へ丸める
activation  FP32のまま
bias        FP32のまま
inference   丸めたweightをFP32へ戻して通常のPyTorch演算

整数のmatrix multiplication kernelは実行していません。したがって推論速度は測らず、「INT4だから8倍速い」とも主張しません。

測れるのは、有限bitへweightを丸めたときの数値誤差、prediction変化、accuracy、理論的なpacked weight容量です。実deploymentにはinteger kernel、scale metadata、packing、operator対応が必要です。

symmetric quantizationの式

signed b-bit integerの正側最大値を次で決めます。

qmax = 2^(b-1) - 1

INT8 qmax = 127
INT4 qmax = 7

weightの最大絶対値からscaleを作ります。

scale = max(|w|) / qmax
q = clip(round(w / scale), -qmax, qmax)
w_hat = q × scale

qが量子化整数、w_hatがdequantized FP32近似値です。zero pointを0に固定するためsymmetricと呼びます。

Python実装です。

qmax = 2 ** (bits - 1) - 1
scale = weight.abs().max() / qmax
q = torch.clamp(torch.round(weight / scale), -qmax, qmax)
dequantized = q * scale

INT4は使えるlevelが15個しかない

今回のsymmetric定義ではINT8は-127~127、INT4は-7~7を使います。

INT8: 255 levels
INT4:  15 levels

bitを半分にすると表現点は半分ではなく、255から15へ大幅に減ります。weight分布が広いとscaleが大きくなり、小さなweightが同じ値や0へ丸められます。

これがINT4 per-tensorでaccuracyが4.51ポイント下がった主因と考えられます。ただしlayer別activationやclass別誤りまで解析していないため、因果を断定しません。

per-tensorはscaleを1個だけ使う

per-tensor quantizationは、layer weight全体の最大絶対値を使います。

scale = weight.abs().max() / qmax

実装とmetadataが単純です。しかし1つのoutput channelだけ大きなweightを持つと、他channelもその大きいscaleへ合わせられ、細かな値を表せません。

今回のINT4 per-tensorです。

accuracy             81.99%
changed predictions   1,026
top-1 agreement       89.74%
max logit difference   3.519
mean logit difference  0.758

10枚に約1枚のpredictionがFP32から変わりました。全体accuracyだけでなくagreementを見ると、内部挙動の変化が大きいと分かります。

per-channelはoutputごとにscaleを持つ

Conv2d weightは[out_channels, in_channels, kh, kw]、Linearは[out_features, in_features]です。output channelごとに残りのaxisをreduceします。

dims = tuple(range(1, weight.ndim))
scale = weight.abs().amax(dim=dims, keepdim=True) / qmax

各filterやneuronのrangeへscaleを合わせられます。

INT4 per-channel結果です。

accuracy             85.95%
changed predictions     482
top-1 agreement       95.18%
max logit difference   1.671
mean logit difference  0.333

per-tensorよりaccuracyは3.96ポイント高く、prediction変化は1,026枚から482枚へ半減しました。mean logit differenceも0.758から0.333へ減っています。

bit数が同じでもgranularityが結果を大きく変えました。

INT8はほぼaccuracyを維持した

INT8 per-tensorは86.55%、per-channelは86.50%でした。FP32の86.50%とほぼ同じです。

per-tensorが0.05ポイント高いのは、量子化でmodelが改善したと断定できる差ではありません。10,000枚中、正解数が5枚増えただけです。丸めで一部の誤答が正解へ変わり、別の正解が誤答へ変わった結果です。

prediction変化を見ると差があります。

INT8 per-tensor   38枚変化
INT8 per-channel  18枚変化

accuracyが同じでも、per-channelの方がFP32 modelに近い出力です。

layer別weight MSEもper-channelが小さい

mean squared errorをlayerごとに保存しました。最大の差があった最初のConv2dです。

INT8 per-tensor    2.92e-6
INT8 per-channel   0.93e-6

INT4 per-tensor    8.88e-4
INT4 per-channel   3.07e-4

Linear 1568→64でもINT4は1.72e-4から4.50e-5へ減りました。channelごとのrange調整がweight reconstruction errorを一貫して下げています。

ただしweight MSEが小さいほどtask accuracyが必ず高いとは限りません。layer sensitivityやactivation distributionが異なるからです。最終判断はvalidation/test metricで行います。

理論容量はFP32の約1/4と1/8

modelのweight element数へbit数を掛け、biasはFP32のままとして計算しました。

FP32 raw parameters  約421.3 KiB
INT8 weights         約103.7 KiB
INT4 weights          約52.1 KiB

INT8は約24.6%、INT4は約12.4%です。biasがFP32なので厳密に1/4、1/8ではありません。

さらに実fileには次が加わります。

  • per-channel scale
  • tensor shapeとdtype
  • operator graph
  • alignmentとpadding
  • file format header
  • optional zero point

今回の値は「packed parameterの理論値」であり、実際のONNXやengine file sizeではありません。

per-channel scaleのmetadataも本来は数える

図の理論容量はscale metadataを除外しました。per-channelはlayerごとにoutput数分のscaleが必要です。

このCNNならoutput channelは16、32、64、10です。

16 + 32 + 64 + 10 = 122 scales
122 × FP32 4 bytes = 488 bytes

約0.48 KiBなので全体には小さいですが、非常に小さなtensorが多いmodelでは無視できません。zero pointもchannelごとならさらに増えます。

accuracyだけでなくprediction差を保存する理由

INT8 per-tensorはFP32よりaccuracyが0.05ポイント高いのに38 predictionが変わりました。

same aggregate score ≠ same model behavior

safety-critical classやrare classで変化が集中していれば、全体accuracyの微差より重要です。今回はchanged countまで測りましたが、次に進むならclass別confusion differenceと、marginが小さいsampleの可視化を追加します。

PTQとQATの違い

今回のように学習済みweightを後から丸める方法はPost-Training Quantizationの簡易simulationです。

Quantization-Aware Trainingでは、forward中に量子化誤差を模擬しながらweightを更新します。特にINT4のaccuracyを回復できる可能性があります。

PTQ: train FP32 → quantize → evaluate
QAT: fake quantizationを入れて追加学習 → quantize → evaluate

ただしQATはtraining cost、implementation、hyperparameterが増えます。まずPTQ baselineでどれだけ落ちるか測ると、QATが必要か判断できます。

activation quantizationは別問題

今回activationはFP32です。実際のinteger inferenceではactivationもINT8へ量子化する場合があります。そのscaleを決めるにはrepresentative calibration dataが必要です。

activation rangeへ外れ値があるとscaleが粗くなります。calibration sampleの選び方、percentile clipping、dynamic/static quantizationによって結果が変わります。

weight-only INT8でaccuracyが維持できたからといって、full integer INT8でも同じとは限りません。

speedを測らなかった理由

fake quantization後のparameterはFP32 tensorです。

parameter.copy_(quantized_integer * scale)

forwardは普通のFP32 Conv2dとLinearを呼びます。計算量もmemory representationもFP32です。この時間を測って「INT4速度」と呼ぶのは誤りです。

速度を論じるには、実際にpacked INT4を読むkernel、対応hardware、runtime、operator coverageを用意します。unsupported operatorがFP32へfallbackすれば期待倍率は出ません。

再現手順

前記事のstate dictを入力に使います。

cd /mnt/c/Users/user/Documents/Codex/2026-08-24/ko/work/ai_lab
/opt/ai-lab/venv/bin/python -u experiment_weight_quant_sim.py
/opt/ai-lab/venv/bin/python plot_weight_quant_sim.py

artifactです。

work/ai_lab/models/fashion_cnn_state_dict.pt
work/ai_lab/experiment_weight_quant_sim.py
work/ai_lab/results/weight_quant_sim.json
work/ai_lab/plot_weight_quant_sim.py
work/figures/int8-int4-weight-quant.png

JSONには方式ごとのaccuracy、10,000枚のagreement、logit差、理論容量、全4 weight layerのMSEを保存しました。

結論

このCNNではINT8 weight-only量子化はほぼaccuracyを維持し、per-channelならFP32から変わったpredictionは18/10,000でした。

INT4はscale設計の影響が大きく、per-tensorでは81.99%まで低下しました。per-output-channelへ変えるだけで85.95%へ回復し、prediction変化も半減しました。

量子化をbit数だけで説明せず、対象tensor、granularity、scale、activation、kernel、task metricを分けて測る必要があります。容量削減の計算と、accuracy・speedの実測は別の検証です。

関連記事