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