Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tm/noise asserts #1782

Merged
merged 3 commits into from
Nov 26, 2024
Merged

Tm/noise asserts #1782

merged 3 commits into from
Nov 26, 2024

Conversation

tmontaigu
Copy link
Contributor

@tmontaigu tmontaigu commented Nov 14, 2024

This adds the noise-asserts feature, which will make
PBS functions do a noise level check.

This also adds an extra MaxNoiseLevel parameter
to Ciphertext::set_noise_level that is used when the noise-asserts
feature is on, to check that the given new-noise level does not
exceed the given MaxNoiseLevel. In case of problems, the code will panic

By default these checks will also be make in cfg(test)

Closes zama-ai/tfhe-rs-internal#313

@cla-bot cla-bot bot added the cla-signed label Nov 14, 2024
@tmontaigu tmontaigu force-pushed the tm/noise-asserts branch 2 times, most recently from 87ac779 to ff11669 Compare November 14, 2024 17:18
@tmontaigu tmontaigu marked this pull request as ready for review November 14, 2024 18:06
Copy link
Contributor

@mayeul-zama mayeul-zama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of asserting on max noise level, I seems to me that it would be better to do operations differently if the noise_level is going to be too big (as is done for the degree).

This would mean we could have more confidence in noise validity (not just in tests or when activating a feature) without the risk of panics.

I guess this would need more work
That being said, this PR is an improvement over the current state

@@ -138,7 +138,13 @@ impl Ciphertext {
self.noise_level
}

pub fn set_noise_level(&mut self, noise_level: NoiseLevel) {
#[cfg_attr(any(feature = "noise-asserts", test), track_caller)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a simple #[track_caller] to avoid some complexity with features

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep went a bit too far here 😁

@@ -913,6 +922,14 @@ impl ServerKey {
ct: &Ciphertext,
acc: &ManyLookupTableOwned,
) -> Vec<Ciphertext> {
if cfg!(any(feature = "noise-asserts", test)) && ct.noise_level() != NoiseLevel::UNKNOWN {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NoiseLevel::UNKNOWN and NoiseLevel::MAX have the same value which means a NoiseLevel::MAX would not be checked here
It seems to me that it would be better to panic if we have NoiseLevel::MAX here

Copy link
Contributor Author

@tmontaigu tmontaigu Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to make the values of unknown and max different, because I do need the distinction because of some things done in the casting keys

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that if the noise level is unknown we may allow to do the PBS, but not if'ts at MAX

In the casting keys, at some point, there's a ciphertext that is set with noise_level unknown and we do a PBS on it, so either we allow to do PBS on NoiseLevel::UNKNOWN noise level, or We change the thing in the casting key to not be at NoiseLevel::UNKNOWN

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here to note that the MAX is usize::MAX i.e. an impossible value to reach, UNKNOWN was set to MAX as a safety

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we need to think about using

MAX = usize::MAX - 1
UNKNOWN = usize::MAX

and see if that could make sense in the general case

given that a noise of usize::MAX - 1

would break anything

also maybe time to change the noise level stuff to use u32 or u64 instead of usize, given that those don't refer to memory

not necessarily in this PR but it begs the question

lhs_b.set_noise_level(lhs_b.noise_level() + borrow.noise_level());
lhs_b.set_noise_level(
lhs_b.noise_level() + borrow.noise_level(),
self.key.max_noise_level,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServerKey.max_degree is initialized with MaxDegree::integer_radix_server_key in radix integers
This keeps space for carry propagation in all operations (except in carry propagation I guess, not sure if there are degree checks there)

I think we should do something similar for noise level
Or are noise checks in carry propagation enough? If so, I guess it would mean some operations could panic in carry propagation even if we could have prevented it in the previous operation (adding a PBS if the max_noise_level_keep_space_for_carry_propagation is too big)

In practice not sure this is a problem as noise and degree checks are often redundant.

Copy link
Contributor Author

@tmontaigu tmontaigu Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I do think max_degree and max_noise_level should be aligned I don't know if its better to align max_degree to max_noise_level or the reverse

Copy link
Contributor Author

@tmontaigu tmontaigu Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only use of the ServerKey.max_degree having space for 1 carry addition is for the carry_propagation algorithm if it's the sequential one, not parallel.

All other algorithm don't really rely on this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_noise_level is separate from the max degree and depends on the optimization of parameters

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's the question about "what do the parameters allow ?" i.e. the max_max_degree and the max_max_noise_level

and

"what are my constraints for these integers" which are the max_degree and max_noise_level

dont know if this is the right PR for this change (having both notions)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe NoiseLevel::MAX makes no sense to be honest

@tmontaigu
Copy link
Contributor Author

tmontaigu commented Nov 15, 2024

Instead of asserting on max noise level, I seems to me that it would be better to do operations differently if the noise_level is going to be too big (as is done for the degree).

It's not the goal here, the goal is to have some checks that enable us in test to know when we, by mistake crossed a limit on the noise level. And since those are at shortint level, its already too late to do the things differently (like you add two shortint blocks, but the sum exceeds the noise level, its too late, you don't have another way to do the addition)

Later at integer level we will add pre-checks on noise level to pre-clean if needed

unchecked_scalar_mul_assign(&mut ct, message_modulus.0 as u8);
let max_noise_level =
MaxNoiseLevel::new((ct.noise_level() * message_modulus.0).get());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this noise comes from "nowhere" right, we have no key for a reference max noise ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mayeul-zama the key has no information of the multiplication required to compress ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compression parameters take into account this shift of the message (which is clean, see assert NoiseLevel::NOMINAL above) to the carry bits.
In my understanding, it means that, in this compression pattern contract, the multiplication required to compress is by message_modulus.

@fakub could you confirm?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is, does it have the constant to multiply with in the key ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's message_modulus which is not stored in CompressionKey

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well that's not good at all, means keys can be easily misused

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When misused, it would most likely fail a few lines above on

 assert_eq!(
    lwe_size,
    ct.ct.lwe_size(),
    "All ciphertexts do not have the same lwe size as the packing keyswitch key"
);

But indeed it would be safer also check against the CompressionKey expected message_modulus

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I followed everything, is there something to fix in this PR ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for you but keys are missing an information as far as I can tell

@agnesLeroy
Copy link
Contributor

Is this something we should also port to the GPU backend you think?

@IceTDrinker
Copy link
Member

Is this something we should also port to the GPU backend you think?

Can be, depending on how you track the noise in your implem, this way during tests you can catch when noise gets too high, but I don't know how you manage the degree/noise data in CUDA :)

@tmontaigu
Copy link
Contributor Author

tmontaigu commented Nov 19, 2024

note that switching from usize -> u64 is a API break (small), and a potential serialization break depending on the serialization format (bincode should be fine)

@IceTDrinker
Copy link
Member

note that switching from usize -> u64 is a API (small), and a potential serialization break depending on the serialization format (bincode should be fine)

if needed tfhe-versionable should help manage that

Copy link
Member

@IceTDrinker IceTDrinker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see lots of small things that could be changed to make everything consistent, do we say it's for another PR ?

@tmontaigu
Copy link
Contributor Author

tmontaigu commented Nov 20, 2024

I'm already working on making the Degree,MaxDegree,MessageModulus,CarryModulus use u64, I can add the commit to this PR once its done

@IceTDrinker
Copy link
Member

I'm already working on make the Degree,MaxDegree,MessageModulus,CarryModulus use u64, I can add the commit to this PR once its done

fine by me

This comment was marked as outdated.

@tmontaigu tmontaigu force-pushed the tm/noise-asserts branch 2 times, most recently from ecc92d8 to 0fbe2f1 Compare November 21, 2024 17:27
Copy link
Member

@IceTDrinker IceTDrinker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot !

@IceTDrinker
Copy link
Member

Some integer test broke with the noise check

@zama-bot zama-bot removed the approved label Nov 22, 2024
@tmontaigu tmontaigu force-pushed the tm/noise-asserts branch 2 times, most recently from 7ad5f9a to c74d53a Compare November 22, 2024 16:42
This adds the noise-asserts feature, which will make
PBS functions do a noise level check.

This also adds an extra MaxNoiseLevel parameter
to Ciphertext::set_noise_level that is used when the noise-asserts
feature is on, to check that the given new-noise level does not
exceed the given MaxNoiseLevel. In case of problems, the code will panic

By default these checks will also be make in cfg(test)
Change from usize to u64 for MaxNoiseLevel and NoiseLevel

This is an API break as `new` and `get` handle/returns u64
instead of usize

This is also a potential serialization break depending on the
serializer used (bincode should be fine as it serializes usize as u64)
This switches from usize to u64 for shortint's metdata:
* Degree
* MaxDegree
* CarryModulus
* MessageModulus

The reasoning is that usize should be preferred when the value is used
as some kind of index, memory access, etc, and not numbers like these
metadata are.

This is a breaking API change
This is also a somewhat breaking serialization change
depending on the serialization format (bincode should be ok as it
encodes usize as u64)
@IceTDrinker
Copy link
Member

Looking good

@tmontaigu tmontaigu merged commit ade9a66 into main Nov 26, 2024
106 checks passed
@tmontaigu tmontaigu deleted the tm/noise-asserts branch November 26, 2024 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants