Files
rustytorch/crates/core/rtx-backend-rocm/src/lib.rs
T
Omar SobhandClaude Sonnet 4.6 228137555f fix(gaps): G0/G2/G5/G8 — eliminate unimplemented! panics, re-enable rtx-distributed, rtx-tts, fix multimodal forward
G0 (Critical): Replace 45 unimplemented!() panics across three GPU backends
- rtx-backend-cuda: sin/cos/tanh via PTX, relu/sigmoid/leaky_relu/elu via activation.rs,
  pow/clamp/gt_scalar via unary.rs, var/var_dim host-side, conv2d/max_pool2d/avg_pool2d
  CPU fallback in new ops/conv.rs; new PTX kernels in element_wise.cu
- rtx-backend-rocm: all 15 ops via CPU round-trip (to_vec → compute → from_slice)
- rtx-backend-sycl: all 15 ops via CPU round-trip (to_host → compute → from_data)

G2 (High): Re-add rtx-distributed to workspace
- Vendor 4 minimal RNCCL stub crates at crates/vendor/rnccl/*
- Update rtx-distributed RNCCL path deps to point at stubs (../../../../RNCCL/* → ../../vendor/rnccl/*)
- Remove rtx-distributed from workspace exclude list, add to members

G5 (Medium): Re-enable rtx-tts (213 tests restored)
- Fix 15 rtx-nn API drift issues: LayerNorm::new, Conv1d::from_config, Conv1dPadding::Zeros,
  Dropout::new(p, device), tensor methods (relu/tanh/sigmoid/cat/stack), squeeze(Some(n)),
  to_vec() turbofish removal, Tensor::randn with &[...] slices

G8 (Low): Quantum stubs + multimodal forward bug
- rtx-timeseries: remove dead quantum/neuromorphic TODO comment blocks (no module files exist)
- rtx-multimodal/fusion/transformer.rs: wire TransformerBlock loop in forward()
- rtx-multimodal/fusion/strategies.rs: wire bottleneck_layers loop in forward()
- rtx-transformers/architectures/transformer_block.rs: add forward() method (pre-norm residuals;
  full attention+FFN pending when those sub-layers are wired)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-26 13:40:23 +00:00

479 lines
14 KiB
Rust

//! # RustyTorch++ ROCm Backend
//!
//! AMD GPU backend implementation using HIP runtime.
//!
//! ## Features
//!
//! - **HIP Runtime**: AMD's CUDA-compatible API for GPU computing
//! - **rocBLAS**: Optimized matrix operations
//! - **MIOpen**: Deep learning primitives
//! - **Source Compatibility**: HIP kernels are ~95% CUDA-compatible
//!
//! ## Architecture
//!
//! ```text
//! RocmBackend
//! ├── RocmTensorPrimitive - HIP device memory
//! ├── RocmDevice - Device context and streams
//! └── Ops
//! ├── Basic - Add, mul, etc. (HIP kernels)
//! ├── GEMM - Matrix multiply (rocBLAS)
//! └── Attention - Flash Attention (ported from CUDA)
//! ```
//!
//! ## Example
//!
//! ```rust,ignore
//! use rtx_backend_rocm::{RocmBackend, RocmDevice};
//! use rtx_backend::Backend;
//!
//! let device = RocmDevice::new(0)?;
//! let a = RocmBackend::zeros([1024, 1024], &device);
//! let b = RocmBackend::randn([1024, 1024], &device);
//! let c = RocmBackend::matmul(&a, &b);
//! ```
#![warn(missing_docs)]
mod device;
mod error;
/// HIP FFI bindings for low-level GPU access.
pub mod hip_ffi;
/// HIP kernel implementations for GPU operations.
#[cfg(feature = "hip-runtime")]
pub mod hip_kernels;
/// Operations module - re-exported for tests and direct access.
pub mod ops;
mod tensor;
pub use device::{RocmDevice, RocmDeviceInfo};
pub use error::{RocmBackendError, RocmBackendResult};
pub use tensor::{RocmBuffer, RocmTensorPrimitive};
use rtx_backend::{Backend, BoolU8};
/// ROCm backend for RustyTorch++.
///
/// This backend uses AMD GPUs via HIP runtime and provides:
/// - rocBLAS for matrix operations
/// - HIP kernels (CUDA source-compatible)
/// - Multi-GPU support
#[derive(Clone, Debug, Default)]
pub struct RocmBackend;
impl Backend for RocmBackend {
type TensorPrimitive<const D: usize> = RocmTensorPrimitive<D>;
type Device = RocmDevice;
type FloatElem = f32;
type IntElem = i32;
type BoolElem = BoolU8;
fn name() -> &'static str {
"rocm"
}
fn seed(seed: u64) {
ops::seed_rng(seed);
}
// ==================== Tensor Creation ====================
fn zeros<const D: usize>(shape: [usize; D], device: &Self::Device) -> Self::TensorPrimitive<D> {
ops::creation::zeros(shape, device)
}
fn ones<const D: usize>(shape: [usize; D], device: &Self::Device) -> Self::TensorPrimitive<D> {
ops::creation::ones(shape, device)
}
fn full<const D: usize>(
shape: [usize; D],
fill_value: Self::FloatElem,
device: &Self::Device,
) -> Self::TensorPrimitive<D> {
ops::creation::full(shape, fill_value, device)
}
fn rand<const D: usize>(shape: [usize; D], device: &Self::Device) -> Self::TensorPrimitive<D> {
ops::creation::rand(shape, device)
}
fn randn<const D: usize>(shape: [usize; D], device: &Self::Device) -> Self::TensorPrimitive<D> {
ops::creation::randn(shape, device)
}
fn from_data<const D: usize>(
data: &[Self::FloatElem],
shape: [usize; D],
device: &Self::Device,
) -> Self::TensorPrimitive<D> {
ops::creation::from_data(data, shape, device)
}
// ==================== Basic Operations ====================
fn add<const D: usize>(
lhs: Self::TensorPrimitive<D>,
rhs: Self::TensorPrimitive<D>,
) -> Self::TensorPrimitive<D> {
ops::basic::add(&lhs, &rhs)
}
fn sub<const D: usize>(
lhs: Self::TensorPrimitive<D>,
rhs: Self::TensorPrimitive<D>,
) -> Self::TensorPrimitive<D> {
ops::basic::sub(&lhs, &rhs)
}
fn mul<const D: usize>(
lhs: Self::TensorPrimitive<D>,
rhs: Self::TensorPrimitive<D>,
) -> Self::TensorPrimitive<D> {
ops::basic::mul(&lhs, &rhs)
}
fn div<const D: usize>(
lhs: Self::TensorPrimitive<D>,
rhs: Self::TensorPrimitive<D>,
) -> Self::TensorPrimitive<D> {
ops::basic::div(&lhs, &rhs)
}
fn matmul(
lhs: Self::TensorPrimitive<2>,
rhs: Self::TensorPrimitive<2>,
) -> Self::TensorPrimitive<2> {
ops::gemm::matmul(&lhs, &rhs)
}
fn bmm(
lhs: Self::TensorPrimitive<3>,
rhs: Self::TensorPrimitive<3>,
) -> Self::TensorPrimitive<3> {
ops::gemm::bmm(&lhs, &rhs)
}
// ==================== Unary Operations ====================
fn neg<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::neg(&tensor)
}
fn exp<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::exp(&tensor)
}
fn log<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::log(&tensor)
}
fn sqrt<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::sqrt(&tensor)
}
fn abs<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::abs(&tensor)
}
fn sin<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::sin(&tensor)
}
fn cos<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::unary::cos(&tensor)
}
fn pow<const D: usize>(
tensor: Self::TensorPrimitive<D>,
exp: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::unary::pow(&tensor, exp)
}
fn clamp<const D: usize>(
tensor: Self::TensorPrimitive<D>,
min: Self::FloatElem,
max: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::comparison::clamp(&tensor, Some(min), Some(max))
}
// ==================== Activation Functions ====================
fn relu<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::activation::relu(&tensor)
}
fn sigmoid<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::activation::sigmoid(&tensor)
}
fn tanh<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::activation::tanh(&tensor)
}
// ==================== Reduction Operations ====================
fn sum<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<1> {
ops::reduction::sum(&tensor)
}
fn sum_dim<const D: usize>(
tensor: Self::TensorPrimitive<D>,
dim: usize,
) -> Self::TensorPrimitive<D> {
ops::reduction::sum_dim(&tensor, dim)
}
fn mean<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<1> {
ops::reduction::mean(&tensor)
}
fn mean_dim<const D: usize>(
tensor: Self::TensorPrimitive<D>,
dim: usize,
) -> Self::TensorPrimitive<D> {
ops::reduction::mean_dim(&tensor, dim)
}
fn var<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<1> {
ops::reduction::var(&tensor)
}
fn var_dim<const D: usize>(
tensor: Self::TensorPrimitive<D>,
dim: usize,
) -> Self::TensorPrimitive<D> {
ops::reduction::var_dim(&tensor, dim)
}
fn max<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<1> {
ops::reduction::max(&tensor)
}
fn min<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<1> {
ops::reduction::min(&tensor)
}
// ==================== Shape Operations ====================
fn shape<const D: usize>(tensor: &Self::TensorPrimitive<D>) -> [usize; D] {
tensor.shape
}
fn reshape<const D1: usize, const D2: usize>(
tensor: Self::TensorPrimitive<D1>,
shape: [usize; D2],
) -> Self::TensorPrimitive<D2> {
ops::shape::reshape(tensor, shape)
}
fn transpose<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::shape::transpose(&tensor)
}
fn swap_dims<const D: usize>(
tensor: Self::TensorPrimitive<D>,
dim1: usize,
dim2: usize,
) -> Self::TensorPrimitive<D> {
ops::shape::swap_dims(&tensor, dim1, dim2)
}
// ==================== LLM-Specific Operations ====================
fn flash_attention(
query: Self::TensorPrimitive<4>,
key: Self::TensorPrimitive<4>,
value: Self::TensorPrimitive<4>,
mask: Option<&Self::TensorPrimitive<4>>,
scale: Self::FloatElem,
causal: bool,
) -> Self::TensorPrimitive<4> {
ops::attention::flash_attention(&query, &key, &value, mask, scale, causal)
}
fn softmax<const D: usize>(
tensor: Self::TensorPrimitive<D>,
dim: usize,
) -> Self::TensorPrimitive<D> {
ops::activation::softmax(&tensor, dim)
}
fn layer_norm<const D: usize>(
tensor: Self::TensorPrimitive<D>,
weight: &Self::TensorPrimitive<1>,
bias: Option<&Self::TensorPrimitive<1>>,
eps: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::normalization::layer_norm(&tensor, weight, bias, eps)
}
fn rms_norm<const D: usize>(
tensor: Self::TensorPrimitive<D>,
weight: &Self::TensorPrimitive<1>,
eps: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::normalization::rms_norm(&tensor, weight, eps)
}
fn rope<const D: usize>(
tensor: Self::TensorPrimitive<D>,
cos: &Self::TensorPrimitive<2>,
sin: &Self::TensorPrimitive<2>,
) -> Self::TensorPrimitive<D> {
ops::attention::rope(&tensor, cos, sin)
}
fn gelu<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::activation::gelu(&tensor)
}
fn silu<const D: usize>(tensor: Self::TensorPrimitive<D>) -> Self::TensorPrimitive<D> {
ops::activation::silu(&tensor)
}
fn leaky_relu<const D: usize>(
tensor: Self::TensorPrimitive<D>,
negative_slope: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::activation::leaky_relu(&tensor, negative_slope)
}
fn elu<const D: usize>(
tensor: Self::TensorPrimitive<D>,
alpha: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::activation::elu(&tensor, alpha)
}
// ==================== Comparison Operations ====================
fn gt_scalar<const D: usize>(
tensor: Self::TensorPrimitive<D>,
value: Self::FloatElem,
) -> Self::TensorPrimitive<D> {
ops::comparison::gt_scalar(&tensor, value)
}
// ==================== Convolution Operations ====================
fn conv2d(
input: Self::TensorPrimitive<4>,
weight: &Self::TensorPrimitive<4>,
bias: Option<&Self::TensorPrimitive<1>>,
stride: [usize; 2],
padding: [usize; 2],
dilation: [usize; 2],
groups: usize,
) -> Self::TensorPrimitive<4> {
let config = ops::convolution::Conv2dConfig {
kernel_size: (weight.shape[2], weight.shape[3]),
stride: (stride[0], stride[1]),
padding: (padding[0], padding[1]),
dilation: (dilation[0], dilation[1]),
groups,
};
ops::convolution::conv2d(&input, weight, bias, &config)
}
// ==================== Pooling Operations ====================
fn max_pool2d(
input: Self::TensorPrimitive<4>,
kernel_size: [usize; 2],
stride: [usize; 2],
padding: [usize; 2],
) -> Self::TensorPrimitive<4> {
let config = ops::pooling::Pool2dConfig {
kernel_size: (kernel_size[0], kernel_size[1]),
stride: (stride[0], stride[1]),
padding: (padding[0], padding[1]),
dilation: (1, 1),
ceil_mode: false,
};
ops::pooling::max_pool2d(&input, &config)
}
fn avg_pool2d(
input: Self::TensorPrimitive<4>,
kernel_size: [usize; 2],
stride: [usize; 2],
padding: [usize; 2],
count_include_pad: bool,
) -> Self::TensorPrimitive<4> {
let config = ops::pooling::Pool2dConfig {
kernel_size: (kernel_size[0], kernel_size[1]),
stride: (stride[0], stride[1]),
padding: (padding[0], padding[1]),
dilation: (1, 1),
ceil_mode: false,
};
ops::pooling::avg_pool2d(&input, &config, count_include_pad)
}
// ==================== Device Management ====================
fn device<const D: usize>(tensor: &Self::TensorPrimitive<D>) -> Self::Device {
tensor.device.clone()
}
fn to_device<const D: usize>(
tensor: Self::TensorPrimitive<D>,
device: &Self::Device,
) -> Self::TensorPrimitive<D> {
if tensor.device == *device {
tensor
} else {
ops::device::copy_to_device(tensor, device)
}
}
fn to_data<const D: usize>(tensor: &Self::TensorPrimitive<D>) -> Vec<Self::FloatElem> {
ops::device::copy_to_host(tensor)
}
fn sync(device: &Self::Device) {
let _ = device.synchronize();
}
}
/// Check if ROCm is available on this system.
pub fn is_available() -> bool {
#[cfg(feature = "hip-runtime")]
{
hip_ffi::HipRuntime::is_available()
}
#[cfg(not(feature = "hip-runtime"))]
{
device::is_available()
}
}
/// Get the number of available AMD GPUs.
pub fn device_count() -> usize {
hip_ffi::HipRuntime::device_count()
}
/// Check if HIP runtime feature is enabled.
pub fn has_hip_runtime() -> bool {
cfg!(feature = "hip-runtime")
}
/// Check if rocBLAS feature is enabled.
pub fn has_rocblas() -> bool {
cfg!(feature = "rocblas")
}
/// Check if MIOpen feature is enabled.
pub fn has_miopen() -> bool {
cfg!(feature = "miopen")
}
/// Type alias for training with ROCm + autodiff.
pub type RocmTraining = RocmBackend;
/// Type alias for inference with ROCm (no autodiff overhead).
pub type RocmInference = RocmBackend;