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