Files
rustytorch/crates/integration/rtx-candle/src/tensor_bridge.rs
T
osobhandClaude Sonnet 5 522400a72b
GPU Tests / Check GPU Availability (push) Successful in 0s
GPU Tests / Metal Tests (push) Has been skipped
GPU Tests / CUDA Tests (11.8) (push) Has been skipped
GPU Tests / CUDA Tests (12.1) (push) Has been skipped
CI / Format Check (push) Failing after 8s
Performance Benchmarks / Run Benchmarks (push) Failing after 8s
CI / Clippy Check (push) Failing after 8s
Documentation / Build API Documentation (push) Failing after 8s
Documentation / Build User Guide (push) Successful in 8s
CI / Build CPU-Only (Explicit) (push) Failing after 25s
CI / Build (macos-latest) (push) Failing after 32s
CI / Build (ubuntu-latest) (push) Failing after 3m8s
CI / Test (macos-latest) (push) Has been skipped
CI / Test (ubuntu-latest) (push) Has been skipped
CI / Python Bindings (maturin) (macos-latest) (push) Has been skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Has been skipped
CI / WASM Build + Size Check (push) Has been skipped
CI / Distributed Training Tests (push) Has been skipped
CI / CI Success (push) Failing after 0s
fix(deps): bump candle-core/nn/transformers 0.8->0.11 for CUDA 13.1 build
candle-kernels 0.9.2/0.8.4's compatibility.cuh has a buggy CUDA-version
guard ((MAJOR<12 || MINOR<2) && ARCH<750) that misfires on CUDA 13.1,
redefining __hmax_nan/__hmin_nan/atomicAdd that 13.1 already provides
natively. Fixed upstream in candle-kernels 0.11.0 (pure ARCH<800 gate),
so bump the workspace-wide candle pin to pull it in.

rtx-csm stays on candle 0.9.1 directly (not the workspace pin) since it
shares Tensor types with moshi 0.6.4, which itself pins candle-core
0.9.1 - both candle trees now build cleanly side by side.

Also fixes two latent compile issues surfaced by actually building the
cuda feature: DType is #[non_exhaustive] with new I16/I32/float8
variants (rtx-candle), and a missing HashMap import gated behind the
candle feature (rtx-inference).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-09 18:04:46 -07:00

136 lines
4.0 KiB
Rust

//! Tensor bridge between rtx-tensor and Candle tensors
use crate::backend::CandleDevice;
use crate::error::{CandleError, Result};
use candle_core::{DType, Tensor as CandleTensor};
use rtx_tensor::{Device, Tensor};
use tracing::debug;
/// Convert an rtx-tensor Tensor to Candle tensor
pub fn rtx_to_candle(tensor: &Tensor, device: &CandleDevice) -> Result<CandleTensor> {
let shape: Vec<usize> = tensor.shape().to_vec();
let data = tensor
.to_vec_f32()
.map_err(|e| CandleError::tensor_conversion(format!("Failed to extract f32 data: {e}")))?;
debug!("Converting rtx tensor {:?} to Candle", shape);
let candle_device = device.to_candle()?;
let candle_tensor = CandleTensor::from_vec(data, shape.as_slice(), &candle_device)?;
Ok(candle_tensor)
}
/// Convert a Candle tensor to rtx-tensor
pub fn candle_to_rtx(tensor: &CandleTensor, device: &Device) -> Result<Tensor> {
let shape: Vec<usize> = tensor.dims().to_vec();
// Get data as f32
let data = match tensor.dtype() {
DType::F32 => tensor
.to_vec1::<f32>()
.or_else(|_| tensor.flatten_all()?.to_vec1::<f32>())?,
DType::F64 => {
let f64_data: Vec<f64> = tensor
.to_vec1()
.or_else(|_| tensor.flatten_all()?.to_vec1())?;
f64_data.into_iter().map(|x| x as f32).collect()
}
DType::F16 => {
let f16_data: Vec<half::f16> = tensor
.to_vec1()
.or_else(|_| tensor.flatten_all()?.to_vec1())?;
f16_data.into_iter().map(half::f16::to_f32).collect()
}
DType::BF16 => {
let bf16_data: Vec<half::bf16> = tensor
.to_vec1()
.or_else(|_| tensor.flatten_all()?.to_vec1())?;
bf16_data.into_iter().map(half::bf16::to_f32).collect()
}
_ => {
return Err(CandleError::UnsupportedDType(format!(
"{:?}",
tensor.dtype()
)));
}
};
debug!("Converting Candle tensor {:?} to rtx", shape);
Tensor::from_vec(data, &shape, device)
.map_err(|e| CandleError::tensor_conversion(format!("Failed to create tensor: {e}")))
}
/// Convert Candle `DType` to string
pub fn dtype_to_string(dtype: DType) -> &'static str {
match dtype {
DType::F32 => "f32",
DType::F64 => "f64",
DType::F16 => "f16",
DType::BF16 => "bf16",
DType::I64 => "i64",
DType::I32 => "i32",
DType::I16 => "i16",
DType::U32 => "u32",
DType::U8 => "u8",
DType::F8E4M3 => "f8e4m3",
DType::F6E2M3 => "f6e2m3",
DType::F6E3M2 => "f6e3m2",
DType::F4 => "f4",
DType::F8E8M0 => "f8e8m0",
_ => "unknown",
}
}
/// Shape utilities
pub struct ShapeUtils;
impl ShapeUtils {
/// Calculate total number of elements
pub fn numel(shape: &[usize]) -> usize {
shape.iter().product()
}
/// Check if shapes are equal
pub fn shapes_equal(a: &[usize], b: &[usize]) -> bool {
a == b
}
/// Validate shape for tensor creation
pub fn validate_shape(shape: &[usize], data_len: usize) -> bool {
Self::numel(shape) == data_len
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_numel() {
assert_eq!(ShapeUtils::numel(&[2, 3, 4]), 24);
assert_eq!(ShapeUtils::numel(&[1]), 1);
assert_eq!(ShapeUtils::numel(&[10, 10]), 100);
}
#[test]
fn test_shapes_equal() {
assert!(ShapeUtils::shapes_equal(&[2, 3], &[2, 3]));
assert!(!ShapeUtils::shapes_equal(&[2, 3], &[3, 2]));
}
#[test]
fn test_validate_shape() {
assert!(ShapeUtils::validate_shape(&[2, 3], 6));
assert!(!ShapeUtils::validate_shape(&[2, 3], 5));
}
#[test]
fn test_dtype_to_string() {
assert_eq!(dtype_to_string(DType::F32), "f32");
assert_eq!(dtype_to_string(DType::F16), "f16");
assert_eq!(dtype_to_string(DType::BF16), "bf16");
}
}