1mod matmul;
13
14pub use matmul::{Gemm, MatView, MatmulError, matmul};
15
16pub const VERSION: &str = env!("CARGO_PKG_VERSION");
18
19#[derive(Debug, Clone, Copy)]
22pub struct View<'a, T> {
23 pub data: &'a [T],
25 pub offset: isize,
27 pub strides: &'a [isize],
29}
30
31pub fn map1<A: Copy, U>(shape: &[usize], a: View<'_, A>, out: &mut [U], f: impl Fn(A) -> U) {
38 for_rows(
39 shape,
40 [a.strides],
41 [a.offset],
42 out,
43 |row, [ba], [sa]| match lane(a.data, ba, sa, row.len()) {
44 Lane::Slice(x) => row.iter_mut().zip(x).for_each(|(o, &x)| *o = f(x)),
45 la => row
46 .iter_mut()
47 .enumerate()
48 .for_each(|(j, o)| *o = f(la.get(j))),
49 },
50 );
51}
52
53pub fn map2<A: Copy, B: Copy, U>(
59 shape: &[usize],
60 a: View<'_, A>,
61 b: View<'_, B>,
62 out: &mut [U],
63 f: impl Fn(A, B) -> U,
64) {
65 for_rows(
66 shape,
67 [a.strides, b.strides],
68 [a.offset, b.offset],
69 out,
70 |row, [ba, bb], [sa, sb]| {
71 let n = row.len();
72 match (lane(a.data, ba, sa, n), lane(b.data, bb, sb, n)) {
73 (Lane::Slice(x), Lane::Slice(y)) => {
74 row.iter_mut()
75 .zip(x)
76 .zip(y)
77 .for_each(|((o, &x), &y)| *o = f(x, y));
78 }
79 (Lane::Slice(x), Lane::Repeat(y)) => {
80 row.iter_mut().zip(x).for_each(|(o, &x)| *o = f(x, y))
81 }
82 (Lane::Repeat(x), Lane::Slice(y)) => {
83 row.iter_mut().zip(y).for_each(|(o, &y)| *o = f(x, y))
84 }
85 (la, lb) => row
86 .iter_mut()
87 .enumerate()
88 .for_each(|(j, o)| *o = f(la.get(j), lb.get(j))),
89 }
90 },
91 );
92}
93
94pub fn map3<A: Copy, B: Copy, C: Copy, U>(
100 shape: &[usize],
101 a: View<'_, A>,
102 b: View<'_, B>,
103 c: View<'_, C>,
104 out: &mut [U],
105 f: impl Fn(A, B, C) -> U,
106) {
107 let strides = [a.strides, b.strides, c.strides];
108 for_rows(
109 shape,
110 strides,
111 [a.offset, b.offset, c.offset],
112 out,
113 |row, [ba, bb, bc], [sa, sb, sc]| {
114 let n = row.len();
115 match (
116 lane(a.data, ba, sa, n),
117 lane(b.data, bb, sb, n),
118 lane(c.data, bc, sc, n),
119 ) {
120 (Lane::Slice(x), Lane::Slice(y), Lane::Slice(z)) => {
121 row.iter_mut()
122 .zip(x)
123 .zip(y)
124 .zip(z)
125 .for_each(|(((o, &x), &y), &z)| *o = f(x, y, z));
126 }
127 (la, lb, lc) => row
128 .iter_mut()
129 .enumerate()
130 .for_each(|(j, o)| *o = f(la.get(j), lb.get(j), lc.get(j))),
131 }
132 },
133 );
134}
135
136enum Lane<'a, T> {
138 Slice(&'a [T]),
140 Repeat(T),
142 Strided {
144 data: &'a [T],
145 start: isize,
146 step: isize,
147 },
148}
149
150impl<T: Copy> Lane<'_, T> {
151 fn get(&self, j: usize) -> T {
152 match self {
153 Self::Slice(x) => x[j],
154 Self::Repeat(x) => *x,
155 Self::Strided { data, start, step } => data[index(start + j as isize * step)],
156 }
157 }
158}
159
160fn lane<T: Copy>(data: &[T], base: isize, step: isize, n: usize) -> Lane<'_, T> {
163 match step {
164 1 => Lane::Slice(&data[index(base)..index(base) + n]),
165 0 => Lane::Repeat(data[index(base)]),
166 _ => Lane::Strided {
167 data,
168 start: base,
169 step,
170 },
171 }
172}
173
174fn for_rows<const N: usize, U>(
177 shape: &[usize],
178 strides: [&[isize]; N],
179 offsets: [isize; N],
180 out: &mut [U],
181 mut row_fn: impl FnMut(&mut [U], [isize; N], [isize; N]),
182) {
183 let size: usize = shape.iter().product();
184 assert_eq!(out.len(), size, "output length does not match shape");
185 if size == 0 {
186 return;
187 }
188 let (shape, strides) = collapse_dims(shape, strides);
189 let ndim = shape.len();
190 if ndim == 0 {
191 row_fn(out, offsets, [0; N]);
192 return;
193 }
194
195 let inner = shape[ndim - 1];
196 let inner_strides: [isize; N] = std::array::from_fn(|k| strides[k][ndim - 1]);
197 let mut position = vec![0usize; ndim - 1];
198 let mut base = offsets;
199 for row in out.chunks_exact_mut(inner) {
200 row_fn(row, base, inner_strides);
201 for d in (0..ndim - 1).rev() {
203 position[d] += 1;
204 for k in 0..N {
205 base[k] += strides[k][d];
206 }
207 if position[d] < shape[d] {
208 break;
209 }
210 for k in 0..N {
211 base[k] -= strides[k][d] * shape[d] as isize;
212 }
213 position[d] = 0;
214 }
215 }
216}
217
218#[allow(clippy::too_many_arguments)]
230pub fn reduce<T: Copy, A: Clone>(
231 kept: (&[usize], &[isize]),
232 reduced: (&[usize], &[isize]),
233 data: &[T],
234 offset: isize,
235 out: &mut [A],
236 init: A,
237 f: impl Fn(A, T, usize) -> A,
238 block: impl Fn(A, &[T]) -> A,
239) {
240 let (shape, [strides]) = collapse_dims(reduced.0, [reduced.1]);
241 let n: usize = shape.iter().product();
242 let contiguous = shape.len() == 1 && strides[0] == 1;
243 let fold = |base: isize| -> A {
244 let mut acc = init.clone();
245 if n == 0 {
246 return acc;
247 }
248 if contiguous || shape.is_empty() {
249 return block(acc, &data[index(base)..index(base) + n]);
250 }
251 let mut position = vec![0usize; shape.len()];
252 let mut cursor = base;
253 for i in 0..n {
254 acc = f(acc, data[index(cursor)], i);
255 for d in (0..shape.len()).rev() {
256 position[d] += 1;
257 cursor += strides[d];
258 if position[d] < shape[d] {
259 break;
260 }
261 cursor -= strides[d] * shape[d] as isize;
262 position[d] = 0;
263 }
264 }
265 acc
266 };
267 for_rows(kept.0, [kept.1], [offset], out, |row, [base], [step]| {
268 for (j, o) in row.iter_mut().enumerate() {
269 *o = fold(base + j as isize * step);
270 }
271 });
272}
273
274fn index(offset: isize) -> usize {
275 usize::try_from(offset).expect("view offset is negative")
276}
277
278pub fn collapse_dims<const N: usize>(
284 shape: &[usize],
285 strides: [&[isize]; N],
286) -> (Vec<usize>, [Vec<isize>; N]) {
287 let mut out_shape: Vec<usize> = Vec::with_capacity(shape.len());
288 let mut out_strides: [Vec<isize>; N] = std::array::from_fn(|_| Vec::with_capacity(shape.len()));
289 for d in 0..shape.len() {
290 if shape[d] == 1 {
291 continue;
292 }
293 let mergeable = out_shape.last().is_some() && {
294 let last = out_strides[0].len() - 1;
295 (0..N).all(|k| out_strides[k][last] == strides[k][d] * shape[d] as isize)
296 };
297 if mergeable {
298 let last = out_shape.len() - 1;
299 out_shape[last] *= shape[d];
300 for k in 0..N {
301 out_strides[k][last] = strides[k][d];
302 }
303 } else {
304 out_shape.push(shape[d]);
305 for k in 0..N {
306 out_strides[k].push(strides[k][d]);
307 }
308 }
309 }
310 (out_shape, out_strides)
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn version_is_set() {
319 assert!(!VERSION.is_empty());
320 }
321
322 #[test]
323 fn contiguous_binary() {
324 let mut out = [0i32; 6];
325 let s = [3, 1];
326 let a = View {
327 data: &[1, 2, 3, 4, 5, 6],
328 offset: 0,
329 strides: &s,
330 };
331 let b = View {
332 data: &[10, 20, 30, 40, 50, 60],
333 offset: 0,
334 strides: &s,
335 };
336 map2(&[2, 3], a, b, &mut out, |x, y| x + y);
337 assert_eq!(out, [11, 22, 33, 44, 55, 66]);
338 }
339
340 #[test]
341 fn transposed_and_broadcast() {
342 let mut out = [0i32; 6];
344 let a = View {
345 data: &[1, 2, 3, 4, 5, 6],
346 offset: 0,
347 strides: &[1, 3],
348 };
349 let b = View {
350 data: &[10, 20],
351 offset: 0,
352 strides: &[0, 1],
353 };
354 map2(&[3, 2], a, b, &mut out, |x, y| x + y);
355 assert_eq!(out, [11, 24, 12, 25, 13, 26]);
356 }
357
358 #[test]
359 fn negative_strides_and_offsets() {
360 let mut out = [0u8; 3];
362 let a = View {
363 data: &[0, 1, 2, 3, 4],
364 offset: 4,
365 strides: &[-2],
366 };
367 map1(&[3], a, &mut out, |x| x);
368 assert_eq!(out, [4, 2, 0]);
369 }
370
371 #[test]
372 fn scalar_and_empty() {
373 let mut out = [0f32; 1];
374 map1(
375 &[],
376 View {
377 data: &[2.5],
378 offset: 0,
379 strides: &[],
380 },
381 &mut out,
382 |x| x * 2.0,
383 );
384 assert_eq!(out, [5.0]);
385 let mut none: [f32; 0] = [];
386 map1(
387 &[3, 0],
388 View {
389 data: &[],
390 offset: 0,
391 strides: &[0, 1],
392 },
393 &mut none,
394 |x: f32| x,
395 );
396 }
397
398 #[test]
399 fn ternary() {
400 let mut out = [0i32; 3];
401 let s = [1];
402 map3(
403 &[3],
404 View {
405 data: &[1u8, 0, 1],
406 offset: 0,
407 strides: &s,
408 },
409 View {
410 data: &[1, 2, 3],
411 offset: 0,
412 strides: &s,
413 },
414 View {
415 data: &[-1],
416 offset: 0,
417 strides: &[0],
418 },
419 &mut out,
420 |c, x, y| if c != 0 { x } else { y },
421 );
422 assert_eq!(out, [1, -1, 3]);
423 }
424
425 #[test]
426 fn reduce_over_middle_axis_of_transposed_layout() {
427 let data: Vec<i32> = (0..24).collect();
429 let (shape, strides) = ([2usize, 3, 4], [1isize, 2, 6]);
430 let mut out = [0i32; 8];
431 reduce(
433 (&[shape[0], shape[2]], &[strides[0], strides[2]]),
434 (&[shape[1]], &[strides[1]]),
435 &data,
436 0,
437 &mut out,
438 0,
439 |acc, x, _| acc + x,
440 |acc, xs| xs.iter().fold(acc, |a, &x| a + x),
441 );
442 let expected: Vec<i32> = (0..2)
443 .flat_map(|i| (0..4).map(move |k| (0..3).map(|j| k * 6 + j * 2 + i).sum()))
444 .collect();
445 assert_eq!(out.to_vec(), expected);
446 }
447
448 #[test]
449 fn reduce_passes_block_index_and_handles_empty() {
450 let pick = |acc: (i32, usize), x: i32, i: usize| if x > acc.0 { (x, i) } else { acc };
451 let block = |acc, xs: &[i32]| xs.iter().enumerate().fold(acc, |a, (i, &x)| pick(a, x, i));
452 let mut out = [(0, 0usize); 2];
454 reduce(
455 (&[2], &[4]),
456 (&[4], &[1]),
457 &[5, 9, 1, 7, 3, 2, 8, 0],
458 0,
459 &mut out,
460 (i32::MIN, 0),
461 pick,
462 block,
463 );
464 assert_eq!(out, [(9, 1), (8, 2)]);
465 reduce(
466 (&[2], &[1]),
467 (&[4], &[2]),
468 &[5, 9, 1, 7, 3, 2, 8, 0],
469 0,
470 &mut out,
471 (i32::MIN, 0),
472 pick,
473 block,
474 );
475 assert_eq!(out, [(8, 3), (9, 0)]);
476 let mut sums = [7i32; 3];
477 let add = |a: i32, x: i32, _| a + x;
478 reduce(
479 (&[3], &[1]),
480 (&[0], &[1]),
481 &[0i32; 0],
482 0,
483 &mut sums,
484 0,
485 add,
486 |a, xs| xs.iter().fold(a, |a, &x| a + x),
487 );
488 assert_eq!(sums, [0, 0, 0]);
489 }
490
491 #[test]
492 fn collapse_merges_contiguous_dims() {
493 let (shape, [s]) = collapse_dims(&[2, 3, 4], [&[12, 4, 1]]);
494 assert_eq!((shape, s), (vec![24], vec![1]));
495 let (shape, [s]) = collapse_dims(&[2, 1, 3], [&[3, 99, 1]]);
496 assert_eq!((shape, s), (vec![6], vec![1]));
497 let (shape, [a, b]) = collapse_dims(&[2, 3], [&[3, 1], &[0, 1]]);
498 assert_eq!((shape, a, b), (vec![2, 3], vec![3, 1], vec![0, 1]));
499 }
500}