use super::*;
#[test]
fn scaled_dot_product_and_the_weighted_sibling_agree_on_cpu() {
let q = Tensor::randn(0f32, 1.0, (2, 4, 3, 8), &Device::Cpu).unwrap();
let k = Tensor::randn(0f32, 1.0, (2, 4, 3, 8), &Device::Cpu).unwrap();
let v = Tensor::randn(0f32, 1.0, (2, 4, 3, 8), &Device::Cpu).unwrap();
let attention_mask = Tensor::new(&[[1u32, 1, 0], [1, 1, 1]], &Device::Cpu).unwrap();
let mask = AttentionMask::padding(&attention_mask, &[2, 3], 4, 8, DType::F32).unwrap();
let plain = scaled_dot_product(&q, &k, &v, &mask, 0.5).unwrap();
let (with_weights, _weights) = scaled_dot_product_with_weights(&q, &k, &v, &mask, 0.5).unwrap();
let diff = max_diff(&plain, &with_weights);
assert!(diff < 1e-6, "cpu context max abs diff {diff}");
}
#[cfg(feature = "metal")]
#[test]
fn scaled_dot_product_and_the_weighted_sibling_agree_on_metal() {
let device = Device::new_metal(0).expect("a metal device");
let head_dim = 64;
assert!(metal_sdpa_applies(&device, head_dim, DType::F32));
let q = Tensor::randn(0f32, 1.0, (2, 4, 5, head_dim), &device).unwrap();
let k = Tensor::randn(0f32, 1.0, (2, 4, 5, head_dim), &device).unwrap();
let v = Tensor::randn(0f32, 1.0, (2, 4, 5, head_dim), &device).unwrap();
let scale = 1.0 / (head_dim as f64).sqrt();
let fused = scaled_dot_product(&q, &k, &v, &AttentionMask::none(), scale).unwrap();
let (matmul, weights) =
scaled_dot_product_with_weights(&q, &k, &v, &AttentionMask::none(), scale).unwrap();
let diff = max_diff(
&fused.to_device(&Device::Cpu).unwrap(),
&matmul.to_device(&Device::Cpu).unwrap(),
);
assert!(diff < 1e-4, "fused vs matmul max abs diff {diff}");
let sums = values(&weights.sum(3).unwrap().to_device(&Device::Cpu).unwrap());
for sum in sums {
assert!((sum - 1.0).abs() < 1e-4, "{sum}");
}
}
#[test]
fn weights_match_hand_computation() {
let q = t(&[1.0, 0.0, 0.0, 1.0], (1, 1, 2, 2));
let k = t(&[0.0, 0.0, 3f32.ln(), 0.0], (1, 1, 2, 2));
let v = t(&[2.0, 3.0, 5.0, 7.0], (1, 1, 2, 2));
let (_, weights) =
scaled_dot_product_with_weights(&q, &k, &v, &AttentionMask::none(), 1.0).unwrap();
let rows = weights
.squeeze(0)
.unwrap()
.squeeze(0)
.unwrap()
.to_vec2::<f32>()
.unwrap();
assert!(
(rows[0][0] - 0.25).abs() < 1e-6 && (rows[0][1] - 0.75).abs() < 1e-6,
"{rows:?}"
);
assert!(
(rows[1][0] - 0.5).abs() < 1e-6 && (rows[1][1] - 0.5).abs() < 1e-6,
"{rows:?}"
);
}
#[test]
fn each_row_of_the_weights_sums_to_one() {
let q = Tensor::randn(0f32, 1.0, (2, 3, 4, 5), &Device::Cpu).unwrap();
let k = Tensor::randn(0f32, 1.0, (2, 3, 4, 5), &Device::Cpu).unwrap();
let v = Tensor::randn(0f32, 1.0, (2, 3, 4, 5), &Device::Cpu).unwrap();
let (_, weights) =
scaled_dot_product_with_weights(&q, &k, &v, &AttentionMask::none(), 0.4).unwrap();
let sums = values(&weights.sum(3).unwrap());
for sum in sums {
assert!((sum - 1.0).abs() < 1e-5, "{sum}");
}
}
#[test]
fn a_padded_position_contributes_nothing_to_a_real_positions_row() {
let q = t(&[1.0, 0.0], (1, 1, 1, 2));
let k = t(&[0.0, 0.0, 3f32.ln(), 0.0], (1, 1, 2, 2));
let v = t(&[9.0, 4.0, 6.0, 2.0], (1, 1, 2, 2));
let attention_mask = Tensor::new(&[[1u32, 0]], &Device::Cpu).unwrap();
let mask = AttentionMask::padding(&attention_mask, &[1], 1, 2, DType::F32).unwrap();
let (_, weights) = scaled_dot_product_with_weights(&q, &k, &v, &mask, 1.0).unwrap();
let row = values(&weights);
assert!(
(row[0] - 1.0).abs() < 1e-6 && row[1].abs() < 1e-6,
"{row:?}"
);
}