use crate::matrix::traits::FusedTensorOps;
use candle_core::{DType, Device, Result, Tensor};
const CEILING: f64 = 30.0;
fn ramp(n: usize, f: usize, scale: f32, shift: f32) -> Vec<f32> {
(0..n * f)
.map(|i| ((i % 97) as f32 / 97.0 - 0.5) * scale + shift)
.collect()
}
fn assert_matches_op_chain(n: usize, f: usize, shape: (usize, usize)) -> Result<()> {
let dev = Device::Cpu;
let (n_off, f_off) = shape;
let s = Tensor::from_vec(ramp(n, f, 80.0, 2.0), (n, f), &dev)?;
let offset = Tensor::from_vec(ramp(n_off, f_off, 12.0, -3.0), shape, &dev)?;
let want = s
.broadcast_add(&offset)?
.minimum(CEILING)?
.exp()?
.flatten_all()?
.to_vec1::<f32>()?;
let got = s
.clamped_exp_add_inplace(&offset, CEILING)
.unwrap()
.flatten_all()?
.to_vec1::<f32>()?;
assert_eq!(
got, want,
"offset {shape:?} on a [{n}, {f}] receiver diverged"
);
Ok(())
}
#[test]
fn every_broadcast_shape_matches_the_op_chain() -> Result<()> {
assert_matches_op_chain(41, 29, (41, 29))?; assert_matches_op_chain(37, 53, (1, 53))?; assert_matches_op_chain(37, 53, (37, 1))?; Ok(())
}
#[test]
fn many_rows_and_thin_panels_stay_in_order() -> Result<()> {
assert_matches_op_chain(1024, 7, (1024, 7))?;
assert_matches_op_chain(1024, 7, (1, 7))?;
assert_matches_op_chain(4096, 3, (4096, 1))?;
Ok(())
}
#[test]
fn the_ceiling_binds() -> Result<()> {
let dev = Device::Cpu;
let s = Tensor::from_vec(vec![100.0f32, 0.0, -100.0, 29.0], (1, 4), &dev)?;
let offset = Tensor::zeros((1, 4), DType::F32, &dev)?;
let got = s
.clamped_exp_add_inplace(&offset, CEILING)
.unwrap()
.flatten_all()?
.to_vec1::<f32>()?;
assert_eq!(got[0], 30f32.exp(), "over the ceiling was not clamped");
assert_eq!(got[1], 1.0);
assert_eq!(
got[2],
(-100f32).exp(),
"underflow is the right answer here"
);
assert_eq!(got[3], 29f32.exp(), "under the ceiling was altered");
assert!(got[0].is_finite(), "the guard let exp overflow");
Ok(())
}
#[test]
fn unfusable_shapes_fall_back() -> Result<()> {
let dev = Device::Cpu;
let (n, f) = (8, 5);
let s = Tensor::from_vec(ramp(n, f, 20.0, 1.0), (n, f), &dev)?;
let offset = Tensor::from_vec(ramp(1, f, 4.0, 0.5), f, &dev)?;
let want = s
.broadcast_add(&offset)?
.minimum(CEILING)?
.exp()?
.flatten_all()?
.to_vec1::<f32>()?;
let got = s
.clamped_exp_add_inplace(&offset, CEILING)
.unwrap()
.flatten_all()?
.to_vec1::<f32>()?;
assert_eq!(got, want, "the fallback changed the answer");
Ok(())
}