optirs_gpu/backends.rs
1//! GPU backend identifiers and capability data.
2//!
3//! This module used to also define a `Backend` trait with five
4//! implementations (`CudaBackend`, `RocmBackend`, `MetalBackend`,
5//! `WgpuBackend`, `CpuBackend`) that were pure fabrication: every
6//! `allocate`/`copy_to_device`/`copy_to_host`/`launch_kernel` was a bare
7//! `Ok(())` regardless of backend availability, `DeviceCapabilities` numbers
8//! were hardcoded fiction (`"CUDA Device"`, 8 GB, compute capability `(8,
9//! 6)`, ...) on every machine, and `copy_to_host` left its destination
10//! untouched while reporting success. Nothing outside this file ever called
11//! any of it — it was dead, misleading public API surface.
12//!
13//! Real GPU access in this crate goes through [`scirs2_core::gpu`]
14//! exclusively (see [`crate::optimizers`], [`crate::multi_gpu`]). What
15//! remains here is the parts of the old surface that are still load-bearing:
16//! the [`GpuBackend`] identifier enum and the [`DeviceCapabilities`] data
17//! struct [`crate::occupancy`] uses to model launch-configuration limits —
18//! neither of those fabricates anything by existing.
19
20use thiserror::Error;
21
22/// GPU backend types supported by the optimizer
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum GpuBackend {
25 /// NVIDIA CUDA backend
26 Cuda,
27 /// AMD ROCm backend
28 Rocm,
29 /// Apple Metal backend
30 Metal,
31 /// WebGPU backend (cross-platform)
32 Wgpu,
33 /// CPU fallback (no GPU acceleration)
34 Cpu,
35}
36
37impl Default for GpuBackend {
38 fn default() -> Self {
39 #[cfg(target_os = "macos")]
40 return Self::Metal;
41
42 #[cfg(not(target_os = "macos"))]
43 return Self::Cuda;
44 }
45}
46
47/// Errors that can occur with GPU backends
48#[derive(Debug, Error)]
49pub enum BackendError {
50 #[error("Backend not available: {backend:?}")]
51 NotAvailable { backend: GpuBackend },
52
53 #[error("Backend initialization failed: {reason}")]
54 InitializationFailed { reason: String },
55
56 #[error("Operation not supported by backend: {operation}")]
57 UnsupportedOperation { operation: String },
58
59 #[error("Backend error: {message}")]
60 BackendSpecific { message: String },
61
62 #[error("Device error: {device_id}")]
63 DeviceError { device_id: u32 },
64}
65
66/// GPU device capabilities.
67///
68/// A plain data holder — nothing in this module manufactures values for it.
69/// [`crate::occupancy`] derives real [`crate::occupancy::SmResourceLimits`]
70/// from whatever is put here; populating it with honest numbers (e.g. from
71/// `scirs2_core::gpu::GpuContext::backend()` plus documented per-vendor
72/// specs) is the caller's responsibility.
73#[derive(Debug, Clone)]
74pub struct DeviceCapabilities {
75 /// Device name
76 pub name: String,
77
78 /// Total memory in bytes
79 pub total_memory: usize,
80
81 /// Available memory in bytes
82 pub available_memory: usize,
83
84 /// Supports half precision (f16)
85 pub supports_f16: bool,
86
87 /// Supports bfloat16
88 pub supports_bf16: bool,
89
90 /// Supports tensor cores
91 pub supports_tensor_cores: bool,
92
93 /// Maximum threads per block
94 pub max_threads_per_block: u32,
95
96 /// Maximum shared memory per block
97 pub max_shared_memory_per_block: usize,
98
99 /// Number of streaming multiprocessors
100 pub multiprocessor_count: u32,
101
102 /// Compute capability (major, minor)
103 pub compute_capability: (u32, u32),
104}
105
106/// Kernel launch geometry: grid/block dimensions, shared-memory footprint
107/// and (optionally) the stream to launch on.
108///
109/// A plain data holder, populated by the caller (e.g. via
110/// [`crate::utils::calculate_block_size`] or
111/// [`crate::occupancy::optimal_block_size`]) — this type does not compute or
112/// fabricate a launch configuration by itself.
113#[derive(Debug, Clone)]
114pub struct LaunchConfig {
115 /// Grid dimensions (x, y, z)
116 pub grid_size: (u32, u32, u32),
117
118 /// Block dimensions (x, y, z)
119 pub block_size: (u32, u32, u32),
120
121 /// Shared memory size in bytes
122 pub shared_memory_size: usize,
123
124 /// Backend-specific stream identifier, if the launch targets one
125 pub stream: Option<u64>,
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn default_backend_matches_the_build_platform() {
134 let backend = GpuBackend::default();
135 #[cfg(target_os = "macos")]
136 assert_eq!(backend, GpuBackend::Metal);
137 #[cfg(not(target_os = "macos"))]
138 assert_eq!(backend, GpuBackend::Cuda);
139 }
140
141 #[test]
142 fn device_capabilities_is_a_plain_data_struct() {
143 // No factory manufactures this — constructing it directly with
144 // caller-supplied numbers is the only way to get one, which is the
145 // point of deleting the fabricating `Backend` impls.
146 let caps = DeviceCapabilities {
147 name: "test device".to_string(),
148 total_memory: 1024,
149 available_memory: 512,
150 supports_f16: false,
151 supports_bf16: false,
152 supports_tensor_cores: false,
153 max_threads_per_block: 256,
154 max_shared_memory_per_block: 0,
155 multiprocessor_count: 1,
156 compute_capability: (0, 0),
157 };
158 assert_eq!(caps.name, "test device");
159 assert_eq!(caps.total_memory, 1024);
160 }
161}