ort/ep/cuda.rs
1use alloc::string::ToString;
2use core::ops::BitOr;
3
4use super::{ArenaExtendStrategy, ExecutionProvider, ExecutionProviderOptions, RegisterError};
5use crate::{error::Result, session::builder::SessionBuilder};
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 supported_by_platform(&self) -> bool {
364 cfg!(any(all(target_os = "linux", any(target_arch = "aarch64", target_arch = "x86_64")), all(target_os = "windows", target_arch = "x86_64")))
365 }
366
367 #[allow(unused, unreachable_code)]
368 fn register(&self, session_builder: &mut SessionBuilder) -> Result<(), RegisterError> {
369 #[cfg(any(feature = "load-dynamic", feature = "cuda"))]
370 {
371 use core::ptr;
372
373 use crate::{AsPointer, ortsys, util};
374
375 let mut cuda_options: *mut ort_sys::OrtCUDAProviderOptionsV2 = ptr::null_mut();
376 ortsys![unsafe CreateCUDAProviderOptions(&mut cuda_options)?];
377 let _guard = util::run_on_drop(|| {
378 ortsys![unsafe ReleaseCUDAProviderOptions(cuda_options)];
379 });
380
381 let ffi_options = self.options.to_ffi();
382 ortsys![unsafe UpdateCUDAProviderOptions(
383 cuda_options,
384 ffi_options.key_ptrs(),
385 ffi_options.value_ptrs(),
386 ffi_options.len()
387 )?];
388
389 ortsys![unsafe SessionOptionsAppendExecutionProvider_CUDA_V2(session_builder.ptr_mut(), cuda_options)?];
390 return Ok(());
391 }
392
393 Err(RegisterError::MissingFeature)
394 }
395}
396
397// Take care in how these are ordered, since some of them depend on each other. Dependencies need to be loaded before
398// their dependents.
399#[cfg(windows)]
400pub const CUDA_DYLIBS: &[&str] = &["cudart64_12.dll", "cublasLt64_12.dll", "cublas64_12.dll", "cufft64_11.dll"];
401#[cfg(not(windows))]
402pub const CUDA_DYLIBS: &[&str] = &["libcudart.so.12", "libcublasLt.so.12", "libcublas.so.12", "libnvrtc.so.12", "libcurand.so.10", "libcufft.so.11"];
403
404#[cfg(windows)]
405pub const CUDNN_DYLIBS: &[&str] = &[
406 "cudnn64_9.dll",
407 "cudnn_graph64_9.dll",
408 "cudnn_ops64_9.dll",
409 "cudnn_heuristic64_9.dll",
410 "cudnn_adv64_9.dll",
411 "cudnn_cnn64_9.dll",
412 "cudnn_engines_precompiled64_9.dll",
413 "cudnn_engines_runtime_compiled64_9.dll"
414];
415#[cfg(not(windows))]
416pub const CUDNN_DYLIBS: &[&str] = &[
417 "libcudnn.so.9",
418 "libcudnn_graph.so.9",
419 "libcudnn_ops.so.9",
420 "libcudnn_heuristic.so.9",
421 "libcudnn_adv.so.9",
422 "libcudnn_cnn.so.9",
423 "libcudnn_engines_precompiled.so.9",
424 "libcudnn_engines_runtime_compiled.so.9"
425];
426
427/// Preload the dylibs required by CUDA/cuDNN.
428///
429/// This attempts to load all dynamic libraries required by the CUDA execution provider from the given CUDA and cuDNN
430/// directories, if they are provided. Passing `None` will prevent preloading binaries for that component. This function
431/// will immediately return with an error when a library fails to load, without attempting to load the rest of the
432/// libraries.
433///
434/// Preloading a library in this way will prioritize it in the search order when the CUDA EP attempts to load its
435/// dependencies, effectively allowing you to customize the CUDA install path without modifying the `PATH` environment
436/// variable. Note that this function intentionally leaks memory; see [`crate::util::preload_dylib`] for more
437/// information.
438///
439/// ```
440/// # use std::path::Path;
441/// use ort::ep;
442///
443/// let cuda_root = Path::new(r#"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6\bin"#);
444/// let cudnn_root = Path::new(r#"D:\cudnn_9.8.0"#);
445///
446/// // Load CUDA & cuDNN
447/// let _ = ep::cuda::preload_dylibs(Some(cuda_root), Some(cudnn_root));
448///
449/// // Only preload cuDNN
450/// let _ = ep::cuda::preload_dylibs(None, Some(cudnn_root));
451/// ```
452#[cfg_attr(docsrs, doc(cfg(any(feature = "preload-dylibs", feature = "load-dynamic"))))]
453#[cfg(all(feature = "preload-dylibs", not(target_arch = "wasm32")))]
454pub fn preload_dylibs(cuda_root_dir: Option<&std::path::Path>, cudnn_root_dir: Option<&std::path::Path>) -> Result<()> {
455 use crate::util::preload_dylib;
456 if let Some(cuda_root_dir) = cuda_root_dir {
457 for dylib in CUDA_DYLIBS {
458 preload_dylib(cuda_root_dir.join(dylib)).map_err(|e| crate::Error::new(format!("Failed to preload `{dylib}`: {e}")))?;
459 }
460 }
461 if let Some(cudnn_root_dir) = cudnn_root_dir {
462 for dylib in CUDNN_DYLIBS {
463 preload_dylib(cudnn_root_dir.join(dylib)).map_err(|e| crate::Error::new(format!("Failed to preload `{dylib}`: {e}")))?;
464 }
465 }
466 Ok(())
467}