use crate::{Error, Result};
use arrow::array::Int32Array;
use wgpu;
#[derive(Debug, Clone)]
pub struct GpuDeviceInfo {
pub name: String,
pub device_type: wgpu::DeviceType,
pub backend: wgpu::Backend,
}
pub struct MultiGpuManager {
devices: Vec<GpuDeviceInfo>,
}
impl MultiGpuManager {
pub fn new() -> Result<Self> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapters = instance.enumerate_adapters(wgpu::Backends::all());
let devices: Vec<GpuDeviceInfo> = adapters
.iter()
.map(|adapter| {
let info = adapter.get_info();
GpuDeviceInfo {
name: info.name,
device_type: info.device_type,
backend: info.backend,
}
})
.collect();
Ok(Self { devices })
}
#[must_use]
pub fn device_count(&self) -> usize {
self.devices.len()
}
#[must_use]
pub fn devices(&self) -> &[GpuDeviceInfo] {
&self.devices
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionStrategy {
Range,
Hash,
}
#[derive(Debug)]
pub struct DataPartition {
pub device_id: usize,
pub data: Int32Array,
}
pub fn partition_data(
data: &Int32Array,
num_partitions: usize,
strategy: PartitionStrategy,
) -> Result<Vec<DataPartition>> {
if num_partitions == 0 {
return Err(Error::InvalidInput("num_partitions must be > 0".to_string()));
}
let partitions = match strategy {
PartitionStrategy::Range => partition_range(data, num_partitions),
PartitionStrategy::Hash => partition_hash(data, num_partitions),
};
Ok(partitions)
}
fn partition_range(data: &Int32Array, num_partitions: usize) -> Vec<DataPartition> {
let len = data.len();
let mut partitions = Vec::with_capacity(num_partitions);
let base_size = len / num_partitions;
let remainder = len % num_partitions;
let mut offset = 0;
for device_id in 0..num_partitions {
let size = if device_id < remainder { base_size + 1 } else { base_size };
let values: Vec<i32> = (offset..offset + size).map(|i| data.value(i)).collect();
partitions.push(DataPartition { device_id, data: Int32Array::from(values) });
offset += size;
}
partitions
}
fn partition_hash(data: &Int32Array, num_partitions: usize) -> Vec<DataPartition> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut buckets: Vec<Vec<i32>> = (0..num_partitions).map(|_| Vec::new()).collect();
for i in 0..data.len() {
let value = data.value(i);
let mut hasher = DefaultHasher::new();
i.hash(&mut hasher);
let hash = hasher.finish();
#[allow(clippy::cast_possible_truncation)]
let partition_id = (hash % num_partitions as u64) as usize;
buckets[partition_id].push(value);
}
let partitions: Vec<DataPartition> = buckets
.into_iter()
.enumerate()
.map(|(device_id, values)| DataPartition { device_id, data: Int32Array::from(values) })
.collect();
partitions
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multigpu_device_detection() {
let manager = MultiGpuManager::new();
match manager {
Ok(mgr) => {
let count = mgr.device_count();
println!("Detected {count} GPU device(s)");
if count > 0 {
for (i, device) in mgr.devices().iter().enumerate() {
println!("GPU {i}: {device:?}");
assert!(!device.name.is_empty(), "Device name should not be empty");
}
}
}
Err(e) => {
panic!("MultiGpuManager::new() failed: {e}");
}
}
}
#[test]
fn test_multigpu_device_count_zero_when_no_gpu() {
let manager = MultiGpuManager::new();
if let Ok(mgr) = manager {
let _count = mgr.device_count();
} else {
}
}
#[test]
fn test_partition_range_even_split() {
let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
let partitions = partition_data(&data, 2, PartitionStrategy::Range).unwrap();
assert_eq!(partitions.len(), 2);
assert_eq!(partitions[0].device_id, 0);
assert_eq!(partitions[0].data.len(), 4);
assert_eq!(partitions[0].data.value(0), 1);
assert_eq!(partitions[0].data.value(3), 4);
assert_eq!(partitions[1].device_id, 1);
assert_eq!(partitions[1].data.len(), 4);
assert_eq!(partitions[1].data.value(0), 5);
assert_eq!(partitions[1].data.value(3), 8);
}
#[test]
fn test_partition_range_uneven_split() {
let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
let partitions = partition_data(&data, 3, PartitionStrategy::Range).unwrap();
assert_eq!(partitions.len(), 3);
let total_len: usize = partitions.iter().map(|p| p.data.len()).sum();
assert_eq!(total_len, 10);
assert_eq!(partitions[0].data.value(0), 1); let last_partition = &partitions[2];
assert_eq!(last_partition.data.value(last_partition.data.len() - 1), 10);
}
#[test]
fn test_partition_hash_distribution() {
let data = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
let partitions = partition_data(&data, 2, PartitionStrategy::Hash).unwrap();
assert_eq!(partitions.len(), 2);
let total_len: usize = partitions.iter().map(|p| p.data.len()).sum();
assert_eq!(total_len, 8);
assert_eq!(partitions[0].device_id, 0);
assert_eq!(partitions[1].device_id, 1);
}
#[test]
fn test_partition_single_gpu() {
let data = Int32Array::from(vec![1, 2, 3, 4]);
let partitions = partition_data(&data, 1, PartitionStrategy::Range).unwrap();
assert_eq!(partitions.len(), 1);
assert_eq!(partitions[0].device_id, 0);
assert_eq!(partitions[0].data.len(), 4);
}
#[test]
fn test_partition_empty_data() {
let data = Int32Array::from(vec![] as Vec<i32>);
let partitions = partition_data(&data, 2, PartitionStrategy::Range).unwrap();
assert_eq!(partitions.len(), 2);
assert_eq!(partitions[0].data.len(), 0);
assert_eq!(partitions[1].data.len(), 0);
}
#[test]
fn test_partition_zero_partitions_error() {
let data = Int32Array::from(vec![1, 2, 3]);
let result = partition_data(&data, 0, PartitionStrategy::Range);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("num_partitions must be > 0"));
}
}