Skip to main content

singe_cutensor/
operation.rs

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