//! ROCm device wrapper for Backend trait. //! //! Provides AMD GPU device management via HIP runtime. //! //! Note: This module requires HIP runtime to be installed on the system. //! Testing is done via CI on AMD GPU systems. use rtx_backend::{DeviceId, DeviceOps}; use std::sync::Arc; use crate::{RocmBackend, RocmBackendError, RocmBackendResult}; /// Device information for ROCm/HIP devices. #[derive(Clone, Debug)] pub struct RocmDeviceInfo { /// Device name (e.g., "AMD Radeon RX 7900 XTX") pub name: String, /// Total global memory in bytes pub total_memory: usize, /// Compute units pub compute_units: u32, /// Max clock frequency in MHz pub max_clock_mhz: u32, /// GCN/CDNA architecture name pub arch_name: String, } impl Default for RocmDeviceInfo { fn default() -> Self { Self { name: "Unknown ROCm Device".to_string(), total_memory: 0, compute_units: 0, max_clock_mhz: 0, arch_name: "unknown".to_string(), } } } /// ROCm device wrapper implementing DeviceOps. /// /// Wraps HIP runtime device handle and provides backend device trait. #[derive(Clone)] pub struct RocmDevice { /// Device index index: usize, /// Device info (cached) info: Arc, } impl RocmDevice { /// Create a new ROCm device for the default device (device 0). pub fn new(index: usize) -> RocmBackendResult { // Check if ROCm is available if !is_available() { return Err(RocmBackendError::NotAvailable); } // Get device info let info = get_device_info(index)?; Ok(Self { index, info: Arc::new(info), }) } /// Get the device index. pub fn device_index(&self) -> usize { self.index } /// Get device name. pub fn name(&self) -> &str { &self.info.name } /// Get device info. pub fn info(&self) -> &RocmDeviceInfo { &self.info } /// Get the number of available ROCm devices. pub fn device_count() -> usize { get_device_count() } /// Synchronize all pending operations on this device. pub fn synchronize(&self) -> RocmBackendResult<()> { synchronize_device(self.index) } } impl Default for RocmDevice { fn default() -> Self { Self::new(0).expect("No ROCm device available") } } impl std::fmt::Debug for RocmDevice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RocmDevice") .field("index", &self.index) .field("name", &self.info.name) .field("arch", &self.info.arch_name) .finish() } } impl PartialEq for RocmDevice { fn eq(&self, other: &Self) -> bool { self.index == other.index } } impl Eq for RocmDevice {} impl std::hash::Hash for RocmDevice { fn hash(&self, state: &mut H) { self.index.hash(state); } } impl DeviceOps for RocmDevice { fn id(&self) -> DeviceId { DeviceId::Rocm(self.index) } fn memory_capacity(&self) -> usize { self.info.total_memory } fn memory_available(&self) -> usize { // Try to get actual available memory via HIP runtime get_memory_available().unwrap_or_else(|_| { // Fallback: estimate 80% available self.info.total_memory * 8 / 10 }) } fn compute_capability(&self) -> Option<(u32, u32)> { // Return GCN/CDNA version as compute capability equivalent // Map architecture to version numbers match self.info.arch_name.as_str() { s if s.starts_with("gfx90") => Some((9, 0)), // MI100/MI200 s if s.starts_with("gfx94") => Some((9, 4)), // MI300 s if s.starts_with("gfx110") => Some((11, 0)), // RDNA 3 s if s.starts_with("gfx103") => Some((10, 3)), // RDNA 2 _ => Some((9, 0)), } } fn synchronize(&self) { let _ = self.synchronize(); } fn is_available(&self) -> bool { is_available() } } // ============== HIP Runtime Integration ============== // These functions wrap HIP runtime calls using the hip_ffi module. #[cfg(feature = "hip-runtime")] use crate::hip_ffi::HipRuntime; /// Check if ROCm/HIP is available on this system. pub fn is_available() -> bool { // First check if HIP runtime feature is enabled and working #[cfg(feature = "hip-runtime")] { HipRuntime::is_available() } // Fallback: check for ROCm tools on Linux #[cfg(all(target_os = "linux", not(feature = "hip-runtime")))] { std::process::Command::new("rocminfo") .output() .map(|o| o.status.success()) .unwrap_or(false) } #[cfg(all(not(target_os = "linux"), not(feature = "hip-runtime")))] { false } } /// Get the number of available HIP devices. fn get_device_count() -> usize { #[cfg(feature = "hip-runtime")] { HipRuntime::device_count() } #[cfg(not(feature = "hip-runtime"))] { // Try to parse rocminfo output as fallback if !is_available() { return 0; } std::process::Command::new("rocminfo") .output() .ok() .and_then(|output| { let stdout = String::from_utf8_lossy(&output.stdout); // Count lines containing "Agent" and "GPU" Some( stdout .lines() .filter(|line| line.contains("Agent") && line.contains("GPU")) .count(), ) }) .unwrap_or(0) } } /// Get device information. fn get_device_info(index: usize) -> RocmBackendResult { if !is_available() { return Err(RocmBackendError::NotAvailable); } #[cfg(feature = "hip-runtime")] { let props = HipRuntime::get_device_properties(index as i32).map_err(|e| { RocmBackendError::DeviceInit(format!("Failed to get device properties: {:?}", e)) })?; Ok(RocmDeviceInfo { name: props.name, total_memory: props.total_global_mem, compute_units: props.multi_processor_count as u32, max_clock_mhz: props.clock_rate_mhz as u32, arch_name: props.gcn_arch_name, }) } #[cfg(not(feature = "hip-runtime"))] { // Parse rocminfo output as fallback let output = std::process::Command::new("rocminfo") .output() .map_err(|e| RocmBackendError::DeviceInit(format!("Failed to run rocminfo: {}", e)))?; let stdout = String::from_utf8_lossy(&output.stdout); let mut current_device = 0usize; let mut name = String::new(); let mut arch_name = String::new(); let mut compute_units = 0u32; for line in stdout.lines() { if line.contains("Marketing Name:") { if current_device == index { name = line .split(':') .nth(1) .map(|s| s.trim().to_string()) .unwrap_or_default(); } } else if line.contains("Name:") && line.contains("gfx") { if current_device == index { arch_name = line .split(':') .nth(1) .map(|s| s.trim().to_string()) .unwrap_or_default(); } } else if line.contains("Compute Unit:") { if current_device == index { compute_units = line .split(':') .nth(1) .and_then(|s| s.trim().parse().ok()) .unwrap_or(0); } } else if line.contains("Agent") && line.contains("GPU") { current_device += 1; } } if name.is_empty() { return Err(RocmBackendError::DeviceInit(format!( "Device {} not found", index ))); } Ok(RocmDeviceInfo { name, total_memory: 0, // Not available without HIP runtime compute_units, max_clock_mhz: 0, // Not available without HIP runtime arch_name, }) } } /// Get available memory on the current device. #[allow(dead_code)] pub fn get_memory_available() -> RocmBackendResult { #[cfg(feature = "hip-runtime")] { let (free, _total) = HipRuntime::get_memory_info().map_err(|e| { RocmBackendError::DeviceInit(format!("Failed to get memory info: {:?}", e)) })?; Ok(free) } #[cfg(not(feature = "hip-runtime"))] { Err(RocmBackendError::NotAvailable) } } /// Synchronize device. fn synchronize_device(_index: usize) -> RocmBackendResult<()> { if !is_available() { return Err(RocmBackendError::NotAvailable); } #[cfg(feature = "hip-runtime")] { HipRuntime::synchronize() .map_err(|e| RocmBackendError::Synchronization(format!("Device sync failed: {:?}", e))) } #[cfg(not(feature = "hip-runtime"))] { Ok(()) // No-op without HIP runtime } } #[cfg(test)] mod tests { use super::*; #[test] fn test_rocm_availability() { let available = is_available(); println!("ROCm available: {}", available); // This should pass regardless of ROCm availability } #[test] fn test_device_count() { let count = RocmDevice::device_count(); println!("ROCm device count: {}", count); } }