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}