実験日: 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は方式で差が出た
- 今回は「fake quantization」
- symmetric quantizationの式
- INT4は使えるlevelが15個しかない
- per-tensorはscaleを1個だけ使う
- per-channelはoutputごとにscaleを持つ
- INT8はほぼaccuracyを維持した
- layer別weight MSEもper-channelが小さい
- 理論容量はFP32の約1/4と1/8
- per-channel scaleのmetadataも本来は数える
- accuracyだけでなくprediction差を保存する理由
- PTQとQATの違い
- activation quantizationは別問題
- speedを測らなかった理由
- 再現手順
- 結論
- 関連記事
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 |

今回は「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の実測は別の検証です。
