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}