Skip to main content

singe_cutensor/
context.rs

1#[allow(unused_imports)]
2use crate::error::Status;
3
4use std::{path::Path, ptr, sync::Arc};
5
6use singe_core::path_to_cstring;
7use singe_cuda::{context::Context as CudaContext, stream::Stream};
8
9use crate::{
10    error::{Error, Result},
11    sys, try_ffi,
12    types::{LoggerLevel, LoggerMask},
13    utility::to_u32,
14};
15
16/// A stateful cuTENSOR handle.
17///
18/// Use one context per host thread or concurrent task. The handle is movable
19/// between threads, but it is intentionally not `Clone` or `Sync`.
20#[derive(Debug)]
21pub struct Context {
22    handle: Handle,
23}
24
25#[derive(Debug)]
26struct Handle {
27    raw: sys::cutensorHandle_t,
28    cuda_ctx: Arc<CudaContext>,
29}
30
31#[derive(Debug, Clone)]
32pub(crate) struct ContextRef {
33    raw: sys::cutensorHandle_t,
34    cuda_ctx: Arc<CudaContext>,
35}
36
37// cuTENSOR handles own mutable plan-cache state. The owning wrapper and
38// lightweight references may move between threads, but are not shared as Sync.
39unsafe impl Send for Handle {}
40unsafe impl Send for ContextRef {}
41
42impl Context {
43    /// Initializes the cuTENSOR library and allocates the memory for the library context.
44    ///
45    /// The device associated with a particular cuTENSOR handle is assumed to remain unchanged after the [`Context::create`] call.
46    /// To use a different device, make that device current through the CUDA wrapper crate and create a new cuTENSOR handle with [`Context::create`].
47    ///
48    /// Each handle has a plan cache that stores least-recently-used cuTENSOR plans.
49    /// Its default capacity is 64, but it can be changed with [`Context::resize_plan_cache`].
50    /// See the Plan Cache Guide for more information.
51    ///
52    /// The returned [`Context`] frees the cuTENSOR handle when dropped.
53    ///
54    /// This blocking call is thread-safe but not reentrant.
55    ///
56    /// # Errors
57    ///
58    /// Returns an error if the CUDA context cannot be bound, if cuTENSOR cannot
59    /// create a handle, or if cuTENSOR returns a null handle.
60    pub fn create(cuda_ctx: &Arc<CudaContext>) -> Result<Self> {
61        cuda_ctx.bind()?;
62
63        let mut handle = ptr::null_mut();
64        unsafe {
65            try_ffi!(sys::cutensorCreate(&raw mut handle))?;
66        }
67
68        if handle.is_null() {
69            return Err(Error::NullHandle);
70        }
71
72        Ok(Self {
73            handle: Handle {
74                raw: handle,
75                cuda_ctx: Arc::clone(cuda_ctx),
76            },
77        })
78    }
79
80    pub fn cuda_context(&self) -> &Arc<CudaContext> {
81        &self.handle.cuda_ctx
82    }
83
84    pub fn bind(&self) -> Result<()> {
85        Ok(self.cuda_context().bind()?)
86    }
87
88    pub(crate) fn as_context_ref(&self) -> ContextRef {
89        ContextRef {
90            raw: self.handle.raw,
91            cuda_ctx: Arc::clone(&self.handle.cuda_ctx),
92        }
93    }
94
95    /// Resizes the plan cache.
96    ///
97    /// Changes the number of plans that can be stored in the plan cache of the handle.
98    ///
99    /// Resizing invalidates the cache.
100    ///
101    /// The call is not thread-safe, but the resulting cache can be shared across threads safely.
102    ///
103    /// This non-blocking call is neither reentrant nor thread-safe.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the context cannot be bound, `entry_count` cannot
108    /// be represented for cuTENSOR, or cuTENSOR rejects the new cache size.
109    pub fn resize_plan_cache(&self, entry_count: usize) -> Result<()> {
110        self.bind()?;
111
112        let entry_count = to_u32(entry_count, "entry_count")?;
113
114        unsafe {
115            try_ffi!(sys::cutensorHandleResizePlanCache(
116                self.as_raw(),
117                entry_count,
118            ))?;
119        }
120        Ok(())
121    }
122
123    /// Writes the plan cache that belongs to this handle to file.
124    ///
125    /// This non-blocking call is thread-safe but not reentrant.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if the context cannot be bound, `path` cannot be
130    /// converted to a C string, no plan cache is attached, or cuTENSOR cannot
131    /// write the file.
132    pub fn write_plan_cache_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
133        self.bind()?;
134
135        let path = path_to_cstring(path.as_ref())?;
136        unsafe {
137            try_ffi!(sys::cutensorHandleWritePlanCacheToFile(
138                self.as_raw(),
139                path.as_ptr(),
140            ))?;
141        }
142        Ok(())
143    }
144
145    /// Reads a plan cache from file and overwrites this handle's cache lines.
146    ///
147    /// A cache is only valid for the same cuTENSOR version, CUDA version, and GPU
148    /// architecture, including multiprocessor count.
149    ///
150    /// This non-blocking call is thread-safe but not reentrant.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if the context cannot be bound, `path` cannot be
155    /// converted to a C string, the file cannot be read, the stored cache is
156    /// incompatible with this cuTENSOR/CUDA/device configuration, the current
157    /// cache is too small for the stored data, or cuTENSOR rejects the cache.
158    pub fn read_plan_cache_from_file(&self, path: impl AsRef<Path>) -> Result<u32> {
159        self.bind()?;
160
161        let path = path_to_cstring(path.as_ref())?;
162        let mut cachelines_read = 0;
163        unsafe {
164            try_ffi!(sys::cutensorHandleReadPlanCacheFromFile(
165                self.as_raw(),
166                path.as_ptr(),
167                &raw mut cachelines_read,
168            ))?;
169        }
170        Ok(cachelines_read)
171    }
172
173    /// Writes the per-library kernel cache to file.
174    ///
175    /// Writes the just-in-time compiled kernels to the provided file.
176    /// These kernels belong to the library, not to the handle.
177    ///
178    /// This non-blocking call is thread-safe but not reentrant.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if the context cannot be bound, `path` cannot be
183    /// converted to a C string, JIT kernel caching is unsupported for this
184    /// operating system, CUDA Toolkit, or device, or cuTENSOR cannot write the
185    /// file.
186    pub fn write_kernel_cache_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
187        self.bind()?;
188
189        let path = path_to_cstring(path.as_ref())?;
190        unsafe {
191            try_ffi!(sys::cutensorWriteKernelCacheToFile(
192                self.as_raw(),
193                path.as_ptr()
194            ))?;
195        }
196        Ok(())
197    }
198
199    /// Reads a kernel cache from file and adds all non-existing JIT compiled kernels to the kernel cache.
200    ///
201    /// A cache is only valid for the same cuTENSOR version, CUDA version, and GPU
202    /// architecture, including multiprocessor count.
203    ///
204    /// This non-blocking call is thread-safe but not reentrant.
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if the context cannot be bound, `path` cannot be
209    /// converted to a C string, the file cannot be read, the stored cache is
210    /// incompatible with this cuTENSOR/CUDA/device configuration, JIT kernel
211    /// caching is unsupported for this operating system, CUDA Toolkit, or
212    /// device, or cuTENSOR rejects the cache.
213    pub fn read_kernel_cache_from_file(&self, path: impl AsRef<Path>) -> Result<()> {
214        self.bind()?;
215
216        let path = path_to_cstring(path.as_ref())?;
217        unsafe {
218            try_ffi!(sys::cutensorReadKernelCacheFromFile(
219                self.as_raw(),
220                path.as_ptr()
221            ))?;
222        }
223        Ok(())
224    }
225
226    pub fn ensure_stream(&self, stream: &Stream) -> Result<()> {
227        if self.cuda_context().as_ref() != stream.context() {
228            return Err(Error::StreamContextMismatch);
229        }
230
231        self.bind()
232    }
233
234    /// Sets the logging callback.
235    ///
236    /// # Safety
237    ///
238    /// `callback` must remain valid for every later cuTENSOR log event that may
239    /// call it and must follow cuTENSOR's callback requirements.
240    ///
241    /// # Errors
242    ///
243    /// Returns an error if cuTENSOR rejects the callback.
244    pub unsafe fn set_logger_callback(callback: sys::cutensorLoggerCallback_t) -> Result<()> {
245        unsafe {
246            try_ffi!(sys::cutensorLoggerSetCallback(callback))?;
247        }
248        Ok(())
249    }
250
251    /// Sets the logging level.
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if cuTENSOR rejects the logging level.
256    pub fn set_logger_level(level: LoggerLevel) -> Result<()> {
257        unsafe {
258            try_ffi!(sys::cutensorLoggerSetLevel(level.as_raw()))?;
259        }
260        Ok(())
261    }
262
263    /// Sets the log mask.
264    ///
265    /// # Errors
266    ///
267    /// Returns an error if cuTENSOR rejects the logging mask.
268    pub fn set_logger_mask(mask: LoggerMask) -> Result<()> {
269        unsafe {
270            try_ffi!(sys::cutensorLoggerSetMask(mask.bits()))?;
271        }
272        Ok(())
273    }
274
275    /// Sets the logging output file.
276    ///
277    /// # Safety
278    ///
279    /// `file` must be a valid C `FILE` pointer for every later cuTENSOR log write
280    /// that may use it, or a null pointer if cuTENSOR accepts that for the active
281    /// logger configuration.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if cuTENSOR rejects the file pointer.
286    pub unsafe fn set_logger_file(file: *mut sys::FILE) -> Result<()> {
287        unsafe {
288            try_ffi!(sys::cutensorLoggerSetFile(file))?;
289        }
290        Ok(())
291    }
292
293    /// Sets the logging output file by path.
294    ///
295    /// # Errors
296    ///
297    /// Returns an error if `path` cannot be converted to a C string or if
298    /// cuTENSOR cannot open the log file.
299    pub fn set_logger_path(path: impl AsRef<Path>) -> Result<()> {
300        let path = path_to_cstring(path.as_ref())?;
301        unsafe {
302            try_ffi!(sys::cutensorLoggerOpenFile(path.as_ptr()))?;
303        }
304        Ok(())
305    }
306
307    /// Disables logging for the entire run.
308    ///
309    /// # Errors
310    ///
311    /// Returns an error if cuTENSOR cannot disable logging.
312    pub fn disable_logger() -> Result<()> {
313        unsafe {
314            try_ffi!(sys::cutensorLoggerForceDisable())?;
315        }
316        Ok(())
317    }
318
319    /// Returns the raw cuTENSOR handle.
320    ///
321    /// The returned handle is borrowed and remains valid only while this
322    /// context and its underlying CUDA context are alive.
323    pub fn as_raw(&self) -> sys::cutensorHandle_t {
324        self.handle.raw
325    }
326}
327
328impl ContextRef {
329    pub fn cuda_context(&self) -> &Arc<CudaContext> {
330        &self.cuda_ctx
331    }
332
333    pub fn bind(&self) -> Result<()> {
334        Ok(self.cuda_ctx.bind()?)
335    }
336
337    pub fn ensure_stream(&self, stream: &Stream) -> Result<()> {
338        if self.cuda_context().as_ref() != stream.context() {
339            return Err(Error::StreamContextMismatch);
340        }
341
342        self.bind()
343    }
344
345    /// Returns the raw cuTENSOR handle.
346    ///
347    /// The returned handle is borrowed and remains valid only while this
348    /// context reference and its underlying CUDA context are alive.
349    pub fn as_raw(&self) -> sys::cutensorHandle_t {
350        self.raw
351    }
352}
353
354impl Drop for Handle {
355    fn drop(&mut self) {
356        if let Err(err) = self.cuda_ctx.bind() {
357            #[cfg(debug_assertions)]
358            eprintln!("failed to bind cuda context before destroying cutensor handle: {err}");
359        }
360
361        unsafe {
362            if let Err(err) = try_ffi!(sys::cutensorDestroy(self.raw)) {
363                #[cfg(debug_assertions)]
364                eprintln!("failed to destroy cutensor context: {err}");
365            }
366        }
367    }
368}