use super::*;
fn empty_hidden(batch: usize, hidden_size: usize) -> Tensor {
Tensor::from_vec(Vec::<f32>::new(), (batch, 0, hidden_size), &Device::Cpu).unwrap()
}
fn empty_mask(batch: usize) -> Tensor {
Tensor::from_vec(Vec::<u32>::new(), (batch, 0), &Device::Cpu).unwrap()
}
#[test]
fn pool_logits_returns_empty_rows_for_a_zero_length_sequence() {
let model = tiny_model();
let rows = model
.pool_logits(&empty_hidden(1, 8), &empty_mask(1))
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].is_empty(), "{:?}", rows[0]);
}
#[test]
fn pool_logits_handles_every_row_of_a_multi_row_zero_length_batch() {
let model = tiny_model();
let rows = model
.pool_logits(&empty_hidden(3, 8), &empty_mask(3))
.unwrap();
assert_eq!(rows.len(), 3);
assert!(rows.iter().all(SparseEmbedding::is_empty));
}
#[test]
fn forward_returns_an_empty_row_for_a_zero_token_batch_of_one() {
let model = tiny_model();
let input_ids = Tensor::from_vec(Vec::<u32>::new(), (1, 0), &Device::Cpu).unwrap();
let attention_mask = Tensor::from_vec(Vec::<u32>::new(), (1, 0), &Device::Cpu).unwrap();
let token_type_ids = input_ids.zeros_like().unwrap();
let input = EncoderInput {
input_ids: &input_ids,
attention_mask: &attention_mask,
token_type_ids: &token_type_ids,
lengths: &[0],
};
let rows = model.forward(&input).unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].is_empty());
}
#[test]
fn a_zero_length_row_among_real_rows_matches_the_row_encoded_alone() {
let model = tiny_model();
let device = Device::Cpu;
let alone_ids = Tensor::from_vec(vec![1u32, 2], (1, 2), &device).unwrap();
let alone_mask = Tensor::from_vec(vec![1u32, 1], (1, 2), &device).unwrap();
let alone_types = alone_ids.zeros_like().unwrap();
let alone = model
.forward(&EncoderInput {
input_ids: &alone_ids,
attention_mask: &alone_mask,
token_type_ids: &alone_types,
lengths: &[2],
})
.unwrap();
let input_ids = Tensor::from_vec(vec![1u32, 2, 3, 0], (2, 2), &device).unwrap();
let attention_mask = Tensor::from_vec(vec![1u32, 1, 0, 0], (2, 2), &device).unwrap();
let token_type_ids = input_ids.zeros_like().unwrap();
let rows = model
.forward(&EncoderInput {
input_ids: &input_ids,
attention_mask: &attention_mask,
token_type_ids: &token_type_ids,
lengths: &[2, 0],
})
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(
rows[0].indices, alone[0].indices,
"{:?} vs {:?}",
rows[0], alone[0]
);
for (a, b) in rows[0].values.iter().zip(&alone[0].values) {
assert!((a - b).abs() < 1e-5, "{:?} vs {:?}", rows[0], alone[0]);
}
assert!(rows[1].is_empty(), "{:?}", rows[1]);
}
#[test]
fn real_rows_on_either_side_of_an_empty_one_each_match_their_own_alone_encoding() {
let model = tiny_model();
let device = Device::Cpu;
let alone_row = |ids: &[u32]| {
let input_ids = Tensor::from_vec(ids.to_vec(), (1, ids.len()), &device).unwrap();
let attention_mask = Tensor::from_vec(vec![1u32; ids.len()], (1, ids.len()), &device).unwrap();
let token_type_ids = input_ids.zeros_like().unwrap();
model
.forward(&EncoderInput {
input_ids: &input_ids,
attention_mask: &attention_mask,
token_type_ids: &token_type_ids,
lengths: &[ids.len()],
})
.unwrap()
.remove(0)
};
let alone_first = alone_row(&[1, 2]);
let alone_third = alone_row(&[4, 5, 3]);
let input_ids = Tensor::from_vec(vec![1u32, 2, 0, 0, 0, 0, 4, 5, 3], (3, 3), &device).unwrap();
let attention_mask =
Tensor::from_vec(vec![1u32, 1, 0, 0, 0, 0, 1, 1, 1], (3, 3), &device).unwrap();
let token_type_ids = input_ids.zeros_like().unwrap();
let rows = model
.forward(&EncoderInput {
input_ids: &input_ids,
attention_mask: &attention_mask,
token_type_ids: &token_type_ids,
lengths: &[2, 0, 3],
})
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].indices, alone_first.indices, "{:?}", rows[0]);
for (a, b) in rows[0].values.iter().zip(&alone_first.values) {
assert!(
(a - b).abs() < 1e-5,
"row 0: {:?} vs {:?}",
rows[0],
alone_first
);
}
assert!(rows[1].is_empty(), "{:?}", rows[1]);
assert_eq!(rows[2].indices, alone_third.indices, "{:?}", rows[2]);
for (a, b) in rows[2].values.iter().zip(&alone_third.values) {
assert!(
(a - b).abs() < 1e-5,
"row 2: {:?} vs {:?}",
rows[2],
alone_third
);
}
}
#[cfg(feature = "metal")]
#[test]
fn a_zero_length_row_among_real_rows_does_not_corrupt_the_real_row_on_metal() {
let device = candle_core::Device::new_metal(0).expect("a metal device");
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
let model = SpladeTiedSparse::load(tiny_qwen3_config(), vb).unwrap();
let alone_ids = Tensor::from_vec(vec![1u32, 2], (1, 2), &device).unwrap();
let alone_mask = Tensor::from_vec(vec![1u32, 1], (1, 2), &device).unwrap();
let alone_types = alone_ids.zeros_like().unwrap();
let alone = model
.forward(&EncoderInput {
input_ids: &alone_ids,
attention_mask: &alone_mask,
token_type_ids: &alone_types,
lengths: &[2],
})
.unwrap();
let input_ids = Tensor::from_vec(vec![1u32, 2, 3, 0], (2, 2), &device).unwrap();
let attention_mask = Tensor::from_vec(vec![1u32, 1, 0, 0], (2, 2), &device).unwrap();
let token_type_ids = input_ids.zeros_like().unwrap();
let rows = model
.forward(&EncoderInput {
input_ids: &input_ids,
attention_mask: &attention_mask,
token_type_ids: &token_type_ids,
lengths: &[2, 0],
})
.unwrap();
assert_eq!(rows.len(), 2);
assert!(
rows[0].values.iter().all(|v| v.is_finite()),
"real row must stay finite on Metal: {:?}",
rows[0]
);
assert_eq!(
rows[0].indices, alone[0].indices,
"{:?} vs {:?}",
rows[0], alone[0]
);
for (a, b) in rows[0].values.iter().zip(&alone[0].values) {
assert!((a - b).abs() < 1e-4, "{:?} vs {:?}", rows[0], alone[0]);
}
assert!(rows[1].is_empty(), "{:?}", rows[1]);
}