pub mod device;
pub mod renaming_backend;
pub mod tensor_ext;
#[cfg(test)]
pub(crate) mod tests {
use std::error::Error;
use std::fmt::Debug;
use approx::{assert_relative_eq, AbsDiffEq, Relative, RelativeEq};
use candle_core::{DType, Device, Tensor, WithDType, D};
use ndarray::{ArrayBase, ArrayD, Data, Dimension};
use rand_core::RngCore;
use rand_pcg::Pcg32;
use snafu::{ResultExt, Snafu};
pub trait IntoArrayD<T> {
fn into_arrayd(self) -> Result<ArrayD<T>, Box<dyn Error>>;
}
impl<T> IntoArrayD<T> for Tensor
where
T: WithDType,
{
fn into_arrayd(self) -> Result<ArrayD<T>, Box<dyn Error>> {
(&self).into_arrayd()
}
}
impl<T> IntoArrayD<T> for &Tensor
where
T: WithDType,
{
fn into_arrayd(self) -> Result<ArrayD<T>, Box<dyn Error>> {
let data = self.reshape(((),))?.to_vec1()?;
Ok(ArrayD::from_shape_vec(self.shape().dims(), data)?)
}
}
impl<S, D, T> IntoArrayD<T> for ArrayBase<S, D>
where
D: Dimension,
S: Data<Elem = T>,
T: Clone,
{
fn into_arrayd(self) -> Result<ArrayD<T>, Box<dyn Error>> where {
Ok(self.to_owned().into_dyn())
}
}
macro_rules! assert_tensor_eq {
($lhs:expr, $rhs:expr $(, $opt:ident = $val:expr)*) => {
crate::util::tests::assert_tensor_eq_($lhs, $rhs, approx::Relative::default()$(.$opt($val))*)
};
($lhs:expr, $rhs:expr $(, $opt:ident = $val:expr)*,) => {
crate::util::tests::assert_tensor_eq_($lhs, $rhs, approx::Relative::default()$(.$opt($val))*)
};
}
pub(crate) use assert_tensor_eq;
pub(crate) fn assert_tensor_eq_<T>(
a: impl IntoArrayD<T>,
b: impl IntoArrayD<T>,
relative: Relative<T>,
) where
T: AbsDiffEq<Epsilon = T> + RelativeEq + Clone + Debug,
{
let a = a.into_arrayd().expect("Cannot convert array");
let b = b.into_arrayd().expect("Cannot convert array");
assert_eq!(
a.shape(),
b.shape(),
"Shape mismatch: {:?}, {:?}",
a.shape(),
b.shape()
);
assert_relative_eq!(
a,
b,
epsilon = relative.epsilon,
max_relative = relative.max_relative
);
}
trait PseudoRandom {
fn pseudo_random(len: usize, device: &Device) -> Self;
}
impl PseudoRandom for Tensor {
fn pseudo_random(len: usize, device: &Device) -> Self {
let mut rng = Pcg32::new(len as u64, 0);
let iter = (0..len).map(|_| {
let next = rng.next_u32();
let mantissa_bits_shift = u32::BITS - f32::MANTISSA_DIGITS;
let zero_one =
(next >> mantissa_bits_shift) as f32 / (1 << f32::MANTISSA_DIGITS) as f32;
let sign = (next & 1) as f32;
zero_one - sign
});
Tensor::from_iter(iter, device).expect("Cannot allocate random tensor")
}
}
#[derive(Debug, Snafu)]
pub enum PseudoRandomReductionError {
#[snafu(display("Cannot calculate matmul of tensor and random vector"))]
MatMul { source: candle_core::Error },
#[snafu(display("Cannot get size of last dimension, tensor is a scalar"))]
Scalar { source: candle_core::Error },
#[snafu(display("Cannot reshape"))]
Shape { source: candle_core::Error },
#[snafu(display("Cannot convert tensor to f32"))]
ToDType { source: candle_core::Error },
}
pub trait PseudoRandomReduction {
fn pseudo_random_reduction(self) -> Result<Tensor, PseudoRandomReductionError>;
}
impl PseudoRandomReduction for &Tensor {
fn pseudo_random_reduction(self) -> Result<Tensor, PseudoRandomReductionError> {
let size = self.dim(D::Minus1).context(ScalarSnafu)?;
let random = Tensor::pseudo_random(size, self.device())
.reshape((size, 1))
.context(ShapeSnafu)?;
self.to_dtype(DType::F32)
.context(ToDTypeSnafu)?
.broadcast_matmul(&random)
.context(MatMulSnafu)?
.squeeze(D::Minus1)
.context(ShapeSnafu)
}
}
}