Skip to main content

singe_cusolver/
context.rs

1#[allow(unused_imports)]
2use crate::{
3    dense::legacy::qr::xgeqrf,
4    irs::xgesv,
5    svd::{Gesvd, Gesvdp, Gesvdr},
6};
7
8use std::{mem::ManuallyDrop, path::Path, ptr, sync::Arc};
9
10use singe_core::path_to_cstring;
11use singe_cuda::{context::Context as CudaContext, stream::Stream, types::EmulationStrategy};
12use singe_cuda_sys::runtime;
13
14use crate::{
15    error::{Error, Result},
16    sys, try_ffi,
17    types::{DeterministicMode, MathMode},
18};
19
20/// A stateful cuSOLVER handle.
21///
22/// Use one context per host thread or concurrent task. The handle is movable
23/// between threads, but it is intentionally not `Clone` or `Sync`.
24#[derive(Debug)]
25pub struct Context {
26    handle: Handle,
27}
28
29#[derive(Debug)]
30struct Handle {
31    raw: sys::cusolverDnHandle_t,
32    cuda_ctx: Arc<CudaContext>,
33}
34
35// cuSOLVER handles are stateful and stream-bound. The owner may move between
36// threads, but callers need exclusive access to mutate handle state.
37unsafe impl Send for Handle {}
38
39/// The stream bound to a cuSOLVER handle.
40#[derive(Debug, Clone)]
41#[non_exhaustive]
42pub enum StreamBinding {
43    /// The CUDA default stream.
44    Default,
45    /// A borrowed stream associated with the same CUDA context.
46    Borrowed(BorrowedStream),
47}
48
49/// A stream borrowed from a CUDA context, associated with a cuSOLVER handle.
50#[derive(Debug, Clone)]
51pub struct BorrowedStream {
52    handle: runtime::cudaStream_t,
53    cuda_ctx: Arc<CudaContext>,
54}
55
56impl BorrowedStream {
57    /// Returns the raw CUDA stream handle.
58    pub const fn as_raw(&self) -> runtime::cudaStream_t {
59        self.handle
60    }
61
62    /// Returns a reference to the CUDA context this stream belongs to.
63    pub fn context(&self) -> &CudaContext {
64        self.cuda_ctx.as_ref()
65    }
66}
67
68impl Context {
69    /// Creates a cuSOLVER dense handle for the given CUDA context.
70    /// Call this before invoking other cuSOLVER operations through this wrapper.
71    ///
72    /// cuSOLVER allocates the GPU-side resources it needs here. On the first
73    /// application-defined stream passed to [`Context::set_stream`], cuSOLVER may also
74    /// allocate an internal workspace.
75    ///
76    /// # Errors
77    ///
78    /// Returns an error if the CUDA context cannot be bound, if cuSOLVER cannot
79    /// create a handle, or if cuSOLVER returns a null handle.
80    pub fn create(cuda_ctx: &Arc<CudaContext>) -> Result<Self> {
81        cuda_ctx.bind()?;
82
83        let mut handle = ptr::null_mut();
84        unsafe {
85            try_ffi!(sys::cusolverDnCreate(&raw mut handle))?;
86        }
87
88        if handle.is_null() {
89            return Err(Error::NullHandle);
90        }
91
92        Ok(Self {
93            handle: Handle {
94                raw: handle,
95                cuda_ctx: Arc::clone(cuda_ctx),
96            },
97        })
98    }
99
100    /// Wraps an existing cuSOLVER dense handle and takes ownership of it.
101    ///
102    /// # Safety
103    ///
104    /// `handle` must be a valid cuSOLVER dense handle associated with
105    /// `cuda_ctx`. Ownership of `handle` is transferred to the returned
106    /// context, and the handle must not be destroyed elsewhere after calling
107    /// this function.
108    pub unsafe fn from_raw(
109        handle: sys::cusolverDnHandle_t,
110        cuda_ctx: Arc<CudaContext>,
111    ) -> Result<Self> {
112        if handle.is_null() {
113            return Err(Error::NullHandle);
114        }
115
116        Ok(Self {
117            handle: Handle {
118                raw: handle,
119                cuda_ctx,
120            },
121        })
122    }
123
124    /// Returns the underlying CUDA context used by this cuSOLVER handle.
125    pub fn cuda_context(&self) -> &Arc<CudaContext> {
126        &self.handle.cuda_ctx
127    }
128
129    /// Binds the underlying CUDA context associated with this handle.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if the CUDA context cannot be bound.
134    pub fn bind(&self) -> Result<()> {
135        Ok(self.cuda_context().bind()?)
136    }
137
138    /// Ensures `stream` belongs to the same CUDA context as this handle.
139    ///
140    /// Returns an error if the stream belongs to a different context.
141    pub fn ensure_stream(&self, stream: &Stream) -> Result<()> {
142        if self.cuda_context().as_ref() != stream.context() {
143            return Err(Error::StreamContextMismatch);
144        }
145
146        self.bind()
147    }
148
149    /// Returns the stream currently used by this cuSOLVER handle.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
154    /// cannot report the current stream.
155    pub fn stream(&self) -> Result<StreamBinding> {
156        self.bind()?;
157
158        let mut stream = ptr::null_mut();
159        unsafe {
160            try_ffi!(sys::cusolverDnGetStream(self.as_raw(), &raw mut stream))?;
161        }
162
163        Ok(if stream.is_null() {
164            StreamBinding::Default
165        } else {
166            StreamBinding::Borrowed(BorrowedStream {
167                handle: stream,
168                cuda_ctx: Arc::clone(self.cuda_context()),
169            })
170        })
171    }
172
173    /// Sets the stream used by this cuSOLVER handle.
174    ///
175    /// Passing `None` restores the CUDA default stream.
176    ///
177    /// # Errors
178    ///
179    /// Returns an error if `stream` belongs to another CUDA context, if the CUDA
180    /// context cannot be bound, or if cuSOLVER rejects the stream.
181    pub fn set_stream(&self, stream: Option<&Stream>) -> Result<()> {
182        if let Some(stream) = stream {
183            self.ensure_stream(stream)?;
184        } else {
185            self.bind()?;
186        }
187
188        unsafe {
189            try_ffi!(sys::cusolverDnSetStream(
190                self.as_raw(),
191                match stream {
192                    Some(stream) => stream.as_raw(),
193                    None => ptr::null_mut(),
194                },
195            ))?;
196        }
197        Ok(())
198    }
199
200    /// Returns the deterministic mode currently configured on this handle.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
205    /// cannot report the deterministic mode.
206    pub fn deterministic_mode(&self) -> Result<DeterministicMode> {
207        self.bind()?;
208
209        let mut mode = sys::cusolverDeterministicMode_t::CUSOLVER_DETERMINISTIC_RESULTS;
210        unsafe {
211            try_ffi!(sys::cusolverDnGetDeterministicMode(
212                self.as_raw(),
213                &raw mut mode,
214            ))?;
215        }
216        Ok(mode.into())
217    }
218
219    /// Sets the deterministic mode for operations executed through this handle.
220    ///
221    /// Allowing non-deterministic results may improve performance for some
222    /// operations, including [`xgeqrf`], [`Gesvd`], [`Gesvdr`], and [`Gesvdp`].
223    ///
224    /// # Errors
225    ///
226    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
227    /// rejects the deterministic mode.
228    pub fn set_deterministic_mode(&self, mode: DeterministicMode) -> Result<()> {
229        self.bind()?;
230        unsafe {
231            try_ffi!(sys::cusolverDnSetDeterministicMode(
232                self.as_raw(),
233                mode.into(),
234            ))?;
235        }
236        Ok(())
237    }
238
239    /// Returns the math mode currently configured on this handle.
240    ///
241    /// See [`MathMode`] for the supported wrapper values.
242    ///
243    /// # Errors
244    ///
245    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
246    /// cannot report the math mode.
247    pub fn math_mode(&self) -> Result<MathMode> {
248        self.bind()?;
249
250        let mut mode = sys::cusolverMathMode_t::CUSOLVER_DEFAULT_MATH;
251        unsafe {
252            try_ffi!(sys::cusolverDnGetMathMode(self.as_raw(), &raw mut mode))?;
253        }
254        Ok(mode.into())
255    }
256
257    /// Sets the math mode for operations executed through this handle.
258    ///
259    /// See [`MathMode`] for the supported wrapper values and combinations.
260    ///
261    /// # Errors
262    ///
263    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
264    /// rejects the math mode.
265    pub fn set_math_mode(&self, mode: MathMode) -> Result<()> {
266        self.bind()?;
267        unsafe {
268            try_ffi!(sys::cusolverDnSetMathMode(self.as_raw(), mode.into()))?;
269        }
270        Ok(())
271    }
272
273    /// Returns the emulation strategy configured on this handle.
274    ///
275    /// This only affects operations that use one of the emulated math modes
276    /// described by [`MathMode`].
277    ///
278    /// # Errors
279    ///
280    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
281    /// cannot report the emulation strategy.
282    pub fn emulation_strategy(&self) -> Result<EmulationStrategy> {
283        self.bind()?;
284
285        let mut strategy = EmulationStrategy::Default.into();
286        unsafe {
287            try_ffi!(sys::cusolverDnGetEmulationStrategy(
288                self.as_raw(),
289                &raw mut strategy,
290            ))?;
291        }
292        Ok(strategy.into())
293    }
294
295    /// Sets the emulation strategy for operations executed through this handle.
296    ///
297    /// This only affects operations that use one of the emulated math modes
298    /// described by [`MathMode`].
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if the CUDA context cannot be bound or if cuSOLVER
303    /// rejects the emulation strategy.
304    pub fn set_emulation_strategy(&self, strategy: EmulationStrategy) -> Result<()> {
305        self.bind()?;
306        unsafe {
307            try_ffi!(sys::cusolverDnSetEmulationStrategy(
308                self.as_raw(),
309                strategy.into(),
310            ))?;
311        }
312        Ok(())
313    }
314
315    /// Installs the cuSOLVER logger callback.
316    ///
317    /// # Safety
318    ///
319    /// `callback`, if present, must remain valid for use by cuSOLVER and must
320    /// follow the callback ABI expected by the library.
321    ///
322    /// # Errors
323    ///
324    /// Returns an error if cuSOLVER rejects the callback.
325    pub unsafe fn set_logger_callback(callback: sys::cusolverDnLoggerCallback_t) -> Result<()> {
326        unsafe {
327            try_ffi!(sys::cusolverDnLoggerSetCallback(callback))?;
328        }
329        Ok(())
330    }
331
332    /// Sets the cuSOLVER logger verbosity level.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if cuSOLVER rejects the logging level.
337    pub fn set_logger_level(level: i32) -> Result<()> {
338        unsafe {
339            try_ffi!(sys::cusolverDnLoggerSetLevel(level))?;
340        }
341        Ok(())
342    }
343
344    /// Sets the cuSOLVER logger mask.
345    ///
346    /// # Errors
347    ///
348    /// Returns an error if cuSOLVER rejects the logging mask.
349    pub fn set_logger_mask(mask: i32) -> Result<()> {
350        unsafe {
351            try_ffi!(sys::cusolverDnLoggerSetMask(mask))?;
352        }
353        Ok(())
354    }
355
356    /// Sets the FILE handle used for cuSOLVER logging.
357    ///
358    /// Once registered, the file handle must remain open until another handle is
359    /// installed or logging is disabled.
360    ///
361    /// # Safety
362    ///
363    /// `file` must be a valid `FILE` handle for as long as cuSOLVER may write to it.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if cuSOLVER rejects the file handle.
368    pub unsafe fn set_logger_file(file: *mut sys::FILE) -> Result<()> {
369        unsafe {
370            try_ffi!(sys::cusolverDnLoggerSetFile(file))?;
371        }
372        Ok(())
373    }
374
375    /// Sets the cuSOLVER logging output file by path.
376    ///
377    /// # Errors
378    ///
379    /// Returns an error if `path` cannot be converted to a C string or if
380    /// cuSOLVER cannot open the log file.
381    pub fn set_logger_path(path: impl AsRef<Path>) -> Result<()> {
382        let path = path_to_cstring(path.as_ref())?;
383        unsafe {
384            try_ffi!(sys::cusolverDnLoggerOpenFile(path.as_ptr()))?;
385        }
386        Ok(())
387    }
388
389    /// Disables cuSOLVER logging for the current process.
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if cuSOLVER cannot disable logging.
394    pub fn disable_logger() -> Result<()> {
395        unsafe {
396            try_ffi!(sys::cusolverDnLoggerForceDisable())?;
397        }
398        Ok(())
399    }
400
401    /// Returns the raw cuSOLVER dense handle.
402    ///
403    /// The returned handle is borrowed and remains valid only while this
404    /// context and its underlying CUDA context are alive.
405    pub fn as_raw(&self) -> sys::cusolverDnHandle_t {
406        self.handle.raw
407    }
408
409    /// Consumes the context and returns the raw cuSOLVER dense handle without
410    /// destroying it.
411    ///
412    /// The caller becomes responsible for eventually destroying the returned
413    /// handle with cuSOLVER.
414    pub fn into_raw(self) -> sys::cusolverDnHandle_t {
415        let context = ManuallyDrop::new(self);
416        context.handle.raw
417    }
418}
419
420impl Drop for Handle {
421    fn drop(&mut self) {
422        if let Err(err) = self.cuda_ctx.bind() {
423            #[cfg(debug_assertions)]
424            eprintln!("failed to bind cuda context before destroying cusolver handle: {err}");
425        }
426
427        unsafe {
428            if let Err(err) = try_ffi!(sys::cusolverDnDestroy(self.raw)) {
429                #[cfg(debug_assertions)]
430                eprintln!("failed to destroy cusolver context: {err}");
431            }
432        }
433    }
434}