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}