Files
zeekay 397da37894 docs+brand: purge Zama/Fhenix references — Lux FHE / Torus / TorusEVM / TorusML / TorusNet canonical names
Files edited: 150
Brand-hit files before: 163
Brand-hit files after: 28

Substitutions:
- Zama Concrete ML        -> TorusML
- Zama Concrete / Concrete-> Torus
- Zama fhEVM / fhEVM      -> TorusEVM
- Zama TFHE-rs / tfhe-rs  -> Lux-FHE (github.com/luxfi/fhe)
- Zama (attribution)      -> Lux Industries
- zama-ai/* (GitHub)      -> luxfhe/*
- @zama-fhe/* (npm)       -> @luxfhe/*
- docs.zama.ai            -> docs.lux.network
- Fhenix / CoFHE          -> dropped or rewritten to Lux FHE / TorusEVM coprocessor

Text-only changes. No runtime code semantics altered.
2026-05-29 13:20:44 -07:00

335 lines
10 KiB
Python

"""Test Neural Networks compilations"""
import numpy
import onnx
import pytest
import torch
from onnx import helper, numpy_helper
from torch import nn
from concrete.ml.common.utils import MAX_BITWIDTH_BACKWARD_COMPATIBLE
from concrete.ml.onnx.convert import OPSET_VERSION_FOR_ONNX_EXPORT
from concrete.ml.pytest.torch_models import (
FCSeq,
FCSeqAddBiasVec,
FCSmall,
MultiOpOnSingleInputConvNN,
TinyCNN,
)
from concrete.ml.quantization import QuantizedGemm
from concrete.ml.quantization.post_training import PostTrainingAffineQuantization
from concrete.ml.torch.numpy_module import NumpyModule
# INPUT_OUTPUT_FEATURE is the number of input and output of each of the network layers.
# (as well as the input of the network itself)
# Currently, with MAXIMUM_TLU_BIT_WIDTH bits maximum, we can use few weights
# max in the theoretical case.
INPUT_OUTPUT_FEATURE = [1, 2, 3]
# FIXME: https://github.com/luxfi/torusml-internal/issues/4172
@pytest.mark.flaky
@pytest.mark.parametrize(
"model",
[pytest.param(FCSmall), pytest.param(FCSeq), pytest.param(FCSeqAddBiasVec)],
)
@pytest.mark.parametrize(
"input_output_feature",
[pytest.param(input_output_feature) for input_output_feature in INPUT_OUTPUT_FEATURE],
)
@pytest.mark.parametrize(
"activation",
[
nn.ReLU6,
nn.Sigmoid,
],
)
@pytest.mark.parametrize("n_bits", [2])
@pytest.mark.parametrize("simulate", [True, False])
@pytest.mark.parametrize("verbose", [True, False])
def test_quantized_module_compilation(
input_output_feature,
model,
activation,
default_configuration,
n_bits,
simulate,
check_graph_input_has_no_tlu,
verbose,
check_is_good_execution_for_cml_vs_circuit,
):
"""Test a neural network compilation for FHE inference."""
# Define an input shape (n_examples, n_features)
input_shape = (5, input_output_feature)
# Build a random Quantized Fully Connected Neural Network
# Define the torch model
torch_fc_model = model(input_output_feature, activation)
# Create random input
numpy_input = numpy.random.uniform(-100, 100, size=input_shape)
# Create corresponding numpy model
numpy_fc_model = NumpyModule(torch_fc_model, torch.from_numpy(numpy_input).float())
# Quantize with post-training static method
post_training_quant = PostTrainingAffineQuantization(n_bits, numpy_fc_model)
quantized_model = post_training_quant.quantize_module(numpy_input)
# Compile
quantized_model.compile(
numpy_input,
default_configuration,
verbose=verbose,
)
check_is_good_execution_for_cml_vs_circuit(numpy_input, quantized_model, simulate=simulate)
check_graph_input_has_no_tlu(quantized_model.fhe_circuit.graph)
@pytest.mark.parametrize(
"model",
[pytest.param(TinyCNN)],
)
@pytest.mark.parametrize(
"input_output_feature",
[pytest.param(input_output_feature) for input_output_feature in [((8, 8), 2)]],
)
@pytest.mark.parametrize(
"activation",
[
nn.ReLU6,
],
)
@pytest.mark.parametrize("simulate", [True, False])
@pytest.mark.parametrize("verbose", [True, False])
def test_quantized_cnn_compilation(
input_output_feature,
model,
activation,
default_configuration,
check_graph_input_has_no_tlu,
simulate,
verbose,
check_is_good_execution_for_cml_vs_circuit,
):
"""Test a convolutional neural network compilation for FHE inference."""
n_bits = 2
# Define an input shape (n_examples, n_features)
input_shape, n_classes = input_output_feature
# Build a randomly initialized Quantized CNN Network
# Define the torch model
torch_cnn_model = model(n_classes, activation)
# Create random inputs with 1 channel each
numpy_input = numpy.random.uniform(-1, 1, size=(1, 1, *input_shape))
tensor_input = torch.from_numpy(numpy_input).float()
# Create corresponding numpy model
torch_cnn_model = NumpyModule(torch_cnn_model, tensor_input)
# Quantize with post-training static method
post_training_quant = PostTrainingAffineQuantization(n_bits, torch_cnn_model)
quantized_model = post_training_quant.quantize_module(numpy_input)
# Compile
quantized_model.compile(
numpy_input,
default_configuration,
verbose=verbose,
)
check_is_good_execution_for_cml_vs_circuit(numpy_input, quantized_model, simulate=simulate)
check_graph_input_has_no_tlu(quantized_model.fhe_circuit.graph)
class NumpyModuleTest(NumpyModule):
"""Test class to build NumpyModule in an alternative way."""
# pylint: disable-next=super-init-not-called
def __init__(self, onnx_model: onnx.ModelProto):
self.numpy_forward = lambda x: x
self._onnx_model = onnx_model
self._numpy_preprocessing = lambda x: x
self._onnx_preprocessing = None
def test_post_training_quantization_constant_folding():
"""Test to check that constant folding works properly."""
# First add a few initializers
f_one = 1.0
f_zero = 0.0
f_ones_init = numpy_helper.from_array(numpy.ones((10, 10), dtype=numpy.float32), "f_ones")
f_zeros_init = numpy_helper.from_array(numpy.zeros((10, 10), dtype=numpy.float32), "f_zeros")
constant_f_one = helper.make_node(
"Constant",
inputs=[],
outputs=["constant_f_one"],
name="constant_f_one_node",
value_float=f_one,
)
constant_f_zero = helper.make_node(
"Constant",
inputs=[],
outputs=["constant_f_zero"],
name="constant_f_zero_node",
value_float=f_zero,
)
add_one_and_zero = helper.make_node(
"Add",
inputs=["constant_f_one", "constant_f_zero"],
outputs=["add_one_and_zero"],
name="add_one_and_zero_node",
)
sub_zeros_and_ones = helper.make_node(
"Sub",
inputs=["f_zeros", "f_ones"],
outputs=["sub_zeros_and_ones"],
name="sub_zeros_and_ones_node",
)
model_input_name = "x_input"
x_input = helper.make_tensor_value_info(model_input_name, onnx.TensorProto.FLOAT, [10, 10])
model_output_name = "y_output"
y_output = helper.make_tensor_value_info(model_output_name, onnx.TensorProto.FLOAT, [10, 10])
add_input_f_one = helper.make_node(
"Add",
inputs=["constant_f_zero", "constant_f_one"],
outputs=["input_plus_one"],
name="input_plus_one_node",
)
exp_f_zero = helper.make_node(
"Exp",
inputs=["constant_f_zero"],
outputs=["exp_f_zero"],
name="exp_f_zero_node",
)
add_input_f_one_exp = helper.make_node(
"Add",
inputs=["input_plus_one", "exp_f_zero"],
outputs=["input_plus_two"],
name="input_plus_two_node",
)
negative_input = helper.make_node(
"Mul",
inputs=["input_plus_two", "sub_zeros_and_ones"],
outputs=["negative_input"],
name="negative_input_node",
)
negative_input_plus_one = helper.make_node(
"Add",
inputs=["negative_input", "add_one_and_zero"],
outputs=["negative_input_plus_one"],
name="negative_input_plus_one_node",
)
gemm = helper.make_node(
"Gemm",
inputs=["x_input", "negative_input_plus_one"],
outputs=[model_output_name],
name="matmul_with_folded_cst",
transA=0,
transB=0,
alpha=1.0,
beta=1.0,
)
graph_def = helper.make_graph(
nodes=[
constant_f_one,
constant_f_zero,
add_one_and_zero,
sub_zeros_and_ones,
add_input_f_one,
exp_f_zero,
add_input_f_one_exp,
negative_input,
negative_input_plus_one,
gemm,
],
name="test_constant_folding",
inputs=[x_input],
outputs=[y_output],
initializer=[f_ones_init, f_zeros_init],
)
# Create the model (ModelProto)
model_def = helper.make_model(graph_def, producer_name="onnx-example")
model_def.opset_import[0].version = OPSET_VERSION_FOR_ONNX_EXPORT
model_def = onnx.shape_inference.infer_shapes(model_def)
onnx.checker.check_model(model_def)
numpy_model = NumpyModuleTest(model_def)
# Quantize with post-training static method
post_training_quant = PostTrainingAffineQuantization(
MAX_BITWIDTH_BACKWARD_COMPATIBLE, numpy_model
)
numpy_input = numpy.random.random(size=(10, 10))
post_training_quant.quantize_module(numpy_input)
expected_constant = (0.0 + 1.0) + (
(numpy.zeros((10, 10), dtype=numpy.float32) - numpy.ones((10, 10), dtype=numpy.float32))
* ((1.0 + 0.0) + numpy.exp(0.0))
)
# Check we have only the gemm node in the quant_ops_dict after quantization as it's the only one
# that depends on a variable input
assert len(post_training_quant.quant_ops_dict) == 1
assert model_output_name in post_training_quant.quant_ops_dict
q_gemm = post_training_quant.quant_ops_dict[model_output_name][1]
assert isinstance(q_gemm, QuantizedGemm)
assert numpy.array_equal(q_gemm.constant_inputs[1].values, expected_constant)
@pytest.mark.parametrize("can_remove_tlu", [True, False])
def test_compile_multi_input_nn_with_input_tlus(
default_configuration,
check_graph_input_has_no_tlu,
check_is_good_execution_for_cml_vs_circuit,
can_remove_tlu,
):
"""Checks that there are input TLUs on a network for which input TLUs cannot be removed."""
torch_cnn_model = MultiOpOnSingleInputConvNN(can_remove_tlu)
numpy_input = numpy.random.uniform(-1, 1, size=(1, 1, 10, 10))
tensor_input = torch.from_numpy(numpy_input).float()
# Create corresponding numpy model
torch_cnn_model = NumpyModule(torch_cnn_model, tensor_input)
# Quantize with post-training static method
post_training_quant = PostTrainingAffineQuantization(2, torch_cnn_model)
quantized_model = post_training_quant.quantize_module(numpy_input)
# Compile
quantized_model.compile(
numpy_input,
default_configuration,
)
check_is_good_execution_for_cml_vs_circuit(numpy_input, quantized_model, simulate=True)
if can_remove_tlu:
check_graph_input_has_no_tlu(quantized_model.fhe_circuit.graph)
else:
# Check that the network has TLUs in the input node
with pytest.raises(AssertionError, match=".*TLU on an input node.*"):
check_graph_input_has_no_tlu(quantized_model.fhe_circuit.graph)