use super::{additive_attention_mask, OpError, NEG_MASK};
use crate::autograd::{self, Tensor};
#[test]
fn additive_attention_mask_builds_broadcastable_rank4_shape() {
let mask = [1u8, 1, 1, 0, 0, 1, 1, 1, 1, 1];
let m = additive_attention_mask(&mask, 2, 5).expect("mask must build");
assert_eq!(
m.shape(),
&[2, 1, 1, 5],
"rank-4 [B,1,1,S] is what broadcasts over [B,heads,T,S] scores"
);
assert_eq!(m.numel(), 10);
for (i, &keep) in mask.iter().enumerate() {
let want = if keep == 1 { 0.0 } else { NEG_MASK };
assert_eq!(
m.data()[i],
want,
"position {i}: keep={keep} must map to {want}"
);
}
}
#[test]
fn additive_attention_mask_uses_a_finite_negative_constant() {
assert!(NEG_MASK.is_finite(), "NEG_MASK must be finite");
assert!(NEG_MASK < -1.0e8, "NEG_MASK must dominate any real logit");
assert!(
NEG_MASK != f32::MIN && NEG_MASK != f32::NEG_INFINITY,
"NEG_MASK must be neither f32::MIN nor -inf"
);
let softmax_term = (NEG_MASK - 20.0f32).exp();
assert_eq!(
softmax_term, 0.0,
"exp(NEG_MASK - max) must underflow to exactly 0.0"
);
}
#[test]
fn additive_attention_mask_is_a_constant_not_graph_connected() {
autograd::clear_graph();
let m = additive_attention_mask(&[1u8, 1, 0], 1, 3).expect("mask must build");
assert!(
!m.requires_grad_enabled(),
"the mask BUILDER is a constant by contract; the graph-connectivity \
obligation lives on apply_additive_mask (plan 01-09), not here"
);
assert!(
m.is_leaf(),
"a constant mask must be a leaf with no recorded backward op"
);
}
#[test]
fn additive_attention_mask_single_row_batch_is_all_zeros_when_fully_valid() {
let m = additive_attention_mask(&[1u8, 1, 1, 1], 1, 4).expect("mask must build");
assert_eq!(m.shape(), &[1, 1, 1, 4]);
assert!(
m.data().iter().all(|&v| v == 0.0),
"a fully valid row must add nothing to the scores"
);
}
#[test]
fn additive_attention_mask_rejects_all_padding_row() {
let mask = [1u8, 1, 0, 0, 0, 0];
assert_eq!(
additive_attention_mask(&mask, 2, 3)
.expect_err("an all-padding row would make the whole softmax row -1e9"),
OpError::AllPaddingRow { row: 1 }
);
}
#[test]
fn additive_attention_mask_rejects_length_mismatch() {
assert_eq!(
additive_attention_mask(&[1u8, 1, 1], 2, 3).expect_err("mask.len() must be batch * seq"),
OpError::LengthMismatch { ids: 6, mask: 3 }
);
}
#[test]
fn additive_attention_mask_rejects_non_binary_value() {
assert_eq!(
additive_attention_mask(&[1u8, 2, 1], 1, 3).expect_err("only 0 and 1 are valid"),
OpError::NonBinaryMaskValue {
value: 2,
position: 1,
}
);
}
#[test]
fn additive_attention_mask_reports_non_binary_before_all_padding() {
assert_eq!(
additive_attention_mask(&[2u8, 2, 2], 1, 3).expect_err("malformed values come first"),
OpError::NonBinaryMaskValue {
value: 2,
position: 0,
}
);
}
#[test]
fn additive_attention_mask_rejects_zero_dimension() {
assert_eq!(
additive_attention_mask(&[], 0, 3).expect_err("batch 0 must be rejected"),
OpError::ZeroDimension { which: "batch" }
);
assert_eq!(
additive_attention_mask(&[], 2, 0).expect_err("seq 0 must be rejected"),
OpError::ZeroDimension { which: "seq" }
);
}
#[test]
fn additive_attention_mask_rejects_shape_overflow() {
assert_eq!(
additive_attention_mask(&[1u8], usize::MAX, 2)
.expect_err("batch * seq overflow must be rejected before allocating"),
OpError::ShapeOverflow {
dims: vec![usize::MAX, 2],
}
);
}
#[test]
fn additive_attention_mask_error_display_names_the_condition() {
let err = additive_attention_mask(&[1u8, 5], 1, 2).expect_err("non-binary expected");
let rendered = err.to_string();
assert!(
rendered.contains("NonBinaryMaskValue") && rendered.contains('5'),
"Display must name the condition and the offending value, got {rendered}"
);
}
#[test]
fn additive_attention_mask_added_to_scores_keeps_the_score_graph_alive() {
autograd::clear_graph();
let scores = Tensor::new(&[0.5, 0.25, -0.75, 1.5], &[1, 1, 1, 4]).requires_grad();
let sid = scores.id();
let m = additive_attention_mask(&[1u8, 1, 0, 0], 1, 4).expect("mask must build");
let masked = scores.add(&m);
assert!(
masked.requires_grad_enabled(),
"adding a constant mask must NOT sever the score graph"
);
masked.sum().backward();
let grad = autograd::get_grad(sid).expect("scores must receive gradient through the mask add");
assert_eq!(grad.shape(), &[1, 1, 1, 4]);
assert!(
grad.data().iter().all(|&v| (v - 1.0).abs() < 1e-6),
"d(sum(scores + const))/dscores must be all ones, got {:?}",
grad.data()
);
}