1use std::alloc::Layout;
2use std::fmt::{Debug, Display};
3use std::marker::PhantomData;
4use std::ops::Range;
5use std::sync::Arc;
6use tract_data::internal::*;
7
8use crate::mmm::{
9 EagerPackedInput, MMMInputFormat, MMMInputValue, PackedExoticFact, PackedMatrixStorage,
10};
11
12use crate::WeightType;
13
14#[cfg(feature = "multithread-mm")]
15const PACKING_MIN_CHUNK_BYTES: usize = 32 * 1024;
16
17#[cfg(feature = "multithread-mm")]
19fn packing_has_enough_work(panels: usize, bytes: usize, threads: usize) -> bool {
20 threads > 1 && panels > 1 && bytes / (4 * threads).min(panels) >= PACKING_MIN_CHUNK_BYTES
21}
22
23fn should_parallel_pack(panels: usize, bytes: usize, elements: usize) -> bool {
25 #[cfg(feature = "multithread-mm")]
26 {
27 use crate::multithread::{Executor, current_tract_executor};
28
29 if panels < 2
30 || bytes < 2 * PACKING_MIN_CHUNK_BYTES
31 || elements < crate::multithread::current_threading_element_threshold()
32 {
33 return false;
34 }
35 let threads = match current_tract_executor() {
36 Executor::MultiThread(pool) => pool.current_num_threads(),
37 Executor::RayonGlobal => rayon::current_num_threads(),
38 Executor::SingleThread => 1,
39 };
40 packing_has_enough_work(panels, bytes, threads)
41 }
42 #[cfg(not(feature = "multithread-mm"))]
43 {
44 let _ = (panels, bytes, elements);
45 false
46 }
47}
48
49#[derive(Clone, Eq, PartialEq, Hash)]
50pub struct PackedFormat {
51 pub dt: DatumType,
52 pub r: usize,
53 pub alignment_bytes: usize,
54 pub end_padding_record: usize,
55}
56
57impl MMMInputFormat for PackedFormat {
58 fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor> {
59 let packed = PackedFormat::pack_tensor(self, t, k_axis, mn_axis)?;
60 Ok(PackedMatrixStorage::new(packed).into_tensor(t.datum_type()))
61 }
62 fn prepare_one_view(
63 &self,
64 t: &TensorView,
65 k_axis: usize,
66 mn_axis: usize,
67 ) -> TractResult<Box<dyn MMMInputValue>> {
68 PackedFormat::pack_tensor_view(self, t, k_axis, mn_axis)
69 }
70
71 fn prepare_one(
72 &self,
73 t: &Tensor,
74 k_axis: usize,
75 mn_axis: usize,
76 ) -> TractResult<Box<dyn MMMInputValue>> {
77 PackedFormat::pack_tensor(self, t, k_axis, mn_axis)
78 }
79
80 fn precursor(&self) -> WeightType {
81 WeightType::Plain(self.dt)
82 }
83
84 fn r(&self) -> usize {
85 self.r
86 }
87
88 fn k_alignment(&self) -> usize {
89 1
90 }
91
92 #[allow(clippy::collapsible_if)]
93 fn merge_with<'o, 'a: 'o, 'b: 'o>(
94 &'a self,
95 other: &'b dyn MMMInputFormat,
96 ) -> Option<&'o dyn MMMInputFormat> {
97 if let Some(other) = other.downcast_ref::<PackedFormat>() {
98 if self.r == other.r && self.dt == other.dt {
99 if self.alignment_bytes % other.alignment_bytes == 0
100 && self.end_padding_record >= other.end_padding_record
101 {
102 return Some(self);
103 }
104 if other.alignment_bytes % self.alignment_bytes == 0
105 && other.end_padding_record >= self.end_padding_record
106 {
107 return Some(other);
108 }
109 }
110 }
111 None
112 }
113
114 fn mem_size(&self, k: TDim, mn: TDim) -> TDim {
115 self.len(k, mn) * self.dt.size_of()
116 }
117
118 fn extract_at_mn_f16(
119 &self,
120 data: &EagerPackedInput,
121 mn: usize,
122 slice: &mut [f16],
123 ) -> TractResult<()> {
124 ensure!(data.format().dyn_eq(self));
125 ensure!(self.len(data.k(), data.mn()) * self.dt.size_of() == data.packed.len());
126 unsafe {
127 let ptr = data.packed.as_ptr().add(
128 (self.single_panel_len(data.k()) * (mn / self.r) + mn % self.r) * self.dt.size_of(),
129 );
130 for (i, slot) in slice.iter_mut().enumerate() {
131 let ptr = ptr.add(i * self.dt.size_of() * self.r);
132 *slot = if self.dt == f16::datum_type() {
133 *(ptr as *const f16)
134 } else if self.dt == f32::datum_type() {
135 f16::from_f32(*(ptr as *const f32))
136 } else {
137 bail!("Unexpected DT {:?}", self.dt)
138 }
139 }
140 }
141 Ok(())
142 }
143
144 fn extract_at_mn_f32(
145 &self,
146 data: &EagerPackedInput,
147 mn: usize,
148 slice: &mut [f32],
149 ) -> TractResult<()> {
150 ensure!(data.format().dyn_eq(self));
151 ensure!(self.len(data.k(), data.mn()) * self.dt.size_of() == data.packed.len());
152 unsafe {
153 let ptr = data.packed.as_ptr().add(
154 (self.single_panel_len(data.k()) * (mn / self.r) + mn % self.r) * self.dt.size_of(),
155 );
156 for (i, slot) in slice.iter_mut().enumerate() {
157 let ptr = ptr.add(i * self.dt.size_of() * self.r);
158 *slot = if self.dt == f16::datum_type() {
159 (*(ptr as *const f16)).to_f32()
160 } else if self.dt == f32::datum_type() {
161 *(ptr as *const f32)
162 } else {
163 bail!("Unexpected DT {:?}", self.dt)
164 }
165 }
166 }
167 Ok(())
168 }
169}
170
171impl Display for PackedFormat {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 write!(f, "Packed{:?}[{}]", self.dt, self.r)
174 }
175}
176
177impl Debug for PackedFormat {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 write!(
180 f,
181 "Packed{:?}[{}]@{}+{}",
182 self.dt, self.r, self.alignment_bytes, self.end_padding_record
183 )
184 }
185}
186
187impl PackedFormat {
188 pub const fn new(dt: DatumType, nr: usize, alignment_bytes: usize) -> PackedFormat {
189 PackedFormat { dt, r: nr, alignment_bytes, end_padding_record: 1 }
190 }
191
192 pub const fn with_end_padding_record(self, end_padding_record: usize) -> Self {
193 PackedFormat { end_padding_record, ..self }
194 }
195
196 #[inline]
197 pub fn align(self, alignment: usize) -> Self {
198 Self { alignment_bytes: alignment, ..self }
199 }
200
201 #[inline]
202 pub fn alignment(&self) -> usize {
203 self.alignment_bytes
204 }
205
206 #[inline]
207 pub fn panel_width(&self) -> usize {
208 self.r
209 }
210
211 #[inline]
212 pub fn len<D: DimLike>(&self, k: D, n: D) -> D {
213 n.divceil(self.r) * self.single_panel_len(k)
214 }
215
216 #[inline]
217 pub fn single_panel_len<D: DimLike>(&self, k: D) -> D {
218 ((k + self.end_padding_record) * self.r).divceil(self.alignment()) * self.alignment()
219 }
220
221 #[inline]
222 pub fn single_panel_layout(&self, k: usize, item_size: usize) -> Layout {
223 Layout::from_size_align(self.single_panel_len(k) * item_size, self.alignment()).unwrap()
224 }
225
226 pub fn pack_tensor(
227 &self,
228 t: &Tensor,
229 k_axis: usize,
230 mn_axis: usize,
231 ) -> TractResult<Box<dyn MMMInputValue>> {
232 ensure!(t.datum_type().is_copy());
233 self.pack_tensor_view(&t.view(), k_axis, mn_axis)
234 }
235
236 pub fn pack_tensor_view(
237 &self,
238 t: &TensorView,
239 k_axis: usize,
240 mn_axis: usize,
241 ) -> TractResult<Box<dyn MMMInputValue>> {
242 ensure!(k_axis != mn_axis, "k_axis and mn_axis must differ (both are {k_axis})");
243 ensure!(
244 t.datum_type().unquantized() == self.dt.unquantized(),
245 "Attempting to pack for {self} tensor view {t:?}"
246 );
247 let k = t.shape()[k_axis];
248 let mn = t.shape()[mn_axis];
249 let packed_len = self.len(k, mn);
250 let panel_len = self.single_panel_len(k);
251 let panel_bytes = panel_len * t.datum_type().size_of();
252 let strides = t.strides();
253 unsafe {
254 let mut packed = Blob::new_for_size_and_align(
255 t.datum_type().size_of() * packed_len,
256 self.alignment_bytes,
257 );
258 if cfg!(debug_assertions) {
259 packed.as_bytes_mut().fill(0u8);
260 } else if mn % self.r != 0 {
261 packed.as_bytes_mut()[(mn / self.r) * panel_bytes..].fill(0u8);
265 }
266 let (k_stride, mn_stride) = (strides[k_axis], strides[mn_axis]);
267 if panel_bytes > 0
268 && (mn_stride == 1 || k_stride == 1)
269 && should_parallel_pack(
270 mn.divceil(self.r),
271 k * mn * t.datum_type().size_of(),
272 packed_len,
273 )
274 {
275 let src = t.as_ptr_unchecked::<u8>() as usize;
278 let dt = t.datum_type();
279 crate::multithread::par_chunks_mut(
280 packed.as_bytes_mut(),
281 panel_bytes,
282 packed_len,
283 |first, chunk| {
284 let panels = chunk.len() / panel_bytes;
285 let mn_range = first * self.r..((first + panels) * self.r);
286 dispatch_copy!(Self::pack_t(dt)(
287 self,
288 chunk.as_mut_ptr() as _,
289 src as *const _,
290 mn,
291 k_stride,
292 mn_stride,
293 0..k,
294 mn_range
295 ));
296 Ok(())
297 },
298 )?;
299 } else {
300 dispatch_copy!(Self::pack_t(t.datum_type())(
301 self,
302 packed.as_mut_ptr() as _,
303 t.as_ptr_unchecked(),
304 mn,
305 k_stride,
306 mn_stride,
307 0..k,
308 0..mn
309 ));
310 }
311 Ok(Box::new(EagerPackedInput {
312 fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k },
313 packed: packed.into(),
314 panel_bytes,
315 mn,
316 }))
317 }
318 }
319
320 pub fn new_packed_buffer(&self, k: usize, mn: usize) -> TractResult<EagerPackedInput> {
327 ensure!(self.dt.is_copy());
328 let packed_len = self.len(k, mn);
329 let packed = unsafe {
330 let mut blob =
331 Blob::new_for_size_and_align(self.dt.size_of() * packed_len, self.alignment_bytes);
332 blob.as_bytes_mut().fill(0u8);
333 blob
334 };
335 Ok(EagerPackedInput {
336 fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k },
337 packed: packed.into(),
338 panel_bytes: self.single_panel_len(k) * self.dt.size_of(),
339 mn,
340 })
341 }
342
343 pub fn repack_tensor_view(
349 &self,
350 dst: &mut EagerPackedInput,
351 t: &TensorView,
352 k_axis: usize,
353 mn_axis: usize,
354 ) -> TractResult<()> {
355 ensure!(
356 t.datum_type().unquantized() == self.dt.unquantized(),
357 "Attempting to pack for {self} tensor view {t:?}"
358 );
359 let k = t.shape()[k_axis];
360 let mn = t.shape()[mn_axis];
361 ensure!(
362 dst.fact.k == k && dst.mn == mn && dst.fact.format.dyn_eq(self),
363 "Packed buffer is {:?} for k={} mn={}, repacking a k={k} mn={mn} view",
364 dst.fact.format,
365 dst.fact.k,
366 dst.mn
367 );
368 let strides = t.strides();
369 let packed = Arc::get_mut(&mut dst.packed).context("Packed buffer is shared")?;
370 unsafe {
371 dispatch_copy!(Self::pack_t(t.datum_type())(
372 self,
373 packed.as_mut_ptr() as _,
374 t.as_ptr_unchecked(),
375 mn,
376 strides[k_axis],
377 strides[mn_axis],
378 0..k,
379 0..mn
380 ));
381 }
382 Ok(())
383 }
384
385 pub unsafe fn pack<'a, 'b>(
386 &self,
387 pb: impl std::borrow::BorrowMut<TensorView<'a>>,
388 b: impl std::borrow::Borrow<TensorView<'b>>,
389 k_axis: usize,
390 mn_axis: usize,
391 ) {
392 let k = b.borrow().shape()[k_axis];
393 let mn = b.borrow().shape()[mn_axis];
394 unsafe { self.pack_segment(pb, b, k_axis, mn_axis, 0..k, 0..mn) };
395 }
396
397
398 #[allow(clippy::too_many_arguments)]
399 #[rustfmt::skip]
400 pub unsafe fn pack_t<T: Datum + Copy>(
401 &self,
402 pb: *mut T,
403 b: *const T,
404 mn: usize,
405 k_stride: isize,
406 mn_stride: isize,
407 k_range: Range<usize>,
408 mn_range: Range<usize>,
409 ) { unsafe {
410 if k_range.len() == 0 || mn_range.len() == 0 {
411 return
412 }
413 if self.r == 1 && k_stride == 1 && mn == 1 {
414 pb.copy_from_nonoverlapping(b.add(k_range.start), k_range.len())
415 } else if mn_stride == 1 {
416 let size_of = T::datum_type().size_of();
417 let rbytes = self.r * size_of;
418 let mn_valid_end = mn_range.end.min(mn);
419 let mn_range_bytes = mn_range.start * size_of..mn_valid_end * size_of;
420 let k_stride_bytes = k_stride * size_of as isize;
421 let bb = b as *const u8;
422 let pbb = pb as *mut u8;
423 let panel_len = self.single_panel_len(k_range.len()) * size_of;
424 match rbytes {
425 16 => pack_mn_major::<[u8; 16]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
426 24 => pack_mn_major::<[u8; 24]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
427 32 => pack_mn_major::<[u8; 32]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
428 48 => pack_mn_major::<[u8; 48]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
429 64 => pack_mn_major::<[u8; 64]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
430 96 => pack_mn_major::<[u8; 96]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
431 128 => pack_mn_major::<[u8; 128]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),
432 _ => {
433 let mut packer = self.write_with_k_outer(pb, k_range.len(), mn_range.len());
434 for k in k_range {
435 for x in mn_range.start..mn_valid_end {
436 packer.write(*b.offset(x as isize + k_stride * k as isize))
437 }
438 for _x in mn_valid_end..mn_range.end {
439 packer.write(T::default())
440 }
441 }
442 }
443 }
444 } else if k_stride == 1 {
445 let mn_valid_end = mn_range.end.min(mn);
447 if mn_valid_end > mn_range.start {
448 pack_k_major(
449 b.offset(mn_range.start as isize * mn_stride + k_range.start as isize),
450 pb,
451 self.single_panel_len(k_range.len()),
452 self.r,
453 mn_stride,
454 k_range.len(),
455 mn_valid_end - mn_range.start,
456 )
457 }
458 } else {
459 let mut packer = self.write_with_k_outer(pb, k_range.len(), mn);
460 let mn_valid_end = mn_range.end.min(mn);
461 for k in k_range {
462 for x in mn_range.start..mn_valid_end {
463 packer.write(*b.offset(x as isize * mn_stride + k_stride * k as isize))
464 }
465 for _x in mn_valid_end..mn_range.end {
466 packer.write(T::default())
467 }
468 }
469 }
470 }}
471
472 #[inline]
473 pub unsafe fn pack_segment<'a, 'b>(
474 &self,
475 mut pb: impl std::borrow::BorrowMut<TensorView<'a>>,
476 b: impl std::borrow::Borrow<TensorView<'b>>,
477 k_axis: usize,
478 mn_axis: usize,
479 k_range: Range<usize>,
480 mn_range: Range<usize>,
481 ) {
482 debug_assert!(pb.borrow().len() >= self.len(k_range.len(), mn_range.len()));
483 let pb = pb.borrow_mut();
484 let b = b.borrow();
485 let dt = pb.datum_type();
486 unsafe {
487 dispatch_copy!(Self::pack_t(dt)(
488 self,
489 pb.as_ptr_mut_unchecked(),
490 b.as_ptr_unchecked(),
491 b.shape()[mn_axis],
492 b.strides()[k_axis],
493 b.strides()[mn_axis],
494 k_range,
495 mn_range
496 ));
497 }
498 }
499
500 pub fn write_with_k_outer<'p, T: Copy + Debug>(
501 &self,
502 pb: *mut T,
503 k: usize,
504 mn: usize,
505 ) -> KOutWriter<'p, T> {
506 KOutWriter::new(pb, self.r, self.single_panel_len(k), mn, k)
507 }
508
509 pub fn write_single_panel_with_k_outer<'p, T: Copy + Debug>(
510 &self,
511 pb: *mut T,
512 ) -> KOutSinglePanelWriter<'p, T> {
513 KOutSinglePanelWriter::new(pb)
514 }
515
516 pub fn write_with_k_inner<'p, T: Copy + Debug>(
517 &self,
518 pb: *mut T,
519 k: usize,
520 mn: usize,
521 ) -> KInWriter<'p, T> {
522 let panel_len = self.single_panel_len(k);
523 KInWriter::new(pb, panel_len, self.r, mn, k)
524 }
525}
526
527pub trait PackingWriter<T: Copy> {
528 fn write(&mut self, t: T);
529
530 #[inline]
537 fn write_slice(&mut self, ts: &[T]) {
538 for t in ts {
539 self.write(*t);
540 }
541 }
542}
543
544#[derive(Debug)]
545pub struct KOutSinglePanelWriter<'p, T>
546where
547 T: Copy + std::fmt::Debug,
548{
549 ptr: *mut T,
550 _phantom: PhantomData<&'p T>,
551}
552
553impl<'p, T> KOutSinglePanelWriter<'p, T>
554where
555 T: Copy + std::fmt::Debug,
556{
557 pub fn new(ptr: *mut T) -> KOutSinglePanelWriter<'p, T> {
558 KOutSinglePanelWriter { ptr, _phantom: PhantomData }
559 }
560}
561
562impl<T> PackingWriter<T> for KOutSinglePanelWriter<'_, T>
563where
564 T: Copy + std::fmt::Debug,
565{
566 #[inline(always)]
567 fn write(&mut self, t: T) {
568 unsafe {
569 *self.ptr = t;
570 self.ptr = self.ptr.offset(1);
571 }
572 }
573
574 #[inline]
575 fn write_slice(&mut self, ts: &[T]) {
576 unsafe {
580 std::ptr::copy_nonoverlapping(ts.as_ptr(), self.ptr, ts.len());
581 self.ptr = self.ptr.add(ts.len());
582 }
583 }
584}
585
586#[derive(Debug)]
587pub struct KOutWriter<'p, T>
588where
589 T: Copy + std::fmt::Debug,
590{
591 ptr: *mut T,
592 panels: usize,
593 panel_width: usize,
594 last_panel_width: usize,
595 remain: usize,
596 current_panel: usize,
597 next_panel: isize,
598 next_lane: isize,
599 _phantom: PhantomData<&'p T>,
600}
601
602impl<'p, T> KOutWriter<'p, T>
603where
604 T: Copy + std::fmt::Debug,
605{
606 pub fn new(
607 ptr: *mut T,
608 panel_width: usize,
609 panel_len: usize,
610 mn: usize,
611 _k: usize,
612 ) -> KOutWriter<'p, T> {
613 let panels = mn.divceil(panel_width);
614 let last_panel_width = mn - (panels - 1) * panel_width;
615 KOutWriter {
616 ptr,
617 panels,
618 panel_width,
619 last_panel_width,
620 remain: if panels > 1 { panel_width } else { last_panel_width },
621 current_panel: 0,
622 next_panel: (panel_len - panel_width) as isize,
623 next_lane: (panel_width - last_panel_width) as isize
624 - (panel_len * (panels - 1)) as isize,
625 _phantom: PhantomData,
626 }
627 }
628}
629
630impl<T> PackingWriter<T> for KOutWriter<'_, T>
631where
632 T: Copy + std::fmt::Debug,
633{
634 #[inline(always)]
635 fn write(&mut self, t: T) {
636 unsafe {
637 *self.ptr = t;
638 self.remain -= 1;
639 self.ptr = self.ptr.offset(1);
640 if self.remain == 0 {
641 self.current_panel += 1;
642 if self.current_panel == self.panels {
643 self.ptr = self.ptr.offset(self.next_lane);
644 self.current_panel = 0;
645 } else {
646 self.ptr = self.ptr.offset(self.next_panel);
647 }
648 if self.current_panel == self.panels - 1 {
649 self.remain = self.last_panel_width;
650 } else {
651 self.remain = self.panel_width;
652 }
653 }
654 }
655 }
656
657 #[inline]
658 fn write_slice(&mut self, ts: &[T]) {
659 let n = ts.len();
667 if n == 0 {
668 return;
669 }
670 if n < self.remain {
671 unsafe {
673 std::ptr::copy_nonoverlapping(ts.as_ptr(), self.ptr, n);
674 self.ptr = self.ptr.add(n);
675 }
676 self.remain -= n;
677 } else if n == self.remain {
678 unsafe {
684 std::ptr::copy_nonoverlapping(ts.as_ptr(), self.ptr, n);
685 self.ptr = self.ptr.add(n);
686 self.current_panel += 1;
687 if self.current_panel == self.panels {
688 self.ptr = self.ptr.offset(self.next_lane);
689 self.current_panel = 0;
690 } else {
691 self.ptr = self.ptr.offset(self.next_panel);
692 }
693 if self.current_panel == self.panels - 1 {
694 self.remain = self.last_panel_width;
695 } else {
696 self.remain = self.panel_width;
697 }
698 }
699 } else {
700 for t in ts {
703 self.write(*t);
704 }
705 }
706 }
707}
708
709#[derive(Debug)]
710pub struct KInWriter<'p, T>
711where
712 T: Copy + Debug,
713{
714 ptr: *mut T,
715 k: usize,
716 panels: usize,
717 panel_width: usize,
718 last_panel_width: usize,
719 remain_on_k: usize,
720 remain_on_mn: usize,
721 current_panel: usize,
722 next_mn_offset: isize,
723 next_panel_offset: isize,
724 _phantom: PhantomData<&'p T>,
725}
726
727impl<'p, T> KInWriter<'p, T>
728where
729 T: Copy + Debug,
730{
731 pub fn new(
732 ptr: *mut T,
733 panel_len: usize,
734 panel_width: usize,
735 mn: usize,
736 k: usize,
737 ) -> KInWriter<'p, T> {
738 assert!(panel_width > 0, "panel_width must be non-zero");
739 assert!(
740 panel_len > 0 || (k == 0 && mn == 0),
741 "panel_len must be non-zero when k or mn is non-zero"
742 );
743 assert!(k.checked_mul(panel_width).is_some(), "k * panel_width overflows");
744 let panels = mn.divceil(panel_width);
745 let last_panel_width = mn - (panels - 1) * panel_width;
746 KInWriter {
747 ptr,
748 k,
749 panels,
750 panel_width,
751 last_panel_width,
752 remain_on_k: k,
753 remain_on_mn: if panels == 1 { last_panel_width } else { panel_width },
754 current_panel: 0,
755 next_mn_offset: 1 - (k * panel_width) as isize,
756 next_panel_offset: panel_len as isize - (k * panel_width + panel_width - 1) as isize,
757 _phantom: PhantomData,
759 }
760 }
761}
762
763impl<T> PackingWriter<T> for KInWriter<'_, T>
764where
765 T: Copy + std::fmt::Debug,
766{
767 #[inline(always)]
768 fn write(&mut self, t: T) {
769 unsafe {
770 *self.ptr = t;
771 self.remain_on_k -= 1;
772 self.ptr = self.ptr.add(self.panel_width);
773 if self.remain_on_k == 0 {
774 self.remain_on_k = self.k;
775 self.remain_on_mn -= 1;
776 if self.remain_on_mn > 0 {
777 self.ptr = self.ptr.offset(self.next_mn_offset);
778 } else {
779 self.ptr = self.ptr.offset(self.next_panel_offset);
780 self.current_panel += 1;
781 if self.current_panel == self.panels - 1 {
782 self.remain_on_mn = self.last_panel_width;
783 } else {
784 self.remain_on_mn = self.panel_width;
785 }
786 }
787 }
788 }
789 }
790}
791
792#[inline(never)]
793unsafe fn pack_mn_major<Chunk: Copy>(
794 b: *const u8,
795 packed: *mut u8,
796 panel_len: usize,
797 k_stride_bytes: isize,
798 mn_range_bytes: Range<usize>,
799 k_range: Range<usize>,
800) {
801 unsafe {
802 let mnr = std::mem::size_of::<Chunk>();
803 let full_panes = mn_range_bytes.len() / mnr;
804 let partial_pane = mn_range_bytes.len() % mnr;
805 for k in 0..k_range.len() {
806 let mut p_row = packed.add(k * mnr);
807 let mut b_row = b.offset(
808 (k_range.start + k) as isize * k_stride_bytes + mn_range_bytes.start as isize,
809 );
810 for _ in 0..full_panes {
811 p_row.copy_from_nonoverlapping(b_row, mnr);
812 p_row = p_row.add(panel_len);
813 b_row = b_row.add(mnr);
814 }
815 if partial_pane > 0 {
816 p_row.copy_from_nonoverlapping(b_row, partial_pane);
817 }
818 }
819 }
820}
821
822const ARMV7_TILE_MIN_ELEMS: usize = 4096;
828
829#[cfg(target_arch = "arm")]
832#[inline]
833fn armv7_has_neon() -> bool {
834 crate::arm32::has_neon()
835}
836
837#[cfg(not(target_arch = "arm"))]
838#[inline]
839fn armv7_has_neon() -> bool {
840 false
841}
842
843#[inline(never)]
852unsafe fn pack_k_major<T: Copy>(
853 b: *const T,
854 packed: *mut T,
855 panel_len: usize,
856 r: usize,
857 mn_stride: isize,
858 k_len: usize,
859 mn_len: usize,
860) {
861 unsafe {
862 let tile = if cfg!(target_arch = "arm") {
869 armv7_has_neon()
870 && matches!(std::mem::size_of::<T>(), 2 | 4)
871 && k_len * mn_len >= ARMV7_TILE_MIN_ELEMS
872 } else {
873 true
874 };
875 for panel in 0..mn_len.divceil(r) {
876 let panel_mn = panel * r;
877 let panel_width = r.min(mn_len - panel_mn);
878 let src = b.offset(panel_mn as isize * mn_stride);
879 let dst = packed.add(panel * panel_len);
880 let tiled_mn = if tile { panel_width / 4 * 4 } else { 0 };
881 let tiled_k = k_len / 4 * 4;
882 for k in (0..tiled_k).step_by(4) {
883 for x in (0..tiled_mn).step_by(4) {
884 transpose_4x4(
885 src.offset(x as isize * mn_stride + k as isize),
886 mn_stride,
887 dst.add(k * r + x),
888 r,
889 );
890 }
891 }
892 for k in tiled_k..k_len {
893 for x in 0..tiled_mn {
894 *dst.add(k * r + x) = *src.offset(x as isize * mn_stride + k as isize);
895 }
896 }
897 for x in tiled_mn..panel_width {
898 let row = src.offset(x as isize * mn_stride);
899 for k in 0..k_len {
900 *dst.add(k * r + x) = *row.add(k);
901 }
902 }
903 }
904 }
905}
906
907#[inline(always)]
912unsafe fn transpose_4x4<T: Copy>(src: *const T, src_stride: isize, dst: *mut T, dst_stride: usize) {
913 unsafe {
914 #[cfg(target_arch = "aarch64")]
917 if std::mem::size_of::<T>() == 4 && std::mem::align_of::<T>() == 4 {
918 transpose_4x4_neon_32(src as _, src_stride, dst as _, dst_stride);
919 return;
920 }
921 #[cfg(target_arch = "aarch64")]
922 if std::mem::size_of::<T>() == 2 && std::mem::align_of::<T>() == 2 {
923 transpose_4x4_neon_16(src as _, src_stride, dst as _, dst_stride);
924 return;
925 }
926 #[cfg(target_arch = "arm")]
931 if std::mem::size_of::<T>() == 4 && std::mem::align_of::<T>() == 4 {
932 transpose_4x4_neon_armv7_32(src as _, src_stride, dst as _, dst_stride);
933 return;
934 }
935 #[cfg(target_arch = "arm")]
936 if std::mem::size_of::<T>() == 2 && std::mem::align_of::<T>() == 2 {
937 transpose_4x4_neon_armv7_16(src as _, src_stride, dst as _, dst_stride);
938 return;
939 }
940 let tile: [[T; 4]; 4] = std::array::from_fn(|i| {
941 let row = src.offset(i as isize * src_stride);
942 std::array::from_fn(|j| *row.add(j))
943 });
944 for j in 0..4 {
945 let out = dst.add(j * dst_stride);
946 for (i, row) in tile.iter().enumerate() {
947 *out.add(i) = row[j];
948 }
949 }
950 }
951}
952
953#[cfg(target_arch = "aarch64")]
955#[target_feature(enable = "neon")]
956unsafe fn transpose_4x4_neon_32(
957 src: *const u32,
958 src_stride: isize,
959 dst: *mut u32,
960 dst_stride: usize,
961) {
962 use std::arch::aarch64::*;
963 unsafe {
964 let a = vld1q_u32(src);
965 let b = vld1q_u32(src.offset(src_stride));
966 let c = vld1q_u32(src.offset(2 * src_stride));
967 let d = vld1q_u32(src.offset(3 * src_stride));
968 let ab_even = vreinterpretq_u64_u32(vtrn1q_u32(a, b));
969 let ab_odd = vreinterpretq_u64_u32(vtrn2q_u32(a, b));
970 let cd_even = vreinterpretq_u64_u32(vtrn1q_u32(c, d));
971 let cd_odd = vreinterpretq_u64_u32(vtrn2q_u32(c, d));
972 vst1q_u32(dst, vreinterpretq_u32_u64(vtrn1q_u64(ab_even, cd_even)));
973 vst1q_u32(dst.add(dst_stride), vreinterpretq_u32_u64(vtrn1q_u64(ab_odd, cd_odd)));
974 vst1q_u32(dst.add(2 * dst_stride), vreinterpretq_u32_u64(vtrn2q_u64(ab_even, cd_even)));
975 vst1q_u32(dst.add(3 * dst_stride), vreinterpretq_u32_u64(vtrn2q_u64(ab_odd, cd_odd)));
976 }
977}
978
979#[cfg(target_arch = "aarch64")]
981#[target_feature(enable = "neon")]
982unsafe fn transpose_4x4_neon_16(
983 src: *const u16,
984 src_stride: isize,
985 dst: *mut u16,
986 dst_stride: usize,
987) {
988 use std::arch::aarch64::*;
989 unsafe {
990 let a = vld1_u16(src);
991 let b = vld1_u16(src.offset(src_stride));
992 let c = vld1_u16(src.offset(2 * src_stride));
993 let d = vld1_u16(src.offset(3 * src_stride));
994 let ab_even = vreinterpret_u32_u16(vtrn1_u16(a, b));
995 let ab_odd = vreinterpret_u32_u16(vtrn2_u16(a, b));
996 let cd_even = vreinterpret_u32_u16(vtrn1_u16(c, d));
997 let cd_odd = vreinterpret_u32_u16(vtrn2_u16(c, d));
998 vst1_u16(dst, vreinterpret_u16_u32(vtrn1_u32(ab_even, cd_even)));
999 vst1_u16(dst.add(dst_stride), vreinterpret_u16_u32(vtrn1_u32(ab_odd, cd_odd)));
1000 vst1_u16(dst.add(2 * dst_stride), vreinterpret_u16_u32(vtrn2_u32(ab_even, cd_even)));
1001 vst1_u16(dst.add(3 * dst_stride), vreinterpret_u16_u32(vtrn2_u32(ab_odd, cd_odd)));
1002 }
1003}
1004
1005#[cfg(target_arch = "arm")]
1015#[inline(always)]
1016unsafe fn transpose_4x4_neon_armv7_32(
1017 src: *const u32,
1018 src_stride: isize,
1019 dst: *mut u32,
1020 dst_stride: usize,
1021) {
1022 use std::arch::asm;
1023 let ss = src_stride * 4;
1024 let ds = (dst_stride * 4) as isize;
1025 let src = src as *const u8;
1026 let dst = dst as *mut u8;
1027 unsafe {
1028 asm!(
1029 ".fpu neon",
1030 "vld1.32 {{d0, d1}}, [{s0}]",
1031 "vld1.32 {{d2, d3}}, [{s1}]",
1032 "vld1.32 {{d4, d5}}, [{s2}]",
1033 "vld1.32 {{d6, d7}}, [{s3}]",
1034 "vtrn.32 q0, q1",
1035 "vtrn.32 q2, q3",
1036 "vswp d1, d4",
1037 "vswp d3, d6",
1038 "vst1.32 {{d0, d1}}, [{o0}]",
1039 "vst1.32 {{d2, d3}}, [{o1}]",
1040 "vst1.32 {{d4, d5}}, [{o2}]",
1041 "vst1.32 {{d6, d7}}, [{o3}]",
1042 s0 = in(reg) src,
1043 s1 = in(reg) src.offset(ss),
1044 s2 = in(reg) src.offset(2 * ss),
1045 s3 = in(reg) src.offset(3 * ss),
1046 o0 = in(reg) dst,
1047 o1 = in(reg) dst.offset(ds),
1048 o2 = in(reg) dst.offset(2 * ds),
1049 o3 = in(reg) dst.offset(3 * ds),
1050 out("q0") _,
1051 out("q1") _,
1052 out("q2") _,
1053 out("q3") _,
1054 options(nostack),
1055 );
1056 }
1057}
1058
1059#[cfg(target_arch = "arm")]
1063#[inline(always)]
1064unsafe fn transpose_4x4_neon_armv7_16(
1065 src: *const u16,
1066 src_stride: isize,
1067 dst: *mut u16,
1068 dst_stride: usize,
1069) {
1070 use std::arch::asm;
1071 let ss = src_stride * 2;
1072 let ds = (dst_stride * 2) as isize;
1073 let src = src as *const u8;
1074 let dst = dst as *mut u8;
1075 unsafe {
1076 asm!(
1077 ".fpu neon",
1078 "vld1.16 {{d0}}, [{s0}]",
1079 "vld1.16 {{d1}}, [{s1}]",
1080 "vld1.16 {{d2}}, [{s2}]",
1081 "vld1.16 {{d3}}, [{s3}]",
1082 "vtrn.16 d0, d1",
1083 "vtrn.16 d2, d3",
1084 "vtrn.32 d0, d2",
1085 "vtrn.32 d1, d3",
1086 "vst1.16 {{d0}}, [{o0}]",
1087 "vst1.16 {{d1}}, [{o1}]",
1088 "vst1.16 {{d2}}, [{o2}]",
1089 "vst1.16 {{d3}}, [{o3}]",
1090 s0 = in(reg) src,
1091 s1 = in(reg) src.offset(ss),
1092 s2 = in(reg) src.offset(2 * ss),
1093 s3 = in(reg) src.offset(3 * ss),
1094 o0 = in(reg) dst,
1095 o1 = in(reg) dst.offset(ds),
1096 o2 = in(reg) dst.offset(2 * ds),
1097 o3 = in(reg) dst.offset(3 * ds),
1098 out("d0") _,
1099 out("d1") _,
1100 out("d2") _,
1101 out("d3") _,
1102 options(nostack),
1103 );
1104 }
1105}
1106
1107#[derive(Debug)]
1112pub struct KOut4Writer<'p, T>
1113where
1114 T: Copy + std::fmt::Debug,
1115{
1116 base: *mut T,
1117 r4: usize, panel_len: usize, panels: usize,
1120 panel_width: usize,
1121 last_panel_width: usize,
1122 kb: usize, kr: usize, panel: usize,
1125 local_mn: usize,
1126 _phantom: PhantomData<&'p T>,
1127}
1128
1129impl<'p, T> KOut4Writer<'p, T>
1130where
1131 T: Copy + std::fmt::Debug,
1132{
1133 pub fn new(base: *mut T, r: usize, panel_len: usize, mn: usize) -> KOut4Writer<'p, T> {
1134 let panels = mn.divceil(r).max(1);
1135 let last_panel_width = mn - (panels - 1) * r;
1136 KOut4Writer {
1137 base,
1138 r4: r * 4,
1139 panel_len,
1140 panels,
1141 panel_width: r,
1142 last_panel_width,
1143 kb: 0,
1144 kr: 0,
1145 panel: 0,
1146 local_mn: 0,
1147 _phantom: PhantomData,
1148 }
1149 }
1150 #[inline(always)]
1151 fn panel_width(&self) -> usize {
1152 if self.panel == self.panels - 1 { self.last_panel_width } else { self.panel_width }
1153 }
1154 #[inline(always)]
1155 fn advance(&mut self, by: usize) {
1156 self.local_mn += by;
1157 if self.local_mn >= self.panel_width() {
1158 self.local_mn = 0;
1159 self.panel += 1;
1160 if self.panel == self.panels {
1161 self.panel = 0;
1162 self.kr += 1;
1163 if self.kr == 4 {
1164 self.kr = 0;
1165 self.kb += 1;
1166 }
1167 }
1168 }
1169 }
1170}
1171
1172impl<T> PackingWriter<T> for KOut4Writer<'_, T>
1173where
1174 T: Copy + std::fmt::Debug,
1175{
1176 #[inline(always)]
1177 fn write(&mut self, t: T) {
1178 unsafe {
1179 let off = self.panel * self.panel_len + self.kb * self.r4 + self.local_mn * 4 + self.kr;
1180 *self.base.add(off) = t;
1181 }
1182 self.advance(1);
1183 }
1184
1185 #[inline]
1186 fn write_slice(&mut self, ts: &[T]) {
1187 let n = ts.len();
1188 if n == 0 {
1189 return;
1190 }
1191 let pw = self.panel_width();
1192 if self.local_mn + n <= pw {
1193 unsafe {
1195 let mut d = self.base.add(
1196 self.panel * self.panel_len + self.kb * self.r4 + self.local_mn * 4 + self.kr,
1197 );
1198 for &t in ts {
1199 *d = t;
1200 d = d.add(4);
1201 }
1202 }
1203 self.advance(n);
1204 } else {
1205 for &t in ts {
1206 self.write(t);
1207 }
1208 }
1209 }
1210}
1211
1212#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1216pub struct PackedI8K4 {
1217 pub r: usize,
1218 pub align: usize,
1219}
1220impl PackedI8K4 {
1221 pub fn new(r: usize) -> Self {
1222 PackedI8K4 { r, align: 16 }
1223 }
1224 fn panel(&self, k: usize) -> usize {
1225 (k.div_ceil(4) * 4) * self.r
1226 }
1227 pub fn single_panel_len(&self, k: usize) -> usize {
1228 self.panel(k)
1229 }
1230 pub fn len(&self, k: usize, mn: usize) -> usize {
1231 mn.divceil(self.r) * self.panel(k)
1232 }
1233 pub fn alignment(&self) -> usize {
1234 self.align
1235 }
1236 pub fn write_with_k_outer<'p, T: Copy + std::fmt::Debug>(
1238 &self,
1239 pb: *mut T,
1240 k: usize,
1241 mn: usize,
1242 ) -> KOut4Writer<'p, T> {
1243 KOut4Writer::new(pb, self.r, self.panel(k), mn)
1244 }
1245 pub fn pack_view(
1247 &self,
1248 t: &TensorView,
1249 k_axis: usize,
1250 mn_axis: usize,
1251 ) -> TractResult<Box<dyn MMMInputValue>> {
1252 ensure!(k_axis != mn_axis, "k_axis and mn_axis must differ (both are {k_axis})");
1253 let k = t.shape()[k_axis];
1254 let mn = t.shape()[mn_axis];
1255 let kp = k.div_ceil(4) * 4;
1256 let pl = kp * self.r;
1257 let panels = mn.div_ceil(self.r);
1258 let st = t.strides();
1259 let mut blob = unsafe { Blob::new_for_size_and_align(panels * pl, self.align) };
1260 blob.as_bytes_mut().fill(0);
1261 let (ks, ms) = (st[k_axis], st[mn_axis]);
1262 let kblocks = kp / 4;
1263 unsafe {
1264 let src = t.as_ptr_unchecked::<i8>();
1265 let dst = blob.as_mut_ptr() as *mut i8;
1266 for p in 0..panels {
1267 let pw = self.r.min(mn - p * self.r);
1268 let panel = dst.add(p * pl);
1269 let mn0 = (p * self.r) as isize;
1270 for kb in 0..kblocks {
1271 for kr in 0..4 {
1272 let kk = kb * 4 + kr;
1273 if kk >= k {
1274 break;
1275 }
1276 let srow = src.offset(kk as isize * ks + mn0 * ms);
1277 let dcol = panel.add(kb * self.r * 4 + kr);
1278 for lm in 0..pw {
1279 *dcol.add(lm * 4) = *srow.offset(lm as isize * ms);
1280 }
1281 }
1282 }
1283 }
1284 }
1285 Ok(Box::new(EagerPackedInput {
1286 fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k },
1287 packed: blob.into(),
1288 panel_bytes: pl,
1289 mn,
1290 }))
1291 }
1292}
1293impl std::fmt::Display for PackedI8K4 {
1294 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1295 write!(f, "I8K4[{}]", self.r)
1296 }
1297}
1298impl MMMInputFormat for PackedI8K4 {
1299 fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor> {
1300 Ok(PackedMatrixStorage::new(self.prepare_one(t, k_axis, mn_axis)?)
1301 .into_tensor(t.datum_type()))
1302 }
1303 fn prepare_one_view(
1304 &self,
1305 t: &TensorView,
1306 k_axis: usize,
1307 mn_axis: usize,
1308 ) -> TractResult<Box<dyn MMMInputValue>> {
1309 self.pack_view(t, k_axis, mn_axis)
1310 }
1311 fn precursor(&self) -> WeightType {
1312 WeightType::Plain(i8::datum_type())
1313 }
1314 fn r(&self) -> usize {
1315 self.r
1316 }
1317 fn k_alignment(&self) -> usize {
1318 4
1319 }
1320 fn merge_with<'o, 'a: 'o, 'b: 'o>(
1321 &'a self,
1322 o: &'b dyn MMMInputFormat,
1323 ) -> Option<&'o dyn MMMInputFormat> {
1324 o.downcast_ref::<PackedI8K4>().filter(|x| x.r == self.r).map(|_| self as _)
1325 }
1326 fn mem_size(&self, k: TDim, mn: TDim) -> TDim {
1327 mn.divceil(self.r) * self.panel(k.to_usize().unwrap_or(0))
1328 }
1329 fn extract_at_mn_f16(&self, _: &EagerPackedInput, _: usize, _: &mut [f16]) -> TractResult<()> {
1330 bail!("no f16 extract")
1331 }
1332 fn extract_at_mn_f32(&self, _: &EagerPackedInput, _: usize, _: &mut [f32]) -> TractResult<()> {
1333 bail!("no f32 extract")
1334 }
1335}
1336
1337pub trait Packing {
1338 fn packing(r: usize) -> PackedFormat;
1339}
1340
1341impl<D: Datum> Packing for D {
1342 fn packing(r: usize) -> PackedFormat {
1343 PackedFormat::new(Self::datum_type(), r, vector_size())
1344 }
1345}
1346
1347#[cfg(test)]
1348mod test {
1349 use std::ops::Range;
1350
1351 use proptest::prelude::*;
1352 use tract_data::internal::num_integer::Integer;
1353 use tract_data::internal::tract_ndarray::Zip;
1354 use tract_data::internal::*;
1355 use tract_ndarray::prelude::*;
1356
1357 #[derive(Debug)]
1358 struct PackProblem {
1359 k: usize,
1360 mn: usize,
1361 is_a: bool,
1362 r: usize,
1363 k_range: Range<usize>,
1364 mn_range: Range<usize>,
1365 align_panel: usize,
1366 }
1367
1368 impl PackProblem {
1369 fn input(&self) -> Array2<u32> {
1370 let shape = if self.is_a { (self.mn, self.k) } else { (self.k, self.mn) };
1371 let data = (0..(self.k * self.mn) as u32).collect();
1372 Array2::from_shape_vec(shape, data).unwrap()
1373 }
1374
1375 fn packer(&self) -> Array2<u32> {
1376 let panels = self.mn_range.len().divceil(self.r);
1377 let packer = super::PackedFormat::new(u32::datum_type(), self.r, self.align_panel)
1378 .with_end_padding_record(0);
1379 let input = self.input().into_tensor();
1380 let panel_len = packer.single_panel_len(self.k_range.len());
1381 let mut output =
1382 Tensor::zero::<u32>(&[packer.len(self.k_range.len(), self.mn_range.len())])
1383 .unwrap();
1384 unsafe {
1385 packer.pack_segment(
1386 output.view_mut(),
1387 input.view(),
1388 self.is_a as usize,
1389 !self.is_a as usize,
1390 self.k_range.clone(),
1391 self.mn_range.clone(),
1392 )
1393 };
1394 output
1395 .into_plain_array::<u32>()
1396 .unwrap()
1397 .into_shape_with_order((panels, panel_len))
1398 .unwrap()
1399 }
1400
1401 fn reference(&self) -> Array2<u32> {
1402 let input = self.input();
1403 let panels = self.mn_range.len().divceil(self.r);
1404 let len = Integer::next_multiple_of(&(self.k_range.len() * self.r), &self.align_panel);
1405 Array2::from_shape_fn([panels, len], |(panel, z)| {
1406 let k = z / self.r;
1407 let x = z % self.r;
1408 let mn = panel * self.r + x + self.mn_range.start;
1409 let k = k + self.k_range.start;
1410 let coords = if self.is_a { (mn, k) } else { (k, mn) };
1411 *input.get(coords).unwrap_or(&0)
1412 })
1413 }
1414
1415 fn valid(&self) -> Array2<bool> {
1416 let panels = self.mn_range.len().divceil(self.r);
1417 let len = Integer::next_multiple_of(&(self.k_range.len() * self.r), &self.align_panel);
1418 Array2::from_shape_fn([panels, len], |(panel, z)| {
1419 let k = z / self.r;
1420 let x = z % self.r;
1421 let k = k + self.k_range.start;
1422 let mn = panel * self.r + x + self.mn_range.start;
1423 k < self.k_range.end.min(self.k) && mn < self.mn_range.end.min(self.mn)
1424 })
1425 }
1426
1427 fn check(&self) {
1428 let mut packer = self.packer();
1429 let mut reference = self.reference();
1430 let valid = self.valid();
1431 Zip::from(&mut packer).and(&valid).for_each(|p, v| *p = if *v { *p } else { -1 as _ });
1432 Zip::from(&mut reference)
1433 .and(&valid)
1434 .for_each(|p, v| *p = if *v { *p } else { -1 as _ });
1435 assert_eq!(packer, reference);
1436 }
1437 }
1438
1439 impl Arbitrary for PackProblem {
1440 type Parameters = ();
1441 type Strategy = BoxedStrategy<PackProblem>;
1442 fn arbitrary_with(_args: ()) -> Self::Strategy {
1443 (any::<bool>(), 1usize..9, 1usize..20, 1usize..20)
1444 .prop_flat_map(|(is_a, r, k, mn)| {
1445 (
1446 Just((is_a, r, k, mn)),
1447 sub_range_strat(0..k),
1448 sub_range_strat(0..mn),
1449 1usize..5,
1450 )
1451 })
1452 .prop_map(|((is_a, r, k, mn), k_range, mn_range, align_panel)| PackProblem {
1453 k,
1454 mn,
1455 is_a,
1456 r,
1457 k_range,
1458 mn_range,
1459 align_panel,
1460 })
1461 .boxed()
1462 }
1463 }
1464
1465 fn sub_range_strat(range: Range<usize>) -> BoxedStrategy<Range<usize>> {
1466 (0..range.len())
1467 .prop_flat_map(|cropped| (Just(cropped), 0..=cropped))
1468 .prop_map(move |(cropped, left)| range.start + left..range.end - (cropped - left))
1469 .boxed()
1470 }
1471
1472 proptest::proptest! {
1473 #[test]
1474 fn prop(pb in any::<PackProblem>()) {
1475 pb.check();
1476 }
1477
1478 #[test]
1479 fn subrange_prop(_range in sub_range_strat(0..20)) {
1480 }
1481
1482 }
1483
1484 #[derive(Debug, Clone)]
1492 struct PackKMajorProblem {
1493 k: usize,
1494 mn: usize,
1495 r: usize,
1496 align_panel: usize,
1497 k_range: Range<usize>,
1498 mn_range: Range<usize>,
1499 }
1500
1501 impl PackKMajorProblem {
1502 fn check<T: Datum + Copy + num_traits::Zero>(&self, value: impl Fn(usize, usize) -> T) {
1503 let input =
1504 Array2::from_shape_fn((self.mn, self.k), |(x, k)| value(x, k)).into_tensor();
1505 let packer = super::PackedFormat::new(T::datum_type(), self.r, self.align_panel);
1506 let len = packer.len(self.k_range.len(), self.mn_range.len());
1507
1508 let mut packed = Tensor::zero::<T>(&[len]).unwrap();
1509 unsafe {
1510 packer.pack_segment(
1512 packed.view_mut(),
1513 input.view(),
1514 1,
1515 0,
1516 self.k_range.clone(),
1517 self.mn_range.clone(),
1518 )
1519 };
1520
1521 let mut reference = Tensor::zero::<T>(&[len]).unwrap();
1522 let input = input.to_plain_array_view::<T>().unwrap();
1523 unsafe {
1524 let mut writer = packer.write_with_k_inner(
1525 reference.as_ptr_mut_unchecked::<T>(),
1526 self.k_range.len(),
1527 self.mn,
1528 );
1529 for x in self.mn_range.start..self.mn_range.end.min(self.mn) {
1530 for k in self.k_range.clone() {
1531 super::PackingWriter::write(&mut writer, input[[x, k]]);
1532 }
1533 }
1534 }
1535
1536 assert_eq!(packed, reference, "{self:?} for {:?}", T::datum_type());
1537 }
1538
1539 fn check_all_widths(&self) {
1540 self.check(|x, k| (x * 41 + k * 7) as u32);
1541 self.check(|x, k| f16::from_f32((x * 41 + k * 7) as f32));
1542 self.check(|x, k| (x * 41 + k * 7) as u8);
1543 }
1544 }
1545
1546 impl Arbitrary for PackKMajorProblem {
1547 type Parameters = ();
1548 type Strategy = BoxedStrategy<PackKMajorProblem>;
1549 fn arbitrary_with(_: ()) -> Self::Strategy {
1550 (
1553 prop::sample::select(vec![1usize, 2, 3, 4, 5, 8, 12, 16, 24, 32]),
1554 1usize..40,
1555 1usize..40,
1556 )
1557 .prop_flat_map(|(r, k, mn)| {
1558 (Just((r, k, mn)), 1usize..5, sub_range_strat(0..k), sub_range_strat(0..mn))
1559 })
1560 .prop_map(|((r, k, mn), align_panel, k_range, mn_range)| PackKMajorProblem {
1561 k,
1562 mn,
1563 r,
1564 align_panel,
1565 k_range,
1566 mn_range,
1567 })
1568 .boxed()
1569 }
1570 }
1571
1572 proptest::proptest! {
1573 #[test]
1574 fn pack_k_major_prop(pb in any::<PackKMajorProblem>()) {
1575 pb.check_all_widths();
1576 }
1577 }
1578
1579 fn k_major(k: usize, mn: usize, r: usize) -> PackKMajorProblem {
1580 PackKMajorProblem { k, mn, r, align_panel: 1, k_range: 0..k, mn_range: 0..mn }
1581 }
1582
1583 #[test]
1584 fn k_major_exact_tiles() {
1585 k_major(4, 4, 4).check_all_widths();
1586 k_major(768, 256, 8).check_all_widths();
1587 k_major(16, 32, 16).check_all_widths();
1588 }
1589
1590 #[test]
1591 fn k_major_tails() {
1592 for k in [1, 2, 3, 5, 7] {
1594 k_major(k, 8, 8).check_all_widths();
1595 k_major(k, 7, 8).check_all_widths();
1596 }
1597 k_major(9, 6, 12).check_all_widths();
1598 k_major(9, 30, 12).check_all_widths();
1599 }
1600
1601 #[test]
1602 fn k_major_tile_over_threshold() {
1603 k_major(129, 41, 8).check_all_widths();
1607 k_major(160, 26, 16).check_all_widths();
1608 k_major(130, 44, 12).check_all_widths();
1609 k_major(140, 30, 8).check_all_widths();
1610 }
1611
1612 #[test]
1613 fn k_major_narrower_than_a_tile() {
1614 for r in [1, 2, 3] {
1615 k_major(9, 7, r).check_all_widths();
1616 }
1617 }
1618
1619 #[test]
1620 fn k_major_segments() {
1621 PackKMajorProblem { k: 20, mn: 20, r: 8, align_panel: 1, k_range: 3..17, mn_range: 0..20 }
1623 .check_all_widths();
1624 PackKMajorProblem { k: 20, mn: 20, r: 8, align_panel: 1, k_range: 0..20, mn_range: 4..12 }
1625 .check_all_widths();
1626 PackKMajorProblem { k: 20, mn: 20, r: 8, align_panel: 1, k_range: 0..20, mn_range: 16..24 }
1628 .check_all_widths();
1629 }
1630
1631 #[derive(Debug, Clone)]
1643 struct PackI8K4Problem {
1644 k: usize,
1645 mn: usize,
1646 r: usize,
1647 is_a: bool,
1651 }
1652
1653 impl PackI8K4Problem {
1654 fn logical(&self) -> Array2<i8> {
1656 Array2::from_shape_fn((self.k, self.mn), |(kk, m)| {
1657 (kk.wrapping_mul(31).wrapping_add(m.wrapping_mul(17)).wrapping_add(1)) as i8
1658 })
1659 }
1660
1661 fn panel_len(&self) -> usize {
1662 (self.k.div_ceil(4) * 4) * self.r
1663 }
1664
1665 fn reference(&self) -> Vec<i8> {
1667 let logical = self.logical();
1668 let r = self.r;
1669 let pl = self.panel_len();
1670 let panels = self.mn.div_ceil(r);
1671 let mut out = vec![0i8; panels * pl];
1672 for p in 0..panels {
1673 let pw = r.min(self.mn - p * r);
1674 for kk in 0..self.k {
1675 for lm in 0..pw {
1676 let m = p * r + lm;
1677 let off = p * pl + (kk / 4) * r * 4 + lm * 4 + (kk % 4);
1678 out[off] = logical[[kk, m]];
1679 }
1680 }
1681 }
1682 out
1683 }
1684
1685 fn pack_view_bytes(&self) -> Vec<i8> {
1687 let logical = self.logical();
1688 let packer = super::PackedI8K4::new(self.r);
1689 let (tensor, k_axis, mn_axis) = if self.is_a {
1690 let a = Array2::from_shape_fn((self.mn, self.k), |(m, kk)| logical[[kk, m]]);
1692 (a.into_tensor(), 1usize, 0usize)
1693 } else {
1694 (logical.clone().into_tensor(), 0usize, 1usize)
1695 };
1696 let packed = packer.pack_view(&tensor.view(), k_axis, mn_axis).unwrap();
1697 let pl = self.panel_len();
1698 let panels = self.mn.div_ceil(self.r);
1699 assert_eq!(packed.panels_count(), panels);
1700 assert_eq!(packed.k(), self.k);
1701 assert_eq!(packed.mn(), self.mn);
1702 let mut out = vec![0i8; panels * pl];
1703 unsafe {
1704 for p in 0..panels {
1705 let ptr = packed.panel_bytes(p, None).unwrap() as *const i8;
1706 std::ptr::copy_nonoverlapping(ptr, out.as_mut_ptr().add(p * pl), pl);
1707 }
1708 }
1709 out
1710 }
1711
1712 fn writer_bytes(&self) -> Vec<i8> {
1714 let logical = self.logical();
1715 let packer = super::PackedI8K4::new(self.r);
1716 let total = packer.len(self.k, self.mn);
1717 assert_eq!(total, self.mn.div_ceil(self.r) * self.panel_len());
1718 let mut buf = vec![0i8; total];
1719 {
1720 let mut w = packer.write_with_k_outer(buf.as_mut_ptr(), self.k, self.mn);
1721 for kk in 0..self.k {
1722 for m in 0..self.mn {
1723 super::PackingWriter::write(&mut w, logical[[kk, m]]);
1724 }
1725 }
1726 }
1727 buf
1728 }
1729
1730 fn check(&self) {
1731 let reference = self.reference();
1732 assert_eq!(
1733 self.pack_view_bytes(),
1734 reference,
1735 "pack_view disagrees with reference for {self:?}"
1736 );
1737 assert_eq!(
1738 self.writer_bytes(),
1739 reference,
1740 "write_with_k_outer disagrees with reference for {self:?}"
1741 );
1742 }
1743 }
1744
1745 impl Arbitrary for PackI8K4Problem {
1746 type Parameters = ();
1747 type Strategy = BoxedStrategy<PackI8K4Problem>;
1748 fn arbitrary_with(_: ()) -> Self::Strategy {
1749 (any::<bool>(), prop::sample::select(vec![4usize, 8, 16, 32]), 1usize..40, 1usize..40)
1751 .prop_map(|(is_a, r, k, mn)| PackI8K4Problem { k, mn, r, is_a })
1752 .boxed()
1753 }
1754 }
1755
1756 proptest::proptest! {
1757 #[test]
1758 fn pack_i8k4_prop(pb in any::<PackI8K4Problem>()) {
1759 pb.check();
1760 }
1761 }
1762
1763 fn k4(k: usize, mn: usize, r: usize, is_a: bool) -> PackI8K4Problem {
1764 PackI8K4Problem { k, mn, r, is_a }
1765 }
1766
1767 #[test]
1768 fn i8k4_smallest() {
1769 k4(1, 1, 4, false).check();
1770 k4(1, 1, 4, true).check();
1771 }
1772
1773 #[test]
1774 fn i8k4_exact_tile() {
1775 k4(4, 4, 4, false).check();
1777 k4(8, 32, 32, false).check();
1778 k4(8, 32, 32, true).check();
1779 }
1780
1781 #[test]
1782 fn i8k4_k_not_multiple_of_4() {
1783 for k in [1, 2, 3, 5, 6, 7, 9] {
1785 k4(k, 4, 4, false).check();
1786 k4(k, 7, 8, true).check();
1787 }
1788 }
1789
1790 #[test]
1791 fn i8k4_partial_last_panel() {
1792 k4(5, 7, 4, false).check();
1794 k4(5, 7, 4, true).check();
1795 k4(4, 33, 32, false).check();
1796 k4(4, 33, 32, true).check();
1797 k4(3, 1, 32, false).check();
1798 }
1799
1800 #[test]
1801 fn i8k4_single_wide_tile() {
1802 k4(7, 1, 32, false).check();
1804 k4(7, 5, 16, true).check();
1805 }
1806
1807 #[test]
1808 fn i8k4_many_panels() {
1809 k4(13, 100, 8, false).check();
1810 k4(13, 100, 8, true).check();
1811 k4(17, 65, 16, false).check();
1812 }
1813
1814 #[test]
1815 fn simple_b_1() {
1816 PackProblem {
1817 k: 2,
1818 mn: 1,
1819 is_a: false,
1820 r: 1,
1821 k_range: 0..2,
1822 mn_range: 0..1,
1823 align_panel: 1,
1824 }
1825 .check();
1826 }
1827
1828 #[test]
1829 fn simple_b_2() {
1830 PackProblem {
1831 k: 2,
1832 mn: 2,
1833 is_a: false,
1834 r: 1,
1835 k_range: 0..2,
1836 mn_range: 0..2,
1837 align_panel: 1,
1838 }
1839 .check()
1840 }
1841
1842 #[test]
1843 fn simple_b_3() {
1844 PackProblem {
1845 k: 2,
1846 mn: 1,
1847 is_a: false,
1848 r: 4,
1849 k_range: 0..2,
1850 mn_range: 0..1,
1851 align_panel: 1,
1852 }
1853 .check();
1854 }
1855
1856 #[test]
1857 fn simple_b_4() {
1858 PackProblem {
1859 k: 1,
1860 mn: 3,
1861 is_a: false,
1862 r: 2,
1863 k_range: 0..1,
1864 mn_range: 0..3,
1865 align_panel: 1,
1866 }
1867 .check();
1868 }
1869
1870 #[test]
1871 fn simple_a_1() {
1872 PackProblem {
1873 k: 2,
1874 mn: 2,
1875 is_a: true,
1876 r: 1,
1877 k_range: 0..2,
1878 mn_range: 0..2,
1879 align_panel: 1,
1880 }
1881 .check();
1882 }
1883
1884 #[test]
1885 fn simple_a_2() {
1886 PackProblem {
1887 k: 2,
1888 mn: 3,
1889 is_a: true,
1890 r: 2,
1891 k_range: 0..2,
1892 mn_range: 0..3,
1893 align_panel: 1,
1894 }
1895 .check();
1896 }
1897
1898 #[test]
1899 fn range_k_0() {
1900 PackProblem {
1901 k: 2,
1902 mn: 1,
1903 is_a: false,
1904 r: 1,
1905 k_range: 1..2,
1906 mn_range: 0..1,
1907 align_panel: 1,
1908 }
1909 .check();
1910 }
1911
1912 #[test]
1913 fn range_k_1() {
1914 PackProblem {
1915 k: 2,
1916 mn: 2,
1917 is_a: false,
1918 r: 1,
1919 k_range: 0..2,
1920 mn_range: 0..1,
1921 align_panel: 1,
1922 }
1923 .check();
1924 }
1925
1926 #[test]
1927 fn range_k_2() {
1928 PackProblem {
1929 k: 2,
1930 mn: 1,
1931 is_a: false,
1932 r: 6,
1933 k_range: 1..2,
1934 mn_range: 0..1,
1935 align_panel: 1,
1936 }
1937 .check();
1938 }
1939
1940 #[test]
1941 fn range_mn_0() {
1942 PackProblem {
1943 k: 1,
1944 mn: 2,
1945 is_a: false,
1946 r: 2,
1947 k_range: 0..1,
1948 mn_range: 0..1,
1949 align_panel: 1,
1950 }
1951 .check();
1952 }
1953
1954 #[test]
1955 fn range_b_4() {
1956 PackProblem {
1957 k: 1,
1958 mn: 2,
1959 is_a: false,
1960 r: 6,
1961 k_range: 0..1,
1962 mn_range: 1..2,
1963 align_panel: 1,
1964 }
1965 .check();
1966 }
1967
1968 #[test]
1969 fn range_b_5() {
1970 PackProblem {
1971 k: 1,
1972 mn: 7,
1973 is_a: false,
1974 r: 6,
1975 k_range: 0..1,
1976 mn_range: 1..7,
1977 align_panel: 1,
1978 }
1979 .check();
1980 }
1981
1982 #[test]
1983 fn align_a_1() {
1984 PackProblem {
1985 k: 2,
1986 mn: 2,
1987 is_a: true,
1988 r: 1,
1989 k_range: 0..1,
1990 mn_range: 0..2,
1991 align_panel: 2,
1992 }
1993 .check();
1994 }
1995
1996 #[test]
1997 fn align_b_1() {
1998 PackProblem {
1999 k: 1,
2000 mn: 1,
2001 is_a: false,
2002 r: 1,
2003 k_range: 0..1,
2004 mn_range: 0..1,
2005 align_panel: 2,
2006 }
2007 .check();
2008 }
2009
2010 #[test]
2011 fn align_b_2() {
2012 PackProblem {
2013 k: 3,
2014 mn: 1,
2015 is_a: false,
2016 r: 1,
2017 k_range: 0..3,
2018 mn_range: 0..1,
2019 align_panel: 2,
2020 }
2021 .check();
2022 }
2023
2024 #[test]
2025 fn align_b_3() {
2026 PackProblem {
2027 k: 1,
2028 mn: 1,
2029 is_a: false,
2030 r: 3,
2031 k_range: 0..1,
2032 mn_range: 0..1,
2033 align_panel: 2,
2034 }
2035 .check();
2036 }
2037
2038 #[test]
2039 fn align_b_4() {
2040 PackProblem {
2041 k: 2,
2042 mn: 1,
2043 is_a: false,
2044 r: 1,
2045 k_range: 0..1,
2046 mn_range: 0..1,
2047 align_panel: 2,
2048 }
2049 .check();
2050 }
2051
2052 #[test]
2053 fn align_b_5() {
2054 PackProblem {
2055 k: 1,
2056 mn: 5,
2057 is_a: false,
2058 r: 4,
2059 k_range: 0..1,
2060 mn_range: 0..5,
2061 align_panel: 3,
2062 }
2063 .check();
2064 }
2065
2066 #[cfg(feature = "multithread-mm")]
2067 #[test]
2068 fn packing_work_threshold() {
2069 use super::{PACKING_MIN_CHUNK_BYTES, packing_has_enough_work};
2070
2071 for threads in [2, 4, 8] {
2072 for panels in [2, 3, 4 * threads - 1, 4 * threads, 4 * threads + 1, 1001] {
2073 let boundary = (4 * threads).min(panels) * PACKING_MIN_CHUNK_BYTES;
2074 assert!(!packing_has_enough_work(panels, boundary - 1, threads));
2075 assert!(packing_has_enough_work(panels, boundary, threads));
2076 assert!(packing_has_enough_work(panels, boundary + 1, threads));
2077 }
2078 }
2079 for threads in [0, 1, 4] {
2080 assert!(!packing_has_enough_work(0, usize::MAX, threads));
2081 assert!(!packing_has_enough_work(1, usize::MAX, threads));
2082 }
2083 assert!(!packing_has_enough_work(1001, usize::MAX, 1));
2084 }
2085
2086 #[cfg(feature = "multithread-mm")]
2087 #[cfg(not(target_arch = "wasm32"))]
2088 #[test]
2089 fn multithreaded_pack_matches_serial() {
2090 use crate::frame::mmm::EagerPackedInput;
2091 use crate::multithread::{Executor, multithread_tract_scope};
2092 let pack = |format: &super::PackedFormat, t: &Tensor, k_axis: usize, mn_axis: usize| {
2094 let k = t.shape()[k_axis];
2095 let packed = format.pack_tensor_view(&t.view(), k_axis, mn_axis).unwrap();
2096 let packed = packed.downcast_ref::<EagerPackedInput>().unwrap();
2097 let values = k * format.r * t.datum_type().size_of();
2098 packed
2099 .packed
2100 .as_bytes()
2101 .chunks(packed.panel_bytes)
2102 .flat_map(|panel| panel[..values].to_vec())
2103 .collect::<Vec<u8>>()
2104 };
2105 let executors = [Executor::multithread(1), Executor::multithread(4)];
2106 for dt in [DatumType::U8, DatumType::F16, DatumType::F32] {
2107 let boundary_mn = 16 * super::PACKING_MIN_CHUNK_BYTES / (128 * dt.size_of());
2108 for (k, mn) in [
2109 (64, 600),
2110 (37, 1001),
2111 (9, 5000),
2112 (16384, 9),
2113 (128, boundary_mn - 1),
2114 (128, boundary_mn),
2115 (128, boundary_mn + 1),
2116 ] {
2117 for r in [4, 5, 8, 16, 24, 32] {
2118 let format = super::PackedFormat::new(dt, r, 16);
2119 for (shape, k_axis, mn_axis) in
2120 [(vec![k, mn], 0, 1), (vec![mn, k], 1, 0), (vec![k, mn, 2], 0, 1)]
2121 {
2122 let data: Vec<f32> = (0..shape.iter().product::<usize>())
2123 .map(|i| (i % 257) as f32 * 0.5)
2124 .collect();
2125 let t = Tensor::from_shape(&shape, &data).unwrap();
2126 let t = t.cast_to_dt(dt).unwrap().into_owned();
2127 let serial = multithread_tract_scope(Executor::SingleThread, || {
2128 pack(&format, &t, k_axis, mn_axis)
2129 });
2130 for executor in &executors {
2131 let parallel = multithread_tract_scope(executor.clone(), || {
2132 pack(&format, &t, k_axis, mn_axis)
2133 });
2134 assert_eq!(
2135 serial, parallel,
2136 "k {k} mn {mn} r {r} {dt:?} shape {shape:?}"
2137 );
2138 }
2139 }
2140 }
2141 }
2142 }
2143 }
2144}