Skip to main content

singe_cutensor/
tensor.rs

1#[allow(unused_imports)]
2use crate::error::Status;
3
4use std::ptr;
5
6use singe_cuda::data_type::{DataType, DataTypeLike};
7
8use crate::{
9    context::Context,
10    error::{Error, Result},
11    sys, try_ffi,
12    utility::to_i64_vec,
13};
14
15#[derive(Debug)]
16pub struct TensorDescriptor {
17    handle: sys::cutensorTensorDescriptor_t,
18    shape: Vec<u64>,
19    strides: Option<Vec<u64>>,
20    data_type: DataType,
21    alignment_requirement: u32,
22}
23
24impl TensorDescriptor {
25    pub fn create_for<T: DataTypeLike>(
26        ctx: &Context,
27        shape: &[u64],
28        alignment_requirement: u32,
29    ) -> Result<Self> {
30        Self::create(ctx, shape, T::data_type(), alignment_requirement)
31    }
32
33    pub fn create(
34        ctx: &Context,
35        shape: &[u64],
36        data_type: DataType,
37        alignment_requirement: u32,
38    ) -> Result<Self> {
39        Self::create_with_strides(ctx, shape, None, data_type, alignment_requirement)
40    }
41
42    /// Creates a tensor descriptor.
43    ///
44    /// This allocates a small amount of host memory.
45    ///
46    /// The descriptor frees the associated cuTENSOR resources when dropped.
47    ///
48    /// This non-blocking call is thread-safe but not reentrant.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if the context cannot be bound, `shape` and `strides`
53    /// have different ranks, an extent or stride cannot be represented for
54    /// cuTENSOR, the data type or descriptor layout is unsupported, or cuTENSOR
55    /// returns a null descriptor handle.
56    pub fn create_with_strides(
57        ctx: &Context,
58        shape: &[u64],
59        strides: Option<&[u64]>,
60        data_type: DataType,
61        alignment_requirement: u32,
62    ) -> Result<Self> {
63        ctx.bind()?;
64
65        let rank = shape.len() as u32;
66        if let Some(strides) = strides
67            && shape.len() != strides.len()
68        {
69            return Err(Error::TensorStrideMismatch {
70                rank,
71                stride_length: strides.len(),
72            });
73        }
74
75        let extent = to_i64_vec(shape, "shape")?;
76        let strides_vec = strides.map(<[u64]>::to_vec);
77        let stride_i64 = strides_vec
78            .as_ref()
79            .map(|values| to_i64_vec(values, "strides"))
80            .transpose()?;
81
82        let mut handle = ptr::null_mut();
83        unsafe {
84            try_ffi!(sys::cutensorCreateTensorDescriptor(
85                ctx.as_raw(),
86                &raw mut handle,
87                rank,
88                extent.as_ptr(),
89                stride_i64.as_ref().map_or(ptr::null(), Vec::as_ptr),
90                data_type.into(),
91                alignment_requirement,
92            ))?;
93        }
94
95        if handle.is_null() {
96            return Err(Error::NullHandle);
97        }
98
99        Ok(Self {
100            handle,
101            shape: shape.to_vec(),
102            strides: strides_vec,
103            data_type,
104            alignment_requirement,
105        })
106    }
107
108    pub fn create_with_strides_for<T: DataTypeLike>(
109        ctx: &Context,
110        shape: &[u64],
111        strides: Option<&[u64]>,
112        alignment_requirement: u32,
113    ) -> Result<Self> {
114        Self::create_with_strides(ctx, shape, strides, T::data_type(), alignment_requirement)
115    }
116
117    pub fn shape(&self) -> &[u64] {
118        &self.shape
119    }
120
121    pub fn strides(&self) -> Option<&[u64]> {
122        self.strides.as_deref()
123    }
124
125    pub fn rank(&self) -> u32 {
126        self.shape.len() as u32
127    }
128
129    pub fn data_type(&self) -> DataType {
130        self.data_type
131    }
132
133    pub fn alignment_requirement(&self) -> u32 {
134        self.alignment_requirement
135    }
136
137    pub const fn as_raw(&self) -> sys::cutensorTensorDescriptor_t {
138        self.handle
139    }
140}
141
142impl Drop for TensorDescriptor {
143    fn drop(&mut self) {
144        unsafe {
145            if let Err(err) = try_ffi!(sys::cutensorDestroyTensorDescriptor(self.handle)) {
146                #[cfg(debug_assertions)]
147                eprintln!("failed to destroy cutensor tensor descriptor: {err}");
148            }
149        }
150    }
151}
152
153#[derive(Debug)]
154pub struct BlockSparseTensorDescriptor {
155    handle: sys::cutensorBlockSparseTensorDescriptor_t,
156    mode_count: u32,
157    non_zero_block_count: u64,
158    data_type: DataType,
159}
160
161impl BlockSparseTensorDescriptor {
162    /// Creates a block-sparse tensor descriptor.
163    ///
164    /// A block-sparse tensor descriptor fully specifies the data layout of a block-sparse tensor (currently limited to up to 8 modes).
165    ///
166    /// Consider an example for a block-sparse tensor of order 2, namely a block-sparse matrix `A`.
167    /// Its first mode (`rows`) is subdivided into 5 sections (with extents 4, 2, 3, 4, 5, respectively), and its second mode (`columns`) is subdivided into 3 sections (with extents 2, 3, 7).
168    /// The matrix has 8 non-zero blocks:
169    ///
170    /// | 2 columns | 3 columns | 7 columns |
171    /// | --- | --- | --- |
172    /// | 4 x 2 | | 4 x 7 |
173    /// | | 2 x 3 | |
174    /// | | 3 x 3 | 3 x 7 |
175    /// | 4 x 2 | | |
176    /// | | 5 x 3 | 5 x 7 |
177    ///
178    /// Each subsection has the same extent across its entire mode: every block
179    /// within the same section of a mode has the same extent.
180    /// For example, in the table above every block on the right has 7 columns,
181    /// and all blocks on the bottom have 5 rows.
182    ///
183    /// Only non-zero blocks are stored; zero blocks are left blank in the
184    /// representation above.
185    ///
186    /// For example:
187    ///
188    /// * strides of block #0: 5, 1, 10, 20.
189    ///   Sorted strides would be: 1, 5, 10, 20.
190    ///   The permutation to sort the strides in this case is to swap the first two elements.
191    /// * strides of block #1: 10, 1, 30, 60.
192    ///   Applying the permutation would result in: 1, 10, 30, 60.
193    ///   The result is sorted in ascending order, which is allowed.
194    /// * strides of block #2: 1, 5, 50, 100.
195    ///   Applying permutation would result in: 5, 1, 50, 100.
196    ///   The result is *not* sorted in ascending order, this is *not* allowed.
197    ///
198    /// This non-blocking call is thread-safe but not reentrant.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if the context cannot be bound, the section extent,
203    /// coordinate, or stride slices do not match the descriptor dimensions, a
204    /// value cannot be represented for cuTENSOR, cuTENSOR rejects the
205    /// descriptor, or cuTENSOR returns a null descriptor handle.
206    pub fn create(
207        ctx: &Context,
208        section_count_per_mode: &[u32],
209        section_extents: &[u64],
210        non_zero_block_count: u64,
211        non_zero_coordinates: &[i32],
212        block_strides: Option<&[u64]>,
213        data_type: DataType,
214    ) -> Result<Self> {
215        ctx.bind()?;
216
217        let mode_count = section_count_per_mode.len() as u32;
218        let expected_section_extents = section_count_per_mode
219            .iter()
220            .try_fold(0usize, |sum, &value| sum.checked_add(value as usize))
221            .ok_or(Error::OutOfRange {
222                name: "section_extents".into(),
223            })?;
224        if section_extents.len() != expected_section_extents {
225            return Err(Error::LengthMismatch {
226                name: "section_extents".into(),
227                expected: expected_section_extents,
228                actual: section_extents.len(),
229            });
230        }
231
232        let expected_coordinates =
233            mode_count
234                .checked_mul(non_zero_block_count as u32)
235                .ok_or(Error::OutOfRange {
236                    name: "non_zero_coordinates".into(),
237                })?;
238        if non_zero_coordinates.len() != expected_coordinates as usize {
239            return Err(Error::LengthMismatch {
240                name: "non_zero_coordinates".into(),
241                expected: expected_coordinates as usize,
242                actual: non_zero_coordinates.len(),
243            });
244        }
245
246        if let Some(block_strides) = block_strides
247            && block_strides.len() != expected_coordinates as usize
248        {
249            return Err(Error::TensorStrideMismatch {
250                rank: expected_coordinates,
251                stride_length: block_strides.len(),
252            });
253        }
254
255        let num_sections_per_mode = section_count_per_mode.to_vec();
256        let section_extents = to_i64_vec(section_extents, "section_extents")?;
257        let block_strides = block_strides
258            .map(|block_strides| to_i64_vec(block_strides, "block_strides"))
259            .transpose()?;
260
261        let mut handle = ptr::null_mut();
262        unsafe {
263            try_ffi!(sys::cutensorCreateBlockSparseTensorDescriptor(
264                ctx.as_raw(),
265                &raw mut handle,
266                mode_count,
267                non_zero_block_count,
268                num_sections_per_mode.as_ptr(),
269                section_extents.as_ptr(),
270                non_zero_coordinates.as_ptr(),
271                block_strides.as_ref().map_or(ptr::null(), Vec::as_ptr),
272                data_type.into(),
273            ))?;
274        }
275
276        if handle.is_null() {
277            return Err(Error::NullHandle);
278        }
279
280        Ok(Self {
281            handle,
282            mode_count,
283            non_zero_block_count,
284            data_type,
285        })
286    }
287
288    pub fn mode_count(&self) -> u32 {
289        self.mode_count
290    }
291
292    pub fn non_zero_block_count(&self) -> u64 {
293        self.non_zero_block_count
294    }
295
296    pub fn data_type(&self) -> DataType {
297        self.data_type
298    }
299
300    pub const fn as_raw(&self) -> sys::cutensorBlockSparseTensorDescriptor_t {
301        self.handle
302    }
303}
304
305impl Drop for BlockSparseTensorDescriptor {
306    fn drop(&mut self) {
307        unsafe {
308            if let Err(err) = try_ffi!(sys::cutensorDestroyBlockSparseTensorDescriptor(self.handle))
309            {
310                #[cfg(debug_assertions)]
311                eprintln!("failed to destroy cutensor block sparse tensor descriptor: {err}");
312            }
313        }
314    }
315}