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