1use super::{FusedKerSpec, FusedSpec, MatMatMulKer, OutputStoreKer};
2use crate::{BinOp, LADatum};
3use downcast_rs::{Downcast, impl_downcast};
4use std::cell::RefCell;
5use std::fmt::Debug;
6use std::sync::atomic::AtomicUsize;
7use tract_data::internal::num_integer::Integer;
8use tract_data::internal::*;
9
10static GENERATION: AtomicUsize = AtomicUsize::new(1);
11
12thread_local! {
13 static TLS: RefCell<TLSScratch> = Default::default();
14}
15
16#[derive(Default, Debug)]
17pub(crate) struct TLSScratch {
18 generation: usize,
19 blob: Blob,
20 ker_specs_16: Vec<FusedKerSpec<f16>>,
21 ker_specs_32: Vec<FusedKerSpec<f32>>,
22 ker_specs_64: Vec<FusedKerSpec<f64>>,
23}
24
25impl TLSScratch {
26 #[allow(unknown_lints, clippy::missing_transmute_annotations)]
27 fn ker_specs<TI: LADatum>(&mut self) -> &mut Vec<FusedKerSpec<TI>> {
28 unsafe {
29 if TI::datum_type() == f32::datum_type() || TI::datum_type() == i32::datum_type() {
30 std::mem::transmute(&mut self.ker_specs_32)
31 } else if TI::datum_type() == f16::datum_type() {
32 std::mem::transmute(&mut self.ker_specs_16)
33 } else if TI::datum_type() == f64::datum_type() {
34 std::mem::transmute(&mut self.ker_specs_64)
35 } else {
36 todo!();
37 }
38 }
39 }
40
41 fn sync<TI: LADatum>(&mut self, scratch: &ScratchSpaceImpl<TI>) {
42 if self.generation == scratch.generation {
43 return;
44 }
45 let ker_specs = self.ker_specs::<TI>();
46 ker_specs.clear();
47 ker_specs.extend_from_slice(&scratch.ker_specs);
48
49 unsafe {
50 self.blob.ensure_size_and_align(scratch.blob_size, scratch.blob_align);
51
52 for LocDependent { loc, ker_spec, .. } in &scratch.loc_dependent {
53 #[allow(clippy::single_match)]
54 if matches!(scratch.ker_specs[*ker_spec], FusedKerSpec::AddMatMul { .. }) {
55 let scratch = &mut *(self.blob.as_ptr().add(*loc) as *mut AddMatMulTemp);
56 scratch.panel_a_id = usize::MAX;
57 scratch.panel_b_id = usize::MAX;
58 };
59 }
60 }
61 self.generation = scratch.generation;
62 }
63}
64
65pub trait ScratchSpace: Downcast + Send {}
66impl_downcast!(ScratchSpace);
67
68#[derive(Debug, Default)]
69pub struct ScratchSpaceImpl<TI: LADatum> {
70 generation: usize,
71 blob_size: usize,
72 blob_align: usize,
73 ker_specs: Vec<FusedKerSpec<TI>>,
74 loc_dependent: TVec<LocDependent>,
75 valid_down_tiles: usize,
76 remnant_down: usize,
77 valid_right_tiles: usize,
78 remnant_right: usize,
79}
80
81#[derive(Debug, new)]
82struct LocDependent {
83 spec: usize,
84 ker_spec: usize,
85 loc: usize,
87 buffer_a: Option<usize>,
89 buffer_b: Option<usize>,
90}
91
92impl<TI: LADatum> ScratchSpace for ScratchSpaceImpl<TI> {}
93unsafe impl<TI: LADatum> Send for ScratchSpaceImpl<TI> {}
94
95#[derive(Debug)]
96struct AddMatMulTemp {
97 ptr_a: *const u8,
98 panel_a_id: usize,
99 ptr_b: *const u8,
100 panel_b_id: usize,
101}
102
103fn partial_m_direct<TI: LADatum>(
111 ker: &impl MatMatMulKer<Acc = TI>,
112 specs: &[FusedSpec],
113 m_remnant: usize,
114 n_remnant: usize,
115) -> bool {
116 if ker.name() != "sme_mmm_f32_32x32" {
117 return false;
118 }
119 if m_remnant == 0
120 || m_remnant >= ker.mr()
121 || n_remnant != ker.nr()
122 || !ker.stores_row_major_tile()
123 {
124 return false;
125 }
126 let mut saw_store = false;
127 let mut saw_matmul = false;
128 for spec in specs {
129 match spec {
130 FusedSpec::Store(store) => {
131 saw_store = true;
132 if store.col_byte_stride != store.item_size as isize || store.item_size != 4 {
133 return false;
134 }
135 }
136 FusedSpec::AddMatMul { .. } => saw_matmul = true,
137 _ => {}
138 }
139 }
140 saw_store && saw_matmul
141}
142
143impl<TI: LADatum> ScratchSpaceImpl<TI> {
144 pub unsafe fn prepare(
145 &mut self,
146 ker: &impl MatMatMulKer<Acc = TI>,
147 m: usize,
148 n: usize,
149 specs: &[FusedSpec],
150 ) -> TractResult<()> {
151 use FusedKerSpec as FKS;
152 use FusedSpec as FS;
153 self.ker_specs.clear();
154 self.loc_dependent.clear();
155 self.ker_specs.reserve(specs.len() + 2);
156 self.ker_specs.push(FusedKerSpec::Clear);
157 self.valid_down_tiles = m / ker.mr();
158 self.remnant_down = m % ker.mr();
159 self.valid_right_tiles = n / ker.nr();
160 self.remnant_right = n % ker.nr();
161 let mut offset = 0;
162 let mut align = std::mem::size_of::<*const ()>();
163 fn ld(spec: usize, uspec: usize, loc: usize) -> LocDependent {
164 LocDependent { spec, ker_spec: uspec, loc, buffer_a: None, buffer_b: None }
165 }
166 for (ix, spec) in specs.iter().enumerate() {
167 offset = offset.next_multiple_of(&align);
168 let ker_spec = match spec {
169 FS::BinScalar(t, op) => match op {
170 BinOp::Min => FKS::ScalarMin(*t.try_as_plain_ram()?.to_scalar()?),
171 BinOp::Max => FKS::ScalarMax(*t.try_as_plain_ram()?.to_scalar()?),
172 BinOp::Mul => FKS::ScalarMul(*t.try_as_plain_ram()?.to_scalar()?),
173 BinOp::Add => FKS::ScalarAdd(*t.try_as_plain_ram()?.to_scalar()?),
174 BinOp::Sub => FKS::ScalarSub(*t.try_as_plain_ram()?.to_scalar()?),
175 BinOp::SubF => FKS::ScalarSubF(*t.try_as_plain_ram()?.to_scalar()?),
176 },
177 FS::ShiftLeft(s) => FKS::ShiftLeft(*s),
178 FS::RoundingShiftRight(s, rp) => FKS::RoundingShiftRight(*s, *rp),
179 FS::QScale(s, rp, m) => FKS::QScale(*s, *rp, *m),
180 FS::BinPerRow(_, _) => {
181 self.loc_dependent.push(ld(ix, self.ker_specs.len(), offset));
182 offset += TI::datum_type().size_of() * ker.mr();
183 FusedKerSpec::Done
184 }
185 FS::BinPerCol(_, _) => {
186 self.loc_dependent.push(ld(ix, self.ker_specs.len(), offset));
187 offset += TI::datum_type().size_of() * ker.nr();
188 FusedKerSpec::Done
189 }
190 FS::AddRowColProducts(_, _) => {
191 self.loc_dependent.push(ld(ix, self.ker_specs.len(), offset));
192 offset += TI::datum_type().size_of() * (ker.mr() + ker.nr());
193 FusedKerSpec::Done
194 }
195 FS::AddUnicast(_) => {
196 self.loc_dependent.push(ld(ix, self.ker_specs.len(), offset));
197 offset += TI::datum_type().size_of() * ker.mr() * ker.nr();
198 FusedKerSpec::Done
199 }
200 FS::Store(store) => {
201 let row_major = ker.stores_row_major_tile()
205 && store.col_byte_stride == store.item_size as isize;
206 let tile_bytes = if row_major {
207 align = align.lcm(&128);
211 offset = Integer::next_multiple_of(&offset, &128);
212 Integer::next_multiple_of(&(store.item_size * ker.nr()), &128) * ker.mr()
213 } else {
214 store.item_size * ker.mr() * ker.nr()
215 };
216 self.loc_dependent.push(ld(ix, self.ker_specs.len(), offset));
217 offset += tile_bytes;
218 FusedKerSpec::Done
219 }
220 FS::LeakyRelu(t) => FKS::LeakyRelu(*t.try_as_plain_ram()?.to_scalar()?),
221 FS::AddMatMul { a, b, packing } => {
222 let mut ld = ld(ix, self.ker_specs.len(), offset);
223 offset += std::mem::size_of::<AddMatMulTemp>();
224 if let Some(tmp) = a.scratch_panel_buffer_layout() {
225 align = tmp.align().lcm(&align);
226 offset = Integer::next_multiple_of(&offset, &tmp.align());
227 ld.buffer_a = Some(offset);
228 offset += tmp.size();
229 }
230 if let Some(tmp) = b.scratch_panel_buffer_layout() {
231 align = tmp.align().lcm(&align);
232 offset = Integer::next_multiple_of(&offset, &tmp.align());
233 ld.buffer_b = Some(offset);
234 offset += tmp.size();
235 }
236 self.loc_dependent.push(ld);
237 FusedKerSpec::AddMatMul {
238 k: 0,
239 pa: std::ptr::null(),
240 pb: std::ptr::null(),
241 packing: *packing,
242 }
243 }
244 };
245 self.ker_specs.push(ker_spec);
246 }
247 self.ker_specs.push(FKS::Done);
248 self.blob_size = offset;
249 self.blob_align = align;
250
251 self.generation = GENERATION.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
252 Ok(())
253 }
254
255 pub unsafe fn run(
256 &self,
257 ker: &impl MatMatMulKer<Acc = TI>,
258 specs: &[FusedSpec],
259 down: usize,
260 right: usize,
261 ) -> TractResult<()> {
262 unsafe {
266 self.run_in_tls_scope(|this, tls| this.run_one_tile(ker, specs, tls, down, right))
267 }
268 }
269
270 pub(crate) unsafe fn run_in_tls_scope<F, R>(&self, f: F) -> R
275 where
276 F: FnOnce(&Self, &mut TLSScratch) -> R,
277 {
278 TLS.with_borrow_mut(|tls| {
279 tls.sync(self);
280 f(self, tls)
281 })
282 }
283
284 #[inline(always)]
287 pub(crate) unsafe fn run_one_tile(
288 &self,
289 ker: &impl MatMatMulKer<Acc = TI>,
290 specs: &[FusedSpec],
291 tls: &mut TLSScratch,
292 down: usize,
293 right: usize,
294 ) -> TractResult<()> {
295 unsafe {
296 if down < self.valid_down_tiles && right < self.valid_right_tiles {
297 self.for_valid_tile(ker, specs, tls, down, right)?;
298 let err = ker.kernel(tls.ker_specs());
299 ensure!(err == 0, "Kernel {} returned error {err}", ker.name());
300 } else {
301 let remnant_down =
302 if down < self.valid_down_tiles { ker.mr() } else { self.remnant_down };
303 let remnant_right =
304 if right < self.valid_right_tiles { ker.nr() } else { self.remnant_right };
305 let direct = self.for_border_tile(
306 ker,
307 specs,
308 tls,
309 down,
310 right,
311 remnant_down,
312 remnant_right,
313 )?;
314 let err = ker.kernel(tls.ker_specs());
315 ensure!(err == 0, "Kernel {} returned error {err}", ker.name());
316 if !direct {
317 self.postprocess_tile(specs, tls, down, right, remnant_down, remnant_right)?;
318 }
319 }
320 Ok(())
321 }
322 }
323
324 #[inline(always)]
325 unsafe fn for_valid_tile(
326 &self,
327 ker: &impl MatMatMulKer<Acc = TI>,
328 specs: &[FusedSpec],
329 tls: &mut TLSScratch,
330 down: usize,
331 right: usize,
332 ) -> TractResult<()> {
333 unsafe {
334 use FusedKerSpec as FKS;
335 use FusedSpec as FS;
336 let ScratchSpaceImpl { ker_specs, loc_dependent, .. } = self;
337 debug_assert!(specs.len() + 2 == ker_specs.len());
338 for LocDependent { spec, ker_spec, loc, buffer_a, buffer_b } in loc_dependent {
339 let spec = specs.get_unchecked(*spec);
340 let it = match spec {
341 FS::BinPerRow(v, op) => {
342 let v = v.as_ptr_unchecked::<TI>().add(down * ker.mr());
343 match op {
344 BinOp::Min => FKS::PerRowMin(v),
345 BinOp::Max => FKS::PerRowMax(v),
346 BinOp::Add => FKS::PerRowAdd(v),
347 BinOp::Mul => FKS::PerRowMul(v),
348 BinOp::Sub => FKS::PerRowSub(v),
349 BinOp::SubF => FKS::PerRowSubF(v),
350 }
351 }
352 FS::BinPerCol(v, op) => {
353 let v = v.as_ptr_unchecked::<TI>().add(right * ker.nr());
354 match op {
355 BinOp::Min => FKS::PerColMin(v),
356 BinOp::Max => FKS::PerColMax(v),
357 BinOp::Add => FKS::PerColAdd(v),
358 BinOp::Mul => FKS::PerColMul(v),
359 BinOp::Sub => FKS::PerColSub(v),
360 BinOp::SubF => FKS::PerColSubF(v),
361 }
362 }
363 FS::AddRowColProducts(rows, cols) => {
364 let row_ptr = rows.as_ptr_unchecked::<TI>().add(down * ker.mr());
365 let col_ptr = cols.as_ptr_unchecked::<TI>().add(right * ker.nr());
366 FKS::AddRowColProducts(row_ptr, col_ptr)
367 }
368 FS::AddUnicast(store) => FKS::AddUnicast(store.tile_c(down, right)),
369 FS::Store(c_store) => FKS::Store(c_store.tile_c(down, right)),
370 FS::AddMatMul { a, b, packing } => {
371 let scratch = (tls.blob.as_mut_ptr().add(*loc) as *mut AddMatMulTemp)
372 .as_mut()
373 .unwrap();
374 if scratch.panel_a_id != down {
375 scratch.ptr_a = a.panel_bytes(
376 down,
377 buffer_a.map(|o| tls.blob.as_mut_ptr().add(o)),
378 )?;
379 scratch.panel_a_id = down;
380 }
381 if scratch.panel_b_id != right {
382 scratch.ptr_b = b.panel_bytes(
383 right,
384 buffer_b.map(|o| tls.blob.as_mut_ptr().add(o)),
385 )?;
386 scratch.panel_b_id = right;
387 }
388 FKS::AddMatMul {
389 k: b.k(),
390 pa: scratch.ptr_a,
391 pb: scratch.ptr_b,
392 packing: *packing,
393 }
394 }
395 _ => std::hint::unreachable_unchecked(),
396 };
397 *tls.ker_specs().get_unchecked_mut(*ker_spec) = it;
398 }
399 Ok(())
400 }
401 }
402
403 #[inline(never)]
404 #[allow(clippy::too_many_arguments)]
405 unsafe fn for_border_tile(
406 &self,
407 ker: &impl MatMatMulKer<Acc = TI>,
408 specs: &[FusedSpec],
409 tls: &mut TLSScratch,
410 down: usize,
411 right: usize,
412 m_remnant: usize,
413 n_remnant: usize,
414 ) -> TractResult<bool> {
415 unsafe {
416 use FusedKerSpec as FKS;
417 use FusedSpec as FS;
418 let direct = partial_m_direct(ker, specs, m_remnant, n_remnant);
424 for LocDependent { spec, ker_spec: uspec, loc, buffer_a, buffer_b } in
425 &self.loc_dependent
426 {
427 let loc = tls.blob.as_mut_ptr().add(*loc);
428 let spec = specs.get_unchecked(*spec);
429 let it = match spec {
430 FS::BinPerRow(v, op) => {
431 let buf = std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr());
432 let ptr = if m_remnant < ker.mr() {
433 if m_remnant > 0 {
434 buf.get_unchecked_mut(..m_remnant).copy_from_slice(
435 v.as_slice_unchecked()
436 .get_unchecked(down * ker.mr()..)
437 .get_unchecked(..m_remnant),
438 );
439 }
440 buf.get_unchecked_mut(m_remnant..)
444 .iter_mut()
445 .for_each(|x| *x = TI::zero());
446 buf.as_ptr()
447 } else {
448 v.as_ptr_unchecked::<TI>().add(down * ker.mr())
449 };
450 match op {
451 BinOp::Min => FKS::PerRowMin(ptr),
452 BinOp::Max => FKS::PerRowMax(ptr),
453 BinOp::Add => FKS::PerRowAdd(ptr),
454 BinOp::Mul => FKS::PerRowMul(ptr),
455 BinOp::Sub => FKS::PerRowSub(ptr),
456 BinOp::SubF => FKS::PerRowSubF(ptr),
457 }
458 }
459 FS::BinPerCol(v, op) => {
460 let buf = std::slice::from_raw_parts_mut(loc as *mut TI, ker.nr());
461 let ptr = if n_remnant < ker.nr() {
462 if n_remnant > 0 {
463 buf.get_unchecked_mut(..n_remnant).copy_from_slice(
464 v.as_slice_unchecked()
465 .get_unchecked(right * ker.nr()..)
466 .get_unchecked(..n_remnant),
467 );
468 }
469 buf.get_unchecked_mut(n_remnant..)
470 .iter_mut()
471 .for_each(|x| *x = TI::zero());
472 buf.as_ptr()
473 } else {
474 v.as_ptr_unchecked::<TI>().add(right * ker.nr())
475 };
476 match op {
477 BinOp::Min => FKS::PerColMin(ptr),
478 BinOp::Max => FKS::PerColMax(ptr),
479 BinOp::Add => FKS::PerColAdd(ptr),
480 BinOp::Mul => FKS::PerColMul(ptr),
481 BinOp::Sub => FKS::PerColSub(ptr),
482 BinOp::SubF => FKS::PerColSubF(ptr),
483 }
484 }
485 FS::AddRowColProducts(rows, cols) => {
486 let r = std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr());
487 let row_ptr = if m_remnant < ker.mr() {
488 r.get_unchecked_mut(..m_remnant).copy_from_slice(
489 rows.as_slice_unchecked()
490 .get_unchecked(down * ker.mr()..)
491 .get_unchecked(..m_remnant),
492 );
493 r.get_unchecked_mut(m_remnant..)
494 .iter_mut()
495 .for_each(|x| *x = TI::zero());
496 r.as_ptr()
497 } else {
498 rows.as_ptr_unchecked::<TI>().add(down * ker.mr())
499 };
500 let c = std::slice::from_raw_parts_mut(
501 (loc as *mut TI).add(ker.mr()),
502 ker.nr(),
503 );
504 let col_ptr = if n_remnant < ker.nr() {
505 c.get_unchecked_mut(..n_remnant).copy_from_slice(
506 cols.as_slice_unchecked()
507 .get_unchecked(right * ker.nr()..)
508 .get_unchecked(..n_remnant),
509 );
510 c.get_unchecked_mut(n_remnant..)
511 .iter_mut()
512 .for_each(|x| *x = TI::zero());
513 c.as_ptr()
514 } else {
515 cols.as_ptr_unchecked::<TI>().add(right * ker.nr())
516 };
517 FKS::AddRowColProducts(row_ptr, col_ptr)
518 }
519 FS::AddUnicast(store) => {
520 let row_byte_stride = store.row_byte_stride;
521 let col_byte_stride = store.col_byte_stride;
522 let tile_offset = row_byte_stride * down as isize * ker.mr() as isize
523 + col_byte_stride * right as isize * ker.nr() as isize;
524 let tile_ptr = store.ptr.offset(tile_offset);
525 let tmp_d_tile =
526 std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr() * ker.nr());
527 tmp_d_tile.iter_mut().for_each(|t| *t = TI::zero());
528 for r in 0..m_remnant as isize {
529 for c in 0..n_remnant as isize {
530 let inner_offset = c * col_byte_stride + r * row_byte_stride;
531 if inner_offset + tile_offset
532 < (store.item_size * store.item_count) as isize
533 {
534 *tmp_d_tile
535 .get_unchecked_mut(r as usize + c as usize * ker.mr()) =
536 *(tile_ptr.offset(inner_offset) as *const TI);
537 }
538 }
539 }
540 FKS::AddUnicast(OutputStoreKer {
541 ptr: tmp_d_tile.as_ptr() as _,
542 row_byte_stride: std::mem::size_of::<TI>() as isize,
543 col_byte_stride: (std::mem::size_of::<TI>() * ker.mr()) as isize,
544 item_size: std::mem::size_of::<TI>(),
545 })
546 }
547 FS::Store(c_store) => {
548 if direct {
549 FKS::Store(c_store.tile_c(down, right))
550 } else {
551 let row_major = ker.stores_row_major_tile()
552 && c_store.col_byte_stride == c_store.item_size as isize;
553 let (row_byte_stride, col_byte_stride) = if row_major {
554 let row = Integer::next_multiple_of(
557 &(c_store.item_size * ker.nr()),
558 &128,
559 );
560 (row as isize, c_store.item_size as isize)
561 } else {
562 (
563 c_store.item_size as isize,
564 (c_store.item_size * ker.mr()) as isize,
565 )
566 };
567 let tmpc = OutputStoreKer {
568 ptr: loc as _,
569 item_size: c_store.item_size,
570 row_byte_stride,
571 col_byte_stride,
572 };
573 FKS::Store(tmpc)
574 }
575 }
576 FS::AddMatMul { a, b, packing } => {
577 let scratch = (loc as *mut AddMatMulTemp).as_mut().unwrap();
578 if scratch.panel_a_id != down {
579 scratch.ptr_a = a.panel_bytes(
580 down,
581 buffer_a.map(|o| tls.blob.as_mut_ptr().add(o)),
582 )?;
583 scratch.panel_a_id = down;
584 }
585 if scratch.panel_b_id != right {
586 scratch.ptr_b = b.panel_bytes(
587 right,
588 buffer_b.map(|o| tls.blob.as_mut_ptr().add(o)),
589 )?;
590 scratch.panel_b_id = right;
591 }
592 let packing = if direct { *packing | (m_remnant << 8) } else { *packing };
593 FKS::AddMatMul { k: b.k(), pa: scratch.ptr_a, pb: scratch.ptr_b, packing }
594 }
595 _ => std::hint::unreachable_unchecked(),
596 };
597 *tls.ker_specs().get_unchecked_mut(*uspec) = it;
598 }
599 Ok(direct)
600 }
601 }
602
603 #[inline]
604 pub fn uspecs(&self) -> &[FusedKerSpec<TI>] {
605 &self.ker_specs
606 }
607
608 unsafe fn postprocess_tile(
609 &self,
610 specs: &[FusedSpec],
611 tls: &mut TLSScratch,
612 down: usize,
613 right: usize,
614 m_remnant: usize,
615 n_remnant: usize,
616 ) -> TractResult<()>
617 where
618 TI: LADatum,
619 {
620 unsafe {
621 for LocDependent { spec, ker_spec: uspec, .. } in self.loc_dependent.iter() {
622 let spec = specs.get_unchecked(*spec);
623 let ker_spec = tls.ker_specs::<TI>().get_unchecked(*uspec);
624 if let (FusedSpec::Store(c_store), FusedKerSpec::Store(tmp)) = (spec, ker_spec) {
625 c_store.set_from_tile(down, right, m_remnant, n_remnant, tmp)
626 }
627 }
628 Ok(())
629 }
630 }
631}