Compare commits

...

1 Commits

Author SHA1 Message Date
tmontaigu
92163c2646 chore(hlapi): Add array conversion from/to Vec<FheType>
Add `From` impl to allow conversion from Vec<FheType> like
Vec<FheUint32> to Cpu/Gpu array.
2025-07-16 16:54:16 +02:00
7 changed files with 316 additions and 8 deletions

View File

@@ -7,7 +7,7 @@ use crate::high_level_api::array::{ArrayBackend, BackendDataContainer, BackendDa
use crate::high_level_api::global_state;
use crate::integer::BooleanBlock;
use crate::prelude::{FheDecrypt, FheTryEncrypt};
use crate::{ClientKey, FheId};
use crate::{ClientKey, FheBool, FheId, Tag};
use rayon::prelude::*;
use std::ops::RangeBounds;
@@ -36,6 +36,28 @@ impl ArrayBackend for CpuFheBoolArrayBackend {
type Owned = Vec<BooleanBlock>;
}
impl From<Vec<FheBool>> for CpuFheBoolArray {
fn from(value: Vec<FheBool>) -> Self {
let vec = value
.into_iter()
.map(|b| BooleanBlock::new_unchecked(b.into_raw_parts()))
.collect::<Vec<_>>();
let shape = vec![vec.len()];
Self::new(vec, shape)
}
}
impl From<CpuFheBoolArray> for Vec<FheBool> {
fn from(value: CpuFheBoolArray) -> Self {
value
.into_container()
.into_iter()
.map(|boolean_block| FheBool::new(boolean_block, Tag::default()))
.collect()
}
}
impl BackendDataContainer for &[BooleanBlock] {
type Backend = CpuFheBoolArrayBackend;

View File

@@ -19,7 +19,7 @@ use crate::integer::server_key::radix_parallel::scalar_div_mod::SignedReciprocab
use crate::integer::server_key::{Reciprocable, ScalarMultiplier};
use crate::integer::{IntegerRadixCiphertext, RadixCiphertext, SignedRadixCiphertext};
use crate::prelude::{FheDecrypt, FheTryEncrypt};
use crate::{ClientKey, Error};
use crate::{ClientKey, Error, FheInt, FheUint, Tag};
use rayon::prelude::*;
use std::marker::PhantomData;
use std::ops::RangeBounds;
@@ -54,6 +54,48 @@ where
type Owned = Vec<T>;
}
impl<Id: FheUintId> From<Vec<FheUint<Id>>> for CpuFheUintArray<Id> {
fn from(value: Vec<FheUint<Id>>) -> Self {
let vec: Vec<_> = value
.into_iter()
.map(|uint| uint.into_raw_parts().0)
.collect();
let shape = vec![vec.len()];
Self::new(vec, shape)
}
}
impl<Id: FheUintId> From<CpuFheUintArray<Id>> for Vec<FheUint<Id>> {
fn from(value: CpuFheUintArray<Id>) -> Self {
value
.into_container()
.into_iter()
.map(|radix| FheUint::new(radix, Tag::default()))
.collect()
}
}
impl<Id: FheIntId> From<Vec<FheInt<Id>>> for CpuFheIntArray<Id> {
fn from(value: Vec<FheInt<Id>>) -> Self {
let vec: Vec<_> = value
.into_iter()
.map(|uint| uint.into_raw_parts().0)
.collect();
let shape = vec![vec.len()];
Self::new(vec, shape)
}
}
impl<Id: FheIntId> From<CpuFheIntArray<Id>> for Vec<FheInt<Id>> {
fn from(value: CpuFheIntArray<Id>) -> Self {
value
.into_container()
.into_iter()
.map(|radix| FheInt::new(radix, Tag::default()))
.collect()
}
}
#[inline]
#[track_caller]
fn par_map_sks_op_on_pair_of_elements<'a, T, F>(

View File

@@ -11,7 +11,7 @@ use super::super::{FheBackendArray, FheBackendArraySlice, FheBackendArraySliceMu
use crate::array::traits::TensorSlice;
use crate::integer::BooleanBlock;
use crate::prelude::{FheDecrypt, FheTryEncrypt};
use crate::{ClientKey, Device};
use crate::{ClientKey, CpuFheBoolArray, Device, FheBool};
use std::borrow::{Borrow, Cow};
use std::ops::RangeBounds;
@@ -33,6 +33,43 @@ impl ArrayBackend for DynFheBoolArrayBackend {
type Owned = InnerBoolArray;
}
impl TryFrom<Vec<FheBool>> for FheBoolArray {
type Error = crate::Error;
fn try_from(values: Vec<FheBool>) -> Result<Self, Self::Error> {
if values.is_empty() {
return Ok(Self::new(InnerBoolArray::Cpu(vec![]), vec![0]));
}
let shape = vec![values.len()];
let device_of_first = values[0].current_device();
let inner = match device_of_first {
Device::Cpu => {
let new_values = values
.into_iter()
.map(|value| value.ciphertext.into_cpu())
.collect::<Vec<_>>();
InnerBoolArray::Cpu(new_values)
}
#[cfg(feature = "gpu")]
Device::CudaGpu => return Err(crate::error!("Array do not support GPU")),
#[cfg(feature = "hpu")]
Device::Hpu => return Err(crate::error!("Array do not support HPU")),
};
Ok(Self::new(inner, shape))
}
}
impl From<CpuFheBoolArray> for FheBoolArray {
fn from(cpu_array: CpuFheBoolArray) -> Self {
let CpuFheBoolArray { elems, dims, _id } = cpu_array;
Self::new(InnerBoolArray::Cpu(elems), dims)
}
}
impl BitwiseArrayBackend for DynFheBoolArrayBackend {
fn bitand<'a>(
lhs: TensorSlice<'_, Self::Slice<'a>>,

View File

@@ -9,7 +9,7 @@ use crate::high_level_api::array::{ArrayBackend, BackendDataContainer, BackendDa
use crate::high_level_api::global_state;
use crate::integer::gpu::ciphertext::boolean_value::CudaBooleanBlock;
use crate::prelude::{FheDecrypt, FheTryEncrypt};
use crate::{ClientKey, FheBoolId};
use crate::{ClientKey, FheBool, FheBoolId, Tag};
use rayon::prelude::*;
use std::ops::RangeBounds;
@@ -72,6 +72,31 @@ impl From<Vec<CudaBooleanBlock>> for GpuBooleanOwned {
}
}
impl From<Vec<FheBool>> for GpuFheBoolArray {
fn from(value: Vec<FheBool>) -> Self {
let container = with_cuda_internal_keys(|cuda_keys| {
value
.into_iter()
.map(|val| val.ciphertext.into_gpu(&cuda_keys.streams))
.collect::<Vec<_>>()
});
let shape = vec![container.len()];
Self::new(container, shape)
}
}
impl From<GpuFheBoolArray> for Vec<FheBool> {
fn from(value: GpuFheBoolArray) -> Self {
value
.into_container()
.0
.into_iter()
.map(|cuda_bool_block| FheBool::new(cuda_bool_block, Tag::default()))
.collect()
}
}
impl BackendDataContainer for GpuBooleanSlice<'_> {
type Backend = GpuFheBoolArrayBackend;

View File

@@ -24,7 +24,7 @@ use crate::integer::gpu::ciphertext::{
use crate::integer::server_key::radix_parallel::scalar_div_mod::SignedReciprocable;
use crate::integer::server_key::{Reciprocable, ScalarMultiplier};
use crate::prelude::{CastInto, FheDecrypt, FheTryEncrypt};
use crate::{ClientKey, Error};
use crate::{ClientKey, Error, FheInt, FheUint, Tag};
use rayon::prelude::*;
use std::marker::PhantomData;
use std::ops::RangeBounds;
@@ -60,6 +60,54 @@ where
}
}
impl<Id: FheUintId> From<Vec<FheUint<Id>>> for GpuFheUintArray<Id> {
fn from(value: Vec<FheUint<Id>>) -> Self {
let container = with_cuda_internal_keys(|cuda_keys| {
value
.into_iter()
.map(|elem| elem.ciphertext.into_gpu(&cuda_keys.streams))
.collect::<Vec<_>>()
});
let shape = vec![container.len()];
Self::new(container, shape)
}
}
impl<Id: FheUintId> From<GpuFheUintArray<Id>> for Vec<FheUint<Id>> {
fn from(value: GpuFheUintArray<Id>) -> Self {
value
.into_container()
.0
.into_iter()
.map(|elem| FheUint::new(elem, Tag::default()))
.collect()
}
}
impl<Id: FheIntId> From<Vec<FheInt<Id>>> for GpuFheIntArray<Id> {
fn from(value: Vec<FheInt<Id>>) -> Self {
let container = with_cuda_internal_keys(|cuda_keys| {
value
.into_iter()
.map(|elem| elem.ciphertext.into_gpu(&cuda_keys.streams))
.collect::<Vec<_>>()
});
let shape = vec![container.len()];
Self::new(container, shape)
}
}
impl<Id: FheIntId> From<GpuFheIntArray<Id>> for Vec<FheInt<Id>> {
fn from(value: GpuFheIntArray<Id>) -> Self {
value
.into_container()
.0
.into_iter()
.map(|elem| FheInt::new(elem, Tag::default()))
.collect()
}
}
impl<T> ArrayBackend for GpuIntegerArrayBackend<T>
where
T: CudaIntegerRadixCiphertext,

View File

@@ -2,7 +2,11 @@ mod booleans;
mod signed;
mod unsigned;
use crate::{generate_keys, set_server_key, ClientKey, ConfigBuilder, FheId};
use crate::prelude::*;
use crate::{
generate_keys, set_server_key, ClientKey, ConfigBuilder, CpuFheBoolArray, CpuFheInt32Array,
CpuFheUint32Array, FheBool, FheId, FheInt32, FheUint32,
};
#[cfg(feature = "gpu")]
use crate::{Config, CudaServerKey};
use rand::distributions::{Distribution, Standard};
@@ -11,6 +15,8 @@ use std::fmt::Debug;
use crate::array::traits::IOwnedArray;
use crate::array::ClearArray;
#[cfg(feature = "gpu")]
use crate::array::{GpuFheBoolArray, GpuFheInt32Array, GpuFheUint32Array};
use crate::high_level_api::array::{FheBackendArray, FheBackendArraySlice};
use crate::prelude::{FheDecrypt, FheTryEncrypt};
use std::ops::{BitAnd, BitOr, BitXor};
@@ -293,3 +299,132 @@ where
assert_eq!(result, expected_result);
}
}
#[cfg(feature = "gpu")]
#[test]
fn test_gpu_conversions() {
let ck = setup_default_gpu();
let num_values = 50;
// Vec<FheUint> <=> GpuFheUint
{
let clears = draw_random_values::<u32>(num_values);
let encrypted = clears
.iter()
.map(|v| FheUint32::encrypt(*v, &ck))
.collect::<Vec<_>>();
let gpu_array = GpuFheUint32Array::from(encrypted);
let decrypted: Vec<u32> = gpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheUint32>::from(gpu_array);
let decrypted: Vec<u32> = encrypted
.iter()
.map(|fheuint| fheuint.decrypt(&ck))
.collect();
assert_eq!(decrypted, clears);
}
// Vec<FheInt> <=> GpuFheInt
{
let clears = draw_random_values::<i32>(num_values);
let encrypted = clears
.iter()
.map(|v| FheInt32::encrypt(*v, &ck))
.collect::<Vec<_>>();
let gpu_array = GpuFheInt32Array::from(encrypted);
let decrypted: Vec<i32> = gpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheInt32>::from(gpu_array);
let decrypted: Vec<i32> = encrypted.iter().map(|fheint| fheint.decrypt(&ck)).collect();
assert_eq!(decrypted, clears);
}
// Vec<FheBool> <=> GpuFheBool
{
let clears = draw_random_values::<bool>(num_values);
let encrypted = clears
.iter()
.map(|v| FheBool::encrypt(*v, &ck))
.collect::<Vec<_>>();
let gpu_array = GpuFheBoolArray::from(encrypted);
let decrypted: Vec<bool> = gpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheBool>::from(gpu_array);
let decrypted: Vec<bool> = encrypted
.iter()
.map(|fhebool| fhebool.decrypt(&ck))
.collect();
assert_eq!(decrypted, clears);
}
}
#[test]
fn test_cpu_conversions() {
let ck = setup_default_cpu();
let num_values = 50;
// Vec<FheUint> <=> CpuFheUint
{
let clears = draw_random_values::<u32>(num_values);
let encrypted = clears
.iter()
.map(|v| FheUint32::encrypt(*v, &ck))
.collect::<Vec<_>>();
let cpu_array = CpuFheUint32Array::from(encrypted);
let decrypted: Vec<u32> = cpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheUint32>::from(cpu_array);
let decrypted: Vec<u32> = encrypted
.iter()
.map(|fheuint| fheuint.decrypt(&ck))
.collect();
assert_eq!(decrypted, clears);
}
// Vec<FheInt> <=> CpuFheInt
{
let clears = draw_random_values::<i32>(num_values);
let encrypted = clears
.iter()
.map(|v| FheInt32::encrypt(*v, &ck))
.collect::<Vec<_>>();
let cpu_array = CpuFheInt32Array::from(encrypted);
let decrypted: Vec<i32> = cpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheInt32>::from(cpu_array);
let decrypted: Vec<i32> = encrypted.iter().map(|fheint| fheint.decrypt(&ck)).collect();
assert_eq!(decrypted, clears);
}
// Vec<FheBool> <=> CpuFheBool
{
let clears = draw_random_values::<bool>(num_values);
let encrypted = clears
.iter()
.map(|v| FheBool::encrypt(*v, &ck))
.collect::<Vec<_>>();
let cpu_array = CpuFheBoolArray::from(encrypted);
let decrypted: Vec<bool> = cpu_array.decrypt(&ck);
assert_eq!(decrypted, clears);
let encrypted = Vec::<FheBool>::from(cpu_array);
let decrypted: Vec<bool> = encrypted
.iter()
.map(|fhebool| fhebool.decrypt(&ck))
.collect();
assert_eq!(decrypted, clears);
}
}

View File

@@ -25,7 +25,7 @@ use crate::integer::hpu::ciphertext::HpuRadixCiphertext;
pub(in crate::high_level_api) enum InnerBoolean {
Cpu(BooleanBlock),
#[cfg(feature = "gpu")]
Cuda(crate::integer::gpu::ciphertext::boolean_value::CudaBooleanBlock),
Cuda(CudaBooleanBlock),
#[cfg(feature = "hpu")]
Hpu(HpuRadixCiphertext),
}
@@ -220,7 +220,6 @@ impl InnerBoolean {
&mut cuda_ct.0
}
#[cfg(feature = "gpu")]
pub(crate) fn into_cpu(self) -> BooleanBlock {
match self {
Self::Cpu(cpu_ct) => cpu_ct,