mirror of
https://github.com/zama-ai/concrete.git
synced 2026-02-10 12:44:57 -05:00
feat: add quantization utilities
This commit is contained in:
42
tests/quantization/test_quantized_activations.py
Normal file
42
tests/quantization/test_quantized_activations.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Tests for the quantized activation functions."""
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
from concrete.quantization import QuantizedArray, QuantizedSigmoid
|
||||
|
||||
N_BITS_ATOL_TUPLE_LIST = [
|
||||
(32, 10 ** -2),
|
||||
(28, 10 ** -2),
|
||||
(20, 10 ** -2),
|
||||
(16, 10 ** -1),
|
||||
(8, 10 ** -0),
|
||||
(4, 10 ** -0),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"n_bits, atol",
|
||||
[pytest.param(n_bits, atol) for n_bits, atol in N_BITS_ATOL_TUPLE_LIST],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"quant_activation, values",
|
||||
[pytest.param(QuantizedSigmoid, numpy.random.uniform(size=(10, 40, 20)))],
|
||||
)
|
||||
def test_activations(quant_activation, values, n_bits, atol):
|
||||
"""Test activation functions."""
|
||||
q_inputs = QuantizedArray(n_bits, values)
|
||||
quant_sigmoid = quant_activation(n_bits)
|
||||
quant_sigmoid.calibrate(values)
|
||||
expected_output = quant_sigmoid.q_out.values
|
||||
q_output = quant_sigmoid(q_inputs)
|
||||
qvalues = q_output.qvalues
|
||||
|
||||
# Quantized values must be contained between 0 and 2**n_bits - 1.
|
||||
assert numpy.max(qvalues) <= 2 ** n_bits - 1
|
||||
assert numpy.min(qvalues) >= 0
|
||||
|
||||
# Dequantized values must be close to original values
|
||||
dequant_values = q_output.dequant()
|
||||
|
||||
# Check that all values are close
|
||||
assert numpy.isclose(dequant_values, expected_output, atol=atol).all()
|
||||
53
tests/quantization/test_quantized_array.py
Normal file
53
tests/quantization/test_quantized_array.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Tests for the quantized array/tensors."""
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
from concrete.quantization import QuantizedArray
|
||||
|
||||
N_BITS_ATOL_TUPLE_LIST = [
|
||||
(32, 10 ** -2),
|
||||
(28, 10 ** -2),
|
||||
(20, 10 ** -2),
|
||||
(16, 10 ** -1),
|
||||
(8, 10 ** -0),
|
||||
(4, 10 ** -0),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"n_bits, atol",
|
||||
[pytest.param(n_bits, atol) for n_bits, atol in N_BITS_ATOL_TUPLE_LIST],
|
||||
)
|
||||
@pytest.mark.parametrize("values", [pytest.param(numpy.random.randn(2000))])
|
||||
def test_quant_dequant_update(values, n_bits, atol):
|
||||
"""Test the quant and dequant function."""
|
||||
|
||||
quant_array = QuantizedArray(n_bits, values)
|
||||
qvalues = quant_array.quant()
|
||||
|
||||
# Quantized values must be contained between 0 and 2**n_bits
|
||||
assert numpy.max(qvalues) <= 2 ** n_bits - 1
|
||||
assert numpy.min(qvalues) >= 0
|
||||
|
||||
# Dequantized values must be close to original values
|
||||
dequant_values = quant_array.dequant()
|
||||
|
||||
# Check that all values are close
|
||||
assert numpy.isclose(dequant_values, values, atol=atol).all()
|
||||
|
||||
# Test update functions
|
||||
new_values = numpy.array([0.3, 0.5, -1.2, -3.4])
|
||||
new_qvalues_ = quant_array.update_values(new_values)
|
||||
|
||||
# Make sure the shape changed for the qvalues
|
||||
assert new_qvalues_.shape != qvalues.shape
|
||||
|
||||
new_qvalues = numpy.array([1, 4, 7, 29])
|
||||
new_values_updated = quant_array.update_qvalues(new_qvalues)
|
||||
|
||||
# Make sure that we can see at least one change.
|
||||
assert not numpy.array_equal(new_qvalues, new_qvalues_)
|
||||
assert not numpy.array_equal(new_values, new_values_updated)
|
||||
|
||||
# Check that the __call__ returns also the qvalues.
|
||||
assert numpy.array_equal(quant_array(), new_qvalues)
|
||||
58
tests/quantization/test_quantized_layers.py
Normal file
58
tests/quantization/test_quantized_layers.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Tests for the quantized layers."""
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
from concrete.quantization import QuantizedArray, QuantizedLinear
|
||||
|
||||
# QuantizedLinear unstable with n_bits>23.
|
||||
N_BITS_LIST = [20, 16, 8, 4]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"n_bits",
|
||||
[pytest.param(n_bits) for n_bits in N_BITS_LIST],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"n_examples, n_features, n_neurons",
|
||||
[
|
||||
pytest.param(20, 500, 30),
|
||||
pytest.param(200, 300, 50),
|
||||
pytest.param(10000, 100, 1),
|
||||
pytest.param(10, 20, 1),
|
||||
],
|
||||
)
|
||||
def test_quantized_linear(n_examples, n_features, n_neurons, n_bits):
|
||||
"""Test the quantization linear layer of numpy.array.
|
||||
|
||||
With n_bits>>0 we expect the results of the quantized linear
|
||||
to be the same as the standard linear layer.
|
||||
"""
|
||||
inputs = numpy.random.uniform(size=(n_examples, n_features))
|
||||
q_inputs = QuantizedArray(n_bits, inputs)
|
||||
|
||||
# shape of weights: (n_examples, n_features, n_neurons)
|
||||
weights = numpy.random.uniform(size=(n_neurons, n_features))
|
||||
q_weights = QuantizedArray(n_bits, weights)
|
||||
|
||||
bias = numpy.random.uniform(size=(n_neurons))
|
||||
q_bias = QuantizedArray(n_bits, bias)
|
||||
|
||||
# Define our QuantizedLinear layer
|
||||
q_linear = QuantizedLinear(n_bits, q_weights, q_bias)
|
||||
|
||||
# Calibrate the Quantized layer
|
||||
q_linear.calibrate(inputs)
|
||||
expected_outputs = q_linear.q_out.values
|
||||
actual_output = q_linear(q_inputs).dequant()
|
||||
|
||||
assert numpy.isclose(expected_outputs, actual_output, rtol=10 ** -1).all()
|
||||
|
||||
# Same test without bias
|
||||
q_linear = QuantizedLinear(n_bits, q_weights)
|
||||
|
||||
# Calibrate the Quantized layer
|
||||
q_linear.calibrate(inputs)
|
||||
expected_outputs = q_linear.q_out.values
|
||||
actual_output = q_linear(q_inputs).dequant()
|
||||
|
||||
assert numpy.isclose(expected_outputs, actual_output, rtol=10 ** -1).all()
|
||||
Reference in New Issue
Block a user