singe_cusparse/vector.rs
1use std::{
2 marker::PhantomData,
3 mem::{ManuallyDrop, MaybeUninit},
4 ptr,
5 sync::Arc,
6};
7
8use singe_cuda::{
9 context::Context as CudaContext, data_type::DataTypeLike, memory::DeviceMemory,
10 types::DevicePtr,
11};
12
13use crate::{
14 context::Context,
15 error::{Error, Result},
16 sys, try_ffi,
17 types::{IndexBase, IndexType, IndexTypeLike},
18 utility::{to_i64, to_usize},
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct SparseVectorInfo {
23 pub size: usize,
24 pub nonzero_count: usize,
25 pub indices: DevicePtr,
26 pub values: DevicePtr,
27 pub index_type: IndexType,
28 pub index_base: IndexBase,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct DenseVectorInfo {
33 pub size: usize,
34 pub values: DevicePtr,
35}
36
37#[derive(Debug)]
38pub struct SparseVectorDescriptor<'a> {
39 handle: sys::cusparseSpVecDescr_t,
40 cuda_ctx: Arc<CudaContext>,
41 _buffers: PhantomData<&'a mut ()>,
42}
43
44#[derive(Debug)]
45pub struct DenseVectorDescriptor<'a> {
46 handle: sys::cusparseDnVecDescr_t,
47 cuda_ctx: Arc<CudaContext>,
48 _buffers: PhantomData<&'a mut ()>,
49}
50
51// Vector descriptors borrow mutable device buffers and can rebind their value
52// pointers, so they are movable but not shared concurrently.
53unsafe impl Send for SparseVectorDescriptor<'_> {}
54unsafe impl Send for DenseVectorDescriptor<'_> {}
55
56impl<'a> SparseVectorDescriptor<'a> {
57 /// Initializes a sparse vector descriptor.
58 ///
59 /// The descriptor borrows the device index and value buffers by pointer; the
60 /// buffers must outlive descriptor use or be replaced with [`SparseVectorDescriptor::set_values`].
61 pub fn create<I: IndexTypeLike, T: DataTypeLike>(
62 ctx: &Context,
63 size: usize,
64 nonzero_count: usize,
65 indices: &'a mut DeviceMemory<I>,
66 values: &'a mut DeviceMemory<T>,
67 index_base: IndexBase,
68 ) -> Result<Self> {
69 ctx.bind()?;
70 let size = to_i64(size, "size")?;
71 let nonzero_count = to_i64(nonzero_count, "nonzero_count")?;
72 let mut handle = ptr::null_mut();
73 unsafe {
74 try_ffi!(sys::cusparseCreateSpVec(
75 &raw mut handle,
76 size,
77 nonzero_count,
78 indices.as_mut_ptr().cast(),
79 values.as_mut_ptr().cast(),
80 I::index_type().into(),
81 index_base.into(),
82 T::data_type().into(),
83 ))?;
84 }
85
86 if handle.is_null() {
87 return Err(Error::NullHandle);
88 }
89
90 Ok(Self {
91 handle,
92 cuda_ctx: Arc::clone(ctx.cuda_context()),
93 _buffers: PhantomData,
94 })
95 }
96
97 /// Returns the index base of this sparse vector descriptor.
98 ///
99 /// # Errors
100 ///
101 /// Returns an error if cuSPARSE cannot report the sparse vector index base.
102 pub fn index_base(&self) -> Result<IndexBase> {
103 let mut value = sys::cusparseIndexBase_t::CUSPARSE_INDEX_BASE_ZERO;
104 unsafe {
105 try_ffi!(sys::cusparseSpVecGetIndexBase(
106 self.as_raw_const(),
107 &raw mut value
108 ))?;
109 }
110 Ok(value.into())
111 }
112
113 /// Returns the values pointer of this sparse vector descriptor.
114 ///
115 /// # Errors
116 ///
117 /// Returns an error if cuSPARSE cannot report the values pointer.
118 pub fn values(&self) -> Result<DevicePtr> {
119 let mut values = ptr::null_mut();
120 unsafe {
121 try_ffi!(sys::cusparseSpVecGetValues(self.as_raw(), &raw mut values))?;
122 }
123 Ok(unsafe { DevicePtr::from_raw(values.cast()) })
124 }
125
126 /// Sets the values pointer of this sparse vector descriptor.
127 ///
128 /// The pointed-to device buffer must remain valid while the descriptor uses it.
129 ///
130 /// # Errors
131 ///
132 /// Returns an error if cuSPARSE rejects the values pointer.
133 pub fn set_values<T: DataTypeLike>(&mut self, values: &'a mut DeviceMemory<T>) -> Result<()> {
134 unsafe {
135 try_ffi!(sys::cusparseSpVecSetValues(
136 self.as_raw(),
137 values.as_mut_ptr().cast()
138 ))?;
139 }
140 Ok(())
141 }
142
143 /// Returns the fields of this sparse vector descriptor.
144 ///
145 /// # Errors
146 ///
147 /// Returns an error if cuSPARSE cannot report the descriptor fields or if
148 /// reported sizes cannot be represented as `usize`.
149 pub fn info(&self) -> Result<SparseVectorInfo> {
150 let mut size = 0_i64;
151 let mut nnz = 0_i64;
152 let mut indices = ptr::null_mut();
153 let mut values = ptr::null_mut();
154 let mut index_type = sys::cusparseIndexType_t::CUSPARSE_INDEX_32I;
155 let mut index_base = sys::cusparseIndexBase_t::CUSPARSE_INDEX_BASE_ZERO;
156 let mut value_type = MaybeUninit::uninit();
157 unsafe {
158 try_ffi!(sys::cusparseSpVecGet(
159 self.as_raw(),
160 &raw mut size,
161 &raw mut nnz,
162 &raw mut indices,
163 &raw mut values,
164 &raw mut index_type,
165 &raw mut index_base,
166 value_type.as_mut_ptr(),
167 ))?;
168 }
169 Ok(SparseVectorInfo {
170 size: to_usize(size, "size")?,
171 nonzero_count: to_usize(nnz, "nonzero_count")?,
172 indices: unsafe { DevicePtr::from_raw(indices.cast()) },
173 values: unsafe { DevicePtr::from_raw(values.cast()) },
174 index_type: index_type.into(),
175 index_base: index_base.into(),
176 })
177 }
178
179 pub fn as_raw(&self) -> sys::cusparseSpVecDescr_t {
180 self.handle
181 }
182
183 pub fn as_raw_const(&self) -> sys::cusparseConstSpVecDescr_t {
184 self.handle.cast_const()
185 }
186
187 pub fn cuda_context(&self) -> &Arc<CudaContext> {
188 &self.cuda_ctx
189 }
190
191 pub(crate) fn ensure_context(&self, ctx: &Context) -> Result<()> {
192 if self.cuda_ctx.as_ref() != ctx.cuda_context().as_ref() {
193 return Err(Error::PlanContextMismatch);
194 }
195 Ok(())
196 }
197
198 /// Wraps an existing cuSPARSE sparse-vector descriptor and takes ownership
199 /// of it.
200 ///
201 /// # Safety
202 ///
203 /// `handle` must be a valid `cusparseSpVecDescr_t`. Any device buffers
204 /// referenced by the descriptor must remain valid for lifetime `'a`.
205 /// Ownership of `handle` is transferred to the returned descriptor, and the
206 /// handle must not be destroyed elsewhere after calling this function.
207 pub unsafe fn from_raw(handle: sys::cusparseSpVecDescr_t, ctx: &Context) -> Result<Self> {
208 if handle.is_null() {
209 return Err(Error::NullHandle);
210 }
211
212 Ok(Self {
213 handle,
214 cuda_ctx: Arc::clone(ctx.cuda_context()),
215 _buffers: PhantomData,
216 })
217 }
218
219 /// Consumes the descriptor and returns the raw cuSPARSE handle without
220 /// destroying it.
221 ///
222 /// The caller becomes responsible for eventually destroying the returned
223 /// handle with `cusparseDestroySpVec`.
224 pub fn into_raw(self) -> sys::cusparseSpVecDescr_t {
225 let descriptor = ManuallyDrop::new(self);
226 descriptor.handle
227 }
228}
229
230impl Drop for SparseVectorDescriptor<'_> {
231 fn drop(&mut self) {
232 unsafe {
233 if let Err(err) = try_ffi!(sys::cusparseDestroySpVec(self.as_raw_const())) {
234 #[cfg(debug_assertions)]
235 eprintln!("failed to destroy cusparse sparse vector descriptor: {err}");
236 }
237 }
238 }
239}
240
241impl<'a> DenseVectorDescriptor<'a> {
242 /// Initializes a dense vector descriptor.
243 ///
244 /// The descriptor borrows the device value buffer by pointer; the buffer must
245 /// outlive descriptor use or be replaced with [`DenseVectorDescriptor::set_values`].
246 ///
247 /// # Errors
248 ///
249 /// Returns an error if `size` cannot be represented by cuSPARSE, if cuSPARSE
250 /// cannot create the descriptor, or if it returns a null handle.
251 pub fn create<T: DataTypeLike>(
252 ctx: &Context,
253 size: usize,
254 values: &'a mut DeviceMemory<T>,
255 ) -> Result<Self> {
256 ctx.bind()?;
257 let size = to_i64(size, "size")?;
258 let mut handle = ptr::null_mut();
259 unsafe {
260 try_ffi!(sys::cusparseCreateDnVec(
261 &raw mut handle,
262 size,
263 values.as_mut_ptr().cast(),
264 T::data_type().into(),
265 ))?;
266 }
267
268 if handle.is_null() {
269 return Err(Error::NullHandle);
270 }
271
272 Ok(Self {
273 handle,
274 cuda_ctx: Arc::clone(ctx.cuda_context()),
275 _buffers: PhantomData,
276 })
277 }
278
279 /// Returns the values pointer of this dense vector descriptor.
280 ///
281 /// # Errors
282 ///
283 /// Returns an error if cuSPARSE cannot report the values pointer.
284 pub fn values(&self) -> Result<DevicePtr> {
285 let mut values = ptr::null_mut();
286 unsafe {
287 try_ffi!(sys::cusparseDnVecGetValues(self.as_raw(), &raw mut values))?;
288 }
289 Ok(unsafe { DevicePtr::from_raw(values.cast()) })
290 }
291
292 /// Sets the values pointer of this dense vector descriptor.
293 ///
294 /// The pointed-to device buffer must remain valid while the descriptor uses it.
295 ///
296 /// # Errors
297 ///
298 /// Returns an error if cuSPARSE rejects the values pointer.
299 pub fn set_values<T: DataTypeLike>(&mut self, values: &'a mut DeviceMemory<T>) -> Result<()> {
300 unsafe {
301 try_ffi!(sys::cusparseDnVecSetValues(
302 self.as_raw(),
303 values.as_mut_ptr().cast()
304 ))?;
305 }
306 Ok(())
307 }
308
309 /// Returns the fields of this dense vector descriptor.
310 ///
311 /// # Errors
312 ///
313 /// Returns an error if cuSPARSE cannot report the descriptor fields or if the
314 /// reported size cannot be represented as `usize`.
315 pub fn info(&self) -> Result<DenseVectorInfo> {
316 let mut size = 0_i64;
317 let mut values = ptr::null_mut();
318 let mut value_type = MaybeUninit::uninit();
319 unsafe {
320 try_ffi!(sys::cusparseDnVecGet(
321 self.as_raw(),
322 &raw mut size,
323 &raw mut values,
324 value_type.as_mut_ptr(),
325 ))?;
326 }
327 Ok(DenseVectorInfo {
328 size: to_usize(size, "size")?,
329 values: unsafe { DevicePtr::from_raw(values.cast()) },
330 })
331 }
332
333 pub fn as_raw(&self) -> sys::cusparseDnVecDescr_t {
334 self.handle
335 }
336
337 pub fn as_raw_const(&self) -> sys::cusparseConstDnVecDescr_t {
338 self.handle.cast_const()
339 }
340
341 pub fn cuda_context(&self) -> &Arc<CudaContext> {
342 &self.cuda_ctx
343 }
344
345 pub(crate) fn ensure_context(&self, ctx: &Context) -> Result<()> {
346 if self.cuda_ctx.as_ref() != ctx.cuda_context().as_ref() {
347 return Err(Error::PlanContextMismatch);
348 }
349 Ok(())
350 }
351
352 /// Wraps an existing cuSPARSE dense-vector descriptor and takes ownership
353 /// of it.
354 ///
355 /// # Safety
356 ///
357 /// `handle` must be a valid `cusparseDnVecDescr_t`. Any device buffer
358 /// referenced by the descriptor must remain valid for lifetime `'a`.
359 /// Ownership of `handle` is transferred to the returned descriptor, and the
360 /// handle must not be destroyed elsewhere after calling this function.
361 pub unsafe fn from_raw(handle: sys::cusparseDnVecDescr_t, ctx: &Context) -> Result<Self> {
362 if handle.is_null() {
363 return Err(Error::NullHandle);
364 }
365
366 Ok(Self {
367 handle,
368 cuda_ctx: Arc::clone(ctx.cuda_context()),
369 _buffers: PhantomData,
370 })
371 }
372
373 /// Consumes the descriptor and returns the raw cuSPARSE handle without
374 /// destroying it.
375 ///
376 /// The caller becomes responsible for eventually destroying the returned
377 /// handle with `cusparseDestroyDnVec`.
378 pub fn into_raw(self) -> sys::cusparseDnVecDescr_t {
379 let descriptor = ManuallyDrop::new(self);
380 descriptor.handle
381 }
382}
383
384impl Drop for DenseVectorDescriptor<'_> {
385 fn drop(&mut self) {
386 unsafe {
387 if let Err(err) = try_ffi!(sys::cusparseDestroyDnVec(self.as_raw_const())) {
388 #[cfg(debug_assertions)]
389 eprintln!("failed to destroy cusparse dense vector descriptor: {err}");
390 }
391 }
392 }
393}
394
395/// Gathers the elements of `dense_vector` into `sparse_vector`.
396///
397/// In other words,
398///
399/// ```text
400/// for i=0 to nnz-1
401/// sparse_values[i] = dense_values[sparse_indices[i]]
402/// ```
403///
404/// [`gather`] supports the following index type for representing `sparse_vector`:
405///
406/// * 32-bit indices ([`IndexType::I32`])
407/// * 64-bit indices ([`IndexType::I64`])
408///
409/// [`gather`] supports the following data types:
410///
411/// | `X`/`Y` | Notes |
412/// | --- | --- |
413/// | [`DataType::F16`](singe_cuda::data_type::DataType::F16) | |
414/// | [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) | |
415/// | [`DataType::F32`](singe_cuda::data_type::DataType::F32) | |
416/// | [`DataType::F64`](singe_cuda::data_type::DataType::F64) | |
417/// | [`DataType::ComplexF16`](singe_cuda::data_type::DataType::ComplexF16) | Deprecated. |
418/// | [`DataType::ComplexBf16`](singe_cuda::data_type::DataType::ComplexBf16) | Deprecated. |
419/// | [`DataType::ComplexF32`](singe_cuda::data_type::DataType::ComplexF32) | |
420/// | [`DataType::ComplexF64`](singe_cuda::data_type::DataType::ComplexF64) | |
421///
422/// [`gather`] has the following constraints:
423///
424/// * The arrays representing `sparse_vector` must be aligned to 16 bytes.
425///
426/// [`gather`] has the following properties:
427///
428/// * Requires no extra storage.
429/// * Supports asynchronous execution.
430/// * Provides deterministic (bitwise) results for each run if `sparse_vector` indices are distinct.
431/// * Allows the indices of `sparse_vector` to be unsorted.
432///
433/// [`gather`] supports the following optimizations:
434///
435/// * CUDA graph capture.
436/// * Hardware Memory Compression.
437pub fn gather(
438 ctx: &Context,
439 sparse_vector: &mut SparseVectorDescriptor,
440 dense_vector: &DenseVectorDescriptor,
441) -> Result<()> {
442 sparse_vector.ensure_context(ctx)?;
443 dense_vector.ensure_context(ctx)?;
444 ctx.bind()?;
445 unsafe {
446 try_ffi!(sys::cusparseGather(
447 ctx.as_raw(),
448 dense_vector.as_raw_const(),
449 sparse_vector.as_raw(),
450 ))?;
451 }
452 Ok(())
453}
454
455/// Scatters the elements of `sparse_vector` into `dense_vector`.
456///
457/// In other words,
458///
459/// ```text
460/// for i=0 to nnz-1
461/// dense_values[sparse_indices[i]] = sparse_values[i]
462/// ```
463///
464/// [`scatter`] supports the following index type for representing `sparse_vector`:
465///
466/// * 32-bit indices ([`IndexType::I32`])
467/// * 64-bit indices ([`IndexType::I64`])
468///
469/// [`scatter`] supports the following data types:
470///
471/// | `X`/`Y` | Notes |
472/// | --- | --- |
473/// | [`DataType::I8`](singe_cuda::data_type::DataType::I8) | |
474/// | [`DataType::F16`](singe_cuda::data_type::DataType::F16) | |
475/// | [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) | |
476/// | [`DataType::F32`](singe_cuda::data_type::DataType::F32) | |
477/// | [`DataType::F64`](singe_cuda::data_type::DataType::F64) | |
478/// | [`DataType::ComplexF16`](singe_cuda::data_type::DataType::ComplexF16) | Deprecated. |
479/// | [`DataType::ComplexBf16`](singe_cuda::data_type::DataType::ComplexBf16) | Deprecated. |
480/// | [`DataType::ComplexF32`](singe_cuda::data_type::DataType::ComplexF32) | |
481/// | [`DataType::ComplexF64`](singe_cuda::data_type::DataType::ComplexF64) | |
482///
483/// [`scatter`] has the following constraints:
484///
485/// * The arrays representing `sparse_vector` must be aligned to 16 bytes.
486///
487/// [`scatter`] has the following properties:
488///
489/// * Requires no extra storage.
490/// * Supports asynchronous execution.
491/// * Provides deterministic (bitwise) results for each run if `sparse_vector` indices are distinct.
492/// * Allows the indices of `sparse_vector` to be unsorted.
493///
494/// [`scatter`] supports the following optimizations:
495///
496/// * CUDA graph capture.
497/// * Hardware Memory Compression.
498pub fn scatter(
499 ctx: &Context,
500 sparse_vector: &SparseVectorDescriptor,
501 dense_vector: &mut DenseVectorDescriptor,
502) -> Result<()> {
503 sparse_vector.ensure_context(ctx)?;
504 dense_vector.ensure_context(ctx)?;
505 ctx.bind()?;
506 unsafe {
507 try_ffi!(sys::cusparseScatter(
508 ctx.as_raw(),
509 sparse_vector.as_raw_const(),
510 dense_vector.as_raw(),
511 ))?;
512 }
513 Ok(())
514}