1use core::fmt;
8use std::num::NonZeroUsize;
9use std::sync::Arc;
10
11use tenferro_tensor::backend::GroupedGemmJob;
12use tenferro_tensor::{
13 DType, DotGeneralAccumulation, Tensor, TensorRead, TensorView, TensorViewMut, TensorWrite,
14};
15
16use crate::arbiter::{with_execution_owner, ResourcePermit};
17use crate::backend::CpuBackendKind;
18use crate::buffer_pool::BufferPool;
19use crate::domain_executor::{indexed_jobs, install_scoped};
20#[cfg(feature = "cpu-blas")]
21use crate::provider_capability::builtin_blas_execution_capabilities;
22#[cfg(not(feature = "cpu-blas"))]
23use crate::provider_capability::serial_capabilities;
24use crate::provider_capability::{engine_worker_capabilities, CpuProviderExecutionCapabilities};
25use crate::resource_domain::CpuResourceDomain;
26use crate::{
27 CpuDomainExecutorError, CpuDomainId, CpuInnerParallelism, CpuPlacementGuarantee, CpuSet,
28};
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum CpuOperand {
40 Lhs,
42 Rhs,
44 Output,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[non_exhaustive]
64pub enum CpuProviderUnsupported {
65 DType(DType),
67 Rank {
69 lhs: usize,
71 rhs: usize,
73 },
74 Layout(CpuOperand),
76 Conjugation,
78 Accumulation,
80 StridedBatch,
82 Grouped,
84 RuntimeUnavailable,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100#[must_use]
101pub enum CpuProviderOutcome {
102 Executed,
104 Unsupported(CpuProviderUnsupported),
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub enum ParallelMode {
119 Sequential,
121 Outer,
124 Inner,
126}
127
128#[derive(Clone, Copy)]
145pub struct CpuExecutionContext<'a> {
146 domain: &'a CpuResourceDomain,
147 parallel_mode: ParallelMode,
148}
149
150impl fmt::Debug for CpuExecutionContext<'_> {
151 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
152 formatter
153 .debug_struct("CpuExecutionContext")
154 .field("domain_id", &self.domain_id())
155 .field("cpus", &self.cpus())
156 .field("thread_budget", &self.thread_budget())
157 .field("placement_guarantee", &self.placement_guarantee())
158 .field("parallel_mode", &self.parallel_mode())
159 .finish_non_exhaustive()
160 }
161}
162
163impl<'a> CpuExecutionContext<'a> {
164 fn entered(domain: &'a CpuResourceDomain, parallel_mode: ParallelMode) -> Self {
165 Self {
166 domain,
167 parallel_mode,
168 }
169 }
170
171 pub fn domain_id(&self) -> CpuDomainId {
182 self.domain.id()
183 }
184
185 pub fn cpus(&self) -> &CpuSet {
196 self.domain.cpus()
197 }
198
199 pub fn thread_budget(&self) -> NonZeroUsize {
210 self.domain.thread_budget()
211 }
212
213 pub fn placement_guarantee(&self) -> CpuPlacementGuarantee {
224 self.domain.placement_guarantee()
225 }
226
227 pub fn parallel_mode(&self) -> ParallelMode {
244 self.parallel_mode
245 }
246
247 #[doc(hidden)]
286 pub fn with_materialized_tensor_read<R>(
287 &self,
288 buffers: &mut BufferPool,
289 op: &'static str,
290 input: TensorRead<'_>,
291 operation: impl FnOnce(&Tensor, &mut BufferPool) -> tenferro_tensor::Result<R>,
292 ) -> tenferro_tensor::Result<R> {
293 match input {
294 TensorRead::Tensor(tensor) => operation(tensor, buffers),
295 TensorRead::View(view) => {
296 let materialized = self.with_native_parallelism(|| {
297 crate::materialize_tensor_read(buffers, op, TensorRead::View(view))
298 })?;
299 let result = operation(&materialized, buffers);
300 reclaim_tensor(buffers, materialized);
301 result
302 }
303 }
304 }
305
306 #[doc(hidden)]
332 pub fn reshape_tensor(
333 &self,
334 input: &Tensor,
335 shape: &[usize],
336 ) -> tenferro_tensor::Result<Tensor> {
337 crate::structural::reshape(input, shape)
338 }
339
340 #[cfg(feature = "cpu-faer")]
356 #[doc(hidden)]
357 pub fn faer_parallelism(self) -> faer::Par {
358 match (
359 self.parallel_mode,
360 self.domain.executor_capabilities().inner_parallelism,
361 ) {
362 (ParallelMode::Inner, CpuInnerParallelism::Rayon) if self.thread_budget().get() > 1 => {
363 faer::Par::rayon(self.thread_budget().get())
364 }
365 _ => faer::Par::Seq,
366 }
367 }
368
369 pub(crate) fn strided_exec_context(&self) -> strided_kernel::ExecContext {
370 match (
371 self.parallel_mode,
372 self.domain.executor_capabilities().inner_parallelism,
373 ) {
374 (ParallelMode::Inner, CpuInnerParallelism::Rayon) if self.thread_budget().get() > 1 => {
375 match strided_kernel::ExecContext::max_threads(self.thread_budget().get()) {
379 Ok(context) => context,
380 Err(_) => unreachable!("CpuExecutionContext has a non-zero thread budget"),
383 }
384 }
385 _ => strided_kernel::ExecContext::serial(),
386 }
387 }
388
389 pub(crate) fn with_native_parallelism<R>(&self, operation: impl FnOnce() -> R) -> R {
390 let policy = match (
391 self.parallel_mode,
392 self.domain.executor_capabilities().inner_parallelism,
393 ) {
394 (ParallelMode::Inner, CpuInnerParallelism::Rayon) if self.thread_budget().get() > 1 => {
395 strided_kernel::ExecutionPolicy::Rayon {
396 max_threads: self.thread_budget(),
397 }
398 }
399 _ => strided_kernel::ExecutionPolicy::Sequential,
400 };
401 strided_kernel::with_execution_policy(policy, operation)
402 }
403}
404
405fn reclaim_tensor(buffers: &mut BufferPool, tensor: Tensor) {
406 match tensor {
407 Tensor::F32(tensor) => crate::backend::reclaim_typed(buffers, tensor),
408 Tensor::F64(tensor) => crate::backend::reclaim_typed(buffers, tensor),
409 Tensor::I32(tensor) => crate::backend::reclaim_typed(buffers, tensor),
410 Tensor::I64(tensor) => crate::backend::reclaim_typed(buffers, tensor),
411 Tensor::Bool(tensor) => crate::backend::reclaim_typed(buffers, tensor),
412 Tensor::C32(tensor) => crate::backend::reclaim_typed(buffers, tensor),
413 Tensor::C64(tensor) => crate::backend::reclaim_typed(buffers, tensor),
414 }
415}
416
417#[derive(Clone, Copy)]
423pub(crate) struct CpuOperationEntry<'a> {
424 domain: &'a CpuResourceDomain,
425 permit: &'a ResourcePermit,
426}
427
428impl<'a> CpuOperationEntry<'a> {
429 pub(crate) fn new(domain: &'a CpuResourceDomain, permit: &'a ResourcePermit) -> Self {
430 Self { domain, permit }
431 }
432
433 pub(crate) fn domain_id(self) -> CpuDomainId {
434 self.domain.id()
435 }
436
437 pub(crate) fn enter<R: Send>(
438 self,
439 parallel_mode: ParallelMode,
440 operation: impl FnOnce(&CpuExecutionContext<'_>) -> R + Send,
441 ) -> Result<R, CpuDomainExecutorError> {
442 if parallel_mode == ParallelMode::Outer {
443 return Err(CpuDomainExecutorError::Scheduling {
444 message: "CPU executor install requires Sequential or Inner mode, got Outer"
445 .to_owned(),
446 });
447 }
448 let owner = self.permit.owner();
449 with_execution_owner(owner, || {
450 install_scoped(self.domain.executor().as_ref(), || {
451 with_execution_owner(owner, || {
452 let context = CpuExecutionContext::entered(self.domain, parallel_mode);
453 operation(&context)
454 })
455 })
456 })
457 }
458
459 pub(crate) fn enter_or_reuse<R: Send>(
460 self,
461 entered: Option<&CpuExecutionContext<'_>>,
462 parallel_mode: ParallelMode,
463 operation: impl FnOnce(&CpuExecutionContext<'_>) -> R + Send,
464 ) -> Result<R, CpuDomainExecutorError> {
465 let Some(entered) = entered else {
466 return self.enter(parallel_mode, operation);
467 };
468 if parallel_mode == ParallelMode::Outer {
469 return Err(CpuDomainExecutorError::Scheduling {
470 message: "entered CPU session requires Sequential or Inner mode, got Outer"
471 .to_owned(),
472 });
473 }
474 if entered.domain_id() != self.domain.id() {
475 return Err(CpuDomainExecutorError::Scheduling {
476 message: format!(
477 "entered CPU session domain {:?} does not match operation domain {:?}",
478 entered.domain_id(),
479 self.domain.id()
480 ),
481 });
482 }
483 let owner = self.permit.owner();
484 Ok(with_execution_owner(owner, || {
485 let context = CpuExecutionContext::entered(self.domain, parallel_mode);
486 operation(&context)
487 }))
488 }
489
490 pub(crate) fn supports_infallible_session_entry(self) -> bool {
491 self.domain.ownership() == crate::CpuDomainOwnership::Managed
492 }
493
494 pub(crate) fn enter_managed_session<R: Send>(
495 self,
496 operation: impl FnOnce(CpuExecutionContext<'a>) -> R + Send,
497 ) -> R {
498 assert!(
499 self.supports_infallible_session_entry(),
500 "managed session entry requires a Tenferro-managed CPU domain"
501 );
502 let mode = self.preferred_engine_mode();
503 self.enter(mode, |_| {
504 operation(CpuExecutionContext::entered(self.domain, mode))
505 })
506 .unwrap_or_else(|error| {
507 panic!("Tenferro-managed CPU executor violated synchronous install contract: {error}")
508 })
509 }
510
511 pub(crate) fn submit_outer(
512 self,
513 len: usize,
514 operation: impl Fn(usize, &CpuExecutionContext<'_>) -> Result<(), CpuDomainExecutorError> + Sync,
515 ) -> Result<(), CpuDomainExecutorError> {
516 if !self.supports_outer() {
517 return Err(CpuDomainExecutorError::Scheduling {
518 message: format!(
519 "CPU domain {:?} does not support Outer mode",
520 self.domain.id()
521 ),
522 });
523 }
524 let owner = self.permit.owner();
525 let lane_count = len.min(self.domain.thread_budget().get());
526 let jobs = indexed_jobs(lane_count, |lane| {
527 let mut index = lane;
531 while index < len {
532 with_execution_owner(owner, || {
533 let context =
534 CpuExecutionContext::entered(self.domain, ParallelMode::Sequential);
535 operation(index, &context)
536 })?;
537 let Some(next) = index.checked_add(lane_count) else {
538 break;
539 };
540 index = next;
541 }
542 Ok(())
543 });
544 with_execution_owner(owner, || self.domain.executor().submit(&jobs))?;
545 if let Some(index) = jobs.invalid_index_attempt() {
546 return Err(CpuDomainExecutorError::Scheduling {
547 message: format!(
548 "executor requested scoped CPU lane index {index}, but the submission has {lane_count} lanes for {len} logical jobs"
549 ),
550 });
551 }
552 Ok(())
553 }
554
555 pub(crate) fn preferred_engine_mode(self) -> ParallelMode {
556 if self.domain.thread_budget().get() > 1
557 && self.domain.executor_capabilities().inner_parallelism == CpuInnerParallelism::Rayon
558 {
559 ParallelMode::Inner
560 } else {
561 ParallelMode::Sequential
562 }
563 }
564
565 pub(crate) fn preferred_provider_mode(
566 self,
567 accepts: impl Fn(ParallelMode) -> bool,
568 ) -> Result<ParallelMode, crate::CpuProviderDomainError> {
569 if self.domain.thread_budget().get() == 1 {
570 return if accepts(ParallelMode::Sequential) {
571 Ok(ParallelMode::Sequential)
572 } else {
573 Err(crate::CpuProviderDomainError::ParallelModeNotSupported {
574 mode: ParallelMode::Sequential,
575 })
576 };
577 }
578 if accepts(ParallelMode::Inner) {
579 return Ok(ParallelMode::Inner);
580 }
581 if accepts(ParallelMode::Sequential) {
582 return Ok(ParallelMode::Sequential);
583 }
584 Err(crate::CpuProviderDomainError::ParallelModeNotSupported {
585 mode: ParallelMode::Inner,
586 })
587 }
588
589 pub(crate) fn preferred_linalg_mode(self, kind: CpuBackendKind) -> ParallelMode {
590 if self.domain.thread_budget().get() == 1 {
591 return ParallelMode::Sequential;
592 }
593 match kind {
594 CpuBackendKind::Faer => self.preferred_engine_mode(),
595 CpuBackendKind::Blas => ParallelMode::Inner,
599 }
600 }
601
602 pub(crate) fn provider_default_compatibility_mode(self) -> ParallelMode {
603 if self.domain.thread_budget().get() == 1 {
604 ParallelMode::Sequential
605 } else {
606 ParallelMode::Inner
607 }
608 }
609
610 pub(crate) fn thread_budget(self) -> NonZeroUsize {
611 self.domain.thread_budget()
612 }
613
614 pub(crate) fn supports_outer(self) -> bool {
615 self.domain.thread_budget().get() > 1
616 && self.domain.executor_capabilities().outer_parallelism
617 }
618}
619
620#[derive(Clone, Copy, Debug, PartialEq, Eq)]
633pub struct CpuBatchedMatrixLayout {
634 offset: isize,
635 row_stride: isize,
636 column_stride: isize,
637 batch_stride: isize,
638}
639
640impl CpuBatchedMatrixLayout {
641 #[allow(dead_code)]
642 pub(crate) fn new(
643 offset: isize,
644 row_stride: isize,
645 column_stride: isize,
646 batch_stride: isize,
647 ) -> Self {
648 Self {
649 offset,
650 row_stride,
651 column_stride,
652 batch_stride,
653 }
654 }
655
656 pub fn offset(self) -> isize {
658 self.offset
659 }
660
661 pub fn row_stride(self) -> isize {
663 self.row_stride
664 }
665
666 pub fn column_stride(self) -> isize {
668 self.column_stride
669 }
670
671 pub fn batch_stride(self) -> isize {
673 self.batch_stride
674 }
675}
676
677#[derive(Debug)]
691pub struct CpuGemmRequest<'request, 'input, 'output> {
692 lhs: &'request TensorRead<'input>,
693 rhs: &'request TensorRead<'input>,
694 output: &'request mut TensorWrite<'output>,
695 rows: usize,
696 columns: usize,
697 contracted: usize,
698 batch_count: usize,
699 lhs_layout: CpuBatchedMatrixLayout,
700 rhs_layout: CpuBatchedMatrixLayout,
701 output_layout: CpuBatchedMatrixLayout,
702 accumulation: DotGeneralAccumulation,
703}
704
705pub(crate) struct CpuGemmRequestParts<'request, 'input, 'output> {
706 pub(crate) lhs: &'request TensorRead<'input>,
707 pub(crate) rhs: &'request TensorRead<'input>,
708 pub(crate) output: &'request mut TensorWrite<'output>,
709 pub(crate) rows: usize,
710 pub(crate) columns: usize,
711 pub(crate) contracted: usize,
712 pub(crate) batch_count: usize,
713 pub(crate) lhs_layout: CpuBatchedMatrixLayout,
714 pub(crate) rhs_layout: CpuBatchedMatrixLayout,
715 pub(crate) output_layout: CpuBatchedMatrixLayout,
716 pub(crate) accumulation: DotGeneralAccumulation,
717}
718
719impl<'request, 'input, 'output> CpuGemmRequest<'request, 'input, 'output> {
720 #[allow(clippy::too_many_arguments, dead_code)]
721 pub(crate) fn new(
722 lhs: &'request TensorRead<'input>,
723 rhs: &'request TensorRead<'input>,
724 output: &'request mut TensorWrite<'output>,
725 rows: usize,
726 columns: usize,
727 contracted: usize,
728 batch_count: usize,
729 lhs_layout: CpuBatchedMatrixLayout,
730 rhs_layout: CpuBatchedMatrixLayout,
731 output_layout: CpuBatchedMatrixLayout,
732 accumulation: DotGeneralAccumulation,
733 ) -> Self {
734 Self {
735 lhs,
736 rhs,
737 output,
738 rows,
739 columns,
740 contracted,
741 batch_count,
742 lhs_layout,
743 rhs_layout,
744 output_layout,
745 accumulation,
746 }
747 }
748
749 pub fn lhs(&self) -> &TensorRead<'input> {
751 self.lhs
752 }
753
754 pub fn rhs(&self) -> &TensorRead<'input> {
756 self.rhs
757 }
758
759 pub fn output(&mut self) -> &mut TensorWrite<'output> {
761 self.output
762 }
763
764 pub fn rows(&self) -> usize {
766 self.rows
767 }
768
769 pub fn columns(&self) -> usize {
771 self.columns
772 }
773
774 pub fn contracted(&self) -> usize {
776 self.contracted
777 }
778
779 pub fn batch_count(&self) -> usize {
781 self.batch_count
782 }
783
784 pub fn lhs_layout(&self) -> CpuBatchedMatrixLayout {
786 self.lhs_layout
787 }
788
789 pub fn rhs_layout(&self) -> CpuBatchedMatrixLayout {
791 self.rhs_layout
792 }
793
794 pub fn output_layout(&self) -> CpuBatchedMatrixLayout {
796 self.output_layout
797 }
798
799 pub fn accumulation(&self) -> DotGeneralAccumulation {
801 self.accumulation
802 }
803
804 pub(crate) fn into_parts(self) -> CpuGemmRequestParts<'request, 'input, 'output> {
805 CpuGemmRequestParts {
806 lhs: self.lhs,
807 rhs: self.rhs,
808 output: self.output,
809 rows: self.rows,
810 columns: self.columns,
811 contracted: self.contracted,
812 batch_count: self.batch_count,
813 lhs_layout: self.lhs_layout,
814 rhs_layout: self.rhs_layout,
815 output_layout: self.output_layout,
816 accumulation: self.accumulation,
817 }
818 }
819}
820
821#[derive(Debug)]
832pub struct CpuGroupedGemmRequest<'request, 'input, 'output> {
833 lhs: &'request TensorRead<'input>,
834 rhs: &'request TensorRead<'input>,
835 output: &'request mut TensorWrite<'output>,
836 jobs: &'request [GroupedGemmJob],
837 accumulation: DotGeneralAccumulation,
838}
839
840impl<'request, 'input, 'output> CpuGroupedGemmRequest<'request, 'input, 'output> {
841 #[allow(dead_code)]
842 pub(crate) fn new(
843 lhs: &'request TensorRead<'input>,
844 rhs: &'request TensorRead<'input>,
845 output: &'request mut TensorWrite<'output>,
846 jobs: &'request [GroupedGemmJob],
847 accumulation: DotGeneralAccumulation,
848 ) -> Self {
849 Self {
850 lhs,
851 rhs,
852 output,
853 jobs,
854 accumulation,
855 }
856 }
857
858 pub fn lhs(&self) -> &TensorRead<'input> {
860 self.lhs
861 }
862
863 pub fn rhs(&self) -> &TensorRead<'input> {
865 self.rhs
866 }
867
868 pub fn output(&mut self) -> &mut TensorWrite<'output> {
870 self.output
871 }
872
873 pub fn jobs(&self) -> &[GroupedGemmJob] {
875 self.jobs
876 }
877
878 pub fn accumulation(&self) -> DotGeneralAccumulation {
880 self.accumulation
881 }
882
883 pub(crate) fn into_parts(
884 self,
885 ) -> (
886 &'request TensorRead<'input>,
887 &'request TensorRead<'input>,
888 &'request mut TensorWrite<'output>,
889 &'request [GroupedGemmJob],
890 DotGeneralAccumulation,
891 ) {
892 (
893 self.lhs,
894 self.rhs,
895 self.output,
896 self.jobs,
897 self.accumulation,
898 )
899 }
900}
901
902#[derive(Clone, Copy, Debug, PartialEq, Eq)]
914pub enum CpuLayoutTransformIntent {
915 CanonicalColumnMajor,
917}
918
919#[derive(Debug)]
930pub struct CpuLayoutTransformRequest<'request, 'input, 'output> {
931 input: &'request TensorRead<'input>,
932 output: &'request mut TensorWrite<'output>,
933 intent: CpuLayoutTransformIntent,
934 conjugate: bool,
935}
936
937impl<'request, 'input, 'output> CpuLayoutTransformRequest<'request, 'input, 'output> {
938 #[allow(dead_code)]
939 pub(crate) fn new(
940 input: &'request TensorRead<'input>,
941 output: &'request mut TensorWrite<'output>,
942 intent: CpuLayoutTransformIntent,
943 conjugate: bool,
944 ) -> Self {
945 Self {
946 input,
947 output,
948 intent,
949 conjugate,
950 }
951 }
952
953 pub fn input(&self) -> &TensorRead<'input> {
955 self.input
956 }
957
958 pub fn output(&mut self) -> &mut TensorWrite<'output> {
960 self.output
961 }
962
963 pub fn intent(&self) -> CpuLayoutTransformIntent {
965 self.intent
966 }
967
968 pub fn conjugate(&self) -> bool {
979 self.conjugate
980 }
981
982 pub(crate) fn into_parts(
983 self,
984 ) -> (
985 &'request TensorRead<'input>,
986 &'request mut TensorWrite<'output>,
987 CpuLayoutTransformIntent,
988 bool,
989 ) {
990 (self.input, self.output, self.intent, self.conjugate)
991 }
992}
993
994#[derive(Clone, Copy, Debug)]
1005pub struct CpuContractionAxes<'a> {
1006 lhs_rank: usize,
1007 rhs_rank: usize,
1008 lhs_contracting: &'a [usize],
1009 rhs_contracting: &'a [usize],
1010 lhs_batch: &'a [usize],
1011 rhs_batch: &'a [usize],
1012 lhs_role_mask: Option<u64>,
1013 rhs_role_mask: Option<u64>,
1014}
1015
1016impl<'a> CpuContractionAxes<'a> {
1017 #[allow(clippy::too_many_arguments, dead_code)]
1018 pub(crate) fn new(
1019 lhs_rank: usize,
1020 rhs_rank: usize,
1021 lhs_contracting: &'a [usize],
1022 rhs_contracting: &'a [usize],
1023 lhs_batch: &'a [usize],
1024 rhs_batch: &'a [usize],
1025 lhs_role_mask: Option<u64>,
1026 rhs_role_mask: Option<u64>,
1027 ) -> Self {
1028 Self {
1029 lhs_rank,
1030 rhs_rank,
1031 lhs_contracting,
1032 rhs_contracting,
1033 lhs_batch,
1034 rhs_batch,
1035 lhs_role_mask,
1036 rhs_role_mask,
1037 }
1038 }
1039
1040 pub fn contracting_pairs(&self) -> impl ExactSizeIterator<Item = (usize, usize)> + '_ {
1042 self.lhs_contracting
1043 .iter()
1044 .copied()
1045 .zip(self.rhs_contracting.iter().copied())
1046 }
1047
1048 pub fn batch_pairs(&self) -> impl ExactSizeIterator<Item = (usize, usize)> + '_ {
1050 self.lhs_batch
1051 .iter()
1052 .copied()
1053 .zip(self.rhs_batch.iter().copied())
1054 }
1055
1056 pub fn lhs_free_axes(&self) -> impl Iterator<Item = usize> + '_ {
1058 (0..self.lhs_rank).filter(move |&axis| !self.lhs_axis_has_role(axis))
1059 }
1060
1061 pub fn rhs_free_axes(&self) -> impl Iterator<Item = usize> + '_ {
1063 (0..self.rhs_rank).filter(move |&axis| !self.rhs_axis_has_role(axis))
1064 }
1065
1066 fn lhs_axis_has_role(&self, axis: usize) -> bool {
1067 self.lhs_role_mask.map_or_else(
1068 || self.lhs_contracting.contains(&axis) || self.lhs_batch.contains(&axis),
1069 |mask| mask & (1_u64 << axis) != 0,
1070 )
1071 }
1072
1073 fn rhs_axis_has_role(&self, axis: usize) -> bool {
1074 self.rhs_role_mask.map_or_else(
1075 || self.rhs_contracting.contains(&axis) || self.rhs_batch.contains(&axis),
1076 |mask| mask & (1_u64 << axis) != 0,
1077 )
1078 }
1079}
1080
1081#[derive(Debug)]
1092pub struct CpuDotGeneralRequest<'request, 'input, 'output> {
1093 lhs: &'request TensorRead<'input>,
1094 rhs: &'request TensorRead<'input>,
1095 output: &'request mut TensorWrite<'output>,
1096 axes: CpuContractionAxes<'request>,
1097 accumulation: DotGeneralAccumulation,
1098}
1099
1100impl<'request, 'input, 'output> CpuDotGeneralRequest<'request, 'input, 'output> {
1101 #[allow(dead_code)]
1102 pub(crate) fn new(
1103 lhs: &'request TensorRead<'input>,
1104 rhs: &'request TensorRead<'input>,
1105 output: &'request mut TensorWrite<'output>,
1106 axes: CpuContractionAxes<'request>,
1107 accumulation: DotGeneralAccumulation,
1108 ) -> Self {
1109 Self {
1110 lhs,
1111 rhs,
1112 output,
1113 axes,
1114 accumulation,
1115 }
1116 }
1117
1118 pub fn lhs(&self) -> &TensorRead<'input> {
1120 self.lhs
1121 }
1122
1123 pub fn rhs(&self) -> &TensorRead<'input> {
1125 self.rhs
1126 }
1127
1128 pub fn output(&mut self) -> &mut TensorWrite<'output> {
1130 self.output
1131 }
1132
1133 pub fn axes(&self) -> &CpuContractionAxes<'request> {
1135 &self.axes
1136 }
1137
1138 pub fn accumulation(&self) -> DotGeneralAccumulation {
1140 self.accumulation
1141 }
1142
1143 pub fn into_parts(
1159 self,
1160 ) -> (
1161 &'request TensorRead<'input>,
1162 &'request TensorRead<'input>,
1163 &'request mut TensorWrite<'output>,
1164 CpuContractionAxes<'request>,
1165 DotGeneralAccumulation,
1166 ) {
1167 (
1168 self.lhs,
1169 self.rhs,
1170 self.output,
1171 self.axes,
1172 self.accumulation,
1173 )
1174 }
1175}
1176
1177pub trait CpuGemmProvider: fmt::Debug + Send + Sync + 'static {
1188 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities;
1196
1197 fn gemm(
1207 &self,
1208 context: &CpuExecutionContext<'_>,
1209 request: CpuGemmRequest<'_, '_, '_>,
1210 ) -> tenferro_tensor::Result<CpuProviderOutcome>;
1211
1212 fn strided_batched_gemm(
1222 &self,
1223 context: &CpuExecutionContext<'_>,
1224 request: CpuGemmRequest<'_, '_, '_>,
1225 ) -> tenferro_tensor::Result<CpuProviderOutcome>;
1226
1227 fn grouped_gemm(
1237 &self,
1238 context: &CpuExecutionContext<'_>,
1239 request: CpuGroupedGemmRequest<'_, '_, '_>,
1240 ) -> tenferro_tensor::Result<CpuProviderOutcome>;
1241}
1242
1243pub trait CpuLayoutTransformProvider: fmt::Debug + Send + Sync + 'static {
1252 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities;
1257
1258 fn materialize(
1268 &self,
1269 context: &CpuExecutionContext<'_>,
1270 request: CpuLayoutTransformRequest<'_, '_, '_>,
1271 ) -> tenferro_tensor::Result<CpuProviderOutcome>;
1272}
1273
1274pub trait CpuGeneralContractionProvider: fmt::Debug + Send + Sync + 'static {
1283 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities;
1288
1289 fn dot_general(
1300 &self,
1301 context: &CpuExecutionContext<'_>,
1302 request: CpuDotGeneralRequest<'_, '_, '_>,
1303 ) -> tenferro_tensor::Result<CpuProviderOutcome>;
1304}
1305
1306#[derive(Clone, Copy, Debug, Default)]
1316pub struct FaerGemmProvider;
1317
1318impl CpuGemmProvider for FaerGemmProvider {
1319 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities {
1320 engine_worker_capabilities()
1321 }
1322
1323 fn gemm(
1324 &self,
1325 context: &CpuExecutionContext<'_>,
1326 request: CpuGemmRequest<'_, '_, '_>,
1327 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1328 #[cfg(feature = "cpu-faer")]
1329 {
1330 crate::gemm::execute_faer_gemm_request(context, request)
1331 }
1332 #[cfg(not(feature = "cpu-faer"))]
1333 {
1334 let _ = (context, request);
1335 Ok(CpuProviderOutcome::Unsupported(
1336 CpuProviderUnsupported::RuntimeUnavailable,
1337 ))
1338 }
1339 }
1340
1341 fn strided_batched_gemm(
1342 &self,
1343 context: &CpuExecutionContext<'_>,
1344 request: CpuGemmRequest<'_, '_, '_>,
1345 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1346 self.gemm(context, request)
1347 }
1348
1349 fn grouped_gemm(
1350 &self,
1351 context: &CpuExecutionContext<'_>,
1352 request: CpuGroupedGemmRequest<'_, '_, '_>,
1353 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1354 #[cfg(feature = "cpu-faer")]
1355 {
1356 crate::gemm::execute_faer_grouped_request(context, request)
1357 }
1358 #[cfg(not(feature = "cpu-faer"))]
1359 {
1360 let _ = (context, request);
1361 Ok(CpuProviderOutcome::Unsupported(
1362 CpuProviderUnsupported::RuntimeUnavailable,
1363 ))
1364 }
1365 }
1366}
1367
1368#[derive(Clone, Copy, Debug, Default)]
1378pub struct BlasGemmProvider;
1379
1380impl CpuGemmProvider for BlasGemmProvider {
1381 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities {
1382 #[cfg(feature = "cpu-blas")]
1383 {
1384 builtin_blas_execution_capabilities()
1385 }
1386 #[cfg(not(feature = "cpu-blas"))]
1387 {
1388 serial_capabilities()
1390 }
1391 }
1392
1393 fn gemm(
1394 &self,
1395 context: &CpuExecutionContext<'_>,
1396 request: CpuGemmRequest<'_, '_, '_>,
1397 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1398 #[cfg(feature = "cpu-blas")]
1399 {
1400 crate::gemm::execute_blas_gemm_request(context, request)
1401 }
1402 #[cfg(not(feature = "cpu-blas"))]
1403 {
1404 let _ = (context, request);
1405 Ok(CpuProviderOutcome::Unsupported(
1406 CpuProviderUnsupported::RuntimeUnavailable,
1407 ))
1408 }
1409 }
1410
1411 fn strided_batched_gemm(
1412 &self,
1413 context: &CpuExecutionContext<'_>,
1414 request: CpuGemmRequest<'_, '_, '_>,
1415 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1416 self.gemm(context, request)
1417 }
1418
1419 fn grouped_gemm(
1420 &self,
1421 context: &CpuExecutionContext<'_>,
1422 request: CpuGroupedGemmRequest<'_, '_, '_>,
1423 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1424 #[cfg(feature = "cpu-blas")]
1425 {
1426 crate::gemm::execute_blas_grouped_request(context, request)
1427 }
1428 #[cfg(not(feature = "cpu-blas"))]
1429 {
1430 let _ = (context, request);
1431 Ok(CpuProviderOutcome::Unsupported(
1432 CpuProviderUnsupported::RuntimeUnavailable,
1433 ))
1434 }
1435 }
1436}
1437
1438#[derive(Clone, Copy, Debug, Default)]
1448pub struct StridedLayoutTransformProvider;
1449
1450impl CpuLayoutTransformProvider for StridedLayoutTransformProvider {
1451 fn execution_capabilities(&self) -> CpuProviderExecutionCapabilities {
1452 engine_worker_capabilities()
1453 }
1454
1455 fn materialize(
1456 &self,
1457 context: &CpuExecutionContext<'_>,
1458 request: CpuLayoutTransformRequest<'_, '_, '_>,
1459 ) -> tenferro_tensor::Result<CpuProviderOutcome> {
1460 context.with_native_parallelism(|| materialize_strided_layout(request))
1461 }
1462}
1463
1464fn materialize_strided_layout(
1465 request: CpuLayoutTransformRequest<'_, '_, '_>,
1466) -> tenferro_tensor::Result<CpuProviderOutcome> {
1467 let (input, output, _intent, conjugate) = request.into_parts();
1468 if conjugate {
1469 macro_rules! dispatch_conjugated {
1470 ($owned:ident, $view:ident) => {
1471 match (input, &mut *output) {
1472 (
1473 TensorRead::Tensor(Tensor::$owned(input)),
1474 TensorWrite::Tensor(Tensor::$owned(output)),
1475 ) => {
1476 let input = input.as_view();
1477 let mut output = output.as_view_mut();
1478 crate::structural::typed_conjugate_view_into(
1479 &input,
1480 &mut output,
1481 "cpu layout materialization",
1482 )?;
1483 return Ok(CpuProviderOutcome::Executed);
1484 }
1485 (
1486 TensorRead::View(TensorView::$view(input)),
1487 TensorWrite::Tensor(Tensor::$owned(output)),
1488 ) => {
1489 let mut output = output.as_view_mut();
1490 crate::structural::typed_conjugate_view_into(
1491 input,
1492 &mut output,
1493 "cpu layout materialization",
1494 )?;
1495 return Ok(CpuProviderOutcome::Executed);
1496 }
1497 (
1498 TensorRead::Tensor(Tensor::$owned(input)),
1499 TensorWrite::View(TensorViewMut::$view(output)),
1500 ) => {
1501 let input = input.as_view();
1502 crate::structural::typed_conjugate_view_into(
1503 &input,
1504 output,
1505 "cpu layout materialization",
1506 )?;
1507 return Ok(CpuProviderOutcome::Executed);
1508 }
1509 (
1510 TensorRead::View(TensorView::$view(input)),
1511 TensorWrite::View(TensorViewMut::$view(output)),
1512 ) => {
1513 crate::structural::typed_conjugate_view_into(
1514 input,
1515 output,
1516 "cpu layout materialization",
1517 )?;
1518 return Ok(CpuProviderOutcome::Executed);
1519 }
1520 _ => {}
1521 }
1522 };
1523 }
1524 dispatch_conjugated!(F32, F32);
1525 dispatch_conjugated!(F64, F64);
1526 dispatch_conjugated!(C32, C32);
1527 dispatch_conjugated!(C64, C64);
1528 return Ok(CpuProviderOutcome::Unsupported(
1529 CpuProviderUnsupported::DType(input.dtype()),
1530 ));
1531 }
1532 macro_rules! dispatch {
1533 ($owned:ident, $view:ident) => {
1534 match (input, &mut *output) {
1535 (
1536 TensorRead::Tensor(Tensor::$owned(input)),
1537 TensorWrite::Tensor(Tensor::$owned(output)),
1538 ) => {
1539 let input = input.as_view();
1540 let mut output = output.as_view_mut();
1541 crate::structural::typed_copy_view_into(
1542 &input,
1543 &mut output,
1544 "cpu layout materialization",
1545 )?;
1546 return Ok(CpuProviderOutcome::Executed);
1547 }
1548 (
1549 TensorRead::View(TensorView::$view(input)),
1550 TensorWrite::Tensor(Tensor::$owned(output)),
1551 ) => {
1552 let mut output = output.as_view_mut();
1553 crate::structural::typed_copy_view_into(
1554 input,
1555 &mut output,
1556 "cpu layout materialization",
1557 )?;
1558 return Ok(CpuProviderOutcome::Executed);
1559 }
1560 (
1561 TensorRead::Tensor(Tensor::$owned(input)),
1562 TensorWrite::View(TensorViewMut::$view(output)),
1563 ) => {
1564 let input = input.as_view();
1565 crate::structural::typed_copy_view_into(
1566 &input,
1567 output,
1568 "cpu layout materialization",
1569 )?;
1570 return Ok(CpuProviderOutcome::Executed);
1571 }
1572 (
1573 TensorRead::View(TensorView::$view(input)),
1574 TensorWrite::View(TensorViewMut::$view(output)),
1575 ) => {
1576 crate::structural::typed_copy_view_into(
1577 input,
1578 output,
1579 "cpu layout materialization",
1580 )?;
1581 return Ok(CpuProviderOutcome::Executed);
1582 }
1583 _ => {}
1584 }
1585 };
1586 }
1587 dispatch!(F32, F32);
1588 dispatch!(F64, F64);
1589 dispatch!(I32, I32);
1590 dispatch!(I64, I64);
1591 dispatch!(Bool, Bool);
1592 dispatch!(C32, C32);
1593 dispatch!(C64, C64);
1594 Ok(CpuProviderOutcome::Unsupported(
1595 CpuProviderUnsupported::DType(input.dtype()),
1596 ))
1597}
1598
1599pub(crate) fn builtin_gemm_provider(kind: CpuBackendKind) -> Arc<dyn CpuGemmProvider> {
1600 match kind {
1601 CpuBackendKind::Faer => Arc::new(FaerGemmProvider),
1602 CpuBackendKind::Blas => Arc::new(BlasGemmProvider),
1603 }
1604}
1605
1606pub(crate) fn builtin_layout_provider() -> Arc<dyn CpuLayoutTransformProvider> {
1607 Arc::new(StridedLayoutTransformProvider)
1608}
1609
1610#[cfg(test)]
1611pub(crate) mod tests;