ort/ep/cuda.rs
1use alloc::string::ToString;
2use core::{ops::BitOr, ptr};
3
4use super::{ArenaExtendStrategy, ExecutionProvider, ExecutionProviderOptions};
5use crate::{AsPointer, error::Result, ortsys, session::builder::SessionBuilder, util};
6
7// https://github.com/microsoft/onnxruntime/blob/ffceed9d44f2f3efb9dd69fa75fea51163c91d91/onnxruntime/contrib_ops/cpu/bert/attention_common.h#L160-L171
8#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[repr(transparent)]
10pub struct AttentionBackend(u32);
11
12impl AttentionBackend {
13 pub const FLASH_ATTENTION: Self = Self(1 << 0);
14 pub const EFFICIENT_ATTENTION: Self = Self(1 << 1);
15 pub const TRT_FUSED_ATTENTION: Self = Self(1 << 2);
16 pub const CUDNN_FLASH_ATTENTION: Self = Self(1 << 3);
17 pub const MATH: Self = Self(1 << 4);
18
19 pub const TRT_FLASH_ATTENTION: Self = Self(1 << 5);
20 pub const TRT_CROSS_ATTENTION: Self = Self(1 << 6);
21 pub const TRT_CAUSAL_ATTENTION: Self = Self(1 << 7);
22
23 pub const LEAN_ATTENTION: Self = Self(1 << 8);
24
25 pub fn none() -> Self {
26 AttentionBackend(0)
27 }
28
29 pub fn all() -> Self {
30 Self::FLASH_ATTENTION
31 | Self::EFFICIENT_ATTENTION
32 | Self::TRT_FUSED_ATTENTION
33 | Self::CUDNN_FLASH_ATTENTION
34 | Self::MATH
35 | Self::TRT_FLASH_ATTENTION
36 | Self::TRT_CROSS_ATTENTION
37 | Self::TRT_CAUSAL_ATTENTION
38 }
39}
40
41impl BitOr for AttentionBackend {
42 type Output = Self;
43 fn bitor(self, rhs: Self) -> Self::Output {
44 Self(rhs.0 | self.0)
45 }
46}
47
48/// The type of search done for cuDNN convolution algorithms.
49#[derive(Debug, Clone, Default)]
50pub enum ConvAlgorithmSearch {
51 /// Expensive exhaustive benchmarking using [`cudnnFindConvolutionForwardAlgorithmEx`][exhaustive].
52 /// This function will attempt all possible algorithms for `cudnnConvolutionForward` to find the fastest algorithm.
53 /// Exhaustive search trades off between memory usage and speed. The first execution of a graph will be slow while
54 /// possible convolution algorithms are tested.
55 ///
56 /// [exhaustive]: https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnnFindConvolutionForwardAlgorithmEx
57 #[default]
58 Exhaustive,
59 /// Lightweight heuristic-based search using [`cudnnGetConvolutionForwardAlgorithm_v7`][heuristic].
60 /// Heuristic search sorts available convolution algorithms by expected (based on internal heuristic) relative
61 /// performance.
62 ///
63 /// [heuristic]: https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnnGetConvolutionForwardAlgorithm_v7
64 Heuristic,
65 /// Uses the default convolution algorithm, [`CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM`][fwdalgo].
66 /// The default algorithm may not have the best performance depending on specific hardware used. It's recommended to
67 /// use [`Exhaustive`] or [`Heuristic`] to search for a faster algorithm instead. However, `Default` does have its
68 /// uses, such as when available memory is tight.
69 ///
70 /// > **NOTE**: This name may be confusing as this is not the default search algorithm for the CUDA EP. The default
71 /// > search algorithm is actually [`Exhaustive`].
72 ///
73 /// [fwdalgo]: https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnnConvolutionFwdAlgo_t
74 /// [`Exhaustive`]: ConvAlgorithmSearch::Exhaustive
75 /// [`Heuristic`]: ConvAlgorithmSearch::Heuristic
76 Default
77}
78
79/// [CUDA execution provider](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html) for NVIDIA
80/// CUDA-enabled GPUs.
81#[derive(Debug, Default, Clone)]
82pub struct CUDA {
83 options: ExecutionProviderOptions
84}
85
86super::impl_ep!(arbitrary; CUDA);
87
88impl CUDA {
89 /// Configures which device the EP should use.
90 ///
91 /// ```
92 /// # use ort::{ep, session::Session};
93 /// # fn main() -> ort::Result<()> {
94 /// let ep = ep::CUDA::default().with_device_id(0).build();
95 /// # Ok(())
96 /// # }
97 /// ```
98 #[must_use]
99 pub fn with_device_id(mut self, device_id: i32) -> Self {
100 self.options.set("device_id", device_id.to_string());
101 self
102 }
103
104 /// Configure the size limit of the device memory arena in bytes.
105 ///
106 /// This only controls how much memory can be allocated to the *arena* - actual memory usage may be higher due to
107 /// internal CUDA allocations, like those required for different [`ConvAlgorithmSearch`] options.
108 ///
109 /// ```
110 /// # use ort::{ep, session::Session};
111 /// # fn main() -> ort::Result<()> {
112 /// let ep = ep::CUDA::default().with_memory_limit(2 * 1024 * 1024 * 1024).build();
113 /// # Ok(())
114 /// # }
115 /// ```
116 #[must_use]
117 pub fn with_memory_limit(mut self, limit: usize) -> Self {
118 self.options.set("gpu_mem_limit", limit.to_string());
119 self
120 }
121
122 /// Configure the strategy for extending the device's memory arena.
123 ///
124 /// ```
125 /// # use ort::{ep::{self, ArenaExtendStrategy}, session::Session};
126 /// # fn main() -> ort::Result<()> {
127 /// let ep = ep::CUDA::default()
128 /// .with_arena_extend_strategy(ArenaExtendStrategy::SameAsRequested)
129 /// .build();
130 /// # Ok(())
131 /// # }
132 /// ```
133 #[must_use]
134 pub fn with_arena_extend_strategy(mut self, strategy: ArenaExtendStrategy) -> Self {
135 self.options.set(
136 "arena_extend_strategy",
137 match strategy {
138 ArenaExtendStrategy::NextPowerOfTwo => "kNextPowerOfTwo",
139 ArenaExtendStrategy::SameAsRequested => "kSameAsRequested"
140 }
141 );
142 self
143 }
144
145 /// Controls the search mode used to select a kernel for `Conv` nodes.
146 ///
147 /// cuDNN, the library used by ONNX Runtime's CUDA EP for many operations, provides many different implementations
148 /// of the `Conv` node. Each of these implementations has different performance characteristics depending on the
149 /// exact hardware and model/input size used. This option controls how cuDNN should determine which implementation
150 /// to use.
151 ///
152 /// The default search algorithm, [`Exhaustive`][exh], will benchmark all available implementations and use the most
153 /// performant one. This option is very resource intensive (both computationally on first run and peak-memory-wise),
154 /// but ensures best performance. It is roughly equivalent to setting `torch.backends.cudnn.benchmark = True` with
155 /// PyTorch. See also [`CUDA::with_conv_max_workspace`] to configure how much memory the exhaustive
156 /// search can use (the default is unlimited).
157 ///
158 /// A less resource-intensive option is [`Heuristic`][heu]. Rather than benchmarking every implementation,
159 /// an optimal implementation is chosen based on a set of heuristics, thus saving compute. [`Heuristic`][heu] should
160 /// generally choose an optimal convolution algorithm, except in some corner cases.
161 ///
162 /// [`Default`][def] can also be passed to instruct cuDNN to always use the default implementation (which is rarely
163 /// the most optimal). Note that the "Default" here refers to the **default convolution algorithm** being used, it
164 /// is not the *default behavior* (that would be [`Exhaustive`][exh]).
165 ///
166 /// ```
167 /// # use ort::{ep, session::Session};
168 /// # fn main() -> ort::Result<()> {
169 /// let ep = ep::CUDA::default()
170 /// .with_conv_algorithm_search(ep::cuda::ConvAlgorithmSearch::Heuristic)
171 /// .build();
172 /// # Ok(())
173 /// # }
174 /// ```
175 ///
176 /// [exh]: ConvAlgorithmSearch::Exhaustive
177 /// [heu]: ConvAlgorithmSearch::Heuristic
178 /// [def]: ConvAlgorithmSearch::Default
179 #[must_use]
180 pub fn with_conv_algorithm_search(mut self, search: ConvAlgorithmSearch) -> Self {
181 self.options.set(
182 "cudnn_conv_algo_search",
183 match search {
184 ConvAlgorithmSearch::Exhaustive => "EXHAUSTIVE",
185 ConvAlgorithmSearch::Heuristic => "HEURISTIC",
186 ConvAlgorithmSearch::Default => "DEFAULT"
187 }
188 );
189 self
190 }
191
192 /// Configure whether the [`Exhaustive`][ConvAlgorithmSearch::Exhaustive] search can use as much memory as it
193 /// needs.
194 ///
195 /// The default is `true`. When `false`, the memory used for the search is limited to 32 MB, which will impact its
196 /// ability to find an optimal convolution algorithm.
197 ///
198 /// ```
199 /// # use ort::{ep, session::Session};
200 /// # fn main() -> ort::Result<()> {
201 /// let ep = ep::CUDA::default().with_conv_max_workspace(false).build();
202 /// # Ok(())
203 /// # }
204 /// ```
205 #[must_use]
206 pub fn with_conv_max_workspace(mut self, enable: bool) -> Self {
207 self.options.set("cudnn_conv_use_max_workspace", if enable { "1" } else { "0" });
208 self
209 }
210
211 // Here once lied `do_copy_in_default_stream`. After reading through upstream it doesn't seem like this option is
212 // used anymore, so the setter here was removed to reduce confusion.
213
214 /// Configure whether or not to pad 3-dimensional convolutions to `[N, C, 1, D]` (as opposed to the default `[N, C,
215 /// D, 1]`).
216 ///
217 /// Enabling this option might significantly improve performance on devices like the A100. This does not affect
218 /// convolution operations that do not use 3-dimensional input shapes, or the *result* of such operations.
219 ///
220 /// ```
221 /// # use ort::{ep, session::Session};
222 /// # fn main() -> ort::Result<()> {
223 /// let ep = ep::CUDA::default().with_conv1d_pad_to_nc1d(true).build();
224 /// # Ok(())
225 /// # }
226 /// ```
227 #[must_use]
228 pub fn with_conv1d_pad_to_nc1d(mut self, enable: bool) -> Self {
229 self.options.set("cudnn_conv1d_pad_to_nc1d", if enable { "1" } else { "0" });
230 self
231 }
232
233 /// Configures whether to create a CUDA graph.
234 ///
235 /// CUDA graphs eliminate the overhead of launching kernels sequentially by capturing the launch sequence into a
236 /// graph that is 'replayed' across runs, reducing CPU overhead and possibly improving performance.
237 ///
238 /// Using CUDA graphs comes with limitations, notably:
239 /// - Models with control flow operators (like `If`, `Loop`, or `Scan`) are not supported.
240 /// - Input/output shapes cannot change across inference calls.
241 /// - The address of inputs/outputs cannot change across inference calls, so
242 /// [`IoBinding`](crate::session::IoBinding) must be used.
243 /// - `Session`s using CUDA graphs are technically not `Send` or `Sync`.
244 ///
245 /// Consult the [ONNX Runtime documentation on CUDA graphs](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html#using-cuda-graphs-preview) for more information.
246 ///
247 /// ```
248 /// # use ort::{ep, session::Session};
249 /// # fn main() -> ort::Result<()> {
250 /// let ep = ep::CUDA::default().with_cuda_graph(true).build();
251 /// # Ok(())
252 /// # }
253 /// ```
254 #[must_use]
255 pub fn with_cuda_graph(mut self, enable: bool) -> Self {
256 self.options.set("enable_cuda_graph", if enable { "1" } else { "0" });
257 self
258 }
259
260 /// Enable 'strict' mode for `SkipLayerNorm` nodes (created via fusion of `Add` & `LayerNorm` nodes).
261 ///
262 /// `SkipLayerNorm`'s strict mode trades performance for accuracy. The default is `false` (strict mode disabled).
263 ///
264 /// ```
265 /// # use ort::{ep, session::Session};
266 /// # fn main() -> ort::Result<()> {
267 /// let ep = ep::CUDA::default().with_skip_layer_norm_strict_mode(true).build();
268 /// # Ok(())
269 /// # }
270 /// ```
271 #[must_use]
272 pub fn with_skip_layer_norm_strict_mode(mut self, enable: bool) -> Self {
273 self.options.set("enable_skip_layer_norm_strict_mode", if enable { "1" } else { "0" });
274 self
275 }
276
277 /// Enable the usage of the reduced-precision [TensorFloat-32](https://blogs.nvidia.com/blog/tensorfloat-32-precision-format/)
278 /// format for matrix multiplications & convolutions.
279 ///
280 /// TensorFloat-32 is a reduced-precision floating point format available on NVIDIA GPUs since the Ampere
281 /// microarchitecture. It allows `MatMul` & `Conv` to run much faster on Ampere's Tensor cores. This option is
282 /// **disabled** by default.
283 ///
284 /// This option is roughly equivalent to `torch.backends.cudnn.allow_tf32 = True` &
285 /// `torch.backends.cuda.matmul.allow_tf32 = True` or `torch.set_float32_matmul_precision("medium")` in PyTorch.
286 ///
287 /// ```
288 /// # use ort::{ep, session::Session};
289 /// # fn main() -> ort::Result<()> {
290 /// let ep = ep::CUDA::default().with_tf32(true).build();
291 /// # Ok(())
292 /// # }
293 /// ```
294 #[must_use]
295 pub fn with_tf32(mut self, enable: bool) -> Self {
296 self.options.set("use_tf32", if enable { "1" } else { "0" });
297 self
298 }
299
300 /// Configure whether to prefer `[N, H, W, C]` layout operations over the default `[N, C, H, W]` layout.
301 ///
302 /// Tensor cores usually operate more efficiently with the NHWC layout, so enabling this option for
303 /// convolution-heavy models on Tensor core-enabled GPUs may provide a significant performance improvement.
304 ///
305 /// ```
306 /// # use ort::{ep, session::Session};
307 /// # fn main() -> ort::Result<()> {
308 /// let ep = ep::CUDA::default().with_prefer_nhwc(true).build();
309 /// # Ok(())
310 /// # }
311 /// ```
312 #[must_use]
313 pub fn with_prefer_nhwc(mut self, enable: bool) -> Self {
314 self.options.set("prefer_nhwc", if enable { "1" } else { "0" });
315 self
316 }
317
318 /// Use a custom CUDA device stream rather than the default one.
319 ///
320 /// # Safety
321 /// The provided `stream` must outlive the environment/session configured to use this execution provider.
322 #[must_use]
323 pub unsafe fn with_compute_stream(mut self, stream: *mut ()) -> Self {
324 self.options.set("has_user_compute_stream", "1");
325 self.options.set("user_compute_stream", (stream as usize).to_string());
326 self
327 }
328
329 /// Configures the available backends used for `Attention` nodes.
330 ///
331 /// ```
332 /// # use ort::{ep, session::Session};
333 /// # fn main() -> ort::Result<()> {
334 /// let ep = ep::CUDA::default()
335 /// .with_attention_backend(
336 /// ep::cuda::AttentionBackend::FLASH_ATTENTION | ep::cuda::AttentionBackend::TRT_FUSED_ATTENTION
337 /// )
338 /// .build();
339 /// # Ok(())
340 /// # }
341 /// ```
342 #[must_use]
343 pub fn with_attention_backend(mut self, flags: AttentionBackend) -> Self {
344 self.options.set("sdpa_kernel", flags.0.to_string());
345 self
346 }
347
348 #[must_use]
349 pub fn with_fuse_conv_bias(mut self, enable: bool) -> Self {
350 self.options.set("fuse_conv_bias", if enable { "1" } else { "0" });
351 self
352 }
353
354 // https://github.com/microsoft/onnxruntime/blob/ffceed9d44f2f3efb9dd69fa75fea51163c91d91/onnxruntime/core/providers/cuda/cuda_execution_provider_info.h#L48
355 // https://github.com/microsoft/onnxruntime/blob/fe8a10caa40f64a8fbd144e7049cf5b14c65542d/onnxruntime/core/providers/cuda/cuda_execution_provider_info.cc#L17
356}
357
358impl ExecutionProvider for CUDA {
359 fn name(&self) -> &'static str {
360 "CUDAExecutionProvider"
361 }
362
363 fn register(&self, session_builder: &mut SessionBuilder) -> Result<()> {
364 let mut cuda_options: *mut ort_sys::OrtCUDAProviderOptionsV2 = ptr::null_mut();
365 ortsys![unsafe CreateCUDAProviderOptions(&mut cuda_options)?];
366 let _guard = util::run_on_drop(|| {
367 ortsys![unsafe ReleaseCUDAProviderOptions(cuda_options)];
368 });
369
370 let ffi_options = self.options.to_ffi();
371 ortsys![unsafe UpdateCUDAProviderOptions(
372 cuda_options,
373 ffi_options.key_ptrs(),
374 ffi_options.value_ptrs(),
375 ffi_options.len()
376 )?];
377
378 ortsys![unsafe SessionOptionsAppendExecutionProvider_CUDA_V2(session_builder.ptr_mut(), cuda_options)?];
379
380 Ok(())
381 }
382}
383
384// Take care in how these are ordered, since some of them depend on each other. Dependencies need to be loaded before
385// their dependents.
386#[cfg(windows)]
387pub const CUDA_DYLIBS: &[&str] = &["cudart64_12.dll", "cublasLt64_12.dll", "cublas64_12.dll", "cufft64_11.dll"];
388#[cfg(not(windows))]
389pub const CUDA_DYLIBS: &[&str] = &["libcudart.so.12", "libcublasLt.so.12", "libcublas.so.12", "libnvrtc.so.12", "libcurand.so.10", "libcufft.so.11"];
390
391#[cfg(windows)]
392pub const CUDNN_DYLIBS: &[&str] = &[
393 "cudnn64_9.dll",
394 "cudnn_graph64_9.dll",
395 "cudnn_ops64_9.dll",
396 "cudnn_heuristic64_9.dll",
397 "cudnn_adv64_9.dll",
398 "cudnn_cnn64_9.dll",
399 "cudnn_engines_precompiled64_9.dll",
400 "cudnn_engines_runtime_compiled64_9.dll"
401];
402#[cfg(not(windows))]
403pub const CUDNN_DYLIBS: &[&str] = &[
404 "libcudnn.so.9",
405 "libcudnn_graph.so.9",
406 "libcudnn_ops.so.9",
407 "libcudnn_heuristic.so.9",
408 "libcudnn_adv.so.9",
409 "libcudnn_cnn.so.9",
410 "libcudnn_engines_precompiled.so.9",
411 "libcudnn_engines_runtime_compiled.so.9"
412];
413
414/// Preload the dylibs required by CUDA/cuDNN.
415///
416/// This attempts to load all dynamic libraries required by the CUDA execution provider from the given CUDA and cuDNN
417/// directories, if they are provided. Passing `None` will prevent preloading binaries for that component. This function
418/// will immediately return with an error when a library fails to load, without attempting to load the rest of the
419/// libraries.
420///
421/// Preloading a library in this way will prioritize it in the search order when the CUDA EP attempts to load its
422/// dependencies, effectively allowing you to customize the CUDA install path without modifying the `PATH` environment
423/// variable. Note that this function intentionally leaks memory; see [`crate::util::preload_dylib`] for more
424/// information.
425///
426/// ```
427/// # use std::path::Path;
428/// use ort::ep;
429///
430/// let cuda_root = Path::new(r#"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6\bin"#);
431/// let cudnn_root = Path::new(r#"D:\cudnn_9.8.0"#);
432///
433/// // Load CUDA & cuDNN
434/// let _ = ep::cuda::preload_dylibs(Some(cuda_root), Some(cudnn_root));
435///
436/// // Only preload cuDNN
437/// let _ = ep::cuda::preload_dylibs(None, Some(cudnn_root));
438/// ```
439#[cfg_attr(docsrs, doc(cfg(any(feature = "preload-dylibs", feature = "load-dynamic"))))]
440#[cfg(all(feature = "preload-dylibs", not(target_arch = "wasm32")))]
441pub fn preload_dylibs(cuda_root_dir: Option<&std::path::Path>, cudnn_root_dir: Option<&std::path::Path>) -> Result<()> {
442 use crate::util::preload_dylib;
443 if let Some(cuda_root_dir) = cuda_root_dir {
444 for dylib in CUDA_DYLIBS {
445 preload_dylib(cuda_root_dir.join(dylib)).map_err(|e| crate::Error::new(format!("Failed to preload `{dylib}`: {e}")))?;
446 }
447 }
448 if let Some(cudnn_root_dir) = cudnn_root_dir {
449 for dylib in CUDNN_DYLIBS {
450 preload_dylib(cudnn_root_dir.join(dylib)).map_err(|e| crate::Error::new(format!("Failed to preload `{dylib}`: {e}")))?;
451 }
452 }
453 Ok(())
454}
455
456pub mod resource {
457 use core::{ffi::c_void, ptr::NonNull};
458
459 use crate::ep::ExecutionProviderResource;
460
461 macro_rules! define_resource {
462 ($name:ident<$ty:ty>($id:literal), $conv:expr) => {
463 pub struct $name;
464 impl ExecutionProviderResource for $name {
465 const VERSION: u32 = 3;
466 type Type = $ty;
467 fn id(&self) -> u32 {
468 $id
469 }
470 fn convert(v: *const c_void) -> Self::Type {
471 ($conv)(v)
472 }
473 }
474 };
475 }
476
477 define_resource!(Stream<*const ()>(10000), |v: *const c_void| v.cast());
478 define_resource!(CuDNNHandle<*const ()>(10001), |v: *const c_void| v.cast());
479 define_resource!(CuBLASHandle<*const ()>(10002), |v: *const c_void| v.cast());
480 define_resource!(
481 DeferredCPUAllocator<Option<crate::memory::Allocator>>(10003),
482 |v: *const c_void| NonNull::new(v.cast_mut().cast()).map(|alloc| unsafe { crate::memory::Allocator::from_raw(alloc, true) })
483 );
484 define_resource!(DeviceId<u32>(10004), |v: *const c_void| v as usize as u32);
485 define_resource!(ArenaExtendStrategy<super::ArenaExtendStrategy>(10005), |v: *const c_void| match v as usize {
486 1 => super::ArenaExtendStrategy::SameAsRequested,
487 _ => super::ArenaExtendStrategy::NextPowerOfTwo
488 });
489 define_resource!(ConvAlgorithmSearch<super::ConvAlgorithmSearch>(10006), |v: *const c_void| match v as usize {
490 1 => super::ConvAlgorithmSearch::Heuristic,
491 2 => super::ConvAlgorithmSearch::Default,
492 _ => super::ConvAlgorithmSearch::Exhaustive
493 });
494 define_resource!(ConvUseMaxWorkspace<bool>(10007), |v: *const c_void| !v.is_null());
495 define_resource!(Conv1dPadToNc1d<bool>(10008), |v: *const c_void| !v.is_null());
496 define_resource!(EnableSkipLayerNormStrictMode<bool>(10009), |v: *const c_void| !v.is_null());
497 define_resource!(PreferNhwc<bool>(10010), |v: *const c_void| !v.is_null());
498 define_resource!(UseTF32<bool>(10011), |v: *const c_void| !v.is_null());
499 define_resource!(FuseConvBias<bool>(10012), |v: *const c_void| !v.is_null());
500}