1use std::any::{Any, TypeId};
64use std::collections::{HashMap, VecDeque};
65use std::fmt;
66use std::num::NonZeroUsize;
67use std::ops::Deref;
68use std::ptr::NonNull;
69use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
70
71use cubecl::client::ComputeClient;
72use cubecl::features::AtomicUsage;
73use cubecl::prelude::{
74 ArrayArg, ComplexCore as CubeComplex, CubeDim, CubeElement, CubePrimitive, Float as CubeFloat,
75 Numeric as CubeNumeric,
76};
77use cubecl::prelude::{CubeCount, Int as CubeInt, StorageType, TensorBinding, Type};
78use cubecl_cuda::CudaRuntime as CubeclCudaRuntime;
79use num_complex::{Complex32, Complex64};
80use tenferro_core_ops::PrimitiveOpKind;
81use tenferro_tensor::CacheStats;
82use tenferro_tensor::{DotGeneralAccumulation, TensorRead, TensorWrite};
83
84use crate::backend::{
85 BackendCachedDot, BackendRuntimeCache, BackendSession, TensorAnalytic, TensorBackend,
86 TensorBuffer, TensorDeviceTransfer, TensorDot, TensorElementwise, TensorFusion, TensorIndexing,
87 TensorReduction, TensorStructural,
88};
89use crate::config::{
90 CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
91};
92use crate::kernels::reduce::{self as cubecl_reduce, ReduceStrategy};
93use crate::kernels::{diagonal, elementwise, indexing, structural};
94use crate::native_permutation::{
95 NativePermutationKind, NativePermutationPlan, NativeTransposeTile,
96};
97use crate::{
98 DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement, StorageBuffer, Tensor, TensorRank,
99 TensorScalar, TensorView, TensorViewCanonicalization, TensorViewMut, TypedTensor,
100 TypedTensorView, TypedTensorViewMut,
101};
102
103mod capability;
104mod device;
105pub(crate) mod dispatch;
106mod error;
107mod event_domain;
108mod exec_session;
109mod ffi;
110mod fusion;
111mod gemm;
112mod identity;
113pub(crate) mod interop;
114mod memory;
115pub(crate) mod op_descriptor;
116mod permutation;
117pub(crate) mod raw;
118mod runtime;
119mod runtime_adapter;
120pub(crate) mod session_cubecl;
121
122use dispatch::{
123 alloc_bool_output, alloc_output, bool_tensor_array_arg, comptime_sequence, cube_count_for_len,
124 cube_dim_1d, dtype_mismatch, ensure_axes_unique, ensure_axis, ensure_rank,
125 ensure_resident_on_runtime, ensure_view_mut_resident_on_runtime,
126 ensure_view_resident_on_runtime, launch_binary, launch_binary_bool_tensor,
127 launch_binary_tensor, launch_bool_tensor_into, launch_compare_bool, launch_nullary_bool_into,
128 launch_nullary_into, launch_select_bool, launch_ternary, launch_unary,
129 launch_unary_bool_tensor, launch_unary_tensor, launch_unary_tensor_into,
130 ternary_dtype_mismatch, typed_tensor_array_arg, typed_tensor_array_arg_as,
131 typed_tensor_binding, typed_view_array_arg, typed_view_binding, typed_view_mut_array_arg,
132};
133use error::{unsupported_dtype, unsupported_operation};
134
135pub use capability::cuda_capabilities;
136pub use device::{cuda_devices, CudaDeviceError, CudaDeviceId, CudaDeviceInfo};
137#[doc(hidden)]
138pub use exec_session::{with_cuda_exec_session, CudaExecSession};
139pub use identity::{CudaComputeCapability, CudaDeviceUuid, GpuExtensionCapability};
140pub use memory::{download_tensor, upload_tensor};
141pub use runtime::{gpu_available, CudaRuntime, CudaRuntimeIdentity};
142pub use runtime_adapter::{cuda_runtime_engine_registration, cuda_runtime_hardware_class};
143
144fn op_name(
145 kind: PrimitiveOpKind,
146 launch: op_descriptor::GpuLaunchKind,
147) -> crate::Result<&'static str> {
148 op_descriptor::require_gpu_descriptor(kind, launch).map(|descriptor| descriptor.name)
149}
150
151fn ensure_atomic_add_supported<T: CubePrimitive>(
152 client: &ComputeClient<CubeclCudaRuntime>,
153 op: &'static str,
154) -> crate::Result<()> {
155 let elem = T::as_type_native_unchecked().elem_type();
156 let atomic_ty = Type::new(StorageType::Atomic(elem));
157 if client
158 .properties()
159 .atomic_type_usage(atomic_ty)
160 .contains(AtomicUsage::Add)
161 {
162 Ok(())
163 } else {
164 Err(unsupported_operation(
165 op,
166 "CubeCL runtime does not support atomic add",
167 ))
168 }
169}
170
171fn checked_dim_product(
172 op: &'static str,
173 role: &'static str,
174 shape: &[usize],
175) -> crate::Result<usize> {
176 shape.iter().try_fold(1usize, |acc, &dim| {
177 acc.checked_mul(dim).ok_or_else(|| {
178 crate::Error::invalid_argument(
179 op,
180 role,
181 format!("{role} product overflow for shape {shape:?}"),
182 )
183 })
184 })
185}
186
187fn view_strides_i64(strides: &[isize], op: &'static str) -> crate::Result<Vec<i64>> {
188 strides
189 .iter()
190 .map(|&stride| {
191 i64::try_from(stride).map_err(|_| {
192 crate::Error::invalid_argument(
193 op,
194 "layout",
195 format!("view stride {stride} exceeds CubeCL i64 metadata limit"),
196 )
197 })
198 })
199 .collect()
200}
201
202fn view_offset_i64(offset: isize, op: &'static str) -> crate::Result<i64> {
203 i64::try_from(offset).map_err(|_| {
204 crate::Error::invalid_argument(
205 op,
206 "layout",
207 format!("view offset {offset} exceeds CubeCL i64 metadata limit"),
208 )
209 })
210}
211
212fn launch_native_materialization<E: CubePrimitive>(
213 backend: &CudaBackend,
214 output: ArrayArg<CubeclCudaRuntime>,
215 input: ArrayArg<CubeclCudaRuntime>,
216 plan: &NativePermutationPlan,
217 op: &'static str,
218) -> crate::Result<()> {
219 if plan.len == 0 {
220 return Ok(());
221 }
222 if plan.kind == NativePermutationKind::TiledTranspose {
223 if let Some(config) = NativeTransposeTile::selected(op)? {
224 let block_rows = config.block_rows as usize;
225 let padding = config.padding as usize;
226 let vector_width = config.vector_width as usize;
227 let src_offset = usize::try_from(plan.src_offset).map_err(|_| {
228 crate::Error::invalid_argument(
229 op,
230 "offset",
231 "tiled transpose requires a non-negative source offset",
232 )
233 })?;
234 if let Some((cubes_x, cubes_y, cubes_z)) =
235 config.dispatch_grid(op, &plan.dims, 65_535)?
236 {
237 let batch_stride = plan.tiled_matrix_len(op)?;
238 unsafe {
239 structural::tiled_transpose_kernel::launch_unchecked::<E, CubeclCudaRuntime>(
243 backend.runtime().client(),
244 CubeCount::Static(cubes_x, cubes_y, cubes_z),
245 CubeDim::new_2d(config.tile / config.vector_width, config.block_rows),
246 output,
247 input,
248 src_offset,
249 batch_stride,
250 plan.dims[0],
251 plan.dims[1],
252 config.tile as usize,
253 block_rows,
254 padding,
255 vector_width,
256 );
257 }
258 return Ok(());
259 }
260 }
261 }
262 let src_strides = view_strides_i64(&plan.src_strides, op)?;
263 let src_offset = view_offset_i64(plan.src_offset, op)?;
264 unsafe {
265 structural::materialize_strided_kernel::launch_unchecked::<E, CubeclCudaRuntime>(
268 backend.runtime().client(),
269 cube_count_for_len(plan.len)?,
270 cube_dim_1d(),
271 output,
272 input,
273 comptime_sequence(&plan.dims),
274 comptime_sequence(&src_strides),
275 src_offset,
276 plan.len,
277 plan.dims.len(),
278 );
279 }
280 Ok(())
281}
282
283fn scatter_update_len(meta: &ScatterLaunchMeta) -> crate::Result<usize> {
284 let batch_len = checked_dim_product("scatter", "batch shape", &meta.batch_shape)?;
285 let window_len =
286 checked_dim_product("scatter", "window update shape", &meta.window_shape_updates)?;
287 batch_len.checked_mul(window_len).ok_or_else(|| {
288 crate::Error::invalid_argument(
289 "scatter",
290 "shape",
291 format!(
292 "scatter update domain product overflow for batch {:?} and window {:?}",
293 meta.batch_shape, meta.window_shape_updates
294 ),
295 )
296 })
297}
298
299#[doc(hidden)]
309struct CudaBackendSessionMarker;
310
311#[derive(Clone)]
312pub struct CudaBackend {
313 inner: Arc<CudaBackendState>,
314}
315
316struct CudaBackendState {
317 cutensor: OnceLock<ffi::cutensor::CutensorHandle>,
321 extension_cache: CudaExtensionCache,
322 rt: CudaRuntime,
323}
324
325impl fmt::Debug for CudaBackend {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.debug_struct("CudaBackend")
328 .field("runtime", &self.inner.rt)
329 .field("cuda_extension_cache", &self.inner.extension_cache)
330 .field("cutensor_initialized", &self.inner.cutensor.get().is_some())
331 .finish_non_exhaustive()
332 }
333}
334
335#[doc(hidden)]
337pub struct CudaExtensionCache {
338 inner: Mutex<CudaExtensionCacheInner>,
339}
340
341impl fmt::Debug for CudaExtensionCache {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 f.debug_struct("CudaExtensionCache")
344 .field("max_entries", &self.max_entries())
345 .field("stats", &self.stats())
346 .finish_non_exhaustive()
347 }
348}
349
350const DEFAULT_CUDA_EXTENSION_CACHE_MAX_ENTRIES: usize = 16;
351const DEFAULT_CUDA_EXTENSION_CACHE_RETAINED_BYTES: usize = 64 * 1024 * 1024;
352
353struct CudaExtensionCacheEntry {
354 value: Box<dyn Any + Send>,
355 retained_bytes: usize,
356}
357
358struct CudaExtensionCacheInner {
359 max_entries: NonZeroUsize,
360 max_retained_bytes: NonZeroUsize,
361 entries: HashMap<TypeId, CudaExtensionCacheEntry>,
362 order: VecDeque<TypeId>,
363 retained_bytes: usize,
364 stats: CacheStats,
365}
366
367impl CudaExtensionCacheInner {
368 fn new(max_entries: NonZeroUsize) -> Self {
369 Self {
370 max_entries,
371 max_retained_bytes: NonZeroUsize::new(DEFAULT_CUDA_EXTENSION_CACHE_RETAINED_BYTES)
372 .unwrap_or(NonZeroUsize::MIN),
373 entries: HashMap::new(),
374 order: VecDeque::new(),
375 retained_bytes: 0,
376 stats: CacheStats::empty(),
377 }
378 }
379
380 fn evict_to_limit(&mut self) {
381 while self.entries.len() > self.max_entries.get()
382 || self.retained_bytes > self.max_retained_bytes.get()
383 {
384 let Some(type_id) = self.order.pop_front() else {
385 break;
386 };
387 if let Some(entry) = self.entries.remove(&type_id) {
388 self.retained_bytes = self.retained_bytes.saturating_sub(entry.retained_bytes);
389 self.stats.evictions = self.stats.evictions.saturating_add(1);
390 }
391 }
392 }
393
394 fn insert<T: Send + 'static>(&mut self, type_id: TypeId, value: T, retained_bytes: usize) {
395 self.entries.insert(
396 type_id,
397 CudaExtensionCacheEntry {
398 value: Box::new(value),
399 retained_bytes,
400 },
401 );
402 self.order.retain(|&existing| existing != type_id);
403 self.order.push_back(type_id);
404 self.retained_bytes = self
405 .entries
406 .values()
407 .map(|entry| entry.retained_bytes)
408 .sum();
409 self.evict_to_limit();
410 }
411
412 fn snapshot_stats(&self) -> CacheStats {
413 CacheStats {
414 entries: self.entries.len(),
415 retained_bytes: self.retained_bytes,
416 ..self.stats
417 }
418 }
419
420 fn refresh_retained_bytes(&mut self) {
421 self.retained_bytes = self
422 .entries
423 .values()
424 .map(|entry| entry.retained_bytes)
425 .sum();
426 }
427}
428
429impl CudaExtensionCache {
430 fn poisoned_lock_error() -> crate::Error {
431 crate::Error::runtime_state("cuda_extension_cache", "extension cache lock poisoned")
432 }
433
434 fn lock_inner(&self) -> crate::Result<MutexGuard<'_, CudaExtensionCacheInner>> {
435 self.inner.lock().map_err(|_| Self::poisoned_lock_error())
436 }
437
438 pub fn new() -> Self {
455 let max_entries = NonZeroUsize::new(DEFAULT_CUDA_EXTENSION_CACHE_MAX_ENTRIES)
456 .unwrap_or(NonZeroUsize::MIN);
457 Self::with_max_entries(max_entries)
458 }
459
460 pub fn with_max_entries(max_entries: NonZeroUsize) -> Self {
462 Self {
463 inner: Mutex::new(CudaExtensionCacheInner::new(max_entries)),
464 }
465 }
466
467 pub fn is_empty(&self) -> crate::Result<bool> {
481 Ok(self.lock_inner()?.entries.is_empty())
482 }
483
484 pub fn clear(&self) -> crate::Result<()> {
493 let mut inner = self.lock_inner()?;
494 inner.entries.clear();
495 inner.order.clear();
496 inner.retained_bytes = 0;
497 let clears = inner.stats.clears.saturating_add(1);
498 inner.stats = CacheStats {
499 clears,
500 ..CacheStats::empty()
501 };
502 Ok(())
503 }
504
505 pub fn stats(&self) -> crate::Result<CacheStats> {
510 let inner = self.lock_inner()?;
511 Ok(inner.snapshot_stats())
512 }
513
514 pub fn max_entries(&self) -> crate::Result<NonZeroUsize> {
519 Ok(self.lock_inner()?.max_entries)
520 }
521
522 pub fn max_retained_bytes(&self) -> crate::Result<NonZeroUsize> {
527 Ok(self.lock_inner()?.max_retained_bytes)
528 }
529
530 pub fn set_max_entries(&self, max_entries: NonZeroUsize) -> crate::Result<()> {
536 let mut inner = self.lock_inner()?;
537 inner.max_entries = max_entries;
538 inner.evict_to_limit();
539 Ok(())
540 }
541
542 pub fn set_max_retained_bytes(&self, max_retained_bytes: NonZeroUsize) -> crate::Result<()> {
549 let mut inner = self.lock_inner()?;
550 inner.max_retained_bytes = max_retained_bytes;
551 inner.evict_to_limit();
552 Ok(())
553 }
554
555 pub fn get_or_try_init<T>(
572 &self,
573 init: impl FnOnce() -> crate::Result<T>,
574 ) -> crate::Result<CudaExtensionCacheGuard<'_, T>>
575 where
576 T: Send + 'static,
577 {
578 let type_id = TypeId::of::<T>();
579 let mut inner = self.lock_inner()?;
580 if !inner.entries.contains_key(&type_id) {
581 inner.stats.misses = inner.stats.misses.saturating_add(1);
582 inner.insert(type_id, init()?, std::mem::size_of::<T>());
583 } else {
584 inner.stats.hits = inner.stats.hits.saturating_add(1);
585 }
586 let value = inner
587 .entries
588 .get(&type_id)
589 .and_then(|entry| entry.value.downcast_ref::<T>())
590 .map(NonNull::from)
591 .ok_or_else(|| {
592 crate::Error::runtime_state(
593 "cuda_extension_cache",
594 format!(
595 "stored entry for {} is missing or has the wrong type",
596 std::any::type_name::<T>()
597 ),
598 )
599 })?;
600 Ok(CudaExtensionCacheGuard {
601 inner,
602 type_id,
603 value,
604 _marker: std::marker::PhantomData,
605 })
606 }
607
608 pub(crate) fn get_cloned<T>(&self) -> crate::Result<Option<T>>
609 where
610 T: Clone + 'static,
611 {
612 let inner = self.lock_inner()?;
613 inner
614 .entries
615 .get(&TypeId::of::<T>())
616 .map(|entry| {
617 entry.value.downcast_ref::<T>().cloned().ok_or_else(|| {
618 crate::Error::runtime_state(
619 "cuda_extension_cache",
620 format!(
621 "stored entry for {} is missing or has the wrong type",
622 std::any::type_name::<T>()
623 ),
624 )
625 })
626 })
627 .transpose()
628 }
629
630 pub(crate) fn update_retained_bytes<T: 'static>(
639 &self,
640 retained_bytes: usize,
641 ) -> crate::Result<()> {
642 let type_id = TypeId::of::<T>();
643 let mut inner = self.lock_inner()?;
644 if let Some(entry) = inner.entries.get_mut(&type_id) {
645 entry.retained_bytes = retained_bytes;
646 inner.refresh_retained_bytes();
647 inner.evict_to_limit();
648 }
649 Ok(())
650 }
651}
652
653impl Default for CudaExtensionCache {
654 fn default() -> Self {
655 Self::new()
656 }
657}
658
659#[doc(hidden)]
661pub struct CudaExtensionCacheGuard<'a, T> {
662 inner: MutexGuard<'a, CudaExtensionCacheInner>,
663 type_id: TypeId,
664 value: NonNull<T>,
665 _marker: std::marker::PhantomData<&'a T>,
666}
667
668impl<T: 'static> fmt::Debug for CudaExtensionCacheGuard<'_, T> {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670 let retained_bytes = self
671 .inner
672 .entries
673 .get(&self.type_id)
674 .map(|entry| entry.retained_bytes)
675 .unwrap_or(0);
676 f.debug_struct("CudaExtensionCacheGuard")
677 .field("value_type", &std::any::type_name::<T>())
678 .field("retained_bytes", &retained_bytes)
679 .finish_non_exhaustive()
680 }
681}
682
683impl<T: 'static> Deref for CudaExtensionCacheGuard<'_, T> {
684 type Target = T;
685
686 fn deref(&self) -> &Self::Target {
687 unsafe { self.value.as_ref() }
691 }
692}
693
694impl CudaBackend {
695 fn duplicate_typed<T>(&self, input: &TypedTensor<T>) -> crate::Result<TypedTensor<T>>
696 where
697 T: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
698 {
699 self.to_contiguous_view_typed(&input.as_view(), "cast")
703 }
704
705 fn duplicate_bool(
706 &self,
707 input: &TypedTensor<bool>,
708 op: &'static str,
709 ) -> crate::Result<TypedTensor<bool>> {
710 launch_unary_bool_tensor(
711 self.runtime(),
712 input,
713 input.shape(),
714 op,
715 |client, count, dim, out, input_arg| unsafe {
716 structural::copy_bool_kernel::launch_unchecked::<CubeclCudaRuntime>(
717 client,
718 count,
719 dim,
720 out.into_array_arg(),
721 input_arg.into_array_arg(),
722 );
723 },
724 )
725 }
726
727 pub fn new(device_id: CudaDeviceId) -> Result<Self, CudaDeviceError> {
743 Ok(Self {
744 inner: Arc::new(CudaBackendState {
745 cutensor: OnceLock::new(),
746 extension_cache: CudaExtensionCache::new(),
747 rt: CudaRuntime::new(device_id)?,
748 }),
749 })
750 }
751
752 pub fn runtime(&self) -> &CudaRuntime {
762 &self.inner.rt
763 }
764
765 pub fn device_id(&self) -> CudaDeviceId {
775 self.inner.rt.device_id()
776 }
777
778 pub fn runtime_identity(&self) -> CudaRuntimeIdentity {
792 self.inner.rt.runtime_identity()
793 }
794
795 fn cutensor_handle(&self) -> crate::Result<&ffi::cutensor::CutensorHandle> {
796 if let Some(handle) = self.inner.cutensor.get() {
797 return Ok(handle);
798 }
799 let handle = ffi::cutensor::CutensorHandle::load()?;
800 let _ = self.inner.cutensor.set(handle);
801 self.inner.cutensor.get().ok_or_else(|| {
802 crate::Error::runtime_state(
803 "cuda_cutensor",
804 "cuTENSOR handle initialization completed without a stored handle",
805 )
806 })
807 }
808
809 #[doc(hidden)]
810 pub fn cuda_extension_cache(&self) -> &CudaExtensionCache {
811 &self.inner.extension_cache
812 }
813
814 pub fn clear_cuda_extension_cache(&self) -> crate::Result<()> {
821 self.inner.extension_cache.clear()
822 }
823
824 pub fn cuda_extension_cache_stats(&self) -> crate::Result<CacheStats> {
831 self.inner.extension_cache.stats()
832 }
833
834 pub fn cuda_extension_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
841 self.inner.extension_cache.max_entries()
842 }
843
844 pub fn cuda_extension_cache_max_retained_bytes(&self) -> crate::Result<NonZeroUsize> {
851 self.inner.extension_cache.max_retained_bytes()
852 }
853
854 pub fn set_cuda_extension_cache_max_entries(
861 &self,
862 max_entries: NonZeroUsize,
863 ) -> crate::Result<()> {
864 self.inner.extension_cache.set_max_entries(max_entries)
865 }
866
867 pub fn set_cuda_extension_cache_max_retained_bytes(
874 &self,
875 max_retained_bytes: NonZeroUsize,
876 ) -> crate::Result<()> {
877 self.inner
878 .extension_cache
879 .set_max_retained_bytes(max_retained_bytes)
880 }
881
882 pub fn cutensor_plan_cache_stats(&self) -> crate::Result<CacheStats> {
891 gemm::cutensor_plan_cache_stats(self)
892 }
893
894 pub fn cutensor_plan_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
899 gemm::cutensor_plan_cache_max_entries(self)
900 }
901
902 pub fn set_cutensor_plan_cache_max_entries(
910 &self,
911 max_entries: NonZeroUsize,
912 ) -> crate::Result<()> {
913 gemm::set_cutensor_plan_cache_max_entries(self, max_entries)
914 }
915
916 pub fn cutensor_permutation_plan_cache_stats(&self) -> crate::Result<CacheStats> {
925 permutation::cutensor_permutation_plan_cache_stats(self)
926 }
927
928 pub fn cutensor_permutation_plan_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
933 permutation::cutensor_permutation_plan_cache_max_entries(self)
934 }
935
936 pub fn set_cutensor_permutation_plan_cache_max_entries(
944 &self,
945 max_entries: NonZeroUsize,
946 ) -> crate::Result<()> {
947 permutation::set_cutensor_permutation_plan_cache_max_entries(self, max_entries)
948 }
949
950 fn transpose_typed<T>(
951 &self,
952 input: &TypedTensor<T>,
953 perm: &[usize],
954 ) -> crate::Result<TypedTensor<T>>
955 where
956 T: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
957 {
958 validate_permutation("transpose", perm, input.shape().len())?;
959 let output_shape: Vec<usize> = perm.iter().map(|&axis| input.shape()[axis]).collect();
960 ensure_resident_on_runtime(self.runtime(), input, "transpose")?;
961 let input_strides =
962 crate::native_permutation::compact_col_major_strides("transpose", input.shape())?;
963 let plan = NativePermutationPlan::for_transpose(
964 "transpose",
965 input.shape(),
966 &input_strides,
967 perm,
968 0,
969 input.n_elements(),
970 input.n_elements(),
971 false,
972 )?;
973 let output = alloc_output::<T>(self.runtime(), &output_shape)?;
974 let output_arg = typed_tensor_array_arg(&output, "transpose")?;
975 let input_arg = typed_tensor_array_arg(input, "transpose")?;
976 launch_native_materialization::<T>(self, output_arg, input_arg, &plan, "transpose")?;
977 Ok(output)
978 }
979
980 fn transpose_bool(
981 &self,
982 input: &TypedTensor<bool>,
983 perm: &[usize],
984 ) -> crate::Result<TypedTensor<bool>> {
985 validate_permutation("transpose", perm, input.shape().len())?;
986 let output_shape: Vec<usize> = perm.iter().map(|&axis| input.shape()[axis]).collect();
987 ensure_resident_on_runtime(self.runtime(), input, "transpose")?;
988 let input_strides =
989 crate::native_permutation::compact_col_major_strides("transpose", input.shape())?;
990 let plan = NativePermutationPlan::for_transpose(
991 "transpose",
992 input.shape(),
993 &input_strides,
994 perm,
995 0,
996 input.n_elements(),
997 input.n_elements(),
998 false,
999 )?;
1000 let output = alloc_bool_output(self.runtime(), &output_shape)?;
1001 let output_arg = bool_tensor_array_arg(&output, "transpose")?;
1002 let input_arg = bool_tensor_array_arg(input, "transpose")?;
1003 launch_native_materialization::<u8>(self, output_arg, input_arg, &plan, "transpose")?;
1004 Ok(output)
1005 }
1006
1007 fn broadcast_typed<T>(
1008 &self,
1009 input: &TypedTensor<T>,
1010 shape: &[usize],
1011 dims: &[usize],
1012 ) -> crate::Result<TypedTensor<T>>
1013 where
1014 T: CubeElement + TensorScalar + CubePrimitive + Clone,
1015 {
1016 validate_broadcast_in_dim(input.shape(), shape, dims)?;
1017 launch_unary_tensor(
1018 self.runtime(),
1019 input,
1020 shape,
1021 "broadcast_in_dim",
1022 |client, count, dim, out, input_arg| unsafe {
1023 structural::broadcast_in_dim_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1024 client,
1025 count,
1026 dim,
1027 out.into_tensor_arg(),
1028 input_arg.into_tensor_arg(),
1029 comptime_sequence(dims),
1030 shape.len(),
1031 );
1032 },
1033 )
1034 }
1035
1036 fn broadcast_bool(
1037 &self,
1038 input: &TypedTensor<bool>,
1039 shape: &[usize],
1040 dims: &[usize],
1041 ) -> crate::Result<TypedTensor<bool>> {
1042 validate_broadcast_in_dim(input.shape(), shape, dims)?;
1043 launch_unary_bool_tensor(
1044 self.runtime(),
1045 input,
1046 shape,
1047 "broadcast_in_dim",
1048 |client, count, dim, out, input_arg| unsafe {
1049 structural::broadcast_in_dim_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1050 client,
1051 count,
1052 dim,
1053 out.into_tensor_arg(),
1054 input_arg.into_tensor_arg(),
1055 comptime_sequence(dims),
1056 shape.len(),
1057 );
1058 },
1059 )
1060 }
1061
1062 fn reverse_typed<T>(
1063 &self,
1064 input: &TypedTensor<T>,
1065 axes: &[usize],
1066 ) -> crate::Result<TypedTensor<T>>
1067 where
1068 T: CubeElement + TensorScalar + CubePrimitive + Clone,
1069 {
1070 ensure_axes_unique("reverse", "axes", axes, input.shape().len())?;
1071 launch_unary_tensor(
1072 self.runtime(),
1073 input,
1074 input.shape(),
1075 "reverse",
1076 |client, count, dim, out, input_arg| unsafe {
1077 structural::reverse_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1078 client,
1079 count,
1080 dim,
1081 out.into_tensor_arg(),
1082 input_arg.into_tensor_arg(),
1083 comptime_sequence(axes),
1084 input.shape().len(),
1085 );
1086 },
1087 )
1088 }
1089
1090 fn reverse_bool(
1091 &self,
1092 input: &TypedTensor<bool>,
1093 axes: &[usize],
1094 ) -> crate::Result<TypedTensor<bool>> {
1095 ensure_axes_unique("reverse", "axes", axes, input.shape().len())?;
1096 launch_unary_bool_tensor(
1097 self.runtime(),
1098 input,
1099 input.shape(),
1100 "reverse",
1101 |client, count, dim, out, input_arg| unsafe {
1102 structural::reverse_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1103 client,
1104 count,
1105 dim,
1106 out.into_tensor_arg(),
1107 input_arg.into_tensor_arg(),
1108 comptime_sequence(axes),
1109 input.shape().len(),
1110 );
1111 },
1112 )
1113 }
1114
1115 fn alloc_ranked_output<T, R>(
1116 &self,
1117 shape: &[usize],
1118 op: &'static str,
1119 ) -> crate::Result<TypedTensor<T, R>>
1120 where
1121 T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
1122 R: TensorRank,
1123 {
1124 let len = checked_dim_product(op, "output shape", shape)?;
1125 let bytes = len.checked_mul(core::mem::size_of::<T>()).ok_or_else(|| {
1126 crate::Error::invalid_argument(
1127 op,
1128 "shape",
1129 format!("CubeCL output byte length overflow for shape {shape:?}"),
1130 )
1131 })?;
1132 let handle = self.runtime().client().empty(bytes);
1133 let shape = R::shape_from_vec(shape.to_vec().into())
1134 .map_err(|err| crate::Error::validation(op, err))?;
1135 TypedTensor::from_buffer_col_major(
1136 shape,
1137 StorageBuffer::Backend(Box::new(crate::CubeclBuffer::new(
1138 handle,
1139 bytes,
1140 self.runtime().device_ordinal(),
1141 self.runtime().allocation_domain_id(),
1142 ))),
1143 Placement {
1144 memory_kind: MemoryKind::Device,
1145 device: Some(DeviceId {
1146 kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
1147 ordinal: self.runtime().device_ordinal(),
1148 }),
1149 cpu_affinity: None,
1150 },
1151 )
1152 }
1153
1154 fn to_contiguous_view_typed<T, R>(
1155 &self,
1156 view: &TypedTensorView<'_, T, R>,
1157 op: &'static str,
1158 ) -> crate::Result<TypedTensor<T, R>>
1159 where
1160 T: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
1161 R: TensorRank,
1162 {
1163 ensure_view_resident_on_runtime(self.runtime(), view, op)?;
1164 let len = checked_dim_product(op, "output shape", view.shape())?;
1165 let source_allocation_len = view
1166 .backend_buffer()
1167 .map(|buffer| buffer.len())
1168 .ok_or_else(|| {
1169 crate::Error::runtime_state(op, "expected CUDA backend view, got host view")
1170 })?;
1171 let plan = NativePermutationPlan::for_contiguous_output(
1172 op,
1173 view.shape(),
1174 view.strides(),
1175 view.offset(),
1176 source_allocation_len,
1177 len,
1178 false,
1179 )?;
1180 let output = self.alloc_ranked_output::<T, R>(view.shape(), op)?;
1181 let output_arg = typed_tensor_array_arg(&output, op)?;
1182 let input_arg = typed_view_array_arg(view, op)?;
1183 launch_native_materialization::<T>(self, output_arg, input_arg, &plan, op)?;
1184 Ok(output)
1185 }
1186
1187 fn to_contiguous_view_cutensor_or_cubecl<T, R>(
1188 &self,
1189 view: &TypedTensorView<'_, T, R>,
1190 op: &'static str,
1191 ) -> crate::Result<TypedTensor<T, R>>
1192 where
1193 T: permutation::CutensorPermutationScalar,
1194 R: TensorRank,
1195 {
1196 if view.strides().iter().any(|&stride| stride < 0) {
1197 return self.to_contiguous_view_typed(view, op);
1202 }
1203 permutation::to_contiguous_view(self, view, op)
1204 }
1205
1206 fn copy_view_to_view_typed<T, R>(
1207 &self,
1208 src: &TypedTensorView<'_, T, R>,
1209 dst: &mut TypedTensorViewMut<'_, T, R>,
1210 op: &'static str,
1211 ) -> crate::Result<()>
1212 where
1213 T: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
1214 R: TensorRank,
1215 {
1216 ensure_view_resident_on_runtime(self.runtime(), src, op)?;
1217 ensure_view_mut_resident_on_runtime(self.runtime(), dst, op)?;
1218 if src.shape() != dst.shape() {
1219 return Err(crate::Error::shape_mismatch(
1220 op,
1221 src.shape().to_vec(),
1222 dst.shape().to_vec(),
1223 ));
1224 }
1225 if src.offset() != 0 || !src.is_col_major_contiguous()? {
1226 return Err(crate::Error::invalid_argument(
1227 op,
1228 "source",
1229 "CUDA copy_into requires a compact source view covering its full allocation; arbitrary-stride source views are unsupported without explicit canonicalization",
1230 ));
1231 }
1232 let source_buffer = src.backend_buffer().ok_or_else(|| {
1233 crate::Error::runtime_state(
1234 op,
1235 "CUDA backend expected a GPU source view; call upload_tensor() first",
1236 )
1237 })?;
1238 let destination_buffer = dst.backend_buffer().ok_or_else(|| {
1239 crate::Error::runtime_state(
1240 op,
1241 "CUDA backend expected a GPU destination view; call upload_tensor() first",
1242 )
1243 })?;
1244 if std::ptr::eq(source_buffer, destination_buffer) {
1245 return Err(crate::Error::invalid_argument(
1246 op,
1247 "source/destination",
1248 "CUDA copy_into source and destination allocations must not alias",
1249 ));
1250 }
1251 let len = src.n_elements();
1252 if len == 0 {
1253 return Ok(());
1254 }
1255 let strides = view_strides_i64(dst.strides(), op)?;
1256 let base_offset = view_offset_i64(dst.offset(), op)?;
1257 let src_arg = typed_view_binding(src, op)?;
1258 let dst_arg = typed_view_mut_array_arg(dst, op)?;
1259 let rank = dst.shape().len();
1260 unsafe {
1261 structural::contiguous_to_view_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1268 self.runtime().client(),
1269 cube_count_for_len(len)?,
1270 cube_dim_1d(),
1271 dst_arg,
1272 src_arg.into_tensor_arg(),
1273 comptime_sequence(&strides),
1274 base_offset,
1275 rank,
1276 );
1277 }
1278 Ok(())
1279 }
1280
1281 fn copy_view_to_view_cutensor_or_cubecl<T, R>(
1282 &self,
1283 src: &TypedTensorView<'_, T, R>,
1284 dst: &mut TypedTensorViewMut<'_, T, R>,
1285 op: &'static str,
1286 ) -> crate::Result<()>
1287 where
1288 T: permutation::CutensorPermutationScalar,
1289 R: TensorRank,
1290 {
1291 if dst.strides().iter().any(|&stride| stride < 0) {
1292 return self.copy_view_to_view_typed(src, dst, op);
1293 }
1294 permutation::copy_view_into(self, src, dst, op)
1295 }
1296
1297 fn convert_float_to_float<In, Out>(
1298 &self,
1299 input: &TypedTensor<In>,
1300 ) -> crate::Result<TypedTensor<Out>>
1301 where
1302 In: CubeElement + TensorScalar + CubeFloat + Clone,
1303 Out: CubeElement + TensorScalar + CubeFloat + Clone,
1304 {
1305 launch_unary(
1306 self.runtime(),
1307 input,
1308 input.shape(),
1309 "convert",
1310 |client, count, dim, out, input_arg| unsafe {
1311 structural::convert_float_to_float::launch_unchecked::<Out, In, CubeclCudaRuntime>(
1312 client, count, dim, out, input_arg,
1313 );
1314 },
1315 )
1316 }
1317
1318 fn convert_numeric<In, Out>(&self, input: &TypedTensor<In>) -> crate::Result<TypedTensor<Out>>
1319 where
1320 In: CubeElement + TensorScalar + CubeNumeric + Clone,
1321 Out: CubeElement + TensorScalar + CubeNumeric + Clone,
1322 {
1323 self.launch_cast_unary(input, |client, count, dim, out, input| unsafe {
1324 structural::convert_numeric::launch_unchecked::<Out, In, CubeclCudaRuntime>(
1325 client, count, dim, out, input,
1326 );
1327 })
1328 }
1329
1330 fn launch_cast_unary<In, Out>(
1331 &self,
1332 input: &TypedTensor<In>,
1333 launch: impl FnOnce(
1334 &ComputeClient<CubeclCudaRuntime>,
1335 CubeCount,
1336 CubeDim,
1337 ArrayArg<CubeclCudaRuntime>,
1338 ArrayArg<CubeclCudaRuntime>,
1339 ),
1340 ) -> crate::Result<TypedTensor<Out>>
1341 where
1342 In: CubeElement + TensorScalar + Clone,
1343 Out: CubeElement + TensorScalar + Clone,
1344 {
1345 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1346 let input_arg = typed_tensor_array_arg(input, "cast")?;
1347 let n = input.n_elements();
1348 let count = if n == 0 {
1349 None
1350 } else {
1351 Some(cube_count_for_len(n)?)
1352 };
1353 let output = alloc_output::<Out>(self.runtime(), input.shape())?;
1354 let Some(count) = count else {
1355 return Ok(output);
1356 };
1357 let output_arg = typed_tensor_array_arg(&output, "cast")?;
1358 launch(
1359 self.runtime().client(),
1360 count,
1361 cube_dim_1d(),
1362 output_arg,
1363 input_arg,
1364 );
1365 Ok(output)
1366 }
1367
1368 fn convert_numeric_to_bool<In>(
1369 &self,
1370 input: &TypedTensor<In>,
1371 ) -> crate::Result<TypedTensor<bool>>
1372 where
1373 In: CubeElement + TensorScalar + CubeNumeric + Clone,
1374 {
1375 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1376 let input_arg = typed_tensor_array_arg(input, "cast")?;
1377 let n = input.n_elements();
1378 let count = if n == 0 {
1379 None
1380 } else {
1381 Some(cube_count_for_len(n)?)
1382 };
1383 let output = alloc_bool_output(self.runtime(), input.shape())?;
1384 let Some(count) = count else {
1385 return Ok(output);
1386 };
1387 let output_arg = bool_tensor_array_arg(&output, "cast")?;
1388 unsafe {
1389 structural::convert_numeric_to_bool::launch_unchecked::<In, CubeclCudaRuntime>(
1390 self.runtime().client(),
1391 count,
1392 cube_dim_1d(),
1393 output_arg,
1394 input_arg,
1395 );
1396 }
1397 Ok(output)
1398 }
1399
1400 fn convert_bool_to_numeric<Out>(
1401 &self,
1402 input: &TypedTensor<bool>,
1403 ) -> crate::Result<TypedTensor<Out>>
1404 where
1405 Out: CubeElement + TensorScalar + CubeNumeric + Clone,
1406 {
1407 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1408 let input_arg = bool_tensor_array_arg(input, "cast")?;
1409 let n = input.n_elements();
1410 let count = if n == 0 {
1411 None
1412 } else {
1413 Some(cube_count_for_len(n)?)
1414 };
1415 let output = alloc_output::<Out>(self.runtime(), input.shape())?;
1416 let Some(count) = count else {
1417 return Ok(output);
1418 };
1419 let output_arg = typed_tensor_array_arg(&output, "cast")?;
1420 unsafe {
1421 structural::convert_bool_to_numeric::launch_unchecked::<Out, CubeclCudaRuntime>(
1422 self.runtime().client(),
1423 count,
1424 cube_dim_1d(),
1425 output_arg,
1426 input_arg,
1427 );
1428 }
1429 Ok(output)
1430 }
1431
1432 fn convert_numeric_to_complex<In, OutComplex, OutFloat>(
1433 &self,
1434 input: &TypedTensor<In>,
1435 ) -> crate::Result<TypedTensor<OutComplex>>
1436 where
1437 In: CubeElement + TensorScalar + CubeNumeric + Clone,
1438 OutComplex: CubeElement + TensorScalar + Clone,
1439 OutFloat: CubeElement + CubeFloat + Clone,
1440 {
1441 self.convert_float_to_complex_raw::<In, OutComplex, OutFloat>(
1442 input,
1443 |client, out, input, count| {
1444 unsafe {
1445 structural::convert_numeric_to_complex_raw::launch_unchecked::<
1446 OutFloat,
1447 In,
1448 CubeclCudaRuntime,
1449 >(client, count, cube_dim_1d(), out, input);
1450 }
1451 Ok(())
1452 },
1453 )
1454 }
1455
1456 fn convert_bool_to_complex<OutComplex, OutFloat>(
1457 &self,
1458 input: &TypedTensor<bool>,
1459 ) -> crate::Result<TypedTensor<OutComplex>>
1460 where
1461 OutComplex: CubeElement + TensorScalar + Clone,
1462 OutFloat: CubeElement + CubeFloat + Clone,
1463 {
1464 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1465 let n = input.n_elements();
1466 let part_len = n.checked_mul(2).ok_or_else(|| {
1467 crate::Error::invalid_argument("cast", "shape", "complex output part length overflow")
1468 })?;
1469 let input_arg = bool_tensor_array_arg(input, "cast")?;
1470 let count = if n == 0 {
1471 None
1472 } else {
1473 Some(cube_count_for_len(n)?)
1474 };
1475 let output = alloc_output::<OutComplex>(self.runtime(), input.shape())?;
1476 let Some(count) = count else {
1477 return Ok(output);
1478 };
1479 let out = typed_tensor_array_arg_as::<OutComplex, OutFloat>(&output, part_len, "cast")?;
1480 unsafe {
1481 structural::convert_bool_to_complex_raw::launch_unchecked::<OutFloat, CubeclCudaRuntime>(
1482 self.runtime().client(),
1483 count,
1484 cube_dim_1d(),
1485 out,
1486 input_arg,
1487 );
1488 }
1489 Ok(output)
1490 }
1491
1492 fn convert_complex_to_numeric<In, Out>(
1493 &self,
1494 input: &TypedTensor<In>,
1495 ) -> crate::Result<TypedTensor<Out>>
1496 where
1497 In: CubeElement + TensorScalar + CubeComplex + Clone,
1498 Out: CubeElement + TensorScalar + CubeNumeric + Clone,
1499 {
1500 self.launch_cast_unary(input, |client, count, dim, out, input| unsafe {
1501 structural::convert_complex_to_numeric::launch_unchecked::<Out, In, CubeclCudaRuntime>(
1502 client, count, dim, out, input,
1503 );
1504 })
1505 }
1506
1507 fn convert_complex_to_bool<In, F>(
1508 &self,
1509 input: &TypedTensor<In>,
1510 ) -> crate::Result<TypedTensor<bool>>
1511 where
1512 In: CubeElement + TensorScalar + CubeComplex<FloatElem = F> + Clone,
1513 F: CubeElement + TensorScalar + CubeFloat,
1514 {
1515 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1516 let part_len = input.n_elements().checked_mul(2).ok_or_else(|| {
1517 crate::Error::invalid_argument("cast", "shape", "complex input part length overflow")
1518 })?;
1519 let input_arg = typed_tensor_array_arg_as::<In, F>(input, part_len, "cast")?;
1520 let n = input.n_elements();
1521 let count = if n == 0 {
1522 None
1523 } else {
1524 Some(cube_count_for_len(n)?)
1525 };
1526 let output = alloc_bool_output(self.runtime(), input.shape())?;
1527 let Some(count) = count else {
1528 return Ok(output);
1529 };
1530 let output_arg = bool_tensor_array_arg(&output, "cast")?;
1531 unsafe {
1532 structural::convert_complex_raw_to_bool::launch_unchecked::<F, CubeclCudaRuntime>(
1533 self.runtime().client(),
1534 count,
1535 cube_dim_1d(),
1536 output_arg,
1537 input_arg,
1538 );
1539 }
1540 Ok(output)
1541 }
1542
1543 fn convert_f32_to_c32(
1544 &self,
1545 input: &TypedTensor<f32>,
1546 ) -> crate::Result<TypedTensor<Complex32>> {
1547 self.convert_float_to_complex_raw::<f32, Complex32, f32>(
1548 input,
1549 |client, out, input, count| {
1550 unsafe {
1551 structural::convert_f32_to_c32_raw::launch_unchecked::<CubeclCudaRuntime>(
1556 client,
1557 count,
1558 cube_dim_1d(),
1559 out,
1560 input,
1561 );
1562 }
1563 Ok(())
1564 },
1565 )
1566 }
1567
1568 fn convert_f32_to_c64(
1569 &self,
1570 input: &TypedTensor<f32>,
1571 ) -> crate::Result<TypedTensor<Complex64>> {
1572 self.convert_float_to_complex_raw::<f32, Complex64, f64>(
1573 input,
1574 |client, out, input, count| {
1575 unsafe {
1576 structural::convert_f32_to_c64_raw::launch_unchecked::<CubeclCudaRuntime>(
1581 client,
1582 count,
1583 cube_dim_1d(),
1584 out,
1585 input,
1586 );
1587 }
1588 Ok(())
1589 },
1590 )
1591 }
1592
1593 fn convert_f64_to_c32(
1594 &self,
1595 input: &TypedTensor<f64>,
1596 ) -> crate::Result<TypedTensor<Complex32>> {
1597 self.convert_float_to_complex_raw::<f64, Complex32, f32>(
1598 input,
1599 |client, out, input, count| {
1600 unsafe {
1601 structural::convert_f64_to_c32_raw::launch_unchecked::<CubeclCudaRuntime>(
1606 client,
1607 count,
1608 cube_dim_1d(),
1609 out,
1610 input,
1611 );
1612 }
1613 Ok(())
1614 },
1615 )
1616 }
1617
1618 fn convert_f64_to_c64(
1619 &self,
1620 input: &TypedTensor<f64>,
1621 ) -> crate::Result<TypedTensor<Complex64>> {
1622 self.convert_float_to_complex_raw::<f64, Complex64, f64>(
1623 input,
1624 |client, out, input, count| {
1625 unsafe {
1626 structural::convert_f64_to_c64_raw::launch_unchecked::<CubeclCudaRuntime>(
1631 client,
1632 count,
1633 cube_dim_1d(),
1634 out,
1635 input,
1636 );
1637 }
1638 Ok(())
1639 },
1640 )
1641 }
1642
1643 fn convert_float_to_complex_raw<InFloat, OutComplex, OutFloat>(
1648 &self,
1649 input: &TypedTensor<InFloat>,
1650 launch: impl FnOnce(
1651 &cubecl::client::ComputeClient<CubeclCudaRuntime>,
1652 ArrayArg<CubeclCudaRuntime>,
1653 ArrayArg<CubeclCudaRuntime>,
1654 CubeCount,
1655 ) -> crate::Result<()>,
1656 ) -> crate::Result<TypedTensor<OutComplex>>
1657 where
1658 InFloat: CubeElement + TensorScalar + Clone,
1659 OutComplex: CubeElement + TensorScalar + Clone,
1660 OutFloat: CubeElement + Clone,
1661 {
1662 ensure_resident_on_runtime(self.runtime(), input, "convert")?;
1663 let input_arg = typed_tensor_array_arg(input, "convert")?;
1664 let n = input.n_elements();
1665 let output_part_len = n.checked_mul(2).ok_or_else(|| {
1666 crate::Error::invalid_argument(
1667 "convert",
1668 "shape",
1669 "complex output part length overflow",
1670 )
1671 })?;
1672 let count = if n == 0 {
1673 None
1674 } else {
1675 Some(cube_count_for_len(n)?)
1676 };
1677 let output = alloc_output::<OutComplex>(self.runtime(), input.shape())?;
1678 let Some(count) = count else {
1679 return Ok(output);
1680 };
1681 let output_parts =
1682 typed_tensor_array_arg_as::<OutComplex, OutFloat>(&output, output_part_len, "convert")?;
1683 launch(self.runtime().client(), output_parts, input_arg, count)?;
1687 Ok(output)
1688 }
1689
1690 fn convert_c32_to_f32(
1691 &self,
1692 input: &TypedTensor<Complex32>,
1693 ) -> crate::Result<TypedTensor<f32>> {
1694 launch_unary(
1695 self.runtime(),
1696 input,
1697 input.shape(),
1698 "convert",
1699 |client, count, dim, out, input_arg| unsafe {
1700 structural::convert_c32_to_f32::launch_unchecked::<CubeclCudaRuntime>(
1701 client, count, dim, out, input_arg,
1702 );
1703 },
1704 )
1705 }
1706
1707 fn convert_c32_to_f64(
1708 &self,
1709 input: &TypedTensor<Complex32>,
1710 ) -> crate::Result<TypedTensor<f64>> {
1711 launch_unary(
1712 self.runtime(),
1713 input,
1714 input.shape(),
1715 "convert",
1716 |client, count, dim, out, input_arg| unsafe {
1717 structural::convert_c32_to_f64::launch_unchecked::<CubeclCudaRuntime>(
1718 client, count, dim, out, input_arg,
1719 );
1720 },
1721 )
1722 }
1723
1724 fn convert_c64_to_f32(
1725 &self,
1726 input: &TypedTensor<Complex64>,
1727 ) -> crate::Result<TypedTensor<f32>> {
1728 launch_unary(
1729 self.runtime(),
1730 input,
1731 input.shape(),
1732 "convert",
1733 |client, count, dim, out, input_arg| unsafe {
1734 structural::convert_c64_to_f32::launch_unchecked::<CubeclCudaRuntime>(
1735 client, count, dim, out, input_arg,
1736 );
1737 },
1738 )
1739 }
1740
1741 fn convert_c64_to_f64(
1742 &self,
1743 input: &TypedTensor<Complex64>,
1744 ) -> crate::Result<TypedTensor<f64>> {
1745 launch_unary(
1746 self.runtime(),
1747 input,
1748 input.shape(),
1749 "convert",
1750 |client, count, dim, out, input_arg| unsafe {
1751 structural::convert_c64_to_f64::launch_unchecked::<CubeclCudaRuntime>(
1752 client, count, dim, out, input_arg,
1753 );
1754 },
1755 )
1756 }
1757
1758 fn convert_complex_to_complex<In, Out, InFloat, OutFloat>(
1759 &self,
1760 input: &TypedTensor<In>,
1761 ) -> crate::Result<TypedTensor<Out>>
1762 where
1763 In: CubeElement + TensorScalar + CubeComplex + Clone,
1764 Out: CubeElement + TensorScalar + CubeComplex + Clone,
1765 InFloat: CubeElement + CubeFloat + Clone,
1766 OutFloat: CubeElement + CubeFloat + Clone,
1767 {
1768 ensure_resident_on_runtime(self.runtime(), input, "cast")?;
1769 let parts = input.n_elements().checked_mul(2).ok_or_else(|| {
1770 crate::Error::invalid_argument("cast", "shape", "complex component length overflow")
1771 })?;
1772 let input_arg = typed_tensor_array_arg_as::<In, InFloat>(input, parts, "cast")?;
1773 let count = if parts == 0 {
1774 None
1775 } else {
1776 Some(cube_count_for_len(parts)?)
1777 };
1778 let output = alloc_output::<Out>(self.runtime(), input.shape())?;
1779 let Some(count) = count else {
1780 return Ok(output);
1781 };
1782 let output_arg = typed_tensor_array_arg_as::<Out, OutFloat>(&output, parts, "cast")?;
1783 unsafe {
1784 structural::convert_complex_raw::launch_unchecked::<OutFloat, InFloat, CubeclCudaRuntime>(
1785 self.runtime().client(),
1786 count,
1787 cube_dim_1d(),
1788 output_arg,
1789 input_arg,
1790 );
1791 }
1792 Ok(output)
1793 }
1794
1795 fn extract_diagonal_typed<T>(
1796 &self,
1797 input: &TypedTensor<T>,
1798 axis_a: usize,
1799 axis_b: usize,
1800 ) -> crate::Result<TypedTensor<T>>
1801 where
1802 T: CubeElement + TensorScalar + CubePrimitive + Clone,
1803 {
1804 let (output_shape, diag_output_axis) =
1805 extract_diagonal_shape(input.shape(), axis_a, axis_b)?;
1806 launch_unary_tensor(
1807 self.runtime(),
1808 input,
1809 &output_shape,
1810 "extract_diagonal",
1811 |client, count, dim, out, input_arg| unsafe {
1812 diagonal::extract_diagonal_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1813 client,
1814 count,
1815 dim,
1816 out.into_tensor_arg(),
1817 input_arg.into_tensor_arg(),
1818 axis_a,
1819 axis_b,
1820 diag_output_axis,
1821 input.shape().len(),
1822 output_shape.len(),
1823 );
1824 },
1825 )
1826 }
1827
1828 fn extract_diagonal_bool(
1829 &self,
1830 input: &TypedTensor<bool>,
1831 axis_a: usize,
1832 axis_b: usize,
1833 ) -> crate::Result<TypedTensor<bool>> {
1834 let (output_shape, diag_output_axis) =
1835 extract_diagonal_shape(input.shape(), axis_a, axis_b)?;
1836 launch_unary_bool_tensor(
1837 self.runtime(),
1838 input,
1839 &output_shape,
1840 "extract_diagonal",
1841 |client, count, dim, out, input_arg| unsafe {
1842 diagonal::extract_diagonal_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1843 client,
1844 count,
1845 dim,
1846 out.into_tensor_arg(),
1847 input_arg.into_tensor_arg(),
1848 axis_a,
1849 axis_b,
1850 diag_output_axis,
1851 input.shape().len(),
1852 output_shape.len(),
1853 );
1854 },
1855 )
1856 }
1857
1858 fn embed_diagonal_typed<T>(
1859 &self,
1860 input: &TypedTensor<T>,
1861 axis_a: usize,
1862 axis_b: usize,
1863 ) -> crate::Result<TypedTensor<T>>
1864 where
1865 T: CubeElement + TensorScalar + CubePrimitive + Clone,
1866 {
1867 let output_shape = embed_diagonal_shape(input.shape(), axis_a, axis_b)?;
1868 let output = alloc_output::<T>(self.runtime(), &output_shape)?;
1869 launch_nullary_into(
1870 self.runtime(),
1871 &output,
1872 "embed_diagonal",
1873 cube_count_for_len(output.n_elements())?,
1874 cube_dim_1d(),
1875 |client, count, dim, out| unsafe {
1876 structural::fill_zero_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1877 client, count, dim, out,
1878 );
1879 },
1880 )?;
1881 launch_unary_tensor_into(
1882 self.runtime(),
1883 &output,
1884 input,
1885 "embed_diagonal",
1886 cube_count_for_len(input.n_elements())?,
1887 cube_dim_1d(),
1888 |client, count, dim, out, input_arg| unsafe {
1889 diagonal::embed_diagonal_copy_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1890 client,
1891 count,
1892 dim,
1893 out.into_tensor_arg(),
1894 input_arg.into_tensor_arg(),
1895 axis_a,
1896 axis_b,
1897 input.shape().len(),
1898 output_shape.len(),
1899 );
1900 },
1901 )?;
1902 Ok(output)
1903 }
1904
1905 fn embed_diagonal_bool(
1906 &self,
1907 input: &TypedTensor<bool>,
1908 axis_a: usize,
1909 axis_b: usize,
1910 ) -> crate::Result<TypedTensor<bool>> {
1911 let output_shape = embed_diagonal_shape(input.shape(), axis_a, axis_b)?;
1912 ensure_resident_on_runtime(self.runtime(), input, "embed_diagonal")?;
1913 typed_tensor_binding(input, "embed_diagonal")?;
1914 let output_len = checked_dim_product("embed_diagonal", "output shape", &output_shape)?;
1915 let output_count = cube_count_for_len(output_len)?;
1916 let input_count = cube_count_for_len(input.n_elements())?;
1917 let output = dispatch::alloc_bool_output(self.runtime(), &output_shape)?;
1918 launch_nullary_bool_into(
1919 self.runtime(),
1920 &output,
1921 "embed_diagonal",
1922 output_count,
1923 cube_dim_1d(),
1924 |client, count, dim, out| unsafe {
1925 structural::fill_zero_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1926 client, count, dim, out,
1927 );
1928 },
1929 )?;
1930 launch_bool_tensor_into(
1931 self.runtime(),
1932 &output,
1933 input,
1934 "embed_diagonal",
1935 input_count,
1936 cube_dim_1d(),
1937 |client, count, dim, out, input_arg| unsafe {
1938 diagonal::embed_diagonal_copy_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1939 client,
1940 count,
1941 dim,
1942 out.into_tensor_arg(),
1943 input_arg.into_tensor_arg(),
1944 axis_a,
1945 axis_b,
1946 input.shape().len(),
1947 output_shape.len(),
1948 );
1949 },
1950 )?;
1951 Ok(output)
1952 }
1953
1954 #[doc(hidden)]
1955 pub fn tril_typed<T>(&self, input: &TypedTensor<T>, k: i64) -> crate::Result<TypedTensor<T>>
1956 where
1957 T: CubeElement + TensorScalar + CubePrimitive + Clone,
1958 {
1959 if input.shape().len() < 2 {
1960 return Err(crate::Error::rank_mismatch("tril", 2, input.shape().len()));
1961 }
1962 launch_unary_tensor(
1963 self.runtime(),
1964 input,
1965 input.shape(),
1966 "tril",
1967 |client, count, dim, out, input_arg| unsafe {
1968 diagonal::tril_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
1969 client,
1970 count,
1971 dim,
1972 out.into_tensor_arg(),
1973 input_arg.into_tensor_arg(),
1974 k,
1975 );
1976 },
1977 )
1978 }
1979
1980 fn tril_bool(&self, input: &TypedTensor<bool>, k: i64) -> crate::Result<TypedTensor<bool>> {
1981 if input.shape().len() < 2 {
1982 return Err(crate::Error::rank_mismatch("tril", 2, input.shape().len()));
1983 }
1984 launch_unary_bool_tensor(
1985 self.runtime(),
1986 input,
1987 input.shape(),
1988 "tril",
1989 |client, count, dim, out, input_arg| unsafe {
1990 diagonal::tril_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
1991 client,
1992 count,
1993 dim,
1994 out.into_tensor_arg(),
1995 input_arg.into_tensor_arg(),
1996 k,
1997 );
1998 },
1999 )
2000 }
2001
2002 #[doc(hidden)]
2003 pub fn triu_typed<T>(&self, input: &TypedTensor<T>, k: i64) -> crate::Result<TypedTensor<T>>
2004 where
2005 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2006 {
2007 if input.shape().len() < 2 {
2008 return Err(crate::Error::rank_mismatch("triu", 2, input.shape().len()));
2009 }
2010 launch_unary_tensor(
2011 self.runtime(),
2012 input,
2013 input.shape(),
2014 "triu",
2015 |client, count, dim, out, input_arg| unsafe {
2016 diagonal::triu_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
2017 client,
2018 count,
2019 dim,
2020 out.into_tensor_arg(),
2021 input_arg.into_tensor_arg(),
2022 k,
2023 );
2024 },
2025 )
2026 }
2027
2028 fn triu_bool(&self, input: &TypedTensor<bool>, k: i64) -> crate::Result<TypedTensor<bool>> {
2029 if input.shape().len() < 2 {
2030 return Err(crate::Error::rank_mismatch("triu", 2, input.shape().len()));
2031 }
2032 launch_unary_bool_tensor(
2033 self.runtime(),
2034 input,
2035 input.shape(),
2036 "triu",
2037 |client, count, dim, out, input_arg| unsafe {
2038 diagonal::triu_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
2039 client,
2040 count,
2041 dim,
2042 out.into_tensor_arg(),
2043 input_arg.into_tensor_arg(),
2044 k,
2045 );
2046 },
2047 )
2048 }
2049
2050 fn launch_reduce_axis_typed<T>(
2051 &self,
2052 input: &TypedTensor<T>,
2053 axis: usize,
2054 op: &'static str,
2055 launch: impl FnOnce(
2056 &ComputeClient<CubeclCudaRuntime>,
2057 TensorBinding<CubeclCudaRuntime>,
2058 TensorBinding<CubeclCudaRuntime>,
2059 ) -> crate::kernels::Result<()>,
2060 ) -> crate::Result<TypedTensor<T>>
2061 where
2062 T: CubeElement + TensorScalar + Clone,
2063 {
2064 let output_shape = reduction_keepdims_shape(input.shape(), axis);
2065 let input_binding = typed_tensor_binding(input, op)?;
2066 let output = alloc_output::<T>(self.runtime(), &output_shape)?;
2067 if output.n_elements() == 0 {
2068 return Ok(output);
2069 }
2070
2071 let output_binding = typed_tensor_binding(&output, op)?;
2072 launch(self.runtime().client(), input_binding, output_binding)
2073 .map_err(|err| crate::Error::backend_source(op, err))?;
2074 Ok(output)
2075 }
2076
2077 fn reduce_axes_typed<T>(
2078 &self,
2079 input: &TypedTensor<T>,
2080 axes: &[usize],
2081 op: &'static str,
2082 mut launch_axis: impl FnMut(&Self, &TypedTensor<T>, usize) -> crate::Result<TypedTensor<T>>,
2083 ) -> crate::Result<TypedTensor<T>>
2084 where
2085 T: CubeElement
2086 + CubePrimitive
2087 + tenferro_tensor::TensorScalar
2088 + Clone
2089 + Send
2090 + Sync
2091 + 'static,
2092 {
2093 ensure_axes_unique(op, "axes", axes, input.shape().len())?;
2094 if axes.is_empty() {
2095 return self.to_contiguous_view_typed(&input.as_view(), op);
2096 }
2097
2098 let final_shape = reduction_output_shape(input.shape(), axes);
2099 let mut sorted_axes = axes.to_vec();
2100 sorted_axes.sort_unstable();
2101
2102 let mut current = self.to_contiguous_view_typed(&input.as_view(), op)?;
2103 for axis in sorted_axes {
2104 current = launch_axis(self, ¤t, axis)?;
2105 }
2106
2107 cubecl_reshape_metadata(current, final_shape, op)
2108 }
2109
2110 fn reduce_sum_float_typed<
2111 F: CubeElement
2112 + CubePrimitive
2113 + CubeFloat
2114 + tenferro_tensor::TensorScalar
2115 + Clone
2116 + Send
2117 + Sync
2118 + 'static,
2119 >(
2120 &self,
2121 input: &TypedTensor<F>,
2122 axes: &[usize],
2123 ) -> crate::Result<TypedTensor<F>> {
2124 let op = op_name(
2125 PrimitiveOpKind::ReduceSum,
2126 op_descriptor::GpuLaunchKind::Reduction,
2127 )?;
2128 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2129 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2130 cubecl_reduce::launch_sum_float::<CubeclCudaRuntime, F>(
2131 client,
2132 input,
2133 output,
2134 axis,
2135 ReduceStrategy::Auto,
2136 )
2137 })
2138 })
2139 }
2140
2141 fn reduce_sum_squares_float_typed<
2142 F: CubeElement
2143 + CubePrimitive
2144 + CubeFloat
2145 + tenferro_tensor::TensorScalar
2146 + Clone
2147 + Send
2148 + Sync
2149 + 'static,
2150 >(
2151 &self,
2152 input: &TypedTensor<F>,
2153 axes: &[usize],
2154 ) -> crate::Result<TypedTensor<F>> {
2155 let op = op_name(
2156 PrimitiveOpKind::ReduceSumSquares,
2157 op_descriptor::GpuLaunchKind::Reduction,
2158 )?;
2159 ensure_axes_unique(op, "axes", axes, input.shape().len())?;
2160 let final_shape = reduction_output_shape(input.shape(), axes);
2161 let mut sorted_axes = axes.to_vec();
2162 sorted_axes.sort_unstable();
2163 let (&first_axis, remaining_axes) = sorted_axes
2164 .split_first()
2165 .ok_or_else(|| crate::Error::invalid_argument(op, "axes", "axes must not be empty"))?;
2166
2167 let mut current =
2168 self.launch_reduce_axis_typed(input, first_axis, op, |client, input, output| {
2169 cubecl_reduce::launch_sum_squares_float::<CubeclCudaRuntime, F>(
2170 client,
2171 input,
2172 output,
2173 first_axis,
2174 ReduceStrategy::Auto,
2175 )
2176 })?;
2177 for &axis in remaining_axes {
2178 current =
2179 self.launch_reduce_axis_typed(¤t, axis, op, |client, input, output| {
2180 cubecl_reduce::launch_sum_float::<CubeclCudaRuntime, F>(
2181 client,
2182 input,
2183 output,
2184 axis,
2185 ReduceStrategy::Auto,
2186 )
2187 })?;
2188 }
2189
2190 cubecl_reshape_metadata(current, final_shape, op)
2191 }
2192
2193 fn reduce_sum_complex_typed<
2194 C: CubeElement
2195 + CubePrimitive
2196 + CubeComplex
2197 + tenferro_tensor::TensorScalar
2198 + Clone
2199 + Send
2200 + Sync
2201 + 'static,
2202 >(
2203 &self,
2204 input: &TypedTensor<C>,
2205 axes: &[usize],
2206 ) -> crate::Result<TypedTensor<C>> {
2207 let op = op_name(
2208 PrimitiveOpKind::ReduceSum,
2209 op_descriptor::GpuLaunchKind::Reduction,
2210 )?;
2211 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2212 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2213 cubecl_reduce::launch_sum_complex::<CubeclCudaRuntime, C>(
2214 client,
2215 input,
2216 output,
2217 axis,
2218 ReduceStrategy::Auto,
2219 )
2220 })
2221 })
2222 }
2223
2224 fn reduce_sum_int_typed<
2225 I: CubeElement
2226 + CubePrimitive
2227 + CubeInt
2228 + tenferro_tensor::TensorScalar
2229 + Clone
2230 + Send
2231 + Sync
2232 + 'static,
2233 >(
2234 &self,
2235 input: &TypedTensor<I>,
2236 axes: &[usize],
2237 ) -> crate::Result<TypedTensor<I>> {
2238 let op = op_name(
2239 PrimitiveOpKind::ReduceSum,
2240 op_descriptor::GpuLaunchKind::Reduction,
2241 )?;
2242 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2243 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2244 cubecl_reduce::launch_sum_int::<CubeclCudaRuntime, I>(
2245 client,
2246 input,
2247 output,
2248 axis,
2249 ReduceStrategy::Auto,
2250 )
2251 })
2252 })
2253 }
2254
2255 fn reduce_prod_float_typed<
2256 F: CubeElement
2257 + CubePrimitive
2258 + CubeFloat
2259 + tenferro_tensor::TensorScalar
2260 + Clone
2261 + Send
2262 + Sync
2263 + 'static,
2264 >(
2265 &self,
2266 input: &TypedTensor<F>,
2267 axes: &[usize],
2268 ) -> crate::Result<TypedTensor<F>> {
2269 let op = op_name(
2270 PrimitiveOpKind::ReduceProd,
2271 op_descriptor::GpuLaunchKind::Reduction,
2272 )?;
2273 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2274 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2275 cubecl_reduce::launch_prod_float::<CubeclCudaRuntime, F>(
2276 client,
2277 input,
2278 output,
2279 axis,
2280 ReduceStrategy::Auto,
2281 )
2282 })
2283 })
2284 }
2285
2286 fn reduce_prod_complex_typed<
2287 C: CubeElement
2288 + CubePrimitive
2289 + CubeComplex
2290 + tenferro_tensor::TensorScalar
2291 + Clone
2292 + Send
2293 + Sync
2294 + 'static,
2295 >(
2296 &self,
2297 input: &TypedTensor<C>,
2298 axes: &[usize],
2299 ) -> crate::Result<TypedTensor<C>> {
2300 let op = op_name(
2301 PrimitiveOpKind::ReduceProd,
2302 op_descriptor::GpuLaunchKind::Reduction,
2303 )?;
2304 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2305 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2306 cubecl_reduce::launch_prod_complex::<CubeclCudaRuntime, C>(
2307 client,
2308 input,
2309 output,
2310 axis,
2311 ReduceStrategy::Auto,
2312 )
2313 })
2314 })
2315 }
2316
2317 fn reduce_prod_int_typed<
2318 I: CubeElement
2319 + CubePrimitive
2320 + CubeInt
2321 + tenferro_tensor::TensorScalar
2322 + Clone
2323 + Send
2324 + Sync
2325 + 'static,
2326 >(
2327 &self,
2328 input: &TypedTensor<I>,
2329 axes: &[usize],
2330 ) -> crate::Result<TypedTensor<I>> {
2331 let op = op_name(
2332 PrimitiveOpKind::ReduceProd,
2333 op_descriptor::GpuLaunchKind::Reduction,
2334 )?;
2335 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2336 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2337 cubecl_reduce::launch_prod_int::<CubeclCudaRuntime, I>(
2338 client,
2339 input,
2340 output,
2341 axis,
2342 ReduceStrategy::Auto,
2343 )
2344 })
2345 })
2346 }
2347
2348 fn reduce_max_float_typed<
2349 F: CubeElement
2350 + CubePrimitive
2351 + CubeFloat
2352 + tenferro_tensor::TensorScalar
2353 + Clone
2354 + Send
2355 + Sync
2356 + 'static,
2357 >(
2358 &self,
2359 input: &TypedTensor<F>,
2360 axes: &[usize],
2361 ) -> crate::Result<TypedTensor<F>> {
2362 let op = op_name(
2363 PrimitiveOpKind::ReduceMax,
2364 op_descriptor::GpuLaunchKind::Reduction,
2365 )?;
2366 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2367 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2368 cubecl_reduce::launch_max_float::<CubeclCudaRuntime, F>(
2369 client,
2370 input,
2371 output,
2372 axis,
2373 ReduceStrategy::Auto,
2374 )
2375 })
2376 })
2377 }
2378
2379 fn reduce_max_int_typed<
2380 I: CubeElement
2381 + CubePrimitive
2382 + CubeInt
2383 + tenferro_tensor::TensorScalar
2384 + Clone
2385 + Send
2386 + Sync
2387 + 'static,
2388 >(
2389 &self,
2390 input: &TypedTensor<I>,
2391 axes: &[usize],
2392 ) -> crate::Result<TypedTensor<I>> {
2393 let op = op_name(
2394 PrimitiveOpKind::ReduceMax,
2395 op_descriptor::GpuLaunchKind::Reduction,
2396 )?;
2397 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2398 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2399 cubecl_reduce::launch_max_int::<CubeclCudaRuntime, I>(
2400 client,
2401 input,
2402 output,
2403 axis,
2404 ReduceStrategy::Auto,
2405 )
2406 })
2407 })
2408 }
2409
2410 fn reduce_min_float_typed<
2411 F: CubeElement
2412 + CubePrimitive
2413 + CubeFloat
2414 + tenferro_tensor::TensorScalar
2415 + Clone
2416 + Send
2417 + Sync
2418 + 'static,
2419 >(
2420 &self,
2421 input: &TypedTensor<F>,
2422 axes: &[usize],
2423 ) -> crate::Result<TypedTensor<F>> {
2424 let op = op_name(
2425 PrimitiveOpKind::ReduceMin,
2426 op_descriptor::GpuLaunchKind::Reduction,
2427 )?;
2428 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2429 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2430 cubecl_reduce::launch_min_float::<CubeclCudaRuntime, F>(
2431 client,
2432 input,
2433 output,
2434 axis,
2435 ReduceStrategy::Auto,
2436 )
2437 })
2438 })
2439 }
2440
2441 fn reduce_min_int_typed<
2442 I: CubeElement
2443 + CubePrimitive
2444 + CubeInt
2445 + tenferro_tensor::TensorScalar
2446 + Clone
2447 + Send
2448 + Sync
2449 + 'static,
2450 >(
2451 &self,
2452 input: &TypedTensor<I>,
2453 axes: &[usize],
2454 ) -> crate::Result<TypedTensor<I>> {
2455 let op = op_name(
2456 PrimitiveOpKind::ReduceMin,
2457 op_descriptor::GpuLaunchKind::Reduction,
2458 )?;
2459 self.reduce_axes_typed(input, axes, op, |backend, current, axis| {
2460 backend.launch_reduce_axis_typed(current, axis, op, |client, input, output| {
2461 cubecl_reduce::launch_min_int::<CubeclCudaRuntime, I>(
2462 client,
2463 input,
2464 output,
2465 axis,
2466 ReduceStrategy::Auto,
2467 )
2468 })
2469 })
2470 }
2471
2472 #[doc(hidden)]
2473 pub fn slice_typed<T>(
2474 &self,
2475 input: &TypedTensor<T>,
2476 config: &SliceConfig,
2477 ) -> crate::Result<TypedTensor<T>>
2478 where
2479 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2480 {
2481 let output_shape = validate_slice(input.shape(), config)?;
2482 launch_unary_tensor(
2483 self.runtime(),
2484 input,
2485 &output_shape,
2486 "slice",
2487 |client, count, dim, out, input_arg| unsafe {
2488 indexing::slice_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
2489 client,
2490 count,
2491 dim,
2492 out.into_tensor_arg(),
2493 input_arg.into_tensor_arg(),
2494 comptime_sequence(&config.starts),
2495 comptime_sequence(&config.strides),
2496 );
2497 },
2498 )
2499 }
2500
2501 fn slice_bool(
2502 &self,
2503 input: &TypedTensor<bool>,
2504 config: &SliceConfig,
2505 ) -> crate::Result<TypedTensor<bool>> {
2506 let output_shape = validate_slice(input.shape(), config)?;
2507 launch_unary_bool_tensor(
2508 self.runtime(),
2509 input,
2510 &output_shape,
2511 "slice",
2512 |client, count, dim, out, input_arg| unsafe {
2513 indexing::slice_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
2514 client,
2515 count,
2516 dim,
2517 out.into_tensor_arg(),
2518 input_arg.into_tensor_arg(),
2519 comptime_sequence(&config.starts),
2520 comptime_sequence(&config.strides),
2521 );
2522 },
2523 )
2524 }
2525
2526 fn dynamic_slice_typed<T, I>(
2527 &self,
2528 input: &TypedTensor<T>,
2529 starts: &TypedTensor<I>,
2530 slice_sizes: &[usize],
2531 ) -> crate::Result<TypedTensor<T>>
2532 where
2533 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2534 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2535 {
2536 ensure_rank("dynamic_slice", input.shape().len(), slice_sizes.len())?;
2537 ensure_rank("dynamic_slice", 1, starts.shape().len())?;
2538 if starts.shape()[0] != input.shape().len() {
2539 return Err(crate::Error::rank_mismatch(
2540 "dynamic_slice",
2541 input.shape().len(),
2542 starts.shape()[0],
2543 ));
2544 }
2545 for (axis, (&window, &dim)) in slice_sizes.iter().zip(input.shape()).enumerate() {
2546 if window > dim {
2547 return Err(crate::Error::invalid_argument(
2548 "dynamic_slice",
2549 "slice_sizes",
2550 format!("slice size exceeds dimension on axis {axis}"),
2551 ));
2552 }
2553 }
2554 let output_len = checked_dim_product("dynamic_slice", "output shape", slice_sizes)?;
2555 if output_len != 0 {
2556 cube_count_for_len(output_len)?;
2557 }
2558 ensure_resident_on_runtime(self.runtime(), input, "dynamic_slice")?;
2559 typed_tensor_binding(input, "dynamic_slice")?;
2560 ensure_resident_on_runtime(self.runtime(), starts, "dynamic_slice")?;
2561 typed_tensor_binding(starts, "dynamic_slice")?;
2562 I::validate(self, starts)?;
2563 launch_binary_tensor(
2564 self.runtime(),
2565 input,
2566 starts,
2567 slice_sizes,
2568 "dynamic_slice",
2569 |client, count, dim, out, input_arg, starts_arg| unsafe {
2570 indexing::dynamic_slice_kernel::launch_unchecked::<T, I, CubeclCudaRuntime>(
2571 client,
2572 count,
2573 dim,
2574 out.into_tensor_arg(),
2575 input_arg.into_tensor_arg(),
2576 starts_arg.into_tensor_arg(),
2577 comptime_sequence(slice_sizes),
2578 );
2579 },
2580 )
2581 }
2582
2583 fn dynamic_slice_bool<I>(
2584 &self,
2585 input: &TypedTensor<bool>,
2586 starts: &TypedTensor<I>,
2587 slice_sizes: &[usize],
2588 ) -> crate::Result<TypedTensor<bool>>
2589 where
2590 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2591 {
2592 ensure_rank("dynamic_slice", input.shape().len(), slice_sizes.len())?;
2593 if starts.shape().len() != 1 {
2594 return Err(crate::Error::invalid_argument(
2595 "dynamic_slice",
2596 "starts",
2597 "starts must be a rank-1 tensor",
2598 ));
2599 }
2600 if starts.shape()[0] != input.shape().len() {
2601 return Err(crate::Error::invalid_argument(
2602 "dynamic_slice",
2603 "starts",
2604 format!(
2605 "starts length {} must match input rank {}",
2606 starts.shape()[0],
2607 input.shape().len()
2608 ),
2609 ));
2610 }
2611 for (axis, (&window, &dim)) in slice_sizes.iter().zip(input.shape()).enumerate() {
2612 if window > dim {
2613 return Err(crate::Error::invalid_argument(
2614 "dynamic_slice",
2615 "slice_sizes",
2616 format!("slice size exceeds dimension on axis {axis}"),
2617 ));
2618 }
2619 }
2620 let output_len = checked_dim_product("dynamic_slice", "output shape", slice_sizes)?;
2621 if output_len != 0 {
2622 cube_count_for_len(output_len)?;
2623 }
2624 ensure_resident_on_runtime(self.runtime(), input, "dynamic_slice")?;
2625 bool_tensor_array_arg(input, "dynamic_slice")?;
2626 ensure_resident_on_runtime(self.runtime(), starts, "dynamic_slice")?;
2627 typed_tensor_binding(starts, "dynamic_slice")?;
2628 I::validate(self, starts)?;
2629 launch_binary_bool_tensor(
2630 self.runtime(),
2631 input,
2632 starts,
2633 slice_sizes,
2634 "dynamic_slice",
2635 |client, count, dim, out, input_arg, starts_arg| unsafe {
2636 indexing::dynamic_slice_kernel::launch_unchecked::<u8, I, CubeclCudaRuntime>(
2637 client,
2638 count,
2639 dim,
2640 out.into_tensor_arg(),
2641 input_arg.into_tensor_arg(),
2642 starts_arg.into_tensor_arg(),
2643 comptime_sequence(slice_sizes),
2644 );
2645 },
2646 )
2647 }
2648
2649 fn pad_typed<T>(
2650 &self,
2651 input: &TypedTensor<T>,
2652 config: &PadConfig,
2653 ) -> crate::Result<TypedTensor<T>>
2654 where
2655 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2656 {
2657 let output_shape = pad_output_shape(input.shape(), config)?;
2658 launch_unary_tensor(
2659 self.runtime(),
2660 input,
2661 &output_shape,
2662 "pad",
2663 |client, count, dim, out, input_arg| unsafe {
2664 indexing::pad_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
2665 client,
2666 count,
2667 dim,
2668 out.into_tensor_arg(),
2669 input_arg.into_tensor_arg(),
2670 comptime_sequence(&config.edge_padding_low),
2671 comptime_sequence(&config.interior_padding),
2672 );
2673 },
2674 )
2675 }
2676
2677 fn pad_bool(
2678 &self,
2679 input: &TypedTensor<bool>,
2680 config: &PadConfig,
2681 ) -> crate::Result<TypedTensor<bool>> {
2682 let output_shape = pad_output_shape(input.shape(), config)?;
2683 launch_unary_bool_tensor(
2684 self.runtime(),
2685 input,
2686 &output_shape,
2687 "pad",
2688 |client, count, dim, out, input_arg| unsafe {
2689 indexing::pad_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
2690 client,
2691 count,
2692 dim,
2693 out.into_tensor_arg(),
2694 input_arg.into_tensor_arg(),
2695 comptime_sequence(&config.edge_padding_low),
2696 comptime_sequence(&config.interior_padding),
2697 );
2698 },
2699 )
2700 }
2701
2702 fn concatenate_typed<T>(
2703 &self,
2704 inputs: &[&TypedTensor<T>],
2705 axis: usize,
2706 ) -> crate::Result<TypedTensor<T>>
2707 where
2708 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2709 {
2710 let output_shape = concatenate_output_shape(inputs, axis)?;
2711 let output = alloc_output::<T>(self.runtime(), &output_shape)?;
2712 let mut offset = 0usize;
2713 for input in inputs {
2714 launch_unary_tensor_into(
2715 self.runtime(),
2716 &output,
2717 input,
2718 "concatenate",
2719 cube_count_for_len(input.n_elements())?,
2720 cube_dim_1d(),
2721 |client, count, dim, out, input_arg| unsafe {
2722 structural::concatenate_copy_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
2723 client,
2724 count,
2725 dim,
2726 out.into_tensor_arg(),
2727 input_arg.into_tensor_arg(),
2728 axis,
2729 offset,
2730 input.shape().len(),
2731 );
2732 },
2733 )?;
2734 offset += input.shape()[axis];
2737 }
2738 Ok(output)
2739 }
2740
2741 fn concatenate_bool(
2742 &self,
2743 inputs: &[&TypedTensor<bool>],
2744 axis: usize,
2745 ) -> crate::Result<TypedTensor<bool>> {
2746 let output_shape = concatenate_output_shape(inputs, axis)?;
2747 for input in inputs {
2748 ensure_resident_on_runtime(self.runtime(), input, "concatenate")?;
2749 typed_tensor_binding(input, "concatenate")?;
2750 }
2751 checked_dim_product("concatenate", "output shape", &output_shape)?;
2752 let launch_counts = inputs
2753 .iter()
2754 .map(|input| cube_count_for_len(input.n_elements()))
2755 .collect::<crate::Result<Vec<_>>>()?;
2756 let output = dispatch::alloc_bool_output(self.runtime(), &output_shape)?;
2757 let mut offset = 0usize;
2758 for (input, launch_count) in inputs.iter().zip(launch_counts) {
2759 launch_bool_tensor_into(
2760 self.runtime(),
2761 &output,
2762 input,
2763 "concatenate",
2764 launch_count,
2765 cube_dim_1d(),
2766 |client, count, dim, out, input_arg| unsafe {
2767 structural::concatenate_copy_kernel::launch_unchecked::<u8, CubeclCudaRuntime>(
2768 client,
2769 count,
2770 dim,
2771 out.into_tensor_arg(),
2772 input_arg.into_tensor_arg(),
2773 axis,
2774 offset,
2775 input.shape().len(),
2776 );
2777 },
2778 )?;
2779 offset += input.shape()[axis];
2780 }
2781 Ok(output)
2782 }
2783
2784 fn gather_typed<T, I>(
2785 &self,
2786 operand: &TypedTensor<T>,
2787 start_indices: &TypedTensor<I>,
2788 config: &GatherConfig,
2789 ) -> crate::Result<TypedTensor<T>>
2790 where
2791 T: CubeElement + TensorScalar + CubePrimitive + Clone,
2792 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2793 {
2794 let meta = gather_launch_meta(operand.shape(), start_indices.shape(), config)?;
2795 let output_len = checked_dim_product("gather", "output shape", &meta.output_shape)?;
2796 if output_len != 0 {
2797 cube_count_for_len(output_len)?;
2798 }
2799 ensure_resident_on_runtime(self.runtime(), operand, "gather")?;
2800 typed_tensor_binding(operand, "gather")?;
2801 ensure_resident_on_runtime(self.runtime(), start_indices, "gather")?;
2802 typed_tensor_binding(start_indices, "gather")?;
2803 I::validate(self, start_indices)?;
2804 launch_binary_tensor(
2805 self.runtime(),
2806 operand,
2807 start_indices,
2808 &meta.output_shape,
2809 "gather",
2810 |client, count, dim, out, operand_arg, indices_arg| unsafe {
2811 indexing::gather_kernel::launch_unchecked::<T, I, CubeclCudaRuntime>(
2812 client,
2813 count,
2814 dim,
2815 out.into_tensor_arg(),
2816 operand_arg.into_tensor_arg(),
2817 indices_arg.into_tensor_arg(),
2818 comptime_sequence(&meta.window_dims),
2819 comptime_sequence(&config.offset_dims),
2820 comptime_sequence(&config.start_index_map),
2821 comptime_sequence(&config.slice_sizes),
2822 config.index_vector_dim,
2823 operand.shape().len(),
2824 meta.output_shape.len(),
2825 start_indices.shape().len(),
2826 );
2827 },
2828 )
2829 }
2830
2831 fn gather_bool<I>(
2832 &self,
2833 operand: &TypedTensor<bool>,
2834 start_indices: &TypedTensor<I>,
2835 config: &GatherConfig,
2836 ) -> crate::Result<TypedTensor<bool>>
2837 where
2838 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2839 {
2840 let meta = gather_launch_meta(operand.shape(), start_indices.shape(), config)?;
2841 let output_len = checked_dim_product("gather", "output shape", &meta.output_shape)?;
2842 if output_len != 0 {
2843 cube_count_for_len(output_len)?;
2844 }
2845 ensure_resident_on_runtime(self.runtime(), operand, "gather")?;
2846 bool_tensor_array_arg(operand, "gather")?;
2847 ensure_resident_on_runtime(self.runtime(), start_indices, "gather")?;
2848 typed_tensor_binding(start_indices, "gather")?;
2849 I::validate(self, start_indices)?;
2850 launch_binary_bool_tensor(
2851 self.runtime(),
2852 operand,
2853 start_indices,
2854 &meta.output_shape,
2855 "gather",
2856 |client, count, dim, out, operand_arg, indices_arg| unsafe {
2857 indexing::gather_kernel::launch_unchecked::<u8, I, CubeclCudaRuntime>(
2858 client,
2859 count,
2860 dim,
2861 out.into_tensor_arg(),
2862 operand_arg.into_tensor_arg(),
2863 indices_arg.into_tensor_arg(),
2864 comptime_sequence(&meta.window_dims),
2865 comptime_sequence(&config.offset_dims),
2866 comptime_sequence(&config.start_index_map),
2867 comptime_sequence(&config.slice_sizes),
2868 config.index_vector_dim,
2869 operand.shape().len(),
2870 meta.output_shape.len(),
2871 start_indices.shape().len(),
2872 );
2873 },
2874 )
2875 }
2876
2877 fn scatter_float_typed<T, I>(
2878 &self,
2879 operand: &TypedTensor<T>,
2880 scatter_indices: &TypedTensor<I>,
2881 updates: &TypedTensor<T>,
2882 config: &ScatterConfig,
2883 ) -> crate::Result<TypedTensor<T>>
2884 where
2885 T: CubeElement + TensorScalar + CubeFloat + Clone,
2886 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2887 {
2888 let meta = scatter_launch_meta(
2889 operand.shape(),
2890 scatter_indices.shape(),
2891 updates.shape(),
2892 config,
2893 )?;
2894 let update_len = scatter_update_len(&meta)?;
2895 let output_len = checked_dim_product("scatter", "output shape", operand.shape())?;
2896 if output_len != 0 {
2897 cube_count_for_len(output_len)?;
2898 }
2899 if update_len != 0 {
2900 cube_count_for_len(update_len)?;
2901 }
2902 let client = self.runtime().client();
2903 ensure_resident_on_runtime(self.runtime(), operand, "scatter")?;
2904 typed_tensor_binding(operand, "scatter")?;
2905 ensure_resident_on_runtime(self.runtime(), scatter_indices, "scatter")?;
2906 typed_tensor_binding(scatter_indices, "scatter")?;
2907 ensure_resident_on_runtime(self.runtime(), updates, "scatter")?;
2908 typed_tensor_binding(updates, "scatter")?;
2909 ensure_atomic_add_supported::<T>(client, "scatter")?;
2910 I::validate(self, scatter_indices)?;
2911 let output = alloc_output::<T>(self.runtime(), operand.shape())?;
2912 if output.n_elements() == 0 {
2913 return Ok(output);
2914 }
2915
2916 launch_unary_tensor_into(
2917 self.runtime(),
2918 &output,
2919 operand,
2920 "scatter",
2921 cube_count_for_len(output.n_elements())?,
2922 cube_dim_1d(),
2923 |client, count, dim, out_arg, operand_arg| unsafe {
2924 indexing::scatter_copy_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
2925 client,
2926 count,
2927 dim,
2928 out_arg.into_tensor_arg(),
2929 operand_arg.into_tensor_arg(),
2930 );
2931 },
2932 )?;
2933
2934 if update_len == 0 {
2935 return Ok(output);
2936 }
2937 let output_parts =
2938 typed_tensor_array_arg_as::<T, T>(&output, output.n_elements(), "scatter")?;
2939 let operand_arg = typed_tensor_binding(operand, "scatter")?;
2940 let scatter_arg = typed_tensor_binding(scatter_indices, "scatter")?;
2941 let updates_arg = typed_tensor_binding(updates, "scatter")?;
2942 unsafe {
2943 indexing::scatter_float_kernel::launch_unchecked::<T, I, CubeclCudaRuntime>(
2951 client,
2952 cube_count_for_len(update_len)?,
2953 cube_dim_1d(),
2954 output_parts,
2955 operand_arg.into_tensor_arg(),
2956 scatter_arg.into_tensor_arg(),
2957 updates_arg.into_tensor_arg(),
2958 comptime_sequence(&meta.window_dims),
2959 comptime_sequence(&config.update_window_dims),
2960 comptime_sequence(&config.scatter_dims_to_operand_dims),
2961 config.index_vector_dim,
2962 operand.shape().len(),
2963 updates.shape().len(),
2964 scatter_indices.shape().len(),
2965 );
2966 }
2967 Ok(output)
2968 }
2969
2970 fn scatter_complex_typed<T, F, I>(
2971 &self,
2972 operand: &TypedTensor<T>,
2973 scatter_indices: &TypedTensor<I>,
2974 updates: &TypedTensor<T>,
2975 config: &ScatterConfig,
2976 ) -> crate::Result<TypedTensor<T>>
2977 where
2978 T: CubeElement + TensorScalar + CubeComplex + Clone,
2979 F: CubeElement + TensorScalar + CubeFloat + Clone,
2980 I: CubeElement + TensorScalar + CubePrimitive + CubeNumeric + Clone + CudaIndexValidation,
2981 {
2982 let meta = scatter_launch_meta(
2983 operand.shape(),
2984 scatter_indices.shape(),
2985 updates.shape(),
2986 config,
2987 )?;
2988 let update_len = scatter_update_len(&meta)?;
2989 let output_len = checked_dim_product("scatter", "output shape", operand.shape())?;
2990 let output_part_len = output_len.checked_mul(2).ok_or_else(|| {
2991 crate::Error::invalid_argument(
2992 "scatter",
2993 "shape",
2994 "complex output part length overflow",
2995 )
2996 })?;
2997 let update_part_len = updates.n_elements().checked_mul(2).ok_or_else(|| {
2998 crate::Error::invalid_argument(
2999 "scatter",
3000 "shape",
3001 "complex update part length overflow",
3002 )
3003 })?;
3004 if output_len != 0 {
3005 cube_count_for_len(output_len)?;
3006 }
3007 if update_len != 0 {
3008 cube_count_for_len(update_len)?;
3009 }
3010 let client = self.runtime().client();
3011 ensure_resident_on_runtime(self.runtime(), operand, "scatter")?;
3012 typed_tensor_binding(operand, "scatter")?;
3013 ensure_resident_on_runtime(self.runtime(), scatter_indices, "scatter")?;
3014 typed_tensor_binding(scatter_indices, "scatter")?;
3015 ensure_resident_on_runtime(self.runtime(), updates, "scatter")?;
3016 typed_tensor_binding(updates, "scatter")?;
3017 typed_tensor_array_arg_as::<T, F>(updates, update_part_len, "scatter")?;
3018 ensure_atomic_add_supported::<F>(client, "scatter")?;
3019 I::validate(self, scatter_indices)?;
3020 let output = alloc_output::<T>(self.runtime(), operand.shape())?;
3021 if output.n_elements() == 0 {
3022 return Ok(output);
3023 }
3024
3025 launch_unary_tensor_into(
3026 self.runtime(),
3027 &output,
3028 operand,
3029 "scatter",
3030 cube_count_for_len(output.n_elements())?,
3031 cube_dim_1d(),
3032 |client, count, dim, out_arg, operand_arg| unsafe {
3033 indexing::scatter_copy_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
3034 client,
3035 count,
3036 dim,
3037 out_arg.into_tensor_arg(),
3038 operand_arg.into_tensor_arg(),
3039 );
3040 },
3041 )?;
3042
3043 if update_len == 0 {
3044 return Ok(output);
3045 }
3046 let output_parts = typed_tensor_array_arg_as::<T, F>(&output, output_part_len, "scatter")?;
3049 let update_parts = typed_tensor_array_arg_as::<T, F>(updates, update_part_len, "scatter")?;
3050 let operand_arg = typed_tensor_binding(operand, "scatter")?;
3051 let scatter_arg = typed_tensor_binding(scatter_indices, "scatter")?;
3052 let updates_arg = typed_tensor_binding(updates, "scatter")?;
3053 unsafe {
3054 indexing::scatter_complex_kernel::launch_unchecked::<T, F, I, CubeclCudaRuntime>(
3062 client,
3063 cube_count_for_len(update_len)?,
3064 cube_dim_1d(),
3065 output_parts,
3066 operand_arg.into_tensor_arg(),
3067 scatter_arg.into_tensor_arg(),
3068 updates_arg.into_tensor_arg(),
3069 update_parts,
3070 comptime_sequence(&meta.window_dims),
3071 comptime_sequence(&config.update_window_dims),
3072 comptime_sequence(&config.scatter_dims_to_operand_dims),
3073 config.index_vector_dim,
3074 operand.shape().len(),
3075 updates.shape().len(),
3076 scatter_indices.shape().len(),
3077 );
3078 }
3079 Ok(output)
3080 }
3081}
3082
3083impl BackendRuntimeCache for CudaBackend {
3084 type RuntimeCache = ();
3085}
3086
3087#[derive(Clone, Copy, Debug)]
3088enum CheckedIntegerDomain {
3089 DivisionByZero,
3090 NegativeExponent,
3091}
3092
3093#[derive(Clone, Copy)]
3094enum CastIntegerTarget {
3095 I32,
3096 I64,
3097}
3098
3099trait CudaCastFloat:
3100 CubeElement
3101 + TensorScalar
3102 + CubeFloat
3103 + CubePrimitive<WithScalar<bool> = bool, WithScalar<Self> = Self>
3104 + Clone
3105 + Send
3106 + Sync
3107 + Copy
3108 + fmt::Display
3109 + 'static
3110{
3111 fn bounds(target: CastIntegerTarget) -> (Self, Self, bool);
3112 fn read_flag(backend: &CudaBackend, flag: &TypedTensor<Self>) -> crate::Result<Self>;
3113 fn invalid_error(self, target: CastIntegerTarget) -> crate::Error;
3114 fn is_nonfinite(self) -> bool;
3115 fn cpu_real_display(self) -> String;
3116}
3117
3118macro_rules! impl_cuda_cast_float {
3119 ($ty:ty, $variant:ident, $i32_max_inclusive:expr, $display:expr) => {
3120 impl CudaCastFloat for $ty {
3121 fn bounds(target: CastIntegerTarget) -> (Self, Self, bool) {
3122 match target {
3123 CastIntegerTarget::I32 => (
3124 i32::MIN as Self,
3125 if $i32_max_inclusive {
3126 i32::MAX as Self
3127 } else {
3128 2_147_483_648.0 as Self
3129 },
3130 $i32_max_inclusive,
3131 ),
3132 CastIntegerTarget::I64 => (
3133 -9_223_372_036_854_775_808.0 as Self,
3134 9_223_372_036_854_775_808.0 as Self,
3135 false,
3136 ),
3137 }
3138 }
3139 fn read_flag(backend: &CudaBackend, flag: &TypedTensor<Self>) -> crate::Result<Self> {
3140 let host = interop::download_typed_tensor(backend.runtime(), flag, "cast")?;
3141 host.as_slice()?.get(1).copied().ok_or_else(|| {
3142 crate::Error::invalid_argument(
3143 "cast",
3144 "validation_flag",
3145 "validation flag was malformed",
3146 )
3147 })
3148 }
3149 fn invalid_error(self, target: CastIntegerTarget) -> crate::Error {
3150 let name = match target {
3151 CastIntegerTarget::I32 => "i32",
3152 CastIntegerTarget::I64 => "i64",
3153 };
3154 let message = if !self.is_finite() {
3155 format!(
3156 "real value must be finite when casting to {name}, got {}",
3157 self.cpu_real_display()
3158 )
3159 } else {
3160 format!(
3161 "real value {} is out of {name} range",
3162 self.cpu_real_display()
3163 )
3164 };
3165 crate::Error::invalid_argument("cast", "value", message)
3166 }
3167 fn is_nonfinite(self) -> bool {
3168 !self.is_finite()
3169 }
3170 fn cpu_real_display(self) -> String {
3171 ($display)(self)
3172 }
3173 }
3174 };
3175}
3176impl_cuda_cast_float!(f32, F32, false, |value: f32| format!("{}", value as f64));
3177impl_cuda_cast_float!(f64, F64, true, |value: f64| format!("{value}"));
3178
3179fn validate_cuda_real_cast<S, F>(
3180 backend: &CudaBackend,
3181 input: &TypedTensor<S>,
3182 stride: usize,
3183 target: CastIntegerTarget,
3184) -> crate::Result<()>
3185where
3186 S: CubeElement + TensorScalar + Clone,
3187 F: CudaCastFloat,
3188{
3189 ensure_resident_on_runtime(backend.runtime(), input, "cast")?;
3190 let n = input.n_elements();
3191 let _validated_input = typed_tensor_array_arg(input, "cast")?;
3192 if n == 0 {
3193 return Ok(());
3194 }
3195 u32::try_from(n).map_err(|_| {
3196 crate::Error::invalid_argument(
3197 "cast",
3198 "shape",
3199 "validation domain exceeds u32::MAX elements",
3200 )
3201 })?;
3202 let count = cube_count_for_len(n)?;
3203 let input_parts = n.checked_mul(stride).ok_or_else(|| {
3204 crate::Error::invalid_argument("cast", "shape", "validation input length overflow")
3205 })?;
3206 let input_arg = typed_tensor_array_arg_as::<S, F>(input, input_parts, "cast")?;
3207 let flag = alloc_output::<F>(backend.runtime(), &[2])?;
3208 let flag_u32_len = std::mem::size_of::<F>()
3209 .checked_mul(2)
3210 .and_then(|x| x.checked_div(std::mem::size_of::<u32>()))
3211 .ok_or_else(|| {
3212 crate::Error::invalid_argument("cast", "shape", "validation flag size overflow")
3213 })?;
3214 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "cast")?;
3215 let flag_values = typed_tensor_array_arg(&flag, "cast")?;
3216 unsafe {
3217 indexing::init_float_index_validation_flag::launch_unchecked::<F, CubeclCudaRuntime>(
3218 backend.runtime().client(),
3219 CubeCount::Static(1, 1, 1),
3220 cube_dim_1d(),
3221 flag_atomic,
3222 flag_values,
3223 );
3224 }
3225 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "cast")?;
3226 let (min, max, inclusive) = F::bounds(target);
3227 unsafe {
3228 structural::validate_real_cast::launch_unchecked::<F, CubeclCudaRuntime>(
3229 backend.runtime().client(),
3230 count,
3231 cube_dim_1d(),
3232 input_arg,
3233 flag_atomic,
3234 min,
3235 max,
3236 stride,
3237 inclusive,
3238 );
3239 }
3240 let input_arg = typed_tensor_array_arg_as::<S, F>(input, input_parts, "cast")?;
3241 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "cast")?;
3242 let flag_values = typed_tensor_array_arg(&flag, "cast")?;
3243 unsafe {
3244 structural::extract_invalid_real_cast::launch_unchecked::<F, CubeclCudaRuntime>(
3245 backend.runtime().client(),
3246 CubeCount::Static(1, 1, 1),
3247 cube_dim_1d(),
3248 input_arg,
3249 flag_atomic,
3250 flag_values,
3251 stride,
3252 );
3253 }
3254 let value = F::read_flag(backend, &flag)?;
3255 let (min, max, inclusive) = F::bounds(target);
3256 if value.is_nonfinite() || value < min || if inclusive { value > max } else { value >= max } {
3257 return Err(value.invalid_error(target));
3258 }
3259 Ok(())
3260}
3261
3262fn checked_integer_domain_error(
3263 domain: CheckedIntegerDomain,
3264 op: &'static str,
3265 dtype: crate::DType,
3266) -> crate::Error {
3267 match domain {
3268 CheckedIntegerDomain::DivisionByZero => error::division_by_zero(op, dtype),
3269 CheckedIntegerDomain::NegativeExponent => error::negative_integer_exponent(op, dtype),
3270 }
3271}
3272
3273fn read_checked_integer_flag(
3274 backend: &CudaBackend,
3275 flag: &TypedTensor<i32>,
3276 op: &'static str,
3277) -> crate::Result<i32> {
3278 let host = interop::download_typed_tensor(backend.runtime(), flag, op)?;
3279 Ok(host.as_slice()?.first().copied().unwrap_or_default())
3280}
3281
3282trait CudaFloatIndex:
3283 CubeElement
3284 + TensorScalar
3285 + CubePrimitive<WithScalar<bool> = bool, WithScalar<Self> = Self>
3286 + CubeFloat
3287 + Clone
3288 + Send
3289 + Sync
3290 + fmt::Display
3291 + Copy
3292 + 'static
3293{
3294 const MAX_EXACT_INTEGER: Self;
3295 fn is_invalid_index(self) -> bool;
3296 fn read_invalid_flag(backend: &CudaBackend, flag: &TypedTensor<Self>) -> crate::Result<Self>;
3297}
3298
3299trait CudaIndexValidation: Sized {
3300 fn validate(backend: &CudaBackend, indices: &TypedTensor<Self>) -> crate::Result<()>;
3301}
3302
3303impl CudaIndexValidation for f32 {
3304 fn validate(backend: &CudaBackend, indices: &TypedTensor<Self>) -> crate::Result<()> {
3305 validate_float_index_tensor(backend, indices)
3306 }
3307}
3308
3309impl CudaIndexValidation for f64 {
3310 fn validate(backend: &CudaBackend, indices: &TypedTensor<Self>) -> crate::Result<()> {
3311 validate_float_index_tensor(backend, indices)
3312 }
3313}
3314
3315impl CudaIndexValidation for i32 {
3316 fn validate(_backend: &CudaBackend, _indices: &TypedTensor<Self>) -> crate::Result<()> {
3317 Ok(())
3318 }
3319}
3320
3321impl CudaIndexValidation for i64 {
3322 fn validate(_backend: &CudaBackend, _indices: &TypedTensor<Self>) -> crate::Result<()> {
3323 Ok(())
3324 }
3325}
3326
3327impl CudaFloatIndex for f32 {
3328 const MAX_EXACT_INTEGER: Self = 16_777_216.0;
3329
3330 fn is_invalid_index(self) -> bool {
3331 !self.is_finite() || self.fract() != 0.0 || self.abs() > 16_777_216.0
3332 }
3333
3334 fn read_invalid_flag(backend: &CudaBackend, flag: &TypedTensor<Self>) -> crate::Result<Self> {
3335 let host = interop::download_typed_tensor(backend.runtime(), flag, "index_tensor")?;
3336 host.as_slice()?.get(1).copied().ok_or_else(|| {
3337 crate::Error::invalid_argument(
3338 "index_tensor",
3339 "validation_flag",
3340 "validation flag was malformed",
3341 )
3342 })
3343 }
3344}
3345
3346impl CudaFloatIndex for f64 {
3347 const MAX_EXACT_INTEGER: Self = 9_007_199_254_740_992.0;
3348
3349 fn is_invalid_index(self) -> bool {
3350 !self.is_finite() || self.fract() != 0.0 || self.abs() > 9_007_199_254_740_992.0
3351 }
3352
3353 fn read_invalid_flag(backend: &CudaBackend, flag: &TypedTensor<Self>) -> crate::Result<Self> {
3354 let host = interop::download_typed_tensor(backend.runtime(), flag, "index_tensor")?;
3355 host.as_slice()?.get(1).copied().ok_or_else(|| {
3356 crate::Error::invalid_argument(
3357 "index_tensor",
3358 "validation_flag",
3359 "validation flag was malformed",
3360 )
3361 })
3362 }
3363}
3364
3365fn validate_float_index_tensor<F>(
3366 backend: &CudaBackend,
3367 indices: &TypedTensor<F>,
3368) -> crate::Result<()>
3369where
3370 F: CudaFloatIndex,
3371{
3372 ensure_resident_on_runtime(backend.runtime(), indices, "index_tensor")?;
3373 let indices_arg = typed_tensor_binding(indices, "index_tensor")?;
3374 if indices.n_elements() == 0 {
3375 return Ok(());
3376 }
3377 u32::try_from(indices.n_elements()).map_err(|_| {
3378 crate::Error::invalid_argument(
3379 "index_tensor",
3380 "shape",
3381 "float index validation domain exceeds u32::MAX elements",
3382 )
3383 })?;
3384 let count = cube_count_for_len(indices.n_elements())?;
3385 let flag_u32_len = std::mem::size_of::<F>()
3386 .checked_mul(2)
3387 .and_then(|bytes| bytes.checked_div(std::mem::size_of::<u32>()))
3388 .ok_or_else(|| {
3389 crate::Error::invalid_argument("index_tensor", "shape", "flag size overflow")
3390 })?;
3391 let flag = alloc_output::<F>(backend.runtime(), &[2])?;
3392 let flag_values = typed_tensor_array_arg(&flag, "index_tensor")?;
3393 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "index_tensor")?;
3394 unsafe {
3395 indexing::init_float_index_validation_flag::launch_unchecked::<F, CubeclCudaRuntime>(
3398 backend.runtime().client(),
3399 CubeCount::Static(1, 1, 1),
3400 cube_dim_1d(),
3401 flag_atomic,
3402 flag_values,
3403 );
3404 }
3405 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "index_tensor")?;
3406 unsafe {
3407 indexing::validate_float_indices_kernel::launch_unchecked::<F, CubeclCudaRuntime>(
3411 backend.runtime().client(),
3412 count,
3413 cube_dim_1d(),
3414 indices_arg.into_tensor_arg(),
3415 flag_atomic,
3416 F::MAX_EXACT_INTEGER,
3417 );
3418 }
3419 let indices_arg = typed_tensor_binding(indices, "index_tensor")?;
3420 let flag_atomic = typed_tensor_array_arg_as::<F, u32>(&flag, flag_u32_len, "index_tensor")?;
3421 let flag_values = typed_tensor_array_arg(&flag, "index_tensor")?;
3422 unsafe {
3423 indexing::extract_invalid_float_index_kernel::launch_unchecked::<F, CubeclCudaRuntime>(
3426 backend.runtime().client(),
3427 CubeCount::Static(1, 1, 1),
3428 cube_dim_1d(),
3429 indices_arg.into_tensor_arg(),
3430 flag_atomic,
3431 flag_values,
3432 );
3433 }
3434 let invalid = F::read_invalid_flag(backend, &flag)?;
3435 if invalid.is_invalid_index() {
3436 return Err(crate::Error::invalid_argument(
3437 "index_tensor",
3438 "index",
3439 format!("index value {invalid} is not an exactly representable i64"),
3440 ));
3441 }
3442 Ok(())
3443}
3444
3445fn launch_checked_integer_binary<I>(
3446 backend: &CudaBackend,
3447 lhs: &TypedTensor<I>,
3448 rhs: &TypedTensor<I>,
3449 op: &'static str,
3450 dtype: crate::DType,
3451 domain: CheckedIntegerDomain,
3452 launch: impl FnOnce(
3453 &ComputeClient<CubeclCudaRuntime>,
3454 CubeCount,
3455 CubeDim,
3456 ArrayArg<CubeclCudaRuntime>,
3457 ArrayArg<CubeclCudaRuntime>,
3458 ArrayArg<CubeclCudaRuntime>,
3459 ArrayArg<CubeclCudaRuntime>,
3460 ),
3461) -> crate::Result<TypedTensor<I>>
3462where
3463 I: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
3464{
3465 dispatch::ensure_same_shape(op, lhs.shape(), rhs.shape())?;
3466 ensure_resident_on_runtime(backend.runtime(), lhs, op)?;
3467 ensure_resident_on_runtime(backend.runtime(), rhs, op)?;
3468
3469 let output = alloc_output::<I>(backend.runtime(), lhs.shape())?;
3470 if output.n_elements() == 0 {
3471 return Ok(output);
3472 }
3473
3474 let flag = alloc_output::<i32>(backend.runtime(), &[1])?;
3475 launch_nullary_into(
3476 backend.runtime(),
3477 &flag,
3478 op,
3479 cube_count_for_len(flag.n_elements())?,
3480 cube_dim_1d(),
3481 |client, count, dim, out| unsafe {
3482 structural::fill_zero_kernel::launch_unchecked::<i32, CubeclCudaRuntime>(
3483 client, count, dim, out,
3484 );
3485 },
3486 )?;
3487
3488 let output_arg = typed_tensor_array_arg(&output, op)?;
3489 let lhs_arg = typed_tensor_array_arg(lhs, op)?;
3490 let rhs_arg = typed_tensor_array_arg(rhs, op)?;
3491 let flag_arg = typed_tensor_array_arg(&flag, op)?;
3492 launch(
3493 backend.runtime().client(),
3494 cube_count_for_len(output.n_elements())?,
3495 cube_dim_1d(),
3496 output_arg,
3497 lhs_arg,
3498 rhs_arg,
3499 flag_arg,
3500 );
3501
3502 if read_checked_integer_flag(backend, &flag, op)? != 0 {
3503 return Err(checked_integer_domain_error(domain, op, dtype));
3504 }
3505 Ok(output)
3506}
3507
3508fn launch_scalar_binary<I>(
3509 backend: &CudaBackend,
3510 lhs: &TypedTensor<I>,
3511 rhs: &TypedTensor<I>,
3512 op: &'static str,
3513 launch: impl FnOnce(
3514 &ComputeClient<CubeclCudaRuntime>,
3515 CubeCount,
3516 CubeDim,
3517 ArrayArg<CubeclCudaRuntime>,
3518 ArrayArg<CubeclCudaRuntime>,
3519 ArrayArg<CubeclCudaRuntime>,
3520 bool,
3521 ),
3522) -> crate::Result<TypedTensor<I>>
3523where
3524 I: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
3525{
3526 if !(lhs.shape().is_empty() ^ rhs.shape().is_empty()) {
3527 return Err(crate::Error::shape_mismatch(
3528 op,
3529 lhs.shape().to_vec(),
3530 rhs.shape().to_vec(),
3531 ));
3532 }
3533 ensure_resident_on_runtime(backend.runtime(), lhs, op)?;
3534 ensure_resident_on_runtime(backend.runtime(), rhs, op)?;
3535
3536 let lhs_scalar = lhs.shape().is_empty();
3537 let output_shape = if lhs_scalar { rhs.shape() } else { lhs.shape() };
3538 let output = alloc_output::<I>(backend.runtime(), output_shape)?;
3539 let output_arg = typed_tensor_array_arg(&output, op)?;
3540 let lhs_arg = typed_tensor_array_arg(lhs, op)?;
3541 let rhs_arg = typed_tensor_array_arg(rhs, op)?;
3542 if output.n_elements() == 0 {
3543 return Ok(output);
3544 }
3545 launch(
3546 backend.runtime().client(),
3547 cube_count_for_len(output.n_elements())?,
3548 cube_dim_1d(),
3549 output_arg,
3550 lhs_arg,
3551 rhs_arg,
3552 lhs_scalar,
3553 );
3554 Ok(output)
3555}
3556
3557fn launch_real_complex_scalar_binary<R, C>(
3558 backend: &CudaBackend,
3559 real: &TypedTensor<R>,
3560 complex: &TypedTensor<C>,
3561 op: &'static str,
3562 real_lhs: bool,
3563 mode: usize,
3564) -> crate::Result<TypedTensor<C>>
3565where
3566 R: TensorScalar + CubeFloat + CubeElement + CubePrimitive + Clone + Send + Sync + 'static,
3567 C: CubeComplex<FloatElem = R>
3568 + TensorScalar
3569 + CubeElement
3570 + CubePrimitive
3571 + Clone
3572 + Send
3573 + Sync
3574 + 'static,
3575{
3576 if !real.shape().is_empty() {
3577 return Err(crate::Error::shape_mismatch(
3578 op,
3579 if real_lhs {
3580 real.shape().to_vec()
3581 } else {
3582 complex.shape().to_vec()
3583 },
3584 if real_lhs {
3585 complex.shape().to_vec()
3586 } else {
3587 real.shape().to_vec()
3588 },
3589 ));
3590 }
3591 ensure_resident_on_runtime(backend.runtime(), real, op)?;
3592 ensure_resident_on_runtime(backend.runtime(), complex, op)?;
3593 let component_len = complex.n_elements().checked_mul(2).ok_or_else(|| {
3594 crate::Error::invalid_argument(op, "shape", "complex component length overflow")
3595 })?;
3596 let real_arg = typed_tensor_array_arg(real, op)?;
3597 let complex_arg = typed_tensor_array_arg_as::<C, R>(complex, component_len, op)?;
3601
3602 let output = alloc_output::<C>(backend.runtime(), complex.shape())?;
3603 let output_arg = typed_tensor_array_arg_as::<C, R>(&output, component_len, op)?;
3604 if output.n_elements() == 0 {
3605 return Ok(output);
3606 }
3607 unsafe {
3608 elementwise::scalar_real_complex_binary::launch_unchecked::<R, CubeclCudaRuntime>(
3609 backend.runtime().client(),
3610 cube_count_for_len(output.n_elements())?,
3611 cube_dim_1d(),
3612 output_arg,
3613 real_arg,
3614 complex_arg,
3615 real_lhs,
3616 mode,
3617 );
3618 }
3619 Ok(output)
3620}
3621
3622fn promoted_real_complex_scalar_binary(
3623 backend: &CudaBackend,
3624 lhs: &Tensor,
3625 rhs: &Tensor,
3626 op: &'static str,
3627 mode: usize,
3628) -> Option<crate::Result<Tensor>> {
3629 match (lhs, rhs) {
3630 (Tensor::F32(real), Tensor::C32(complex)) if real.shape().is_empty() => Some(
3631 launch_real_complex_scalar_binary(backend, real, complex, op, true, mode)
3632 .map(Tensor::C32),
3633 ),
3634 (Tensor::C32(complex), Tensor::F32(real)) if real.shape().is_empty() => Some(
3635 launch_real_complex_scalar_binary(backend, real, complex, op, false, mode)
3636 .map(Tensor::C32),
3637 ),
3638 (Tensor::F64(real), Tensor::C64(complex)) if real.shape().is_empty() => Some(
3639 launch_real_complex_scalar_binary(backend, real, complex, op, true, mode)
3640 .map(Tensor::C64),
3641 ),
3642 (Tensor::C64(complex), Tensor::F64(real)) if real.shape().is_empty() => Some(
3643 launch_real_complex_scalar_binary(backend, real, complex, op, false, mode)
3644 .map(Tensor::C64),
3645 ),
3646 _ => None,
3647 }
3648}
3649
3650fn launch_checked_integer_scalar_binary<I>(
3651 backend: &CudaBackend,
3652 lhs: &TypedTensor<I>,
3653 rhs: &TypedTensor<I>,
3654 op: &'static str,
3655 dtype: crate::DType,
3656 domain: CheckedIntegerDomain,
3657 launch: impl FnOnce(
3658 &ComputeClient<CubeclCudaRuntime>,
3659 CubeCount,
3660 CubeDim,
3661 ArrayArg<CubeclCudaRuntime>,
3662 ArrayArg<CubeclCudaRuntime>,
3663 ArrayArg<CubeclCudaRuntime>,
3664 ArrayArg<CubeclCudaRuntime>,
3665 bool,
3666 ),
3667) -> crate::Result<TypedTensor<I>>
3668where
3669 I: CubeElement + TensorScalar + CubePrimitive + Clone + Send + Sync + 'static,
3670{
3671 if !(lhs.shape().is_empty() ^ rhs.shape().is_empty()) {
3672 return Err(crate::Error::shape_mismatch(
3673 op,
3674 lhs.shape().to_vec(),
3675 rhs.shape().to_vec(),
3676 ));
3677 }
3678 ensure_resident_on_runtime(backend.runtime(), lhs, op)?;
3679 ensure_resident_on_runtime(backend.runtime(), rhs, op)?;
3680
3681 let lhs_scalar = lhs.shape().is_empty();
3682 let output_shape = if lhs_scalar { rhs.shape() } else { lhs.shape() };
3683 let output = alloc_output::<I>(backend.runtime(), output_shape)?;
3684 let output_arg = typed_tensor_array_arg(&output, op)?;
3685 let lhs_arg = typed_tensor_array_arg(lhs, op)?;
3686 let rhs_arg = typed_tensor_array_arg(rhs, op)?;
3687 if output.n_elements() == 0 {
3688 return Ok(output);
3689 }
3690 let flag = alloc_output::<i32>(backend.runtime(), &[1])?;
3691 let flag_arg = typed_tensor_array_arg(&flag, op)?;
3692 launch_nullary_into(
3693 backend.runtime(),
3694 &flag,
3695 op,
3696 cube_count_for_len(flag.n_elements())?,
3697 cube_dim_1d(),
3698 |client, count, dim, out| unsafe {
3699 structural::fill_zero_kernel::launch_unchecked::<i32, CubeclCudaRuntime>(
3700 client, count, dim, out,
3701 );
3702 },
3703 )?;
3704
3705 launch(
3706 backend.runtime().client(),
3707 cube_count_for_len(output.n_elements())?,
3708 cube_dim_1d(),
3709 output_arg,
3710 lhs_arg,
3711 rhs_arg,
3712 flag_arg,
3713 lhs_scalar,
3714 );
3715 if read_checked_integer_flag(backend, &flag, op)? != 0 {
3716 return Err(checked_integer_domain_error(domain, op, dtype));
3717 }
3718 Ok(output)
3719}
3720
3721impl TensorElementwise for CudaBackend {
3722 fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
3723 if let Some(result) =
3724 promoted_real_complex_scalar_binary(self, lhs, rhs, "add", elementwise::MIXED_ADD)
3725 {
3726 return result;
3727 }
3728 dispatch::dispatch_binary_float_complex_int!(
3729 self,
3730 lhs,
3731 rhs,
3732 PrimitiveOpKind::Add,
3733 add_float,
3734 add_int,
3735 add_complex
3736 )
3737 }
3738
3739 fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
3740 if let Some(result) =
3741 promoted_real_complex_scalar_binary(self, lhs, rhs, "sub", elementwise::MIXED_SUB)
3742 {
3743 return result;
3744 }
3745 dispatch::dispatch_binary_float_complex_int!(
3746 self,
3747 lhs,
3748 rhs,
3749 PrimitiveOpKind::Sub,
3750 sub_float,
3751 sub_int,
3752 sub_complex
3753 )
3754 }
3755
3756 fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
3757 if let Some(result) =
3758 promoted_real_complex_scalar_binary(self, lhs, rhs, "mul", elementwise::MIXED_MUL)
3759 {
3760 return result;
3761 }
3762 dispatch::dispatch_binary_float_complex_int!(
3763 self,
3764 lhs,
3765 rhs,
3766 PrimitiveOpKind::Mul,
3767 mul_float,
3768 mul_int,
3769 mul_complex
3770 )
3771 }
3772
3773 fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor> {
3774 dispatch::dispatch_unary_float_complex_int!(
3775 self,
3776 input,
3777 PrimitiveOpKind::Neg,
3778 neg_float,
3779 neg_int,
3780 neg_complex
3781 )
3782 }
3783
3784 fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor> {
3785 let op = op_name(
3786 PrimitiveOpKind::Conj,
3787 op_descriptor::GpuLaunchKind::UnaryFloatComplex,
3788 )?;
3789 match input {
3790 Tensor::F32(tensor) => {
3791 ensure_resident_on_runtime(self.runtime(), tensor, op)?;
3792 self.to_contiguous_view_typed(&tensor.as_view(), op)
3793 .map(Tensor::F32)
3794 }
3795 Tensor::F64(tensor) => {
3796 ensure_resident_on_runtime(self.runtime(), tensor, op)?;
3797 self.to_contiguous_view_typed(&tensor.as_view(), op)
3798 .map(Tensor::F64)
3799 }
3800 Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => {
3801 Err(unsupported_dtype(op, input.dtype()))
3802 }
3803 Tensor::C32(tensor) => launch_unary(
3804 self.runtime(),
3805 tensor,
3806 tensor.shape(),
3807 op,
3808 |client, count, dim, out, input_arg| unsafe {
3809 elementwise::conj_complex::launch_unchecked::<Complex32, CubeclCudaRuntime>(
3810 client, count, dim, out, input_arg,
3811 );
3812 },
3813 )
3814 .map(Tensor::C32),
3815 Tensor::C64(tensor) => launch_unary(
3816 self.runtime(),
3817 tensor,
3818 tensor.shape(),
3819 op,
3820 |client, count, dim, out, input_arg| unsafe {
3821 elementwise::conj_complex::launch_unchecked::<Complex64, CubeclCudaRuntime>(
3822 client, count, dim, out, input_arg,
3823 );
3824 },
3825 )
3826 .map(Tensor::C64),
3827 }
3828 }
3829
3830 fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
3831 let op = op_name(
3832 PrimitiveOpKind::Div,
3833 op_descriptor::GpuLaunchKind::BinaryFloatComplexInt,
3834 )?;
3835 if let Some(result) =
3836 promoted_real_complex_scalar_binary(self, lhs, rhs, op, elementwise::MIXED_DIV)
3837 {
3838 return result;
3839 }
3840 match (lhs, rhs) {
3841 (Tensor::F32(lhs), Tensor::F32(rhs)) if lhs.shape() != rhs.shape() => {
3842 launch_scalar_binary(
3843 self,
3844 lhs,
3845 rhs,
3846 op,
3847 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
3848 elementwise::scalar_div_float::launch_unchecked::<f32, CubeclCudaRuntime>(
3849 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
3850 );
3851 },
3852 )
3853 .map(Tensor::F32)
3854 }
3855 (Tensor::F32(lhs), Tensor::F32(rhs)) => launch_binary(
3856 self.runtime(),
3857 lhs,
3858 rhs,
3859 lhs.shape(),
3860 op,
3861 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
3862 elementwise::div_float::launch_unchecked::<f32, CubeclCudaRuntime>(
3863 client, count, dim, out, lhs_arg, rhs_arg,
3864 );
3865 },
3866 )
3867 .map(Tensor::F32),
3868 (Tensor::F64(lhs), Tensor::F64(rhs)) if lhs.shape() != rhs.shape() => {
3869 launch_scalar_binary(
3870 self,
3871 lhs,
3872 rhs,
3873 op,
3874 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
3875 elementwise::scalar_div_float::launch_unchecked::<f64, CubeclCudaRuntime>(
3876 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
3877 );
3878 },
3879 )
3880 .map(Tensor::F64)
3881 }
3882 (Tensor::F64(lhs), Tensor::F64(rhs)) => launch_binary(
3883 self.runtime(),
3884 lhs,
3885 rhs,
3886 lhs.shape(),
3887 op,
3888 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
3889 elementwise::div_float::launch_unchecked::<f64, CubeclCudaRuntime>(
3890 client, count, dim, out, lhs_arg, rhs_arg,
3891 );
3892 },
3893 )
3894 .map(Tensor::F64),
3895 (Tensor::I32(lhs), Tensor::I32(rhs)) if lhs.shape() != rhs.shape() => {
3896 launch_checked_integer_scalar_binary(
3897 self,
3898 lhs,
3899 rhs,
3900 op,
3901 crate::DType::I32,
3902 CheckedIntegerDomain::DivisionByZero,
3903 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
3904 elementwise::scalar_div_int_checked::launch_unchecked::<
3905 i32,
3906 CubeclCudaRuntime,
3907 >(
3908 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
3909 );
3910 },
3911 )
3912 .map(Tensor::I32)
3913 }
3914 (Tensor::I32(lhs), Tensor::I32(rhs)) => launch_checked_integer_binary(
3915 self,
3916 lhs,
3917 rhs,
3918 op,
3919 crate::DType::I32,
3920 CheckedIntegerDomain::DivisionByZero,
3921 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
3922 elementwise::div_int_checked::launch_unchecked::<i32, CubeclCudaRuntime>(
3923 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
3924 );
3925 },
3926 )
3927 .map(Tensor::I32),
3928 (Tensor::I64(lhs), Tensor::I64(rhs)) if lhs.shape() != rhs.shape() => {
3929 launch_checked_integer_scalar_binary(
3930 self,
3931 lhs,
3932 rhs,
3933 op,
3934 crate::DType::I64,
3935 CheckedIntegerDomain::DivisionByZero,
3936 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
3937 elementwise::scalar_div_int_checked::launch_unchecked::<
3938 i64,
3939 CubeclCudaRuntime,
3940 >(
3941 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
3942 );
3943 },
3944 )
3945 .map(Tensor::I64)
3946 }
3947 (Tensor::I64(lhs), Tensor::I64(rhs)) => launch_checked_integer_binary(
3948 self,
3949 lhs,
3950 rhs,
3951 op,
3952 crate::DType::I64,
3953 CheckedIntegerDomain::DivisionByZero,
3954 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
3955 elementwise::div_int_checked::launch_unchecked::<i64, CubeclCudaRuntime>(
3956 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
3957 );
3958 },
3959 )
3960 .map(Tensor::I64),
3961 (Tensor::C32(lhs), Tensor::C32(rhs)) => launch_binary(
3962 self.runtime(),
3963 lhs,
3964 rhs,
3965 lhs.shape(),
3966 op,
3967 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
3968 elementwise::div_complex::launch_unchecked::<Complex32, CubeclCudaRuntime>(
3969 client, count, dim, out, lhs_arg, rhs_arg,
3970 );
3971 },
3972 )
3973 .map(Tensor::C32),
3974 (Tensor::C64(lhs), Tensor::C64(rhs)) => launch_binary(
3975 self.runtime(),
3976 lhs,
3977 rhs,
3978 lhs.shape(),
3979 op,
3980 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
3981 elementwise::div_complex::launch_unchecked::<Complex64, CubeclCudaRuntime>(
3982 client, count, dim, out, lhs_arg, rhs_arg,
3983 );
3984 },
3985 )
3986 .map(Tensor::C64),
3987 _ => Err(dtype_mismatch(op, lhs, rhs)),
3988 }
3989 }
3990
3991 fn rem(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
3992 let op = op_name(
3993 PrimitiveOpKind::Rem,
3994 op_descriptor::GpuLaunchKind::BinaryFloatInt,
3995 )?;
3996 match (lhs, rhs) {
3997 (Tensor::F32(lhs), Tensor::F32(rhs)) if lhs.shape() != rhs.shape() => {
3998 launch_scalar_binary(
3999 self,
4000 lhs,
4001 rhs,
4002 op,
4003 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
4004 elementwise::scalar_rem_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4005 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
4006 );
4007 },
4008 )
4009 .map(Tensor::F32)
4010 }
4011 (Tensor::F32(lhs), Tensor::F32(rhs)) => launch_binary(
4012 self.runtime(),
4013 lhs,
4014 rhs,
4015 lhs.shape(),
4016 op,
4017 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4018 elementwise::rem_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4019 client, count, dim, out, lhs_arg, rhs_arg,
4020 );
4021 },
4022 )
4023 .map(Tensor::F32),
4024 (Tensor::F64(lhs), Tensor::F64(rhs)) if lhs.shape() != rhs.shape() => {
4025 launch_scalar_binary(
4026 self,
4027 lhs,
4028 rhs,
4029 op,
4030 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
4031 elementwise::scalar_rem_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4032 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
4033 );
4034 },
4035 )
4036 .map(Tensor::F64)
4037 }
4038 (Tensor::F64(lhs), Tensor::F64(rhs)) => launch_binary(
4039 self.runtime(),
4040 lhs,
4041 rhs,
4042 lhs.shape(),
4043 op,
4044 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4045 elementwise::rem_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4046 client, count, dim, out, lhs_arg, rhs_arg,
4047 );
4048 },
4049 )
4050 .map(Tensor::F64),
4051 (Tensor::I32(lhs), Tensor::I32(rhs)) if lhs.shape() != rhs.shape() => {
4052 launch_checked_integer_scalar_binary(
4053 self,
4054 lhs,
4055 rhs,
4056 op,
4057 crate::DType::I32,
4058 CheckedIntegerDomain::DivisionByZero,
4059 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
4060 elementwise::scalar_rem_int_checked::launch_unchecked::<
4061 i32,
4062 CubeclCudaRuntime,
4063 >(
4064 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
4065 );
4066 },
4067 )
4068 .map(Tensor::I32)
4069 }
4070 (Tensor::I32(lhs), Tensor::I32(rhs)) => launch_checked_integer_binary(
4071 self,
4072 lhs,
4073 rhs,
4074 op,
4075 crate::DType::I32,
4076 CheckedIntegerDomain::DivisionByZero,
4077 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
4078 elementwise::rem_int_checked::launch_unchecked::<i32, CubeclCudaRuntime>(
4079 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
4080 );
4081 },
4082 )
4083 .map(Tensor::I32),
4084 (Tensor::I64(lhs), Tensor::I64(rhs)) if lhs.shape() != rhs.shape() => {
4085 launch_checked_integer_scalar_binary(
4086 self,
4087 lhs,
4088 rhs,
4089 op,
4090 crate::DType::I64,
4091 CheckedIntegerDomain::DivisionByZero,
4092 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
4093 elementwise::scalar_rem_int_checked::launch_unchecked::<
4094 i64,
4095 CubeclCudaRuntime,
4096 >(
4097 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
4098 );
4099 },
4100 )
4101 .map(Tensor::I64)
4102 }
4103 (Tensor::I64(lhs), Tensor::I64(rhs)) => launch_checked_integer_binary(
4104 self,
4105 lhs,
4106 rhs,
4107 op,
4108 crate::DType::I64,
4109 CheckedIntegerDomain::DivisionByZero,
4110 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
4111 elementwise::rem_int_checked::launch_unchecked::<i64, CubeclCudaRuntime>(
4112 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
4113 );
4114 },
4115 )
4116 .map(Tensor::I64),
4117 (Tensor::C32(_), Tensor::C32(_)) | (Tensor::C64(_), Tensor::C64(_)) => {
4118 Err(unsupported_dtype(op, lhs.dtype()))
4119 }
4120 _ => Err(dtype_mismatch(op, lhs, rhs)),
4121 }
4122 }
4123
4124 fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4125 let descriptor = op_descriptor::require_gpu_descriptor(
4126 PrimitiveOpKind::Abs,
4127 op_descriptor::GpuLaunchKind::UnaryFloatInt,
4128 )?;
4129 let op = descriptor.name;
4130 dispatch::require_owned_capability(self, PrimitiveOpKind::Abs, input.dtype())?;
4131 match input {
4132 Tensor::F32(tensor) => {
4133 dispatch::launch_unary_elementwise_kernel!(self, tensor, op, abs_float, f32, F32)
4134 }
4135 Tensor::F64(tensor) => {
4136 dispatch::launch_unary_elementwise_kernel!(self, tensor, op, abs_float, f64, F64)
4137 }
4138 Tensor::I32(tensor) => {
4139 dispatch::launch_unary_elementwise_kernel!(self, tensor, op, abs_int, i32, I32)
4140 }
4141 Tensor::I64(tensor) => {
4142 dispatch::launch_unary_elementwise_kernel!(self, tensor, op, abs_int, i64, I64)
4143 }
4144 Tensor::C32(tensor) => dispatch::launch_unary(
4145 self.runtime(),
4146 tensor,
4147 tensor.shape(),
4148 op,
4149 |client, count, dim, out, input_arg| unsafe {
4150 elementwise::abs_complex32::launch_unchecked::<CubeclCudaRuntime>(
4151 client, count, dim, out, input_arg,
4152 );
4153 },
4154 )
4155 .map(Tensor::F32),
4156 Tensor::C64(tensor) => dispatch::launch_unary(
4157 self.runtime(),
4158 tensor,
4159 tensor.shape(),
4160 op,
4161 |client, count, dim, out, input_arg| unsafe {
4162 elementwise::abs_complex64::launch_unchecked::<CubeclCudaRuntime>(
4163 client, count, dim, out, input_arg,
4164 );
4165 },
4166 )
4167 .map(Tensor::F64),
4168 Tensor::Bool(_) => Err(unsupported_dtype(op, input.dtype())),
4169 }
4170 }
4171
4172 fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4173 dispatch::dispatch_unary_float_int!(
4174 self,
4175 input,
4176 PrimitiveOpKind::Sign,
4177 sign_float,
4178 sign_int
4179 )
4180 }
4181
4182 fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
4183 dispatch::dispatch_binary_float_int!(
4184 self,
4185 lhs,
4186 rhs,
4187 PrimitiveOpKind::Maximum,
4188 maximum_float,
4189 maximum_int
4190 )
4191 }
4192
4193 fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
4194 dispatch::dispatch_binary_float_int!(
4195 self,
4196 lhs,
4197 rhs,
4198 PrimitiveOpKind::Minimum,
4199 minimum_float,
4200 minimum_int
4201 )
4202 }
4203
4204 fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor> {
4205 let op = op_name(
4206 PrimitiveOpKind::Compare,
4207 op_descriptor::GpuLaunchKind::CompareFloatIntToBool,
4208 )?;
4209 match (lhs, rhs) {
4210 (Tensor::F32(lhs), Tensor::F32(rhs)) => launch_compare_bool(
4211 self.runtime(),
4212 lhs,
4213 rhs,
4214 lhs.shape(),
4215 op,
4216 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4217 elementwise::compare_float_bool::launch_unchecked::<f32, CubeclCudaRuntime>(
4218 client,
4219 count,
4220 dim,
4221 out,
4222 lhs_arg,
4223 rhs_arg,
4224 dispatch::compare_mode(dir),
4225 );
4226 },
4227 )
4228 .map(Tensor::Bool),
4229 (Tensor::F64(lhs), Tensor::F64(rhs)) => launch_compare_bool(
4230 self.runtime(),
4231 lhs,
4232 rhs,
4233 lhs.shape(),
4234 op,
4235 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4236 elementwise::compare_float_bool::launch_unchecked::<f64, CubeclCudaRuntime>(
4237 client,
4238 count,
4239 dim,
4240 out,
4241 lhs_arg,
4242 rhs_arg,
4243 dispatch::compare_mode(dir),
4244 );
4245 },
4246 )
4247 .map(Tensor::Bool),
4248 (Tensor::I32(lhs), Tensor::I32(rhs)) => launch_compare_bool(
4249 self.runtime(),
4250 lhs,
4251 rhs,
4252 lhs.shape(),
4253 op,
4254 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4255 elementwise::compare_int_bool::launch_unchecked::<i32, CubeclCudaRuntime>(
4256 client,
4257 count,
4258 dim,
4259 out,
4260 lhs_arg,
4261 rhs_arg,
4262 dispatch::compare_mode(dir),
4263 );
4264 },
4265 )
4266 .map(Tensor::Bool),
4267 (Tensor::I64(lhs), Tensor::I64(rhs)) => launch_compare_bool(
4268 self.runtime(),
4269 lhs,
4270 rhs,
4271 lhs.shape(),
4272 op,
4273 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4274 elementwise::compare_int_bool::launch_unchecked::<i64, CubeclCudaRuntime>(
4275 client,
4276 count,
4277 dim,
4278 out,
4279 lhs_arg,
4280 rhs_arg,
4281 dispatch::compare_mode(dir),
4282 );
4283 },
4284 )
4285 .map(Tensor::Bool),
4286 (Tensor::C32(_), Tensor::C32(_)) | (Tensor::C64(_), Tensor::C64(_)) => {
4287 Err(unsupported_dtype(op, lhs.dtype()))
4288 }
4289 _ => Err(dtype_mismatch(op, lhs, rhs)),
4290 }
4291 }
4292
4293 fn select(
4294 &mut self,
4295 pred: &Tensor,
4296 on_true: &Tensor,
4297 on_false: &Tensor,
4298 ) -> crate::Result<Tensor> {
4299 let op = op_name(
4300 PrimitiveOpKind::Select,
4301 op_descriptor::GpuLaunchKind::SelectBoolFloatInt,
4302 )?;
4303 match (pred, on_true, on_false) {
4304 (Tensor::Bool(pred), Tensor::F32(on_true), Tensor::F32(on_false)) => {
4305 launch_select_bool(
4306 self.runtime(),
4307 pred,
4308 on_true,
4309 on_false,
4310 pred.shape(),
4311 op,
4312 |client, count, dim, out, pred_arg, true_arg, false_arg| unsafe {
4313 elementwise::select_bool_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4314 client, count, dim, out, pred_arg, true_arg, false_arg,
4315 );
4316 },
4317 )
4318 .map(Tensor::F32)
4319 }
4320 (Tensor::Bool(pred), Tensor::F64(on_true), Tensor::F64(on_false)) => {
4321 launch_select_bool(
4322 self.runtime(),
4323 pred,
4324 on_true,
4325 on_false,
4326 pred.shape(),
4327 op,
4328 |client, count, dim, out, pred_arg, true_arg, false_arg| unsafe {
4329 elementwise::select_bool_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4330 client, count, dim, out, pred_arg, true_arg, false_arg,
4331 );
4332 },
4333 )
4334 .map(Tensor::F64)
4335 }
4336 (Tensor::Bool(pred), Tensor::I32(on_true), Tensor::I32(on_false)) => {
4337 launch_select_bool(
4338 self.runtime(),
4339 pred,
4340 on_true,
4341 on_false,
4342 pred.shape(),
4343 op,
4344 |client, count, dim, out, pred_arg, true_arg, false_arg| unsafe {
4345 elementwise::select_bool_int::launch_unchecked::<i32, CubeclCudaRuntime>(
4346 client, count, dim, out, pred_arg, true_arg, false_arg,
4347 );
4348 },
4349 )
4350 .map(Tensor::I32)
4351 }
4352 (Tensor::Bool(pred), Tensor::I64(on_true), Tensor::I64(on_false)) => {
4353 launch_select_bool(
4354 self.runtime(),
4355 pred,
4356 on_true,
4357 on_false,
4358 pred.shape(),
4359 op,
4360 |client, count, dim, out, pred_arg, true_arg, false_arg| unsafe {
4361 elementwise::select_bool_int::launch_unchecked::<i64, CubeclCudaRuntime>(
4362 client, count, dim, out, pred_arg, true_arg, false_arg,
4363 );
4364 },
4365 )
4366 .map(Tensor::I64)
4367 }
4368 (Tensor::C32(_), Tensor::C32(_), Tensor::C32(_))
4369 | (Tensor::C64(_), Tensor::C64(_), Tensor::C64(_)) => {
4370 Err(unsupported_dtype(op, pred.dtype()))
4371 }
4372 _ => Err(ternary_dtype_mismatch(op, pred, on_true, on_false)),
4373 }
4374 }
4375
4376 fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor> {
4377 let op = op_name(
4378 PrimitiveOpKind::Clamp,
4379 op_descriptor::GpuLaunchKind::ClampFloat,
4380 )?;
4381 match (input, lower, upper) {
4382 (Tensor::F32(input), Tensor::F32(lower), Tensor::F32(upper)) => launch_ternary(
4383 self.runtime(),
4384 input,
4385 lower,
4386 upper,
4387 input.shape(),
4388 op,
4389 |client, count, dim, out, input_arg, lower_arg, upper_arg| unsafe {
4390 elementwise::clamp_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4391 client, count, dim, out, input_arg, lower_arg, upper_arg,
4392 );
4393 },
4394 )
4395 .map(Tensor::F32),
4396 (Tensor::F64(input), Tensor::F64(lower), Tensor::F64(upper)) => launch_ternary(
4397 self.runtime(),
4398 input,
4399 lower,
4400 upper,
4401 input.shape(),
4402 op,
4403 |client, count, dim, out, input_arg, lower_arg, upper_arg| unsafe {
4404 elementwise::clamp_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4405 client, count, dim, out, input_arg, lower_arg, upper_arg,
4406 );
4407 },
4408 )
4409 .map(Tensor::F64),
4410 (Tensor::C32(_), Tensor::C32(_), Tensor::C32(_))
4411 | (Tensor::C64(_), Tensor::C64(_), Tensor::C64(_)) => {
4412 Err(unsupported_dtype(op, input.dtype()))
4413 }
4414 _ => Err(ternary_dtype_mismatch(op, input, lower, upper)),
4415 }
4416 }
4417}
4418
4419impl TensorAnalytic for CudaBackend {
4420 fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4421 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Exp, exp_float)
4422 }
4423
4424 fn log(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4425 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Log, log_float)
4426 }
4427
4428 fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4429 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Sin, sin_float)
4430 }
4431
4432 fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4433 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Cos, cos_float)
4434 }
4435
4436 fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4437 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Tanh, tanh_float)
4438 }
4439
4440 fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4441 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Sqrt, sqrt_float)
4442 }
4443
4444 fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4445 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Rsqrt, rsqrt_float)
4446 }
4447
4448 fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
4449 let op = op_name(
4450 PrimitiveOpKind::Pow,
4451 op_descriptor::GpuLaunchKind::BinaryFloatInt,
4452 )?;
4453 if lhs.dtype() != rhs.dtype() {
4454 return Err(dtype_mismatch(op, lhs, rhs));
4455 }
4456 match (lhs, rhs) {
4457 (Tensor::F32(lhs), Tensor::F32(rhs)) if lhs.shape() != rhs.shape() => {
4458 launch_scalar_binary(
4459 self,
4460 lhs,
4461 rhs,
4462 op,
4463 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
4464 elementwise::scalar_pow_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4465 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
4466 );
4467 },
4468 )
4469 .map(Tensor::F32)
4470 }
4471 (Tensor::F32(lhs), Tensor::F32(rhs)) => launch_binary(
4472 self.runtime(),
4473 lhs,
4474 rhs,
4475 lhs.shape(),
4476 op,
4477 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4478 elementwise::pow_float::launch_unchecked::<f32, CubeclCudaRuntime>(
4479 client, count, dim, out, lhs_arg, rhs_arg,
4480 );
4481 },
4482 )
4483 .map(Tensor::F32),
4484 (Tensor::F64(lhs), Tensor::F64(rhs)) if lhs.shape() != rhs.shape() => {
4485 launch_scalar_binary(
4486 self,
4487 lhs,
4488 rhs,
4489 op,
4490 |client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar| unsafe {
4491 elementwise::scalar_pow_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4492 client, count, dim, out, lhs_arg, rhs_arg, lhs_scalar,
4493 );
4494 },
4495 )
4496 .map(Tensor::F64)
4497 }
4498 (Tensor::F64(lhs), Tensor::F64(rhs)) => launch_binary(
4499 self.runtime(),
4500 lhs,
4501 rhs,
4502 lhs.shape(),
4503 op,
4504 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
4505 elementwise::pow_float::launch_unchecked::<f64, CubeclCudaRuntime>(
4506 client, count, dim, out, lhs_arg, rhs_arg,
4507 );
4508 },
4509 )
4510 .map(Tensor::F64),
4511 (Tensor::I32(lhs), Tensor::I32(rhs)) if lhs.shape() != rhs.shape() => {
4512 launch_checked_integer_scalar_binary(
4513 self,
4514 lhs,
4515 rhs,
4516 op,
4517 crate::DType::I32,
4518 CheckedIntegerDomain::NegativeExponent,
4519 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
4520 elementwise::scalar_pow_int_checked::launch_unchecked::<
4521 i32,
4522 CubeclCudaRuntime,
4523 >(
4524 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
4525 );
4526 },
4527 )
4528 .map(Tensor::I32)
4529 }
4530 (Tensor::I32(lhs), Tensor::I32(rhs)) => launch_checked_integer_binary(
4531 self,
4532 lhs,
4533 rhs,
4534 op,
4535 crate::DType::I32,
4536 CheckedIntegerDomain::NegativeExponent,
4537 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
4538 elementwise::pow_int_checked::launch_unchecked::<i32, CubeclCudaRuntime>(
4539 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
4540 );
4541 },
4542 )
4543 .map(Tensor::I32),
4544 (Tensor::I64(lhs), Tensor::I64(rhs)) if lhs.shape() != rhs.shape() => {
4545 launch_checked_integer_scalar_binary(
4546 self,
4547 lhs,
4548 rhs,
4549 op,
4550 crate::DType::I64,
4551 CheckedIntegerDomain::NegativeExponent,
4552 |client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar| unsafe {
4553 elementwise::scalar_pow_int_checked::launch_unchecked::<
4554 i64,
4555 CubeclCudaRuntime,
4556 >(
4557 client, count, dim, out, lhs_arg, rhs_arg, err_arg, lhs_scalar,
4558 );
4559 },
4560 )
4561 .map(Tensor::I64)
4562 }
4563 (Tensor::I64(lhs), Tensor::I64(rhs)) => launch_checked_integer_binary(
4564 self,
4565 lhs,
4566 rhs,
4567 op,
4568 crate::DType::I64,
4569 CheckedIntegerDomain::NegativeExponent,
4570 |client, count, dim, out, lhs_arg, rhs_arg, err_arg| unsafe {
4571 elementwise::pow_int_checked::launch_unchecked::<i64, CubeclCudaRuntime>(
4572 client, count, dim, out, lhs_arg, rhs_arg, err_arg,
4573 );
4574 },
4575 )
4576 .map(Tensor::I64),
4577 (Tensor::C32(lhs), Tensor::C32(rhs)) => {
4578 dispatch::ensure_same_shape(op, lhs.shape(), rhs.shape())?;
4579 Err(unsupported_dtype(op, crate::DType::C32))
4580 }
4581 (Tensor::C64(lhs), Tensor::C64(rhs)) => {
4582 dispatch::ensure_same_shape(op, lhs.shape(), rhs.shape())?;
4583 Err(unsupported_dtype(op, crate::DType::C64))
4584 }
4585 _ => {
4586 dispatch::ensure_same_shape(op, lhs.shape(), rhs.shape())?;
4587 Err(dtype_mismatch(op, lhs, rhs))
4588 }
4589 }
4590 }
4591
4592 fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4593 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Expm1, expm1_float)
4594 }
4595
4596 fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor> {
4597 dispatch::dispatch_unary_float_only!(self, input, PrimitiveOpKind::Log1p, log1p_float)
4598 }
4599}
4600
4601impl TensorStructural for CudaBackend {
4602 fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
4603 macro_rules! materialize_cutensor {
4604 ($variant:ident, $view:expr) => {{
4605 let view = $view;
4606 self.to_contiguous_view_cutensor_or_cubecl(&view, "CudaBackend::to_contiguous_read")
4607 .map(Tensor::$variant)
4608 }};
4609 }
4610 macro_rules! materialize_cubecl {
4611 ($variant:ident, $view:expr) => {{
4612 let view = $view;
4613 self.to_contiguous_view_typed(&view, "CudaBackend::to_contiguous_read")
4614 .map(Tensor::$variant)
4615 }};
4616 }
4617
4618 match input {
4619 TensorRead::Tensor(Tensor::F32(input)) => materialize_cutensor!(F32, input.as_view()),
4620 TensorRead::Tensor(Tensor::F64(input)) => materialize_cutensor!(F64, input.as_view()),
4621 TensorRead::Tensor(Tensor::I32(input)) => materialize_cubecl!(I32, input.as_view()),
4622 TensorRead::Tensor(Tensor::I64(input)) => materialize_cubecl!(I64, input.as_view()),
4623 TensorRead::Tensor(Tensor::Bool(_)) => Err(unsupported_dtype(
4624 "CudaBackend::to_contiguous_read",
4625 crate::DType::Bool,
4626 )),
4627 TensorRead::Tensor(Tensor::C32(input)) => materialize_cutensor!(C32, input.as_view()),
4628 TensorRead::Tensor(Tensor::C64(input)) => materialize_cutensor!(C64, input.as_view()),
4629 TensorRead::View(TensorView::F32(input)) => materialize_cutensor!(F32, input),
4630 TensorRead::View(TensorView::F64(input)) => materialize_cutensor!(F64, input),
4631 TensorRead::View(TensorView::I32(input)) => materialize_cubecl!(I32, input),
4632 TensorRead::View(TensorView::I64(input)) => materialize_cubecl!(I64, input),
4633 TensorRead::View(TensorView::Bool(_)) => Err(unsupported_dtype(
4634 "CudaBackend::to_contiguous_read",
4635 crate::DType::Bool,
4636 )),
4637 TensorRead::View(TensorView::C32(input)) => materialize_cutensor!(C32, input),
4638 TensorRead::View(TensorView::C64(input)) => materialize_cutensor!(C64, input),
4639 }
4640 }
4641
4642 fn copy_read_into(&mut self, src: TensorRead<'_>, dst: TensorWrite<'_>) -> crate::Result<()> {
4643 let src_dtype = src.dtype();
4644 let dst_dtype = dst.dtype();
4645 macro_rules! copy_source_typed {
4646 ($variant:ident, $src:expr) => {{
4647 let src = $src;
4648 match dst {
4649 TensorWrite::Tensor(Tensor::$variant(dst)) => {
4650 let mut dst = dst.as_view_mut();
4651 self.copy_view_to_view_typed(&src, &mut dst, "CudaBackend::copy_read_into")
4652 }
4653 TensorWrite::View(TensorViewMut::$variant(mut dst)) => {
4654 self.copy_view_to_view_typed(&src, &mut dst, "CudaBackend::copy_read_into")
4655 }
4656 _ => Err(crate::Error::dtype_mismatch(
4657 "CudaBackend::copy_read_into",
4658 src_dtype,
4659 dst_dtype,
4660 )),
4661 }
4662 }};
4663 }
4664 macro_rules! copy_source_cutensor {
4665 ($variant:ident, $src:expr) => {{
4666 let src = $src;
4667 match dst {
4668 TensorWrite::Tensor(Tensor::$variant(dst)) => {
4669 let mut dst = dst.as_view_mut();
4670 self.copy_view_to_view_cutensor_or_cubecl(
4671 &src,
4672 &mut dst,
4673 "CudaBackend::copy_read_into",
4674 )
4675 }
4676 TensorWrite::View(TensorViewMut::$variant(mut dst)) => self
4677 .copy_view_to_view_cutensor_or_cubecl(
4678 &src,
4679 &mut dst,
4680 "CudaBackend::copy_read_into",
4681 ),
4682 _ => Err(crate::Error::dtype_mismatch(
4683 "CudaBackend::copy_read_into",
4684 src_dtype,
4685 dst_dtype,
4686 )),
4687 }
4688 }};
4689 }
4690 macro_rules! reject_bool_source {
4691 () => {{
4692 match dst {
4693 TensorWrite::Tensor(Tensor::Bool(_))
4694 | TensorWrite::View(TensorViewMut::Bool(_)) => Err(unsupported_dtype(
4695 "CudaBackend::copy_read_into",
4696 crate::DType::Bool,
4697 )),
4698 _ => Err(crate::Error::dtype_mismatch(
4699 "CudaBackend::copy_read_into",
4700 src_dtype,
4701 dst_dtype,
4702 )),
4703 }
4704 }};
4705 }
4706
4707 match src {
4708 TensorRead::Tensor(Tensor::F32(src)) => copy_source_cutensor!(F32, src.as_view()),
4709 TensorRead::Tensor(Tensor::F64(src)) => copy_source_cutensor!(F64, src.as_view()),
4710 TensorRead::Tensor(Tensor::I32(src)) => copy_source_typed!(I32, src.as_view()),
4711 TensorRead::Tensor(Tensor::I64(src)) => copy_source_typed!(I64, src.as_view()),
4712 TensorRead::Tensor(Tensor::Bool(_)) => reject_bool_source!(),
4713 TensorRead::Tensor(Tensor::C32(src)) => copy_source_cutensor!(C32, src.as_view()),
4714 TensorRead::Tensor(Tensor::C64(src)) => copy_source_cutensor!(C64, src.as_view()),
4715 TensorRead::View(TensorView::F32(src)) => copy_source_cutensor!(F32, src),
4716 TensorRead::View(TensorView::F64(src)) => copy_source_cutensor!(F64, src),
4717 TensorRead::View(TensorView::I32(src)) => copy_source_typed!(I32, src),
4718 TensorRead::View(TensorView::I64(src)) => copy_source_typed!(I64, src),
4719 TensorRead::View(TensorView::Bool(_)) => reject_bool_source!(),
4720 TensorRead::View(TensorView::C32(src)) => copy_source_cutensor!(C32, src),
4721 TensorRead::View(TensorView::C64(src)) => copy_source_cutensor!(C64, src),
4722 }
4723 }
4724
4725 fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor> {
4726 match input {
4727 Tensor::F32(t) => permutation::transpose(self, t, perm).map(Tensor::F32),
4728 Tensor::F64(t) => permutation::transpose(self, t, perm).map(Tensor::F64),
4729 Tensor::I32(t) => self.transpose_typed(t, perm).map(Tensor::I32),
4730 Tensor::I64(t) => self.transpose_typed(t, perm).map(Tensor::I64),
4731 Tensor::Bool(t) => self.transpose_bool(t, perm).map(Tensor::Bool),
4732 Tensor::C32(t) => permutation::transpose(self, t, perm).map(Tensor::C32),
4733 Tensor::C64(t) => permutation::transpose(self, t, perm).map(Tensor::C64),
4734 }
4735 }
4736
4737 fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor> {
4738 let old_n = checked_dim_product("reshape", "input shape", input.shape())?;
4739 let new_n = checked_dim_product("reshape", "output shape", shape)?;
4740 if old_n != new_n {
4741 return Err(crate::Error::validation(
4742 "reshape",
4743 tenferro_tensor::ShapeMismatch::ReshapeElementCount {
4744 from: old_n,
4745 to: new_n,
4746 }
4747 .into(),
4748 ));
4749 }
4750 let contiguous = match input {
4754 Tensor::Bool(tensor) => self.duplicate_bool(tensor, "reshape").map(Tensor::Bool)?,
4755 _ => self.to_contiguous_read(TensorRead::from_tensor(input))?,
4756 };
4757 match contiguous {
4758 Tensor::F32(t) => {
4759 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::F32)
4760 }
4761 Tensor::F64(t) => {
4762 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::F64)
4763 }
4764 Tensor::I32(t) => {
4765 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::I32)
4766 }
4767 Tensor::I64(t) => {
4768 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::I64)
4769 }
4770 Tensor::Bool(t) => {
4771 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::Bool)
4772 }
4773 Tensor::C32(t) => {
4774 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::C32)
4775 }
4776 Tensor::C64(t) => {
4777 cubecl_reshape_metadata(t, shape.to_vec(), "reshape").map(Tensor::C64)
4778 }
4779 }
4780 }
4781
4782 fn broadcast_in_dim(
4783 &mut self,
4784 input: &Tensor,
4785 shape: &[usize],
4786 dims: &[usize],
4787 ) -> crate::Result<Tensor> {
4788 match input {
4789 Tensor::F32(t) => self.broadcast_typed(t, shape, dims).map(Tensor::F32),
4790 Tensor::F64(t) => self.broadcast_typed(t, shape, dims).map(Tensor::F64),
4791 Tensor::I32(t) => self.broadcast_typed(t, shape, dims).map(Tensor::I32),
4792 Tensor::I64(t) => self.broadcast_typed(t, shape, dims).map(Tensor::I64),
4793 Tensor::Bool(t) => self.broadcast_bool(t, shape, dims).map(Tensor::Bool),
4794 Tensor::C32(t) => self.broadcast_typed(t, shape, dims).map(Tensor::C32),
4795 Tensor::C64(t) => self.broadcast_typed(t, shape, dims).map(Tensor::C64),
4796 }
4797 }
4798
4799 fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
4800 match (input, to) {
4801 (Tensor::F32(t), crate::DType::F32) => self.duplicate_typed(t).map(Tensor::F32),
4802 (Tensor::F64(t), crate::DType::F64) => self.duplicate_typed(t).map(Tensor::F64),
4803 (Tensor::I32(t), crate::DType::I32) => self.duplicate_typed(t).map(Tensor::I32),
4804 (Tensor::I64(t), crate::DType::I64) => self.duplicate_typed(t).map(Tensor::I64),
4805 (Tensor::Bool(t), crate::DType::Bool) => {
4806 self.duplicate_bool(t, "cast").map(Tensor::Bool)
4807 }
4808 (Tensor::C32(t), crate::DType::C32) => self.duplicate_typed(t).map(Tensor::C32),
4809 (Tensor::C64(t), crate::DType::C64) => self.duplicate_typed(t).map(Tensor::C64),
4810 (Tensor::F32(t), crate::DType::F64) => {
4811 self.convert_float_to_float::<f32, f64>(t).map(Tensor::F64)
4812 }
4813 (Tensor::F32(t), crate::DType::I32) => {
4814 validate_cuda_real_cast::<f32, f32>(self, t, 1, CastIntegerTarget::I32)?;
4815 self.convert_numeric::<f32, i32>(t).map(Tensor::I32)
4816 }
4817 (Tensor::F32(t), crate::DType::I64) => {
4818 validate_cuda_real_cast::<f32, f32>(self, t, 1, CastIntegerTarget::I64)?;
4819 self.convert_numeric::<f32, i64>(t).map(Tensor::I64)
4820 }
4821 (Tensor::F32(t), crate::DType::Bool) => {
4822 self.convert_numeric_to_bool(t).map(Tensor::Bool)
4823 }
4824 (Tensor::F32(t), crate::DType::C32) => self.convert_f32_to_c32(t).map(Tensor::C32),
4825 (Tensor::F32(t), crate::DType::C64) => self.convert_f32_to_c64(t).map(Tensor::C64),
4826 (Tensor::F64(t), crate::DType::F32) => {
4827 self.convert_float_to_float::<f64, f32>(t).map(Tensor::F32)
4828 }
4829 (Tensor::F64(t), crate::DType::I32) => {
4830 validate_cuda_real_cast::<f64, f64>(self, t, 1, CastIntegerTarget::I32)?;
4831 self.convert_numeric::<f64, i32>(t).map(Tensor::I32)
4832 }
4833 (Tensor::F64(t), crate::DType::I64) => {
4834 validate_cuda_real_cast::<f64, f64>(self, t, 1, CastIntegerTarget::I64)?;
4835 self.convert_numeric::<f64, i64>(t).map(Tensor::I64)
4836 }
4837 (Tensor::F64(t), crate::DType::Bool) => {
4838 self.convert_numeric_to_bool(t).map(Tensor::Bool)
4839 }
4840 (Tensor::F64(t), crate::DType::C32) => self.convert_f64_to_c32(t).map(Tensor::C32),
4841 (Tensor::F64(t), crate::DType::C64) => self.convert_f64_to_c64(t).map(Tensor::C64),
4842 (Tensor::I32(t), crate::DType::F32) => {
4843 self.convert_numeric::<i32, f32>(t).map(Tensor::F32)
4844 }
4845 (Tensor::I32(t), crate::DType::F64) => {
4846 self.convert_numeric::<i32, f64>(t).map(Tensor::F64)
4847 }
4848 (Tensor::I32(t), crate::DType::I64) => {
4849 self.convert_numeric::<i32, i64>(t).map(Tensor::I64)
4850 }
4851 (Tensor::I32(t), crate::DType::Bool) => {
4852 self.convert_numeric_to_bool(t).map(Tensor::Bool)
4853 }
4854 (Tensor::I32(t), crate::DType::C32) => self
4855 .convert_numeric_to_complex::<i32, Complex32, f32>(t)
4856 .map(Tensor::C32),
4857 (Tensor::I32(t), crate::DType::C64) => self
4858 .convert_numeric_to_complex::<i32, Complex64, f64>(t)
4859 .map(Tensor::C64),
4860 (Tensor::I64(t), crate::DType::F32) => {
4861 self.convert_numeric::<i64, f32>(t).map(Tensor::F32)
4862 }
4863 (Tensor::I64(t), crate::DType::F64) => {
4864 self.convert_numeric::<i64, f64>(t).map(Tensor::F64)
4865 }
4866 (Tensor::I64(t), crate::DType::I32) => {
4867 self.convert_numeric::<i64, i32>(t).map(Tensor::I32)
4868 }
4869 (Tensor::I64(t), crate::DType::Bool) => {
4870 self.convert_numeric_to_bool(t).map(Tensor::Bool)
4871 }
4872 (Tensor::I64(t), crate::DType::C32) => self
4873 .convert_numeric_to_complex::<i64, Complex32, f32>(t)
4874 .map(Tensor::C32),
4875 (Tensor::I64(t), crate::DType::C64) => self
4876 .convert_numeric_to_complex::<i64, Complex64, f64>(t)
4877 .map(Tensor::C64),
4878 (Tensor::Bool(t), crate::DType::F32) => {
4879 self.convert_bool_to_numeric::<f32>(t).map(Tensor::F32)
4880 }
4881 (Tensor::Bool(t), crate::DType::F64) => {
4882 self.convert_bool_to_numeric::<f64>(t).map(Tensor::F64)
4883 }
4884 (Tensor::Bool(t), crate::DType::I32) => {
4885 self.convert_bool_to_numeric::<i32>(t).map(Tensor::I32)
4886 }
4887 (Tensor::Bool(t), crate::DType::I64) => {
4888 self.convert_bool_to_numeric::<i64>(t).map(Tensor::I64)
4889 }
4890 (Tensor::Bool(t), crate::DType::C32) => self
4891 .convert_bool_to_complex::<Complex32, f32>(t)
4892 .map(Tensor::C32),
4893 (Tensor::Bool(t), crate::DType::C64) => self
4894 .convert_bool_to_complex::<Complex64, f64>(t)
4895 .map(Tensor::C64),
4896 (Tensor::C32(t), crate::DType::F32) => self.convert_c32_to_f32(t).map(Tensor::F32),
4897 (Tensor::C32(t), crate::DType::F64) => self.convert_c32_to_f64(t).map(Tensor::F64),
4898 (Tensor::C32(t), crate::DType::I32) => {
4899 validate_cuda_real_cast::<Complex32, f32>(self, t, 2, CastIntegerTarget::I32)?;
4900 self.convert_complex_to_numeric::<Complex32, i32>(t)
4901 .map(Tensor::I32)
4902 }
4903 (Tensor::C32(t), crate::DType::I64) => {
4904 validate_cuda_real_cast::<Complex32, f32>(self, t, 2, CastIntegerTarget::I64)?;
4905 self.convert_complex_to_numeric::<Complex32, i64>(t)
4906 .map(Tensor::I64)
4907 }
4908 (Tensor::C32(t), crate::DType::Bool) => self
4909 .convert_complex_to_bool::<Complex32, f32>(t)
4910 .map(Tensor::Bool),
4911 (Tensor::C32(t), crate::DType::C64) => self
4912 .convert_complex_to_complex::<Complex32, Complex64, f32, f64>(t)
4913 .map(Tensor::C64),
4914 (Tensor::C64(t), crate::DType::F32) => self.convert_c64_to_f32(t).map(Tensor::F32),
4915 (Tensor::C64(t), crate::DType::F64) => self.convert_c64_to_f64(t).map(Tensor::F64),
4916 (Tensor::C64(t), crate::DType::I32) => {
4917 validate_cuda_real_cast::<Complex64, f64>(self, t, 2, CastIntegerTarget::I32)?;
4918 self.convert_complex_to_numeric::<Complex64, i32>(t)
4919 .map(Tensor::I32)
4920 }
4921 (Tensor::C64(t), crate::DType::I64) => {
4922 validate_cuda_real_cast::<Complex64, f64>(self, t, 2, CastIntegerTarget::I64)?;
4923 self.convert_complex_to_numeric::<Complex64, i64>(t)
4924 .map(Tensor::I64)
4925 }
4926 (Tensor::C64(t), crate::DType::Bool) => self
4927 .convert_complex_to_bool::<Complex64, f64>(t)
4928 .map(Tensor::Bool),
4929 (Tensor::C64(t), crate::DType::C32) => self
4930 .convert_complex_to_complex::<Complex64, Complex32, f64, f32>(t)
4931 .map(Tensor::C32),
4932 }
4933 }
4934
4935 fn extract_diagonal(
4936 &mut self,
4937 input: &Tensor,
4938 axis_a: usize,
4939 axis_b: usize,
4940 ) -> crate::Result<Tensor> {
4941 match input {
4942 Tensor::F32(t) => self
4943 .extract_diagonal_typed(t, axis_a, axis_b)
4944 .map(Tensor::F32),
4945 Tensor::F64(t) => self
4946 .extract_diagonal_typed(t, axis_a, axis_b)
4947 .map(Tensor::F64),
4948 Tensor::I32(t) => self
4949 .extract_diagonal_typed(t, axis_a, axis_b)
4950 .map(Tensor::I32),
4951 Tensor::I64(t) => self
4952 .extract_diagonal_typed(t, axis_a, axis_b)
4953 .map(Tensor::I64),
4954 Tensor::Bool(t) => self
4955 .extract_diagonal_bool(t, axis_a, axis_b)
4956 .map(Tensor::Bool),
4957 Tensor::C32(t) => self
4958 .extract_diagonal_typed(t, axis_a, axis_b)
4959 .map(Tensor::C32),
4960 Tensor::C64(t) => self
4961 .extract_diagonal_typed(t, axis_a, axis_b)
4962 .map(Tensor::C64),
4963 }
4964 }
4965
4966 fn embed_diagonal(
4967 &mut self,
4968 input: &Tensor,
4969 axis_a: usize,
4970 axis_b: usize,
4971 ) -> crate::Result<Tensor> {
4972 match input {
4973 Tensor::F32(t) => self
4974 .embed_diagonal_typed(t, axis_a, axis_b)
4975 .map(Tensor::F32),
4976 Tensor::F64(t) => self
4977 .embed_diagonal_typed(t, axis_a, axis_b)
4978 .map(Tensor::F64),
4979 Tensor::I32(t) => self
4980 .embed_diagonal_typed(t, axis_a, axis_b)
4981 .map(Tensor::I32),
4982 Tensor::I64(t) => self
4983 .embed_diagonal_typed(t, axis_a, axis_b)
4984 .map(Tensor::I64),
4985 Tensor::Bool(t) => self
4986 .embed_diagonal_bool(t, axis_a, axis_b)
4987 .map(Tensor::Bool),
4988 Tensor::C32(t) => self
4989 .embed_diagonal_typed(t, axis_a, axis_b)
4990 .map(Tensor::C32),
4991 Tensor::C64(t) => self
4992 .embed_diagonal_typed(t, axis_a, axis_b)
4993 .map(Tensor::C64),
4994 }
4995 }
4996
4997 fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor> {
4998 match input {
4999 Tensor::F32(t) => self.tril_typed(t, k).map(Tensor::F32),
5000 Tensor::F64(t) => self.tril_typed(t, k).map(Tensor::F64),
5001 Tensor::I32(t) => self.tril_typed(t, k).map(Tensor::I32),
5002 Tensor::I64(t) => self.tril_typed(t, k).map(Tensor::I64),
5003 Tensor::Bool(t) => self.tril_bool(t, k).map(Tensor::Bool),
5004 Tensor::C32(t) => self.tril_typed(t, k).map(Tensor::C32),
5005 Tensor::C64(t) => self.tril_typed(t, k).map(Tensor::C64),
5006 }
5007 }
5008
5009 fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor> {
5010 match input {
5011 Tensor::F32(t) => self.triu_typed(t, k).map(Tensor::F32),
5012 Tensor::F64(t) => self.triu_typed(t, k).map(Tensor::F64),
5013 Tensor::I32(t) => self.triu_typed(t, k).map(Tensor::I32),
5014 Tensor::I64(t) => self.triu_typed(t, k).map(Tensor::I64),
5015 Tensor::Bool(t) => self.triu_bool(t, k).map(Tensor::Bool),
5016 Tensor::C32(t) => self.triu_typed(t, k).map(Tensor::C32),
5017 Tensor::C64(t) => self.triu_typed(t, k).map(Tensor::C64),
5018 }
5019 }
5020}
5021
5022impl TensorReduction for CudaBackend {
5023 fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
5024 let op = op_name(
5025 PrimitiveOpKind::ReduceSum,
5026 op_descriptor::GpuLaunchKind::Reduction,
5027 )?;
5028 match input {
5029 Tensor::F32(t) => self.reduce_sum_float_typed(t, axes).map(Tensor::F32),
5030 Tensor::F64(t) => self.reduce_sum_float_typed(t, axes).map(Tensor::F64),
5031 Tensor::I32(t) => self.reduce_sum_int_typed(t, axes).map(Tensor::I32),
5032 Tensor::I64(t) => self.reduce_sum_int_typed(t, axes).map(Tensor::I64),
5033 Tensor::Bool(_) => Err(unsupported_dtype(op, input.dtype())),
5034 Tensor::C32(t) => self.reduce_sum_complex_typed(t, axes).map(Tensor::C32),
5035 Tensor::C64(t) => self.reduce_sum_complex_typed(t, axes).map(Tensor::C64),
5036 }
5037 }
5038
5039 fn reduce_sum_squares_read(
5040 &mut self,
5041 input: TensorRead<'_>,
5042 axes: &[usize],
5043 ) -> crate::Result<Tensor> {
5044 let op = op_name(
5045 PrimitiveOpKind::ReduceSumSquares,
5046 op_descriptor::GpuLaunchKind::Reduction,
5047 )?;
5048 let Some(input) = input.as_tensor() else {
5049 return Err(crate::Error::unsupported(
5050 op,
5051 "CUDA sum-of-squares requires a resident tensor",
5052 ));
5053 };
5054 if axes.is_empty() {
5055 return match input {
5056 Tensor::F32(_) | Tensor::F64(_) => self.mul(input, input),
5057 _ => Err(unsupported_dtype(op, input.dtype())),
5058 };
5059 }
5060 match input {
5061 Tensor::F32(t) => self
5062 .reduce_sum_squares_float_typed(t, axes)
5063 .map(Tensor::F32),
5064 Tensor::F64(t) => self
5065 .reduce_sum_squares_float_typed(t, axes)
5066 .map(Tensor::F64),
5067 _ => Err(unsupported_dtype(op, input.dtype())),
5068 }
5069 }
5070
5071 fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
5072 let op = op_name(
5073 PrimitiveOpKind::ReduceProd,
5074 op_descriptor::GpuLaunchKind::Reduction,
5075 )?;
5076 match input {
5077 Tensor::F32(t) => self.reduce_prod_float_typed(t, axes).map(Tensor::F32),
5078 Tensor::F64(t) => self.reduce_prod_float_typed(t, axes).map(Tensor::F64),
5079 Tensor::I32(t) => self.reduce_prod_int_typed(t, axes).map(Tensor::I32),
5080 Tensor::I64(t) => self.reduce_prod_int_typed(t, axes).map(Tensor::I64),
5081 Tensor::Bool(_) => Err(unsupported_dtype(op, input.dtype())),
5082 Tensor::C32(t) => self.reduce_prod_complex_typed(t, axes).map(Tensor::C32),
5083 Tensor::C64(t) => self.reduce_prod_complex_typed(t, axes).map(Tensor::C64),
5084 }
5085 }
5086
5087 fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
5088 let op = op_name(
5089 PrimitiveOpKind::ReduceMax,
5090 op_descriptor::GpuLaunchKind::Reduction,
5091 )?;
5092 match input {
5093 Tensor::F32(t) => self.reduce_max_float_typed(t, axes).map(Tensor::F32),
5094 Tensor::F64(t) => self.reduce_max_float_typed(t, axes).map(Tensor::F64),
5095 Tensor::I32(t) => self.reduce_max_int_typed(t, axes).map(Tensor::I32),
5096 Tensor::I64(t) => self.reduce_max_int_typed(t, axes).map(Tensor::I64),
5097 Tensor::Bool(_) | Tensor::C32(_) | Tensor::C64(_) => {
5098 Err(unsupported_dtype(op, input.dtype()))
5099 }
5100 }
5101 }
5102
5103 fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
5104 let op = op_name(
5105 PrimitiveOpKind::ReduceMin,
5106 op_descriptor::GpuLaunchKind::Reduction,
5107 )?;
5108 match input {
5109 Tensor::F32(t) => self.reduce_min_float_typed(t, axes).map(Tensor::F32),
5110 Tensor::F64(t) => self.reduce_min_float_typed(t, axes).map(Tensor::F64),
5111 Tensor::I32(t) => self.reduce_min_int_typed(t, axes).map(Tensor::I32),
5112 Tensor::I64(t) => self.reduce_min_int_typed(t, axes).map(Tensor::I64),
5113 Tensor::Bool(_) | Tensor::C32(_) | Tensor::C64(_) => {
5114 Err(unsupported_dtype(op, input.dtype()))
5115 }
5116 }
5117 }
5118}
5119
5120impl TensorDot for CudaBackend {
5121 fn dot_general(
5122 &mut self,
5123 lhs: &Tensor,
5124 rhs: &Tensor,
5125 config: &DotGeneralConfig,
5126 ) -> crate::Result<Tensor> {
5127 gemm::dot_general(self, lhs, rhs, config)
5128 }
5129
5130 fn dot_general_with_conj(
5131 &mut self,
5132 lhs: &Tensor,
5133 rhs: &Tensor,
5134 config: &DotGeneralConfig,
5135 lhs_conj: bool,
5136 rhs_conj: bool,
5137 ) -> crate::Result<Tensor> {
5138 gemm::dot_general_with_conj(self, lhs, rhs, config, lhs_conj, rhs_conj)
5139 }
5140
5141 fn dot_general_read_into_accum(
5147 &mut self,
5148 lhs: TensorRead<'_>,
5149 rhs: TensorRead<'_>,
5150 config: &DotGeneralConfig,
5151 accumulation: DotGeneralAccumulation,
5152 mut out: TensorWrite<'_>,
5153 ) -> crate::Result<()> {
5154 tenferro_tensor::backend::validate_dot_general_accumulation(
5155 &lhs,
5156 &rhs,
5157 config,
5158 accumulation,
5159 &out,
5160 "dot_general",
5161 )?;
5162 gemm::dot_general_read_into_accum(self, &lhs, &rhs, config, accumulation, &mut out)
5163 }
5164}
5165
5166impl TensorIndexing for CudaBackend {
5167 fn gather(
5168 &mut self,
5169 operand: &Tensor,
5170 start_indices: &Tensor,
5171 config: &GatherConfig,
5172 ) -> crate::Result<Tensor> {
5173 match (operand, start_indices) {
5174 (Tensor::F32(operand), Tensor::F32(indices)) => {
5175 self.gather_typed(operand, indices, config).map(Tensor::F32)
5176 }
5177 (Tensor::F64(operand), Tensor::F32(indices)) => {
5178 self.gather_typed(operand, indices, config).map(Tensor::F64)
5179 }
5180 (Tensor::C32(operand), Tensor::F32(indices)) => {
5181 self.gather_typed(operand, indices, config).map(Tensor::C32)
5182 }
5183 (Tensor::C64(operand), Tensor::F32(indices)) => {
5184 self.gather_typed(operand, indices, config).map(Tensor::C64)
5185 }
5186 (Tensor::I32(operand), Tensor::F32(indices)) => {
5187 self.gather_typed(operand, indices, config).map(Tensor::I32)
5188 }
5189 (Tensor::F32(operand), Tensor::F64(indices)) => {
5190 self.gather_typed(operand, indices, config).map(Tensor::F32)
5191 }
5192 (Tensor::F64(operand), Tensor::F64(indices)) => {
5193 self.gather_typed(operand, indices, config).map(Tensor::F64)
5194 }
5195 (Tensor::C32(operand), Tensor::F64(indices)) => {
5196 self.gather_typed(operand, indices, config).map(Tensor::C32)
5197 }
5198 (Tensor::C64(operand), Tensor::F64(indices)) => {
5199 self.gather_typed(operand, indices, config).map(Tensor::C64)
5200 }
5201 (Tensor::I32(operand), Tensor::F64(indices)) => {
5202 self.gather_typed(operand, indices, config).map(Tensor::I32)
5203 }
5204 (Tensor::F32(operand), Tensor::I32(indices)) => {
5205 self.gather_typed(operand, indices, config).map(Tensor::F32)
5206 }
5207 (Tensor::F64(operand), Tensor::I32(indices)) => {
5208 self.gather_typed(operand, indices, config).map(Tensor::F64)
5209 }
5210 (Tensor::C32(operand), Tensor::I32(indices)) => {
5211 self.gather_typed(operand, indices, config).map(Tensor::C32)
5212 }
5213 (Tensor::C64(operand), Tensor::I32(indices)) => {
5214 self.gather_typed(operand, indices, config).map(Tensor::C64)
5215 }
5216 (Tensor::I32(operand), Tensor::I32(indices)) => {
5217 self.gather_typed(operand, indices, config).map(Tensor::I32)
5218 }
5219 (Tensor::F32(operand), Tensor::I64(indices)) => {
5220 self.gather_typed(operand, indices, config).map(Tensor::F32)
5221 }
5222 (Tensor::F64(operand), Tensor::I64(indices)) => {
5223 self.gather_typed(operand, indices, config).map(Tensor::F64)
5224 }
5225 (Tensor::C32(operand), Tensor::I64(indices)) => {
5226 self.gather_typed(operand, indices, config).map(Tensor::C32)
5227 }
5228 (Tensor::C64(operand), Tensor::I64(indices)) => {
5229 self.gather_typed(operand, indices, config).map(Tensor::C64)
5230 }
5231 (Tensor::I32(operand), Tensor::I64(indices)) => {
5232 self.gather_typed(operand, indices, config).map(Tensor::I32)
5233 }
5234 (Tensor::Bool(operand), Tensor::F32(indices)) => {
5235 self.gather_bool(operand, indices, config).map(Tensor::Bool)
5236 }
5237 (Tensor::Bool(operand), Tensor::F64(indices)) => {
5238 self.gather_bool(operand, indices, config).map(Tensor::Bool)
5239 }
5240 (Tensor::Bool(operand), Tensor::I32(indices)) => {
5241 self.gather_bool(operand, indices, config).map(Tensor::Bool)
5242 }
5243 (Tensor::Bool(operand), Tensor::I64(indices)) => {
5244 self.gather_bool(operand, indices, config).map(Tensor::Bool)
5245 }
5246 (_, Tensor::Bool(_)) => Err(unsupported_dtype("gather", start_indices.dtype())),
5247 (_, Tensor::C32(_) | Tensor::C64(_)) => {
5248 Err(unsupported_dtype("gather", start_indices.dtype()))
5249 }
5250 (Tensor::I64(_), _) => Err(unsupported_dtype("gather", operand.dtype())),
5251 }
5252 }
5253
5254 fn scatter(
5255 &mut self,
5256 operand: &Tensor,
5257 scatter_indices: &Tensor,
5258 updates: &Tensor,
5259 config: &ScatterConfig,
5260 ) -> crate::Result<Tensor> {
5261 match (operand, scatter_indices, updates) {
5262 (Tensor::F32(operand), Tensor::F32(indices), Tensor::F32(updates)) => self
5263 .scatter_float_typed(operand, indices, updates, config)
5264 .map(Tensor::F32),
5265 (Tensor::F64(operand), Tensor::F32(indices), Tensor::F64(updates)) => self
5266 .scatter_float_typed(operand, indices, updates, config)
5267 .map(Tensor::F64),
5268 (Tensor::C32(operand), Tensor::F32(indices), Tensor::C32(updates)) => self
5269 .scatter_complex_typed::<_, f32, _>(operand, indices, updates, config)
5270 .map(Tensor::C32),
5271 (Tensor::C64(operand), Tensor::F32(indices), Tensor::C64(updates)) => self
5272 .scatter_complex_typed::<_, f64, _>(operand, indices, updates, config)
5273 .map(Tensor::C64),
5274 (Tensor::F32(operand), Tensor::F64(indices), Tensor::F32(updates)) => self
5275 .scatter_float_typed(operand, indices, updates, config)
5276 .map(Tensor::F32),
5277 (Tensor::F64(operand), Tensor::F64(indices), Tensor::F64(updates)) => self
5278 .scatter_float_typed(operand, indices, updates, config)
5279 .map(Tensor::F64),
5280 (Tensor::C32(operand), Tensor::F64(indices), Tensor::C32(updates)) => self
5281 .scatter_complex_typed::<_, f32, _>(operand, indices, updates, config)
5282 .map(Tensor::C32),
5283 (Tensor::C64(operand), Tensor::F64(indices), Tensor::C64(updates)) => self
5284 .scatter_complex_typed::<_, f64, _>(operand, indices, updates, config)
5285 .map(Tensor::C64),
5286 (Tensor::F32(operand), Tensor::I32(indices), Tensor::F32(updates)) => self
5287 .scatter_float_typed(operand, indices, updates, config)
5288 .map(Tensor::F32),
5289 (Tensor::F64(operand), Tensor::I32(indices), Tensor::F64(updates)) => self
5290 .scatter_float_typed(operand, indices, updates, config)
5291 .map(Tensor::F64),
5292 (Tensor::C32(operand), Tensor::I32(indices), Tensor::C32(updates)) => self
5293 .scatter_complex_typed::<_, f32, _>(operand, indices, updates, config)
5294 .map(Tensor::C32),
5295 (Tensor::C64(operand), Tensor::I32(indices), Tensor::C64(updates)) => self
5296 .scatter_complex_typed::<_, f64, _>(operand, indices, updates, config)
5297 .map(Tensor::C64),
5298 (Tensor::F32(operand), Tensor::I64(indices), Tensor::F32(updates)) => self
5299 .scatter_float_typed(operand, indices, updates, config)
5300 .map(Tensor::F32),
5301 (Tensor::F64(operand), Tensor::I64(indices), Tensor::F64(updates)) => self
5302 .scatter_float_typed(operand, indices, updates, config)
5303 .map(Tensor::F64),
5304 (Tensor::C32(operand), Tensor::I64(indices), Tensor::C32(updates)) => self
5305 .scatter_complex_typed::<_, f32, _>(operand, indices, updates, config)
5306 .map(Tensor::C32),
5307 (Tensor::C64(operand), Tensor::I64(indices), Tensor::C64(updates)) => self
5308 .scatter_complex_typed::<_, f64, _>(operand, indices, updates, config)
5309 .map(Tensor::C64),
5310 (_, Tensor::Bool(_), _) => Err(unsupported_dtype("scatter", scatter_indices.dtype())),
5311 (_, Tensor::C32(_) | Tensor::C64(_), _) => {
5312 Err(unsupported_dtype("scatter", scatter_indices.dtype()))
5313 }
5314 (Tensor::Bool(_), _, _) => Err(unsupported_operation(
5315 "scatter",
5316 "Bool data tensors are not supported by additive scatter",
5317 )),
5318 (Tensor::I32(_), _, _) | (Tensor::I64(_), _, _) => {
5319 Err(unsupported_dtype("scatter", operand.dtype()))
5320 }
5321 (_, _, _) => Err(ternary_dtype_mismatch(
5322 "scatter",
5323 operand,
5324 scatter_indices,
5325 updates,
5326 )),
5327 }
5328 }
5329
5330 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor> {
5331 match input {
5332 Tensor::F32(t) => self.slice_typed(t, config).map(Tensor::F32),
5333 Tensor::F64(t) => self.slice_typed(t, config).map(Tensor::F64),
5334 Tensor::I32(t) => self.slice_typed(t, config).map(Tensor::I32),
5335 Tensor::I64(t) => self.slice_typed(t, config).map(Tensor::I64),
5336 Tensor::Bool(t) => self.slice_bool(t, config).map(Tensor::Bool),
5337 Tensor::C32(t) => self.slice_typed(t, config).map(Tensor::C32),
5338 Tensor::C64(t) => self.slice_typed(t, config).map(Tensor::C64),
5339 }
5340 }
5341
5342 fn dynamic_slice(
5343 &mut self,
5344 input: &Tensor,
5345 starts: &Tensor,
5346 slice_sizes: &[usize],
5347 ) -> crate::Result<Tensor> {
5348 match (input, starts) {
5349 (Tensor::F32(input), Tensor::F32(starts)) => self
5350 .dynamic_slice_typed(input, starts, slice_sizes)
5351 .map(Tensor::F32),
5352 (Tensor::F64(input), Tensor::F32(starts)) => self
5353 .dynamic_slice_typed(input, starts, slice_sizes)
5354 .map(Tensor::F64),
5355 (Tensor::C32(input), Tensor::F32(starts)) => self
5356 .dynamic_slice_typed(input, starts, slice_sizes)
5357 .map(Tensor::C32),
5358 (Tensor::C64(input), Tensor::F32(starts)) => self
5359 .dynamic_slice_typed(input, starts, slice_sizes)
5360 .map(Tensor::C64),
5361 (Tensor::I32(input), Tensor::F32(starts)) => self
5362 .dynamic_slice_typed(input, starts, slice_sizes)
5363 .map(Tensor::I32),
5364 (Tensor::F32(input), Tensor::F64(starts)) => self
5365 .dynamic_slice_typed(input, starts, slice_sizes)
5366 .map(Tensor::F32),
5367 (Tensor::F64(input), Tensor::F64(starts)) => self
5368 .dynamic_slice_typed(input, starts, slice_sizes)
5369 .map(Tensor::F64),
5370 (Tensor::C32(input), Tensor::F64(starts)) => self
5371 .dynamic_slice_typed(input, starts, slice_sizes)
5372 .map(Tensor::C32),
5373 (Tensor::C64(input), Tensor::F64(starts)) => self
5374 .dynamic_slice_typed(input, starts, slice_sizes)
5375 .map(Tensor::C64),
5376 (Tensor::I32(input), Tensor::F64(starts)) => self
5377 .dynamic_slice_typed(input, starts, slice_sizes)
5378 .map(Tensor::I32),
5379 (Tensor::F32(input), Tensor::I32(starts)) => self
5380 .dynamic_slice_typed(input, starts, slice_sizes)
5381 .map(Tensor::F32),
5382 (Tensor::F64(input), Tensor::I32(starts)) => self
5383 .dynamic_slice_typed(input, starts, slice_sizes)
5384 .map(Tensor::F64),
5385 (Tensor::C32(input), Tensor::I32(starts)) => self
5386 .dynamic_slice_typed(input, starts, slice_sizes)
5387 .map(Tensor::C32),
5388 (Tensor::C64(input), Tensor::I32(starts)) => self
5389 .dynamic_slice_typed(input, starts, slice_sizes)
5390 .map(Tensor::C64),
5391 (Tensor::I32(input), Tensor::I32(starts)) => self
5392 .dynamic_slice_typed(input, starts, slice_sizes)
5393 .map(Tensor::I32),
5394 (Tensor::F32(input), Tensor::I64(starts)) => self
5395 .dynamic_slice_typed(input, starts, slice_sizes)
5396 .map(Tensor::F32),
5397 (Tensor::F64(input), Tensor::I64(starts)) => self
5398 .dynamic_slice_typed(input, starts, slice_sizes)
5399 .map(Tensor::F64),
5400 (Tensor::C32(input), Tensor::I64(starts)) => self
5401 .dynamic_slice_typed(input, starts, slice_sizes)
5402 .map(Tensor::C32),
5403 (Tensor::C64(input), Tensor::I64(starts)) => self
5404 .dynamic_slice_typed(input, starts, slice_sizes)
5405 .map(Tensor::C64),
5406 (Tensor::I32(input), Tensor::I64(starts)) => self
5407 .dynamic_slice_typed(input, starts, slice_sizes)
5408 .map(Tensor::I32),
5409 (Tensor::Bool(input), Tensor::I32(starts)) => self
5410 .dynamic_slice_bool(input, starts, slice_sizes)
5411 .map(Tensor::Bool),
5412 (Tensor::Bool(input), Tensor::I64(starts)) => self
5413 .dynamic_slice_bool(input, starts, slice_sizes)
5414 .map(Tensor::Bool),
5415 (Tensor::Bool(input), Tensor::F32(starts)) => self
5416 .dynamic_slice_bool(input, starts, slice_sizes)
5417 .map(Tensor::Bool),
5418 (Tensor::Bool(input), Tensor::F64(starts)) => self
5419 .dynamic_slice_bool(input, starts, slice_sizes)
5420 .map(Tensor::Bool),
5421 (_, Tensor::Bool(_)) => Err(unsupported_dtype("dynamic_slice", starts.dtype())),
5422 (_, Tensor::C32(_) | Tensor::C64(_)) => {
5423 Err(unsupported_dtype("dynamic_slice", starts.dtype()))
5424 }
5425 (Tensor::I64(_), _) => Err(unsupported_dtype("dynamic_slice", input.dtype())),
5426 }
5427 }
5428
5429 fn dynamic_update_slice(
5430 &mut self,
5431 _operand: &Tensor,
5432 _update: &Tensor,
5433 _starts: &Tensor,
5434 ) -> crate::Result<Tensor> {
5435 Err(unsupported_operation(
5436 "dynamic_update_slice",
5437 "not implemented for the CubeCL backend",
5438 ))
5439 }
5440
5441 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor> {
5442 match input {
5443 Tensor::F32(t) => self.pad_typed(t, config).map(Tensor::F32),
5444 Tensor::F64(t) => self.pad_typed(t, config).map(Tensor::F64),
5445 Tensor::I32(t) => self.pad_typed(t, config).map(Tensor::I32),
5446 Tensor::I64(t) => self.pad_typed(t, config).map(Tensor::I64),
5447 Tensor::Bool(t) => self.pad_bool(t, config).map(Tensor::Bool),
5448 Tensor::C32(t) => self.pad_typed(t, config).map(Tensor::C32),
5449 Tensor::C64(t) => self.pad_typed(t, config).map(Tensor::C64),
5450 }
5451 }
5452
5453 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor> {
5454 let first = inputs.first().copied().ok_or_else(|| {
5455 crate::Error::invalid_argument(
5456 "concatenate",
5457 "inputs",
5458 "concatenate requires at least one input",
5459 )
5460 })?;
5461 match first {
5462 Tensor::F32(_) => {
5463 let typed: crate::Result<Vec<&TypedTensor<f32>>> = inputs
5464 .iter()
5465 .map(|tensor| match tensor {
5466 Tensor::F32(t) => Ok(t),
5467 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5468 })
5469 .collect();
5470 self.concatenate_typed(&typed?, axis).map(Tensor::F32)
5471 }
5472 Tensor::F64(_) => {
5473 let typed: crate::Result<Vec<&TypedTensor<f64>>> = inputs
5474 .iter()
5475 .map(|tensor| match tensor {
5476 Tensor::F64(t) => Ok(t),
5477 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5478 })
5479 .collect();
5480 self.concatenate_typed(&typed?, axis).map(Tensor::F64)
5481 }
5482 Tensor::I32(_) => {
5483 let typed: crate::Result<Vec<&TypedTensor<i32>>> = inputs
5484 .iter()
5485 .map(|tensor| match tensor {
5486 Tensor::I32(t) => Ok(t),
5487 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5488 })
5489 .collect();
5490 self.concatenate_typed(&typed?, axis).map(Tensor::I32)
5491 }
5492 Tensor::I64(_) => {
5493 let typed: crate::Result<Vec<&TypedTensor<i64>>> = inputs
5494 .iter()
5495 .map(|tensor| match tensor {
5496 Tensor::I64(t) => Ok(t),
5497 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5498 })
5499 .collect();
5500 self.concatenate_typed(&typed?, axis).map(Tensor::I64)
5501 }
5502 Tensor::Bool(_) => {
5503 let typed: crate::Result<Vec<&TypedTensor<bool>>> = inputs
5504 .iter()
5505 .map(|tensor| match tensor {
5506 Tensor::Bool(t) => Ok(t),
5507 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5508 })
5509 .collect();
5510 self.concatenate_bool(&typed?, axis).map(Tensor::Bool)
5511 }
5512 Tensor::C32(_) => {
5513 let typed: crate::Result<Vec<&TypedTensor<Complex32>>> = inputs
5514 .iter()
5515 .map(|tensor| match tensor {
5516 Tensor::C32(t) => Ok(t),
5517 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5518 })
5519 .collect();
5520 self.concatenate_typed(&typed?, axis).map(Tensor::C32)
5521 }
5522 Tensor::C64(_) => {
5523 let typed: crate::Result<Vec<&TypedTensor<Complex64>>> = inputs
5524 .iter()
5525 .map(|tensor| match tensor {
5526 Tensor::C64(t) => Ok(t),
5527 _ => Err(dtype_mismatch("concatenate", first, tensor)),
5528 })
5529 .collect();
5530 self.concatenate_typed(&typed?, axis).map(Tensor::C64)
5531 }
5532 }
5533 }
5534
5535 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor> {
5536 match input {
5537 Tensor::F32(t) => self.reverse_typed(t, axes).map(Tensor::F32),
5538 Tensor::F64(t) => self.reverse_typed(t, axes).map(Tensor::F64),
5539 Tensor::I32(t) => self.reverse_typed(t, axes).map(Tensor::I32),
5540 Tensor::I64(t) => self.reverse_typed(t, axes).map(Tensor::I64),
5541 Tensor::Bool(t) => self.reverse_bool(t, axes).map(Tensor::Bool),
5542 Tensor::C32(t) => self.reverse_typed(t, axes).map(Tensor::C32),
5543 Tensor::C64(t) => self.reverse_typed(t, axes).map(Tensor::C64),
5544 }
5545 }
5546}
5547
5548impl TensorDeviceTransfer for CudaBackend {
5549 fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor> {
5550 let tensor = tensor.as_tensor().ok_or_else(|| {
5551 crate::Error::unsupported(
5552 "CudaBackend::download_to_host",
5553 "CUDA transfer currently requires an owned tensor; materialize a view explicitly first",
5554 )
5555 })?;
5556 download_tensor(self.runtime(), tensor)
5557 }
5558
5559 fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor> {
5560 let tensor = tensor.as_tensor().ok_or_else(|| {
5561 crate::Error::unsupported(
5562 "CudaBackend::upload_host_tensor",
5563 "CUDA transfer currently requires an owned tensor; materialize a view explicitly first",
5564 )
5565 })?;
5566 upload_tensor(self.runtime(), tensor)
5567 }
5568}
5569
5570macro_rules! impl_cubecl_view_canonicalization {
5571 ($($ty:ty),* $(,)?) => {
5572 $(
5573 impl<R> TensorViewCanonicalization<$ty, R> for CudaBackend
5574 where
5575 R: TensorRank,
5576 {
5577 fn to_contiguous(
5578 &mut self,
5579 view: &TypedTensorView<'_, $ty, R>,
5580 ) -> crate::Result<TypedTensor<$ty, R>> {
5581 self.to_contiguous_view_typed(view, "CudaBackend::to_contiguous")
5582 }
5583
5584 fn copy_into(
5585 &mut self,
5586 src: &TypedTensorView<'_, $ty, R>,
5587 dst: &mut TypedTensorViewMut<'_, $ty, R>,
5588 ) -> crate::Result<()> {
5589 self.copy_view_to_view_typed(src, dst, "CudaBackend::copy_into")
5590 }
5591 }
5592 )*
5593 };
5594}
5595
5596macro_rules! impl_cutensor_view_canonicalization {
5597 ($($ty:ty),* $(,)?) => {
5598 $(
5599 impl<R> TensorViewCanonicalization<$ty, R> for CudaBackend
5600 where
5601 R: TensorRank,
5602 {
5603 fn to_contiguous(
5604 &mut self,
5605 view: &TypedTensorView<'_, $ty, R>,
5606 ) -> crate::Result<TypedTensor<$ty, R>> {
5607 self.to_contiguous_view_cutensor_or_cubecl(view, "CudaBackend::to_contiguous")
5608 }
5609
5610 fn copy_into(
5611 &mut self,
5612 src: &TypedTensorView<'_, $ty, R>,
5613 dst: &mut TypedTensorViewMut<'_, $ty, R>,
5614 ) -> crate::Result<()> {
5615 self.copy_view_to_view_typed(src, dst, "CudaBackend::copy_into")
5616 }
5617 }
5618 )*
5619 };
5620}
5621
5622impl_cutensor_view_canonicalization!(f32, f64, Complex32, Complex64);
5623impl_cubecl_view_canonicalization!(i32, i64);
5624
5625impl<R> TensorViewCanonicalization<bool, R> for CudaBackend
5626where
5627 R: TensorRank,
5628{
5629 fn to_contiguous(
5630 &mut self,
5631 _view: &TypedTensorView<'_, bool, R>,
5632 ) -> crate::Result<TypedTensor<bool, R>> {
5633 Err(unsupported_dtype(
5634 "CudaBackend::to_contiguous",
5635 crate::DType::Bool,
5636 ))
5637 }
5638
5639 fn copy_into(
5640 &mut self,
5641 _src: &TypedTensorView<'_, bool, R>,
5642 _dst: &mut TypedTensorViewMut<'_, bool, R>,
5643 ) -> crate::Result<()> {
5644 Err(unsupported_dtype(
5645 "CudaBackend::copy_into",
5646 crate::DType::Bool,
5647 ))
5648 }
5649}
5650
5651impl TensorFusion for CudaBackend {
5652 fn execute_elementwise_fusion(
5653 &mut self,
5654 inputs: &[&Tensor],
5655 plan: &crate::backend::ElementwiseFusionPlan,
5656 ) -> crate::Result<Option<Vec<Tensor>>> {
5657 fusion::execute_elementwise_fusion(self, inputs, plan)
5658 }
5659
5660 fn execute_broadcast_multiply(
5661 &mut self,
5662 lhs: TensorRead<'_>,
5663 lhs_shape: &[usize],
5664 lhs_dims: &[usize],
5665 rhs: TensorRead<'_>,
5666 rhs_shape: &[usize],
5667 rhs_dims: &[usize],
5668 ) -> crate::Result<Option<Tensor>> {
5669 let (TensorRead::Tensor(lhs), TensorRead::Tensor(rhs)) = (lhs, rhs) else {
5670 return Ok(None);
5671 };
5672 match (lhs, rhs) {
5673 (Tensor::F32(lhs), Tensor::F32(rhs)) => launch_broadcast_multiply_typed(
5674 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5675 )
5676 .map(Tensor::F32)
5677 .map(Some),
5678 (Tensor::F64(lhs), Tensor::F64(rhs)) => launch_broadcast_multiply_typed(
5679 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5680 )
5681 .map(Tensor::F64)
5682 .map(Some),
5683 (Tensor::I32(lhs), Tensor::I32(rhs)) => launch_broadcast_multiply_int_typed(
5684 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5685 )
5686 .map(Tensor::I32)
5687 .map(Some),
5688 (Tensor::I64(lhs), Tensor::I64(rhs)) => launch_broadcast_multiply_int_typed(
5689 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5690 )
5691 .map(Tensor::I64)
5692 .map(Some),
5693 (Tensor::C32(lhs), Tensor::C32(rhs)) => launch_broadcast_multiply_complex_typed(
5694 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5695 )
5696 .map(Tensor::C32)
5697 .map(Some),
5698 (Tensor::C64(lhs), Tensor::C64(rhs)) => launch_broadcast_multiply_complex_typed(
5699 self, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims,
5700 )
5701 .map(Tensor::C64)
5702 .map(Some),
5703 (Tensor::Bool(_), Tensor::Bool(_)) => Ok(None),
5704 _ => Err(dtype_mismatch("broadcast_multiply", lhs, rhs)),
5705 }
5706 }
5707}
5708
5709impl BackendSession for CudaBackend {
5710 fn session_type_id(&self) -> TypeId {
5711 TypeId::of::<CudaBackendSessionMarker>()
5712 }
5713
5714 unsafe fn session_data_mut(&mut self) -> *mut () {
5715 self as *mut Self as *mut ()
5716 }
5717}
5718
5719impl BackendCachedDot for CudaBackend {}
5720
5721impl TensorBuffer for CudaBackend {}
5722
5723impl TensorBackend for CudaBackend {}
5724
5725fn validate_permutation(op: &'static str, perm: &[usize], rank: usize) -> crate::Result<()> {
5726 ensure_rank(op, rank, perm.len())?;
5727 ensure_axes_unique(op, "perm", perm, rank)
5728}
5729
5730fn ensure_same_shape_for_broadcast_multiply(
5731 lhs_shape: &[usize],
5732 rhs_shape: &[usize],
5733) -> crate::Result<()> {
5734 if lhs_shape != rhs_shape {
5735 return Err(crate::Error::shape_mismatch(
5736 "broadcast_multiply",
5737 lhs_shape.to_vec(),
5738 rhs_shape.to_vec(),
5739 ));
5740 }
5741 Ok(())
5742}
5743
5744fn launch_broadcast_multiply_typed<T>(
5745 backend: &CudaBackend,
5746 lhs: &TypedTensor<T>,
5747 lhs_shape: &[usize],
5748 lhs_dims: &[usize],
5749 rhs: &TypedTensor<T>,
5750 rhs_shape: &[usize],
5751 rhs_dims: &[usize],
5752) -> crate::Result<TypedTensor<T>>
5753where
5754 T: CubeElement + TensorScalar + CubePrimitive + CubeFloat + Clone,
5755{
5756 ensure_same_shape_for_broadcast_multiply(lhs_shape, rhs_shape)?;
5757 validate_broadcast_in_dim(lhs.shape(), lhs_shape, lhs_dims)?;
5758 validate_broadcast_in_dim(rhs.shape(), rhs_shape, rhs_dims)?;
5759 launch_binary_tensor(
5760 backend.runtime(),
5761 lhs,
5762 rhs,
5763 lhs_shape,
5764 "broadcast_multiply",
5765 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
5766 elementwise::broadcast_multiply_float::launch_unchecked::<T, CubeclCudaRuntime>(
5767 client,
5768 count,
5769 dim,
5770 out.into_tensor_arg(),
5771 lhs_arg.into_tensor_arg(),
5772 rhs_arg.into_tensor_arg(),
5773 comptime_sequence(lhs_dims),
5774 comptime_sequence(rhs_dims),
5775 lhs_shape.len(),
5776 );
5777 },
5778 )
5779}
5780
5781fn launch_broadcast_multiply_int_typed<T>(
5782 backend: &CudaBackend,
5783 lhs: &TypedTensor<T>,
5784 lhs_shape: &[usize],
5785 lhs_dims: &[usize],
5786 rhs: &TypedTensor<T>,
5787 rhs_shape: &[usize],
5788 rhs_dims: &[usize],
5789) -> crate::Result<TypedTensor<T>>
5790where
5791 T: CubeElement + TensorScalar + CubePrimitive + CubeInt + Clone,
5792{
5793 ensure_same_shape_for_broadcast_multiply(lhs_shape, rhs_shape)?;
5794 validate_broadcast_in_dim(lhs.shape(), lhs_shape, lhs_dims)?;
5795 validate_broadcast_in_dim(rhs.shape(), rhs_shape, rhs_dims)?;
5796 launch_binary_tensor(
5797 backend.runtime(),
5798 lhs,
5799 rhs,
5800 lhs_shape,
5801 "broadcast_multiply",
5802 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
5803 elementwise::broadcast_multiply_int::launch_unchecked::<T, CubeclCudaRuntime>(
5804 client,
5805 count,
5806 dim,
5807 out.into_tensor_arg(),
5808 lhs_arg.into_tensor_arg(),
5809 rhs_arg.into_tensor_arg(),
5810 comptime_sequence(lhs_dims),
5811 comptime_sequence(rhs_dims),
5812 lhs_shape.len(),
5813 );
5814 },
5815 )
5816}
5817
5818fn launch_broadcast_multiply_complex_typed<T>(
5819 backend: &CudaBackend,
5820 lhs: &TypedTensor<T>,
5821 lhs_shape: &[usize],
5822 lhs_dims: &[usize],
5823 rhs: &TypedTensor<T>,
5824 rhs_shape: &[usize],
5825 rhs_dims: &[usize],
5826) -> crate::Result<TypedTensor<T>>
5827where
5828 T: CubeElement + TensorScalar + CubePrimitive + CubeComplex + Clone,
5829{
5830 ensure_same_shape_for_broadcast_multiply(lhs_shape, rhs_shape)?;
5831 validate_broadcast_in_dim(lhs.shape(), lhs_shape, lhs_dims)?;
5832 validate_broadcast_in_dim(rhs.shape(), rhs_shape, rhs_dims)?;
5833 launch_binary_tensor(
5834 backend.runtime(),
5835 lhs,
5836 rhs,
5837 lhs_shape,
5838 "broadcast_multiply",
5839 |client, count, dim, out, lhs_arg, rhs_arg| unsafe {
5840 elementwise::broadcast_multiply_complex::launch_unchecked::<T, CubeclCudaRuntime>(
5841 client,
5842 count,
5843 dim,
5844 out.into_tensor_arg(),
5845 lhs_arg.into_tensor_arg(),
5846 rhs_arg.into_tensor_arg(),
5847 comptime_sequence(lhs_dims),
5848 comptime_sequence(rhs_dims),
5849 lhs_shape.len(),
5850 );
5851 },
5852 )
5853}
5854
5855fn validate_broadcast_in_dim(
5856 input_shape: &[usize],
5857 shape: &[usize],
5858 dims: &[usize],
5859) -> crate::Result<()> {
5860 ensure_rank("broadcast_in_dim", input_shape.len(), dims.len())?;
5861 let mut seen = vec![false; shape.len()];
5862 for (src_axis, &dst_axis) in dims.iter().enumerate() {
5863 ensure_axis("broadcast_in_dim", dst_axis, shape.len())?;
5864 if seen[dst_axis] {
5865 return Err(crate::Error::duplicate_axis(
5866 "broadcast_in_dim",
5867 dst_axis,
5868 "dims",
5869 ));
5870 }
5871 seen[dst_axis] = true;
5872 let src = input_shape[src_axis];
5873 let dst = shape[dst_axis];
5874 if src != dst && src != 1 {
5875 return Err(crate::Error::shape_mismatch(
5876 "broadcast_in_dim",
5877 input_shape.to_vec(),
5878 shape.to_vec(),
5879 ));
5880 }
5881 }
5882 Ok(())
5883}
5884
5885fn extract_diagonal_shape(
5886 input_shape: &[usize],
5887 axis_a: usize,
5888 axis_b: usize,
5889) -> crate::Result<(Vec<usize>, usize)> {
5890 ensure_axis("extract_diagonal", axis_a, input_shape.len())?;
5891 ensure_axis("extract_diagonal", axis_b, input_shape.len())?;
5892 if axis_a == axis_b {
5893 return Err(crate::Error::duplicate_axis(
5894 "extract_diagonal",
5895 axis_a,
5896 "axes",
5897 ));
5898 }
5899 let diag_output_axis = if axis_a < axis_b { axis_a } else { axis_a - 1 };
5900 let diag_dim = input_shape[axis_a].min(input_shape[axis_b]);
5901 let mut output_shape = input_shape.to_vec();
5902 output_shape.remove(axis_b);
5903 output_shape[diag_output_axis] = diag_dim;
5904 Ok((output_shape, diag_output_axis))
5905}
5906
5907fn embed_diagonal_shape(
5908 input_shape: &[usize],
5909 axis_a: usize,
5910 axis_b: usize,
5911) -> crate::Result<Vec<usize>> {
5912 ensure_axis("embed_diagonal", axis_a, input_shape.len())?;
5913 if axis_b > input_shape.len() {
5914 return Err(crate::Error::axis_out_of_bounds(
5915 "embed_diagonal",
5916 axis_b,
5917 input_shape.len(),
5918 ));
5919 }
5920 let mut output_shape = input_shape.to_vec();
5921 output_shape.insert(axis_b, input_shape[axis_a]);
5922 Ok(output_shape)
5923}
5924
5925fn reduction_output_shape(input_shape: &[usize], axes: &[usize]) -> Vec<usize> {
5926 input_shape
5927 .iter()
5928 .enumerate()
5929 .filter_map(|(axis, &dim)| (!axes.contains(&axis)).then_some(dim))
5930 .collect()
5931}
5932
5933fn reduction_keepdims_shape(input_shape: &[usize], axis: usize) -> Vec<usize> {
5934 let mut output_shape = input_shape.to_vec();
5935 output_shape[axis] = 1;
5936 output_shape
5937}
5938
5939fn cubecl_reshape_metadata<T: crate::TensorScalar + Clone>(
5940 tensor: TypedTensor<T>,
5941 shape: Vec<usize>,
5942 op: &'static str,
5943) -> crate::Result<TypedTensor<T>> {
5944 let len = shape
5945 .iter()
5946 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
5947 .ok_or_else(|| {
5948 crate::Error::invalid_argument(
5949 op,
5950 "shape",
5951 format!("shape product overflow for CubeCL reshape shape {shape:?}"),
5952 )
5953 })?;
5954 let tensor_len = tensor.n_elements();
5955 if len != tensor_len {
5956 return Err(crate::Error::validation(
5957 op,
5958 tenferro_tensor::ShapeMismatch::ReshapeElementCount {
5959 from: tensor_len,
5960 to: len,
5961 }
5962 .into(),
5963 ));
5964 }
5965
5966 let value = crate::TensorValue::from_tensor(
5971 <T as crate::TensorScalar>::typed_tensor_into_tensor(tensor),
5972 )
5973 .reshape_view(shape)?;
5974 let (group, slot, _, _) = value.try_into_group_parts().map_err(|_| {
5975 crate::Error::runtime_state(
5976 op,
5977 "failed to publish the reshaped tensor descriptor without copying",
5978 )
5979 })?;
5980 let tensor = group.into_tensor(slot).map_err(|(_, error)| {
5981 crate::Error::runtime_state(
5982 op,
5983 format!("failed to detach the reshaped tensor owner: {error}"),
5984 )
5985 })?;
5986 <T as crate::TensorScalar>::into_typed(tensor)
5987}
5988
5989fn validate_slice(input_shape: &[usize], config: &SliceConfig) -> crate::Result<Vec<usize>> {
5990 let rank = input_shape.len();
5991 ensure_rank("slice", rank, config.starts.len())?;
5992 ensure_rank("slice", rank, config.limits.len())?;
5993 ensure_rank("slice", rank, config.strides.len())?;
5994 input_shape
5995 .iter()
5996 .enumerate()
5997 .map(|(axis, &dim)| {
5998 let start = config.starts[axis];
5999 let limit = config.limits[axis];
6000 let stride = config.strides[axis];
6001 if start > limit {
6002 return Err(crate::Error::invalid_argument(
6003 "slice",
6004 "bounds",
6005 format!("start exceeds limit on axis {axis}"),
6006 ));
6007 }
6008 if limit > dim {
6013 return Err(crate::Error::invalid_argument(
6014 "slice",
6015 "configuration",
6016 format!("limit {limit} on axis {axis} exceeds dimension size {dim}"),
6017 ));
6018 }
6019 if stride == 0 {
6020 return Err(crate::Error::invalid_argument(
6021 "slice",
6022 "strides",
6023 format!("stride must be positive on axis {axis}"),
6024 ));
6025 }
6026 let span = limit - start;
6027 Ok(span.div_ceil(stride))
6028 })
6029 .collect()
6030}
6031
6032fn pad_output_shape(input_shape: &[usize], config: &PadConfig) -> crate::Result<Vec<usize>> {
6033 let rank = input_shape.len();
6034 ensure_rank("pad", rank, config.edge_padding_low.len())?;
6035 ensure_rank("pad", rank, config.edge_padding_high.len())?;
6036 ensure_rank("pad", rank, config.interior_padding.len())?;
6037 let mut out_shape = Vec::with_capacity(rank);
6038 for (axis, &input_dim_raw) in input_shape.iter().enumerate().take(rank) {
6039 if config.interior_padding[axis] < 0 {
6040 return Err(crate::Error::invalid_argument(
6041 "pad",
6042 "interior_padding",
6043 format!("interior padding must be non-negative on axis {axis}"),
6044 ));
6045 }
6046 let input_dim = i64::try_from(input_dim_raw).map_err(|_| {
6047 crate::Error::invalid_argument(
6048 "pad",
6049 "input_shape",
6050 format!("input dimension on axis {axis} must fit in i64"),
6051 )
6052 })?;
6053 let base = if input_dim == 0 {
6054 0
6055 } else {
6056 let spacing = config.interior_padding[axis]
6057 .checked_add(1)
6058 .ok_or_else(|| {
6059 crate::Error::invalid_argument(
6060 "pad",
6061 "interior_padding",
6062 format!("interior padding overflow on axis {axis}"),
6063 )
6064 })?;
6065 input_dim
6066 .checked_sub(1)
6067 .and_then(|extent| extent.checked_mul(spacing))
6068 .and_then(|extent| extent.checked_add(1))
6069 .ok_or_else(|| {
6070 crate::Error::invalid_argument(
6071 "pad",
6072 "interior_padding",
6073 format!("padded interior extent overflow on axis {axis}"),
6074 )
6075 })?
6076 };
6077 let dim = config.edge_padding_low[axis]
6078 .checked_add(config.edge_padding_high[axis])
6079 .and_then(|edge| edge.checked_add(base))
6080 .ok_or_else(|| {
6081 crate::Error::invalid_argument(
6082 "pad",
6083 "padding",
6084 format!("output dimension overflow on axis {axis}"),
6085 )
6086 })?;
6087 out_shape.push(usize::try_from(dim).map_err(|_| {
6088 crate::Error::invalid_argument(
6089 "pad",
6090 "padding",
6091 format!("negative output dimension on axis {axis}"),
6092 )
6093 })?);
6094 }
6095 Ok(out_shape)
6096}
6097
6098fn validate_slice_sizes_within_operand(
6099 op: &'static str,
6100 operand_shape: &[usize],
6101 slice_sizes: &[usize],
6102) -> crate::Result<()> {
6103 ensure_rank(op, operand_shape.len(), slice_sizes.len())?;
6104 for (axis, (&slice_size, &dim_size)) in slice_sizes.iter().zip(operand_shape).enumerate() {
6105 if slice_size > dim_size {
6106 return Err(crate::Error::invalid_argument(
6107 op,
6108 "slice_sizes",
6109 format!("slice_sizes[{axis}]={slice_size} exceeds operand dimension {dim_size}"),
6110 ));
6111 }
6112 }
6113 Ok(())
6114}
6115
6116fn index_vector_size(shape: &[usize], index_vector_dim: usize) -> usize {
6117 if index_vector_dim == shape.len() {
6118 1
6119 } else {
6120 shape[index_vector_dim]
6121 }
6122}
6123
6124fn index_batch_shape(shape: &[usize], index_vector_dim: usize) -> Vec<usize> {
6125 if index_vector_dim == shape.len() {
6126 return shape.to_vec();
6127 }
6128 shape
6129 .iter()
6130 .enumerate()
6131 .filter_map(|(axis, &dim)| (axis != index_vector_dim).then_some(dim))
6132 .collect()
6133}
6134
6135fn operand_window_dims(rank: usize, collapsed_or_inserted: &[usize]) -> Vec<usize> {
6136 (0..rank)
6137 .filter(|dim| !collapsed_or_inserted.contains(dim))
6138 .collect()
6139}
6140
6141#[derive(Debug)]
6142struct GatherLaunchMeta {
6143 output_shape: Vec<usize>,
6144 window_dims: Vec<usize>,
6145}
6146
6147fn gather_launch_meta(
6148 operand_shape: &[usize],
6149 start_indices_shape: &[usize],
6150 config: &GatherConfig,
6151) -> crate::Result<GatherLaunchMeta> {
6152 ensure_rank("gather", operand_shape.len(), config.slice_sizes.len())?;
6153 validate_slice_sizes_within_operand("gather", operand_shape, &config.slice_sizes)?;
6154 if config.index_vector_dim > start_indices_shape.len() {
6155 return Err(crate::Error::axis_out_of_bounds(
6156 "gather",
6157 config.index_vector_dim,
6158 start_indices_shape.len(),
6159 ));
6160 }
6161 let index_size = index_vector_size(start_indices_shape, config.index_vector_dim);
6162 if index_size != config.start_index_map.len() {
6163 return Err(crate::Error::invalid_argument(
6164 "gather",
6165 "start_index_map",
6166 "start_index_map length mismatch",
6167 ));
6168 }
6169 ensure_axes_unique(
6170 "gather",
6171 "collapsed_slice_dims",
6172 &config.collapsed_slice_dims,
6173 operand_shape.len(),
6174 )?;
6175 for &dim in &config.collapsed_slice_dims {
6176 if config.slice_sizes[dim] != 1 {
6177 return Err(crate::Error::invalid_argument(
6178 "gather",
6179 "collapsed_slice_dims",
6180 format!(
6181 "collapsed slice dimension {dim} must have slice_size == 1, got {}",
6182 config.slice_sizes[dim]
6183 ),
6184 ));
6185 }
6186 }
6187 ensure_axes_unique(
6188 "gather",
6189 "start_index_map",
6190 &config.start_index_map,
6191 operand_shape.len(),
6192 )?;
6193 let window_dims = operand_window_dims(operand_shape.len(), &config.collapsed_slice_dims);
6194 if config.offset_dims.len() != window_dims.len() {
6195 return Err(crate::Error::invalid_argument(
6196 "gather",
6197 "offset_dims",
6198 "offset_dims length mismatch",
6199 ));
6200 }
6201 let batch_shape = index_batch_shape(start_indices_shape, config.index_vector_dim);
6202 let out_rank = batch_shape.len() + config.offset_dims.len();
6203 ensure_axes_unique("gather", "offset_dims", &config.offset_dims, out_rank)?;
6204 let mut output_shape = vec![0usize; out_rank];
6205 let mut out_axis_to_operand_dim = vec![None; out_rank];
6206 for (offset_axis, &out_axis) in config.offset_dims.iter().enumerate() {
6207 out_axis_to_operand_dim[out_axis] = Some(window_dims[offset_axis]);
6208 }
6209 let mut batch_axis = 0usize;
6210 for out_axis in 0..out_rank {
6211 if let Some(operand_dim) = out_axis_to_operand_dim[out_axis] {
6212 output_shape[out_axis] = config.slice_sizes[operand_dim];
6213 } else {
6214 output_shape[out_axis] = batch_shape[batch_axis];
6215 batch_axis += 1;
6216 }
6217 }
6218 Ok(GatherLaunchMeta {
6219 output_shape,
6220 window_dims,
6221 })
6222}
6223
6224#[derive(Debug)]
6225struct ScatterLaunchMeta {
6226 batch_shape: Vec<usize>,
6227 window_dims: Vec<usize>,
6228 window_shape_updates: Vec<usize>,
6229}
6230
6231fn scatter_launch_meta(
6232 operand_shape: &[usize],
6233 scatter_indices_shape: &[usize],
6234 updates_shape: &[usize],
6235 config: &ScatterConfig,
6236) -> crate::Result<ScatterLaunchMeta> {
6237 if config.index_vector_dim > scatter_indices_shape.len() {
6238 return Err(crate::Error::axis_out_of_bounds(
6239 "scatter",
6240 config.index_vector_dim,
6241 scatter_indices_shape.len(),
6242 ));
6243 }
6244 let index_size = index_vector_size(scatter_indices_shape, config.index_vector_dim);
6245 if index_size != config.scatter_dims_to_operand_dims.len() {
6246 return Err(crate::Error::invalid_argument(
6247 "scatter",
6248 "scatter_dims_to_operand_dims",
6249 "scatter_dims_to_operand_dims length mismatch",
6250 ));
6251 }
6252 ensure_axes_unique(
6253 "scatter",
6254 "inserted_window_dims",
6255 &config.inserted_window_dims,
6256 operand_shape.len(),
6257 )?;
6258 ensure_axes_unique(
6259 "scatter",
6260 "scatter_dims_to_operand_dims",
6261 &config.scatter_dims_to_operand_dims,
6262 operand_shape.len(),
6263 )?;
6264 ensure_axes_unique(
6265 "scatter",
6266 "update_window_dims",
6267 &config.update_window_dims,
6268 updates_shape.len(),
6269 )?;
6270 let batch_shape = index_batch_shape(scatter_indices_shape, config.index_vector_dim);
6271 let window_dims = operand_window_dims(operand_shape.len(), &config.inserted_window_dims);
6272 if config.update_window_dims.len() != window_dims.len() {
6273 return Err(crate::Error::invalid_argument(
6274 "scatter",
6275 "update_window_dims",
6276 "update_window_dims length mismatch",
6277 ));
6278 }
6279 let updates_batch_rank = updates_shape.len() - config.update_window_dims.len();
6280 if updates_batch_rank != batch_shape.len() {
6281 return Err(crate::Error::rank_mismatch(
6282 "scatter",
6283 batch_shape.len(),
6284 updates_batch_rank,
6285 ));
6286 }
6287 let mut is_update_window_dim = vec![false; updates_shape.len()];
6288 for &axis in &config.update_window_dims {
6289 is_update_window_dim[axis] = true;
6290 }
6291 let mut batch_axis = 0usize;
6292 for (axis, &actual) in updates_shape.iter().enumerate() {
6293 if is_update_window_dim[axis] {
6294 continue;
6295 }
6296 let expected = batch_shape[batch_axis];
6297 if actual != expected {
6298 return Err(crate::Error::shape_mismatch(
6299 "scatter",
6300 vec![expected],
6301 vec![actual],
6302 ));
6303 }
6304 batch_axis += 1;
6305 }
6306 let window_shape_updates = config
6307 .update_window_dims
6308 .iter()
6309 .map(|&axis| updates_shape[axis])
6310 .collect();
6311 Ok(ScatterLaunchMeta {
6312 batch_shape,
6313 window_dims,
6314 window_shape_updates,
6315 })
6316}
6317
6318fn concatenate_output_shape<T>(
6319 inputs: &[&TypedTensor<T>],
6320 axis: usize,
6321) -> crate::Result<Vec<usize>> {
6322 let first = inputs[0];
6323 let rank = first.shape().len();
6324 ensure_axis("concatenate", axis, rank)?;
6325 let mut out_shape = first.shape().to_vec();
6326 let mut axis_extent = 0usize;
6327 for input in inputs {
6328 ensure_rank("concatenate", rank, input.shape().len())?;
6329 for dim in 0..rank {
6330 if dim == axis {
6331 axis_extent = axis_extent.checked_add(input.shape()[dim]).ok_or_else(|| {
6332 crate::Error::invalid_argument(
6333 "concatenate",
6334 "shape",
6335 "concatenate axis extent overflows usize",
6336 )
6337 })?;
6338 } else if input.shape()[dim] != first.shape()[dim] {
6339 return Err(crate::Error::shape_mismatch(
6340 "concatenate",
6341 first.shape().to_vec(),
6342 input.shape().to_vec(),
6343 ));
6344 }
6345 }
6346 }
6347 out_shape[axis] = axis_extent;
6348 Ok(out_shape)
6349}
6350
6351#[cfg(test)]
6352mod tests;