1use half::f16;
4
5#[derive(Debug, Clone, Copy)]
7pub struct MatView<'a, T> {
8 pub data: &'a [T],
10 pub offset: isize,
12 pub row_stride: isize,
14 pub col_stride: isize,
16}
17
18impl<T> MatView<'_, T> {
19 fn assert_in_bounds(&self, rows: usize, cols: usize) {
21 if rows == 0 || cols == 0 {
22 return;
23 }
24 let span = |n: usize, stride: isize| (n as isize - 1) * stride;
25 let (r, c) = (span(rows, self.row_stride), span(cols, self.col_stride));
26 let lo = self.offset + r.min(0) + c.min(0);
27 let hi = self.offset + r.max(0) + c.max(0);
28 assert!(
29 lo >= 0 && (hi as usize) < self.data.len(),
30 "matrix view reaches outside its data"
31 );
32 }
33}
34
35mod sealed {
36 pub trait Sealed {}
37}
38
39pub trait Gemm: Copy + Default + sealed::Sealed + 'static {
42 fn mul_add(acc: Self, a: Self, b: Self) -> Self;
44
45 #[cfg(not(miri))]
52 unsafe fn gemm(
53 m: usize,
54 n: usize,
55 k: usize,
56 dst: &mut [Self],
57 a: MatView<'_, Self>,
58 b: MatView<'_, Self>,
59 );
60}
61
62macro_rules! impl_gemm {
63 ($t:ty, $g:ty, $mul_add:expr, $one:expr) => {
64 impl sealed::Sealed for $t {}
65 impl Gemm for $t {
66 fn mul_add(acc: Self, a: Self, b: Self) -> Self {
67 let f: fn(Self, Self, Self) -> Self = $mul_add;
68 f(acc, a, b)
69 }
70
71 #[cfg(not(miri))]
72 unsafe fn gemm(
73 m: usize,
74 n: usize,
75 k: usize,
76 dst: &mut [Self],
77 a: MatView<'_, Self>,
78 b: MatView<'_, Self>,
79 ) {
80 let lhs = a.data.as_ptr().wrapping_offset(a.offset).cast::<$g>();
81 let rhs = b.data.as_ptr().wrapping_offset(b.offset).cast::<$g>();
82 unsafe {
87 gemm::gemm::<$g>(
88 m,
89 n,
90 k,
91 dst.as_mut_ptr().cast::<$g>(),
92 1,
93 n as isize,
94 false,
95 lhs,
96 a.col_stride,
97 a.row_stride,
98 rhs,
99 b.col_stride,
100 b.row_stride,
101 $one,
102 $one,
103 false,
104 false,
105 false,
106 gemm::Parallelism::Rayon(0),
107 );
108 }
109 }
110 }
111 };
112}
113
114impl_gemm!(f32, f32, |acc, a, b| acc + a * b, 1.0);
115impl_gemm!(f64, f64, |acc, a, b| acc + a * b, 1.0);
116impl_gemm!(
117 f16,
118 gemm::f16,
119 |acc, a, b| f16::from_f32(acc.to_f32() + a.to_f32() * b.to_f32()),
120 gemm::f16::ONE
121);
122impl_gemm!(
123 [f32; 2],
124 gemm::c32,
125 |acc, a, b| [
126 acc[0] + a[0] * b[0] - a[1] * b[1],
127 acc[1] + a[0] * b[1] + a[1] * b[0]
128 ],
129 gemm::c32::new(1.0, 0.0)
130);
131
132pub fn matmul<T: Gemm>(
139 m: usize,
140 n: usize,
141 k: usize,
142 a: MatView<'_, T>,
143 b: MatView<'_, T>,
144 out: &mut [T],
145) {
146 assert_eq!(out.len(), m * n, "output length does not match m·n");
147 a.assert_in_bounds(m, k);
148 b.assert_in_bounds(k, n);
149 if m == 0 || n == 0 {
150 return;
151 }
152 if k == 0 {
153 out.fill(T::default());
154 return;
155 }
156 #[cfg(not(miri))]
157 unsafe {
160 T::gemm(m, n, k, out, a, b);
161 }
162 #[cfg(miri)]
163 naive(m, n, k, a, b, out);
164}
165
166#[cfg_attr(not(any(miri, test)), allow(dead_code))]
169fn naive<T: Gemm>(
170 m: usize,
171 n: usize,
172 k: usize,
173 a: MatView<'_, T>,
174 b: MatView<'_, T>,
175 out: &mut [T],
176) {
177 let at = |v: &MatView<'_, T>, i: usize, j: usize| {
178 v.data[(v.offset + i as isize * v.row_stride + j as isize * v.col_stride) as usize]
179 };
180 for i in 0..m {
181 for j in 0..n {
182 out[i * n + j] = (0..k).fold(T::default(), |acc, p| {
183 T::mul_add(acc, at(&a, i, p), at(&b, p, j))
184 });
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn matches_naive_on_strided_operands() {
195 let (m, n, k) = (7, 5, 9);
196 let data: Vec<f64> = (0..200).map(|i| (i as f64 * 0.37).sin()).collect();
197 let a = MatView {
199 data: &data,
200 offset: 10,
201 row_stride: 1,
202 col_stride: 11,
203 };
204 let b = MatView {
205 data: &data,
206 offset: 150,
207 row_stride: -12,
208 col_stride: 2,
209 };
210 let (mut fast, mut slow) = (vec![0.0; m * n], vec![0.0; m * n]);
211 matmul(m, n, k, a, b, &mut fast);
212 naive(m, n, k, a, b, &mut slow);
213 for (x, y) in fast.iter().zip(&slow) {
214 assert!((x - y).abs() < 1e-12, "{x} vs {y}");
215 }
216 }
217
218 #[test]
219 fn complex_and_zero_k() {
220 let a = [[1.0f32, 2.0], [0.0, 1.0]]; let b = [[3.0f32, 0.0], [0.0, -1.0]]; let mut out = [[0.0f32; 2]; 1];
223 let av = MatView {
224 data: &a,
225 offset: 0,
226 row_stride: 2,
227 col_stride: 1,
228 };
229 let bv = MatView {
230 data: &b,
231 offset: 0,
232 row_stride: 1,
233 col_stride: 1,
234 };
235 matmul(1, 1, 2, av, bv, &mut out);
236 assert_eq!(out, [[4.0, 6.0]]); let mut zeros = [9.0f32; 4];
238 let empty = MatView {
239 data: &[0.0f32; 0],
240 offset: 0,
241 row_stride: 0,
242 col_stride: 0,
243 };
244 matmul(2, 2, 0, empty, empty, &mut zeros);
245 assert_eq!(zeros, [0.0; 4]);
246 }
247
248 #[test]
249 #[should_panic(expected = "outside its data")]
250 fn rejects_out_of_bounds_views() {
251 let data = [0.0f32; 4];
252 let v = MatView {
253 data: &data,
254 offset: 0,
255 row_stride: 2,
256 col_stride: 1,
257 };
258 let mut out = [0.0f32; 9];
259 matmul(3, 3, 2, v, v, &mut out);
260 }
261}