-
Notifications
You must be signed in to change notification settings - Fork 5
Description
In our research group, we have students who are implementing algorithms using mdarray and using a naive matrix multiplication routine for now. We will (soon) need to use efficient matrix multiplication and also other subroutines à la LAPACK.
I have to work at least on a stopgap solution, but would prefer to eventually come up with something proper. Here are some thoughts on the topic. I’d greatly appreciate comments and suggestions.
-
In addition to classic BLAS and LAPACK, there are at least faer, and matrixmultiply. Faer contains both a high-level and a low-level interface.
-
One way to supply dot-product and matrix multiplication is through a single crate where the backend(s) are selected through features. Here one problem that I see is that crate features must be additive. This seems to imply that there would have ot be some form of precedence for the case where there are more than two backends.
-
Another way to supply dot-product and matrix multiplication is through multiple crates. One for LAPACK, say, one for faer, one for matrixmultiply. This is more flexible, and it also avoids complicated features (because all of these backends have their own features which are relevant as well). Since traits can be implemented only once for each trait-type pair in Rust, I presume that they way to go would be to have a separate API for each crates. (One could strive to have a common form for these APIs and ideally to keep them interchangeable.) An advantage of this approach would be that it keeps the individual crates less complex.
-
Alternatively, one could try to introduce common traits, and require newtypes for the mdarray types. Or perhaps use some exotic approach like generic traits parametrized by marker types? But I fear complexity and boilerplate here.
-
Here is an idea that I just had. Traits could be implemented not for arrays, but for particular libraries. For example there could be a
MatMultrait that describes the multiplication of two mdarray slices. Then a crate might implement this trait and the usage would look something likeuse mdarray_blas::Blas; // Implements the mdarray_traits::MatMul trait fn main() { let a: &mdarray::Slice<...> = ...; let b: &mdarray::Slice<...> = ...; let c = Blas.matmul(a, b).into_new(); // into_new is a method of mdarray_traits::MatMulResult }
Implementing traits not on the matrices themselves but rather on types that represent the library (or library contexts - they could also take parameters) seems to have several advantages:
-
The use of the builder pattern for operations allows to have optional arguments and different kinds of dealing with the result (reusing allocations or creating new ones, say)
-
There is a way to implement a common standard API (as specified by the trait), but each library can have own extensions, parameters, etc.