Skip to main content

tenferro_cpu/
backend.rs

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/// CPU provider selected by a [`CpuBackend`] instance.
155///
156/// CPU provider features are additive at compile time; this runtime selector
157/// chooses which compiled provider an individual backend uses for provider-owned
158/// kernels such as GEMM.
159///
160/// # Examples
161///
162/// ```
163/// use tenferro_cpu::CpuBackendKind;
164///
165/// let kind = CpuBackendKind::default_compiled();
166/// assert!(matches!(kind, CpuBackendKind::Faer | CpuBackendKind::Blas));
167/// ```
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
169pub enum CpuBackendKind {
170    /// faer-backed CPU kernels.
171    Faer,
172    /// BLAS/LAPACK-backed CPU kernels.
173    Blas,
174}
175
176impl CpuBackendKind {
177    /// Return the default compiled CPU provider.
178    ///
179    /// BLAS is preferred when both BLAS and faer are compiled in because an
180    /// application that links a BLAS/LAPACK provider normally expects
181    /// provider-backed kernels to use it by default.
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use tenferro_cpu::CpuBackendKind;
187    ///
188    /// let _kind = CpuBackendKind::default_compiled();
189    /// ```
190    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    // Used by feature-specific diagnostics; some feature combinations leave
202    // the formatter path inactive.
203    #[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// Used by feature-disabled backend paths; a given feature build may compile no
245// direct call site for one provider.
246#[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
254/// CPU execution backend.
255///
256/// # Examples
257///
258/// ```
259/// use tenferro_cpu::CpuBackend;
260///
261/// let backend = CpuBackend::new();
262/// ```
263pub 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    /// Create a CPU backend using the environment-driven CPU context.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use tenferro_cpu::CpuBackend;
287    ///
288    /// let backend = CpuBackend::new();
289    /// ```
290    pub fn new() -> Self {
291        Self::from_context(Arc::new(CpuContext::from_env()))
292    }
293
294    /// Create a CPU backend using the selected compiled provider.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use tenferro_cpu::{CpuBackend, CpuBackendKind};
300    ///
301    /// let backend = CpuBackend::with_kind(CpuBackendKind::default_compiled()).unwrap();
302    /// assert_eq!(backend.kind(), CpuBackendKind::default_compiled());
303    /// ```
304    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    /// Try to create a CPU backend using `RAYON_NUM_THREADS`.
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// use tenferro_cpu::CpuBackend;
314    ///
315    /// let backend = CpuBackend::try_new()
316    ///     .unwrap_or_else(|_| CpuBackend::with_threads(1).unwrap());
317    /// let _ = backend.num_threads();
318    /// ```
319    pub fn try_new() -> crate::Result<Self> {
320        CpuContext::try_from_env().map(|ctx| Self::from_context(Arc::new(ctx)))
321    }
322
323    /// Create a CPU backend from an existing context.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use std::sync::Arc;
329    /// use tenferro_cpu::{CpuBackend, CpuContext};
330    ///
331    /// let ctx = Arc::new(CpuContext::with_threads(2).unwrap());
332    /// let backend = CpuBackend::from_context(ctx);
333    /// assert_eq!(backend.num_threads(), 2);
334    /// ```
335    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    /// Create a CPU backend from an existing context and buffer-pool retention cap.
356    ///
357    /// The cap is measured in retained vector capacity bytes. A cap of zero
358    /// disables buffer retention.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use std::sync::Arc;
364    /// use tenferro_cpu::{CpuBackend, CpuContext};
365    ///
366    /// let ctx = Arc::new(CpuContext::with_threads(1).unwrap());
367    /// let backend = CpuBackend::from_context_with_buffer_pool_limit(ctx, 0);
368    /// assert_eq!(backend.buffer_pool_limit_bytes(), 0);
369    /// ```
370    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    /// Create a CPU backend with a custom thread count.
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use tenferro_cpu::CpuBackend;
399    ///
400    /// let backend = CpuBackend::with_threads(2).unwrap();
401    /// assert_eq!(backend.num_threads(), 2);
402    /// ```
403    ///
404    /// # Errors
405    ///
406    /// Returns an error when `num_threads` is zero or Rayon rejects the pool.
407    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    /// Create a CPU backend with a custom thread count and provider.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// use tenferro_cpu::{CpuBackend, CpuBackendKind};
428    ///
429    /// let backend = CpuBackend::with_threads_and_kind(
430    ///     1,
431    ///     CpuBackendKind::default_compiled(),
432    /// )?;
433    /// assert_eq!(backend.num_threads(), 1);
434    /// # Ok::<(), tenferro_tensor::Error>(())
435    /// ```
436    ///
437    /// # Errors
438    ///
439    /// Returns an error when `num_threads` is zero, Rayon rejects the pool, or
440    /// the selected provider is unavailable.
441    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    /// Return the runtime CPU provider selected by this backend.
462    ///
463    /// # Examples
464    ///
465    /// ```
466    /// use tenferro_cpu::{CpuBackend, CpuBackendKind};
467    ///
468    /// let backend = CpuBackend::new();
469    /// assert_eq!(backend.kind(), CpuBackendKind::default_compiled());
470    /// ```
471    pub fn kind(&self) -> CpuBackendKind {
472        self.kind
473    }
474
475    /// Return the number of threads in this backend's CPU context.
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// use tenferro_cpu::CpuBackend;
481    ///
482    /// let backend = CpuBackend::with_threads(2).unwrap();
483    /// assert_eq!(backend.num_threads(), 2);
484    /// ```
485    pub fn num_threads(&self) -> usize {
486        self.ctx.num_threads()
487    }
488
489    /// Number of retained typed host buffers currently held by this backend.
490    ///
491    /// # Examples
492    ///
493    /// ```
494    /// use tenferro_cpu::CpuBackend;
495    ///
496    /// let backend = CpuBackend::new();
497    /// assert_eq!(backend.buffer_pool_len(), 0);
498    /// ```
499    pub fn buffer_pool_len(&self) -> usize {
500        self.buffers.len()
501    }
502
503    /// Snapshot reusable typed host buffers currently retained by this backend.
504    ///
505    /// # Examples
506    ///
507    /// ```
508    /// use tenferro_cpu::CpuBackend;
509    ///
510    /// let backend = CpuBackend::new();
511    /// let stats = backend.buffer_pool_stats();
512    /// assert_eq!(stats.buffers, 0);
513    /// assert_eq!(stats.capacity_bytes, 0);
514    /// ```
515    pub fn buffer_pool_stats(&self) -> BufferPoolStats {
516        self.buffers.stats()
517    }
518
519    /// Return cache-style stats for the CPU buffer pool.
520    ///
521    /// # Examples
522    ///
523    /// ```
524    /// use tenferro_cpu::CpuBackend;
525    ///
526    /// let backend = CpuBackend::new();
527    /// let stats = backend.buffer_pool_cache_stats();
528    /// assert_eq!(stats.entries, 0);
529    /// assert_eq!(stats.retained_bytes, 0);
530    /// ```
531    pub fn buffer_pool_cache_stats(&self) -> CacheStats {
532        self.buffers.cache_stats()
533    }
534
535    /// Current CPU buffer-pool retention limit in bytes.
536    ///
537    /// # Examples
538    ///
539    /// ```
540    /// use std::sync::Arc;
541    /// use tenferro_cpu::{CpuBackend, CpuContext};
542    ///
543    /// let backend = CpuBackend::from_context_with_buffer_pool_limit(
544    ///     Arc::new(CpuContext::with_threads(1).unwrap()),
545    ///     4096,
546    /// );
547    /// assert_eq!(backend.buffer_pool_limit_bytes(), 4096);
548    /// ```
549    pub fn buffer_pool_limit_bytes(&self) -> usize {
550        self.buffers.max_retained_capacity_bytes()
551    }
552
553    /// Update the CPU buffer-pool retention limit in bytes.
554    ///
555    /// Shrinking the limit evicts retained buffers immediately. A limit of zero
556    /// disables buffer retention.
557    ///
558    /// # Examples
559    ///
560    /// ```
561    /// use tenferro_cpu::CpuBackend;
562    ///
563    /// let mut backend = CpuBackend::new();
564    /// backend.set_buffer_pool_limit_bytes(0);
565    /// assert_eq!(backend.buffer_pool_limit_bytes(), 0);
566    /// assert_eq!(backend.buffer_pool_len(), 0);
567    /// ```
568    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    /// Reset reusable typed host buffers currently retained by this backend.
574    ///
575    /// This releases pool-owned vectors to the process allocator. Operating
576    /// system RSS may not fall immediately because allocators can retain freed
577    /// pages for future allocations.
578    ///
579    /// # Examples
580    ///
581    /// ```
582    /// use tenferro_cpu::CpuBackend;
583    ///
584    /// let mut backend = CpuBackend::new();
585    /// backend.reset_buffer_pool();
586    /// assert_eq!(backend.buffer_pool_len(), 0);
587    /// ```
588    pub fn reset_buffer_pool(&mut self) {
589        self.buffers.clear();
590    }
591
592    /// Run a closure in this backend's CPU execution scope.
593    ///
594    /// # Examples
595    ///
596    /// ```
597    /// use tenferro_cpu::CpuBackend;
598    ///
599    /// let backend = CpuBackend::with_threads(1).unwrap();
600    /// let value = backend.install(|| 1 + 1);
601    /// assert_eq!(value, 2);
602    /// ```
603    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    // Selected when the BLAS provider is active; default Faer-only builds keep
614    // it dormant.
615    #[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    /// Run an external linalg implementation with this backend's buffer pool.
629    ///
630    /// This is exposed for operation-family crates that own their backend
631    /// implementation while still sharing the CPU backend's allocation pool.
632    #[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    /// Clone the CPU context used by external linalg implementations.
638    #[cfg(feature = "cpu-faer")]
639    #[doc(hidden)]
640    pub fn linalg_context(&self) -> Arc<CpuContext> {
641        Arc::clone(&self.ctx)
642    }
643
644    // Selected when the Faer provider handles cached GEMM execution; some
645    // feature combinations compile only the uncached or BLAS path.
646    #[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    // Selected when the BLAS provider handles cached GEMM execution; default
658    // Faer-only builds keep it dormant.
659    #[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;