mirror of
https://github.com/zama-ai/concrete.git
synced 2026-01-09 12:57:55 -05:00
chore(optimizer): enhance optimizer errors
This commit is contained in:
committed by
Alexandre Péré
parent
165746d406
commit
26dd90311c
@@ -11,7 +11,7 @@ pub enum Location {
|
||||
impl Display for Location {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unknown => write!(f, "unknown location"),
|
||||
Self::Unknown => write!(f, "unknown"),
|
||||
Self::File(file) => write!(f, "{}", file.file_name().unwrap().to_str().unwrap()),
|
||||
Self::Line(file, line) => {
|
||||
write!(f, "{}:{line}", file.file_name().unwrap().to_str().unwrap())
|
||||
|
||||
@@ -406,7 +406,7 @@ impl VariancedDag {
|
||||
.check_growing_input_noise()
|
||||
.map_err(|err| match err {
|
||||
Err::NotComposable(prev) => {
|
||||
Err::NotComposable(format!("At {loc}: please add `fhe.refresh(...)` to guarantee the function composability.\n{prev}."))
|
||||
Err::NotComposable(format!("At location {loc}:\n{prev}."))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
@@ -912,7 +912,7 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "called `Result::unwrap()` on an `Err` value: NotComposable(\"At unknown location: please add `fhe.refresh(...)` to guarantee the function composability.\\nThe noise of the node 0 is contaminated by noise coming straight from the input (partition: 0, coeff: 1.21).\")"
|
||||
expected = "called `Result::unwrap()` on an `Err` value: NotComposable(\"At location unknown:\\nThe noise of the node 0 is contaminated by noise coming straight from the input (partition: 0, coeff: 1.21).\")"
|
||||
)]
|
||||
fn test_composition_with_growing_inputs_panics() {
|
||||
let mut dag = unparametrized::Dag::new();
|
||||
@@ -944,8 +944,8 @@ pub mod tests {
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
let expected_constraint_strings = vec![
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-5 (1bits partition:0 count:1, dom=10)",
|
||||
"At location unknown location:\n1σ²Br[0] < (2²)**-6 (2bits partition:0 count:1, dom=12)",
|
||||
"1σ²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-5 (1bits partition:0 count:1, dom=10)",
|
||||
"1σ²Br[0] < (2²)**-6 (2bits partition:0 count:1, dom=12)",
|
||||
];
|
||||
assert!(actual_constraint_strings == expected_constraint_strings);
|
||||
}
|
||||
@@ -967,10 +967,10 @@ pub mod tests {
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
let expected_constraint_strings = vec![
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²Br[1] + 1σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"At location unknown location:\n1σ²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
|
||||
"1σ²Br[0] + 1σ²Br[1] + 1σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
];
|
||||
assert_eq!(actual_constraint_strings, expected_constraint_strings);
|
||||
let partitions = vec![
|
||||
@@ -1010,12 +1010,12 @@ pub mod tests {
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
let expected_constraint_strings = vec![
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→2] + 1σ²M[2] < (2²)**-17 (13bits partition:2 count:1, dom=34)",
|
||||
"At location unknown location:\n1σ²Br[2] < (2²)**-7 (3bits partition:2 count:1, dom=14)",
|
||||
"At location unknown location:\n1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"At location unknown location:\n1σ²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
|
||||
"1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→2] + 1σ²M[2] < (2²)**-17 (13bits partition:2 count:1, dom=34)",
|
||||
"1σ²Br[2] < (2²)**-7 (3bits partition:2 count:1, dom=14)",
|
||||
"1σ²Br[0] + 1σ²FK[0→1] + 1σ²Br[1] + 1σ²Br[2] + 1σ²FK[2→1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
"1σ²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
|
||||
];
|
||||
assert_eq!(actual_constraint_strings, expected_constraint_strings);
|
||||
let partitions = [1, 1, 0, 1, 1, 1, 2, 0]
|
||||
@@ -1272,17 +1272,17 @@ pub mod tests {
|
||||
.collect();
|
||||
let expected_constraints = [
|
||||
// First lut to force partition HIGH_PRECISION_PARTITION
|
||||
"At location unknown location:\n1σ²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
"1σ²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
// 16384(shift) = (2**7)², for Br[1]
|
||||
"At location unknown location:\n16384σ²Br[1] + 16384σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=22)",
|
||||
"16384σ²Br[1] + 16384σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=22)",
|
||||
// 4096(shift) = (2**6)², 1(due to 1 erase bit) for Br[0] and 1 for Br[1]
|
||||
"At location unknown location:\n4096σ²Br[0] + 4096σ²Br[1] + 4096σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=20)",
|
||||
"4096σ²Br[0] + 4096σ²Br[1] + 4096σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=20)",
|
||||
// 1024(shift) = (2**5)², 2(due to 2 erase bit for Br[0] and 1 for Br[1]
|
||||
"At location unknown location:\n2048σ²Br[0] + 1024σ²Br[1] + 1024σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=19)",
|
||||
"2048σ²Br[0] + 1024σ²Br[1] + 1024σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=19)",
|
||||
// 3(erase bit) Br[0] and 1 initial Br[1]
|
||||
"At location unknown location:\n3σ²Br[0] + 1σ²Br[1] + 1σ²FK[1→0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=18)",
|
||||
"3σ²Br[0] + 1σ²Br[1] + 1σ²FK[1→0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=18)",
|
||||
// Last lut to close the cycle
|
||||
"At location unknown location:\n1σ²Br[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
"1σ²Br[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
];
|
||||
for (c, ec) in constraints.iter().zip(expected_constraints) {
|
||||
assert!(
|
||||
@@ -1333,14 +1333,14 @@ pub mod tests {
|
||||
.collect();
|
||||
let expected_constraints = [
|
||||
// First lut to force partition HIGH_PRECISION_PARTITION
|
||||
"At location unknown location:\n1σ²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
"1σ²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
|
||||
// 16384(shift) = (2**7)², for Br[1]
|
||||
"At location unknown location:\n16384σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=22)",
|
||||
"16384σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=22)",
|
||||
// 4096(shift) = (2**6)², 1(due to 1 erase bit) for Br[0] and 1 for Br[1]
|
||||
"At location unknown location:\n4096σ²Br[0] + 4096σ²FK[0→1] + 4096σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=20)",
|
||||
"4096σ²Br[0] + 4096σ²FK[0→1] + 4096σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=20)",
|
||||
// 1024(shift) = (2**5)², 2(due to 2 erase bit for Br[0] and 1 for Br[1]
|
||||
"At location unknown location:\n2048σ²Br[0] + 2048σ²FK[0→1] + 1024σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=19)",
|
||||
"At location unknown location:\n3σ²Br[0] + 3σ²FK[0→1] + 1σ²Br[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=18)",
|
||||
"2048σ²Br[0] + 2048σ²FK[0→1] + 1024σ²Br[1] + 1σ²K[1→0] + 1σ²M[0] < (2²)**-4 (0bits partition:0 count:1, dom=19)",
|
||||
"3σ²Br[0] + 3σ²FK[0→1] + 1σ²Br[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=18)",
|
||||
];
|
||||
for (c, ec) in constraints.iter().zip(expected_constraints) {
|
||||
assert!(
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::optimization::dag::multi_parameters::feasible::Feasible;
|
||||
use crate::optimization::dag::multi_parameters::partition_cut::PartitionCut;
|
||||
use crate::optimization::dag::multi_parameters::partitions::PartitionIndex;
|
||||
use crate::optimization::dag::multi_parameters::{analyze, keys_spec};
|
||||
use crate::optimization::Err::NoParametersFound;
|
||||
|
||||
use super::feasible::Feasibility;
|
||||
use super::keys_spec::InstructionKeys;
|
||||
@@ -1050,7 +1049,14 @@ pub fn optimize(
|
||||
fix_point = params.clone();
|
||||
}
|
||||
if best_params.is_none() {
|
||||
return Err(NoParametersFound);
|
||||
match params.is_feasible {
|
||||
Feasibility::Unfeasible(ref unfeasible_constraint) => {
|
||||
return Err(optimization::Err::UnfeasibleVarianceConstraint(Box::new(
|
||||
unfeasible_constraint.to_owned(),
|
||||
)));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
let best_params = best_params.unwrap();
|
||||
sanity_check(
|
||||
@@ -1197,7 +1203,9 @@ pub fn optimize_to_circuit_solution(
|
||||
{
|
||||
return keys_spec::CircuitSolution::from_native_solution(sol, nb_instr);
|
||||
}
|
||||
return keys_spec::CircuitSolution::no_solution(NoParametersFound.to_string());
|
||||
return keys_spec::CircuitSolution::no_solution(
|
||||
optimization::Err::NoParametersFound.to_string(),
|
||||
);
|
||||
}
|
||||
let default_partition = PartitionIndex::FIRST;
|
||||
let dag_and_params = optimize(
|
||||
|
||||
@@ -18,8 +18,7 @@ impl fmt::Display for VarianceConstraint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"At location {}:\n{} < (2²)**{} ({}bits partition:{} count:{}, dom={})",
|
||||
self.location,
|
||||
"{} < (2²)**{} ({}bits partition:{} count:{}, dom={})",
|
||||
self.variance,
|
||||
self.safe_variance_bound.log2().round() / 2.0,
|
||||
self.precision,
|
||||
|
||||
@@ -16,10 +16,15 @@ pub enum Err {
|
||||
impl std::fmt::Display for Err {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NotComposable(details) => write!(f, "Program can not be composed: {details}"),
|
||||
Self::NotComposable(details) => write!(f, "Program can not be composed (see https://docs.zama.ai/concrete/compilation/common_errors#id-8.-unfeasible-noise-constraint): {details}"),
|
||||
Self::NoParametersFound => write!(f, "No crypto parameters could be found"),
|
||||
Self::UnfeasibleVarianceConstraint(constraint) => {
|
||||
write!(f, "Unfeasible noise constraint encountered: {constraint}")
|
||||
write!(
|
||||
f,
|
||||
"Unfeasible noise constraint encountered (see https://docs.zama.ai/concrete/compilation/common_errors#id-9.-non-composable-circuit): At location {}:\n{}.",
|
||||
constraint.location,
|
||||
constraint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ This document explains the most common errors and provides solutions to fix them
|
||||
**Possible solutions**:
|
||||
- Try to simplify your circuit.
|
||||
- Use smaller weights.
|
||||
- Add intermediate PBS to reduce the noise, with identity function `fhe.univariate(lambda x: x)`.
|
||||
- Add intermediate PBS to reduce the noise, with identity function `fhe.refresh(lambda x: x)`.
|
||||
|
||||
## 4. Too long inputs for table looup
|
||||
|
||||
@@ -77,5 +77,22 @@ This document explains the most common errors and provides solutions to fix them
|
||||
- Change your program.
|
||||
- Consider using tricks to replace ternary-if, as `c ? t : f = f + c * (t-f)`.
|
||||
|
||||
## 8. Unfeasible noise constraint
|
||||
|
||||
**Error message**: `Unfeasible noise constraint encountered`
|
||||
|
||||
**Cause**: The optimizer can't find cryptographic parameters for the circuit that are both secure and correct.
|
||||
|
||||
**Possible solutions**:
|
||||
- Try to simplify your circuit.
|
||||
- Use smaller weights.
|
||||
- Add intermediate PBS to reduce the noise, with identity function `fhe.refresh(x)`.
|
||||
|
||||
## 9. Non composable circuit
|
||||
|
||||
**Error message**: `Program can not be composed`
|
||||
|
||||
**Cause**: Some circuit outputs are contaminated by unrefreshed input noise.
|
||||
|
||||
**Possible solutions**:
|
||||
- Add intermediate PBS to refresh the noise with `fhe.refresh(x)`.
|
||||
|
||||
@@ -52,7 +52,7 @@ ignore=CVS
|
||||
# ignore-list. The regex matches against paths and can be in Posix or Windows
|
||||
# format. Because '\\' represents the directory delimiter on Windows systems,
|
||||
# it can't be used as an escape character.
|
||||
ignore-paths=
|
||||
ignore-paths=concrete/fhe/mlir/*
|
||||
|
||||
# Files or directories matching the regular expression patterns are skipped.
|
||||
# The regex matches against base names, not paths. The default value ignores
|
||||
|
||||
@@ -3,7 +3,6 @@ Tests of everything related to modules.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import re
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -125,14 +124,19 @@ def test_non_composable_message():
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
line_of_add = inspect.currentframe().f_lineno - 2
|
||||
expected_message = f"""\
|
||||
Program can not be composed: At test_modules.py:{line_of_add}:0: please add `fhe.refresh(...)` to guarantee the function composability.
|
||||
The noise of the node 0 is contaminated by noise coming straight from the input (partition: 0, coeff: 2.00).\
|
||||
"""
|
||||
with pytest.raises(RuntimeError, match=re.escape(expected_message)):
|
||||
line = inspect.currentframe().f_lineno - 2
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
Module.compile({"add": [(0, 0), (3, 3)]})
|
||||
|
||||
assert (
|
||||
str(excinfo.value)
|
||||
== f"""\
|
||||
Program can not be composed (see https://docs.zama.ai/concrete/compilation/common_errors#id-8.-unfeasible-noise-constraint): \
|
||||
At location test_modules.py:{line}:0:\nThe noise of the node 0 is contaminated by noise coming straight from the input \
|
||||
(partition: 0, coeff: 2.00).\
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_call_clear_circuits():
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Tests errors returned by the compiler.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from concrete import fhe
|
||||
|
||||
# pylint: disable=missing-class-docstring, missing-function-docstring, no-self-argument, unused-variable, no-member, unused-argument, function-redefined, expression-not-assigned
|
||||
# same disables for ruff:
|
||||
# ruff: noqa: N805, E501, F841, ARG002, F811, B015, RUF001
|
||||
|
||||
|
||||
def test_non_composable(helpers):
|
||||
"""
|
||||
Test optimizer error for lack of refresh.
|
||||
"""
|
||||
|
||||
@fhe.compiler({"x": "encrypted"})
|
||||
def circuit(x):
|
||||
return x * 2
|
||||
|
||||
line = inspect.currentframe().f_lineno - 2
|
||||
inputset = range(100)
|
||||
config = helpers.configuration().fork(composable=True, parameter_selection_strategy="MULTI")
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
circuit = circuit.compile(inputset, config)
|
||||
|
||||
assert (
|
||||
str(excinfo.value)
|
||||
== f"Program can not be composed (see https://docs.zama.ai/concrete/compilation/common_errors#id-8.-unfeasible-noise-constraint): \
|
||||
At location test_optimizer_errors.py:{line}:0:\nThe noise of the node 0 is contaminated by noise coming straight from the input \
|
||||
(partition: 0, coeff: 4.00)."
|
||||
)
|
||||
|
||||
|
||||
def test_unfeasible(helpers):
|
||||
"""
|
||||
Test optimizer error for unfeasible circuit.
|
||||
"""
|
||||
|
||||
@fhe.module()
|
||||
class Module:
|
||||
@fhe.function({"x": "encrypted"})
|
||||
def a(x):
|
||||
return fhe.refresh(x * 10)
|
||||
|
||||
@fhe.function({"x": "encrypted"})
|
||||
def b(x):
|
||||
return fhe.refresh(x * 1000)
|
||||
|
||||
line = inspect.currentframe().f_lineno - 2
|
||||
inputset = [np.random.randint(1, 1000, size=()) for _ in range(100)]
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
module = Module.compile({"a": inputset, "b": inputset}, p_error=0.000001)
|
||||
|
||||
assert (
|
||||
str(excinfo.value)
|
||||
== f"Unfeasible noise constraint encountered (see https://docs.zama.ai/concrete/compilation/common_errors#id-9.-non-composable-circuit): \
|
||||
At location test_optimizer_errors.py:{line}:0:\n21990232555520000000σ²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-4.5 (0bits partition:0 count:1, dom=73)."
|
||||
)
|
||||
Reference in New Issue
Block a user