use candle_core::Device;
use candle_core::Tensor;
use super::*;
#[test]
fn padding_mask_zeroes_real_tokens_and_floors_padding() {
let attention_mask = Tensor::new(&[[1u32, 1, 0], [1, 1, 1]], &Device::Cpu).unwrap();
let mask = AttentionMask::padding(&attention_mask, &[2, 3], 4, 64, DType::F16).unwrap();
let additive = mask.additive().expect("padded rows produce a mask");
assert_eq!(additive.dims(), &[2, 1, 1, 3]);
assert_eq!(additive.dtype(), DType::F16);
let values = additive
.to_dtype(DType::F32)
.unwrap()
.flatten_all()
.unwrap()
.to_vec1::<f32>()
.unwrap();
assert_eq!(values, vec![0.0, 0.0, -65504.0, 0.0, 0.0, 0.0]);
assert!(!mask.is_causal());
}
#[test]
fn padding_mask_is_none_without_padding() {
let attention_mask = Tensor::new(&[[1u32, 1], [1, 1]], &Device::Cpu).unwrap();
let mask = AttentionMask::padding(&attention_mask, &[2, 2], 2, 64, DType::F32).unwrap();
assert!(mask.additive().is_none() && !mask.is_causal());
}
#[test]
fn causal_mask_is_upper_triangular_on_cpu() {
let mask = AttentionMask::causal(3, 128, DType::F32, &Device::Cpu).unwrap();
let values = mask
.additive()
.unwrap()
.squeeze(0)
.unwrap()
.squeeze(0)
.unwrap()
.to_vec2::<f32>()
.unwrap();
let floor = f32::MIN;
assert_eq!(
values,
vec![
vec![0.0, floor, floor],
vec![0.0, 0.0, floor],
vec![0.0, 0.0, 0.0]
]
);
assert!(
!mask.is_causal(),
"the flag is only raised for the Metal kernel"
);
}
#[test]
fn a_lengths_slice_that_does_not_match_the_batch_is_refused() {
let attention_mask = Tensor::new(&[[1u32, 1, 0], [1, 1, 1]], &Device::Cpu).unwrap();
let bias = Tensor::zeros((1, 4, 3, 3), DType::F32, &Device::Cpu).unwrap();
for lengths in [&[3usize][..], &[3, 3, 3][..], &[][..]] {
for built in [
AttentionMask::padding(&attention_mask, lengths, 4, 64, DType::F32),
AttentionMask::padding_with_bias(&attention_mask, lengths, &bias, 4, 64, DType::F32),
AttentionMask::sliding(&attention_mask, lengths, 2, 4, 64, DType::F32),
] {
let err = built
.err()
.unwrap_or_else(|| panic!("{} lengths for 2 rows must be refused", lengths.len()));
assert!(
err.to_string().contains("for a batch of 2 rows"),
"{lengths:?}: {err}"
);
}
}
assert!(AttentionMask::padding(&attention_mask, &[2, 3], 4, 64, DType::F32).is_ok());
}
#[test]
fn causal_mask_materializes_for_a_head_size_the_kernel_rejects() {
let mask = AttentionMask::causal(3, 40, DType::F32, &Device::Cpu).unwrap();
assert!(mask.additive().is_some());
}
#[test]
fn sliding_mask_combines_the_window_with_padding() {
let attention_mask =
Tensor::new(&[[1u32, 1, 1, 1, 1, 1], [1, 1, 1, 1, 0, 0]], &Device::Cpu).unwrap();
let mask = AttentionMask::sliding(&attention_mask, &[6, 4], 3, 2, 64, DType::F32).unwrap();
let additive = mask
.additive()
.expect("a window narrower than the sequence produces a mask");
assert_eq!(additive.dims(), &[2, 1, 6, 6]);
let rows = additive.squeeze(1).unwrap().to_vec3::<f32>().unwrap();
let floor = f32::MIN;
let (row0, row1) = (&rows[0], &rows[1]);
for (i, (cols0, cols1)) in row0.iter().zip(row1.iter()).enumerate() {
for (j, (&value0, &value1)) in cols0.iter().zip(cols1.iter()).enumerate() {
let in_window = i.abs_diff(j) < 3;
assert_eq!(value0 == 0.0, in_window, "row 0 ({i}, {j})");
let visible1 = (in_window && j < 4) || i == j;
assert_eq!(value1 == 0.0, visible1, "row 1 ({i}, {j})");
if !visible1 {
assert_eq!(value1, floor);
}
}
}
assert!(!mask.is_causal());
}
#[test]
fn sliding_mask_is_none_when_the_window_covers_everything() {
let attention_mask = Tensor::new(&[[1u32, 1, 1]], &Device::Cpu).unwrap();
let mask = AttentionMask::sliding(&attention_mask, &[3], 3, 1, 64, DType::F32).unwrap();
assert!(mask.additive().is_none());
let padded = Tensor::new(&[[1u32, 1, 0]], &Device::Cpu).unwrap();
let mask = AttentionMask::sliding(&padded, &[2], 3, 1, 64, DType::F16).unwrap();
let additive = mask.additive().expect("padding still needs a mask");
assert_eq!(additive.dtype(), DType::F16);
let rows = additive
.to_dtype(DType::F32)
.unwrap()
.squeeze(1)
.unwrap()
.to_vec3::<f32>()
.unwrap();
assert_eq!(rows[0][0][2], -65504.0);
assert_eq!(rows[0][0][1], 0.0);
}
#[test]
fn sliding_mask_leaves_no_row_entirely_floored() {
let attention_mask = Tensor::new(&[[1u32, 1, 0, 0, 0, 0, 0, 0]], &Device::Cpu).unwrap();
let mask = AttentionMask::sliding(&attention_mask, &[2], 2, 1, 64, DType::F32).unwrap();
let rows = mask
.additive()
.expect("a padded row produces a mask")
.squeeze(1)
.unwrap()
.to_vec3::<f32>()
.unwrap();
for (i, row) in rows[0].iter().enumerate() {
let open: Vec<usize> = row
.iter()
.enumerate()
.filter(|(_, value)| **value == 0.0)
.map(|(j, _)| j)
.collect();
assert!(!open.is_empty(), "row {i} has no visible key");
assert!(
open.contains(&i),
"row {i} cannot see its own key: {open:?}"
);
}
assert_eq!(
rows[0][7],
vec![
f32::MIN,
f32::MIN,
f32::MIN,
f32::MIN,
f32::MIN,
f32::MIN,
f32::MIN,
0.0
]
);
}
#[cfg(feature = "metal")]
#[test]
fn every_mask_reaches_the_metal_kernel_as_a_broadcast_view() {
let device = Device::new_metal(0).expect("a metal device");
let padded = Tensor::new(&[[1u32, 1, 0], [1, 1, 1]], &device).unwrap();
let full = Tensor::new(&[[1u32, 1, 1], [1, 1, 1]], &device).unwrap();
for dtype in [DType::F32, DType::F16, DType::BF16] {
let bias = Tensor::zeros((1, 4, 3, 3), DType::F32, &device).unwrap();
for (name, built, strides) in [
(
"padding",
AttentionMask::padding(&padded, &[2, 3], 4, 64, dtype),
[3usize, 0, 0, 1],
),
(
"padding_with_bias, unpadded",
AttentionMask::padding_with_bias(&full, &[3, 3], &bias, 4, 64, dtype),
[0, 9, 3, 1],
),
(
"sliding",
AttentionMask::sliding(&padded, &[2, 3], 2, 4, 64, dtype),
[9, 0, 3, 1],
),
] {
let additive = built
.unwrap()
.additive()
.unwrap_or_else(|| panic!("{name} in {dtype:?} produces an additive term"))
.clone();
assert_eq!(additive.dims(), &[2, 4, 3, 3], "{name} in {dtype:?}");
assert_eq!(additive.dtype(), dtype, "{name} in {dtype:?}");
assert_eq!(additive.stride(), &strides, "{name} in {dtype:?}");
assert!(
!additive.is_contiguous(),
"{name} in {dtype:?} was materialized"
);
}
}
}
#[test]
fn mask_floor_per_dtype() {
assert_eq!(mask_min(DType::F16), -65504.0);
assert_eq!(mask_min(DType::F32), f32::MIN as f64);
assert!(mask_min(DType::BF16) < -3.0e38);
}
mod bias;