1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! GPU backend abstraction.
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::string::String;
/// GPU backend type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum GpuBackend {
/// Automatically select best available backend.
Auto,
/// NVIDIA CUDA backend (uses cuFFT).
Cuda,
/// Apple Metal backend.
Metal,
}
impl GpuBackend {
/// Check if this backend is available on the current system.
#[must_use]
pub fn is_available(self) -> bool {
match self {
Self::Auto => {
#[cfg(feature = "cuda")]
if super::cuda::is_available() {
return true;
}
#[cfg(feature = "metal")]
if super::metal::is_available() {
return true;
}
false
}
Self::Cuda => {
#[cfg(feature = "cuda")]
{
super::cuda::is_available()
}
#[cfg(not(feature = "cuda"))]
{
false
}
}
Self::Metal => {
#[cfg(feature = "metal")]
{
super::metal::is_available()
}
#[cfg(not(feature = "metal"))]
{
false
}
}
}
}
/// Get the human-readable name of this backend.
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Auto => "Auto",
Self::Cuda => "CUDA",
Self::Metal => "Metal",
}
}
}
/// GPU device capabilities.
#[derive(Debug, Clone)]
pub struct GpuCapabilities {
/// Backend type.
pub backend: GpuBackend,
/// Device name.
pub device_name: String,
/// Total device memory in bytes.
pub total_memory: u64,
/// Available device memory in bytes.
pub available_memory: u64,
/// Maximum supported FFT size.
pub max_fft_size: usize,
/// Whether f64 (double precision) is supported.
pub supports_f64: bool,
/// Whether f16 (half precision) is supported.
pub supports_f16: bool,
/// Number of compute units.
pub compute_units: u32,
/// Maximum work group size.
pub max_workgroup_size: u32,
/// Whether transforms on this backend actually run on the GPU.
///
/// `true` means kernels are dispatched to device hardware (Metal today).
/// `false` means the backend currently emulates the transform on the CPU
/// (the CUDA backend, pending real kernel-launch support in oxicuda-fft).
/// Query [`crate::gpu::GpuFft::execution_target`] for the same information
/// at the plan level.
pub hardware_accelerated: bool,
}
impl Default for GpuCapabilities {
fn default() -> Self {
Self {
backend: GpuBackend::Auto,
device_name: String::new(),
total_memory: 0,
available_memory: 0,
max_fft_size: 0,
supports_f64: false,
supports_f16: false,
compute_units: 0,
max_workgroup_size: 0,
hardware_accelerated: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_name() {
assert_eq!(GpuBackend::Cuda.name(), "CUDA");
assert_eq!(GpuBackend::Metal.name(), "Metal");
assert_eq!(GpuBackend::Auto.name(), "Auto");
}
#[test]
fn test_backend_availability() {
// These tests just verify the functions don't panic
let _ = GpuBackend::Cuda.is_available();
let _ = GpuBackend::Metal.is_available();
let _ = GpuBackend::Auto.is_available();
}
}