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

feat(hlapi): add Array types #1089

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

feat(hlapi): add Array types #1089

wants to merge 1 commit into from

Conversation

tmontaigu
Copy link
Contributor

No description provided.

@cla-bot cla-bot bot added the cla-signed label Apr 17, 2024
@tmontaigu tmontaigu force-pushed the tm/vector-hlapi branch 2 times, most recently from 4374ba3 to d385138 Compare April 22, 2024 11:32
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.

The general construction of backends looks straight forward with the current trait design, I'm a bit worried about the complexity of the trait bounds, though it might not really be an issue if those are meant to stay internal, but even then it might be hard to have other people onboarded on that code. Also sometimes the rust trait solvers chokes on more intricate constructs.

I don't know if it's doable but as core crypto containers are basically aliases for AsRef/AsMut, I'm wondering if we are not missing something somewhere to get the array like structs down to slices, so that all the combinations of BitAnd trait bounds e.g. would reduce to something like ArrayLikeBitAnd<LhsArrayLike, RhsArrayLike> where the array like arguments can reduce to slices and therefore have a single bound, maybe it's a matter of trait aliases/super traits we could discuss when there is some time to do so.

I'm a bit curious as to what the GPU stuff would look like in the Dyn backend

/// This trait is for "Owned" array types
pub trait IOwnedArray: Clone + Slicing + SlicingMut {}

/// Internal trait to abstract how container store data for the
Copy link
Member

Choose a reason for hiding this comment

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

if the trait is internal does it need to be pub ?

I was just curious given the Slicing traits above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to be pub because it appears in trait bounds of pub functions

but it being pub does not mean users can actually implement it since its in a private module

Comment on lines +39 to +49
pub trait Slicing {
type Slice<'a>
where
Self: 'a;

fn slice(&self, range: impl RangeBounds<usize>) -> Self::Slice<'_>;

fn as_slice(&self) -> Self::Slice<'_> {
self.slice(..)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

from a high level point of view I currently don't quite make out the difference between the slicing primitives here and the ones for backends lower in the file, also naming conventions seem to slightly differ between the two though they seem to generally do the same thing from afar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally, I would like to be able to have

pub trait Slicing {
    type Slice<'a>
    where
        Self: 'a;

    fn slice(&self, range: impl RangeBounds<usize>) -> Self::Slice<'_>;

    fn as_slice(&self) -> Self::Slice<'_> {
        self.slice(..)
    }
}


pub trait BackendDataContaier: Slicing {
    type Backend: ArrayBackend;
}

but, then I need to express that Slicing::Slice == <Backend as ArrayBackend>::Slice which is something normally easy', but due to the fact that I use GAT (type Slice<'a> where Self: 'a) it becomes a nightmare / impossible as neither of the below things work

pub trait BackendDataContaier: Slicing {
   type Backend: for <'a> ArrayBackend<Slice<'a> = Slicing::Slice<'a>>;
}
pub trait BackendDataContaier<'a>: Slicing {
   type Backend: ArrayBackend<Slice<'a> = Slicing::Slice<'a>>;
}

Comment on lines +107 to +108
+ BitOr<Array, Output = Array>
+ for<'a> BitOr<&'a Array, Output = Array>,
Copy link
Member

Choose a reason for hiding this comment

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

might be interesting to do some super traits for those if possible to have a single "simple" bound for BitOr, unless we don't expect users to ever use those

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I also thought about something like this :
https://github.com/zama-ai/tfhe-rs-internal/issues/488#issue-2167005871

Comment on lines +38 to +39
+ BitAnd<FheBackendArray<Backend, Id>, Output = FheBackendArray<Backend, Id>>
+ for<'a> BitAnd<&'a FheBackendArray<Backend, Id>, Output = FheBackendArray<Backend, Id>>,
Copy link
Member

Choose a reason for hiding this comment

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

same remark as below (to have some super trait if possible) though here the bounds look even more complex

Something like ArrayArrayBitAnd, ArraySliceBitAnd (maybe symetric to manage SliceArray as well with e.g. an alias)

This design very much like makes me think of the container stuff from core the approach is to:

  • take things that can be turned into slices
  • always use slices to abstract over the original container

I might be missing something and am sure the bounds are like this for a good reason but they look crazy 😅

If external users are never meant to fiddle with those it might be ok, but it still looks heavy and the rust trait solver sometimes chokes on more elaborate usage


pub struct DynFheBoolArrayBackend;

impl ArrayBackend for DynFheBoolArrayBackend {
Copy link
Member

Choose a reason for hiding this comment

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

the dyn backend is the one that could mix devices right ?

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 one where you can select and change device at runtime

@tmontaigu
Copy link
Contributor Author

The problem with AsRef, AsMut, Borrow, BorrowMut family of traits is that they do not work with struct where the reference is internal to the type since they require you to return some kind of &T.

In the cuda backend (and maybe other backends) slice type will likely need to be defined as

struct CudaSlice<'a>{
   ptr: *mu c_void,  // non owning ptr to contiguous memory chunk
   len: usize,
  ref: PhantomData<&'a ()>,
}

Here, the reference is 'internal' / inside of the type as opposed as the CPU side where the slice type is a primitive type [T] and &'a [T] is a ref to a [T].

So on the cuda side, we will have a
fn vec_as_slice(&self: CudaVec, range: ...) -> CudaSlice<'a>, which is not compatible with AsRef :

pub trait AsRef<T>
where
    T: ?Sized {
    // Required method
    fn as_ref(&self) -> &T;
}

because impl<'a> AsRef<CudaSlice<'a>> for CudaVec; would require me to return a &CudaSlice<'a> which we cannot

struct CudaVec {
  cuda_ptr: *mut c_void,
  len: usize,
}

impl<'a> AsRef<CudaSlice<'a>> for CudaVec {
    fn as_ref(&self> &CudaSlice<'a> {
        let slice = CudaSlice{ptr: self.ptr, len: self.len, ref: PhantomData};
      &slice // Ooopsy
    }
}

@IceTDrinker
Copy link
Member

IceTDrinker commented Apr 23, 2024

I know the AsRef/AsMut cannot work because they force to return a ref/ref mut was wondering if the current design is the closest to that behavior (converting to slice types always) without returning refs

fn bitnot(lhs: Self::Slice<'_>) -> Self::Owned;
}

/// Trait for backends that can do bitwise operations
Copy link
Contributor

Choose a reason for hiding this comment

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

with clear elements

pub trait BackendDataContainer {
type Backend: ArrayBackend;

fn as_sub_slice(
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename slice to be more consistent with trait Slicing
Same for mut variant

type SliceMut<'a>: BackendDataContainerMut<Backend = Self>
where
Self: 'a;
type Owned: BackendDataContainerMut<Backend = Self>;
Copy link
Contributor

Choose a reason for hiding this comment

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

BackendDataContainerMut for Owned, is this an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, BackendDataContainerMut is meant to be impl on type where we can mutate the data that is contained, which is something SlicesMut and Owned containers can do

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.

None yet

3 participants