#[allow(unused_imports)]
use crate::device::Device;
use crate::errors::Result;
use crate::tensor::Tensor;
#[cfg(feature = "rocm")]
use crate::errors::TrustformersError;
#[cfg(feature = "rocm")]
use std::collections::HashMap;
#[cfg(feature = "rocm")]
use std::sync::Arc;
#[cfg(feature = "rocm")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BufferId(u64);
#[cfg(feature = "rocm")]
impl BufferId {
pub fn new() -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
BufferId(COUNTER.fetch_add(1, Ordering::SeqCst))
}
}
#[cfg(feature = "rocm")]
impl Default for BufferId {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "rocm")]
pub struct RocmBackend {
device_id: usize,
}
#[cfg(feature = "rocm")]
impl RocmBackend {
pub fn new(device_id: usize) -> Result<Self> {
if !Self::is_rocm_available() {
return Err(TrustformersError::hardware_error(
"ROCm runtime not found. Please install ROCm toolkit.",
"RocmBackend::new",
));
}
tracing::debug!("ROCm backend initialized on device {}", device_id);
Ok(Self { device_id })
}
fn is_rocm_available() -> bool {
#[cfg(target_os = "linux")]
{
unsafe {
libloading::Library::new("libamdhip64.so")
.or_else(|_| libloading::Library::new("libamdhip64.so.5"))
.or_else(|_| libloading::Library::new("libamdhip64.so.6"))
.is_ok()
}
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
pub fn matmul_f32(
&self,
a: &[f32],
b: &[f32],
m: usize,
k: usize,
n: usize,
) -> Result<Vec<f32>> {
tracing::debug!(
"ROCm HIP kernel dispatch not yet wired up (device {}) - using the OxiBLAS-backed \
CPU GEMM path instead of a hand-rolled loop",
self.device_id
);
let a_tensor = Tensor::from_vec(a.to_vec(), &[m, k])?;
let b_tensor = Tensor::from_vec(b.to_vec(), &[k, n])?;
let result = a_tensor.matmul(&b_tensor)?;
result.data()
}
pub fn gelu_f32(&self, input: &[f32]) -> Result<Vec<f32>> {
tracing::debug!("ROCm GPU operations not yet implemented - using CPU fallback");
let result: Vec<f32> = input
.iter()
.map(|&x| {
if x > 10.0 {
x
} else if x < -10.0 {
0.0
} else {
let x_cubed = x * x * x;
let inner = 0.7978845608f32 * (x + 0.044715 * x_cubed);
let clamped = inner.clamp(-20.0, 20.0);
0.5 * x * (1.0 + clamped.tanh())
}
})
.collect();
Ok(result)
}
pub fn layernorm_f32(
&self,
input: &[f32],
weight: &[f32],
bias: &[f32],
seq_len: usize,
hidden_size: usize,
eps: f32,
) -> Result<Vec<f32>> {
tracing::debug!("ROCm GPU operations not yet implemented - using CPU fallback");
let total_size = seq_len * hidden_size;
let mut result = vec![0.0f32; total_size];
for pos in 0..seq_len {
let offset = pos * hidden_size;
let sum: f32 = input[offset..offset + hidden_size].iter().sum();
let mean = sum / hidden_size as f32;
let var_sum: f32 = input[offset..offset + hidden_size]
.iter()
.map(|&x| {
let diff = x - mean;
diff * diff
})
.sum();
let variance = var_sum / hidden_size as f32;
let std_dev = (variance + eps).sqrt();
for i in 0..hidden_size {
let normalized = (input[offset + i] - mean) / std_dev;
result[offset + i] = normalized * weight[i] + bias[i];
}
}
Ok(result)
}
pub fn device_info(&self) -> String {
format!(
"ROCm host {} - HIP runtime detected but kernel dispatch is not wired up yet; \
operations run on the CPU via OxiBLAS, not on the GPU",
self.device_id
)
}
}
#[cfg(feature = "rocm")]
pub fn get_rocm_backend(device_id: usize) -> Result<Arc<RocmBackend>> {
static ROCM_BACKENDS: once_cell::sync::Lazy<
std::sync::Mutex<HashMap<usize, Arc<RocmBackend>>>,
> = once_cell::sync::Lazy::new(|| std::sync::Mutex::new(HashMap::new()));
let mut cache = ROCM_BACKENDS.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock ROCm backend cache", "get_rocm_backend")
})?;
if let std::collections::hash_map::Entry::Vacant(e) = cache.entry(device_id) {
let backend = RocmBackend::new(device_id)?;
e.insert(Arc::new(backend));
}
cache.get(&device_id).cloned().ok_or_else(|| {
TrustformersError::hardware_error("ROCm backend not found", "get_rocm_backend")
})
}
#[allow(unused_variables)]
pub fn dispatch_rocm_matmul(a: &Tensor, b: &Tensor, device_id: usize) -> Result<Tensor> {
#[cfg(feature = "rocm")]
{
match (a, b) {
(Tensor::F32(a_arr), Tensor::F32(b_arr)) => {
if a_arr.ndim() != 2 || b_arr.ndim() != 2 {
return Err(TrustformersError::shape_error(
"ROCm dispatch currently only supports 2D tensors".to_string(),
));
}
let a_2d = a_arr
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|e| {
TrustformersError::shape_error(format!("Failed to convert to 2D: {}", e))
})?;
let b_2d = b_arr
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|e| {
TrustformersError::shape_error(format!("Failed to convert to 2D: {}", e))
})?;
let (m, k) = a_2d.dim();
let (k2, n) = b_2d.dim();
if k != k2 {
return Err(TrustformersError::shape_error(format!(
"Matrix dimension mismatch: {}×{} vs {}×{}",
m, k, k2, n
)));
}
let backend = get_rocm_backend(device_id)?;
let a_data: Vec<f32> = a_2d.iter().copied().collect();
let b_data: Vec<f32> = b_2d.iter().copied().collect();
let result_data = backend.matmul_f32(&a_data, &b_data, m, k, n)?;
let result_2d = scirs2_core::ndarray::Array2::from_shape_vec((m, n), result_data)
.map_err(|e| {
TrustformersError::shape_error(format!("Failed to reshape result: {}", e))
})?;
let result_dyn = result_2d.into_dyn();
Ok(Tensor::F32(result_dyn))
},
_ => a.matmul(b),
}
}
#[cfg(not(feature = "rocm"))]
{
a.matmul(b)
}
}
#[cfg(all(test, feature = "rocm"))]
impl RocmBackend {
fn new_for_test(device_id: usize) -> Self {
Self { device_id }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "rocm")]
fn test_rocm_availability() {
let available = RocmBackend::is_rocm_available();
println!("ROCm available: {}", available);
}
#[test]
#[cfg(all(feature = "rocm", not(target_os = "linux")))]
fn test_is_rocm_available_false_on_non_linux() {
assert!(!RocmBackend::is_rocm_available());
}
#[test]
#[cfg(feature = "rocm")]
fn test_matmul_f32_rectangular_matches_hand_computed_reference() {
let backend = RocmBackend::new_for_test(0);
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let b = vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0];
let result = backend.matmul_f32(&a, &b, 2, 3, 2).expect("matmul_f32 should succeed");
let expected = [58.0, 64.0, 139.0, 154.0];
for (i, (&res, &exp)) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(res - exp).abs() < 1e-3,
"mismatch at index {}: {} vs {}",
i,
res,
exp
);
}
}
#[test]
#[cfg(feature = "rocm")]
fn test_device_info_does_not_claim_gpu_execution() {
let backend = RocmBackend::new_for_test(0);
let info = backend.device_info();
assert!(
info.contains("CPU"),
"device_info must make clear execution is on the CPU, got: {info}"
);
}
#[test]
#[cfg(feature = "rocm")]
fn test_rocm_backend() -> Result<()> {
match RocmBackend::new(0) {
Ok(backend) => {
println!("ROCm backend: {}", backend.device_info());
Ok(())
},
Err(_) => {
tracing::debug!("Skipping ROCm test: not available");
Ok(())
},
}
}
#[test]
#[cfg(feature = "rocm")]
fn test_rocm_matmul_fallback() -> Result<()> {
let backend = match RocmBackend::new(0) {
Ok(b) => b,
Err(_) => {
tracing::debug!("Skipping ROCm test: not available");
return Ok(());
},
};
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
let result = backend.matmul_f32(&a, &b, 2, 2, 2)?;
let expected = [19.0, 22.0, 43.0, 50.0];
for (i, (&res, &exp)) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(res - exp).abs() < 1e-4,
"Mismatch at index {}: {} vs {}",
i,
res,
exp
);
}
Ok(())
}
}