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