1use super::argmax::GemmWithArgmax;
2use super::kernel::{Microkernel, MicrokernelWithArgmax, PortableMicrokernel};
3use super::packing::{pack_a, pack_b, packed_a_size, packed_b_size, Layout, Transpose};
4use super::tiling::{BlockIterator, TilingParams};
5use super::workspace::{GemmWorkspace, PackingBuffers};
6use crate::types::{TropicalSemiring, TropicalWithArgmax};
7
8#[cfg(feature = "parallel")]
9use rayon::prelude::*;
10
11pub unsafe fn tropical_gemm_portable<T: TropicalSemiring>(
35 m: usize,
36 n: usize,
37 k: usize,
38 a: *const T::Scalar,
39 lda: usize,
40 trans_a: Transpose,
41 b: *const T::Scalar,
42 ldb: usize,
43 trans_b: Transpose,
44 c: *mut T,
45 ldc: usize,
46) {
47 let params = TilingParams::PORTABLE;
48 let kernel = PortableMicrokernel;
49
50 tropical_gemm_inner::<T, PortableMicrokernel>(
51 m, n, k, a, lda, trans_a, b, ldb, trans_b, c, ldc, ¶ms, &kernel,
52 );
53}
54
55pub unsafe fn tropical_gemm_inner<T: TropicalSemiring, K: Microkernel<T> + Sync>(
64 m: usize,
65 n: usize,
66 k: usize,
67 a: *const T::Scalar,
68 lda: usize,
69 trans_a: Transpose,
70 b: *const T::Scalar,
71 ldb: usize,
72 trans_b: Transpose,
73 c: *mut T,
74 ldc: usize,
75 params: &TilingParams,
76 kernel: &K,
77) {
78 tropical_gemm_inner_with_workspace(
79 m,
80 n,
81 k,
82 a,
83 lda,
84 trans_a,
85 b,
86 ldb,
87 trans_b,
88 c,
89 ldc,
90 params,
91 kernel,
92 &mut GemmWorkspace::new(),
93 );
94}
95
96#[allow(clippy::too_many_arguments)]
101pub unsafe fn tropical_gemm_inner_with_workspace<T: TropicalSemiring, K: Microkernel<T> + Sync>(
102 m: usize,
103 n: usize,
104 k: usize,
105 a: *const T::Scalar,
106 lda: usize,
107 trans_a: Transpose,
108 b: *const T::Scalar,
109 ldb: usize,
110 trans_b: Transpose,
111 c: *mut T,
112 ldc: usize,
113 params: &TilingParams,
114 kernel: &K,
115 workspace: &mut GemmWorkspace<T::Scalar>,
116) {
117 #[cfg(feature = "parallel")]
118 if let Some(tiles) = split_gemm::<T>(
119 m,
120 n,
121 k,
122 a,
123 lda,
124 trans_a,
125 b,
126 ldb,
127 trans_b,
128 c,
129 std::ptr::null_mut(),
130 ldc,
131 K::MR,
132 K::NR,
133 ) {
134 let buffers = workspace.tasks(tiles.len());
135 tiles
136 .into_par_iter()
137 .zip(buffers.par_iter_mut())
138 .for_each(|(tile, buffers)| unsafe {
139 tropical_gemm_serial::<T, K>(
140 tile.m, tile.n, k, tile.a, lda, trans_a, tile.b, ldb, trans_b, tile.c, ldc,
141 params, kernel, buffers,
142 );
143 });
144 return;
145 }
146 tropical_gemm_serial::<T, K>(
147 m,
148 n,
149 k,
150 a,
151 lda,
152 trans_a,
153 b,
154 ldb,
155 trans_b,
156 c,
157 ldc,
158 params,
159 kernel,
160 &mut workspace.tasks(1)[0],
161 );
162}
163
164#[allow(clippy::too_many_arguments)]
165unsafe fn tropical_gemm_serial<T: TropicalSemiring, K: Microkernel<T>>(
166 m: usize,
167 n: usize,
168 k: usize,
169 a: *const T::Scalar,
170 lda: usize,
171 trans_a: Transpose,
172 b: *const T::Scalar,
173 ldb: usize,
174 trans_b: Transpose,
175 c: *mut T,
176 ldc: usize,
177 params: &TilingParams,
178 kernel: &K,
179 buffers: &mut PackingBuffers<T::Scalar>,
180) {
181 if m == 0 || n == 0 {
182 return;
183 }
184 for i in 0..m {
186 for j in 0..n {
187 c.add(i * ldc + j).write(T::tropical_zero());
188 }
189 }
190 if k == 0 {
191 return;
192 }
193
194 buffers.prepare(
195 packed_a_size(m.min(params.mc), k.min(params.kc), K::MR),
196 packed_b_size(k.min(params.kc), n.min(params.nc), K::NR),
197 );
198 let (packed_a, packed_b) = (&mut buffers.a, &mut buffers.b);
199
200 for (jc, nc) in BlockIterator::new(n, params.nc) {
203 for (pc, kc) in BlockIterator::new(k, params.kc) {
205 pack_b::<T::Scalar>(
207 kc,
208 nc,
209 b_panel_ptr(b, pc, jc, ldb, trans_b),
210 ldb,
211 Layout::RowMajor,
212 trans_b,
213 packed_b.as_mut_ptr(),
214 K::NR,
215 );
216
217 for (ic, mc) in BlockIterator::new(m, params.mc) {
219 pack_a::<T::Scalar>(
221 mc,
222 kc,
223 a_panel_ptr(a, ic, pc, lda, trans_a),
224 lda,
225 Layout::RowMajor,
226 trans_a,
227 packed_a.as_mut_ptr(),
228 K::MR,
229 );
230
231 let n_blocks = nc.div_ceil(K::NR);
233 for jr in 0..n_blocks {
234 let j_start = jr * K::NR;
235 let nr = (nc - j_start).min(K::NR);
236
237 let m_blocks = mc.div_ceil(K::MR);
239 for ir in 0..m_blocks {
240 let i_start = ir * K::MR;
241 let mr = (mc - i_start).min(K::MR);
242
243 let a_ptr = packed_a.as_ptr().add(ir * K::MR * kc);
245 let b_ptr = packed_b.as_ptr().add(jr * K::NR * kc);
246 let c_ptr = c.add((ic + i_start) * ldc + (jc + j_start));
247
248 kernel.execute(mr, nr, kc, a_ptr, b_ptr, c_ptr, ldc);
249 }
250 }
251 }
252 }
253 }
254}
255
256pub unsafe fn tropical_gemm_with_argmax_portable<T: TropicalWithArgmax<Index = u32>>(
263 m: usize,
264 n: usize,
265 k: usize,
266 a: *const T::Scalar,
267 lda: usize,
268 trans_a: Transpose,
269 b: *const T::Scalar,
270 ldb: usize,
271 trans_b: Transpose,
272 result: &mut GemmWithArgmax<T>,
273) {
274 let params = TilingParams::PORTABLE;
275 let kernel = PortableMicrokernel;
276
277 tropical_gemm_with_argmax_inner::<T, PortableMicrokernel>(
278 m, n, k, a, lda, trans_a, b, ldb, trans_b, result, ¶ms, &kernel,
279 );
280}
281
282pub unsafe fn tropical_gemm_with_argmax_inner<
291 T: TropicalWithArgmax<Index = u32>,
292 K: MicrokernelWithArgmax<T> + Sync,
293>(
294 m: usize,
295 n: usize,
296 k: usize,
297 a: *const T::Scalar,
298 lda: usize,
299 trans_a: Transpose,
300 b: *const T::Scalar,
301 ldb: usize,
302 trans_b: Transpose,
303 result: &mut GemmWithArgmax<T>,
304 params: &TilingParams,
305 kernel: &K,
306) {
307 tropical_gemm_with_argmax_inner_with_workspace(
308 m,
309 n,
310 k,
311 a,
312 lda,
313 trans_a,
314 b,
315 ldb,
316 trans_b,
317 result,
318 params,
319 kernel,
320 &mut GemmWorkspace::new(),
321 );
322}
323
324#[allow(clippy::too_many_arguments)]
329pub unsafe fn tropical_gemm_with_argmax_inner_with_workspace<
330 T: TropicalWithArgmax<Index = u32>,
331 K: MicrokernelWithArgmax<T> + Sync,
332>(
333 m: usize,
334 n: usize,
335 k: usize,
336 a: *const T::Scalar,
337 lda: usize,
338 trans_a: Transpose,
339 b: *const T::Scalar,
340 ldb: usize,
341 trans_b: Transpose,
342 result: &mut GemmWithArgmax<T>,
343 params: &TilingParams,
344 kernel: &K,
345 workspace: &mut GemmWorkspace<T::Scalar>,
346) {
347 let ldc = result.ld;
348 let (c, argmax) = result.as_mut_ptrs();
349 #[cfg(feature = "parallel")]
350 if let Some(tiles) = split_gemm::<T>(
351 m,
352 n,
353 k,
354 a,
355 lda,
356 trans_a,
357 b,
358 ldb,
359 trans_b,
360 c,
361 argmax,
362 ldc,
363 K::MR,
364 K::NR,
365 ) {
366 let buffers = workspace.tasks(tiles.len());
367 tiles
368 .into_par_iter()
369 .zip(buffers.par_iter_mut())
370 .for_each(|(tile, buffers)| unsafe {
371 tropical_gemm_with_argmax_serial::<T, K>(
372 tile.m,
373 tile.n,
374 k,
375 tile.a,
376 lda,
377 trans_a,
378 tile.b,
379 ldb,
380 trans_b,
381 tile.c,
382 tile.argmax,
383 ldc,
384 params,
385 kernel,
386 buffers,
387 );
388 });
389 return;
390 }
391 tropical_gemm_with_argmax_serial::<T, K>(
392 m,
393 n,
394 k,
395 a,
396 lda,
397 trans_a,
398 b,
399 ldb,
400 trans_b,
401 c,
402 argmax,
403 ldc,
404 params,
405 kernel,
406 &mut workspace.tasks(1)[0],
407 );
408}
409
410#[allow(clippy::too_many_arguments)]
411unsafe fn tropical_gemm_with_argmax_serial<
412 T: TropicalWithArgmax<Index = u32>,
413 K: MicrokernelWithArgmax<T>,
414>(
415 m: usize,
416 n: usize,
417 k: usize,
418 a: *const T::Scalar,
419 lda: usize,
420 trans_a: Transpose,
421 b: *const T::Scalar,
422 ldb: usize,
423 trans_b: Transpose,
424 c: *mut T,
425 argmax: *mut u32,
426 ldc: usize,
427 params: &TilingParams,
428 kernel: &K,
429 buffers: &mut PackingBuffers<T::Scalar>,
430) {
431 if m == 0 || n == 0 {
432 return;
433 }
434 for i in 0..m {
435 for j in 0..n {
436 c.add(i * ldc + j).write(T::tropical_zero());
437 argmax.add(i * ldc + j).write(0);
438 }
439 }
440 if k == 0 {
441 return;
442 }
443
444 buffers.prepare(
445 packed_a_size(m.min(params.mc), k.min(params.kc), K::MR),
446 packed_b_size(k.min(params.kc), n.min(params.nc), K::NR),
447 );
448 let (packed_a, packed_b) = (&mut buffers.a, &mut buffers.b);
449
450 for (jc, nc) in BlockIterator::new(n, params.nc) {
452 for (pc, kc) in BlockIterator::new(k, params.kc) {
453 pack_b::<T::Scalar>(
454 kc,
455 nc,
456 b_panel_ptr(b, pc, jc, ldb, trans_b),
457 ldb,
458 Layout::RowMajor,
459 trans_b,
460 packed_b.as_mut_ptr(),
461 K::NR,
462 );
463
464 for (ic, mc) in BlockIterator::new(m, params.mc) {
465 pack_a::<T::Scalar>(
466 mc,
467 kc,
468 a_panel_ptr(a, ic, pc, lda, trans_a),
469 lda,
470 Layout::RowMajor,
471 trans_a,
472 packed_a.as_mut_ptr(),
473 K::MR,
474 );
475
476 let n_blocks = nc.div_ceil(K::NR);
477 for jr in 0..n_blocks {
478 let j_start = jr * K::NR;
479 let nr = (nc - j_start).min(K::NR);
480
481 let m_blocks = mc.div_ceil(K::MR);
482 for ir in 0..m_blocks {
483 let i_start = ir * K::MR;
484 let mr = (mc - i_start).min(K::MR);
485
486 let a_ptr = packed_a.as_ptr().add(ir * K::MR * kc);
487 let b_ptr = packed_b.as_ptr().add(jr * K::NR * kc);
488 let c_ptr = c.add((ic + i_start) * ldc + (jc + j_start));
489 let argmax_ptr = argmax.add((ic + i_start) * ldc + (jc + j_start));
490
491 kernel.execute_with_argmax(
492 mr, nr, kc, pc, a_ptr, b_ptr, c_ptr, argmax_ptr, ldc,
493 );
494 }
495 }
496 }
497 }
498 }
499
500 for i in 0..m {
508 for j in 0..n {
509 if (*c.add(i * ldc + j)).is_no_contribution() {
510 argmax.add(i * ldc + j).write(0);
511 }
512 }
513 }
514}
515
516#[cfg(feature = "parallel")]
520struct GemmTile<T: TropicalSemiring> {
521 m: usize,
522 n: usize,
523 a: *const T::Scalar,
524 b: *const T::Scalar,
525 c: *mut T,
526 argmax: *mut u32,
527}
528
529#[cfg(feature = "parallel")]
533unsafe impl<T: TropicalSemiring> Send for GemmTile<T> {}
534
535#[cfg(feature = "parallel")]
536#[allow(clippy::too_many_arguments)]
537unsafe fn split_gemm<T: TropicalSemiring>(
538 m: usize,
539 n: usize,
540 k: usize,
541 a: *const T::Scalar,
542 lda: usize,
543 trans_a: Transpose,
544 b: *const T::Scalar,
545 ldb: usize,
546 trans_b: Transpose,
547 c: *mut T,
548 argmax: *mut u32,
549 ldc: usize,
550 mr: usize,
551 nr: usize,
552) -> Option<Vec<GemmTile<T>>> {
553 const MIN_WORK_PER_TASK: usize = 2 * 1024 * 1024;
556 let work = m.saturating_mul(n).saturating_mul(k);
557 if m == 0 || n == 0 || k == 0 || work / MIN_WORK_PER_TASK < 2 {
558 return None;
559 }
560 let workers = rayon::current_num_threads().min(work / MIN_WORK_PER_TASK);
561 let split_rows = m >= n;
562 let (dim, tile) = if split_rows { (m, mr) } else { (n, nr) };
563 let blocks = dim.div_ceil(tile);
564 let workers = workers.min(blocks);
565 if workers < 2 {
566 return None;
567 }
568 let chunk = blocks.div_ceil(workers) * tile;
569 let mut tiles = Vec::with_capacity(workers);
570 for (start, len) in BlockIterator::new(dim, chunk) {
571 let (row, col, rows, cols) = if split_rows {
572 (start, 0, len, n)
573 } else {
574 (0, start, m, len)
575 };
576 let offset = row * ldc + col;
577 tiles.push(GemmTile {
578 m: rows,
579 n: cols,
580 a: a_panel_ptr(a, row, 0, lda, trans_a),
581 b: b_panel_ptr(b, 0, col, ldb, trans_b),
582 c: c.add(offset),
583 argmax: if argmax.is_null() {
584 argmax
585 } else {
586 argmax.add(offset)
587 },
588 });
589 }
590 Some(tiles)
591}
592
593#[inline]
595unsafe fn a_panel_ptr<T>(
596 a: *const T,
597 row: usize,
598 col: usize,
599 lda: usize,
600 trans: Transpose,
601) -> *const T {
602 match trans {
603 Transpose::NoTrans => a.add(row * lda + col),
604 Transpose::Trans => a.add(col * lda + row),
605 }
606}
607
608#[inline]
610unsafe fn b_panel_ptr<T>(
611 b: *const T,
612 row: usize,
613 col: usize,
614 ldb: usize,
615 trans: Transpose,
616) -> *const T {
617 match trans {
618 Transpose::NoTrans => b.add(row * ldb + col),
619 Transpose::Trans => b.add(col * ldb + row),
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use crate::types::TropicalMaxPlus;
627 use crate::types::TropicalScalar;
628
629 #[test]
630 fn test_simple_gemm() {
631 let m = 2;
632 let n = 2;
633 let k = 3;
634
635 let a: [f64; 6] = [
637 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ];
640
641 let b: [f64; 6] = [
643 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ];
647
648 let mut c = vec![TropicalMaxPlus::tropical_zero(); m * n];
649
650 unsafe {
651 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
652 m,
653 n,
654 k,
655 a.as_ptr(),
656 3,
657 Transpose::NoTrans,
658 b.as_ptr(),
659 2,
660 Transpose::NoTrans,
661 c.as_mut_ptr(),
662 n,
663 );
664 }
665
666 assert_eq!(c[0].0, 8.0);
668 assert_eq!(c[1].0, 9.0);
670 assert_eq!(c[2].0, 11.0);
672 assert_eq!(c[3].0, 12.0);
674 }
675
676 #[test]
677 fn test_gemm_with_argmax() {
678 let m = 2;
679 let n = 2;
680 let k = 3;
681
682 let a: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
683 let b: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
684
685 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
686
687 unsafe {
688 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
689 m,
690 n,
691 k,
692 a.as_ptr(),
693 3,
694 Transpose::NoTrans,
695 b.as_ptr(),
696 2,
697 Transpose::NoTrans,
698 &mut result,
699 );
700 }
701
702 assert_eq!(result.get(0, 0).0, 8.0);
704 assert_eq!(result.get_argmax(0, 0), 2);
705
706 assert_eq!(result.get(1, 1).0, 12.0);
708 assert_eq!(result.get_argmax(1, 1), 2);
709 }
710
711 #[test]
712 fn test_gemm_with_argmax_all_positions() {
713 let m = 2;
715 let n = 2;
716 let k = 3;
717
718 let a: [f64; 6] = [
722 10.0, 1.0, 1.0, 1.0, 1.0, 10.0, ];
725 let b: [f64; 6] = [
726 10.0, 1.0, 1.0, 10.0, 1.0, 1.0, ];
730
731 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
732
733 unsafe {
734 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
735 m,
736 n,
737 k,
738 a.as_ptr(),
739 3,
740 Transpose::NoTrans,
741 b.as_ptr(),
742 2,
743 Transpose::NoTrans,
744 &mut result,
745 );
746 }
747
748 assert_eq!(result.get(0, 0).0, 20.0);
750 assert_eq!(result.get_argmax(0, 0), 0);
751
752 assert_eq!(result.get(0, 1).0, 11.0);
754 assert_eq!(result.get_argmax(0, 1), 0);
756
757 assert_eq!(result.get(1, 0).0, 11.0);
759 assert_eq!(result.get_argmax(1, 0), 0); assert_eq!(result.get(1, 1).0, 11.0);
763 assert_eq!(result.get_argmax(1, 1), 1); }
765
766 #[test]
767 fn test_gemm_minplus_with_argmax() {
768 use crate::types::TropicalMinPlus;
769
770 let m = 2;
771 let n = 2;
772 let k = 3;
773
774 let a: [f64; 6] = [
776 1.0, 5.0, 3.0, 2.0, 4.0, 6.0, ];
779 let b: [f64; 6] = [
780 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ];
784
785 let mut result: GemmWithArgmax<TropicalMinPlus<f64>> = GemmWithArgmax::new(m, n);
786
787 unsafe {
788 tropical_gemm_with_argmax_portable::<TropicalMinPlus<f64>>(
789 m,
790 n,
791 k,
792 a.as_ptr(),
793 3,
794 Transpose::NoTrans,
795 b.as_ptr(),
796 2,
797 Transpose::NoTrans,
798 &mut result,
799 );
800 }
801
802 assert_eq!(result.get(0, 0).0, 2.0);
804 assert_eq!(result.get_argmax(0, 0), 0);
805
806 assert_eq!(result.get(0, 1).0, 3.0);
808 assert_eq!(result.get_argmax(0, 1), 0);
809
810 assert_eq!(result.get(1, 0).0, 3.0);
812 assert_eq!(result.get_argmax(1, 0), 0);
813
814 assert_eq!(result.get(1, 1).0, 4.0);
816 assert_eq!(result.get_argmax(1, 1), 0);
817 }
818
819 #[test]
820 fn test_gemm_larger_with_argmax() {
821 let m = 8;
823 let n = 8;
824 let k = 8;
825
826 let a: Vec<f64> = (0..m * k).map(|i| i as f64).collect();
827 let b: Vec<f64> = (0..k * n).map(|i| (k * n - 1 - i) as f64).collect();
828
829 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
830
831 unsafe {
832 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
833 m,
834 n,
835 k,
836 a.as_ptr(),
837 k,
838 Transpose::NoTrans,
839 b.as_ptr(),
840 n,
841 Transpose::NoTrans,
842 &mut result,
843 );
844 }
845
846 for i in 0..m {
848 for j in 0..n {
849 assert!(result.get(i, j).0.is_finite());
850 assert!(result.get_argmax(i, j) < k as u32);
851 }
852 }
853 }
854
855 #[test]
856 fn test_gemm_trans_a() {
857 let m = 2;
861 let n = 2;
862 let k = 3;
863
864 let a: [f64; 6] = [
865 1.0, 4.0, 2.0, 5.0, 3.0, 6.0, ];
869
870 let b: [f64; 6] = [
871 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ];
875
876 let mut c = vec![TropicalMaxPlus::tropical_zero(); m * n];
877
878 unsafe {
879 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
880 m,
881 n,
882 k,
883 a.as_ptr(),
884 2,
885 Transpose::Trans, b.as_ptr(),
887 2,
888 Transpose::NoTrans,
889 c.as_mut_ptr(),
890 n,
891 );
892 }
893
894 assert_eq!(c[0].0, 8.0);
898 assert_eq!(c[1].0, 9.0);
900 assert_eq!(c[2].0, 11.0);
902 assert_eq!(c[3].0, 12.0);
904 }
905
906 #[test]
907 fn test_gemm_trans_b() {
908 let m = 2;
911 let n = 2;
912 let k = 3;
913
914 let a: [f64; 6] = [
915 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ];
918
919 let b: [f64; 6] = [
921 1.0, 3.0, 5.0, 2.0, 4.0, 6.0, ];
924
925 let mut c = vec![TropicalMaxPlus::tropical_zero(); m * n];
926
927 unsafe {
928 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
929 m,
930 n,
931 k,
932 a.as_ptr(),
933 3,
934 Transpose::NoTrans,
935 b.as_ptr(),
936 3,
937 Transpose::Trans, c.as_mut_ptr(),
939 n,
940 );
941 }
942
943 assert_eq!(c[0].0, 8.0);
947 assert_eq!(c[1].0, 9.0);
948 assert_eq!(c[2].0, 11.0);
949 assert_eq!(c[3].0, 12.0);
950 }
951
952 #[test]
953 fn test_gemm_trans_both() {
954 let m = 2;
956 let n = 2;
957 let k = 3;
958
959 let a: [f64; 6] = [1.0, 4.0, 2.0, 5.0, 3.0, 6.0];
961 let b: [f64; 6] = [1.0, 3.0, 5.0, 2.0, 4.0, 6.0];
963
964 let mut c = vec![TropicalMaxPlus::tropical_zero(); m * n];
965
966 unsafe {
967 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
968 m,
969 n,
970 k,
971 a.as_ptr(),
972 2,
973 Transpose::Trans,
974 b.as_ptr(),
975 3,
976 Transpose::Trans,
977 c.as_mut_ptr(),
978 n,
979 );
980 }
981
982 assert_eq!(c[0].0, 8.0);
983 assert_eq!(c[1].0, 9.0);
984 assert_eq!(c[2].0, 11.0);
985 assert_eq!(c[3].0, 12.0);
986 }
987
988 #[test]
989 fn test_gemm_empty_m() {
990 let m = 0;
991 let n = 2;
992 let k = 3;
993
994 let a: [f64; 0] = [];
995 let b: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
996 let mut c: Vec<TropicalMaxPlus<f64>> = vec![];
997
998 unsafe {
999 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
1000 m,
1001 n,
1002 k,
1003 a.as_ptr(),
1004 3,
1005 Transpose::NoTrans,
1006 b.as_ptr(),
1007 2,
1008 Transpose::NoTrans,
1009 c.as_mut_ptr(),
1010 n,
1011 );
1012 }
1013
1014 assert!(c.is_empty());
1016 }
1017
1018 #[test]
1019 fn test_gemm_empty_n() {
1020 let m = 2;
1021 let n = 0;
1022 let k = 3;
1023
1024 let a: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1025 let b: [f64; 0] = [];
1026 let mut c: Vec<TropicalMaxPlus<f64>> = vec![];
1027
1028 unsafe {
1029 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
1030 m,
1031 n,
1032 k,
1033 a.as_ptr(),
1034 3,
1035 Transpose::NoTrans,
1036 b.as_ptr(),
1037 2,
1038 Transpose::NoTrans,
1039 c.as_mut_ptr(),
1040 n,
1041 );
1042 }
1043
1044 assert!(c.is_empty());
1045 }
1046
1047 #[test]
1048 fn test_gemm_empty_k() {
1049 let m = 2;
1050 let n = 2;
1051 let k = 0;
1052
1053 let a: [f64; 0] = [];
1054 let b: [f64; 0] = [];
1055 let mut c = vec![TropicalMaxPlus::tropical_zero(); m * n];
1056
1057 unsafe {
1058 tropical_gemm_portable::<TropicalMaxPlus<f64>>(
1059 m,
1060 n,
1061 k,
1062 a.as_ptr(),
1063 0,
1064 Transpose::NoTrans,
1065 b.as_ptr(),
1066 2,
1067 Transpose::NoTrans,
1068 c.as_mut_ptr(),
1069 n,
1070 );
1071 }
1072
1073 for val in &c {
1075 assert!(val.0.is_infinite() && val.0 < 0.0);
1076 }
1077 }
1078
1079 #[test]
1080 fn test_gemm_with_argmax_empty_k() {
1081 let m = 2;
1082 let n = 2;
1083 let k = 0;
1084
1085 let a: [f64; 0] = [];
1086 let b: [f64; 0] = [];
1087 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
1088
1089 unsafe {
1090 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
1091 m,
1092 n,
1093 k,
1094 a.as_ptr(),
1095 0,
1096 Transpose::NoTrans,
1097 b.as_ptr(),
1098 2,
1099 Transpose::NoTrans,
1100 &mut result,
1101 );
1102 }
1103
1104 assert_eq!(result.m, 2);
1106 assert_eq!(result.n, 2);
1107 }
1108
1109 #[test]
1110 fn test_gemm_with_argmax_trans_a() {
1111 let m = 2;
1112 let n = 2;
1113 let k = 3;
1114
1115 let a: [f64; 6] = [1.0, 4.0, 2.0, 5.0, 3.0, 6.0];
1116 let b: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1117
1118 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
1119
1120 unsafe {
1121 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
1122 m,
1123 n,
1124 k,
1125 a.as_ptr(),
1126 2,
1127 Transpose::Trans,
1128 b.as_ptr(),
1129 2,
1130 Transpose::NoTrans,
1131 &mut result,
1132 );
1133 }
1134
1135 assert_eq!(result.get(0, 0).0, 8.0);
1136 assert_eq!(result.get_argmax(0, 0), 2);
1137 }
1138
1139 #[test]
1140 fn test_gemm_with_argmax_trans_b() {
1141 let m = 2;
1142 let n = 2;
1143 let k = 3;
1144
1145 let a: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1146 let b: [f64; 6] = [1.0, 3.0, 5.0, 2.0, 4.0, 6.0];
1147
1148 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
1149
1150 unsafe {
1151 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
1152 m,
1153 n,
1154 k,
1155 a.as_ptr(),
1156 3,
1157 Transpose::NoTrans,
1158 b.as_ptr(),
1159 3,
1160 Transpose::Trans,
1161 &mut result,
1162 );
1163 }
1164
1165 assert_eq!(result.get(0, 0).0, 8.0);
1166 assert_eq!(result.get_argmax(0, 0), 2);
1167 }
1168
1169 #[test]
1170 fn test_gemm_with_argmax_int_zero_cell_canonicalized() {
1171 let m = 2;
1175 let n = 2;
1176 let k = 3;
1177 let neg = <i32 as TropicalScalar>::neg_infinity();
1178 let a: [i32; 6] = [
1179 neg, neg, neg, 1, 2, 3, ];
1182 let b: [i32; 6] = [
1183 4, 5, 6, 7, 8, 9, ];
1187
1188 let mut result: GemmWithArgmax<TropicalMaxPlus<i32>> = GemmWithArgmax::new(m, n);
1189 unsafe {
1190 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<i32>>(
1191 m,
1192 n,
1193 k,
1194 a.as_ptr(),
1195 3,
1196 Transpose::NoTrans,
1197 b.as_ptr(),
1198 2,
1199 Transpose::NoTrans,
1200 &mut result,
1201 );
1202 }
1203
1204 for j in 0..n {
1206 assert!(
1207 result.get(0, j).0.is_drifted_neg_zero(),
1208 "C[0,{j}] should be in tropical-zero territory"
1209 );
1210 assert_eq!(
1211 result.get_argmax(0, j),
1212 0,
1213 "zero-cell argmax must canonicalize to 0, not drift"
1214 );
1215 }
1216 assert_eq!(result.get(1, 0).0, 11);
1219 assert_eq!(result.get_argmax(1, 0), 2);
1220 assert_eq!(result.get(1, 1).0, 12);
1221 assert_eq!(result.get_argmax(1, 1), 2);
1222 }
1223
1224 #[test]
1225 fn test_gemm_with_argmax_float_zero_cell_keeps_seed() {
1226 let m = 2;
1229 let n = 2;
1230 let k = 3;
1231 let a: [f64; 6] = [
1232 f64::NEG_INFINITY,
1233 f64::NEG_INFINITY,
1234 f64::NEG_INFINITY,
1235 1.0,
1236 2.0,
1237 3.0,
1238 ];
1239 let b: [f64; 6] = [4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
1240
1241 let mut result: GemmWithArgmax<TropicalMaxPlus<f64>> = GemmWithArgmax::new(m, n);
1242 unsafe {
1243 tropical_gemm_with_argmax_portable::<TropicalMaxPlus<f64>>(
1244 m,
1245 n,
1246 k,
1247 a.as_ptr(),
1248 3,
1249 Transpose::NoTrans,
1250 b.as_ptr(),
1251 2,
1252 Transpose::NoTrans,
1253 &mut result,
1254 );
1255 }
1256
1257 for j in 0..n {
1258 assert_eq!(result.get(0, j).0, f64::NEG_INFINITY);
1259 assert_eq!(result.get_argmax(0, j), 0);
1260 }
1261 }
1262}