Skip to content

Commit

Permalink
chore(optimizer): enhance optimizer errors
Browse files Browse the repository at this point in the history
  • Loading branch information
aPere3 committed Sep 2, 2024
1 parent 165746d commit 26dd903
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
})
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -944,8 +944,8 @@ pub mod tests {
.map(ToString::to_string)
.collect::<Vec<String>>();
let expected_constraint_strings = vec![
"At location unknown location:\n²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-5 (1bits partition:0 count:1, dom=10)",
"At location unknown location:\n²Br[0] < (2²)**-6 (2bits partition:0 count:1, dom=12)",
"²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-5 (1bits partition:0 count:1, dom=10)",
"²Br[0] < (2²)**-6 (2bits partition:0 count:1, dom=12)",
];
assert!(actual_constraint_strings == expected_constraint_strings);
}
Expand All @@ -967,10 +967,10 @@ pub mod tests {
.map(ToString::to_string)
.collect::<Vec<String>>();
let expected_constraint_strings = vec![
"At location unknown location:\n²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
"At location unknown location:\n²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
"At location unknown location:\n²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:\n²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
"²Br[0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
"²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
"²Br[0] + 1σ²Br[1] + 1σ²FK[1→0] + 1σ²K[0] + 1σ²M[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
"²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
];
assert_eq!(actual_constraint_strings, expected_constraint_strings);
let partitions = vec![
Expand Down Expand Up @@ -1010,12 +1010,12 @@ pub mod tests {
.map(ToString::to_string)
.collect::<Vec<String>>();
let expected_constraint_strings = vec![
"At location unknown location:\n²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:\n²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
"At location unknown location:\n²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:\n²Br[2] < (2²)**-7 (3bits partition:2 count:1, dom=14)",
"At location unknown location:\n²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:\n²Br[0] < (2²)**-7 (3bits partition:0 count:1, dom=14)",
"²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)",
"²Br[0] + 1σ²K[0→1] + 1σ²M[1] < (2²)**-10 (6bits partition:1 count:1, dom=20)",
"²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)",
"²Br[2] < (2²)**-7 (3bits partition:2 count:1, dom=14)",
"²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)",
"²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]
Expand Down Expand Up @@ -1272,17 +1272,17 @@ pub mod tests {
.collect();
let expected_constraints = [
// First lut to force partition HIGH_PRECISION_PARTITION
"At location unknown location:\n²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
"²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:\n²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)",
"²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:\n²Br[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
"²Br[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
];
for (c, ec) in constraints.iter().zip(expected_constraints) {
assert!(
Expand Down Expand Up @@ -1333,14 +1333,14 @@ pub mod tests {
.collect();
let expected_constraints = [
// First lut to force partition HIGH_PRECISION_PARTITION
"At location unknown location:\n²In[1] + 1σ²K[1] + 1σ²M[1] < (2²)**-8 (4bits partition:1 count:1, dom=16)",
"²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:\n²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)",
"²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!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}
}
Expand Down
19 changes: 18 additions & 1 deletion docs/compilation/common_errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)`.
2 changes: 1 addition & 1 deletion frontends/concrete-python/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 11 additions & 7 deletions frontends/concrete-python/tests/compilation/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""

import inspect
import re
import tempfile
from pathlib import Path

Expand Down Expand Up @@ -125,14 +124,19 @@ class Module:
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():
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -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)."
)

0 comments on commit 26dd903

Please sign in to comment.