Files
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

521 lines
22 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 QuantRNN module.
"""
import pytest
import torch
from torch import nn
import numpy as np
from pytorch_quantization.nn.modules import quant_rnn
from pytorch_quantization import tensor_quant
from tests.fixtures import verbose
from . import utils
# make everything run on the GPU
torch.set_default_tensor_type('torch.cuda.FloatTensor')
# change default type to double if utils.compare flags a small error, may just be floating point rounding error
# torch.set_default_tensor_type('torch.cuda.DoubleTensor')
np.random.seed(1234)
torch.manual_seed(1234)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(1234)
# pylint: disable=no-self-use, missing-docstring, redefined-builtin, bad-continuation
# global state for saving/loading test vectors
SAVE_VECTORS = 0
VECTOR_FILE = 'tests/quant_rnn_test_vectors.pt'
if SAVE_VECTORS:
TEST_VECTORS = dict()
else:
TEST_VECTORS = torch.load(VECTOR_FILE)
class TestQuantLSTMCell():
"""
tests for quant_rnn.QuantLSTMCell
default parameters in QuantLSTMCell:
bias=True,
num_bits_weight=8, quant_mode_weight='per_channel',
num_bits_input=8, quant_mode_input='per_tensor'
Tests of real quantization mode (nonfake) are disabled as it is not fully supported yet.
"""
def test_basic_forward(self, verbose):
"""Do a forward pass on the cell module and see if anything catches fire."""
batch = 7
input_size = 11
hidden_size = 9
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=8)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=8, axis=(1,))
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
quant_rnn_object._input_quantizer.disable()
quant_rnn_object._weight_quantizer.disable()
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_rnn_object(input, hx=(hidden, cell))
def test_no_quant_input_hidden(self, verbose):
"""QuantLSTM with quantization disabled vs. pytorch LSTM for input and hidden inputs."""
batch = 17
input_size = 13
hidden_size = 7
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=False)
quant_rnn_object._input_quantizer.disable()
quant_rnn_object._weight_quantizer.disable()
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=False)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
ref_hout, ref_cout = ref_rnn_object(input, hx=(hidden, cell))
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_no_quant_input_hidden_bias(self, verbose):
"""QuantLSTMCell with quantization disabled vs. pytorch LSTMCell for input, hidden inputs and bias."""
batch = 19
input_size = 11
hidden_size = 3
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=True)
quant_rnn_object._input_quantizer.disable()
quant_rnn_object._weight_quantizer.disable()
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=True)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
ref_hout, ref_cout = ref_rnn_object(input, hx=(hidden, cell))
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_against_unquantized(self, verbose):
"""Quantization should introduce bounded error utils.compare to pytorch implementation."""
batch = 9
input_size = 13
hidden_size = 7
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=16)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=16, axis=(1,))
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=False)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
ref_hout, ref_cout = ref_rnn_object(input, hx=(hidden, cell))
# The difference between reference and quantized should be bounded in a range
# Small values which become 0 after quantization lead to large relative errors. rtol and atol could be
# much smaller without those values
utils.compare(quant_hout, ref_hout, rtol=1e-4, atol=1e-4)
utils.compare(quant_cout, ref_cout, rtol=1e-4, atol=1e-4)
# check that quantization introduces some error
utils.assert_min_mse(quant_hout, ref_hout, tol=1e-20)
utils.assert_min_mse(quant_cout, ref_cout, tol=1e-20)
def test_quant_input_hidden(self, verbose):
"""QuantLSTMCell vs. manual input quantization + pytorchLSTMCell."""
batch = 15
input_size = 121
hidden_size = 51
num_bits = 4
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=False)
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits)
ref_hout, ref_cout = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_quant_input_hidden_bias(self, verbose):
"""QuantLSTMCell vs. manual input quantization + pytorchLSTMCell
bias should not be quantized
"""
batch = 9
input_size = 23
hidden_size = 31
num_bits = 7
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=True,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=True)
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits)
ref_hout, ref_cout = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_quant_different_prec(self, verbose):
"""QuantLSTMCell vs. manual input quantization + pytorch LSTMCell
different input and weight precisions
"""
batch = 27
input_size = 11
hidden_size = 10
num_bits_weight = 4
num_bits_input = 8
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits_input)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits_weight)
quant_rnn_object = quant_rnn.QuantLSTMCell(input_size, hidden_size, bias=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTMCell(input_size, hidden_size, bias=False)
input = torch.randn(batch, input_size)
hidden = torch.randn(batch, hidden_size)
cell = torch.randn(batch, hidden_size)
quant_hout, quant_cout = quant_rnn_object(input, hx=(hidden, cell))
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits_input)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits_weight)
ref_hout, ref_cout = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
class TestQuantLSTM():
"""
tests for quant_rnn.QuantLSTM
default parameters in QuantLSTM:
bias=True,
quant_weight=True, bits_weight=8, fake_quantTrue, quant_mode_weight='channel',
quant_input=True, bits_acts=8, quant_mode_input='tensor'
Tests of real quantization mode (nonfake) are disabled as it is not fully supported yet.
"""
def test_basic_forward(self, verbose):
"""Do a forward pass on the layer module and see if anything catches fire."""
batch = 5
input_size = 13
hidden_size = 31
seq_len = 1
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=8)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=8, axis=(1,))
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_rnn_object(input, hx=(hidden, cell))
def test_no_quant(self, verbose):
"""QuantLSTM with quantization disabled vs. pytorch LSTM."""
batch = 11
input_size = 14
hidden_size = 22
seq_len = 1
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False)
quant_rnn_object._input_quantizers[0].disable()
quant_rnn_object._weight_quantizers[0].disable()
ref_rnn_object = nn.LSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input)
ref_out, (ref_hout, ref_cout) = ref_rnn_object(input)
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_no_quant_input_hidden(self, verbose):
"""QuantLSTM with quantization disabled vs. pytorch LSTM for input and hidden inputs."""
batch = 13
input_size = 19
hidden_size = 20
seq_len = 1
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False)
quant_rnn_object._input_quantizers[0].disable()
quant_rnn_object._weight_quantizers[0].disable()
ref_rnn_object = nn.LSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(input, hx=(hidden, cell))
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_no_quant_all_modes(self, verbose):
"""QuantLSTM with quantization disabled vs. pytorch LSTM for all modes."""
def testcase(input_size, hidden_size, seq_len, batch, num_layers, bias, batch_first, dropout, bidirectional):
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size,
num_layers=num_layers, bias=bias, batch_first=batch_first, dropout=dropout,
bidirectional=bidirectional)
num_quantizers = num_layers * 2 if bidirectional else num_layers
for i in range(num_quantizers):
quant_rnn_object._input_quantizers[i].disable()
quant_rnn_object._weight_quantizers[i].disable()
ref_rnn_object = nn.LSTM(input_size, hidden_size,
num_layers=num_layers, bias=bias, batch_first=batch_first, dropout=dropout,
bidirectional=bidirectional)
# copy state from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(seq_len, batch, input_size)
num_directions = 2 if bidirectional else 1
hidden = torch.randn(num_layers*num_directions, batch, hidden_size)
cell = torch.randn(num_layers*num_directions, batch, hidden_size)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(input, hx=(hidden, cell))
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
# test various permuatations of the following parameters:
# size, num_layers, bias, batch_first, dropout, bidirectional
testcase(32, 27, 1, 1, 1, False, False, 0, False)
testcase(19, 63, 1, 1, 2, False, False, 0, False)
testcase(11, 41, 1, 1, 1, True, False, 0, False)
testcase(33, 31, 1, 1, 1, False, True, 0, False)
# testcase(32, 32, 1, 1, 2, False, False, 0.5, False) #TODO(pjudd) this fails look into dropout seeding
testcase(73, 13, 1, 1, 1, False, False, 0, True)
def test_against_unquantized(self, verbose):
"""Quantization should introduce bounded error utils.compare to pytorch implementation."""
batch = 21
input_size = 33
hidden_size = 25
seq_len = 1
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=16)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=16, axis=(1,))
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTM(input_size, hidden_size,
num_layers=1, bias=False, batch_first=False, dropout=0, bidirectional=False)
# copy weights from one rnn to the other
ref_rnn_object.load_state_dict(quant_rnn_object.state_dict())
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(input, hx=(hidden, cell))
# The difference between reference and quantized should be bounded in a range
# Small values which become 0 after quantization lead to large relative errors. rtol and atol could be
# much smaller without those values
utils.compare(quant_out, ref_out, rtol=1e-4, atol=1e-4)
utils.compare(quant_hout, ref_hout, rtol=1e-4, atol=1e-4)
utils.compare(quant_cout, ref_cout, rtol=1e-4, atol=1e-4)
# check that quantization introduces some error
utils.assert_min_mse(quant_out, ref_out, tol=1e-20)
utils.assert_min_mse(quant_hout, ref_hout, tol=1e-20)
utils.assert_min_mse(quant_cout, ref_cout, tol=1e-20)
def test_quant_input_hidden(self, verbose):
"""QuantLSTM vs. manual input quantization + pytorchLSTM."""
batch = 13
input_size = 17
hidden_size = 7
seq_len = 1
num_bits = 6
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size, num_layers=1, bias=False,
batch_first=False, dropout=0, bidirectional=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTM(input_size, hidden_size, num_layers=1, bias=False,
batch_first=False, dropout=0, bidirectional=False)
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_quant_input_hidden_bias(self, verbose):
"""QuantLSTM vs. manual input quantization + pytorchLSTM."""
batch = 17
input_size = 13
hidden_size = 7
seq_len = 1
num_bits = 5
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits)
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size, num_layers=1, bias=True,
batch_first=False, dropout=0, bidirectional=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTM(input_size, hidden_size, num_layers=1, bias=True,
batch_first=False, dropout=0, bidirectional=False)
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
def test_quant_different_prec(self, verbose):
"""QuantLSTM vs. manual input quantization + pytorchLSTM."""
batch = 22
input_size = 23
hidden_size = 24
seq_len = 1
num_bits_weight = 4
num_bits_input = 8
quant_desc_input = tensor_quant.QuantDescriptor(num_bits=num_bits_input)
quant_desc_weight = tensor_quant.QuantDescriptor(num_bits=num_bits_weight)
quant_rnn_object = quant_rnn.QuantLSTM(input_size, hidden_size, num_layers=1, bias=False,
batch_first=False, dropout=0, bidirectional=False,
quant_desc_input=quant_desc_input, quant_desc_weight=quant_desc_weight)
ref_rnn_object = nn.LSTM(input_size, hidden_size, num_layers=1, bias=False,
batch_first=False, dropout=0, bidirectional=False)
input = torch.randn(seq_len, batch, input_size)
hidden = torch.randn(seq_len, batch, hidden_size)
cell = torch.randn(seq_len, batch, hidden_size)
quant_input, quant_hidden = utils.quantize_by_range_fused((input, hidden), num_bits_input)
utils.copy_state_and_quantize_fused(ref_rnn_object, quant_rnn_object, num_bits_weight)
quant_out, (quant_hout, quant_cout) = quant_rnn_object(input, hx=(hidden, cell))
ref_out, (ref_hout, ref_cout) = ref_rnn_object(quant_input, hx=(quant_hidden, cell))
utils.compare(quant_out, ref_out)
utils.compare(quant_hout, ref_hout)
utils.compare(quant_cout, ref_cout)
class TestEpilogue():
"""Run after all tests to save globals."""
def test_save_vectors(self, verbose):
"""Save test vectors to file."""
if SAVE_VECTORS:
torch.save(TEST_VECTORS, VECTOR_FILE)
raise Exception('Saved test vectors to {}, for testing set SAVE_VECTORS = 0'.format(VECTOR_FILE))