Skip to main content

singe_cutensor/
operation.rs

1use std::{
2    mem::{ManuallyDrop, MaybeUninit},
3    ptr,
4};
5
6use singe_cuda::{
7    data_type::{DataType, DataTypeLike},
8    types::{Complex32, Complex64, bf16, f4e2m1, f6e2m3, f6e3m2, f8e4m3, f8e5m2, f8ue8m0, f16},
9};
10
11use crate::{
12    context::{Context, ContextRef, validate_same_context},
13    error::{Error, Result},
14    sys,
15    tensor::{BlockSparseTensorDescriptor, TensorDescriptor},
16    try_ffi,
17    types::{Mode, OperationDescriptorAttribute, Operator},
18    utility::{modes_to_i32_vec, to_i32},
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[repr(transparent)]
23pub struct ComputeDescriptor(sys::cutensorComputeDescriptor_t);
24
25impl ComputeDescriptor {
26    // TODO: Not sure if these extern values are always valid.
27    pub const fn f16() -> Self {
28        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_16F })
29    }
30
31    pub const fn bf16() -> Self {
32        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_16BF })
33    }
34
35    pub const fn tf32() -> Self {
36        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_TF32 })
37    }
38
39    pub const fn tf32x3() -> Self {
40        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_3XTF32 })
41    }
42
43    pub const fn f32() -> Self {
44        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_32F })
45    }
46
47    pub const fn f64() -> Self {
48        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_64F })
49    }
50
51    pub const fn bf16x9() -> Self {
52        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_9X16BF })
53    }
54
55    pub const fn i8x8() -> Self {
56        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_8XINT8 })
57    }
58
59    pub const fn f16x4() -> Self {
60        Self(unsafe { sys::CUTENSOR_COMPUTE_DESC_4X16F })
61    }
62
63    pub const fn as_raw(self) -> sys::cutensorComputeDescriptor_t {
64        self.0
65    }
66}
67
68impl From<sys::cutensorComputeDescriptor_t> for ComputeDescriptor {
69    fn from(value: sys::cutensorComputeDescriptor_t) -> Self {
70        Self(value)
71    }
72}
73
74impl From<ComputeDescriptor> for sys::cutensorComputeDescriptor_t {
75    fn from(value: ComputeDescriptor) -> Self {
76        value.0
77    }
78}
79
80#[derive(Debug, Clone, Copy)]
81pub struct TensorOperand<'a> {
82    desc: &'a TensorDescriptor,
83    modes: &'a [Mode],
84    op: Operator,
85}
86
87impl<'a> TensorOperand<'a> {
88    pub const fn new(desc: &'a TensorDescriptor, modes: &'a [Mode], op: Operator) -> Self {
89        Self { desc, modes, op }
90    }
91
92    pub fn identity(desc: &'a TensorDescriptor, modes: &'a [Mode]) -> Self {
93        Self::new(desc, modes, Operator::Identity)
94    }
95}
96
97#[derive(Debug, Clone, Copy)]
98pub struct BlockSparseTensorOperand<'a> {
99    desc: &'a BlockSparseTensorDescriptor,
100    modes: &'a [Mode],
101    op: Operator,
102}
103
104impl<'a> BlockSparseTensorOperand<'a> {
105    pub const fn new(
106        desc: &'a BlockSparseTensorDescriptor,
107        modes: &'a [Mode],
108        op: Operator,
109    ) -> Self {
110        Self { desc, modes, op }
111    }
112
113    pub fn identity(desc: &'a BlockSparseTensorDescriptor, modes: &'a [Mode]) -> Self {
114        Self::new(desc, modes, Operator::Identity)
115    }
116}
117
118#[derive(Debug)]
119pub struct OperationDescriptor {
120    handle: sys::cutensorOperationDescriptor_t,
121    context: ContextRef,
122    output_rank: u32,
123    output_data_type: DataType,
124    signature: OperationSignature,
125    padding_value: Option<OwnedPaddingValue>,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[non_exhaustive]
130pub enum OperationKind {
131    ElementwiseTrinary {
132        a: DataType,
133        b: DataType,
134        c: DataType,
135        d: DataType,
136    },
137    ElementwiseBinary {
138        a: DataType,
139        c: DataType,
140        d: DataType,
141    },
142    Permutation {
143        a: DataType,
144        b: DataType,
145    },
146    Contraction {
147        a: DataType,
148        b: DataType,
149        c: DataType,
150        d: DataType,
151    },
152    Reduction {
153        a: DataType,
154        c: DataType,
155        d: DataType,
156    },
157    ContractionTrinary {
158        a: DataType,
159        b: DataType,
160        c: DataType,
161        d: DataType,
162        e: DataType,
163    },
164    BlockSparseContraction {
165        a_non_zero_blocks: u64,
166        b_non_zero_blocks: u64,
167        c_non_zero_blocks: u64,
168        d_non_zero_blocks: u64,
169    },
170}
171
172impl OperationKind {
173    pub const fn name(self) -> &'static str {
174        match self {
175            Self::ElementwiseTrinary { .. } => "elementwise_trinary",
176            Self::ElementwiseBinary { .. } => "elementwise_binary",
177            Self::Permutation { .. } => "permute",
178            Self::Contraction { .. } => "contract",
179            Self::Reduction { .. } => "reduce",
180            Self::ContractionTrinary { .. } => "contract_trinary",
181            Self::BlockSparseContraction { .. } => "block_sparse_contract",
182        }
183    }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub struct OperationSignature {
188    pub kind: OperationKind,
189    pub scalar_type: DataType,
190}
191
192#[derive(Debug, Clone, Copy)]
193enum OwnedPaddingValue {
194    F32(f32),
195    F64(f64),
196    F16(f16),
197    Bf16(bf16),
198    F8E4M3(f8e4m3),
199    F8E5M2(f8e5m2),
200    F8UE8M0(f8ue8m0),
201    F6E2M3(f6e2m3),
202    F6E3M2(f6e3m2),
203    F4E2M1(f4e2m1),
204    I8(i8),
205    U8(u8),
206    I32(i32),
207    U32(u32),
208    Complex32(Complex32),
209    Complex64(Complex64),
210}
211
212impl OwnedPaddingValue {
213    fn from_value<T: DataTypeLike>(value: T) -> Result<Self> {
214        match T::data_type() {
215            DataType::F32 => Ok(Self::F32(unsafe { std::mem::transmute_copy(&value) })),
216            DataType::F64 => Ok(Self::F64(unsafe { std::mem::transmute_copy(&value) })),
217            DataType::F16 => Ok(Self::F16(unsafe { std::mem::transmute_copy(&value) })),
218            DataType::Bf16 => Ok(Self::Bf16(unsafe { std::mem::transmute_copy(&value) })),
219            DataType::F8E4M3 => Ok(Self::F8E4M3(unsafe { std::mem::transmute_copy(&value) })),
220            DataType::F8E5M2 => Ok(Self::F8E5M2(unsafe { std::mem::transmute_copy(&value) })),
221            DataType::F8UE8M0 => Ok(Self::F8UE8M0(unsafe { std::mem::transmute_copy(&value) })),
222            DataType::F6E2M3 => Ok(Self::F6E2M3(unsafe { std::mem::transmute_copy(&value) })),
223            DataType::F6E3M2 => Ok(Self::F6E3M2(unsafe { std::mem::transmute_copy(&value) })),
224            DataType::F4E2M1 => Ok(Self::F4E2M1(unsafe { std::mem::transmute_copy(&value) })),
225            DataType::I8 => Ok(Self::I8(unsafe { std::mem::transmute_copy(&value) })),
226            DataType::U8 => Ok(Self::U8(unsafe { std::mem::transmute_copy(&value) })),
227            DataType::I32 => Ok(Self::I32(unsafe { std::mem::transmute_copy(&value) })),
228            DataType::U32 => Ok(Self::U32(unsafe { std::mem::transmute_copy(&value) })),
229            DataType::ComplexF32 => {
230                Ok(Self::Complex32(unsafe { std::mem::transmute_copy(&value) }))
231            }
232            DataType::ComplexF64 => {
233                Ok(Self::Complex64(unsafe { std::mem::transmute_copy(&value) }))
234            }
235            data_type => Err(Error::UnsupportedScalarDataType {
236                name: T::rust_type_name().into(),
237                data_type,
238            }),
239        }
240    }
241
242    fn host_ptr(&self) -> *const () {
243        match self {
244            Self::F32(value) => ptr::from_ref(value).cast(),
245            Self::F64(value) => ptr::from_ref(value).cast(),
246            Self::F16(value) => ptr::from_ref(value).cast(),
247            Self::Bf16(value) => ptr::from_ref(value).cast(),
248            Self::F8E4M3(value) => ptr::from_ref(value).cast(),
249            Self::F8E5M2(value) => ptr::from_ref(value).cast(),
250            Self::F8UE8M0(value) => ptr::from_ref(value).cast(),
251            Self::F6E2M3(value) => ptr::from_ref(value).cast(),
252            Self::F6E3M2(value) => ptr::from_ref(value).cast(),
253            Self::F4E2M1(value) => ptr::from_ref(value).cast(),
254            Self::I8(value) => ptr::from_ref(value).cast(),
255            Self::U8(value) => ptr::from_ref(value).cast(),
256            Self::I32(value) => ptr::from_ref(value).cast(),
257            Self::U32(value) => ptr::from_ref(value).cast(),
258            Self::Complex32(value) => ptr::from_ref(value).cast(),
259            Self::Complex64(value) => ptr::from_ref(value).cast(),
260        }
261    }
262
263    fn size_bytes(&self) -> usize {
264        match self {
265            Self::F32(_) => size_of::<f32>(),
266            Self::F64(_) => size_of::<f64>(),
267            Self::F16(_) => size_of::<f16>(),
268            Self::Bf16(_) => size_of::<bf16>(),
269            Self::F8E4M3(_) => size_of::<f8e4m3>(),
270            Self::F8E5M2(_) => size_of::<f8e5m2>(),
271            Self::F8UE8M0(_) => size_of::<f8ue8m0>(),
272            Self::F6E2M3(_) => size_of::<f6e2m3>(),
273            Self::F6E3M2(_) => size_of::<f6e3m2>(),
274            Self::F4E2M1(_) => size_of::<f4e2m1>(),
275            Self::I8(_) => size_of::<i8>(),
276            Self::U8(_) => size_of::<u8>(),
277            Self::I32(_) => size_of::<i32>(),
278            Self::U32(_) => size_of::<u32>(),
279            Self::Complex32(_) => size_of::<Complex32>(),
280            Self::Complex64(_) => size_of::<Complex64>(),
281        }
282    }
283}
284
285impl OperationDescriptor {
286    /// Creates an operation descriptor that encodes an element-wise trinary operation.
287    ///
288    /// The trinary operation has the following general form:
289    ///
290    /// $$ D\_{\Pi^C(i\_0,i\_1,...,i\_n)} = \Phi\_{ABC}(\Phi\_{AB}(\alpha op\_A(A\_{\Pi^A(i\_0,i\_1,...,i\_n)}), \beta op\_B(B\_{\Pi^B(i\_0,i\_1,...,i\_n)})), \gamma op\_C(C\_{\Pi^C(i\_0,i\_1,...,i\_n)})) $$
291    ///
292    /// Where:
293    ///
294    /// * A,B,C,D are multi-mode tensors (of arbitrary data types).
295    /// * $\Pi^A, \Pi^B, \Pi^C$ are permutation operators that permute the modes of A, B, and C respectively.
296    /// * $op\_{A},op\_{B},op\_{C}$ are unary element-wise operators, such as IDENTITY and CONJUGATE.
297    /// * $\Phi\_{ABC}, \Phi\_{AB}$ are binary element-wise operators, such as ADD, MUL, MAX, and MIN.
298    ///
299    /// Broadcasting can be achieved by omitting that mode from the respective tensor.
300    ///
301    /// Modes may appear in any order.
302    /// The only **restrictions** are:
303    ///
304    /// * modes that appear in A or B *must* also appear in the output tensor; a mode that only appears in the input would be contracted and such an operation would be covered by either [`Plan::contract`](crate::plan::Plan::contract) or [`Plan::reduce`](crate::plan::Plan::reduce).
305    /// * each mode may appear in each tensor at most once.
306    ///
307    /// Input tensors may be read even if the value of the corresponding scalar is zero.
308    ///
309    /// Examples:
310    ///
311    /// * $D\_{a,b,c,d} = A\_{b,d,a,c}$
312    /// * $D\_{a,b,c,d} = 2.2 \cdot A\_{b,d,a,c} + 1.3 \cdot B\_{c,b,d,a}$
313    /// * $D\_{a,b,c,d} = 2.2 \cdot A\_{b,d,a,c} + 1.3 \cdot B\_{c,b,d,a} + C\_{a,b,c,d}$
314    /// * $D\_{a,b,c,d} = min((2.2 \cdot A\_{b,d,a,c} + 1.3 \cdot B\_{c,b,d,a}), C\_{a,b,c,d})$
315    ///
316    /// Call [`Plan::elementwise_trinary`](crate::plan::Plan::elementwise_trinary) to perform the actual operation.
317    ///
318    /// The returned descriptor frees the cuTENSOR descriptor when dropped.
319    ///
320    /// Supported data-type combinations are:
321    ///
322    /// | A type | B type | C type | compute descriptor |
323    /// | --- | --- | --- | --- |
324    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f16`] |
325    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
326    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::bf16`] |
327    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] |
328    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] |
329    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] |
330    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] |
331    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] |
332    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
333    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F32`] | [`ComputeDescriptor::f64`] |
334    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f64`] |
335    ///
336    /// This may call asynchronous CUDA functions. cuTENSOR treats descriptor
337    /// creation as thread-safe but not reentrant.
338    ///
339    /// # Errors
340    ///
341    /// Returns an error if the context cannot be bound, tensor modes do not
342    /// match descriptor ranks, the target architecture is unsupported or not
343    /// ready, cuTENSOR rejects the operand descriptors, or cuTENSOR returns a
344    /// null operation descriptor.
345    pub fn elementwise_trinary(
346        ctx: &Context,
347        lhs: TensorOperand<'_>,
348        rhs: TensorOperand<'_>,
349        aux: TensorOperand<'_>,
350        out: TensorOperand<'_>,
351        op_ab: Operator,
352        op_abc: Operator,
353        compute_descriptor: ComputeDescriptor,
354    ) -> Result<Self> {
355        let context = ctx.as_context_ref();
356        validate_tensor_operand_context(&context, lhs, "lhs")?;
357        validate_tensor_operand_context(&context, rhs, "rhs")?;
358        validate_tensor_operand_context(&context, aux, "aux")?;
359        validate_tensor_operand_context(&context, out, "out")?;
360        validate_modes(lhs.desc.rank(), lhs.modes)?;
361        validate_modes(rhs.desc.rank(), rhs.modes)?;
362        validate_modes(aux.desc.rank(), aux.modes)?;
363        validate_modes(out.desc.rank(), out.modes)?;
364
365        let mode_a = modes_to_i32_vec(lhs.modes);
366        let mode_b = modes_to_i32_vec(rhs.modes);
367        let mode_c = modes_to_i32_vec(aux.modes);
368        let mode_d = modes_to_i32_vec(out.modes);
369
370        let mut raw = ptr::null_mut();
371        ctx.bind()?;
372        unsafe {
373            try_ffi!(sys::cutensorCreateElementwiseTrinary(
374                ctx.as_raw(),
375                &raw mut raw,
376                lhs.desc.as_raw(),
377                mode_a.as_ptr(),
378                lhs.op.into(),
379                rhs.desc.as_raw(),
380                mode_b.as_ptr(),
381                rhs.op.into(),
382                aux.desc.as_raw(),
383                mode_c.as_ptr(),
384                aux.op.into(),
385                out.desc.as_raw(),
386                mode_d.as_ptr(),
387                op_ab.into(),
388                op_abc.into(),
389                compute_descriptor.into(),
390            ))?;
391        }
392
393        Self::from_raw(
394            raw,
395            context,
396            out.desc.rank(),
397            out.desc.data_type(),
398            OperationKind::ElementwiseTrinary {
399                a: lhs.desc.data_type(),
400                b: rhs.desc.data_type(),
401                c: aux.desc.data_type(),
402                d: out.desc.data_type(),
403            },
404        )
405    }
406
407    /// Creates an operation descriptor for an element-wise binary operation.
408    ///
409    /// The binary operation has the following general form:
410    ///
411    /// $$ D\_{\Pi^C(i\_0,i\_1,...,i\_n)} = \Phi\_{AC}(\alpha op\_A(A\_{\Pi^A(i\_0,i\_1,...,i\_n)}), \gamma op\_C(C\_{\Pi^C(i\_0,i\_1,...,i\_n)})) $$
412    ///
413    /// Call [`Plan::elementwise_binary`](crate::plan::Plan::elementwise_binary) to perform the actual operation.
414    ///
415    /// Supported data-type combinations are:
416    ///
417    /// | A type | C type | compute descriptor |
418    /// | --- | --- | --- |
419    /// | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f16`] |
420    /// | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
421    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::bf16`] |
422    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] |
423    /// | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] |
424    /// | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] |
425    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] |
426    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] |
427    /// | [`DataType::F32`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
428    /// | [`DataType::F64`] | [`DataType::F32`] | [`ComputeDescriptor::f64`] |
429    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f64`] |
430    ///
431    /// This may call asynchronous CUDA functions. cuTENSOR treats descriptor
432    /// creation as thread-safe but not reentrant.
433    ///
434    /// # Errors
435    ///
436    /// Returns an error if the context cannot be bound, tensor modes do not
437    /// match descriptor ranks, input and output descriptors or mode sets are
438    /// incompatible, a data type or operator combination is unsupported,
439    /// cuTENSOR rejects the operands, or cuTENSOR returns a null operation
440    /// descriptor.
441    pub fn elementwise_binary(
442        ctx: &Context,
443        a: TensorOperand<'_>,
444        c: TensorOperand<'_>,
445        d: TensorOperand<'_>,
446        op_ac: Operator,
447        compute_descriptor: ComputeDescriptor,
448    ) -> Result<Self> {
449        let context = ctx.as_context_ref();
450        validate_tensor_operand_context(&context, a, "a")?;
451        validate_tensor_operand_context(&context, c, "c")?;
452        validate_tensor_operand_context(&context, d, "d")?;
453        validate_modes(a.desc.rank(), a.modes)?;
454        validate_modes(c.desc.rank(), c.modes)?;
455        validate_modes(d.desc.rank(), d.modes)?;
456        validate_tensor_descriptors_match(c.desc, d.desc, "elementwise binary output")?;
457        validate_mode_names_match(c.modes, d.modes, "elementwise binary output")?;
458
459        let mode_a = modes_to_i32_vec(a.modes);
460        let mode_c = modes_to_i32_vec(c.modes);
461        let mode_d = modes_to_i32_vec(d.modes);
462
463        let mut raw = ptr::null_mut();
464        ctx.bind()?;
465        unsafe {
466            try_ffi!(sys::cutensorCreateElementwiseBinary(
467                ctx.as_raw(),
468                &raw mut raw,
469                a.desc.as_raw(),
470                mode_a.as_ptr(),
471                a.op.into(),
472                c.desc.as_raw(),
473                mode_c.as_ptr(),
474                c.op.into(),
475                d.desc.as_raw(),
476                mode_d.as_ptr(),
477                op_ac.into(),
478                compute_descriptor.into(),
479            ))?;
480        }
481
482        Self::from_raw(
483            raw,
484            context,
485            d.desc.rank(),
486            d.desc.data_type(),
487            OperationKind::ElementwiseBinary {
488                a: a.desc.data_type(),
489                c: c.desc.data_type(),
490                d: d.desc.data_type(),
491            },
492        )
493    }
494
495    /// Creates an operation descriptor for a tensor permutation.
496    ///
497    /// The tensor permutation has the following general form:
498    ///
499    /// $$ B\_{\Pi^B(i\_0,i\_1,...,i\_n)} = \alpha op\_A(A\_{\Pi^A(i\_0,i\_1,...,i\_n)}) $$
500    ///
501    /// Consequently, tensor permutation is an out-of-place operation and a specialization of [`OperationDescriptor::elementwise_binary`].
502    ///
503    /// Where:
504    ///
505    /// * `A` and `B` are multi-mode tensors of arbitrary data types.
506    /// * $\Pi^A$ and $\Pi^B$ permute the modes of `A` and `B`, respectively.
507    /// * $op\_A$ is a unary element-wise operator such as identity, square, or conjugate.
508    ///
509    /// Broadcasting can be achieved by omitting that mode from the respective tensor.
510    ///
511    /// Modes may appear in any order.
512    /// The only **restrictions** are:
513    ///
514    /// * modes that appear in A *must* also appear in the output tensor.
515    /// * each mode may appear in each tensor at most once.
516    ///
517    /// Supported data-type combinations are:
518    ///
519    /// | A type | B type | compute descriptor |
520    /// | --- | --- | --- |
521    /// | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f16`] |
522    /// | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
523    /// | [`DataType::F16`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] |
524    /// | [`DataType::F32`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
525    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::bf16`] |
526    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] |
527    /// | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] |
528    /// | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] |
529    /// | [`DataType::F32`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] |
530    /// | [`DataType::F64`] | [`DataType::F32`] | [`ComputeDescriptor::f64`] |
531    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] |
532    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] |
533    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] |
534    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f64`] |
535    ///
536    /// This may call asynchronous CUDA functions. cuTENSOR treats descriptor
537    /// creation as thread-safe but not reentrant.
538    ///
539    /// # Errors
540    ///
541    /// Returns an error if the context cannot be bound, tensor modes do not
542    /// match descriptor ranks, a requested output mode is invalid, a data type
543    /// or operator combination is unsupported, cuTENSOR rejects the operands,
544    /// or cuTENSOR returns a null operation descriptor.
545    pub fn permutation(
546        ctx: &Context,
547        a: TensorOperand<'_>,
548        b: TensorOperand<'_>,
549        compute_descriptor: ComputeDescriptor,
550    ) -> Result<Self> {
551        let context = ctx.as_context_ref();
552        validate_tensor_operand_context(&context, a, "a")?;
553        validate_tensor_operand_context(&context, b, "b")?;
554        validate_modes(a.desc.rank(), a.modes)?;
555        validate_modes(b.desc.rank(), b.modes)?;
556
557        let mode_a = modes_to_i32_vec(a.modes);
558        let mode_b = modes_to_i32_vec(b.modes);
559
560        let mut raw = ptr::null_mut();
561        ctx.bind()?;
562        unsafe {
563            try_ffi!(sys::cutensorCreatePermutation(
564                ctx.as_raw(),
565                &raw mut raw,
566                a.desc.as_raw(),
567                mode_a.as_ptr(),
568                a.op.into(),
569                b.desc.as_raw(),
570                mode_b.as_ptr(),
571                compute_descriptor.into(),
572            ))?;
573        }
574
575        Self::from_raw(
576            raw,
577            context,
578            b.desc.rank(),
579            b.desc.data_type(),
580            OperationKind::Permutation {
581                a: a.desc.data_type(),
582                b: b.desc.data_type(),
583            },
584        )
585    }
586
587    /// Creates an operation descriptor for a tensor contraction of the form $D = \alpha \mathcal{A} \mathcal{B} + \beta \mathcal{C}$.
588    ///
589    /// The descriptor represents a tensor contraction of the form:
590    ///
591    /// $$ \mathcal{D}\_{{modes}\_\mathcal{D}} \gets \alpha op\_\mathcal{A}(\mathcal{A}\_{{modes}\_\mathcal{A}}) op\_\mathcal{B}(B\_{{modes}\_\mathcal{B}}) + \beta op\_\mathcal{C}(\mathcal{C}\_{{modes}\_\mathcal{C}}). $$
592    /// Use [`Plan::create`](crate::plan::Plan::create) to select a kernel, then call [`Plan::contract`](crate::plan::Plan::contract) to execute the contraction.
593    ///
594    /// The returned descriptor frees the cuTENSOR operation descriptor when dropped.
595    ///
596    /// Supported data-type combinations are:
597    ///
598    /// | A type | B type | C type | compute descriptor | scalar type | Tensor Core |
599    /// | --- | --- | --- | --- | --- | --- |
600    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | Volta+ |
601    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | Ampere+ |
602    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | No |
603    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::tf32`] | [`DataType::F32`] | Ampere+ |
604    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::tf32x3`] | [`DataType::F32`] | Ampere+ |
605    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::bf16`] | [`DataType::F32`] | Ampere+ |
606    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f16`] | [`DataType::F32`] | Volta+ |
607    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] | [`DataType::F64`] | Ampere+ |
608    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f32`] | [`DataType::F64`] | No |
609    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] | [`DataType::ComplexF32`] | No |
610    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::tf32`] | [`DataType::ComplexF32`] | Ampere+ |
611    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::tf32x3`] | [`DataType::ComplexF32`] | Ampere+ |
612    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] | [`DataType::ComplexF64`] | Ampere+ |
613    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f32`] | [`DataType::ComplexF64`] | No |
614    /// | [`DataType::F64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] | [`DataType::ComplexF64`] | No |
615    /// | [`DataType::ComplexF64`] | [`DataType::F64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] | [`DataType::ComplexF64`] | No |
616    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::i8x8`] | [`DataType::F64`] | Hopper+ |
617    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::i8x8`] | [`DataType::ComplexF64`] | Hopper+ |
618    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::bf16x9`] | [`DataType::F32`] | Hopper+ |
619    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::bf16x9`] | [`DataType::ComplexF32`] | Hopper+ |
620    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f16x4`] | [`DataType::F32`] | Blackwell+ |
621    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f16x4`] | [`DataType::ComplexF32`] | Blackwell+ |
622    ///
623    /// # Errors
624    ///
625    /// Returns an error if the context cannot be bound, tensor modes do not
626    /// match descriptor ranks, input and output descriptors or mode sets are
627    /// incompatible, a data type or operator combination is unsupported,
628    /// cuTENSOR rejects the operands, or cuTENSOR returns a null operation
629    /// descriptor.
630    pub fn contraction(
631        ctx: &Context,
632        a: TensorOperand<'_>,
633        b: TensorOperand<'_>,
634        c: TensorOperand<'_>,
635        d: TensorOperand<'_>,
636        compute_descriptor: ComputeDescriptor,
637    ) -> Result<Self> {
638        let context = ctx.as_context_ref();
639        validate_tensor_operand_context(&context, a, "a")?;
640        validate_tensor_operand_context(&context, b, "b")?;
641        validate_tensor_operand_context(&context, c, "c")?;
642        validate_tensor_operand_context(&context, d, "d")?;
643        validate_modes(a.desc.rank(), a.modes)?;
644        validate_modes(b.desc.rank(), b.modes)?;
645        validate_modes(c.desc.rank(), c.modes)?;
646        validate_modes(d.desc.rank(), d.modes)?;
647        validate_tensor_descriptors_match(c.desc, d.desc, "contraction output")?;
648        validate_mode_names_match(c.modes, d.modes, "contraction output")?;
649
650        let mode_a = modes_to_i32_vec(a.modes);
651        let mode_b = modes_to_i32_vec(b.modes);
652        let mode_c = modes_to_i32_vec(c.modes);
653        let mode_d = modes_to_i32_vec(d.modes);
654
655        let mut raw = ptr::null_mut();
656        ctx.bind()?;
657        unsafe {
658            try_ffi!(sys::cutensorCreateContraction(
659                ctx.as_raw(),
660                &raw mut raw,
661                a.desc.as_raw(),
662                mode_a.as_ptr(),
663                a.op.into(),
664                b.desc.as_raw(),
665                mode_b.as_ptr(),
666                b.op.into(),
667                c.desc.as_raw(),
668                mode_c.as_ptr(),
669                c.op.into(),
670                d.desc.as_raw(),
671                mode_d.as_ptr(),
672                compute_descriptor.into(),
673            ))?;
674        }
675
676        Self::from_raw(
677            raw,
678            context,
679            d.desc.rank(),
680            d.desc.data_type(),
681            OperationKind::Contraction {
682                a: a.desc.data_type(),
683                b: b.desc.data_type(),
684                c: c.desc.data_type(),
685                d: d.desc.data_type(),
686            },
687        )
688    }
689
690    /// Creates an operation descriptor for a tensor reduction of the form $D = \alpha \cdot op\_reduce(op\_A(A)) + \beta \cdot op\_C(C)$.
691    ///
692    /// For example, this can reduce an entire tensor to a scalar: `C[] = alpha * A[i,j,k]`.
693    ///
694    /// Can also perform partial reductions; for instance,
695    /// `C[i,j] = alpha * A[k,j,i]`.
696    /// In this case only elements along the `k` mode are contracted.
697    ///
698    /// The binary `op_reduce` operator controls the kind of reduction that is performed.
699    /// For instance, setting `op_reduce` to [`Operator::Add`] reduces elements of A via a summation while [`Operator::Max`] would find the largest element in A.
700    ///
701    /// Supported data-type combinations are:
702    ///
703    /// | A type | B type | C type | compute type |
704    /// | --- | --- | --- | --- |
705    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f16`] |
706    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] |
707    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::bf16`] |
708    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] |
709    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] |
710    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] |
711    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] |
712    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] |
713    ///
714    /// # Errors
715    ///
716    /// Returns an error if the context cannot be bound, tensor modes do not
717    /// match descriptor ranks, input and output descriptors or mode sets are
718    /// incompatible, a data type or reduction operator combination is
719    /// unsupported, cuTENSOR rejects the operands, or cuTENSOR returns a null
720    /// operation descriptor.
721    pub fn reduction(
722        ctx: &Context,
723        a: TensorOperand<'_>,
724        c: TensorOperand<'_>,
725        d: TensorOperand<'_>,
726        op_reduce: Operator,
727        compute_descriptor: ComputeDescriptor,
728    ) -> Result<Self> {
729        let context = ctx.as_context_ref();
730        validate_tensor_operand_context(&context, a, "a")?;
731        validate_tensor_operand_context(&context, c, "c")?;
732        validate_tensor_operand_context(&context, d, "d")?;
733        validate_modes(a.desc.rank(), a.modes)?;
734        validate_modes(c.desc.rank(), c.modes)?;
735        validate_modes(d.desc.rank(), d.modes)?;
736        validate_tensor_descriptors_match(c.desc, d.desc, "reduction output")?;
737        validate_mode_names_match(c.modes, d.modes, "reduction output")?;
738
739        let mode_a = modes_to_i32_vec(a.modes);
740        let mode_c = modes_to_i32_vec(c.modes);
741        let mode_d = modes_to_i32_vec(d.modes);
742
743        let mut raw = ptr::null_mut();
744        ctx.bind()?;
745        unsafe {
746            try_ffi!(sys::cutensorCreateReduction(
747                ctx.as_raw(),
748                &raw mut raw,
749                a.desc.as_raw(),
750                mode_a.as_ptr(),
751                a.op.into(),
752                c.desc.as_raw(),
753                mode_c.as_ptr(),
754                c.op.into(),
755                d.desc.as_raw(),
756                mode_d.as_ptr(),
757                op_reduce.into(),
758                compute_descriptor.into(),
759            ))?;
760        }
761
762        Self::from_raw(
763            raw,
764            context,
765            d.desc.rank(),
766            d.desc.data_type(),
767            OperationKind::Reduction {
768                a: a.desc.data_type(),
769                c: c.desc.data_type(),
770                d: d.desc.data_type(),
771            },
772        )
773    }
774
775    /// Creates an operation descriptor for a tensor contraction of the form $\mathcal{E} = \alpha \mathcal{A} \mathcal{B} \mathcal{C} + \beta \mathcal{D}$.
776    ///
777    /// The descriptor represents a tensor contraction of the form:
778    ///
779    /// $$ \mathcal{E}\_{{modes}\_\mathcal{E}} \gets \alpha op\_\mathcal{A}(\mathcal{A}\_{{modes}\_\mathcal{A}}) op\_\mathcal{B}(\mathcal{B}\_{{modes}\_\mathcal{B}}) op\_\mathcal{C}(\mathcal{C}\_{{modes}\_\mathcal{C}}) + \beta op\_\mathcal{D}(\mathcal{D}\_{{modes}\_\mathcal{D}}). $$
780    /// Use [`Plan::create`](crate::plan::Plan::create) to select a kernel, then call [`Plan::contract_trinary`](crate::plan::Plan::contract_trinary) to execute the contraction.
781    ///
782    /// The returned descriptor frees the cuTENSOR operation descriptor when dropped.
783    ///
784    /// Performance improvements are currently especially high for host-resident
785    /// data, also called out-of-core data, on Grace-based systems.
786    ///
787    /// Supported data-type combinations are:
788    ///
789    /// | A type | B type | C type | D type | compute descriptor | scalar type | Tensor Core |
790    /// | --- | --- | --- | --- | --- | --- | --- |
791    /// | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`DataType::F16`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | Volta+ |
792    /// | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`DataType::Bf16`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | Ampere+ |
793    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] | No |
794    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::tf32`] | [`DataType::F32`] | Ampere+ |
795    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::tf32x3`] | [`DataType::F32`] | Ampere+ |
796    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::bf16`] | [`DataType::F32`] | Ampere+ |
797    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f16`] | [`DataType::F32`] | Volta+ |
798    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] | [`DataType::F64`] | Ampere+ |
799    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f32`] | [`DataType::F64`] | No |
800    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] | [`DataType::ComplexF32`] | No |
801    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::tf32`] | [`DataType::ComplexF32`] | Ampere+ |
802    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::tf32x3`] | [`DataType::ComplexF32`] | Ampere+ |
803    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] | [`DataType::ComplexF64`] | Ampere+ |
804    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f32`] | [`DataType::ComplexF64`] | No |
805    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::i8x8`] | [`DataType::F64`] | Hopper+ |
806    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::i8x8`] | [`DataType::ComplexF64`] | Hopper+ |
807    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::bf16x9`] | [`DataType::F32`] | Hopper+ |
808    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::bf16x9`] | [`DataType::ComplexF32`] | Hopper+ |
809    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f16x4`] | [`DataType::F32`] | Blackwell+ |
810    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f16x4`] | [`DataType::ComplexF32`] | Blackwell+ |
811    ///
812    /// # Errors
813    ///
814    /// Returns an error if the context cannot be bound, tensor modes do not
815    /// match descriptor ranks, input and output descriptors or mode sets are
816    /// incompatible, a data type or operator combination is unsupported,
817    /// cuTENSOR rejects the operands, or cuTENSOR returns a null operation
818    /// descriptor.
819    pub fn contraction_trinary(
820        ctx: &Context,
821        lhs: TensorOperand<'_>,
822        rhs: TensorOperand<'_>,
823        aux: TensorOperand<'_>,
824        input: TensorOperand<'_>,
825        output: TensorOperand<'_>,
826        compute_descriptor: ComputeDescriptor,
827    ) -> Result<Self> {
828        let context = ctx.as_context_ref();
829        validate_tensor_operand_context(&context, lhs, "lhs")?;
830        validate_tensor_operand_context(&context, rhs, "rhs")?;
831        validate_tensor_operand_context(&context, aux, "aux")?;
832        validate_tensor_operand_context(&context, input, "input")?;
833        validate_tensor_operand_context(&context, output, "output")?;
834        validate_modes(lhs.desc.rank(), lhs.modes)?;
835        validate_modes(rhs.desc.rank(), rhs.modes)?;
836        validate_modes(aux.desc.rank(), aux.modes)?;
837        validate_modes(input.desc.rank(), input.modes)?;
838        validate_modes(output.desc.rank(), output.modes)?;
839        validate_tensor_descriptors_match(input.desc, output.desc, "trinary contraction output")?;
840        validate_mode_names_match(input.modes, output.modes, "trinary contraction output")?;
841
842        let mode_a = modes_to_i32_vec(lhs.modes);
843        let mode_b = modes_to_i32_vec(rhs.modes);
844        let mode_c = modes_to_i32_vec(aux.modes);
845        let mode_d = modes_to_i32_vec(input.modes);
846        let mode_e = modes_to_i32_vec(output.modes);
847
848        let mut raw = ptr::null_mut();
849        ctx.bind()?;
850        unsafe {
851            try_ffi!(sys::cutensorCreateContractionTrinary(
852                ctx.as_raw(),
853                &raw mut raw,
854                lhs.desc.as_raw(),
855                mode_a.as_ptr(),
856                lhs.op.into(),
857                rhs.desc.as_raw(),
858                mode_b.as_ptr(),
859                rhs.op.into(),
860                aux.desc.as_raw(),
861                mode_c.as_ptr(),
862                aux.op.into(),
863                input.desc.as_raw(),
864                mode_d.as_ptr(),
865                input.op.into(),
866                output.desc.as_raw(),
867                mode_e.as_ptr(),
868                compute_descriptor.into(),
869            ))?;
870        }
871
872        Self::from_raw(
873            raw,
874            context,
875            output.desc.rank(),
876            output.desc.data_type(),
877            OperationKind::ContractionTrinary {
878                a: lhs.desc.data_type(),
879                b: rhs.desc.data_type(),
880                c: aux.desc.data_type(),
881                d: input.desc.data_type(),
882                e: output.desc.data_type(),
883            },
884        )
885    }
886
887    /// Creates an operation descriptor for a block-sparse tensor contraction of the form $D = \alpha \mathcal{A} \mathcal{B} + \beta \mathcal{C}$.
888    ///
889    /// The descriptor represents a block-sparse tensor contraction of the form:
890    ///
891    /// $$ \mathcal{D}\_{{modes}\_\mathcal{D}} \gets \alpha op\_\mathcal{A}(\mathcal{A}\_{{modes}\_\mathcal{A}}) op\_\mathcal{B}(B\_{{modes}\_\mathcal{B}}) + \beta op\_\mathcal{C}(\mathcal{C}\_{{modes}\_\mathcal{C}}). $$
892    ///
893    /// Only the predefined non-zero blocks of $\mathcal{D}$ that were specified in [`BlockSparseTensorDescriptor::create`] are actually computed.
894    /// The other blocks are omitted, even if the true result of the contraction would be non-zero.
895    /// Conversely, if a predefined non-zero block of $\mathcal{D}$ is present but the result of the contraction is zero for this block, explicit zeros are stored.
896    ///
897    /// Currently, the data-types for the tensors `A`, `B`, `C`, and `D`, as well as the scalars $\alpha$ and $\beta$ must all be identical, and the only supported types are [`DataType::ComplexF64`], [`DataType::ComplexF32`], [`DataType::F64`], and [`DataType::F32`].
898    /// The compute type must match as well; currently supported combinations are:
899    ///
900    /// | A type | B type | C type | D type | compute descriptor | scalar type |
901    /// | --- | --- | --- | --- | --- | --- |
902    /// | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`DataType::F32`] | [`ComputeDescriptor::f32`] | [`DataType::F32`] |
903    /// | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`DataType::F64`] | [`ComputeDescriptor::f64`] | [`DataType::F64`] |
904    /// | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`DataType::ComplexF32`] | [`ComputeDescriptor::f32`] | [`DataType::ComplexF32`] |
905    /// | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`DataType::ComplexF64`] | [`ComputeDescriptor::f64`] | [`DataType::ComplexF64`] |
906    ///
907    /// For every mode, the segmentation of that mode must be identical in all tensors that it occurs in.
908    /// For example, if mode `i` of tensor `A` matches mode `j` of tensor `B`, then the section count for mode `i` in `A` must match the section count for mode `j` in `B`, and the corresponding section extents must be identical.
909    ///
910    /// For example, let A, B, and C be block matrices and consider the ordinary matrix-matrix product $C\_{mn}=A\_{mk}B\_{kn}$.
911    /// Then:
912    ///
913    /// * Mode ‘m’: C and A must have the same number of block-rows, and each block-row of C must contain the same number of rows as the corresponding block-row of A.
914    /// * Mode ‘n’: C and B must have the same number of block-columns of matching size.
915    /// * Mode ‘k’: A must have the same number of block-columns as B has block-rows, and each block-column of A must contain the same number of columns as the number of rows in the corresponding block-row of B.
916    ///
917    /// `input` and `output` must use identical descriptors: the same opaque pointer must be passed, and the layouts of the input and output tensors must be identical.
918    ///
919    /// See [`sys::cutensorCreatePlan`] to create the plan, [`Plan::estimate_workspace_size`](crate::plan::Plan::estimate_workspace_size) to compute the required workspace, and finally [`Plan::block_sparse_contract`](crate::plan::Plan::block_sparse_contract) to perform the actual contraction.
920    ///
921    /// The returned descriptor frees the cuTENSOR operation descriptor when dropped.
922    ///
923    /// # Errors
924    ///
925    /// Returns an error if the context cannot be bound, block-sparse tensor
926    /// modes do not match descriptor ranks, input and output descriptors or
927    /// mode sets are incompatible, section sizes are invalid, a data type or
928    /// operator combination is unsupported, cuTENSOR rejects the operands, or
929    /// cuTENSOR returns a null operation descriptor.
930    pub fn block_sparse_contraction(
931        ctx: &Context,
932        a: BlockSparseTensorOperand<'_>,
933        b: BlockSparseTensorOperand<'_>,
934        c: BlockSparseTensorOperand<'_>,
935        d: BlockSparseTensorOperand<'_>,
936        compute_descriptor: ComputeDescriptor,
937    ) -> Result<Self> {
938        let context = ctx.as_context_ref();
939        validate_block_sparse_tensor_operand_context(&context, a, "a")?;
940        validate_block_sparse_tensor_operand_context(&context, b, "b")?;
941        validate_block_sparse_tensor_operand_context(&context, c, "c")?;
942        validate_block_sparse_tensor_operand_context(&context, d, "d")?;
943        validate_modes(a.desc.mode_count(), a.modes)?;
944        validate_modes(b.desc.mode_count(), b.modes)?;
945        validate_modes(c.desc.mode_count(), c.modes)?;
946        validate_modes(d.desc.mode_count(), d.modes)?;
947        validate_block_sparse_tensor_descriptors_match(c.desc, d.desc, "block sparse output")?;
948        validate_mode_names_match(c.modes, d.modes, "block sparse output")?;
949
950        let mode_a = modes_to_i32_vec(a.modes);
951        let mode_b = modes_to_i32_vec(b.modes);
952        let mode_c = modes_to_i32_vec(c.modes);
953        let mode_d = modes_to_i32_vec(d.modes);
954
955        let mut raw = ptr::null_mut();
956        ctx.bind()?;
957        unsafe {
958            try_ffi!(sys::cutensorCreateBlockSparseContraction(
959                ctx.as_raw(),
960                &raw mut raw,
961                a.desc.as_raw(),
962                mode_a.as_ptr(),
963                a.op.into(),
964                b.desc.as_raw(),
965                mode_b.as_ptr(),
966                b.op.into(),
967                c.desc.as_raw(),
968                mode_c.as_ptr(),
969                c.op.into(),
970                d.desc.as_raw(),
971                mode_d.as_ptr(),
972                compute_descriptor.into(),
973            ))?;
974        }
975
976        Self::from_raw(
977            raw,
978            context,
979            d.desc.mode_count(),
980            d.desc.data_type(),
981            OperationKind::BlockSparseContraction {
982                a_non_zero_blocks: a.desc.non_zero_block_count(),
983                b_non_zero_blocks: b.desc.non_zero_block_count(),
984                c_non_zero_blocks: c.desc.non_zero_block_count(),
985                d_non_zero_blocks: d.desc.non_zero_block_count(),
986            },
987        )
988    }
989
990    fn set_attribute<T>(&mut self, attr: OperationDescriptorAttribute, value: &T) -> Result<()> {
991        validate_operation_attribute_size(attr, size_of::<T>())?;
992
993        self.context.bind()?;
994        unsafe {
995            try_ffi!(sys::cutensorOperationDescriptorSetAttribute(
996                self.context.as_raw(),
997                self.handle,
998                attr.into(),
999                ptr::from_ref(value).cast(),
1000                size_of::<T>() as _,
1001            ))?;
1002        }
1003        Ok(())
1004    }
1005
1006    fn attribute<T>(&self, attr: OperationDescriptorAttribute) -> Result<T> {
1007        validate_operation_attribute_size(attr, size_of::<T>())?;
1008
1009        let mut value = MaybeUninit::<T>::uninit();
1010        self.context.bind()?;
1011        unsafe {
1012            try_ffi!(sys::cutensorOperationDescriptorGetAttribute(
1013                self.context.as_raw(),
1014                self.handle,
1015                attr.into(),
1016                value.as_mut_ptr().cast(),
1017                size_of::<T>() as _,
1018            ))?;
1019            Ok(value.assume_init())
1020        }
1021    }
1022
1023    pub fn tag(&self) -> Result<i32> {
1024        let value = self.attribute(OperationDescriptorAttribute::Tag)?;
1025        Ok(value)
1026    }
1027
1028    pub fn set_tag(&mut self, tag: i32) -> Result<()> {
1029        self.set_attribute(OperationDescriptorAttribute::Tag, &tag)
1030    }
1031
1032    pub fn scalar_type(&self) -> Result<DataType> {
1033        self.attribute(OperationDescriptorAttribute::ScalarType)
1034    }
1035
1036    pub fn set_scalar_type(&mut self, data_type: DataType) -> Result<()> {
1037        self.set_attribute(OperationDescriptorAttribute::ScalarType, &data_type)?;
1038        self.signature.scalar_type = data_type;
1039        Ok(())
1040    }
1041
1042    pub fn padding_left(&self) -> Result<Vec<u32>> {
1043        self.padding(OperationDescriptorAttribute::PaddingLeft)
1044    }
1045
1046    pub fn set_padding_left(&mut self, padding: &[u32]) -> Result<()> {
1047        self.set_padding(OperationDescriptorAttribute::PaddingLeft, padding)
1048    }
1049
1050    pub fn padding_right(&self) -> Result<Vec<u32>> {
1051        self.padding(OperationDescriptorAttribute::PaddingRight)
1052    }
1053
1054    pub fn set_padding_right(&mut self, padding: &[u32]) -> Result<()> {
1055        self.set_padding(OperationDescriptorAttribute::PaddingRight, padding)
1056    }
1057
1058    pub fn set_padding_value<T: DataTypeLike>(&mut self, value: T) -> Result<()> {
1059        if self.output_data_type != T::data_type() {
1060            return Err(Error::ScalarDataTypeMismatch {
1061                descriptor: self.output_data_type,
1062                memory: T::data_type(),
1063            });
1064        }
1065
1066        let owned = OwnedPaddingValue::from_value(value)?;
1067        self.context.bind()?;
1068        unsafe {
1069            try_ffi!(sys::cutensorOperationDescriptorSetAttribute(
1070                self.context.as_raw(),
1071                self.handle,
1072                OperationDescriptorAttribute::PaddingValue.into(),
1073                owned.host_ptr().cast(),
1074                owned.size_bytes() as _,
1075            ))?;
1076        }
1077        self.padding_value = Some(owned);
1078        Ok(())
1079    }
1080
1081    pub fn flops(&self) -> Result<f32> {
1082        self.attribute(OperationDescriptorAttribute::Flops)
1083    }
1084
1085    pub fn moved_bytes(&self) -> Result<f32> {
1086        self.attribute(OperationDescriptorAttribute::MovedBytes)
1087    }
1088
1089    pub(crate) fn signature(&self) -> OperationSignature {
1090        self.signature
1091    }
1092
1093    pub const fn as_raw(&self) -> sys::cutensorOperationDescriptor_t {
1094        self.handle
1095    }
1096
1097    /// Consumes the descriptor and returns the raw cuTENSOR operation
1098    /// descriptor handle without destroying it.
1099    ///
1100    /// The caller becomes responsible for eventually destroying the returned
1101    /// handle with cuTENSOR.
1102    pub fn into_raw(self) -> sys::cutensorOperationDescriptor_t {
1103        let descriptor = ManuallyDrop::new(self);
1104        descriptor.handle
1105    }
1106
1107    fn from_raw(
1108        handle: sys::cutensorOperationDescriptor_t,
1109        ctx: ContextRef,
1110        output_rank: u32,
1111        output_data_type: DataType,
1112        kind: OperationKind,
1113    ) -> Result<Self> {
1114        if handle.is_null() {
1115            return Err(Error::NullHandle);
1116        }
1117
1118        let scalar_type = {
1119            let mut value = MaybeUninit::<DataType>::uninit();
1120            ctx.bind()?;
1121            unsafe {
1122                try_ffi!(sys::cutensorOperationDescriptorGetAttribute(
1123                    ctx.as_raw(),
1124                    handle,
1125                    OperationDescriptorAttribute::ScalarType.into(),
1126                    value.as_mut_ptr().cast(),
1127                    size_of::<DataType>() as _,
1128                ))?;
1129                value.assume_init()
1130            }
1131        };
1132
1133        Ok(Self {
1134            handle,
1135            context: ctx,
1136            output_rank,
1137            output_data_type,
1138            signature: OperationSignature { kind, scalar_type },
1139            padding_value: None,
1140        })
1141    }
1142
1143    fn padding(&self, attr: OperationDescriptorAttribute) -> Result<Vec<u32>> {
1144        debug_assert!(matches!(
1145            attr,
1146            OperationDescriptorAttribute::PaddingLeft | OperationDescriptorAttribute::PaddingRight
1147        ));
1148
1149        let mut padding = vec![0_u32; self.output_rank as usize];
1150        self.context.bind()?;
1151        unsafe {
1152            try_ffi!(sys::cutensorOperationDescriptorGetAttribute(
1153                self.context.as_raw(),
1154                self.handle,
1155                attr.into(),
1156                padding.as_mut_ptr().cast(),
1157                size_of_val(padding.as_slice()) as _,
1158            ))?;
1159        }
1160
1161        Ok(padding)
1162    }
1163
1164    fn set_padding(&mut self, attr: OperationDescriptorAttribute, padding: &[u32]) -> Result<()> {
1165        debug_assert!(matches!(
1166            attr,
1167            OperationDescriptorAttribute::PaddingLeft | OperationDescriptorAttribute::PaddingRight
1168        ));
1169
1170        if padding.len() != self.output_rank as usize {
1171            return Err(Error::LengthMismatch {
1172                name: "padding".into(),
1173                expected: self.output_rank as usize,
1174                actual: padding.len(),
1175            });
1176        }
1177        for &value in padding {
1178            to_i32(value, "padding")?;
1179        }
1180
1181        let padding = padding.to_vec();
1182        self.context.bind()?;
1183        unsafe {
1184            try_ffi!(sys::cutensorOperationDescriptorSetAttribute(
1185                self.context.as_raw(),
1186                self.handle,
1187                attr.into(),
1188                padding.as_ptr().cast(),
1189                size_of_val(padding.as_slice()) as _,
1190            ))?;
1191        }
1192        Ok(())
1193    }
1194}
1195
1196impl Drop for OperationDescriptor {
1197    fn drop(&mut self) {
1198        if let Err(err) = self.context.bind() {
1199            #[cfg(debug_assertions)]
1200            eprintln!(
1201                "failed to bind cutensor context before destroying operation descriptor: {err}"
1202            );
1203        }
1204
1205        unsafe {
1206            if let Err(err) = try_ffi!(sys::cutensorDestroyOperationDescriptor(self.handle)) {
1207                #[cfg(debug_assertions)]
1208                eprintln!("failed to destroy cutensor operation descriptor: {err}");
1209            }
1210        }
1211    }
1212}
1213
1214fn validate_modes(rank: u32, modes: &[Mode]) -> Result<()> {
1215    if rank as usize != modes.len() {
1216        return Err(Error::TensorModeMismatch {
1217            rank,
1218            mode_length: modes.len(),
1219        });
1220    }
1221
1222    Ok(())
1223}
1224
1225fn validate_tensor_operand_context(
1226    expected: &ContextRef,
1227    operand: TensorOperand<'_>,
1228    name: &str,
1229) -> Result<()> {
1230    validate_same_context(expected, operand.desc.context(), name)
1231}
1232
1233fn validate_block_sparse_tensor_operand_context(
1234    expected: &ContextRef,
1235    operand: BlockSparseTensorOperand<'_>,
1236    name: &str,
1237) -> Result<()> {
1238    validate_same_context(expected, operand.desc.context(), name)
1239}
1240
1241fn validate_mode_names_match(lhs: &[Mode], rhs: &[Mode], name: &str) -> Result<()> {
1242    if lhs != rhs {
1243        return Err(Error::ModeMismatch { name: name.into() });
1244    }
1245
1246    Ok(())
1247}
1248
1249fn validate_tensor_descriptors_match(
1250    lhs: &TensorDescriptor,
1251    rhs: &TensorDescriptor,
1252    name: &str,
1253) -> Result<()> {
1254    if lhs.shape() != rhs.shape()
1255        || lhs.strides() != rhs.strides()
1256        || lhs.data_type() != rhs.data_type()
1257        || lhs.alignment_requirement() != rhs.alignment_requirement()
1258    {
1259        return Err(Error::DescriptorMismatch { name: name.into() });
1260    }
1261
1262    Ok(())
1263}
1264
1265fn validate_block_sparse_tensor_descriptors_match(
1266    lhs: &BlockSparseTensorDescriptor,
1267    rhs: &BlockSparseTensorDescriptor,
1268    name: &str,
1269) -> Result<()> {
1270    if lhs.mode_count() != rhs.mode_count()
1271        || lhs.non_zero_block_count() != rhs.non_zero_block_count()
1272        || lhs.data_type() != rhs.data_type()
1273    {
1274        return Err(Error::DescriptorMismatch { name: name.into() });
1275    }
1276
1277    Ok(())
1278}
1279
1280fn validate_operation_attribute_size(
1281    attr: OperationDescriptorAttribute,
1282    actual: usize,
1283) -> Result<()> {
1284    let expected = match attr {
1285        OperationDescriptorAttribute::Tag => size_of::<i32>(),
1286        OperationDescriptorAttribute::ScalarType => size_of::<sys::cutensorDataType_t>(),
1287        OperationDescriptorAttribute::Flops | OperationDescriptorAttribute::MovedBytes => {
1288            size_of::<f32>()
1289        }
1290        OperationDescriptorAttribute::PaddingLeft | OperationDescriptorAttribute::PaddingRight => {
1291            return Ok(());
1292        }
1293        OperationDescriptorAttribute::PaddingValue => return Ok(()),
1294    };
1295
1296    if actual != expected {
1297        return Err(Error::OperationDescriptorInvalidAttributeSize {
1298            attr,
1299            expected,
1300            actual,
1301        });
1302    }
1303
1304    Ok(())
1305}