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}