1use crate::buffer_pool::{BufferPool, PoolScalar};
2use crate::engine::guard_unwind;
3use crate::{CpuEngine, CpuPlanCache, Tensor, TensorRead, TensorValue, TensorWrite};
4use num_complex::{Complex32, Complex64};
5use tenferro_tensor::backend::{ElementwiseFusionPlan, Engine};
6use tenferro_tensor::{
7 ActivationOp, CompareDir, ContractionScalar, DType, DotGeneralConfig, ElementwiseReadOp,
8 GatherConfig, PadConfig, ScatterConfig, SliceConfig, TensorView, TypedTensor,
9};
10use tenferro_tensor::{
11 DotGeneralAccumulation, SessionCachedDot, TensorAnalytic, TensorBuffer, TensorDeviceTransfer,
12 TensorDot, TensorElementwise, TensorFusion, TensorIndexing, TensorReduction, TensorStructural,
13};
14
15use super::backend::elementwise_read_into_fallback_with_pool;
16use super::{
17 activation, analytic, copy_tensor_read_into, elementwise, indexing,
18 materialize_tensor_read_in_domain, reduction, softmax, structural,
19};
20
21pub(super) struct CpuEngineMarker;
24
25fn eager_dot_cache_slot(
40 capacity: usize,
41 lhs: &TensorRead<'_>,
42 rhs: &TensorRead<'_>,
43 config: &DotGeneralConfig,
44 lhs_conj: bool,
45 rhs_conj: bool,
46) -> Option<usize> {
47 use std::hash::{Hash, Hasher};
48
49 if capacity == 0 {
50 return None;
51 }
52 let mut hasher = std::collections::hash_map::DefaultHasher::new();
53 lhs.dtype().hash(&mut hasher);
54 lhs.shape().hash(&mut hasher);
55 rhs.shape().hash(&mut hasher);
56 config.lhs_contracting_dims.hash(&mut hasher);
57 config.rhs_contracting_dims.hash(&mut hasher);
58 config.lhs_batch_dims.hash(&mut hasher);
59 config.rhs_batch_dims.hash(&mut hasher);
60 lhs_conj.hash(&mut hasher);
61 rhs_conj.hash(&mut hasher);
62 Some(hasher.finish() as usize % capacity)
63}
64
65fn pooled_zero_tensor<T>(
66 buffers: &mut BufferPool,
67 shape: impl tenferro_tensor::IntoShapeVec,
68) -> crate::Result<TypedTensor<T>>
69where
70 T: PoolScalar + Clone + 'static,
71{
72 let shape = shape.into_shape_vec();
73 let element_count =
74 tenferro_tensor::validate::checked_shape_product("dot_general", "output", &shape)?;
75 TypedTensor::from_vec_col_major(shape, T::pool_acquire_zeroed(buffers, element_count))
76}
77
78fn allocate_dot_output(
79 buffers: &mut BufferPool,
80 dtype: DType,
81 shape: impl tenferro_tensor::IntoShapeVec,
82) -> crate::Result<Tensor> {
83 let shape = shape.into_shape_vec();
84 match dtype {
85 DType::F32 => pooled_zero_tensor(buffers, &shape).map(Tensor::from_typed::<f32>),
86 DType::F64 => pooled_zero_tensor(buffers, &shape).map(Tensor::from_typed::<f64>),
87 DType::C32 => pooled_zero_tensor(buffers, &shape)
88 .map(Tensor::from_typed::<tenferro_tensor::Complex32>),
89 DType::C64 => pooled_zero_tensor(buffers, &shape)
90 .map(Tensor::from_typed::<tenferro_tensor::Complex64>),
91 dtype => Err(crate::Error::unsupported_dtype(
92 "dot_general",
93 dtype,
94 crate::cpu_contraction_unsupported_dtype_message(dtype),
95 )),
96 }
97}
98
99fn flatten_compact_read(
100 input: TensorRead<'_>,
101 element_count: usize,
102) -> crate::Result<TensorRead<'_>> {
103 macro_rules! flatten {
104 ($variant:ident, $view:expr) => {
105 Ok(TensorRead::from_view(TensorView::$variant(
106 $view.reshape_view(&[element_count])?,
107 )))
108 };
109 }
110 fn read_refusal(tensor: &Tensor) -> crate::Error {
112 crate::Error::unsupported_dtype(
113 "flatten_compact_read",
114 tensor.dtype(),
115 "an externally defined payload is not a compact runtime read",
116 )
117 }
118
119 match input {
120 TensorRead::Tensor(tensor) => match tensor.dtype() {
121 DType::F32 => flatten!(
122 F32,
123 tensor
124 .as_typed::<f32>()
125 .ok_or_else(|| read_refusal(tensor))?
126 .as_view()
127 ),
128 DType::F64 => flatten!(
129 F64,
130 tensor
131 .as_typed::<f64>()
132 .ok_or_else(|| read_refusal(tensor))?
133 .as_view()
134 ),
135 DType::I32 => flatten!(
136 I32,
137 tensor
138 .as_typed::<i32>()
139 .ok_or_else(|| read_refusal(tensor))?
140 .as_view()
141 ),
142 DType::I64 => flatten!(
143 I64,
144 tensor
145 .as_typed::<i64>()
146 .ok_or_else(|| read_refusal(tensor))?
147 .as_view()
148 ),
149 DType::Bool => flatten!(
150 Bool,
151 tensor
152 .as_typed::<bool>()
153 .ok_or_else(|| read_refusal(tensor))?
154 .as_view()
155 ),
156 DType::C32 => flatten!(
157 C32,
158 tensor
159 .as_typed::<Complex32>()
160 .ok_or_else(|| read_refusal(tensor))?
161 .as_view()
162 ),
163 DType::C64 => flatten!(
164 C64,
165 tensor
166 .as_typed::<Complex64>()
167 .ok_or_else(|| read_refusal(tensor))?
168 .as_view()
169 ),
170 DType::External(_) => Err(read_refusal(tensor)),
172 },
173 TensorRead::View(TensorView::F32(view)) => flatten!(F32, view),
174 TensorRead::View(TensorView::F64(view)) => flatten!(F64, view),
175 TensorRead::View(TensorView::I32(view)) => flatten!(I32, view),
176 TensorRead::View(TensorView::I64(view)) => flatten!(I64, view),
177 TensorRead::View(TensorView::Bool(view)) => flatten!(Bool, view),
178 TensorRead::View(TensorView::C32(view)) => flatten!(C32, view),
179 TensorRead::View(TensorView::C64(view)) => flatten!(C64, view),
180 }
181}
182
183impl CpuEngine {
184 pub(crate) fn replace_batch_policy(
186 &mut self,
187 policy: crate::CpuBatchPolicy,
188 ) -> crate::CpuBatchPolicy {
189 self.set_batch_policy(policy)
190 }
191
192 fn run_native_with_exec<R>(
196 &mut self,
197 op: impl FnOnce(threadteam::Exec<'_>, &mut BufferPool) -> crate::Result<R>,
198 ) -> crate::Result<R> {
199 self.check_entry("CpuEngine")?;
200 let (exec, buffers, _, broken) = self.parts();
201 guard_unwind(buffers, broken, |buffers| op(exec.into_token(), buffers))
202 }
203
204 fn run_native_with_plans<R>(
205 &mut self,
206 op: impl FnOnce(threadteam::Exec<'_>, &mut BufferPool, &CpuPlanCache) -> crate::Result<R>,
207 ) -> crate::Result<R> {
208 self.check_entry("CpuEngine")?;
209 let (exec, buffers, plans, broken) = self.parts();
210 guard_unwind(buffers, broken, |buffers| {
211 op(exec.into_token(), buffers, plans)
212 })
213 }
214
215 fn run_contraction<R>(
218 &mut self,
219 op: impl FnOnce(&mut cpueinsum::Exec<'_>, &mut BufferPool, &CpuPlanCache) -> crate::Result<R>,
220 ) -> crate::Result<R> {
221 self.check_entry("dot_general")?;
222 let _retention = self
223 .resources
224 .retention_guard(self.buffers.max_retained_capacity_bytes());
225 let mut exec = self.resources.exec(self.batch_policy);
226 let plans = &self.plans;
227 guard_unwind(&mut self.buffers, &mut self.broken, |buffers| {
228 op(&mut exec, buffers, plans)
229 })
230 }
231}
232
233impl TensorDeviceTransfer for CpuEngine {
234 fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor> {
235 self.check_entry("CpuEngine::download_to_host")?;
236 if tensor.backend_family().is_some() {
237 return Err(crate::Error::runtime_state(
238 "CpuEngine::download_to_host",
239 "CPU backend received a backend buffer; download the tensor to host with its owning backend before CPU execution",
240 ));
241 }
242 tensor.tensor_view().duplicate()
243 }
244
245 fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor> {
246 self.check_entry("CpuEngine::upload_host_tensor")?;
247 if tensor.backend_family().is_some() {
248 return Err(crate::Error::runtime_state(
249 "CpuEngine::upload_host_tensor",
250 "CPU backend upload_host_tensor expects a host tensor; download backend buffers to host before CPU execution",
251 ));
252 }
253 tensor.tensor_view().duplicate()
254 }
255}
256
257macro_rules! delegate_with_pool {
260 ($name:ident($($arg:ident : $ty:ty),*) => $callee:path) => {
261 fn $name(&mut self, $($arg: $ty),*) -> crate::Result<Tensor> {
262 self.run_native_with_exec(|exec, buffers| $callee(buffers, $($arg),*, exec))
263 }
264 };
265}
266
267impl TensorElementwise for CpuEngine {
268 fn elementwise_read_into(
270 &mut self,
271 op: ElementwiseReadOp,
272 inputs: &[TensorRead<'_>],
273 out: TensorWrite<'_>,
274 ) -> crate::Result<()> {
275 self.run_native_with_exec(|exec, buffers| {
276 tenferro_internal_cpu_kernels::elementwise_read_into_with_exec(
278 op,
279 inputs,
280 out,
281 |inputs, out, exec| {
282 elementwise_read_into_fallback_with_pool(buffers, op, inputs, out, exec)
283 },
284 exec,
285 )
286 })
287 }
288
289 fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
290 self.run_native_with_exec(|exec, buffers| {
291 elementwise::add_read_with_pool(buffers, lhs, rhs, exec)
292 })
293 }
294
295 delegate_with_pool!(sub_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::sub_read_with_pool);
296 delegate_with_pool!(mul_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::mul_read_with_pool);
297 delegate_with_pool!(neg_read(input: TensorRead<'_>) => elementwise::neg_read_with_pool);
298 delegate_with_pool!(conj_read(input: TensorRead<'_>) => elementwise::conj_read_with_pool);
299 delegate_with_pool!(div_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::div_read_with_pool);
300 delegate_with_pool!(rem(lhs: &Tensor, rhs: &Tensor) => elementwise::rem_with_pool);
301 delegate_with_pool!(rem_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::rem_read_with_pool);
302 delegate_with_pool!(abs_read(input: TensorRead<'_>) => elementwise::abs_read_with_pool);
303 delegate_with_pool!(sign_read(input: TensorRead<'_>) => elementwise::sign_read_with_pool);
304 delegate_with_pool!(maximum_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::maximum_read_with_pool);
305 delegate_with_pool!(minimum_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => elementwise::minimum_read_with_pool);
306 delegate_with_pool!(compare_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>, dir: &CompareDir) => elementwise::compare_read_with_pool);
307 delegate_with_pool!(select_read(pred: TensorRead<'_>, on_true: TensorRead<'_>, on_false: TensorRead<'_>) => elementwise::select_read_with_pool);
308 delegate_with_pool!(clamp_read(input: TensorRead<'_>, lower: TensorRead<'_>, upper: TensorRead<'_>) => elementwise::clamp_read_with_pool);
309}
310
311impl TensorAnalytic for CpuEngine {
312 delegate_with_pool!(exp_read(input: TensorRead<'_>) => analytic::exp_read_with_pool);
314 delegate_with_pool!(log_read(input: TensorRead<'_>) => analytic::log_read_with_pool);
315 delegate_with_pool!(sin_read(input: TensorRead<'_>) => analytic::sin_read_with_pool);
316 delegate_with_pool!(cos_read(input: TensorRead<'_>) => analytic::cos_read_with_pool);
317 delegate_with_pool!(tanh_read(input: TensorRead<'_>) => analytic::tanh_read_with_pool);
318 delegate_with_pool!(sqrt_read(input: TensorRead<'_>) => analytic::sqrt_read_with_pool);
319 delegate_with_pool!(rsqrt_read(input: TensorRead<'_>) => analytic::rsqrt_read_with_pool);
320 delegate_with_pool!(pow_read(lhs: TensorRead<'_>, rhs: TensorRead<'_>) => analytic::pow_read_with_pool);
321 delegate_with_pool!(expm1_read(input: TensorRead<'_>) => analytic::expm1_read_with_pool);
322 delegate_with_pool!(log1p_read(input: TensorRead<'_>) => analytic::log1p_read_with_pool);
323 delegate_with_pool!(erf_read(input: TensorRead<'_>) => analytic::erf_read_with_pool);
324}
325
326impl TensorStructural for CpuEngine {
327 fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
329 if input.as_tensor().is_none() {
333 self.resources.count_read_staging();
336 }
337 let domain = self.shared_allocation_domain().cloned();
338 if matches!(input, TensorRead::Tensor(_)) && input.backend_family().is_none() {
339 self.check_entry("CpuEngine::to_contiguous_read")?;
340 return materialize_tensor_read_in_domain(
342 &mut self.buffers,
343 "CpuEngine::to_contiguous_read",
344 input,
345 domain.as_deref(),
346 threadteam::Exec::serial(),
347 );
348 }
349 self.run_native_with_exec(|exec, buffers| {
350 materialize_tensor_read_in_domain(
351 buffers,
352 "CpuEngine::to_contiguous_read",
353 input,
354 domain.as_deref(),
355 exec,
356 )
357 })
358 }
359
360 fn copy_read_into(&mut self, src: TensorRead<'_>, dst: TensorWrite<'_>) -> crate::Result<()> {
361 self.run_native_with_exec(|exec, _| {
362 copy_tensor_read_into("CpuEngine::copy_read_into", src, dst, exec)
363 })
364 }
365
366 fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
367 self.run_native_with_exec(|exec, buffers| {
368 structural::transpose_read_with_pool(buffers, input, perm, exec)
369 })
370 }
371
372 fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
373 self.check_entry("CpuEngine::reshape_read")?;
374 if let TensorRead::Tensor(tensor) = &input {
383 if !structural::reshape_may_use_pool(
384 tensor,
385 self.thread_budget(),
386 self.dispatch_costs(),
387 )? {
388 return structural::reshape(tensor, shape);
389 }
390 return self.run_native_with_exec(|exec, buffers| {
391 if let TensorRead::Tensor(tensor) = &input {
392 if !structural::reshape_uses_pool(tensor, &exec)? {
393 return structural::reshape(tensor, shape);
394 }
395 }
396 structural::reshape_read_with_pool(buffers, input, shape, exec)
397 });
398 }
399 self.run_native_with_exec(|exec, buffers| {
400 structural::reshape_read_with_pool(buffers, input, shape, exec)
401 })
402 }
403
404 fn broadcast_in_dim_read(
405 &mut self,
406 input: TensorRead<'_>,
407 shape: &[usize],
408 dims: &[usize],
409 ) -> crate::Result<Tensor> {
410 self.run_native_with_exec(|exec, buffers| {
411 structural::broadcast_in_dim_read_with_pool(buffers, input, shape, dims, exec)
412 })
413 }
414
415 fn tril_read(
416 &mut self,
417 input: tenferro_tensor::TensorRead<'_>,
418 k: i64,
419 ) -> crate::Result<Tensor> {
420 self.run_native_with_exec(|exec, buffers| {
421 structural::tril_read_with_pool(buffers, input, k, exec)
422 })
423 }
424
425 fn triu_read(
426 &mut self,
427 input: tenferro_tensor::TensorRead<'_>,
428 k: i64,
429 ) -> crate::Result<Tensor> {
430 self.run_native_with_exec(|exec, buffers| {
431 structural::triu_read_with_pool(buffers, input, k, exec)
432 })
433 }
434
435 fn extract_diagonal_read(
436 &mut self,
437 input: tenferro_tensor::TensorRead<'_>,
438 axis_a: usize,
439 axis_b: usize,
440 ) -> crate::Result<Tensor> {
441 self.run_native_with_exec(|exec, buffers| {
442 structural::extract_diagonal_read_with_pool(buffers, input, axis_a, axis_b, exec)
443 })
444 }
445
446 fn embed_diagonal_read(
447 &mut self,
448 input: tenferro_tensor::TensorRead<'_>,
449 axis_a: usize,
450 axis_b: usize,
451 ) -> crate::Result<Tensor> {
452 self.run_native_with_exec(|exec, buffers| {
453 structural::embed_diagonal_read_with_pool(buffers, input, axis_a, axis_b, exec)
454 })
455 }
456
457 delegate_with_pool!(cast(input: &Tensor, to: crate::DType) => structural::cast_with_pool);
458 delegate_with_pool!(extract_diagonal(input: &Tensor, axis_a: usize, axis_b: usize) => structural::extract_diagonal_with_pool);
459 delegate_with_pool!(embed_diagonal(input: &Tensor, axis_a: usize, axis_b: usize) => structural::embed_diagonal_with_pool);
460 delegate_with_pool!(tril(input: &Tensor, k: i64) => structural::tril_with_pool);
461 delegate_with_pool!(triu(input: &Tensor, k: i64) => structural::triu_with_pool);
462}
463
464impl TensorReduction for CpuEngine {
465 fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
468 self.run_native_with_exec(|exec, buffers| {
469 reduction::reduce_sum_read(buffers, input, axes, exec)
470 })
471 }
472
473 fn reduce_sum_squares_read(
474 &mut self,
475 input: TensorRead<'_>,
476 axes: &[usize],
477 ) -> crate::Result<Tensor> {
478 self.run_native_with_exec(|exec, buffers| {
479 reduction::reduce_sum_squares_read(buffers, input, axes, exec)
480 })
481 }
482
483 fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
484 self.run_native_with_exec(|exec, buffers| {
485 reduction::reduce_prod_read(buffers, input, axes, exec)
486 })
487 }
488
489 fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
490 self.run_native_with_exec(|exec, buffers| {
491 reduction::reduce_max_read(buffers, input, axes, exec)
492 })
493 }
494
495 fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
496 self.run_native_with_exec(|exec, buffers| {
497 reduction::reduce_min_read(buffers, input, axes, exec)
498 })
499 }
500}
501
502impl CpuEngine {
503 fn execute_dot_allocated(
504 &mut self,
505 cache_slot: Option<usize>,
506 lhs: TensorRead<'_>,
507 rhs: TensorRead<'_>,
508 config: &DotGeneralConfig,
509 lhs_conj: bool,
510 rhs_conj: bool,
511 ) -> crate::Result<Tensor> {
512 let dtype = lhs.dtype();
513 if rhs.dtype() != dtype {
514 return Err(crate::Error::dtype_mismatch(
515 "dot_general",
516 dtype,
517 rhs.dtype(),
518 ));
519 }
520 let output_shape = tenferro_tensor::backend::dot_general_output_shape(
521 lhs.shape(),
522 rhs.shape(),
523 config,
524 "dot_general",
525 )?;
526 let accumulation = DotGeneralAccumulation {
527 lhs_conj,
528 rhs_conj,
529 alpha: ContractionScalar::one(dtype)?,
530 beta: ContractionScalar::zero(dtype)?,
531 };
532 if matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
533 return self.run_contraction(|exec, buffers, plans| {
534 crate::contraction::dot_fresh(
535 exec,
536 buffers,
537 plans,
538 cache_slot,
539 lhs,
540 rhs,
541 config,
542 accumulation,
543 output_shape,
544 )
545 });
546 }
547 let mut output = allocate_dot_output(&mut self.buffers, dtype, output_shape)?;
548 self.dot_general_read_into_accum_cached(
549 cache_slot,
550 lhs,
551 rhs,
552 config,
553 accumulation,
554 TensorWrite::from_tensor(&mut output),
555 )?;
556 Ok(output)
557 }
558}
559
560impl TensorDot for CpuEngine {
561 fn dot_general_read(
562 &mut self,
563 lhs: TensorRead<'_>,
564 rhs: TensorRead<'_>,
565 config: &DotGeneralConfig,
566 ) -> crate::Result<Tensor> {
567 let slot = eager_dot_cache_slot(
568 self.plans.contraction_capacity(),
569 &lhs,
570 &rhs,
571 config,
572 false,
573 false,
574 );
575 self.execute_dot_allocated(slot, lhs, rhs, config, false, false)
576 }
577
578 fn dot_general_with_conj_read(
579 &mut self,
580 lhs: TensorRead<'_>,
581 rhs: TensorRead<'_>,
582 config: &DotGeneralConfig,
583 lhs_conj: bool,
584 rhs_conj: bool,
585 ) -> crate::Result<Tensor> {
586 let slot = eager_dot_cache_slot(
589 self.plans.contraction_capacity(),
590 &lhs,
591 &rhs,
592 config,
593 lhs_conj,
594 rhs_conj,
595 );
596 self.execute_dot_allocated(slot, lhs, rhs, config, lhs_conj, rhs_conj)
597 }
598
599 fn dot_general_read_into(
600 &mut self,
601 lhs: TensorRead<'_>,
602 rhs: TensorRead<'_>,
603 config: &DotGeneralConfig,
604 out: TensorWrite<'_>,
605 ) -> crate::Result<()> {
606 let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
607 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
608 }
609
610 fn dot_general_read_into_accum(
611 &mut self,
612 lhs: TensorRead<'_>,
613 rhs: TensorRead<'_>,
614 config: &DotGeneralConfig,
615 accumulation: DotGeneralAccumulation,
616 out: TensorWrite<'_>,
617 ) -> crate::Result<()> {
618 let slot = eager_dot_cache_slot(
619 self.plans.contraction_capacity(),
620 &lhs,
621 &rhs,
622 config,
623 accumulation.lhs_conj,
624 accumulation.rhs_conj,
625 );
626 self.dot_general_read_into_accum_cached(slot, lhs, rhs, config, accumulation, out)
627 }
628
629 fn dot_general_with_conj(
630 &mut self,
631 lhs: &Tensor,
632 rhs: &Tensor,
633 config: &DotGeneralConfig,
634 lhs_conj: bool,
635 rhs_conj: bool,
636 ) -> crate::Result<Tensor> {
637 let lhs = TensorRead::from_tensor(lhs);
638 let rhs = TensorRead::from_tensor(rhs);
639 let slot = eager_dot_cache_slot(
640 self.plans.contraction_capacity(),
641 &lhs,
642 &rhs,
643 config,
644 lhs_conj,
645 rhs_conj,
646 );
647 self.execute_dot_allocated(slot, lhs, rhs, config, lhs_conj, rhs_conj)
648 }
649}
650
651impl SessionCachedDot for CpuEngine {
652 fn dot_general_cached(
653 &mut self,
654 cache_slot: Option<usize>,
655 lhs: &Tensor,
656 rhs: &Tensor,
657 config: &DotGeneralConfig,
658 ) -> crate::Result<Tensor> {
659 self.execute_dot_allocated(
660 cache_slot,
661 TensorRead::from_tensor(lhs),
662 TensorRead::from_tensor(rhs),
663 config,
664 false,
665 false,
666 )
667 }
668
669 fn dot_general_read_cached(
670 &mut self,
671 cache_slot: Option<usize>,
672 lhs: TensorRead<'_>,
673 rhs: TensorRead<'_>,
674 config: &DotGeneralConfig,
675 ) -> crate::Result<Tensor> {
676 self.execute_dot_allocated(cache_slot, lhs, rhs, config, false, false)
680 }
681
682 fn dot_general_with_conj_cached(
683 &mut self,
684 cache_slot: Option<usize>,
685 lhs: &Tensor,
686 rhs: &Tensor,
687 config: &DotGeneralConfig,
688 lhs_conj: bool,
689 rhs_conj: bool,
690 ) -> crate::Result<Tensor> {
691 self.execute_dot_allocated(
692 cache_slot,
693 TensorRead::from_tensor(lhs),
694 TensorRead::from_tensor(rhs),
695 config,
696 lhs_conj,
697 rhs_conj,
698 )
699 }
700
701 fn dot_general_with_conj_read_cached(
702 &mut self,
703 cache_slot: Option<usize>,
704 lhs: TensorRead<'_>,
705 rhs: TensorRead<'_>,
706 config: &DotGeneralConfig,
707 lhs_conj: bool,
708 rhs_conj: bool,
709 ) -> crate::Result<Tensor> {
710 self.execute_dot_allocated(cache_slot, lhs, rhs, config, lhs_conj, rhs_conj)
711 }
712
713 fn dot_general_read_into_accum_cached(
714 &mut self,
715 cache_slot: Option<usize>,
716 lhs: TensorRead<'_>,
717 rhs: TensorRead<'_>,
718 config: &DotGeneralConfig,
719 accumulation: DotGeneralAccumulation,
720 out: TensorWrite<'_>,
721 ) -> crate::Result<()> {
722 if matches!(
723 lhs.dtype(),
724 DType::F32 | DType::F64 | DType::C32 | DType::C64
725 ) {
726 return self.run_contraction(|exec, buffers, plans| {
727 crate::contraction::dot_into(
728 exec,
729 buffers,
730 plans,
731 cache_slot,
732 lhs,
733 rhs,
734 config,
735 accumulation,
736 out,
737 )
738 });
739 }
740 Err(crate::Error::unsupported_dtype(
743 "dot_general",
744 lhs.dtype(),
745 crate::cpu_contraction_unsupported_dtype_message(lhs.dtype()),
746 ))
747 }
748}
749
750impl TensorIndexing for CpuEngine {
751 fn gather(
753 &mut self,
754 operand: &Tensor,
755 start_indices: &Tensor,
756 config: &GatherConfig,
757 ) -> crate::Result<Tensor> {
758 self.run_native_with_plans(|exec, buffers, cache| {
759 indexing::gather_with_pool(buffers, cache, operand, start_indices, config, exec)
760 })
761 }
762 fn scatter(
763 &mut self,
764 operand: &Tensor,
765 indices: &Tensor,
766 updates: &Tensor,
767 config: &ScatterConfig,
768 ) -> crate::Result<Tensor> {
769 self.run_native_with_plans(|exec, buffers, cache| {
770 indexing::scatter_with_pool(buffers, cache, operand, indices, updates, config, exec)
771 })
772 }
773 fn dynamic_update_slice_read(
774 &mut self,
775 operand: tenferro_tensor::TensorRead<'_>,
776 update: tenferro_tensor::TensorRead<'_>,
777 starts: tenferro_tensor::TensorRead<'_>,
778 ) -> crate::Result<Tensor> {
779 self.run_native_with_plans(|exec, buffers, cache| {
780 indexing::dynamic_update_slice_read_with_pool(
781 buffers, cache, operand, update, starts, exec,
782 )
783 })
784 }
785 fn scatter_read(
786 &mut self,
787 operand: tenferro_tensor::TensorRead<'_>,
788 indices: tenferro_tensor::TensorRead<'_>,
789 updates: tenferro_tensor::TensorRead<'_>,
790 config: &ScatterConfig,
791 ) -> crate::Result<Tensor> {
792 self.run_native_with_plans(|exec, buffers, cache| {
793 indexing::scatter_read_with_pool(
794 buffers, cache, operand, indices, updates, config, exec,
795 )
796 })
797 }
798 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor> {
799 self.run_native_with_exec(|exec, buffers| {
800 indexing::try_slice_with_pool(buffers, input, config, exec)
801 })
802 }
803 fn slice_read(
804 &mut self,
805 input: tenferro_tensor::TensorRead<'_>,
806 config: &SliceConfig,
807 ) -> crate::Result<Tensor> {
808 self.run_native_with_exec(|exec, buffers| {
809 indexing::slice_read_with_pool(buffers, input, config, exec)
810 })
811 }
812 fn dynamic_slice_read(
813 &mut self,
814 input: tenferro_tensor::TensorRead<'_>,
815 starts: tenferro_tensor::TensorRead<'_>,
816 slice_sizes: &[usize],
817 ) -> crate::Result<Tensor> {
818 self.run_native_with_plans(|exec, buffers, cache| {
819 indexing::dynamic_slice_read_with_pool(buffers, cache, input, starts, slice_sizes, exec)
820 })
821 }
822 fn dynamic_slice(
823 &mut self,
824 input: &Tensor,
825 starts: &Tensor,
826 slice_sizes: &[usize],
827 ) -> crate::Result<Tensor> {
828 self.run_native_with_plans(|exec, buffers, cache| {
829 indexing::dynamic_slice_with_pool(buffers, cache, input, starts, slice_sizes, exec)
830 })
831 }
832 fn dynamic_update_slice(
833 &mut self,
834 operand: &Tensor,
835 update: &Tensor,
836 starts: &Tensor,
837 ) -> crate::Result<Tensor> {
838 self.run_native_with_plans(|exec, buffers, cache| {
839 indexing::dynamic_update_slice_with_pool(buffers, cache, operand, update, starts, exec)
840 })
841 }
842 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor> {
843 self.run_native_with_exec(|exec, buffers| {
844 indexing::try_pad_with_pool(buffers, input, config, exec)
845 })
846 }
847 fn pad_read(
848 &mut self,
849 input: tenferro_tensor::TensorRead<'_>,
850 config: &PadConfig,
851 ) -> crate::Result<Tensor> {
852 self.run_native_with_exec(|exec, buffers| {
853 indexing::pad_read_with_pool(buffers, input, config, exec)
854 })
855 }
856 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor> {
857 self.run_native_with_exec(|exec, buffers| {
858 indexing::try_concatenate_with_pool(buffers, inputs, axis, exec)
859 })
860 }
861 fn concatenate_read(
862 &mut self,
863 inputs: &[tenferro_tensor::TensorRead<'_>],
864 axis: usize,
865 ) -> crate::Result<Tensor> {
866 self.run_native_with_exec(|exec, buffers| {
867 indexing::concatenate_read_with_pool(buffers, inputs, axis, exec)
868 })
869 }
870 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
871 self.run_native_with_exec(|exec, buffers| {
872 indexing::reverse_with_pool(buffers, input, axes, exec)
873 })
874 }
875 fn reverse_read(
876 &mut self,
877 input: tenferro_tensor::TensorRead<'_>,
878 axes: &[usize],
879 ) -> crate::Result<Tensor> {
880 self.run_native_with_exec(|exec, buffers| {
881 indexing::reverse_read_with_pool(buffers, input, axes, exec)
882 })
883 }
884}
885
886impl<T, R> tenferro_tensor::TensorViewCanonicalization<T, R> for CpuEngine
888where
889 T: tenferro_tensor::TensorScalar + PoolScalar,
890 R: tenferro_tensor::TensorRank,
891 R::Shape: Send + Sync,
892 R::Strides: Send + Sync,
893{
894 fn to_contiguous(
895 &mut self,
896 view: &tenferro_tensor::TypedTensorView<'_, T, R>,
897 ) -> crate::Result<TypedTensor<T, R>> {
898 self.run_native_with_exec(|exec, buffers| {
899 crate::structural::typed_materialize_view_with_pool(
900 buffers,
901 view,
902 "CpuEngine::to_contiguous",
903 exec,
904 )
905 })
906 }
907
908 fn copy_into(
909 &mut self,
910 src: &tenferro_tensor::TypedTensorView<'_, T, R>,
911 dst: &mut tenferro_tensor::TypedTensorViewMut<'_, T, R>,
912 ) -> crate::Result<()> {
913 self.run_native_with_exec(|exec, _| {
914 crate::structural::typed_copy_view_into(src, dst, "CpuEngine::copy_into", exec)
915 })
916 }
917}
918
919impl TensorBuffer for CpuEngine {
920 fn reclaim_buffer(&mut self, tensor: Tensor) {
921 crate::backend::reclaim_tensor(&mut self.buffers, tensor);
922 }
923}
924
925impl TensorFusion for CpuEngine {
926 fn execute_elementwise_fusion(
927 &mut self,
928 inputs: &[&Tensor],
929 plan: &ElementwiseFusionPlan,
930 ) -> crate::Result<Option<Vec<Tensor>>> {
931 self.run_native_with_exec(|exec, buffers| {
932 tenferro_cpu_fused::elementwise_fusion_with_pool(buffers, inputs, plan, exec)
933 })
934 }
935
936 fn execute_broadcast_multiply(
937 &mut self,
938 lhs: TensorRead<'_>,
939 lhs_shape: &[usize],
940 lhs_dims: &[usize],
941 rhs: TensorRead<'_>,
942 rhs_shape: &[usize],
943 rhs_dims: &[usize],
944 ) -> crate::Result<Option<Tensor>> {
945 self.run_native_with_exec(|exec, buffers| {
946 elementwise::broadcast_multiply_read_with_pool(
947 buffers, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims, exec,
948 )
949 })
950 }
951
952 fn execute_broadcast_multiply_value(
953 &mut self,
954 lhs: TensorRead<'_>,
955 lhs_shape: &[usize],
956 lhs_dims: &[usize],
957 rhs: TensorRead<'_>,
958 rhs_shape: &[usize],
959 rhs_dims: &[usize],
960 ) -> crate::Result<Option<TensorValue>> {
961 self.run_native_with_exec(|exec, buffers| {
962 elementwise::broadcast_multiply_value_with_pool_and_tag(
963 buffers,
964 lhs,
965 lhs_shape,
966 lhs_dims,
967 rhs,
968 rhs_shape,
969 rhs_dims,
970 |_| {},
971 exec,
972 )
973 })
974 }
975}
976
977impl Engine for CpuEngine {
978 fn gather_read(
979 &mut self,
980 operand: tenferro_tensor::TensorRead<'_>,
981 start_indices: tenferro_tensor::TensorRead<'_>,
982 config: &GatherConfig,
983 ) -> crate::Result<Tensor> {
984 self.run_native_with_plans(|exec, buffers, cache| {
985 indexing::gather_read_with_pool(buffers, cache, operand, start_indices, config, exec)
986 })
987 }
988 fn vdot_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
989 tenferro_tensor::backend::validate_vdot_read(&lhs, &rhs)?;
990 crate::blas1::validate_cpu_read("Engine::vdot_read", &lhs)?;
991 crate::blas1::validate_cpu_read("Engine::vdot_read", &rhs)?;
992 let element_count = tenferro_tensor::validate::checked_shape_product(
993 "Engine::vdot_read",
994 "shape",
995 lhs.shape(),
996 )?;
997 let (lhs, rhs, rank) = if lhs.shape().len() > 2
998 && lhs.is_col_major_contiguous()?
999 && rhs.is_col_major_contiguous()?
1000 {
1001 (
1002 flatten_compact_read(lhs, element_count)?,
1003 flatten_compact_read(rhs, element_count)?,
1004 1,
1005 )
1006 } else {
1007 let rank = lhs.shape().len();
1008 (lhs, rhs, rank)
1009 };
1010 let axes = (0..rank).collect::<tinyvec::TinyVec<[usize; 4]>>();
1011 let config = DotGeneralConfig {
1012 lhs_contracting_dims: axes.clone(),
1013 rhs_contracting_dims: axes,
1014 lhs_batch_dims: Default::default(),
1015 rhs_batch_dims: Default::default(),
1016 };
1017 self.execute_dot_allocated(None, lhs, rhs, &config, true, false)
1018 }
1019
1020 fn norm_squared_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1021 tenferro_tensor::backend::validate_norm_squared_read(&input)?;
1022 crate::blas1::validate_cpu_read("Engine::norm_squared_read", &input)?;
1023 self.run_native_with_exec(|exec, buffers| {
1024 reduction::norm_squared_read(buffers, input, exec)
1025 })
1026 }
1027
1028 fn fused_activation_read(
1029 &mut self,
1030 op: ActivationOp,
1031 input: TensorRead<'_>,
1032 ) -> crate::Result<Option<Tensor>> {
1033 crate::blas1::validate_cpu_read(op.label(), &input)?;
1034 self.run_native_with_exec(|exec, buffers| {
1035 activation::fused_activation_read_with_pool(buffers, op, input, exec)
1036 })
1037 }
1038
1039 fn softmax_read(
1040 &mut self,
1041 input: TensorRead<'_>,
1042 axis: usize,
1043 ) -> crate::Result<Option<Tensor>> {
1044 crate::blas1::validate_cpu_read("softmax", &input)?;
1045 self.run_native_with_exec(|exec, buffers| {
1046 softmax::softmax_read_with_pool(buffers, input, axis, exec)
1047 })
1048 }
1049
1050 fn axpby_read_into_accum(
1051 &mut self,
1052 alpha: ContractionScalar,
1053 x: TensorRead<'_>,
1054 beta: ContractionScalar,
1055 y: TensorWrite<'_>,
1056 ) -> crate::Result<()> {
1057 tenferro_tensor::backend::validate_axpby_read_into_accum(alpha, &x, beta, &y)?;
1058 self.run_native_with_exec(|exec, buffers| {
1059 crate::blas1::axpby_read_into_accum(buffers, alpha, x, beta, y, exec)
1060 })
1061 }
1062
1063 fn native_engine(&mut self) -> Option<tenferro_tensor::NativeSessionRef<'_>> {
1064 Some(unsafe { tenferro_tensor::NativeSessionRef::new::<CpuEngineMarker, _>(self) })
1068 }
1069}
1070
1071#[cfg(test)]
1072mod tests;