1use crate::{Error, Result};
18use arrow::array::Int32Array;
19use wgpu;
20
21#[derive(Debug, Clone)]
23pub struct GpuDeviceInfo {
24 pub name: String,
26 pub device_type: wgpu::DeviceType,
28 pub backend: wgpu::Backend,
30}
31
32pub struct MultiGpuManager {
34 devices: Vec<GpuDeviceInfo>,
36}
37
38impl MultiGpuManager {
39 pub fn new() -> Result<Self> {
44 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
48 backends: super::gpu_backends(),
49 ..Default::default()
50 });
51
52 let adapters = instance.enumerate_adapters(super::gpu_backends());
54
55 let devices: Vec<GpuDeviceInfo> = adapters
57 .iter()
58 .map(|adapter| {
59 let info = adapter.get_info();
60 GpuDeviceInfo {
61 name: info.name,
62 device_type: info.device_type,
63 backend: info.backend,
64 }
65 })
66 .collect();
67
68 Ok(Self { devices })
69 }
70
71 #[must_use]
73 pub fn device_count(&self) -> usize {
74 self.devices.len()
75 }
76
77 #[must_use]
79 pub fn devices(&self) -> &[GpuDeviceInfo] {
80 &self.devices
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum PartitionStrategy {
87 Range,
90 Hash,
93}
94
95#[derive(Debug)]
97pub struct DataPartition {
98 pub device_id: usize,
100 pub data: Int32Array,
102}
103
104pub fn partition_data(
117 data: &Int32Array,
118 num_partitions: usize,
119 strategy: PartitionStrategy,
120) -> Result<Vec<DataPartition>> {
121 if num_partitions == 0 {
122 return Err(Error::InvalidInput("num_partitions must be > 0".to_string()));
123 }
124
125 let partitions = match strategy {
126 PartitionStrategy::Range => partition_range(data, num_partitions),
127 PartitionStrategy::Hash => partition_hash(data, num_partitions),
128 };
129 Ok(partitions)
130}
131
132fn partition_range(data: &Int32Array, num_partitions: usize) -> Vec<DataPartition> {
134 let len = data.len();
135 let mut partitions = Vec::with_capacity(num_partitions);
136
137 let base_size = len / num_partitions;
139 let remainder = len % num_partitions;
140
141 let mut offset = 0;
142 for device_id in 0..num_partitions {
143 let size = if device_id < remainder { base_size + 1 } else { base_size };
145
146 let values: Vec<i32> = (offset..offset + size).map(|i| data.value(i)).collect();
148
149 partitions.push(DataPartition { device_id, data: Int32Array::from(values) });
150
151 offset += size;
152 }
153
154 partitions
155}
156
157fn partition_hash(data: &Int32Array, num_partitions: usize) -> Vec<DataPartition> {
159 use std::collections::hash_map::DefaultHasher;
160 use std::hash::{Hash, Hasher};
161
162 let mut buckets: Vec<Vec<i32>> = (0..num_partitions).map(|_| Vec::new()).collect();
164
165 for i in 0..data.len() {
167 let value = data.value(i);
168
169 let mut hasher = DefaultHasher::new();
171 i.hash(&mut hasher);
172 let hash = hasher.finish();
173
174 #[allow(clippy::cast_possible_truncation)]
175 let partition_id = (hash % num_partitions as u64) as usize;
176 buckets[partition_id].push(value);
177 }
178
179 let partitions: Vec<DataPartition> = buckets
181 .into_iter()
182 .enumerate()
183 .map(|(device_id, values)| DataPartition { device_id, data: Int32Array::from(values) })
184 .collect();
185
186 partitions
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_multigpu_device_detection() {
195 let manager = MultiGpuManager::new();
197
198 match manager {
201 Ok(mgr) => {
202 let count = mgr.device_count();
204 println!("Detected {count} GPU device(s)");
205
206 if count > 0 {
208 for (i, device) in mgr.devices().iter().enumerate() {
209 println!("GPU {i}: {device:?}");
210 assert!(!device.name.is_empty(), "Device name should not be empty");
211 }
212 }
213 }
214 Err(e) => {
215 panic!("MultiGpuManager::new() failed: {e}");
216 }
217 }
218 }
219
220 #[test]
221 fn test_multigpu_device_count_zero_when_no_gpu() {
222 let manager = MultiGpuManager::new();
225
226 if let Ok(mgr) = manager {
227 let _count = mgr.device_count();
230 } else {
231 }
234 }
235
236 #[test]
237 fn test_partition_range_even_split() {
238 let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
240 let partitions = partition_data(&data, 2, PartitionStrategy::Range).unwrap();
241
242 assert_eq!(partitions.len(), 2);
243
244 assert_eq!(partitions[0].device_id, 0);
246 assert_eq!(partitions[0].data.len(), 4);
247 assert_eq!(partitions[0].data.value(0), 1);
248 assert_eq!(partitions[0].data.value(3), 4);
249
250 assert_eq!(partitions[1].device_id, 1);
252 assert_eq!(partitions[1].data.len(), 4);
253 assert_eq!(partitions[1].data.value(0), 5);
254 assert_eq!(partitions[1].data.value(3), 8);
255 }
256
257 #[test]
258 fn test_partition_range_uneven_split() {
259 let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
262 let partitions = partition_data(&data, 3, PartitionStrategy::Range).unwrap();
263
264 assert_eq!(partitions.len(), 3);
265
266 let total_len: usize = partitions.iter().map(|p| p.data.len()).sum();
268 assert_eq!(total_len, 10);
269
270 assert_eq!(partitions[0].data.value(0), 1); let last_partition = &partitions[2];
273 assert_eq!(last_partition.data.value(last_partition.data.len() - 1), 10);
274 }
276
277 #[test]
278 fn test_partition_hash_distribution() {
279 let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
281 let partitions = partition_data(&data, 2, PartitionStrategy::Hash).unwrap();
282
283 assert_eq!(partitions.len(), 2);
284
285 let total_len: usize = partitions.iter().map(|p| p.data.len()).sum();
287 assert_eq!(total_len, 8);
288
289 assert_eq!(partitions[0].device_id, 0);
292 assert_eq!(partitions[1].device_id, 1);
293 }
294
295 #[test]
296 fn test_partition_single_gpu() {
297 let data = Int32Array::from(vec![1, 2, 3, 4]);
300 let partitions = partition_data(&data, 1, PartitionStrategy::Range).unwrap();
301
302 assert_eq!(partitions.len(), 1);
303 assert_eq!(partitions[0].device_id, 0);
304 assert_eq!(partitions[0].data.len(), 4);
305 }
306
307 #[test]
308 fn test_partition_empty_data() {
309 let data = Int32Array::from(vec![] as Vec<i32>);
311 let partitions = partition_data(&data, 2, PartitionStrategy::Range).unwrap();
312
313 assert_eq!(partitions.len(), 2);
314 assert_eq!(partitions[0].data.len(), 0);
316 assert_eq!(partitions[1].data.len(), 0);
317 }
318
319 #[test]
320 fn test_partition_zero_partitions_error() {
321 let data = Int32Array::from(vec![1, 2, 3]);
324 let result = partition_data(&data, 0, PartitionStrategy::Range);
325
326 assert!(result.is_err());
327 assert!(result.unwrap_err().to_string().contains("num_partitions must be > 0"));
328 }
329}