1use std::{mem::ManuallyDrop, ptr};
2
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4use singe_core::impl_enum_conversion;
5use singe_cuda::stream::StreamBinding;
6use singe_cutensor_sys as sys;
7
8use crate::{
9 error::{Error, Result},
10 mg::{
11 context::{Context, ContextRef, validate_same_context},
12 copy::validate_count,
13 memory::{DistributedDeviceMemory, WorkspaceMemory},
14 tensor::TensorDescriptor,
15 types::Algorithm,
16 workspace::Workspace,
17 },
18 try_ffi,
19 types::{ComputeType, Mode, WorkspacePreference},
20 utility::modes_to_i32_vec,
21};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
24#[repr(u32)]
25#[non_exhaustive]
26pub enum ContractionFindAttribute {
27 Max = sys::cutensorMgContractionFindAttribute_t::CUTENSORMG_CONTRACTION_FIND_ATTRIBUTE_MAX as _,
28}
29
30impl_enum_conversion!(
31 sys::cutensorMgContractionFindAttribute_t,
32 ContractionFindAttribute
33);
34
35#[derive(Debug)]
36pub struct ContractionFind {
37 handle: sys::cutensorMgContractionFind_t,
38 context: ContextRef,
39 algorithm: Algorithm,
40}
41
42#[derive(Debug)]
43pub struct ContractionDescriptor {
44 handle: sys::cutensorMgContractionDescriptor_t,
45 context: ContextRef,
46 a_pointer_count: usize,
47 b_pointer_count: usize,
48 c_pointer_count: usize,
49 d_pointer_count: usize,
50}
51
52#[derive(Debug)]
53pub struct ContractionPlan {
54 handle: sys::cutensorMgContractionPlan_t,
55 context: ContextRef,
56 a_pointer_count: usize,
57 b_pointer_count: usize,
58 c_pointer_count: usize,
59 d_pointer_count: usize,
60 workspace: Workspace,
61}
62
63impl ContractionFind {
64 pub fn create(context: &Context, algorithm: Algorithm) -> Result<Self> {
65 let mut handle = ptr::null_mut();
66 unsafe {
67 try_ffi!(sys::cutensorMgCreateContractionFind(
68 context.as_raw(),
69 &raw mut handle,
70 algorithm.into(),
71 ))?;
72 }
73
74 if handle.is_null() {
75 return Err(Error::NullHandle);
76 }
77
78 Ok(Self {
79 handle,
80 context: context.as_context_ref(),
81 algorithm,
82 })
83 }
84
85 pub fn create_default(context: &Context) -> Result<Self> {
86 Self::create(context, Algorithm::Default)
87 }
88
89 pub unsafe fn from_raw(
97 handle: sys::cutensorMgContractionFind_t,
98 context: &Context,
99 algorithm: Algorithm,
100 ) -> Result<Self> {
101 if handle.is_null() {
102 return Err(Error::NullHandle);
103 }
104
105 Ok(Self {
106 handle,
107 context: context.as_context_ref(),
108 algorithm,
109 })
110 }
111
112 pub fn algorithm(&self) -> Algorithm {
113 self.algorithm
114 }
115
116 pub fn set_attribute<T>(&mut self, attr: ContractionFindAttribute, value: &T) -> Result<()> {
117 unsafe {
118 try_ffi!(sys::cutensorMgContractionFindSetAttribute(
119 self.context.as_raw(),
120 self.handle,
121 attr.into(),
122 ptr::from_ref(value).cast(),
123 size_of::<T>() as i64,
124 ))?;
125 }
126 Ok(())
127 }
128
129 pub(crate) fn context(&self) -> &ContextRef {
130 &self.context
131 }
132
133 pub const fn as_raw(&self) -> sys::cutensorMgContractionFind_t {
134 self.handle
135 }
136
137 pub fn into_raw(self) -> sys::cutensorMgContractionFind_t {
141 let this = ManuallyDrop::new(self);
142 this.handle
143 }
144}
145
146impl ContractionDescriptor {
147 pub fn create(
148 context: &Context,
149 a: &TensorDescriptor,
150 modes_a: &[Mode],
151 b: &TensorDescriptor,
152 modes_b: &[Mode],
153 c: &TensorDescriptor,
154 modes_c: &[Mode],
155 d: &TensorDescriptor,
156 modes_d: &[Mode],
157 compute: ComputeType,
158 ) -> Result<Self> {
159 let context_ref = context.as_context_ref();
160 validate_same_context(&context_ref, a.context(), "a")?;
161 validate_same_context(&context_ref, b.context(), "b")?;
162 validate_same_context(&context_ref, c.context(), "c")?;
163 validate_same_context(&context_ref, d.context(), "d")?;
164 validate_modes(a, modes_a)?;
165 validate_modes(b, modes_b)?;
166 validate_modes(c, modes_c)?;
167 validate_modes(d, modes_d)?;
168
169 let modes_a = modes_to_i32_vec(modes_a);
170 let modes_b = modes_to_i32_vec(modes_b);
171 let modes_c = modes_to_i32_vec(modes_c);
172 let modes_d = modes_to_i32_vec(modes_d);
173 let mut handle = ptr::null_mut();
174 unsafe {
175 try_ffi!(sys::cutensorMgCreateContractionDescriptor(
176 context.as_raw(),
177 &raw mut handle,
178 a.as_raw(),
179 modes_a.as_ptr(),
180 b.as_raw(),
181 modes_b.as_ptr(),
182 c.as_raw(),
183 modes_c.as_ptr(),
184 d.as_raw(),
185 modes_d.as_ptr(),
186 compute.into(),
187 ))?;
188 }
189
190 if handle.is_null() {
191 return Err(Error::NullHandle);
192 }
193
194 Ok(Self {
195 handle,
196 context: context.as_context_ref(),
197 a_pointer_count: a.devices().len(),
198 b_pointer_count: b.devices().len(),
199 c_pointer_count: c.devices().len(),
200 d_pointer_count: d.devices().len(),
201 })
202 }
203
204 pub unsafe fn from_raw(
213 handle: sys::cutensorMgContractionDescriptor_t,
214 context: &Context,
215 a_pointer_count: usize,
216 b_pointer_count: usize,
217 c_pointer_count: usize,
218 d_pointer_count: usize,
219 ) -> Result<Self> {
220 if handle.is_null() {
221 return Err(Error::NullHandle);
222 }
223
224 Ok(Self {
225 handle,
226 context: context.as_context_ref(),
227 a_pointer_count,
228 b_pointer_count,
229 c_pointer_count,
230 d_pointer_count,
231 })
232 }
233
234 pub fn workspace(
235 &self,
236 find: &ContractionFind,
237 preference: WorkspacePreference,
238 ) -> Result<Workspace> {
239 validate_same_context(&self.context, find.context(), "find")?;
240 let mut device_workspace_size = vec![0; self.context.device_count()];
241 let mut host_workspace_size = 0;
242 unsafe {
243 try_ffi!(sys::cutensorMgContractionGetWorkspace(
244 self.context.as_raw(),
245 self.handle,
246 find.as_raw(),
247 preference.into(),
248 device_workspace_size.as_mut_ptr(),
249 &raw mut host_workspace_size,
250 ))?;
251 }
252 Workspace::from_raw(device_workspace_size, host_workspace_size)
253 }
254
255 pub(crate) fn context(&self) -> &ContextRef {
256 &self.context
257 }
258
259 pub const fn as_raw(&self) -> sys::cutensorMgContractionDescriptor_t {
260 self.handle
261 }
262
263 pub fn into_raw(self) -> sys::cutensorMgContractionDescriptor_t {
267 let this = ManuallyDrop::new(self);
268 this.handle
269 }
270}
271
272impl ContractionPlan {
273 pub fn create(
274 context: &Context,
275 desc: &ContractionDescriptor,
276 find: &ContractionFind,
277 workspace: Workspace,
278 ) -> Result<Self> {
279 validate_same_context(&context.as_context_ref(), desc.context(), "desc")?;
280 validate_same_context(desc.context(), find.context(), "find")?;
281 workspace.validate_for_context(desc.context())?;
282
283 let mut handle = ptr::null_mut();
284 unsafe {
285 try_ffi!(sys::cutensorMgCreateContractionPlan(
286 context.as_raw(),
287 &raw mut handle,
288 desc.as_raw(),
289 find.as_raw(),
290 workspace.device_sizes_ptr(),
291 workspace.host_size(),
292 ))?;
293 }
294
295 if handle.is_null() {
296 return Err(Error::NullHandle);
297 }
298
299 Ok(Self {
300 handle,
301 context: context.as_context_ref(),
302 a_pointer_count: desc.a_pointer_count,
303 b_pointer_count: desc.b_pointer_count,
304 c_pointer_count: desc.c_pointer_count,
305 d_pointer_count: desc.d_pointer_count,
306 workspace,
307 })
308 }
309
310 pub unsafe fn from_raw(
319 handle: sys::cutensorMgContractionPlan_t,
320 context: &Context,
321 a_pointer_count: usize,
322 b_pointer_count: usize,
323 c_pointer_count: usize,
324 d_pointer_count: usize,
325 workspace: Workspace,
326 ) -> Result<Self> {
327 if handle.is_null() {
328 return Err(Error::NullHandle);
329 }
330
331 Ok(Self {
332 handle,
333 context: context.as_context_ref(),
334 a_pointer_count,
335 b_pointer_count,
336 c_pointer_count,
337 d_pointer_count,
338 workspace,
339 })
340 }
341
342 pub fn workspace(&self) -> &Workspace {
343 &self.workspace
344 }
345
346 pub fn create_workspace_memory(&self) -> Result<WorkspaceMemory> {
347 WorkspaceMemory::create(&self.context, &self.workspace)
348 }
349
350 pub fn contract<TA, TB, TC, TD, TScalar>(
351 &self,
352 alpha: &TScalar,
353 a: &DistributedDeviceMemory<TA>,
354 b: &DistributedDeviceMemory<TB>,
355 beta: &TScalar,
356 c: &DistributedDeviceMemory<TC>,
357 d: &mut DistributedDeviceMemory<TD>,
358 workspace: &mut WorkspaceMemory,
359 streams: &[StreamBinding],
360 ) -> Result<()> {
361 self.validate_execution_slices(
362 a.pointer_count(),
363 b.pointer_count(),
364 c.pointer_count(),
365 d.pointer_count(),
366 workspace.device_pointer_count(),
367 streams.len(),
368 )?;
369
370 let a = a.const_ptrs();
371 let b = b.const_ptrs();
372 let c = c.const_ptrs();
373 let mut d = d.mut_ptrs();
374 let mut device_workspace = workspace.device_mut_ptrs();
375
376 unsafe {
377 self.contract_raw(
378 ptr::from_ref(alpha).cast(),
379 &a,
380 &b,
381 ptr::from_ref(beta).cast(),
382 &c,
383 &mut d,
384 &mut device_workspace,
385 workspace.host_mut_ptr(),
386 streams,
387 )
388 }
389 }
390
391 pub fn contract_in_place<TA, TB, TC, TScalar>(
392 &self,
393 alpha: &TScalar,
394 a: &DistributedDeviceMemory<TA>,
395 b: &DistributedDeviceMemory<TB>,
396 beta: &TScalar,
397 c_and_d: &mut DistributedDeviceMemory<TC>,
398 workspace: &mut WorkspaceMemory,
399 streams: &[StreamBinding],
400 ) -> Result<()> {
401 self.validate_execution_slices(
402 a.pointer_count(),
403 b.pointer_count(),
404 c_and_d.pointer_count(),
405 c_and_d.pointer_count(),
406 workspace.device_pointer_count(),
407 streams.len(),
408 )?;
409
410 let a = a.const_ptrs();
411 let b = b.const_ptrs();
412 let c = c_and_d.const_ptrs();
413 let mut d = c_and_d.mut_ptrs();
414 let mut device_workspace = workspace.device_mut_ptrs();
415
416 unsafe {
417 self.contract_raw(
418 ptr::from_ref(alpha).cast(),
419 &a,
420 &b,
421 ptr::from_ref(beta).cast(),
422 &c,
423 &mut d,
424 &mut device_workspace,
425 workspace.host_mut_ptr(),
426 streams,
427 )
428 }
429 }
430
431 pub fn contract_in_place_on_default_streams<TA, TB, TC, TScalar>(
432 &self,
433 alpha: &TScalar,
434 a: &DistributedDeviceMemory<TA>,
435 b: &DistributedDeviceMemory<TB>,
436 beta: &TScalar,
437 c_and_d: &mut DistributedDeviceMemory<TC>,
438 workspace: &mut WorkspaceMemory,
439 ) -> Result<()> {
440 self.validate_execution_slices(
441 a.pointer_count(),
442 b.pointer_count(),
443 c_and_d.pointer_count(),
444 c_and_d.pointer_count(),
445 self.context.device_count(),
446 self.context.device_count(),
447 )?;
448
449 let a = a.const_ptrs();
450 let b = b.const_ptrs();
451 let c = c_and_d.const_ptrs();
452 let mut d = c_and_d.mut_ptrs();
453 let mut device_workspace = workspace.device_mut_ptrs();
454
455 unsafe {
456 self.contract_raw_on_default_streams(
457 ptr::from_ref(alpha).cast(),
458 &a,
459 &b,
460 ptr::from_ref(beta).cast(),
461 &c,
462 &mut d,
463 &mut device_workspace,
464 workspace.host_mut_ptr(),
465 )
466 }
467 }
468
469 pub unsafe fn contract_raw(
476 &self,
477 alpha: *const (),
478 a: &[*const ()],
479 b: &[*const ()],
480 beta: *const (),
481 c: &[*const ()],
482 d: &mut [*mut ()],
483 device_workspace: &mut [*mut ()],
484 host_workspace: *mut (),
485 streams: &[StreamBinding],
486 ) -> Result<()> {
487 self.validate_execution_slices(
488 a.len(),
489 b.len(),
490 c.len(),
491 d.len(),
492 device_workspace.len(),
493 streams.len(),
494 )?;
495 let mut a = a.to_vec();
496 let mut b = b.to_vec();
497 let mut c = c.to_vec();
498 let mut streams: Vec<_> = streams.iter().map(StreamBinding::as_raw).collect();
499 unsafe {
500 try_ffi!(sys::cutensorMgContraction(
501 self.context.as_raw(),
502 self.handle,
503 alpha as _,
504 a.as_mut_ptr() as _,
505 b.as_mut_ptr() as _,
506 beta as _,
507 c.as_mut_ptr() as _,
508 d.as_mut_ptr() as _,
509 device_workspace.as_mut_ptr() as _,
510 host_workspace as _,
511 streams.as_mut_ptr(),
512 ))?;
513 }
514 Ok(())
515 }
516
517 pub unsafe fn contract_raw_on_default_streams(
524 &self,
525 alpha: *const (),
526 a: &[*const ()],
527 b: &[*const ()],
528 beta: *const (),
529 c: &[*const ()],
530 d: &mut [*mut ()],
531 device_workspace: &mut [*mut ()],
532 host_workspace: *mut (),
533 ) -> Result<()> {
534 self.validate_execution_slices(
535 a.len(),
536 b.len(),
537 c.len(),
538 d.len(),
539 device_workspace.len(),
540 self.context.device_count(),
541 )?;
542 let mut a = a.to_vec();
543 let mut b = b.to_vec();
544 let mut c = c.to_vec();
545 let mut streams = vec![ptr::null_mut(); self.context.device_count()];
546 unsafe {
547 try_ffi!(sys::cutensorMgContraction(
548 self.context.as_raw(),
549 self.handle,
550 alpha as _,
551 a.as_mut_ptr() as _,
552 b.as_mut_ptr() as _,
553 beta as _,
554 c.as_mut_ptr() as _,
555 d.as_mut_ptr() as _,
556 device_workspace.as_mut_ptr() as _,
557 host_workspace as _,
558 streams.as_mut_ptr(),
559 ))?;
560 }
561 Ok(())
562 }
563
564 fn validate_execution_slices(
565 &self,
566 a_count: usize,
567 b_count: usize,
568 c_count: usize,
569 d_count: usize,
570 device_workspace_count: usize,
571 stream_count: usize,
572 ) -> Result<()> {
573 validate_count("a", self.a_pointer_count, a_count)?;
574 validate_count("b", self.b_pointer_count, b_count)?;
575 validate_count("c", self.c_pointer_count, c_count)?;
576 validate_count("d", self.d_pointer_count, d_count)?;
577 validate_count(
578 "device_workspace",
579 self.context.device_count(),
580 device_workspace_count,
581 )?;
582 validate_count("streams", self.context.device_count(), stream_count)
583 }
584
585 pub const fn as_raw(&self) -> sys::cutensorMgContractionPlan_t {
586 self.handle
587 }
588
589 pub fn into_raw(self) -> sys::cutensorMgContractionPlan_t {
593 let this = ManuallyDrop::new(self);
594 this.handle
595 }
596}
597
598impl Drop for ContractionFind {
599 fn drop(&mut self) {
600 unsafe {
601 if let Err(err) = try_ffi!(sys::cutensorMgDestroyContractionFind(self.handle)) {
602 #[cfg(debug_assertions)]
603 eprintln!("failed to destroy cutensormg contraction find: {err}");
604 }
605 }
606 }
607}
608
609impl Drop for ContractionDescriptor {
610 fn drop(&mut self) {
611 unsafe {
612 if let Err(err) = try_ffi!(sys::cutensorMgDestroyContractionDescriptor(self.handle)) {
613 #[cfg(debug_assertions)]
614 eprintln!("failed to destroy cutensormg contraction descriptor: {err}");
615 }
616 }
617 }
618}
619
620impl Drop for ContractionPlan {
621 fn drop(&mut self) {
622 unsafe {
623 if let Err(err) = try_ffi!(sys::cutensorMgDestroyContractionPlan(self.handle)) {
624 #[cfg(debug_assertions)]
625 eprintln!("failed to destroy cutensormg contraction plan: {err}");
626 }
627 }
628 }
629}
630
631fn validate_modes(desc: &TensorDescriptor, modes: &[Mode]) -> Result<()> {
632 if desc.rank() as usize != modes.len() {
633 return Err(Error::TensorModeMismatch {
634 rank: desc.rank(),
635 mode_length: modes.len(),
636 });
637 }
638 Ok(())
639}