Skip to main content

oxicuda_sparse/
handle.rs

1//! Sparse handle management.
2//!
3//! [`SparseHandle`] is the central object for all sparse operations, analogous
4//! to `cusparseHandle_t` in cuSPARSE. It owns a CUDA stream, a [`BlasHandle`]
5//! for dense sub-operations, a [`PtxCache`] for kernel caching, and caches the
6//! target SM version.
7//!
8//! # Example
9//!
10//! ```rust,no_run
11//! # use std::sync::Arc;
12//! # use oxicuda_driver::Context;
13//! # use oxicuda_sparse::handle::SparseHandle;
14//! # fn main() -> Result<(), oxicuda_sparse::error::SparseError> {
15//! # let ctx: Arc<Context> = unimplemented!();
16//! let handle = SparseHandle::new(&ctx)?;
17//! # Ok(())
18//! # }
19//! ```
20
21use std::sync::Arc;
22
23use oxicuda_blas::BlasHandle;
24use oxicuda_driver::{Context, Stream};
25use oxicuda_ptx::arch::SmVersion;
26use oxicuda_ptx::cache::PtxCache;
27
28use crate::error::{SparseError, SparseResult};
29
30/// Central handle for sparse matrix operations.
31///
32/// Every sparse routine requires a `SparseHandle`. The handle binds operations
33/// to a specific CUDA context and stream, maintains a BLAS sub-handle for
34/// dense operations (e.g. SpMM output accumulation), caches compiled PTX
35/// kernels, and stores the device's SM version for kernel dispatch.
36///
37/// # Thread safety
38///
39/// `SparseHandle` is `Send` but **not** `Sync`. Each thread should create its
40/// own handle (possibly sharing the same [`Arc<Context>`]).
41pub struct SparseHandle {
42    /// The CUDA context this handle is bound to.
43    context: Arc<Context>,
44    /// The stream on which sparse kernels are launched.
45    stream: Stream,
46    /// Sub-handle for BLAS operations.
47    blas_handle: BlasHandle,
48    /// On-disk cache for compiled PTX modules.
49    ptx_cache: PtxCache,
50    /// SM architecture of the device, cached for kernel selection.
51    sm_version: SmVersion,
52}
53
54impl SparseHandle {
55    /// Creates a new sparse handle with a freshly-allocated default stream.
56    ///
57    /// The device's compute capability is queried once and cached.
58    ///
59    /// # Errors
60    ///
61    /// Returns [`SparseError::Cuda`] if stream creation or device query fails.
62    pub fn new(ctx: &Arc<Context>) -> SparseResult<Self> {
63        let stream = Stream::new(ctx)?;
64        Self::build(ctx, stream)
65    }
66
67    /// Creates a new sparse handle bound to an existing stream.
68    ///
69    /// # Errors
70    ///
71    /// Same as [`new`](Self::new) except stream creation cannot fail.
72    pub fn with_stream(ctx: &Arc<Context>, stream: Stream) -> SparseResult<Self> {
73        Self::build(ctx, stream)
74    }
75
76    /// Shared construction logic.
77    fn build(ctx: &Arc<Context>, stream: Stream) -> SparseResult<Self> {
78        let device = ctx.device();
79        let (major, minor) = device.compute_capability()?;
80        let sm_version = SmVersion::from_compute_capability(major, minor).ok_or_else(|| {
81            SparseError::InternalError(format!("unsupported compute capability: {major}.{minor}"))
82        })?;
83
84        let blas_stream = Stream::new(ctx)?;
85        let blas_handle = BlasHandle::with_stream(ctx, blas_stream)?;
86        let ptx_cache = PtxCache::new()?;
87
88        Ok(Self {
89            context: Arc::clone(ctx),
90            stream,
91            blas_handle,
92            ptx_cache,
93            sm_version,
94        })
95    }
96
97    // -- Accessors -----------------------------------------------------------
98
99    /// Returns a reference to the CUDA context.
100    #[inline]
101    pub fn context(&self) -> &Arc<Context> {
102        &self.context
103    }
104
105    /// Returns a reference to the stream used for kernel launches.
106    #[inline]
107    pub fn stream(&self) -> &Stream {
108        &self.stream
109    }
110
111    /// Returns a reference to the internal BLAS handle.
112    #[inline]
113    pub fn blas_handle(&self) -> &BlasHandle {
114        &self.blas_handle
115    }
116
117    /// Returns a mutable reference to the internal BLAS handle.
118    #[inline]
119    pub fn blas_handle_mut(&mut self) -> &mut BlasHandle {
120        &mut self.blas_handle
121    }
122
123    /// Returns the SM version of the bound device.
124    #[inline]
125    pub fn sm_version(&self) -> SmVersion {
126        self.sm_version
127    }
128
129    /// Returns a reference to the PTX cache.
130    #[inline]
131    pub fn ptx_cache(&self) -> &PtxCache {
132        &self.ptx_cache
133    }
134
135    // -- Mutators ------------------------------------------------------------
136
137    /// Replaces the stream used for subsequent sparse operations.
138    pub fn set_stream(&mut self, stream: Stream) {
139        self.stream = stream;
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use oxicuda_ptx::arch::SmVersion;
146
147    #[test]
148    fn sm_version_is_copy() {
149        let v = SmVersion::Sm80;
150        let v2 = v;
151        assert_eq!(v, v2);
152    }
153}