Files
nvidia--tensorrt/tools/pytorch-quantization/tests/calibrator_test.py
T
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

453 lines
16 KiB
Python

#
# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests of calibrators"""
import pytest
import numpy as np
import torch
from pytorch_quantization import utils as quant_utils
from pytorch_quantization import calib
from pytorch_quantization import nn as quant_nn
import tests.utils as test_utils
from tests.fixtures import verbose
from tests.fixtures.models import QuantLeNet
np.random.seed(12345)
torch.manual_seed(12345)
# pylint:disable=missing-docstring, no-self-use
class TestMaxCalibrator():
def test_simple_run(self):
max_calibrator = calib.MaxCalibrator(8, None, False)
x_1 = torch.rand(129).cuda()
x_2 = torch.rand(127).cuda()
max_calibrator.collect(x_1)
max_calibrator.collect(x_2)
test_utils.compare(max_calibrator.compute_amax(), torch.max(x_1.max(), x_2.max()), atol=0, rtol=0, ctol=0)
# Nothing to test other than creation
max_calibrator = calib.MaxCalibrator(8, None, True)
def test_fine_grain(self):
axis = 0
reducs_axis = (1, 2, 3)
max_calibrator = calib.MaxCalibrator(8, axis, False)
x_1 = torch.rand(31, 63, 7, 7).cuda()
x_2 = torch.rand(31, 63, 7, 7).cuda()
max_calibrator.collect(x_1)
max_calibrator.collect(x_2)
assert max_calibrator.compute_amax().shape[0] == 31
test_utils.compare(max_calibrator.compute_amax(),
quant_utils.reduce_amax(torch.max(x_1, x_2), axis=reducs_axis),
atol=0, rtol=0, ctol=0)
max_calibrator.reset()
assert max_calibrator.compute_amax() is None
def test_reverse_axis(self):
axis = -4
reducs_axis = (1, 2, 3)
max_calibrator = calib.MaxCalibrator(8, axis, False)
x_1 = torch.rand(31, 63, 7, 7).cuda()
x_2 = torch.rand(31, 63, 7, 7).cuda()
max_calibrator.collect(x_1)
max_calibrator.collect(x_2)
assert max_calibrator.compute_amax().shape[0] == 31
test_utils.compare(max_calibrator.compute_amax(),
quant_utils.reduce_amax(torch.max(x_1, x_2), axis=reducs_axis),
atol=0, rtol=0, ctol=0)
max_calibrator.reset()
assert max_calibrator.compute_amax() is None
def test_raises(self):
axis = 0
max_calibrator = calib.MaxCalibrator(8, axis, False)
x_2 = torch.rand(32, 63, 7, 7).cuda()
x_3 = torch.rand(33, 63, 7, 7).cuda()
max_calibrator.collect(x_2)
with pytest.raises(RuntimeError, match="shape changed"):
max_calibrator.collect(x_3)
def test_track_amax(self):
max_calibrator = calib.MaxCalibrator(8, None, False, track_amax=True)
x_1 = torch.rand(129).cuda()
x_2 = torch.rand(127).cuda()
max_calibrator.collect(x_1)
max_calibrator.collect(x_2)
test_utils.compare(max_calibrator.compute_amax(), torch.max(x_1.max(), x_2.max()), atol=0, rtol=0, ctol=0)
np.testing.assert_array_equal(max_calibrator.amaxs[0], x_1.max().cpu().numpy())
np.testing.assert_array_equal(max_calibrator.amaxs[1], x_2.max().cpu().numpy())
def test_repr(self):
max_calibrator = calib.MaxCalibrator(8, None, False, track_amax=True)
repr(max_calibrator)
class TestHistogramCalibrator():
def test_grow(self, verbose):
x_1 = torch.tensor([0, 255, 255, 255, 255, 255]).cuda()
x_2 = torch.tensor([0, 255, 255, 255, 255, 256]).cuda()
hist_calibrator = calib.HistogramCalibrator(8, None, False, grow_method='stretch')
hist_calibrator.collect(x_1)
hist_calibrator.collect(x_2)
amax = hist_calibrator.compute_amax(method='entropy')
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be closer to 256 because the last bin gets stretched to (~255, 257)
assert (amax - 255.).abs() < (amax - 256.).abs()
hist_calibrator = calib.HistogramCalibrator(8, None, False, grow_method='append')
hist_calibrator.collect(x_1)
hist_calibrator.collect(x_2)
amax = hist_calibrator.compute_amax(method='mse')
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be closer to 255
assert (amax - 255.).abs() < 0.5
def test_skip_zeros(self, verbose):
x_1 = torch.tensor([0, 0, 0, 0, 0, 1, 2, 3, 4, 5])
x_2 = torch.tensor([0, 0, 0, 0, 0, 6, 7, 8, 9, 10])
calibrator = calib.HistogramCalibrator(8, None, False, skip_zeros=True)
calibrator.collect(x_1)
calibrator.collect(x_2)
amax = calibrator.compute_amax("percentile", percentile=50)
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be close to 5
assert (amax - 5.).abs() < 10/2048
def test_torch_hist(self):
x_1 = torch.rand(1023, device="cuda")
x_1[0] = 0
x_2 = torch.rand(1023, device="cuda") + 1 # Make sure histogram bins need to be grown
x_2[1] = 0
calibrator_np = calib.HistogramCalibrator(8, None, False, num_bins=19, torch_hist=False)
calibrator_torch = calib.HistogramCalibrator(8, None, False, num_bins=19, torch_hist=True)
calibrator_np.collect(x_1)
calibrator_torch.collect(x_1)
assert calibrator_torch._calib_hist.numel() == calibrator_torch._calib_bin_edges.numel() - 1
np.testing.assert_array_equal(calibrator_np._calib_hist, calibrator_torch._calib_hist.cpu().numpy())
np.testing.assert_array_almost_equal(
calibrator_np._calib_bin_edges, calibrator_torch._calib_bin_edges.cpu().numpy())
# Test multiple collections with some of them needs to expand range
for _ in range(3):
calibrator_np.collect(x_2)
calibrator_torch.collect(x_2)
calibrator_np.collect(x_1)
calibrator_torch.collect(x_1)
# Test compute_amax function doesn't convert _calib_hist and _calib_bin_edges unnecessarily
calibrator_np.compute_amax("percentile", percentile=99.99)
calibrator_torch.compute_amax("percentile", percentile=99.99)
np.testing.assert_array_equal(calibrator_np._calib_hist, calibrator_torch._calib_hist.cpu().numpy())
np.testing.assert_array_almost_equal(
calibrator_np._calib_bin_edges, calibrator_torch._calib_bin_edges.cpu().numpy())
assert calibrator_torch._calib_hist.numel() == calibrator_torch._calib_bin_edges.numel() - 1
class TestEntropyCalibrator():
def test_one_tensor(self, verbose):
hist_calibrator = calib.HistogramCalibrator(8, None, False, grow_method='stretch')
x_2 = torch.rand(11, 7, 3, 3).cuda() # uniform in (0,1)
x_2[1, 1, 1, 1] = 10. # create outlier
hist_calibrator.collect(x_2)
# Don't have a better test metric. One outlier 10 should be discared by KL-divergence
amax = hist_calibrator.compute_amax("entropy")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
assert amax < 1.1
def test_unsigned(self, verbose):
hist_calibrator = calib.HistogramCalibrator(8, None, True, grow_method='stretch')
x_2 = torch.rand(11, 7, 3, 3).cuda() # uniform in (0,1)
x_2[1, 1, 1, 1] = 10. # create outlier
hist_calibrator.collect(x_2)
amax = hist_calibrator.compute_amax("entropy")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
assert amax < 1.1
@pytest.mark.parametrize("torch_hist", [False, True])
def test_two_tensor(self, torch_hist, verbose):
hist_calibrator = calib.HistogramCalibrator(8, None, False, torch_hist=torch_hist)
x_2 = torch.rand(11, 7, 3, 3).cuda() # uniform in (0,1)
x_2[1, 1, 1, 1] = 10. # create outlier
x_2 = torch.rand(11, 7, 3, 3).cuda() # uniform in (0,1)
x_2[1, 1, 1, 1] = 10. # create outlier
hist_calibrator.collect(x_2)
x_3 = torch.rand(11, 7, 3, 3).cuda()
hist_calibrator.collect(x_3)
# Don't have a better test metric. One outlier 10 should be discared by KL-divergence
amax = hist_calibrator.compute_amax("entropy")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
assert amax < 1.1
def test_repr(self):
hist_calibrator = calib.HistogramCalibrator(8, None, True)
repr(hist_calibrator)
class TestMSECalibrator():
def test_one_tensor(self, verbose):
calibrator = calib.HistogramCalibrator(8, None, False)
x_1 = torch.ones(11, 7, 3, 3).cuda() * 255.
x_1[1, 1, 1, 1] = 256. # create an outlier
calibrator.collect(x_1)
amax = calibrator.compute_amax("mse")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be closer to 255
assert (amax - 255.).abs() < (amax - 256.).abs()
def test_unsigned_one_tensor(self, verbose):
calibrator = calib.HistogramCalibrator(8, None, True)
x_1 = torch.ones(11, 7, 3, 3).cuda() * 512.
x_1[1, 1, 1, 1] = 513. # create an outlier
calibrator.collect(x_1)
amax = calibrator.compute_amax("mse")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be closer to 512
assert (amax - 512.).abs() < (amax - 513.).abs()
@pytest.mark.parametrize("torch_hist", [False, True])
def test_two_tensor(self, torch_hist, verbose):
calibrator = calib.HistogramCalibrator(8, None, False, torch_hist=torch_hist)
x_1 = torch.ones(11, 7, 3, 3).cuda() * 255.
x_1[1, 1, 1, 1] = 256. # create an outlier
calibrator.collect(x_1)
x_2 = torch.ones(11, 7, 3, 3).cuda() * 255.
calibrator.collect(x_2)
amax = calibrator.compute_amax("mse")
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be closer to 255
assert (amax - 255.).abs() < (amax - 256.).abs()
def test_repr(self):
calibrator = calib.HistogramCalibrator(8, None, False)
repr(calibrator)
class TestPercentileCalibrator():
def test_one_tensor(self, verbose):
calibrator = calib.HistogramCalibrator(8, None, False)
x_1 = torch.arange(100)
calibrator.collect(x_1)
amax = calibrator.compute_amax("percentile", percentile=90)
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be approximately 89
assert (amax - 89.).abs() < 100/1024
def test_unsigned_one_tensor(self, verbose):
calibrator = calib.HistogramCalibrator( 8, None, True)
x_1 = torch.arange(100)
calibrator.collect(x_1)
amax = calibrator.compute_amax("percentile", percentile=80)
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be approximately 79
assert (amax - 79.).abs() < 100/2048
@pytest.mark.parametrize("torch_hist", [False, True])
def test_two_tensor(self, torch_hist, verbose):
calibrator = calib.HistogramCalibrator(8, None, False, torch_hist=torch_hist)
x_1 = torch.arange(100)
calibrator.collect(x_1)
x_2 = torch.arange(0, 50, 0.5)
calibrator.collect(x_2)
amax = calibrator.compute_amax("percentile", percentile=99)
if verbose:
print('amax={:.4f}'.format(amax.item()), end=' ')
# amax should be approximately 97
assert (amax - 97.).abs() < 100/1024
def test_repr(self):
calibrator = calib.HistogramCalibrator(8, None, False)
repr(calibrator)
def test_range(self):
calibrator = calib.HistogramCalibrator(8, None, False)
x_1 = torch.arange(100)
calibrator.collect(x_1)
with pytest.raises(ValueError, match="range"):
calibrator.compute_amax("percentile", percentile=-10)
with pytest.raises(ValueError, match="range"):
calibrator.compute_amax("percentile", percentile=200)
class TestCalibrateWeights():
def test_max(self):
torch.manual_seed(12345)
ref_lenet = QuantLeNet()
torch.manual_seed(12345)
test_lenet = QuantLeNet()
for module in ref_lenet.modules():
if isinstance(module, (quant_nn.QuantConv2d, quant_nn.QuantLinear)):
module.weight_quantizer.enable_calib()
module.weight_quantizer.disable_quant()
module.weight_quantizer(module.weight)
module.weight_quantizer.load_calib_amax()
calib.calibrate_weights(test_lenet, method="max")
for ref_module, test_module in zip(ref_lenet.modules(), test_lenet.modules()):
if isinstance(ref_module, (quant_nn.QuantConv2d, quant_nn.QuantLinear)):
test_utils.compare(
ref_module.weight_quantizer.amax, test_module.weight_quantizer.amax, rtol=0, atol=0, ctol=0)
assert ref_module.weight_quantizer.amax.shape == test_module.weight_quantizer.amax.shape
def test_shape_with_axis(self):
"""Check calibrate_weight function returns same shape as TensorQuantizer"""
torch.manual_seed(12345)
ref_lenet = QuantLeNet()
torch.manual_seed(12345)
test_lenet = QuantLeNet()
for module in ref_lenet.modules():
if isinstance(module, (quant_nn.QuantConv2d, quant_nn.QuantLinear)):
module.weight_quantizer.enable_calib()
module.weight_quantizer.disable_quant()
module.weight_quantizer(module.weight)
module.weight_quantizer.load_calib_amax()
calib.calibrate_weights(test_lenet, method="percentile")
for ref_module, test_module in zip(ref_lenet.modules(), test_lenet.modules()):
if isinstance(ref_module, (quant_nn.QuantConv2d, quant_nn.QuantLinear)):
assert ref_module.weight_quantizer.amax.shape == test_module.weight_quantizer.amax.shape
def test_percentile(self):
torch.manual_seed(12345)
test_lenet = QuantLeNet()
test_percentile = 99.99
ref_calibrator = calib.HistogramCalibrator(8, None, False)
calib.calibrate_weights(test_lenet, method="percentile", perchannel=False, percentile=test_percentile)
ref_calibrator.collect(test_lenet.conv1.weight)
ref_amax = ref_calibrator.compute_amax("percentile", percentile=test_percentile)
test_utils.compare(ref_amax, test_lenet.conv1.weight_quantizer.amax, rtol=0, atol=0, ctol=0)
def test_percentile_with_axis(self):
torch.manual_seed(12345)
test_lenet = QuantLeNet()
test_percentile = 99.99
ref_calibrator = calib.HistogramCalibrator(8, None, False)
calib.calibrate_weights(test_lenet, method="percentile", perchannel=True, percentile=test_percentile)
ref_calibrator.collect(test_lenet.conv2.weight[1])
ref_amax = ref_calibrator.compute_amax("percentile", percentile=test_percentile)
test_utils.compare(ref_amax, test_lenet.conv2.weight_quantizer.amax[1], rtol=0, atol=0, ctol=0)
def test_mse(self):
torch.manual_seed(12345)
test_lenet = QuantLeNet()
ref_calibrator = calib.HistogramCalibrator(8, None, False)
calib.calibrate_weights(test_lenet, method="mse", perchannel=False)
ref_calibrator.collect(test_lenet.conv1.weight)
ref_amax = ref_calibrator.compute_amax("mse")
test_utils.compare(ref_amax, test_lenet.conv1.weight_quantizer.amax, rtol=0, atol=0, ctol=0)
def test_mse_with_axis(self):
torch.manual_seed(12345)
test_lenet = QuantLeNet()
ref_calibrator = calib.HistogramCalibrator(8, None, False)
calib.calibrate_weights(test_lenet, method="mse", perchannel=True)
ref_calibrator.collect(test_lenet.conv2.weight[1])
ref_amax = ref_calibrator.compute_amax("mse")
test_utils.compare(ref_amax, test_lenet.conv2.weight_quantizer.amax[1], rtol=0, atol=0, ctol=0)