From edaa208733f0ac5b9694e37a3b17e4b230bcde51 Mon Sep 17 00:00:00 2001 From: Umut Date: Fri, 6 Sep 2024 10:01:43 +0300 Subject: [PATCH] test(frontend-python): enable coverage back --- frontends/concrete-python/Makefile | 14 +++++--- .../concrete/fhe/compilation/artifacts.py | 2 +- .../concrete/fhe/compilation/module.py | 2 +- .../concrete/fhe/mlir/converter.py | 36 ++++++++----------- .../concrete/fhe/mlir/operations/min_max.py | 2 +- .../fhe/mlir/processors/assign_bit_widths.py | 3 -- .../concrete/fhe/representation/node.py | 3 ++ .../concrete/fhe/tfhers/context.py | 12 +++---- .../concrete/fhe/tfhers/values.py | 2 +- .../tests/compilation/test_modules.py | 5 +++ .../tests/execution/test_min_max.py | 14 +++++--- .../tests/execution/test_others.py | 8 +++++ .../tests/mlir/test_converter.py | 17 +++++++++ 13 files changed, 76 insertions(+), 44 deletions(-) diff --git a/frontends/concrete-python/Makefile b/frontends/concrete-python/Makefile index 356b156d54..52914b22af 100644 --- a/frontends/concrete-python/Makefile +++ b/frontends/concrete-python/Makefile @@ -6,10 +6,8 @@ BINDINGS_DIRECTORY=${COMPILER_BUILD_DIRECTORY}/tools/concretelang/python_package TFHERS_UTILS_DIRECTORY ?= $(PWD)/tests/tfhers-utils/ OS=undefined -COVERAGE_OPT="" ifeq ($(shell uname), Linux) OS=linux - COVERAGE_OPT="--cov=concrete.fhe --cov-fail-under=100 --cov-report=term-missing:skip-covered" RUNTIME_LIBRARY?=${COMPILER_BUILD_DIRECTORY}/lib/libConcretelangRuntime.so else ifeq ($(shell uname), Darwin) OS=darwin @@ -72,7 +70,9 @@ pytest: pytest-default pytest-default: tfhers-utils eval $(shell make silent_cp_activate) pytest tests -svv -n auto \ - ${COVERAGE_OPT} \ + --cov=concrete.fhe \ + --cov-fail-under=100 \ + --cov-report=term-missing:skip-covered \ --key-cache "${KEY_CACHE_DIRECTORY}" \ -m "${PYTEST_MARKERS}" @@ -96,7 +96,9 @@ pytest-multi: tfhers-utils pytest tests -svv -n auto \ --precision=multi \ --strategy=multi \ - ${COVERAGE_OPT} \ + --cov=concrete.fhe \ + --cov-fail-under=100 \ + --cov-report=term-missing:skip-covered \ --key-cache "${KEY_CACHE_DIRECTORY}" \ -m "${PYTEST_MARKERS}" @@ -110,7 +112,9 @@ pytest-gpu: tfhers-utils # test multi precision pytest tests -svv -n0 --use_gpu \ --precision=multi \ - ${COVERAGE_OPT} \ + --cov=concrete.fhe \ + --cov-fail-under=100 \ + --cov-report=term-missing:skip-covered \ --key-cache "${KEY_CACHE_DIRECTORY}" \ -m "${PYTEST_MARKERS}" diff --git a/frontends/concrete-python/concrete/fhe/compilation/artifacts.py b/frontends/concrete-python/concrete/fhe/compilation/artifacts.py index f113b56cd8..c19b0c2712 100644 --- a/frontends/concrete-python/concrete/fhe/compilation/artifacts.py +++ b/frontends/concrete-python/concrete/fhe/compilation/artifacts.py @@ -11,7 +11,7 @@ from ..representation import Graph -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from .module import ExecutionRt from .utils import Lazy diff --git a/frontends/concrete-python/concrete/fhe/compilation/module.py b/frontends/concrete-python/concrete/fhe/compilation/module.py index d6f0b96e7e..71bd05e7e1 100644 --- a/frontends/concrete-python/concrete/fhe/compilation/module.py +++ b/frontends/concrete-python/concrete/fhe/compilation/module.py @@ -121,7 +121,7 @@ def __str__(self): return self.graph.format() def __repr__(self) -> str: - return f"FheFunction({self.name=})" + return f"FheFunction(name={self.name})" def simulate(self, *args: Any) -> Any: """ diff --git a/frontends/concrete-python/concrete/fhe/mlir/converter.py b/frontends/concrete-python/concrete/fhe/mlir/converter.py index d7daae819c..2ec158dbcf 100644 --- a/frontends/concrete-python/concrete/fhe/mlir/converter.py +++ b/frontends/concrete-python/concrete/fhe/mlir/converter.py @@ -538,17 +538,13 @@ def matmul(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conversio def max(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conversion: assert len(preds) == 1 - - if all(pred.is_encrypted for pred in preds): - return ctx.min_max( - ctx.typeof(node), - preds[0], - axes=node.properties["kwargs"].get("axis", ()), - keep_dims=node.properties["kwargs"].get("keepdims", False), - operation="max", - ) - - return self.tlu(ctx, node, preds) + return ctx.min_max( + ctx.typeof(node), + preds[0], + axes=node.properties["kwargs"].get("axis", ()), + keep_dims=node.properties["kwargs"].get("keepdims", False), + operation="max", + ) def maximum(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conversion: assert len(preds) == 2 @@ -578,17 +574,13 @@ def maxpool3d(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conver def min(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conversion: assert len(preds) == 1 - - if all(pred.is_encrypted for pred in preds): - return ctx.min_max( - ctx.typeof(node), - preds[0], - axes=node.properties["kwargs"].get("axis", ()), - keep_dims=node.properties["kwargs"].get("keepdims", False), - operation="min", - ) - - return self.tlu(ctx, node, preds) + return ctx.min_max( + ctx.typeof(node), + preds[0], + axes=node.properties["kwargs"].get("axis", ()), + keep_dims=node.properties["kwargs"].get("keepdims", False), + operation="min", + ) def minimum(self, ctx: Context, node: Node, preds: List[Conversion]) -> Conversion: assert len(preds) == 2 diff --git a/frontends/concrete-python/concrete/fhe/mlir/operations/min_max.py b/frontends/concrete-python/concrete/fhe/mlir/operations/min_max.py index b1a6b54826..1df0f753ee 100644 --- a/frontends/concrete-python/concrete/fhe/mlir/operations/min_max.py +++ b/frontends/concrete-python/concrete/fhe/mlir/operations/min_max.py @@ -179,7 +179,7 @@ def __init__(self, index: Tuple[int, ...]): # get the representation of the mock def __repr__(self) -> str: - return f"{self.indices}" + return f"{self.indices}" # pragma: no cover # combine the indices of the mock with another mock into a new mock def combine(self, other: "Mock") -> "Mock": diff --git a/frontends/concrete-python/concrete/fhe/mlir/processors/assign_bit_widths.py b/frontends/concrete-python/concrete/fhe/mlir/processors/assign_bit_widths.py index b3a78da647..d229abb97a 100644 --- a/frontends/concrete-python/concrete/fhe/mlir/processors/assign_bit_widths.py +++ b/frontends/concrete-python/concrete/fhe/mlir/processors/assign_bit_widths.py @@ -357,9 +357,6 @@ def minimum_maximum(self, node: Node, preds: List[Node]): x = preds[0] y = preds[1] - assert x.output.is_encrypted - assert y.output.is_encrypted - assert isinstance(x.output.dtype, Integer) assert isinstance(y.output.dtype, Integer) assert isinstance(node.output.dtype, Integer) diff --git a/frontends/concrete-python/concrete/fhe/representation/node.py b/frontends/concrete-python/concrete/fhe/representation/node.py index 2d5aa54f04..3856bfacc7 100644 --- a/frontends/concrete-python/concrete/fhe/representation/node.py +++ b/frontends/concrete-python/concrete/fhe/representation/node.py @@ -377,6 +377,9 @@ def format(self, predecessors: List[str], maximum_constant_length: int = 45) -> if name not in KWARGS_IGNORED_IN_FORMATTING ) + if name in {"amin", "amax"}: + name = name[1:] + return f"{name}({', '.join(args)})" def label(self) -> str: diff --git a/frontends/concrete-python/concrete/fhe/tfhers/context.py b/frontends/concrete-python/concrete/fhe/tfhers/context.py index a5e2c7da2b..b78300b836 100644 --- a/frontends/concrete-python/concrete/fhe/tfhers/context.py +++ b/frontends/concrete-python/concrete/fhe/tfhers/context.py @@ -41,7 +41,7 @@ def _input_type(self, input_idx: int) -> Optional[TFHERSIntegerType]: Optional[TFHERSIntegerType]: input type. None means a non-tfhers type """ if isinstance(self.input_types, list): - return self.input_types[input_idx] + return self.input_types[input_idx] # pragma: no cover return self.input_types def _output_type(self, output_idx: int) -> Optional[TFHERSIntegerType]: @@ -54,7 +54,7 @@ def _output_type(self, output_idx: int) -> Optional[TFHERSIntegerType]: Optional[TFHERSIntegerType]: output type. None means a non-tfhers type """ if isinstance(self.output_types, list): - return self.output_types[output_idx] + return self.output_types[output_idx] # pragma: no cover return self.output_types def _input_keyid(self, input_idx: int) -> int: @@ -125,11 +125,11 @@ def import_value(self, buffer: bytes, input_idx: int) -> "fhe.Value": TfhersExporter.import_fheuint8(buffer, fheint_desc, keyid, variance) ) - msg = ( + msg = ( # pragma: no cover f"importing {'signed' if signed else 'unsigned'} integers of {bit_width}bits is not" " yet supported" ) - raise NotImplementedError(msg) + raise NotImplementedError(msg) # pragma: no cover def export_value(self, value: "fhe.Value", output_idx: int) -> bytes: """Export a value as a serialized TFHErs integer. @@ -154,11 +154,11 @@ def export_value(self, value: "fhe.Value", output_idx: int) -> bytes: if not signed: return TfhersExporter.export_fheuint8(value.inner, fheint_desc) - msg = ( + msg = ( # pragma: no cover f"exporting value to {'signed' if signed else 'unsigned'} integers of {bit_width}bits" " is not yet supported" ) - raise NotImplementedError(msg) + raise NotImplementedError(msg) # pragma: no cover def serialize_input_secret_key(self, input_idx: int) -> bytes: """Serialize secret key used for a specific input. diff --git a/frontends/concrete-python/concrete/fhe/tfhers/values.py b/frontends/concrete-python/concrete/fhe/tfhers/values.py index 492f946e39..c1ead341bd 100644 --- a/frontends/concrete-python/concrete/fhe/tfhers/values.py +++ b/frontends/concrete-python/concrete/fhe/tfhers/values.py @@ -27,7 +27,7 @@ def __init__( except Exception as e: # pylint: disable=broad-except msg = f"got error while trying to convert list value into a numpy array: {e}" raise ValueError(msg) from e - if value.dtype == np.dtype("O"): + if value.dtype == np.dtype("O"): # pragma: no cover msg = "malformed value array" raise ValueError(msg) diff --git a/frontends/concrete-python/tests/compilation/test_modules.py b/frontends/concrete-python/tests/compilation/test_modules.py index 5ba68e7156..0bc7edf84f 100644 --- a/frontends/concrete-python/tests/compilation/test_modules.py +++ b/frontends/concrete-python/tests/compilation/test_modules.py @@ -252,6 +252,9 @@ def dec(x): module.cleanup() assert set(artifacts.functions.keys()) == {"inc", "dec"} + assert repr(module.inc) == "FheFunction(name=inc)" + assert repr(module.dec) == "FheFunction(name=dec)" + def test_compiled_wrong_attribute(): """ @@ -331,6 +334,8 @@ def dec(x): assert module.inc.simulate(5) == 6 assert module.dec.simulate(5) == 4 + module.cleanup() + @pytest.mark.graphviz def test_print(helpers): diff --git a/frontends/concrete-python/tests/execution/test_min_max.py b/frontends/concrete-python/tests/execution/test_min_max.py index c89dff2df5..30b9ecd4f4 100644 --- a/frontends/concrete-python/tests/execution/test_min_max.py +++ b/frontends/concrete-python/tests/execution/test_min_max.py @@ -12,7 +12,7 @@ from concrete.fhe.values import ValueDescription cases = [] -for operation in [("max", lambda x: np.max(x)), ("min", lambda x: np.min(x))]: +for operation in ["max", "min"]: for bit_width in range(1, 5): for is_signed in [False, True]: for shape in [(), (4,), (3, 3)]: @@ -135,20 +135,26 @@ def test_min_max( Test np.min/np.max on encrypted values. """ - name, function = operation - dtype = Integer(is_signed=is_signed, bit_width=bit_width) description = ValueDescription(dtype, shape=shape, is_encrypted=True) print() print() print( - f"np.{name}({description}, axis={axis}, keepdims={keepdims})" + f"np.{operation}({description}, axis={axis}, keepdims={keepdims})" + (f" {{{strategy}}}" if strategy is not None else "") ) print() print() + assert operation in {"min", "max"} + + def function(x): + if operation == "min": + return np.min(x, axis=axis, keepdims=keepdims) + else: + return np.max(x, axis=axis, keepdims=keepdims) + parameter_encryption_statuses = {"x": "encrypted"} configuration = helpers.configuration() diff --git a/frontends/concrete-python/tests/execution/test_others.py b/frontends/concrete-python/tests/execution/test_others.py index 7da67fe48c..6607f0fc0a 100644 --- a/frontends/concrete-python/tests/execution/test_others.py +++ b/frontends/concrete-python/tests/execution/test_others.py @@ -995,6 +995,14 @@ def issue650(x): {}, id="(x ** 3, x + 100)", ), + pytest.param( + lambda x: np.min(x, 0), + { + "x": {"range": [0, 10], "status": "encrypted", "shape": (2, 2)}, + }, + {}, + id="np.min(x, 0)", + ), ], ) def test_others(function, parameters, configuration_overrides, helpers): diff --git a/frontends/concrete-python/tests/mlir/test_converter.py b/frontends/concrete-python/tests/mlir/test_converter.py index 58b62e5438..0e68edb7f3 100644 --- a/frontends/concrete-python/tests/mlir/test_converter.py +++ b/frontends/concrete-python/tests/mlir/test_converter.py @@ -1088,6 +1088,23 @@ def assign(x, y): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ but it needs to be for where operation return %3 + """, # noqa: E501 + ), + pytest.param( + lambda x: np.min(x), + {"x": "clear"}, + fhe.inputset(fhe.tensor[fhe.uint3, 3, 2]), # type: ignore + RuntimeError, + """ + +Function you are trying to compile cannot be compiled + +%0 = x # ClearTensor ∈ [0, 7] +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value is clear +%1 = min(%0) # ClearScalar ∈ [0, 4] +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ but computing min of clear values is not supported +return %1 + """, # noqa: E501 ), ],