1use std::cmp::Reverse;
2use std::collections::HashMap;
3use std::env;
4use std::fmt;
5use std::sync::{Arc, Mutex, OnceLock};
6use std::thread;
7use std::time::{Duration, Instant};
8
9use crate::buffer_pool::{BufferPool, BufferPoolStats, PoolScalar};
10use crate::{
11 Buffer, CacheStats, Tensor, TensorRank, TensorRead, TensorValue, TensorWrite, TypedTensor,
12 TypedTensorView, TypedTensorViewMut,
13};
14use tenferro_tensor::backend::{
15 dot_general_accum_via_temp, grouped_gemm_via_sequential, validate_dot_general_accumulation,
16 validate_grouped_gemm, ElementwiseFusionPlan, GroupedGemmConfig,
17};
18use tenferro_tensor::{
19 BackendCachedDot, BackendRuntimeCache, BackendSession, BackendSessionHost,
20 DotGeneralAccumulation, TensorAnalytic, TensorBackend, TensorBuffer, TensorDeviceTransfer,
21 TensorDot, TensorElementwise, TensorFusion, TensorIndexing, TensorReduction, TensorStructural,
22 TensorViewCanonicalization,
23};
24use tenferro_tensor::{
25 CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
26};
27
28use super::exec_session::CpuExecSession;
29use super::{
30 analytic, elementwise, gemm, indexing, materialize_tensor_read, reduction, structural,
31 CpuContext,
32};
33
34#[derive(Debug, Default, Clone)]
35struct CpuSessionProfileEntry {
36 calls: usize,
37 total_time: Duration,
38}
39
40fn cpu_session_profile_enabled() -> bool {
41 static ENABLED: OnceLock<bool> = OnceLock::new();
42 *ENABLED.get_or_init(|| env::var("TENFERRO_PROFILE_CPU_SESSION").is_ok())
43}
44
45fn cpu_session_profile_print_every() -> Option<usize> {
46 static PRINT_EVERY: OnceLock<Option<usize>> = OnceLock::new();
47 *PRINT_EVERY.get_or_init(|| {
48 env::var("TENFERRO_PROFILE_CPU_SESSION_PRINT_EVERY")
49 .ok()
50 .and_then(|value| value.parse::<usize>().ok())
51 .filter(|&value| value > 0)
52 })
53}
54
55fn cpu_session_profile_state() -> &'static Mutex<HashMap<&'static str, CpuSessionProfileEntry>> {
56 static STATE: OnceLock<Mutex<HashMap<&'static str, CpuSessionProfileEntry>>> = OnceLock::new();
57 STATE.get_or_init(|| Mutex::new(HashMap::new()))
58}
59
60fn record_cpu_session_profile(section: &'static str, elapsed: Duration) {
61 if !cpu_session_profile_enabled() {
62 return;
63 }
64 let Ok(mut state) = cpu_session_profile_state().lock() else {
65 return;
66 };
67 let entry = state.entry(section).or_default();
68 entry.calls += 1;
69 entry.total_time += elapsed;
70}
71
72fn profile_cpu_session_section<T>(section: &'static str, f: impl FnOnce() -> T) -> T {
73 if !cpu_session_profile_enabled() {
74 return f();
75 }
76 let started = Instant::now();
77 let result = f();
78 record_cpu_session_profile(section, started.elapsed());
79 result
80}
81
82fn maybe_print_cpu_session_profile() {
83 let Some(print_every) = cpu_session_profile_print_every() else {
84 return;
85 };
86 let should_print = {
87 let Ok(state) = cpu_session_profile_state().lock() else {
88 return;
89 };
90 state
91 .get("with_backend_session_cached.total")
92 .is_some_and(|entry| entry.calls % print_every == 0)
93 };
94 if !should_print {
95 return;
96 }
97 let mut entries = {
98 let Ok(mut state) = cpu_session_profile_state().lock() else {
99 return;
100 };
101 let entries = state
102 .iter()
103 .map(|(section, entry)| (*section, entry.clone()))
104 .collect::<Vec<_>>();
105 state.clear();
106 entries
107 };
108 entries.sort_by_key(|(_, entry)| Reverse(entry.total_time));
109 eprintln!("=== tenferro CPU session profile ===");
110 for (section, entry) in entries {
111 eprintln!(
112 "{section}: calls={} total={:.6}ms per_call={:.3}us",
113 entry.calls,
114 entry.total_time.as_secs_f64() * 1.0e3,
115 entry.total_time.as_secs_f64() * 1.0e6 / entry.calls as f64,
116 );
117 }
118}
119
120struct BufferPoolLoan<'a> {
121 target: &'a mut BufferPool,
122 buffers: Option<BufferPool>,
123}
124
125impl<'a> BufferPoolLoan<'a> {
126 fn new(target: &'a mut BufferPool) -> Self {
127 Self {
128 buffers: Some(std::mem::take(target)),
129 target,
130 }
131 }
132
133 fn get_mut(&mut self) -> &mut BufferPool {
134 self.buffers
135 .as_mut()
136 .expect("buffer pool loan already restored")
137 }
138}
139
140impl Drop for BufferPoolLoan<'_> {
141 fn drop(&mut self) {
142 if let Some(buffers) = self.buffers.take() {
143 let mut buffers = buffers;
144 if thread::panicking() {
145 buffers.replenish_in_flight_retained();
146 } else {
147 buffers.clear_in_flight_retained();
148 }
149 *self.target = buffers;
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
169pub enum CpuBackendKind {
170 Faer,
172 Blas,
174}
175
176impl CpuBackendKind {
177 pub fn default_compiled() -> Self {
191 #[cfg(feature = "cpu-blas")]
192 {
193 Self::Blas
194 }
195 #[cfg(all(not(feature = "cpu-blas"), feature = "cpu-faer"))]
196 {
197 Self::Faer
198 }
199 }
200
201 #[allow(dead_code)]
204 pub(crate) fn name(self) -> &'static str {
205 match self {
206 Self::Faer => "faer",
207 Self::Blas => "blas",
208 }
209 }
210}
211
212fn ensure_cpu_backend_kind_available(kind: CpuBackendKind, op: &'static str) -> crate::Result<()> {
213 let _ = op;
214 match kind {
215 CpuBackendKind::Faer => {
216 #[cfg(feature = "cpu-faer")]
217 {
218 Ok(())
219 }
220 #[cfg(not(feature = "cpu-faer"))]
221 {
222 Err(crate::Error::InvalidConfig {
223 op,
224 message: "CpuBackendKind::Faer requires the cpu-faer feature".to_string(),
225 })
226 }
227 }
228 CpuBackendKind::Blas => {
229 #[cfg(feature = "cpu-blas")]
230 {
231 Ok(())
232 }
233 #[cfg(not(feature = "cpu-blas"))]
234 {
235 Err(crate::Error::InvalidConfig {
236 op,
237 message: "CpuBackendKind::Blas requires the cpu-blas feature".to_string(),
238 })
239 }
240 }
241 }
242}
243
244#[allow(dead_code)]
247pub(super) fn unavailable_cpu_backend_kind(kind: CpuBackendKind, op: &'static str) -> crate::Error {
248 crate::Error::InvalidConfig {
249 op,
250 message: format!("CPU backend kind {} is not compiled in", kind.name()),
251 }
252}
253
254pub struct CpuBackend {
264 pub(crate) ctx: Arc<CpuContext>,
265 pub(crate) buffers: BufferPool,
266 kind: CpuBackendKind,
267}
268
269impl fmt::Debug for CpuBackend {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 f.debug_struct("CpuBackend")
272 .field("kind", &self.kind)
273 .field("num_threads", &self.num_threads())
274 .field("buffer_pool_cache_stats", &self.buffer_pool_cache_stats())
275 .field("buffer_pool_limit_bytes", &self.buffer_pool_limit_bytes())
276 .finish_non_exhaustive()
277 }
278}
279
280impl CpuBackend {
281 pub fn new() -> Self {
291 Self::from_context(Arc::new(CpuContext::from_env()))
292 }
293
294 pub fn with_kind(kind: CpuBackendKind) -> crate::Result<Self> {
305 Self::try_from_context_with_kind(Arc::new(CpuContext::from_env()), kind)
306 }
307
308 pub fn try_new() -> crate::Result<Self> {
320 CpuContext::try_from_env().map(|ctx| Self::from_context(Arc::new(ctx)))
321 }
322
323 pub fn from_context(ctx: Arc<CpuContext>) -> Self {
336 Self {
337 ctx,
338 buffers: BufferPool::new(),
339 kind: CpuBackendKind::default_compiled(),
340 }
341 }
342
343 fn try_from_context_with_kind(
344 ctx: Arc<CpuContext>,
345 kind: CpuBackendKind,
346 ) -> crate::Result<Self> {
347 ensure_cpu_backend_kind_available(kind, "CpuBackend::with_kind")?;
348 Ok(Self {
349 ctx,
350 buffers: BufferPool::new(),
351 kind,
352 })
353 }
354
355 pub fn from_context_with_buffer_pool_limit(
371 ctx: Arc<CpuContext>,
372 max_retained_capacity_bytes: usize,
373 ) -> Self {
374 Self::from_context_with_buffer_pool_limit_and_kind(
375 ctx,
376 max_retained_capacity_bytes,
377 CpuBackendKind::default_compiled(),
378 )
379 }
380
381 fn from_context_with_buffer_pool_limit_and_kind(
382 ctx: Arc<CpuContext>,
383 max_retained_capacity_bytes: usize,
384 kind: CpuBackendKind,
385 ) -> Self {
386 Self {
387 ctx,
388 buffers: BufferPool::with_max_retained_capacity_bytes(max_retained_capacity_bytes),
389 kind,
390 }
391 }
392
393 pub fn with_threads(num_threads: usize) -> crate::Result<Self> {
408 CpuContext::with_threads(num_threads)
409 .map(|ctx| Self::from_context(Arc::new(ctx)))
410 .map_err(|err| match err {
411 crate::Error::InvalidConfig { message, .. } => crate::Error::InvalidConfig {
412 op: "CpuBackend::with_threads",
413 message,
414 },
415 crate::Error::BackendFailure { message, .. } => {
416 crate::Error::backend_failure("CpuBackend::with_threads", message)
417 }
418 err => err,
419 })
420 }
421
422 pub fn with_threads_and_kind(num_threads: usize, kind: CpuBackendKind) -> crate::Result<Self> {
442 ensure_cpu_backend_kind_available(kind, "CpuBackend::with_threads_and_kind")?;
443 CpuContext::with_threads(num_threads)
444 .map(|ctx| Self {
445 ctx: Arc::new(ctx),
446 buffers: BufferPool::new(),
447 kind,
448 })
449 .map_err(|err| match err {
450 crate::Error::InvalidConfig { message, .. } => crate::Error::InvalidConfig {
451 op: "CpuBackend::with_threads_and_kind",
452 message,
453 },
454 crate::Error::BackendFailure { message, .. } => {
455 crate::Error::backend_failure("CpuBackend::with_threads_and_kind", message)
456 }
457 err => err,
458 })
459 }
460
461 pub fn kind(&self) -> CpuBackendKind {
472 self.kind
473 }
474
475 pub fn num_threads(&self) -> usize {
486 self.ctx.num_threads()
487 }
488
489 pub fn buffer_pool_len(&self) -> usize {
500 self.buffers.len()
501 }
502
503 pub fn buffer_pool_stats(&self) -> BufferPoolStats {
516 self.buffers.stats()
517 }
518
519 pub fn buffer_pool_cache_stats(&self) -> CacheStats {
532 self.buffers.cache_stats()
533 }
534
535 pub fn buffer_pool_limit_bytes(&self) -> usize {
550 self.buffers.max_retained_capacity_bytes()
551 }
552
553 pub fn set_buffer_pool_limit_bytes(&mut self, max_retained_capacity_bytes: usize) {
569 self.buffers
570 .set_max_retained_capacity_bytes(max_retained_capacity_bytes);
571 }
572
573 pub fn reset_buffer_pool(&mut self) {
589 self.buffers.clear();
590 }
591
592 pub fn install<R: Send>(&self, op: impl FnOnce() -> R + Send) -> R {
604 self.ctx.install(op)
605 }
606
607 fn install_with_pool<R: Send>(&mut self, op: impl FnOnce(&mut BufferPool) -> R + Send) -> R {
608 let mut buffers = BufferPoolLoan::new(&mut self.buffers);
609 let ctx = Arc::clone(&self.ctx);
610 ctx.install(|| op(buffers.get_mut()))
611 }
612
613 #[allow(dead_code)]
616 fn run_with_pool<R>(&mut self, op: impl FnOnce(&mut BufferPool) -> R) -> R {
617 let mut buffers = BufferPoolLoan::new(&mut self.buffers);
618 op(buffers.get_mut())
619 }
620
621 fn linalg_with_pool<R: Send>(&mut self, op: impl FnOnce(&mut BufferPool) -> R + Send) -> R {
622 match self.kind {
623 CpuBackendKind::Faer => self.install_with_pool(op),
624 CpuBackendKind::Blas => self.run_with_pool(op),
625 }
626 }
627
628 #[doc(hidden)]
633 pub fn with_linalg_pool<R: Send>(&mut self, op: impl FnOnce(&mut BufferPool) -> R + Send) -> R {
634 self.linalg_with_pool(op)
635 }
636
637 #[cfg(feature = "cpu-faer")]
639 #[doc(hidden)]
640 pub fn linalg_context(&self) -> Arc<CpuContext> {
641 Arc::clone(&self.ctx)
642 }
643
644 #[allow(dead_code)]
647 fn install_with_pool_and_gemm_cache<R: Send>(
648 &mut self,
649 gemm_analysis_cache: &mut gemm::GemmAnalysisCache,
650 op: impl FnOnce(&mut BufferPool, &mut gemm::GemmAnalysisCache) -> R + Send,
651 ) -> R {
652 let mut buffers = BufferPoolLoan::new(&mut self.buffers);
653 let ctx = Arc::clone(&self.ctx);
654 ctx.install(|| op(buffers.get_mut(), gemm_analysis_cache))
655 }
656
657 #[allow(dead_code)]
660 fn run_with_pool_and_gemm_cache<R>(
661 &mut self,
662 gemm_analysis_cache: &mut gemm::GemmAnalysisCache,
663 op: impl FnOnce(&mut BufferPool, &mut gemm::GemmAnalysisCache) -> R,
664 ) -> R {
665 let mut buffers = BufferPoolLoan::new(&mut self.buffers);
666 op(buffers.get_mut(), gemm_analysis_cache)
667 }
668}
669
670impl BackendRuntimeCache for CpuBackend {
671 type RuntimeCache = gemm::GemmAnalysisCache;
672}
673
674impl TensorElementwise for CpuBackend {
675 fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
676 self.install_with_pool(|buffers| elementwise::add_with_pool(buffers, lhs, rhs))
677 }
678
679 fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
680 self.install_with_pool(|buffers| elementwise::add_read_with_pool(buffers, lhs, rhs))
681 }
682
683 fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
684 self.install_with_pool(|buffers| elementwise::sub_with_pool(buffers, lhs, rhs))
685 }
686
687 fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
688 self.install_with_pool(|buffers| elementwise::sub_read_with_pool(buffers, lhs, rhs))
689 }
690
691 fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
692 self.install_with_pool(|buffers| elementwise::mul_with_pool(buffers, lhs, rhs))
693 }
694
695 fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
696 self.install_with_pool(|buffers| elementwise::mul_read_with_pool(buffers, lhs, rhs))
697 }
698
699 fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor> {
700 self.install_with_pool(|buffers| elementwise::neg_with_pool(buffers, input))
701 }
702
703 fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
704 self.install_with_pool(|buffers| elementwise::neg_read_with_pool(buffers, input))
705 }
706
707 fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor> {
708 self.install_with_pool(|buffers| elementwise::conj_with_pool(buffers, input))
709 }
710
711 fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
712 self.install_with_pool(|buffers| elementwise::conj_read_with_pool(buffers, input))
713 }
714
715 fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
716 self.install_with_pool(|buffers| elementwise::div_with_pool(buffers, lhs, rhs))
717 }
718
719 fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
720 self.install_with_pool(|buffers| elementwise::div_read_with_pool(buffers, lhs, rhs))
721 }
722
723 fn rem(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
724 self.install_with_pool(|buffers| elementwise::rem_with_pool(buffers, lhs, rhs))
725 }
726
727 fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
728 self.install_with_pool(|buffers| elementwise::rem_read_with_pool(buffers, lhs, rhs))
729 }
730
731 fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor> {
732 self.install_with_pool(|buffers| elementwise::abs_with_pool(buffers, input))
733 }
734
735 fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
736 self.install_with_pool(|buffers| elementwise::abs_read_with_pool(buffers, input))
737 }
738
739 fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor> {
740 self.install_with_pool(|buffers| elementwise::sign_with_pool(buffers, input))
741 }
742
743 fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
744 self.install_with_pool(|buffers| elementwise::sign_read_with_pool(buffers, input))
745 }
746
747 fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
748 self.install_with_pool(|buffers| elementwise::maximum_with_pool(buffers, lhs, rhs))
749 }
750
751 fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
752 self.install_with_pool(|buffers| elementwise::maximum_read_with_pool(buffers, lhs, rhs))
753 }
754
755 fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
756 self.install_with_pool(|buffers| elementwise::minimum_with_pool(buffers, lhs, rhs))
757 }
758
759 fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
760 self.install_with_pool(|buffers| elementwise::minimum_read_with_pool(buffers, lhs, rhs))
761 }
762
763 fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor> {
764 self.install_with_pool(|buffers| elementwise::compare_with_pool(buffers, lhs, rhs, dir))
765 }
766
767 fn compare_read(
768 &mut self,
769 lhs: TensorRead<'_>,
770 rhs: TensorRead<'_>,
771 dir: &CompareDir,
772 ) -> crate::Result<Tensor> {
773 self.install_with_pool(|buffers| {
774 elementwise::compare_read_with_pool(buffers, lhs, rhs, dir)
775 })
776 }
777
778 fn select(
779 &mut self,
780 pred: &Tensor,
781 on_true: &Tensor,
782 on_false: &Tensor,
783 ) -> crate::Result<Tensor> {
784 self.install_with_pool(|buffers| {
785 elementwise::select_with_pool(buffers, pred, on_true, on_false)
786 })
787 }
788
789 fn select_read(
790 &mut self,
791 pred: TensorRead<'_>,
792 on_true: TensorRead<'_>,
793 on_false: TensorRead<'_>,
794 ) -> crate::Result<Tensor> {
795 self.install_with_pool(|buffers| {
796 elementwise::select_read_with_pool(buffers, pred, on_true, on_false)
797 })
798 }
799
800 fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor> {
801 self.install_with_pool(|buffers| elementwise::clamp_with_pool(buffers, input, lower, upper))
802 }
803
804 fn clamp_read(
805 &mut self,
806 input: TensorRead<'_>,
807 lower: TensorRead<'_>,
808 upper: TensorRead<'_>,
809 ) -> crate::Result<Tensor> {
810 self.install_with_pool(|buffers| {
811 elementwise::clamp_read_with_pool(buffers, input, lower, upper)
812 })
813 }
814}
815
816impl TensorAnalytic for CpuBackend {
817 fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor> {
818 self.install_with_pool(|buffers| analytic::exp_with_pool(buffers, input))
819 }
820
821 fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
822 self.install_with_pool(|buffers| analytic::exp_read_with_pool(buffers, input))
823 }
824
825 fn log(&mut self, input: &Tensor) -> crate::Result<Tensor> {
826 self.install_with_pool(|buffers| analytic::log_with_pool(buffers, input))
827 }
828
829 fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
830 self.install_with_pool(|buffers| analytic::log_read_with_pool(buffers, input))
831 }
832
833 fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor> {
834 self.install_with_pool(|buffers| analytic::sin_with_pool(buffers, input))
835 }
836
837 fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
838 self.install_with_pool(|buffers| analytic::sin_read_with_pool(buffers, input))
839 }
840
841 fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor> {
842 self.install_with_pool(|buffers| analytic::cos_with_pool(buffers, input))
843 }
844
845 fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
846 self.install_with_pool(|buffers| analytic::cos_read_with_pool(buffers, input))
847 }
848
849 fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor> {
850 self.install_with_pool(|buffers| analytic::tanh_with_pool(buffers, input))
851 }
852
853 fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
854 self.install_with_pool(|buffers| analytic::tanh_read_with_pool(buffers, input))
855 }
856
857 fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor> {
858 self.install_with_pool(|buffers| analytic::sqrt_with_pool(buffers, input))
859 }
860
861 fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
862 self.install_with_pool(|buffers| analytic::sqrt_read_with_pool(buffers, input))
863 }
864
865 fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor> {
866 self.install_with_pool(|buffers| analytic::rsqrt_with_pool(buffers, input))
867 }
868
869 fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
870 self.install_with_pool(|buffers| analytic::rsqrt_read_with_pool(buffers, input))
871 }
872
873 fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
874 self.install_with_pool(|buffers| analytic::pow_with_pool(buffers, lhs, rhs))
875 }
876
877 fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
878 self.install_with_pool(|buffers| analytic::pow_read_with_pool(buffers, lhs, rhs))
879 }
880
881 fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor> {
882 self.install_with_pool(|buffers| analytic::expm1_with_pool(buffers, input))
883 }
884
885 fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
886 self.install_with_pool(|buffers| analytic::expm1_read_with_pool(buffers, input))
887 }
888
889 fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor> {
890 self.install_with_pool(|buffers| analytic::log1p_with_pool(buffers, input))
891 }
892
893 fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
894 self.install_with_pool(|buffers| analytic::log1p_read_with_pool(buffers, input))
895 }
896}
897
898impl TensorStructural for CpuBackend {
899 fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor> {
900 self.install_with_pool(|buffers| structural::transpose_with_pool(buffers, input, perm))
901 }
902
903 fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
904 if let Some(input) = input.as_tensor() {
905 return self.transpose(input, perm);
906 }
907
908 let input = materialize_tensor_read("transpose", input)?;
909 self.transpose(&input, perm)
910 }
911
912 fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor> {
913 self.install(|| structural::reshape(input, shape))
914 }
915
916 fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
917 if let Some(input) = input.as_tensor() {
918 return self.reshape(input, shape);
919 }
920
921 let input = materialize_tensor_read("reshape", input)?;
922 self.reshape(&input, shape)
923 }
924
925 fn broadcast_in_dim(
926 &mut self,
927 input: &Tensor,
928 shape: &[usize],
929 dims: &[usize],
930 ) -> crate::Result<Tensor> {
931 self.install_with_pool(|buffers| {
932 structural::broadcast_in_dim_with_pool(buffers, input, shape, dims)
933 })
934 }
935
936 fn broadcast_in_dim_read(
937 &mut self,
938 input: TensorRead<'_>,
939 shape: &[usize],
940 dims: &[usize],
941 ) -> crate::Result<Tensor> {
942 if let Some(input) = input.as_tensor() {
943 return self.broadcast_in_dim(input, shape, dims);
944 }
945
946 let input = materialize_tensor_read("broadcast_in_dim", input)?;
947 self.broadcast_in_dim(&input, shape, dims)
948 }
949
950 fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
951 self.install_with_pool(|buffers| structural::cast_with_pool(buffers, input, to))
952 }
953
954 fn extract_diagonal(
955 &mut self,
956 input: &Tensor,
957 axis_a: usize,
958 axis_b: usize,
959 ) -> crate::Result<Tensor> {
960 self.install_with_pool(|buffers| {
961 structural::extract_diagonal_with_pool(buffers, input, axis_a, axis_b)
962 })
963 }
964
965 fn embed_diagonal(
966 &mut self,
967 input: &Tensor,
968 axis_a: usize,
969 axis_b: usize,
970 ) -> crate::Result<Tensor> {
971 self.install_with_pool(|buffers| {
972 structural::embed_diagonal_with_pool(buffers, input, axis_a, axis_b)
973 })
974 }
975
976 fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor> {
977 self.install_with_pool(|buffers| structural::tril_with_pool(buffers, input, k))
978 }
979
980 fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor> {
981 self.install_with_pool(|buffers| structural::triu_with_pool(buffers, input, k))
982 }
983}
984
985impl TensorReduction for CpuBackend {
986 fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
987 self.install(|| reduction::reduce_sum(input, axes))
988 }
989
990 fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
991 self.install(|| reduction::reduce_sum_read(input, axes))
992 }
993
994 fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
995 self.install(|| reduction::reduce_prod(input, axes))
996 }
997
998 fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
999 self.install(|| reduction::reduce_prod_read(input, axes))
1000 }
1001
1002 fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
1003 self.install(|| reduction::reduce_max(input, axes))
1004 }
1005
1006 fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1007 self.install(|| reduction::reduce_max_read(input, axes))
1008 }
1009
1010 fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
1011 self.install(|| reduction::reduce_min(input, axes))
1012 }
1013
1014 fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1015 self.install(|| reduction::reduce_min_read(input, axes))
1016 }
1017}
1018
1019impl TensorDot for CpuBackend {
1020 fn dot_general(
1021 &mut self,
1022 lhs: &Tensor,
1023 rhs: &Tensor,
1024 config: &DotGeneralConfig,
1025 ) -> crate::Result<Tensor> {
1026 let mut cache = gemm::GemmAnalysisCache::default();
1027 BackendCachedDot::dot_general_cached(self, &mut cache, None, lhs, rhs, config)
1028 }
1029
1030 fn dot_general_read(
1031 &mut self,
1032 lhs: TensorRead<'_>,
1033 rhs: TensorRead<'_>,
1034 config: &DotGeneralConfig,
1035 ) -> crate::Result<Tensor> {
1036 let mut cache = gemm::GemmAnalysisCache::default();
1037 let direct = match self.kind {
1038 CpuBackendKind::Faer => {
1039 #[cfg(feature = "cpu-faer")]
1040 {
1041 let ctx = Arc::clone(&self.ctx);
1042 self.install_with_pool_and_gemm_cache(&mut cache, |buffers, cache| {
1043 gemm::dot_general_faer_read_cached(
1044 buffers,
1045 cache,
1046 None,
1047 ctx.as_ref(),
1048 lhs.clone(),
1049 rhs.clone(),
1050 config,
1051 )
1052 })?
1053 }
1054 #[cfg(not(feature = "cpu-faer"))]
1055 {
1056 return Err(unavailable_cpu_backend_kind(self.kind, "dot_general"));
1057 }
1058 }
1059 CpuBackendKind::Blas => {
1060 #[cfg(feature = "cpu-blas")]
1061 {
1062 self.run_with_pool_and_gemm_cache(&mut cache, |buffers, cache| {
1063 gemm::dot_general_blas_read_cached(
1064 buffers,
1065 cache,
1066 None,
1067 lhs.clone(),
1068 rhs.clone(),
1069 config,
1070 )
1071 })?
1072 }
1073 #[cfg(not(feature = "cpu-blas"))]
1074 {
1075 return Err(unavailable_cpu_backend_kind(self.kind, "dot_general"));
1076 }
1077 }
1078 };
1079 if let Some(result) = direct {
1080 return Ok(result);
1081 }
1082
1083 let lhs = materialize_tensor_read("dot_general", lhs)?;
1084 let rhs = materialize_tensor_read("dot_general", rhs)?;
1085 BackendCachedDot::dot_general_cached(self, &mut cache, None, &lhs, &rhs, config)
1086 }
1087
1088 fn dot_general_read_into(
1089 &mut self,
1090 lhs: TensorRead<'_>,
1091 rhs: TensorRead<'_>,
1092 config: &DotGeneralConfig,
1093 out: TensorWrite<'_>,
1094 ) -> crate::Result<()> {
1095 let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
1096 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
1097 }
1098
1099 fn dot_general_read_into_accum(
1100 &mut self,
1101 lhs: TensorRead<'_>,
1102 rhs: TensorRead<'_>,
1103 config: &DotGeneralConfig,
1104 accumulation: DotGeneralAccumulation,
1105 out: TensorWrite<'_>,
1106 ) -> crate::Result<()> {
1107 let mut cache = gemm::GemmAnalysisCache::default();
1108 BackendCachedDot::dot_general_read_into_accum_cached(
1109 self,
1110 &mut cache,
1111 None,
1112 lhs,
1113 rhs,
1114 config,
1115 accumulation,
1116 out,
1117 )
1118 }
1119
1120 fn dot_general_with_conj(
1121 &mut self,
1122 lhs: &Tensor,
1123 rhs: &Tensor,
1124 config: &DotGeneralConfig,
1125 lhs_conj: bool,
1126 rhs_conj: bool,
1127 ) -> crate::Result<Tensor> {
1128 let mut cache = gemm::GemmAnalysisCache::default();
1129 BackendCachedDot::dot_general_with_conj_cached(
1130 self, &mut cache, None, lhs, rhs, config, lhs_conj, rhs_conj,
1131 )
1132 }
1133}
1134
1135impl BackendCachedDot for CpuBackend {
1136 fn dot_general_cached(
1137 &mut self,
1138 cache: &mut Self::RuntimeCache,
1139 cache_slot: Option<usize>,
1140 lhs: &Tensor,
1141 rhs: &Tensor,
1142 config: &DotGeneralConfig,
1143 ) -> crate::Result<Tensor> {
1144 match self.kind {
1145 CpuBackendKind::Faer => {
1146 #[cfg(feature = "cpu-faer")]
1147 {
1148 let ctx = Arc::clone(&self.ctx);
1149 self.install_with_pool_and_gemm_cache(cache, |buffers, cache| {
1150 match (lhs, rhs) {
1151 (Tensor::F32(a), Tensor::F32(b)) => gemm::dot_general_faer_cached(
1152 buffers,
1153 cache,
1154 cache_slot,
1155 ctx.as_ref(),
1156 a,
1157 b,
1158 config,
1159 )
1160 .map(Tensor::F32),
1161 (Tensor::F64(a), Tensor::F64(b)) => gemm::dot_general_faer_cached(
1162 buffers,
1163 cache,
1164 cache_slot,
1165 ctx.as_ref(),
1166 a,
1167 b,
1168 config,
1169 )
1170 .map(Tensor::F64),
1171 (Tensor::C32(a), Tensor::C32(b)) => gemm::dot_general_faer_cached(
1172 buffers,
1173 cache,
1174 cache_slot,
1175 ctx.as_ref(),
1176 a,
1177 b,
1178 config,
1179 )
1180 .map(Tensor::C32),
1181 (Tensor::C64(a), Tensor::C64(b)) => gemm::dot_general_faer_cached(
1182 buffers,
1183 cache,
1184 cache_slot,
1185 ctx.as_ref(),
1186 a,
1187 b,
1188 config,
1189 )
1190 .map(Tensor::C64),
1191 _ => Err(crate::Error::DTypeMismatch {
1192 op: "dot_general",
1193 lhs: lhs.dtype(),
1194 rhs: rhs.dtype(),
1195 }),
1196 }
1197 })
1198 }
1199 #[cfg(not(feature = "cpu-faer"))]
1200 {
1201 Err(unavailable_cpu_backend_kind(self.kind, "dot_general"))
1202 }
1203 }
1204 CpuBackendKind::Blas => {
1205 #[cfg(feature = "cpu-blas")]
1206 {
1207 self.run_with_pool_and_gemm_cache(cache, |buffers, cache| match (lhs, rhs) {
1208 (Tensor::F32(a), Tensor::F32(b)) => {
1209 gemm::dot_general_blas_cached(buffers, cache, cache_slot, a, b, config)
1210 .map(Tensor::F32)
1211 }
1212 (Tensor::F64(a), Tensor::F64(b)) => {
1213 gemm::dot_general_blas_cached(buffers, cache, cache_slot, a, b, config)
1214 .map(Tensor::F64)
1215 }
1216 (Tensor::C32(a), Tensor::C32(b)) => {
1217 gemm::dot_general_blas_cached(buffers, cache, cache_slot, a, b, config)
1218 .map(Tensor::C32)
1219 }
1220 (Tensor::C64(a), Tensor::C64(b)) => {
1221 gemm::dot_general_blas_cached(buffers, cache, cache_slot, a, b, config)
1222 .map(Tensor::C64)
1223 }
1224 _ => Err(crate::Error::DTypeMismatch {
1225 op: "dot_general",
1226 lhs: lhs.dtype(),
1227 rhs: rhs.dtype(),
1228 }),
1229 })
1230 }
1231 #[cfg(not(feature = "cpu-blas"))]
1232 {
1233 Err(unavailable_cpu_backend_kind(self.kind, "dot_general"))
1234 }
1235 }
1236 }
1237 }
1238
1239 fn dot_general_with_conj_cached(
1240 &mut self,
1241 cache: &mut Self::RuntimeCache,
1242 cache_slot: Option<usize>,
1243 lhs: &Tensor,
1244 rhs: &Tensor,
1245 config: &DotGeneralConfig,
1246 lhs_conj: bool,
1247 rhs_conj: bool,
1248 ) -> crate::Result<Tensor> {
1249 match self.kind {
1250 CpuBackendKind::Faer => {
1251 #[cfg(feature = "cpu-faer")]
1252 {
1253 let ctx = Arc::clone(&self.ctx);
1254 self.install_with_pool_and_gemm_cache(cache, |buffers, cache| {
1255 match (lhs, rhs) {
1256 (Tensor::F32(a), Tensor::F32(b)) => {
1257 gemm::dot_general_faer_with_conj_cached(
1258 buffers,
1259 cache,
1260 cache_slot,
1261 ctx.as_ref(),
1262 a,
1263 b,
1264 config,
1265 lhs_conj,
1266 rhs_conj,
1267 )
1268 .map(Tensor::F32)
1269 }
1270 (Tensor::F64(a), Tensor::F64(b)) => {
1271 gemm::dot_general_faer_with_conj_cached(
1272 buffers,
1273 cache,
1274 cache_slot,
1275 ctx.as_ref(),
1276 a,
1277 b,
1278 config,
1279 lhs_conj,
1280 rhs_conj,
1281 )
1282 .map(Tensor::F64)
1283 }
1284 (Tensor::C32(a), Tensor::C32(b)) => {
1285 gemm::dot_general_faer_with_conj_cached(
1286 buffers,
1287 cache,
1288 cache_slot,
1289 ctx.as_ref(),
1290 a,
1291 b,
1292 config,
1293 lhs_conj,
1294 rhs_conj,
1295 )
1296 .map(Tensor::C32)
1297 }
1298 (Tensor::C64(a), Tensor::C64(b)) => {
1299 gemm::dot_general_faer_with_conj_cached(
1300 buffers,
1301 cache,
1302 cache_slot,
1303 ctx.as_ref(),
1304 a,
1305 b,
1306 config,
1307 lhs_conj,
1308 rhs_conj,
1309 )
1310 .map(Tensor::C64)
1311 }
1312 _ => Err(crate::Error::DTypeMismatch {
1313 op: "dot_general",
1314 lhs: lhs.dtype(),
1315 rhs: rhs.dtype(),
1316 }),
1317 }
1318 })
1319 }
1320 #[cfg(not(feature = "cpu-faer"))]
1321 {
1322 Err(unavailable_cpu_backend_kind(self.kind, "dot_general"))
1323 }
1324 }
1325 CpuBackendKind::Blas => {
1326 #[cfg(feature = "cpu-blas")]
1327 {
1328 self.run_with_pool_and_gemm_cache(cache, |buffers, cache| match (lhs, rhs) {
1329 (Tensor::F32(a), Tensor::F32(b)) => {
1330 gemm::dot_general_blas_with_conj_cached(
1331 buffers, cache, cache_slot, a, b, config, lhs_conj, rhs_conj,
1332 )
1333 .map(Tensor::F32)
1334 }
1335 (Tensor::F64(a), Tensor::F64(b)) => {
1336 gemm::dot_general_blas_with_conj_cached(
1337 buffers, cache, cache_slot, a, b, config, lhs_conj, rhs_conj,
1338 )
1339 .map(Tensor::F64)
1340 }
1341 (Tensor::C32(a), Tensor::C32(b)) => {
1342 gemm::dot_general_blas_with_conj_cached(
1343 buffers, cache, cache_slot, a, b, config, lhs_conj, rhs_conj,
1344 )
1345 .map(Tensor::C32)
1346 }
1347 (Tensor::C64(a), Tensor::C64(b)) => {
1348 gemm::dot_general_blas_with_conj_cached(
1349 buffers, cache, cache_slot, a, b, config, lhs_conj, rhs_conj,
1350 )
1351 .map(Tensor::C64)
1352 }
1353 _ => Err(crate::Error::DTypeMismatch {
1354 op: "dot_general",
1355 lhs: lhs.dtype(),
1356 rhs: rhs.dtype(),
1357 }),
1358 })
1359 }
1360 #[cfg(not(feature = "cpu-blas"))]
1361 {
1362 Err(unavailable_cpu_backend_kind(self.kind, "dot_general"))
1363 }
1364 }
1365 }
1366 }
1367
1368 fn dot_general_read_into_accum_cached(
1369 &mut self,
1370 cache: &mut Self::RuntimeCache,
1371 cache_slot: Option<usize>,
1372 lhs: TensorRead<'_>,
1373 rhs: TensorRead<'_>,
1374 config: &DotGeneralConfig,
1375 accumulation: DotGeneralAccumulation,
1376 mut out: TensorWrite<'_>,
1377 ) -> crate::Result<()> {
1378 validate_dot_general_accumulation(&lhs, &rhs, config, accumulation, &out, "dot_general")?;
1379 let direct = match self.kind {
1380 CpuBackendKind::Faer => {
1381 #[cfg(feature = "cpu-faer")]
1382 {
1383 let ctx = Arc::clone(&self.ctx);
1384 self.install_with_pool_and_gemm_cache(cache, |_buffers, cache| {
1385 gemm::dot_general_faer_read_into_accum_cached(
1386 cache,
1387 cache_slot,
1388 ctx.as_ref(),
1389 lhs.clone(),
1390 rhs.clone(),
1391 config,
1392 accumulation,
1393 &mut out,
1394 )
1395 })?
1396 }
1397 #[cfg(not(feature = "cpu-faer"))]
1398 {
1399 return Err(unavailable_cpu_backend_kind(self.kind, "dot_general"));
1400 }
1401 }
1402 CpuBackendKind::Blas => {
1403 #[cfg(feature = "cpu-blas")]
1404 {
1405 self.run_with_pool_and_gemm_cache(cache, |buffers, cache| {
1406 gemm::dot_general_blas_read_into_accum_cached(
1407 buffers,
1408 cache,
1409 cache_slot,
1410 lhs.clone(),
1411 rhs.clone(),
1412 config,
1413 accumulation,
1414 &mut out,
1415 )
1416 })?
1417 }
1418 #[cfg(not(feature = "cpu-blas"))]
1419 {
1420 return Err(unavailable_cpu_backend_kind(self.kind, "dot_general"));
1421 }
1422 }
1423 };
1424 if direct {
1425 return Ok(());
1426 }
1427
1428 dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
1429 }
1430
1431 fn grouped_gemm_cached(
1432 &mut self,
1433 _cache: &mut Self::RuntimeCache,
1434 _cache_slot: Option<usize>,
1435 lhs: TensorRead<'_>,
1436 rhs: TensorRead<'_>,
1437 config: &GroupedGemmConfig<'_>,
1438 mut out: TensorWrite<'_>,
1439 ) -> crate::Result<()> {
1440 validate_grouped_gemm(&lhs, &rhs, &out, config, "grouped_gemm")?;
1441 let direct = match self.kind {
1442 CpuBackendKind::Faer => {
1443 #[cfg(feature = "cpu-faer")]
1444 {
1445 let ctx = Arc::clone(&self.ctx);
1446 self.install_with_pool(|_buffers| {
1447 gemm::grouped_gemm_faer_cached(
1448 ctx.as_ref(),
1449 lhs.clone(),
1450 rhs.clone(),
1451 config,
1452 &mut out,
1453 )
1454 })?
1455 }
1456 #[cfg(not(feature = "cpu-faer"))]
1457 {
1458 return Err(unavailable_cpu_backend_kind(self.kind, "grouped_gemm"));
1459 }
1460 }
1461 CpuBackendKind::Blas => {
1462 #[cfg(feature = "cpu-blas")]
1463 {
1464 self.run_with_pool(|_buffers| {
1465 gemm::grouped_gemm_blas_cached(lhs.clone(), rhs.clone(), config, &mut out)
1466 })?
1467 }
1468 #[cfg(not(feature = "cpu-blas"))]
1469 {
1470 return Err(unavailable_cpu_backend_kind(self.kind, "grouped_gemm"));
1471 }
1472 }
1473 };
1474 if direct {
1475 return Ok(());
1476 }
1477
1478 grouped_gemm_via_sequential(self, lhs, rhs, config, out)
1479 }
1480}
1481
1482impl TensorIndexing for CpuBackend {
1483 fn gather(
1484 &mut self,
1485 operand: &Tensor,
1486 start_indices: &Tensor,
1487 config: &GatherConfig,
1488 ) -> crate::Result<Tensor> {
1489 self.install_with_pool(|buffers| {
1490 indexing::gather_with_pool(buffers, operand, start_indices, config)
1491 })
1492 }
1493
1494 fn scatter(
1495 &mut self,
1496 operand: &Tensor,
1497 scatter_indices: &Tensor,
1498 updates: &Tensor,
1499 config: &ScatterConfig,
1500 ) -> crate::Result<Tensor> {
1501 self.install_with_pool(|buffers| {
1502 indexing::scatter_with_pool(buffers, operand, scatter_indices, updates, config)
1503 })
1504 }
1505
1506 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor> {
1507 self.install_with_pool(|buffers| indexing::try_slice_with_pool(buffers, input, config))
1508 }
1509
1510 fn dynamic_slice(
1511 &mut self,
1512 input: &Tensor,
1513 starts: &Tensor,
1514 slice_sizes: &[usize],
1515 ) -> crate::Result<Tensor> {
1516 self.install_with_pool(|buffers| {
1517 indexing::dynamic_slice_with_pool(buffers, input, starts, slice_sizes)
1518 })
1519 }
1520
1521 fn dynamic_update_slice(
1522 &mut self,
1523 operand: &Tensor,
1524 update: &Tensor,
1525 starts: &Tensor,
1526 ) -> crate::Result<Tensor> {
1527 self.install_with_pool(|buffers| {
1528 indexing::dynamic_update_slice_with_pool(buffers, operand, update, starts)
1529 })
1530 }
1531
1532 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor> {
1533 self.install_with_pool(|buffers| indexing::try_pad_with_pool(buffers, input, config))
1534 }
1535
1536 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor> {
1537 self.install_with_pool(|buffers| indexing::try_concatenate_with_pool(buffers, inputs, axis))
1538 }
1539
1540 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
1541 self.install_with_pool(|buffers| indexing::reverse_with_pool(buffers, input, axes))
1542 }
1543}
1544
1545impl BackendSessionHost for CpuBackend {
1546 fn with_backend_session<R: Send>(
1547 &mut self,
1548 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
1549 ) -> R {
1550 let mut cache = profile_cpu_session_section("with_backend_session.cache_default", || {
1551 gemm::GemmAnalysisCache::default()
1552 });
1553 self.with_backend_session_cached(&mut cache, f)
1554 }
1555
1556 fn with_backend_session_cached<R: Send>(
1557 &mut self,
1558 cache: &mut Self::RuntimeCache,
1559 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
1560 ) -> R {
1561 if !cpu_session_profile_enabled() {
1562 let mut buffers = BufferPoolLoan::new(&mut self.buffers);
1563 let ctx = Arc::clone(&self.ctx);
1564 let kind = self.kind;
1565 return ctx.install(|| {
1566 let mut session = CpuExecSession {
1567 ctx: ctx.as_ref(),
1568 buffers: buffers.get_mut(),
1569 gemm_analysis_cache: cache,
1570 kind,
1571 };
1572 f(&mut session)
1573 });
1574 }
1575
1576 let total_started = Instant::now();
1577 let mut buffers =
1578 profile_cpu_session_section("with_backend_session_cached.take_buffers", || {
1579 BufferPoolLoan::new(&mut self.buffers)
1580 });
1581 let ctx = Arc::clone(&self.ctx);
1582 let kind = self.kind;
1583 let result =
1584 profile_cpu_session_section("with_backend_session_cached.exec_session", || {
1585 ctx.install(|| {
1586 let session_started = Instant::now();
1587 let mut session = CpuExecSession {
1588 ctx: ctx.as_ref(),
1589 buffers: buffers.get_mut(),
1590 gemm_analysis_cache: cache,
1591 kind,
1592 };
1593 record_cpu_session_profile(
1594 "with_backend_session_cached.session_construct",
1595 session_started.elapsed(),
1596 );
1597
1598 let exec_started = Instant::now();
1599 let result = f(&mut session);
1600 record_cpu_session_profile(
1601 "with_backend_session_cached.exec_body",
1602 exec_started.elapsed(),
1603 );
1604 result
1605 })
1606 });
1607 profile_cpu_session_section("with_backend_session_cached.restore_buffers", || {
1608 drop(buffers);
1609 });
1610 record_cpu_session_profile("with_backend_session_cached.total", total_started.elapsed());
1611 maybe_print_cpu_session_profile();
1612 result
1613 }
1614}
1615
1616impl TensorBuffer for CpuBackend {
1617 fn reclaim_buffer(&mut self, tensor: Tensor) {
1618 match tensor {
1619 Tensor::F32(t) => reclaim_typed(&mut self.buffers, t),
1620 Tensor::F64(t) => reclaim_typed(&mut self.buffers, t),
1621 Tensor::I32(t) => reclaim_typed(&mut self.buffers, t),
1622 Tensor::I64(t) => reclaim_typed(&mut self.buffers, t),
1623 Tensor::Bool(t) => reclaim_typed(&mut self.buffers, t),
1624 Tensor::C32(t) => reclaim_typed(&mut self.buffers, t),
1625 Tensor::C64(t) => reclaim_typed(&mut self.buffers, t),
1626 }
1627 }
1628}
1629
1630impl<T, R> TensorViewCanonicalization<T, R> for CpuBackend
1631where
1632 T: Clone + 'static,
1633 R: TensorRank,
1634{
1635 fn to_contiguous(
1636 &mut self,
1637 view: &TypedTensorView<'_, T, R>,
1638 ) -> crate::Result<TypedTensor<T, R>> {
1639 if view.backend_buffer().is_some() {
1640 return Err(crate::Error::backend_failure(
1641 "CpuBackend::to_contiguous",
1642 "CPU backend received a backend tensor view; download the tensor to host before CPU view canonicalization",
1643 ));
1644 }
1645 view.to_contiguous()
1646 }
1647
1648 fn copy_from_contiguous(
1649 &mut self,
1650 src: &TypedTensor<T, R>,
1651 dst: &mut TypedTensorViewMut<'_, T, R>,
1652 ) -> crate::Result<()> {
1653 if matches!(src.buffer(), Buffer::Backend(_)) {
1654 return Err(crate::Error::backend_failure(
1655 "CpuBackend::copy_from_contiguous",
1656 "CPU backend received a backend source tensor; download the tensor to host before CPU view copy-back",
1657 ));
1658 }
1659 if dst.backend_buffer().is_some() {
1660 return Err(crate::Error::backend_failure(
1661 "CpuBackend::copy_from_contiguous",
1662 "CPU backend received a backend destination view; download the tensor to host before CPU view copy-back",
1663 ));
1664 }
1665 dst.copy_from_contiguous(src)
1666 }
1667}
1668
1669impl TensorFusion for CpuBackend {
1670 fn execute_elementwise_fusion(
1671 &mut self,
1672 inputs: &[&Tensor],
1673 plan: &ElementwiseFusionPlan,
1674 ) -> crate::Result<Option<Vec<Tensor>>> {
1675 self.install_with_pool(|buffers| {
1676 elementwise::elementwise_fusion_with_pool(buffers, inputs, plan)
1677 })
1678 }
1679
1680 fn execute_broadcast_multiply(
1681 &mut self,
1682 lhs: TensorRead<'_>,
1683 lhs_shape: &[usize],
1684 lhs_dims: &[usize],
1685 rhs: TensorRead<'_>,
1686 rhs_shape: &[usize],
1687 rhs_dims: &[usize],
1688 ) -> crate::Result<Option<Tensor>> {
1689 self.install_with_pool(|buffers| {
1690 elementwise::broadcast_multiply_read_with_pool(
1691 buffers, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
1692 )
1693 })
1694 }
1695
1696 fn execute_broadcast_multiply_value(
1697 &mut self,
1698 lhs: TensorRead<'_>,
1699 lhs_shape: &[usize],
1700 lhs_dims: &[usize],
1701 rhs: TensorRead<'_>,
1702 rhs_shape: &[usize],
1703 rhs_dims: &[usize],
1704 ) -> crate::Result<Option<TensorValue>> {
1705 self.install_with_pool(|buffers| {
1706 elementwise::broadcast_multiply_value_with_pool(
1707 buffers, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
1708 )
1709 })
1710 }
1711}
1712
1713impl TensorDeviceTransfer for CpuBackend {
1714 fn download_to_host(&mut self, tensor: &Tensor) -> crate::Result<Tensor> {
1715 if tensor.is_backend_buffer() {
1716 return Err(crate::Error::backend_failure(
1717 "CpuBackend::download_to_host",
1718 "CPU backend received a backend buffer; download the tensor to host with its owning backend before CPU execution",
1719 ));
1720 }
1721 Ok(tensor.clone())
1722 }
1723
1724 fn upload_host_tensor(&mut self, tensor: &Tensor) -> crate::Result<Tensor> {
1725 if tensor.is_backend_buffer() {
1726 return Err(crate::Error::backend_failure(
1727 "CpuBackend::upload_host_tensor",
1728 "CPU backend upload_host_tensor expects a host tensor; download backend buffers to host before CPU execution",
1729 ));
1730 }
1731 Ok(tensor.clone())
1732 }
1733}
1734
1735impl TensorBackend for CpuBackend {}
1736
1737pub(crate) fn reclaim_typed<T: PoolScalar>(pool: &mut BufferPool, typed: TypedTensor<T>) {
1738 let (buffer, _, _) = typed.into_parts();
1739 match buffer {
1740 Buffer::Host(data) => T::pool_release(pool, data),
1741 Buffer::Backend(_) => {}
1742 }
1743}
1744
1745impl Default for CpuBackend {
1746 fn default() -> Self {
1747 Self::new()
1748 }
1749}
1750
1751#[cfg(test)]
1752mod tests;