Skip to main content

trueno/backends/gpu/
pool.rs

1//! Multi-GPU device pool for data-parallel workloads
2//!
3//! Provides [`GpuDevicePool`] for managing multiple GPU devices simultaneously.
4//! Designed for data-parallel training where each GPU processes a shard of the
5//! mini-batch independently.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use trueno::backends::gpu::GpuDevicePool;
11//!
12//! // Open all available GPUs
13//! let pool = GpuDevicePool::all()?;
14//! println!("Found {} GPUs", pool.len());
15//!
16//! // Or select specific GPUs by index
17//! let pool = GpuDevicePool::with_indices(&[0, 1])?;
18//! # Ok::<(), String>(())
19//! ```
20
21use super::GpuDevice;
22
23/// Pool of GPU devices for multi-GPU workloads
24///
25/// Thin wrapper over `Vec<GpuDevice>` that handles enumeration
26/// and selective device opening.
27pub struct GpuDevicePool {
28    devices: Vec<GpuDevice>,
29    indices: Vec<u32>,
30}
31
32impl GpuDevicePool {
33    /// Open all available non-CPU GPU adapters
34    ///
35    /// Filters out adapters with `Noop` backend (CPU fallback).
36    #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
37    pub fn all() -> Result<Self, String> {
38        use super::runtime;
39        runtime::block_on(Self::all_async())
40    }
41
42    /// Open all available non-CPU GPU adapters (async)
43    pub async fn all_async() -> Result<Self, String> {
44        // PMAT-778: reuse the process-global instance so the broken freedreno
45        // ICD (GB10/aarch64) is enumerated exactly once, never concurrently.
46        let instance = super::device::shared_instance();
47        let adapters = instance.enumerate_adapters(wgpu::Backends::all());
48
49        if adapters.is_empty() {
50            return Err("No GPU adapters found".to_string());
51        }
52
53        // Filter out CPU/Noop backends
54        let gpu_adapters: Vec<(usize, _)> = adapters
55            .into_iter()
56            .enumerate()
57            .filter(|(_, adapter)| adapter.get_info().backend != wgpu::Backend::Noop)
58            .collect();
59
60        if gpu_adapters.is_empty() {
61            return Err("No non-CPU GPU adapters found".to_string());
62        }
63
64        let mut devices = Vec::with_capacity(gpu_adapters.len());
65        let mut indices = Vec::with_capacity(gpu_adapters.len());
66
67        for (idx, adapter) in gpu_adapters {
68            let mut limits = wgpu::Limits::default();
69            limits.max_buffer_size = adapter.limits().max_buffer_size;
70            limits.max_storage_buffer_binding_size =
71                adapter.limits().max_storage_buffer_binding_size;
72
73            let (device, queue) = adapter
74                .request_device(&wgpu::DeviceDescriptor {
75                    label: Some(&format!("Trueno GPU Device [{}]", idx)),
76                    required_features: wgpu::Features::empty(),
77                    required_limits: limits,
78                    memory_hints: wgpu::MemoryHints::Performance,
79                    experimental_features: Default::default(),
80                    trace: Default::default(),
81                })
82                .await
83                .map_err(|e| format!("Failed to create device at index {}: {}", idx, e))?;
84
85            devices.push(GpuDevice { device, queue });
86            indices.push(idx as u32);
87        }
88
89        Ok(Self { devices, indices })
90    }
91
92    /// Open specific GPU adapters by index
93    #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
94    pub fn with_indices(adapter_indices: &[u32]) -> Result<Self, String> {
95        use super::runtime;
96        runtime::block_on(Self::with_indices_async(adapter_indices))
97    }
98
99    /// Open specific GPU adapters by index (async)
100    pub async fn with_indices_async(adapter_indices: &[u32]) -> Result<Self, String> {
101        if adapter_indices.is_empty() {
102            return Err("No adapter indices specified".to_string());
103        }
104
105        let mut devices = Vec::with_capacity(adapter_indices.len());
106        let mut indices = Vec::with_capacity(adapter_indices.len());
107
108        for &idx in adapter_indices {
109            let device = GpuDevice::new_with_adapter_index_async(idx).await?;
110            devices.push(device);
111            indices.push(idx);
112        }
113
114        Ok(Self { devices, indices })
115    }
116
117    /// Number of devices in the pool
118    #[must_use]
119    pub fn len(&self) -> usize {
120        self.devices.len()
121    }
122
123    /// Whether the pool is empty
124    #[must_use]
125    pub fn is_empty(&self) -> bool {
126        self.devices.is_empty()
127    }
128
129    /// Get a device by pool position (0-based within this pool)
130    #[must_use]
131    pub fn get(&self, pool_index: usize) -> Option<&GpuDevice> {
132        self.devices.get(pool_index)
133    }
134
135    /// Get the adapter index for a pool position
136    #[must_use]
137    pub fn adapter_index(&self, pool_index: usize) -> Option<u32> {
138        self.indices.get(pool_index).copied()
139    }
140
141    /// Iterate over (adapter_index, device) pairs
142    pub fn iter(&self) -> impl Iterator<Item = (u32, &GpuDevice)> {
143        self.indices.iter().copied().zip(self.devices.iter())
144    }
145
146    /// Consume the pool and return the devices
147    pub fn into_devices(self) -> Vec<GpuDevice> {
148        self.devices
149    }
150}
151
152#[cfg(all(test, feature = "gpu", not(target_arch = "wasm32")))]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_pool_len_matches_devices() {
158        if !GpuDevice::is_available() {
159            eprintln!("GPU not available, skipping pool test");
160            return;
161        }
162
163        let pool = GpuDevicePool::all();
164        match pool {
165            Ok(p) => {
166                assert!(!p.is_empty());
167                assert!(p.len() > 0);
168                assert!(p.get(0).is_some());
169                assert!(p.adapter_index(0).is_some());
170            }
171            Err(e) => {
172                eprintln!("Pool creation failed (expected on CPU-only): {}", e);
173            }
174        }
175    }
176}