Skip to main content

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}