Skip to content

overflowing neg #2238

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion tfhe/src/high_level_api/integers/signed/overflowing_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::high_level_api::global_state::with_thread_local_cuda_streams;
use crate::high_level_api::integers::FheIntId;
use crate::high_level_api::keys::InternalServerKey;
use crate::integer::block_decomposition::DecomposableInto;
use crate::prelude::{OverflowingAdd, OverflowingMul, OverflowingSub};
use crate::prelude::{OverflowingAdd, OverflowingMul, OverflowingNeg, OverflowingSub};
use crate::{FheBool, FheInt};

impl<Id> OverflowingAdd<Self> for &FheInt<Id>
Expand Down Expand Up @@ -515,3 +515,68 @@ where
<&Self as OverflowingMul<&Self>>::overflowing_mul(&self, other)
}
}

impl<Id> OverflowingNeg for &FheInt<Id>
where
Id: FheIntId,
{
type Output = FheInt<Id>;

/// Negates self, overflowing if this is equal to the minimum value.
///
/// * The operation is modular, i.e. on overflow the result wraps around.
/// * On overflow the [FheBool] is true (if self encrypts the minimum value), otherwise false
///
/// # Example
///
/// ```rust
/// use tfhe::prelude::*;
/// use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt16};
///
/// let (client_key, server_key) = generate_keys(ConfigBuilder::default());
/// set_server_key(server_key);
///
/// let a = FheInt16::encrypt(i16::MIN, &client_key);
///
/// let (result, overflowed) = a.overflowing_neg();
/// let (expected_result, expected_overflowed) = i16::MIN.overflowing_neg();
/// let result: i16 = result.decrypt(&client_key);
/// assert_eq!(result, expected_result);
/// assert_eq!(overflowed.decrypt(&client_key), expected_overflowed);
/// assert!(overflowed.decrypt(&client_key));
/// ```
fn overflowing_neg(self) -> (Self::Output, FheBool) {
global_state::with_internal_keys(|key| match key {
InternalServerKey::Cpu(cpu_key) => {
let (result, overflow) = cpu_key
.pbs_key()
.overflowing_neg_parallelized(&*self.ciphertext.on_cpu());
(
FheInt::new(result, cpu_key.tag.clone()),
FheBool::new(overflow, cpu_key.tag.clone()),
)
}
#[cfg(feature = "gpu")]
InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
let (result, overflow) = cuda_key
.pbs_key()
.overflowing_neg(&*self.ciphertext.on_gpu(streams), streams);
(
FheInt::new(result, cuda_key.tag.clone()),
FheBool::new(overflow, cuda_key.tag.clone()),
)
}),
})
}
}

impl<Id> OverflowingNeg for FheInt<Id>
where
Id: FheIntId,
{
type Output = Self;

fn overflowing_neg(self) -> (Self::Output, FheBool) {
<&Self as OverflowingNeg>::overflowing_neg(&self)
}
}
44 changes: 43 additions & 1 deletion tfhe/src/high_level_api/integers/unsigned/overflowing_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::high_level_api::global_state::with_thread_local_cuda_streams;
use crate::high_level_api::integers::FheUintId;
use crate::high_level_api::keys::InternalServerKey;
use crate::integer::block_decomposition::DecomposableInto;
use crate::prelude::{CastInto, OverflowingAdd, OverflowingMul, OverflowingSub};
use crate::prelude::{CastInto, OverflowingAdd, OverflowingMul, OverflowingNeg, OverflowingSub};
use crate::{FheBool, FheUint};

impl<Id> OverflowingAdd<Self> for &FheUint<Id>
Expand Down Expand Up @@ -509,3 +509,45 @@ where
<&Self as OverflowingMul<&Self>>::overflowing_mul(&self, other)
}
}

impl<Id> OverflowingNeg for &FheUint<Id>
where
Id: FheUintId,
{
type Output = FheUint<Id>;

fn overflowing_neg(self) -> (Self::Output, FheBool) {
global_state::with_internal_keys(|key| match key {
InternalServerKey::Cpu(cpu_key) => {
let (result, overflow) = cpu_key
.pbs_key()
.overflowing_neg_parallelized(&*self.ciphertext.on_cpu());
(
FheUint::new(result, cpu_key.tag.clone()),
FheBool::new(overflow, cpu_key.tag.clone()),
)
}
#[cfg(feature = "gpu")]
InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
let (result, overflow) = cuda_key
.pbs_key()
.overflowing_neg(&*self.ciphertext.on_gpu(streams), streams);
(
FheUint::new(result, cuda_key.tag.clone()),
FheBool::new(overflow, cuda_key.tag.clone()),
)
}),
})
}
}

impl<Id> OverflowingNeg for FheUint<Id>
where
Id: FheUintId,
{
type Output = Self;

fn overflowing_neg(self) -> (Self::Output, FheBool) {
<&Self as OverflowingNeg>::overflowing_neg(&self)
}
}
4 changes: 2 additions & 2 deletions tfhe/src/high_level_api/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
pub use crate::high_level_api::traits::{
BitSlice, CiphertextList, DivRem, FheDecrypt, FheEncrypt, FheEq, FheKeyswitch, FheMax, FheMin,
FheOrd, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt, IfThenElse, OverflowingAdd,
OverflowingMul, OverflowingSub, RotateLeft, RotateLeftAssign, RotateRight, RotateRightAssign,
ScalarIfThenElse, SquashNoise, Tagged,
OverflowingMul, OverflowingNeg, OverflowingSub, RotateLeft, RotateLeftAssign, RotateRight,
RotateRightAssign, ScalarIfThenElse, SquashNoise, Tagged,
};

pub use crate::conformance::ParameterSetConformant;
Expand Down
6 changes: 6 additions & 0 deletions tfhe/src/high_level_api/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ pub trait OverflowingMul<Rhs> {
fn overflowing_mul(self, rhs: Rhs) -> (Self::Output, FheBool);
}

pub trait OverflowingNeg {
type Output;

fn overflowing_neg(self) -> (Self::Output, FheBool);
}

pub trait BitSlice<Bounds> {
type Output;

Expand Down
36 changes: 31 additions & 5 deletions tfhe/src/integer/gpu/server_key/radix/bitwise_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::core_crypto::gpu::algorithms::{
use crate::core_crypto::gpu::vec::CudaVec;
use crate::core_crypto::gpu::CudaStreams;
use crate::core_crypto::prelude::LweBskGroupingFactor;
use crate::integer::gpu::ciphertext::boolean_value::CudaBooleanBlock;
use crate::integer::gpu::ciphertext::CudaIntegerRadixCiphertext;
use crate::integer::gpu::server_key::CudaBootstrappingKey;
use crate::integer::gpu::{
Expand Down Expand Up @@ -95,6 +96,31 @@ impl CudaServerKey {
ct.as_mut().info = ct.as_ref().info.after_bitnot();
}

pub(crate) unsafe fn unchecked_boolean_bitnot_assign_async(
&self,
ct: &mut CudaBooleanBlock,
streams: &CudaStreams,
) {
// We do (-ciphertext) + (msg_mod -1) as it allows to avoid an allocation
cuda_lwe_ciphertext_negate_assign(&mut ct.0.as_mut().d_blocks, streams);

let ct_blocks = ct.0.as_ref().d_blocks.lwe_ciphertext_count().0;

let shift_plaintext = self.encoding().encode(Cleartext(1u64)).0;

let scalar_vector = vec![shift_plaintext; ct_blocks];
let mut d_decomposed_scalar =
CudaVec::<u64>::new_async(ct.0.as_ref().d_blocks.lwe_ciphertext_count().0, streams, 0);
d_decomposed_scalar.copy_from_cpu_async(scalar_vector.as_slice(), streams, 0);

cuda_lwe_ciphertext_plaintext_add_assign(
&mut ct.0.as_mut().d_blocks,
&d_decomposed_scalar,
streams,
);
// Neither noise level nor the degree changes
}

pub fn unchecked_bitnot_assign<T: CudaIntegerRadixCiphertext>(
&self,
ct: &mut T,
Expand Down Expand Up @@ -164,7 +190,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn unchecked_bitop_assign_async<T: CudaIntegerRadixCiphertext>(
&self,
ct_left: &mut T,
Expand Down Expand Up @@ -447,7 +473,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn bitand_assign_async<T: CudaIntegerRadixCiphertext>(
&self,
ct_left: &mut T,
Expand Down Expand Up @@ -553,7 +579,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn bitor_assign_async<T: CudaIntegerRadixCiphertext>(
&self,
ct_left: &mut T,
Expand Down Expand Up @@ -658,7 +684,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn bitxor_assign_async<T: CudaIntegerRadixCiphertext>(
&self,
ct_left: &mut T,
Expand Down Expand Up @@ -756,7 +782,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn bitnot_assign_async<T: CudaIntegerRadixCiphertext>(
&self,
ct: &mut T,
Expand Down
56 changes: 54 additions & 2 deletions tfhe/src/integer/gpu/server_key/radix/neg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::core_crypto::gpu::CudaStreams;
use crate::integer::gpu::ciphertext::CudaIntegerRadixCiphertext;
use crate::integer::gpu::ciphertext::boolean_value::CudaBooleanBlock;
use crate::integer::gpu::ciphertext::{
CudaIntegerRadixCiphertext, CudaSignedRadixCiphertext, CudaUnsignedRadixCiphertext,
};
use crate::integer::gpu::server_key::CudaServerKey;
use crate::integer::gpu::unchecked_negate_integer_radix_async;
use crate::integer::server_key::radix_parallel::OutputFlag;
Expand Down Expand Up @@ -126,7 +129,7 @@ impl CudaServerKey {
/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronised
/// not be dropped until streams is synchronized
pub unsafe fn neg_async<T: CudaIntegerRadixCiphertext>(
&self,
ctxt: &T,
Expand All @@ -147,4 +150,53 @@ impl CudaServerKey {
self.propagate_single_carry_assign_async(&mut res, streams, None, OutputFlag::None);
res
}

/// # Safety
///
/// - `streams` __must__ be synchronized to guarantee computation has finished, and inputs must
/// not be dropped until streams is synchronized
pub unsafe fn overflowing_neg_async<T>(
&self,
ctxt: &T,
streams: &CudaStreams,
) -> (T, CudaBooleanBlock)
where
T: CudaIntegerRadixCiphertext,
{
let mut ct = if ctxt.block_carries_are_empty() {
ctxt.duplicate_async(streams)
} else {
let mut ct = ctxt.duplicate_async(streams);
self.full_propagate_assign_async(&mut ct, streams);
ct
};

self.bitnot_assign_async(&mut ct, streams);

if T::IS_SIGNED {
let tmp = CudaSignedRadixCiphertext {
ciphertext: ct.into_inner(),
};
let (result, overflowed) = self.signed_overflowing_scalar_add(&tmp, 1, streams);
let result = T::from(result.into_inner());
(result, overflowed)
} else {
let mut tmp = CudaUnsignedRadixCiphertext {
ciphertext: ct.into_inner(),
};
let mut overflowed = self.unsigned_overflowing_scalar_add_assign(&mut tmp, 1, streams);
self.unchecked_boolean_bitnot_assign_async(&mut overflowed, streams);
let result = T::from(tmp.into_inner());
(result, overflowed)
}
}

pub fn overflowing_neg<T>(&self, ctxt: &T, streams: &CudaStreams) -> (T, CudaBooleanBlock)
where
T: CudaIntegerRadixCiphertext,
{
let result = unsafe { self.overflowing_neg_async(ctxt, streams) };
streams.synchronize();
result
}
}
34 changes: 34 additions & 0 deletions tfhe/src/integer/gpu/server_key/radix/tests_signed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ where
}
}

impl<'a, F> FunctionExecutor<&'a SignedRadixCiphertext, (SignedRadixCiphertext, BooleanBlock)>
for GpuFunctionExecutor<F>
where
F: Fn(
&CudaServerKey,
&CudaSignedRadixCiphertext,
&CudaStreams,
) -> (CudaSignedRadixCiphertext, CudaBooleanBlock),
{
fn setup(&mut self, cks: &RadixClientKey, sks: Arc<ServerKey>) {
self.setup_from_keys(cks, &sks);
}

fn execute(
&mut self,
input: &'a SignedRadixCiphertext,
) -> (SignedRadixCiphertext, BooleanBlock) {
let context = self
.context
.as_ref()
.expect("setup was not properly called");

let d_ctxt =
CudaSignedRadixCiphertext::from_signed_radix_ciphertext(input, &context.streams);

let (gpu_result_0, gpu_result_1) = (self.func)(&context.sks, &d_ctxt, &context.streams);

(
gpu_result_0.to_signed_radix_ciphertext(&context.streams),
gpu_result_1.to_boolean_block(&context.streams),
)
}
}

impl<'a, F> FunctionExecutor<&'a SignedRadixCiphertext, (RadixCiphertext, BooleanBlock)>
for GpuFunctionExecutor<F>
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ use crate::integer::gpu::server_key::radix::tests_unsigned::{
};
use crate::integer::gpu::CudaServerKey;
use crate::integer::server_key::radix_parallel::tests_signed::test_neg::{
signed_default_neg_test, signed_unchecked_neg_test,
default_overflowing_neg_test, signed_default_neg_test, signed_unchecked_neg_test,
};
use crate::shortint::parameters::test_params::*;
use crate::shortint::parameters::*;

create_gpu_parameterized_test!(integer_unchecked_neg);
create_gpu_parameterized_test!(integer_neg);
create_gpu_parameterized_test!(integer_overflowing_neg);

fn integer_unchecked_neg<P>(param: P)
where
Expand All @@ -26,3 +27,8 @@ where
let executor = GpuFunctionExecutor::new(&CudaServerKey::neg);
signed_default_neg_test(param, executor);
}

fn integer_overflowing_neg(param: impl Into<PBSParameters>) {
let executor = GpuFunctionExecutor::new(&CudaServerKey::overflowing_neg);
default_overflowing_neg_test(param, executor);
}
Loading
Loading