Skip to main content

tenferro_gpu/cubecl/
mod.rs

1//! CubeCL-based GPU backend for tenferro tensors.
2//!
3//! This module provides GPU acceleration via [CubeCL](https://github.com/tracel-ai/cubecl)
4//! running on NVIDIA CUDA devices. It is gated behind the `cuda` feature flag and
5//! requires **CUDA 12.8+** with a compatible NVIDIA GPU.
6//!
7//! # Enabling the feature
8//!
9//! Add to your `Cargo.toml`:
10//!
11//! ```toml
12//! tenferro-gpu = { version = "...", features = ["cuda"] }
13//! ```
14//!
15//! You must also enable a CPU backend (`cpu-faer` or `cpu-blas`); the CubeCL backend
16//! complements the CPU path but does not replace it.
17//!
18//! # Prerequisites
19//!
20//! - NVIDIA GPU with CUDA compute capability ≥ 7.0
21//! - CUDA Toolkit 12.8 or newer installed (provides NVRTC for JIT kernel compilation)
22//! - cuTENSOR shared library available on `LD_LIBRARY_PATH`
23//!
24//! ## Environment variables
25//!
26//! | Variable | Purpose |
27//! |----------|---------|
28//! | `CUDA_PATH` | CUDA toolkit root (e.g. `/usr/local/cuda-12.8`) |
29//! | `CUBECL_DEBUG_LOG` | Set to `0` to suppress verbose JIT logs |
30//! | `TENFERRO_CUTENSOR_PATH` | Override cuTENSOR library search path |
31//!
32//! # Basic usage
33//!
34//! GPU tensors must be explicitly uploaded before use on the device and downloaded
35//! back to the host afterwards (no implicit CPU↔GPU transfer, following the PyTorch
36//! convention).
37//!
38//! ```rust
39//! use tenferro_gpu::{cuda::cuda_devices, cuda::CudaBackend, cuda::CudaDeviceError};
40//!
41//! fn first_cuda_backend() -> Result<Option<CudaBackend>, CudaDeviceError> {
42//!     let devices = cuda_devices()?;
43//!     let Some(device) = devices.first() else {
44//!         return Ok(None);
45//!     };
46//!     Ok(Some(CudaBackend::new(device.id())?))
47//! }
48//!
49//! let _example: fn() -> Result<Option<CudaBackend>, CudaDeviceError> = first_cuda_backend;
50//! ```
51//!
52//! # Running GPU tests
53//!
54//! All GPU tests are marked `#[ignore]` so that `cargo test --features cuda`
55//! passes on machines without a GPU. To actually run them:
56//!
57//! ```sh
58//! CUBECL_DEBUG_LOG=0 \
59//! CUDA_PATH=/usr/local/cuda-12.8 \
60//! cargo test -p tenferro-gpu --features cuda -- --ignored
61//! ```
62
63use 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                    // SAFETY: The tiled classification proves a compact 2D
244                    // transpose. Bounds guards cover edge tiles and every unit
245                    // reaches the shared-memory barrier.
246                    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        // SAFETY: `NativePermutationPlan` validated both allocation ranges,
270        // destination non-overlap, and disjoint source/destination storage.
271        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/// CubeCL-based GPU backend.
304///
305/// # Examples
306///
307/// ```
308/// use tenferro_gpu::{cuda::CudaBackend, cuda::CudaDeviceError, cuda::CudaDeviceId};
309///
310/// let _ctor: fn(CudaDeviceId) -> Result<CudaBackend, CudaDeviceError> = CudaBackend::new;
311/// ```
312#[doc(hidden)]
313struct CudaBackendSessionMarker;
314
315#[derive(Clone)]
316pub struct CudaBackend {
317    inner: Arc<CudaBackendState>,
318}
319
320struct CudaBackendState {
321    // CUDA library handles are dropped before `rt`; Rust drops fields in
322    // declaration order, so cache-owned handles release while the CUDA primary
323    // context is still retained by `CudaRuntime`.
324    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/// Type-indexed cache for CUDA extension-owned backend state.
340#[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    /// Create an empty extension cache.
443    ///
444    /// # Examples
445    ///
446    /// ```
447    /// use tenferro_gpu::cuda::CudaExtensionCache;
448    ///
449    /// let cache = CudaExtensionCache::new();
450    /// assert!(cache.is_empty()?);
451    /// # Ok::<(), tenferro_tensor::Error>(())
452    /// ```
453    ///
454    /// The cache retains at most 16 extension states by default. Use
455    /// [`Self::with_max_entries`] to choose a different bound. Later cache
456    /// operations return [`crate::Error::RuntimeState`] if the cache mutex is
457    /// poisoned.
458    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    /// Create an empty extension cache with an explicit entry bound.
465    pub fn with_max_entries(max_entries: NonZeroUsize) -> Self {
466        Self {
467            inner: Mutex::new(CudaExtensionCacheInner::new(max_entries)),
468        }
469    }
470
471    /// Returns `true` when no extension state has been initialized.
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// use tenferro_gpu::cuda::CudaExtensionCache;
477    ///
478    /// assert!(CudaExtensionCache::new().is_empty()?);
479    /// # Ok::<(), tenferro_tensor::Error>(())
480    /// ```
481    /// # Errors
482    ///
483    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
484    pub fn is_empty(&self) -> crate::Result<bool> {
485        Ok(self.lock_inner()?.entries.is_empty())
486    }
487
488    /// Remove every cached CUDA extension state value.
489    ///
490    /// This operation returns a runtime-state error if the cache mutex is
491    /// poisoned.
492    ///
493    /// # Errors
494    ///
495    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
496    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    /// Snapshot the number of retained entries and logical retained bytes.
510    /// # Errors
511    ///
512    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
513    pub fn stats(&self) -> crate::Result<CacheStats> {
514        let inner = self.lock_inner()?;
515        Ok(inner.snapshot_stats())
516    }
517
518    /// Return the configured entry bound.
519    /// # Errors
520    ///
521    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
522    pub fn max_entries(&self) -> crate::Result<NonZeroUsize> {
523        Ok(self.lock_inner()?.max_entries)
524    }
525
526    /// Return the configured logical retained-byte bound.
527    /// # Errors
528    ///
529    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
530    pub fn max_retained_bytes(&self) -> crate::Result<NonZeroUsize> {
531        Ok(self.lock_inner()?.max_retained_bytes)
532    }
533
534    /// Replace the entry bound and evict oldest entries if needed.
535    /// # Errors
536    ///
537    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned
538    /// while changing the bound.
539    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    /// Configure the logical retained-byte bound and evict oldest entries if
547    /// needed.
548    /// # Errors
549    ///
550    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned
551    /// while changing the bound.
552    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    /// Get or lazily initialize one cache entry keyed by `T`.
560    ///
561    /// # Examples
562    ///
563    /// ```
564    /// use tenferro_gpu::cuda::CudaExtensionCache;
565    ///
566    /// let cache = CudaExtensionCache::new();
567    /// let value = cache.get_or_try_init::<usize>(|| Ok(3)).unwrap();
568    /// assert_eq!(*value, 3);
569    /// ```
570    /// # Errors
571    ///
572    /// Propagates the initializer's typed error, returns
573    /// [`crate::Error::RuntimeState`] for a poisoned cache or a missing/wrongly
574    /// typed entry, and preserves backend errors from initialization.
575    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    /// Update the logical retained-byte estimate for an existing typed entry.
635    ///
636    /// This supports extension states whose own internal cache grows after the
637    /// top-level entry is initialized. If another thread clears or evicts the
638    /// typed entry before the update, the update is treated as a no-op.
639    /// # Errors
640    ///
641    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
642    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/// Borrow guard for one cached CUDA extension state value.
670#[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        // SAFETY: get_or_try_init validates the downcast while holding this
698        // same mutex guard. The entry cannot move or be evicted while this
699        // guard owns the mutex.
700        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        // Same-dtype casts are explicit copies. Use the native materialization
710        // path so an identity copy preserves NaN payloads instead of routing
711        // through cuTENSOR's alpha-scaled permutation operation.
712        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    /// Create a new CubeCL backend for the caller-selected CUDA device.
738    ///
739    /// # Examples
740    ///
741    /// ```
742    /// use tenferro_gpu::{cuda::CudaBackend, cuda::CudaDeviceError, cuda::CudaDeviceId};
743    ///
744    /// let _ctor: fn(CudaDeviceId) -> Result<CudaBackend, CudaDeviceError> = CudaBackend::new;
745    /// ```
746    /// # Errors
747    ///
748    /// Returns [`CudaDeviceError::Discovery`] when device discovery fails,
749    /// [`CudaDeviceError::Unavailable`] when the selected device is not
750    /// discovered, or [`CudaDeviceError::Initialization`] when CUDA runtime,
751    /// context, or CubeCL client initialization fails.
752    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    /// Borrow the underlying CubeCL runtime.
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use tenferro_gpu::{cuda::CudaBackend, cuda::CudaRuntime};
768    ///
769    /// let _runtime: fn(&CudaBackend) -> &CudaRuntime = CudaBackend::runtime;
770    /// ```
771    pub fn runtime(&self) -> &CudaRuntime {
772        &self.inner.rt
773    }
774
775    /// Return the caller-selected CUDA device identity used by this backend.
776    ///
777    /// # Examples
778    ///
779    /// ```
780    /// use tenferro_gpu::{cuda::CudaBackend, cuda::CudaDeviceId};
781    ///
782    /// let _device_id: fn(&CudaBackend) -> CudaDeviceId = CudaBackend::device_id;
783    /// ```
784    pub fn device_id(&self) -> CudaDeviceId {
785        self.inner.rt.device_id()
786    }
787
788    /// Return the opaque identity of this exact executable backend instance.
789    ///
790    /// Clones of a backend return the same identity. Independently constructed
791    /// backends return different identities even when they target the same
792    /// CUDA device ordinal.
793    ///
794    /// # Examples
795    ///
796    /// ```
797    /// use tenferro_gpu::cuda::CudaBackend;
798    ///
799    /// let _identity = CudaBackend::runtime_identity;
800    /// ```
801    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    /// Clear CUDA extension-owned backend state.
826    ///
827    /// # Errors
828    ///
829    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
830    /// poisoned.
831    pub fn clear_cuda_extension_cache(&self) -> crate::Result<()> {
832        self.inner.extension_cache.clear()
833    }
834
835    /// Return CUDA extension cache stats.
836    ///
837    /// # Errors
838    ///
839    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
840    /// poisoned.
841    pub fn cuda_extension_cache_stats(&self) -> crate::Result<CacheStats> {
842        self.inner.extension_cache.stats()
843    }
844
845    /// Return the CUDA extension cache entry bound.
846    ///
847    /// # Errors
848    ///
849    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
850    /// poisoned.
851    pub fn cuda_extension_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
852        self.inner.extension_cache.max_entries()
853    }
854
855    /// Return the CUDA extension cache logical retained-byte bound.
856    ///
857    /// # Errors
858    ///
859    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
860    /// poisoned.
861    pub fn cuda_extension_cache_max_retained_bytes(&self) -> crate::Result<NonZeroUsize> {
862        self.inner.extension_cache.max_retained_bytes()
863    }
864
865    /// Configure the CUDA extension cache entry bound.
866    ///
867    /// # Errors
868    ///
869    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
870    /// poisoned while changing the bound.
871    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    /// Configure the CUDA extension cache logical retained-byte bound.
879    ///
880    /// # Errors
881    ///
882    /// Returns [`crate::Error::RuntimeState`] if the extension cache mutex is
883    /// poisoned while changing the bound.
884    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    /// Return cuTENSOR contraction plan cache stats.
894    ///
895    /// The returned entry count is the number of retained cuTENSOR contraction
896    /// plans inside the CUDA backend's extension cache entry. Logical retained
897    /// bytes include cached cuTENSOR device workspace estimates.
898    /// # Errors
899    ///
900    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
901    pub fn cutensor_plan_cache_stats(&self) -> crate::Result<CacheStats> {
902        gemm::cutensor_plan_cache_stats(self)
903    }
904
905    /// Return the cuTENSOR contraction plan entry bound.
906    /// # Errors
907    ///
908    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
909    pub fn cutensor_plan_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
910        gemm::cutensor_plan_cache_max_entries(self)
911    }
912
913    /// Configure the cuTENSOR contraction plan entry bound.
914    ///
915    /// The cache is initialized if it does not already exist so a setting made
916    /// before the first CUDA `dot_general` call is preserved.
917    /// # Errors
918    ///
919    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
920    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    /// Return cuTENSOR structural permutation plan cache stats.
928    ///
929    /// The returned entry count is the number of retained cuTENSOR permutation
930    /// plans inside the CUDA backend's extension cache entry. Logical retained
931    /// bytes include cached descriptor and plan state.
932    /// # Errors
933    ///
934    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
935    pub fn cutensor_permutation_plan_cache_stats(&self) -> crate::Result<CacheStats> {
936        permutation::cutensor_permutation_plan_cache_stats(self)
937    }
938
939    /// Return the cuTENSOR structural permutation plan entry bound.
940    /// # Errors
941    ///
942    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
943    pub fn cutensor_permutation_plan_cache_max_entries(&self) -> crate::Result<NonZeroUsize> {
944        permutation::cutensor_permutation_plan_cache_max_entries(self)
945    }
946
947    /// Configure the cuTENSOR structural permutation plan entry bound.
948    ///
949    /// The cache is initialized if it does not already exist so a setting made
950    /// before the first CUDA structural permutation call is preserved.
951    /// # Errors
952    ///
953    /// Returns [`crate::Error::RuntimeState`] if the cache mutex is poisoned.
954    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            // cuTENSOR 2.x rejects zero/negative-stride tensor descriptors. This
1209            // keeps existing CUDA view coverage for a layout the vendor
1210            // permutation path cannot represent; it is not a missing-library
1211            // fallback for cuTENSOR-supported descriptors.
1212            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            // SAFETY: The source is an owned compact CubeCL tensor on this
1273            // runtime. Allocation identity validation above proves source and
1274            // destination do not alias. The destination view has validated
1275            // reachable offsets and no internal overlap, and the launch domain
1276            // covers each source element and destination logical coordinate
1277            // exactly once.
1278            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                    // SAFETY: `convert_float_to_complex_raw` validated that
1563                    // `input` has `n` elements and `out` has `2 * n` scalar
1564                    // components. The kernel launches exactly `n` logical input
1565                    // positions and guards with `ABSOLUTE_POS < input.len()`.
1566                    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                    // SAFETY: `convert_float_to_complex_raw` validated that
1588                    // `input` has `n` elements and `out` has `2 * n` scalar
1589                    // components. The kernel launches exactly `n` logical input
1590                    // positions and guards with `ABSOLUTE_POS < input.len()`.
1591                    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                    // SAFETY: `convert_float_to_complex_raw` validated that
1613                    // `input` has `n` elements and `out` has `2 * n` scalar
1614                    // components. The kernel launches exactly `n` logical input
1615                    // positions and guards with `ABSOLUTE_POS < input.len()`.
1616                    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                    // SAFETY: `convert_float_to_complex_raw` validated that
1638                    // `input` has `n` elements and `out` has `2 * n` scalar
1639                    // components. The kernel launches exactly `n` logical input
1640                    // positions and guards with `ABSOLUTE_POS < input.len()`.
1641                    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    /// Generic float-to-complex conversion via raw interleaved kernel.
1655    ///
1656    /// The kernel writes `(re, 0, re, 0, ...)` into a raw float buffer that
1657    /// is then reinterpreted as complex.
1658    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        // SAFETY: The checked raw-array helpers prove that `input_arg` covers
1695        // exactly the dense input shape and `output_parts` covers the complete
1696        // real/imaginary scalar representation of the output allocation.
1697        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, &current, 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(&current, 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            // INVARIANT: `concatenate_output_shape(inputs, axis)?` above checks
2746            // the total axis extent, so every partial offset stays bounded.
2747            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            // SAFETY: `scatter_launch_meta` validates the scatter/update
2955            // shapes and dimension-number mappings. `typed_tensor_binding`
2956            // validates input logical tensor buffers, while
2957            // `typed_tensor_array_arg_as` proves the atomic output view stays
2958            // within its backing allocation. The launch domain is
2959            // `scatter_update_len(meta)`, and the kernel maps each launched
2960            // update through the validated metadata before indexing.
2961            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        // num_complex::Complex<T> is repr(C) as { re: T, im: T }, so the
3058        // complex buffers can be viewed as real scalar parts for atomic add.
3059        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            // SAFETY: `scatter_launch_meta` validates the scatter/update
3066            // shapes and dimension-number mappings. `typed_tensor_binding`
3067            // validates logical tensor buffers, while `typed_tensor_array_arg_as`
3068            // proves complex real/imaginary part arrays stay within their
3069            // backing allocations. The launch domain is
3070            // `scatter_update_len(meta)` and the kernel indexes via the
3071            // validated metadata.
3072            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        // SAFETY: the flag allocation has two `F` elements, and the checked
3407        // reinterpretation above proves the atomic-u32 view fits that buffer.
3408        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        // SAFETY: the input binding was validated before allocation, the
3419        // launch domain is the checked input length, and the scalar flag view
3420        // was bounds-checked above.
3421        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        // SAFETY: one worker reads the atomically selected in-range index and
3435        // copies that single value into the second element of the same flag.
3436        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    // INVARIANT: `num_complex::Complex<T>` is `repr(C)` with interleaved `{ re, im }`
3609    // fields; the checked `2 * n_elements` length and binding validator prove this
3610    // real-component view covers exactly the resident complex allocation.
3611    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
3732/// Operand accepted by a CUDA `_read` entry point.
3733///
3734/// The traced runtime prepares operands as `TensorRead`, which is either an
3735/// owned tensor or a borrowed view over another tensor's storage. CUDA kernels
3736/// consume owned compact tensors, so a borrowed view is materialized once
3737/// through this backend's own `to_contiguous_read`.
3738enum CudaReadInput<'a> {
3739    /// The caller owns the tensor for the duration of the call.
3740    Borrowed(&'a Tensor),
3741    /// A borrowed view materialized into backend storage for the call.
3742    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    /// Accept a read operand the runtime prepared, materializing a view.
3756    ///
3757    /// Keeping the materialized tensor inside [`CudaReadInput`] bounds the
3758    /// view's device storage to the call that needs it; the traced runtime
3759    /// releases it together with the operation result.
3760    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    // Borrowed-view entry points. The traced runtime prepares operands as
3772    // `TensorRead`; a view is materialized before the CUDA kernel runs.
3773    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    // Borrowed-view entry points. The traced runtime prepares operands as
4579    // `TensorRead`; a view is materialized before the CUDA kernel runs.
4580    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    // Borrowed-view entry points. The traced runtime prepares operands as
4814    // `TensorRead`; a view is materialized before the CUDA kernel runs.
4815    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        // An owned tensor cannot be returned by shallowly reusing a backend
4984        // buffer. Materialize one explicit same-placement copy first, then
4985        // change only its compact metadata.
4986        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    // Borrowed-view entry points. The traced runtime prepares operands as
5257    // `TensorRead`; a view is materialized before the CUDA kernel runs.
5258    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    // CUDA-native accumulation (tensor4all/tenferro-rs#1287): one cuTENSOR
5397    // contraction with C = D = out; no temporary result tensor, no host
5398    // transfer. Stage 2 accepts compact owned tensors and borrowed strided
5399    // views over device buffers on all three slots; host-backed views are an
5400    // explicit backend error.
5401    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    // `TypedTensor::into_parts` intentionally materializes host storage and
6240    // therefore cannot preserve a backend-owned root. Move the owner through
6241    // `TensorValue`/`AllocationGroup` instead so this metadata-only reshape
6242    // keeps the exact CubeCL allocation and performs no implicit download.
6243    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            // INVARIANT: This boundary check intentionally mirrors CPU's
6282            // validator. CPU and GPU are independent backend leaves, and
6283            // sharing it via tenferro-tensor would require a new public
6284            // validation API.
6285            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;