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