1#![cfg_attr(
22 all(test, feature = "provider-inject"),
23 allow(dead_code, unused_imports)
24)]
25
26#[cfg(not(any(feature = "cpu-faer", feature = "cpu-blas")))]
27compile_error!("enable at least one CPU backend: cpu-faer or cpu-blas");
28
29#[cfg(all(feature = "provider-inject", not(feature = "cpu-blas")))]
30compile_error!("provider-inject requires cpu-blas");
31
32#[cfg(any(
33 all(feature = "blas-openblas", feature = "blas-accelerate"),
34 all(feature = "blas-openblas", feature = "blas-mkl"),
35 all(feature = "blas-accelerate", feature = "blas-mkl"),
36))]
37compile_error!(
38 "enable at most one explicit BLAS provider feature: blas-openblas, blas-accelerate, or blas-mkl"
39);
40
41#[cfg(all(
42 feature = "provider-inject",
43 any(
44 feature = "blas-openblas",
45 feature = "blas-accelerate",
46 feature = "blas-mkl"
47 )
48))]
49compile_error!("provider-inject cannot be combined with explicit BLAS provider features");
50
51pub mod affinity;
52mod affinity_policy;
53mod analytic;
54mod arbiter;
55pub mod backend;
56pub(crate) mod buffer_pool {
57 pub use tenferro_internal_cpu_kernels::buffer_pool::*;
58}
59mod capability;
60pub mod context;
61#[allow(dead_code)]
64mod domain_executor;
65#[allow(dead_code)]
66mod dot_runtime;
67pub(crate) use tenferro_internal_cpu_kernels::elementwise;
68pub(crate) use tenferro_internal_cpu_kernels::PooledUninitOutput;
69mod engine;
70mod exec_session;
71mod gemm;
72mod indexed_plan_cache;
73mod indexing;
74#[cfg(feature = "provider-inject")]
75pub mod inject;
76mod placement;
77pub mod provider;
78mod provider_capability;
79mod reduction;
80mod resource_domain;
81mod runtime_adapter;
82mod structural;
83mod topology;
84
85use std::mem::MaybeUninit;
86use std::ptr::NonNull;
87#[cfg(test)]
88use strided_kernel::StridedArray;
89use strided_kernel::{col_major_strides as kernel_col_major_strides, StridedView};
90
91use crate::buffer_pool::BufferPool;
92pub(crate) use tenferro_tensor::*;
93
94pub(crate) fn erased_raw_strided_ref<'a>(
95 dtype: strided_kernel::KernelDType,
96 data: &'a [u8],
97 dims: &'a [usize],
98 strides: &'a [isize],
99 offset: isize,
100) -> strided_kernel::Result<strided_kernel::ErasedRawStridedRef<'a>> {
101 let data_ptr = NonNull::new(data.as_ptr().cast_mut()).unwrap_or_else(NonNull::dangling);
102 unsafe {
105 strided_kernel::ErasedRawStridedRef::from_raw_parts(
106 dtype,
107 data_ptr,
108 data.len(),
109 dims,
110 strides,
111 offset,
112 )
113 }
114}
115
116pub(crate) fn erased_raw_strided_mut<'a>(
117 dtype: strided_kernel::KernelDType,
118 data: &'a mut [u8],
119 dims: &'a [usize],
120 strides: &'a [isize],
121 offset: isize,
122) -> strided_kernel::Result<strided_kernel::ErasedRawStridedMut<'a>> {
123 let data_ptr = NonNull::new(data.as_mut_ptr()).unwrap_or_else(NonNull::dangling);
124 unsafe {
127 strided_kernel::ErasedRawStridedMut::from_raw_parts(
128 dtype,
129 data_ptr,
130 data.len(),
131 dims,
132 strides,
133 offset,
134 )
135 }
136}
137
138pub(crate) fn erased_raw_strided_uninit_mut<'a>(
139 dtype: strided_kernel::KernelDType,
140 data: &'a mut [MaybeUninit<u8>],
141 dims: &'a [usize],
142 strides: &'a [isize],
143 offset: isize,
144) -> strided_kernel::Result<strided_kernel::ErasedRawStridedUninitMut<'a>> {
145 let data_ptr = NonNull::new(data.as_mut_ptr().cast::<u8>()).unwrap_or_else(NonNull::dangling);
146 unsafe {
149 strided_kernel::ErasedRawStridedUninitMut::from_raw_parts(
150 dtype,
151 data_ptr,
152 data.len(),
153 dims,
154 strides,
155 offset,
156 )
157 }
158}
159
160#[cfg(feature = "provider-src")]
161extern crate blas_src as _;
162#[cfg(feature = "provider-inject")]
163extern crate cblas_inject as _;
164#[cfg(feature = "provider-src")]
165extern crate cblas_src as _;
166#[cfg(feature = "provider-inject")]
167extern crate lapack_inject as _;
168#[cfg(feature = "provider-src")]
169extern crate lapack_src as _;
170
171pub use affinity::{
172 available_parallelism, process_cpu_affinity, process_cpu_affinity_count, CpuAffinityError,
173};
174pub use affinity_policy::{
175 resolve_cpu_affinity, resolve_cpu_affinity_with_override, CpuAffinityInput,
176 CpuAffinityInputError, CpuAffinityPolicy, CpuAffinityResolutionError, CpuAffinitySelection,
177 CpuAffinitySelectionReason,
178};
179pub use backend::{
180 CpuBackend, CpuBackendError, CpuBackendKind, CpuExecutionInfo, CpuExecutionMode,
181 CpuRuntimeIdentity, ExternalCpuDomainRegistryError,
182};
183pub use buffer_pool::BufferPoolStats;
184pub use capability::cpu_capabilities;
185pub use context::{CpuContext, CpuContextError};
186pub use domain_executor::{
187 CpuDomainExecutor, CpuDomainExecutorCapabilities, CpuDomainExecutorError, CpuExecutorAffinity,
188 CpuExecutorReentrancy, CpuExecutorShutdown, CpuInnerParallelism, ScopedCpuJob, ScopedCpuJobs,
189};
190pub use dot_runtime::{
191 CpuProviderBundle, CpuProviderBundleBuildError, CpuProviderBundleBuilder,
192 CpuProviderBundleInstallError, CpuProviderSlot, GeneralContractionPolicy,
193};
194#[doc(hidden)]
195pub use exec_session::CpuExecSession;
196pub use indexed_plan_cache::IndexedPlanCacheLimits;
197pub use placement::{
198 CpuEngineConstructionError, CpuPlacement, CpuPlacementError, CpuPlacementGuarantee,
199 ResolvedCpuPlacement,
200};
201pub use provider::{CpuExecutionContext, ParallelMode};
202pub use provider_capability::{
203 CpuPlacementControl, CpuProviderDomainError, CpuProviderExecutionCapabilities,
204 CpuThreadCountControl,
205};
206pub use resource_domain::{CpuDomainOwnership, ExternalCpuDomain, ExternalCpuDomainError};
207pub use runtime_adapter::{
208 runtime_engine_id, runtime_engine_registration, runtime_engine_registration_with_id,
209 runtime_hardware_class,
210};
211pub use topology::{
212 discover_cpu_topology, CpuId, CpuNode, CpuSet, CpuSetError, CpuTopology, CpuTopologyError,
213 NumaNodeId,
214};
215
216#[doc(hidden)]
223pub fn with_cpu_exec_session<B, R>(
224 session: &mut B,
225 f: impl for<'a> FnOnce(&'a mut CpuExecSession<'a>) -> R,
226) -> Option<R>
227where
228 B: tenferro_tensor::BackendSession + ?Sized,
229{
230 if session.session_type_name() != std::any::type_name::<CpuExecSession<'static>>() {
231 return None;
232 }
233 let data = unsafe { session.session_data_mut() };
234 Some(unsafe { f(&mut *(data.cast::<CpuExecSession<'static>>())) })
240}
241
242#[cfg(test)]
245pub(crate) use analytic::pow;
246#[cfg(test)]
247macro_rules! test_elementwise_wrapper {
248 ($name:ident($($arg:ident: $ty:ty),*) => $with_pool:ident) => {
249 pub(crate) fn $name($($arg: $ty),*) -> crate::Result<Tensor> {
250 let mut buffers = BufferPool::new();
251 elementwise::$with_pool(&mut buffers, $($arg),*)
252 }
253 };
254}
255#[cfg(test)]
256test_elementwise_wrapper!(abs(input: &Tensor) => abs_with_pool);
257#[cfg(test)]
258test_elementwise_wrapper!(add(lhs: &Tensor, rhs: &Tensor) => add_with_pool);
259#[cfg(test)]
260test_elementwise_wrapper!(clamp(input: &Tensor, lower: &Tensor, upper: &Tensor) => clamp_with_pool);
261#[cfg(test)]
262test_elementwise_wrapper!(compare(lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) => compare_with_pool);
263#[cfg(test)]
264test_elementwise_wrapper!(conj(input: &Tensor) => conj_with_pool);
265#[cfg(test)]
266test_elementwise_wrapper!(div(lhs: &Tensor, rhs: &Tensor) => div_with_pool);
267#[cfg(test)]
268test_elementwise_wrapper!(maximum(lhs: &Tensor, rhs: &Tensor) => maximum_with_pool);
269#[cfg(test)]
270test_elementwise_wrapper!(minimum(lhs: &Tensor, rhs: &Tensor) => minimum_with_pool);
271#[cfg(test)]
272test_elementwise_wrapper!(mul(lhs: &Tensor, rhs: &Tensor) => mul_with_pool);
273#[cfg(test)]
274test_elementwise_wrapper!(neg(input: &Tensor) => neg_with_pool);
275#[cfg(test)]
276test_elementwise_wrapper!(rem(lhs: &Tensor, rhs: &Tensor) => rem_with_pool);
277#[cfg(test)]
278test_elementwise_wrapper!(select(pred: &Tensor, on_true: &Tensor, on_false: &Tensor) => select_with_pool);
279#[cfg(test)]
280test_elementwise_wrapper!(sign(input: &Tensor) => sign_with_pool);
281#[cfg(test)]
282test_elementwise_wrapper!(sub(lhs: &Tensor, rhs: &Tensor) => sub_with_pool);
283#[cfg(test)]
284pub(crate) use indexing::{dynamic_slice, dynamic_update_slice, gather, pad, scatter};
285#[cfg(test)]
286pub(crate) use reduction::{reduce_max, reduce_min, reduce_prod, reduce_sum, reduce_sum_squares};
287#[cfg(test)]
288pub(crate) use structural::{
289 broadcast_in_dim, embed_diagonal, extract_diagonal, reshape, transpose, tril, triu,
290};
291
292#[doc(hidden)]
298pub mod linalg_interop {
299 pub use crate::buffer_pool::{BufferPool, PoolScalar};
300 pub use tenferro_internal_cpu_kernels::PooledUninitOutput;
301}
302
303pub(crate) fn cpu_backend_buffer_error(op: &'static str) -> crate::Error {
304 crate::Error::runtime_state(
305 op,
306 "CPU backend received backend buffer; download to host before CPU execution",
307 )
308}
309
310#[derive(Debug, thiserror::Error)]
311pub(crate) enum CpuNumericalError {
312 #[error("{op} received a negative integer exponent for dtype {dtype:?}")]
313 NegativeIntegerExponent { op: &'static str, dtype: DType },
314}
315
316pub(crate) fn cpu_negative_integer_exponent(op: &'static str, dtype: DType) -> crate::Error {
317 crate::Error::extension(
318 op,
319 "cpu",
320 ErrorKind::NumericalFailure,
321 CpuNumericalError::NegativeIntegerExponent { op, dtype },
322 )
323}
324
325pub(crate) trait ConjElem {
326 fn conj_elem(self) -> Self;
327}
328
329impl ConjElem for f32 {
330 fn conj_elem(self) -> Self {
331 self
332 }
333}
334
335impl ConjElem for f64 {
336 fn conj_elem(self) -> Self {
337 self
338 }
339}
340
341impl ConjElem for num_complex::Complex32 {
342 fn conj_elem(self) -> Self {
343 self.conj()
344 }
345}
346
347impl ConjElem for num_complex::Complex64 {
348 fn conj_elem(self) -> Self {
349 self.conj()
350 }
351}
352
353pub(crate) fn typed_host_data<'a, T>(
354 op: &'static str,
355 tensor: &'a TypedTensor<T>,
356) -> crate::Result<&'a [T]> {
357 match tensor.buffer() {
358 Buffer::Host(data) => Ok(data.as_slice()),
359 Buffer::Backend(_) => Err(cpu_backend_buffer_error(op)),
360 }
361}
362
363pub(crate) fn typed_view<'a, T: Copy>(
364 op: &'static str,
365 tensor: &'a TypedTensor<T>,
366) -> crate::Result<StridedView<'a, T>> {
367 match tensor.buffer() {
368 Buffer::Host(data) => {
369 let strides = kernel_col_major_strides(tensor.shape());
370 StridedView::new(data.as_slice(), tensor.shape(), &strides, 0)
371 .map_err(|err| crate::Error::backend_source(op, err))
372 }
373 Buffer::Backend(_) => Err(cpu_backend_buffer_error(op)),
374 }
375}
376
377pub(crate) fn typed_view_from_view<'a, T: Copy + 'static, R: TensorRank>(
378 op: &'static str,
379 view: &TypedTensorView<'a, T, R>,
380) -> crate::Result<StridedView<'a, T>> {
381 if view.backend_buffer().is_some() {
382 return Err(cpu_backend_buffer_error(op));
383 }
384 StridedView::new(
385 view.host_storage()?,
386 view.shape(),
387 view.strides(),
388 view.offset(),
389 )
390 .map_err(|err| crate::Error::backend_source(op, err))
391}
392
393pub(crate) fn materialize_tensor_read(
394 buffers: &mut BufferPool,
395 op: &'static str,
396 input: TensorRead<'_>,
397) -> crate::Result<Tensor> {
398 match input {
399 TensorRead::Tensor(tensor) => clone_host_tensor_read(op, tensor),
400 TensorRead::View(view) => materialize_tensor_view(buffers, op, view),
401 }
402}
403
404pub(crate) fn copy_tensor_read_into(
405 op: &'static str,
406 src: TensorRead<'_>,
407 dst: TensorWrite<'_>,
408) -> crate::Result<()> {
409 let src_dtype = src.dtype();
410 let dst_dtype = dst.dtype();
411 macro_rules! copy_source {
412 ($variant:ident, $src:expr) => {{
413 let src = $src;
414 match dst {
415 TensorWrite::Tensor(Tensor::$variant(dst)) => {
416 let mut dst = dst.as_view_mut();
417 structural::typed_copy_view_into(&src, &mut dst, op)
418 }
419 TensorWrite::View(TensorViewMut::$variant(mut dst)) => {
420 structural::typed_copy_view_into(&src, &mut dst, op)
421 }
422 _ => Err(crate::Error::dtype_mismatch(op, src_dtype, dst_dtype)),
423 }
424 }};
425 }
426
427 match src {
428 TensorRead::Tensor(Tensor::F32(src)) => copy_source!(F32, src.as_view()),
429 TensorRead::Tensor(Tensor::F64(src)) => copy_source!(F64, src.as_view()),
430 TensorRead::Tensor(Tensor::I32(src)) => copy_source!(I32, src.as_view()),
431 TensorRead::Tensor(Tensor::I64(src)) => copy_source!(I64, src.as_view()),
432 TensorRead::Tensor(Tensor::Bool(src)) => copy_source!(Bool, src.as_view()),
433 TensorRead::Tensor(Tensor::C32(src)) => copy_source!(C32, src.as_view()),
434 TensorRead::Tensor(Tensor::C64(src)) => copy_source!(C64, src.as_view()),
435 TensorRead::View(TensorView::F32(src)) => copy_source!(F32, src),
436 TensorRead::View(TensorView::F64(src)) => copy_source!(F64, src),
437 TensorRead::View(TensorView::I32(src)) => copy_source!(I32, src),
438 TensorRead::View(TensorView::I64(src)) => copy_source!(I64, src),
439 TensorRead::View(TensorView::Bool(src)) => copy_source!(Bool, src),
440 TensorRead::View(TensorView::C32(src)) => copy_source!(C32, src),
441 TensorRead::View(TensorView::C64(src)) => copy_source!(C64, src),
442 }
443}
444
445fn clone_host_tensor_read(op: &'static str, tensor: &Tensor) -> crate::Result<Tensor> {
446 macro_rules! clone_host {
447 ($variant:ident, $tensor:expr) => {{
448 structural::validate_cpu_host_placement(op, "source", $tensor.placement())?;
449 typed_host_data(op, $tensor)?;
450 Ok(Tensor::$variant($tensor.clone()))
451 }};
452 }
453
454 match tensor {
455 Tensor::F32(tensor) => clone_host!(F32, tensor),
456 Tensor::F64(tensor) => clone_host!(F64, tensor),
457 Tensor::I32(tensor) => clone_host!(I32, tensor),
458 Tensor::I64(tensor) => clone_host!(I64, tensor),
459 Tensor::Bool(tensor) => clone_host!(Bool, tensor),
460 Tensor::C32(tensor) => clone_host!(C32, tensor),
461 Tensor::C64(tensor) => clone_host!(C64, tensor),
462 }
463}
464
465fn materialize_tensor_view(
466 buffers: &mut BufferPool,
467 op: &'static str,
468 view: TensorView<'_>,
469) -> crate::Result<Tensor> {
470 macro_rules! materialize {
471 ($variant:ident, $view:expr) => {{
472 Ok(Tensor::$variant(
473 structural::typed_materialize_view_with_pool(buffers, &$view, op)?,
474 ))
475 }};
476 }
477
478 match view {
479 TensorView::F32(view) => materialize!(F32, view),
480 TensorView::F64(view) => materialize!(F64, view),
481 TensorView::I32(view) => materialize!(I32, view),
482 TensorView::I64(view) => materialize!(I64, view),
483 TensorView::Bool(view) => materialize!(Bool, view),
484 TensorView::C32(view) => materialize!(C32, view),
485 TensorView::C64(view) => materialize!(C64, view),
486 }
487}
488
489#[allow(clippy::uninit_vec)]
495#[cfg(test)]
496pub(crate) unsafe fn typed_array_uninit<T>(shape: &[usize]) -> StridedArray<T> {
497 let total: usize = shape.iter().product();
498 let strides = kernel_col_major_strides(shape);
499 let mut data = Vec::with_capacity(total);
500 unsafe { data.set_len(total) };
502 StridedArray::from_parts(data, shape, &strides, 0).expect("column-major output array")
505}
506
507#[cfg(test)]
508pub(crate) fn tensor_from_array<T: Clone>(array: StridedArray<T>) -> TypedTensor<T> {
509 TypedTensor::from_vec_col_major(array.dims().to_vec(), array.into_data())
511 .expect("strided array dimensions match owned data length")
512}
513
514pub(crate) fn default_placement() -> Placement {
515 Placement {
516 memory_kind: MemoryKind::UnpinnedHost,
517 device: None,
518 cpu_affinity: None,
519 }
520}
521
522pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
523 assert_eq!(shape.len(), out.len());
524 for (axis, &dim) in shape.iter().enumerate() {
525 if dim == 0 {
526 out[axis] = 0;
527 } else {
528 out[axis] = flat % dim;
529 flat /= dim;
530 }
531 }
532}
533
534#[cfg(all(test, not(feature = "provider-inject")))]
539mod tests;