use candle_core::Device;
use candle_nn::VarMap;
use tempfile::tempdir;
use super::*;
use crate::layers::activation::Activation;
use crate::models::bert::BertConfig;
fn tiny_config() -> BertConfig {
BertConfig {
vocab_size: 64,
hidden_size: 1,
num_hidden_layers: 0,
num_attention_heads: 1,
intermediate_size: 1,
hidden_act: Activation::Gelu,
max_position_embeddings: 32,
type_vocab_size: 1,
layer_norm_eps: 1e-12,
}
}
fn xlm_config(bert: BertConfig) -> XlmRobertaConfig {
XlmRobertaConfig {
bert,
pad_token_id: 1,
position_embedding_type: "absolute".to_string(),
}
}
fn dummy_encoder() -> XlmRobertaModel {
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
XlmRobertaModel::load(xlm_config(tiny_config()), vb).unwrap()
}
fn head(weight: Tensor, bias: Tensor, vocab_size: usize) -> M3Sparse {
M3Sparse {
encoder: dummy_encoder(),
weight,
bias,
vocab_size,
dropped: DROPPED,
}
}
#[test]
fn a_repeated_token_keeps_its_largest_weight() {
let weight = Tensor::new(&[[1f32]], &Device::Cpu).unwrap();
let bias = Tensor::new(&[0f32], &Device::Cpu).unwrap();
let model = head(weight, bias, 64);
let hidden = Tensor::new(&[[[0.25f32], [0.75]]], &Device::Cpu).unwrap();
let input_ids = Tensor::from_vec(vec![5u32, 5], (1, 2), &Device::Cpu).unwrap();
let rows = model.scatter_rows(&hidden, &input_ids, &[2]).unwrap();
assert_eq!(rows[0].indices, vec![5]);
assert!(
(rows[0].values[0] - 0.75).abs() < 1e-6,
"{:?}",
rows[0].values
);
}
#[test]
fn the_four_special_ids_are_dropped() {
let weight = Tensor::new(&[[1f32]], &Device::Cpu).unwrap();
let bias = Tensor::new(&[0.1f32], &Device::Cpu).unwrap();
let model = head(weight, bias, 64);
let hidden = Tensor::new(
&[[[0.1f32], [0.2], [0.3], [0.4], [0.5], [0.6]]],
&Device::Cpu,
)
.unwrap();
let input_ids = Tensor::from_vec(vec![0u32, 2, 1, 3, 9, 42], (1, 6), &Device::Cpu).unwrap();
let rows = model.scatter_rows(&hidden, &input_ids, &[6]).unwrap();
assert_eq!(rows[0].indices, vec![9, 42]);
for (got, want) in rows[0].values.iter().zip([0.6f32, 0.7]) {
assert!((got - want).abs() < 1e-6, "{got} vs {want}");
}
}
#[test]
fn an_all_zero_row_is_an_empty_embedding_not_an_error() {
let weight = Tensor::new(&[[0f32]], &Device::Cpu).unwrap();
let bias = Tensor::new(&[0f32], &Device::Cpu).unwrap();
let model = head(weight, bias, 64);
let hidden = Tensor::new(&[[[1f32], [2.0]]], &Device::Cpu).unwrap();
let input_ids = Tensor::from_vec(vec![9u32, 10], (1, 2), &Device::Cpu).unwrap();
let rows = model.scatter_rows(&hidden, &input_ids, &[2]).unwrap();
assert!(rows[0].is_empty(), "{:?}", rows[0]);
}
#[test]
fn padded_positions_never_reach_the_fold() {
let weight = Tensor::new(&[[1f32]], &Device::Cpu).unwrap();
let bias = Tensor::new(&[0f32], &Device::Cpu).unwrap();
let model = head(weight, bias, 64);
let hidden = Tensor::new(&[[[0.1f32], [0.5], [0.05], [9.0], [9.0]]], &Device::Cpu).unwrap();
let input_ids = Tensor::from_vec(vec![0u32, 55, 2, 999, 999], (1, 5), &Device::Cpu).unwrap();
let rows = model.scatter_rows(&hidden, &input_ids, &[3]).unwrap();
assert_eq!(rows[0].indices, vec![55]);
assert!(
(rows[0].values[0] - 0.5).abs() < 1e-6,
"{:?}",
rows[0].values
);
}
#[test]
fn vocab_size_never_sizes_a_tensor() {
let weight = Tensor::new(&[[1f32]], &Device::Cpu).unwrap();
let bias = Tensor::new(&[0f32], &Device::Cpu).unwrap();
let model = head(weight, bias, 250_002);
let seq = 256;
let hidden = Tensor::from_vec(vec![0.5f32; seq], (1, seq, 1), &Device::Cpu).unwrap();
let ids: Vec<u32> = (0..seq as u32).collect();
let input_ids = Tensor::from_vec(ids, (1, seq), &Device::Cpu).unwrap();
let rows = model.scatter_rows(&hidden, &input_ids, &[seq]).unwrap();
assert_eq!(rows[0].len(), seq - 4);
}
#[test]
fn a_missing_sparse_linear_file_is_a_named_load_error() {
let dir = tempdir().unwrap();
let missing = dir.path().join("sparse_linear.pt");
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
let err = M3Sparse::load(xlm_config(tiny_config()), vb, &missing).unwrap_err();
assert!(
matches!(err, CanddingError::MissingFile(ref p) if p == &missing),
"{err}"
);
assert!(err.to_string().contains("sparse_linear.pt"), "{err}");
}
#[test]
fn load_reads_weight_and_bias_from_a_real_archive() {
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("pickle")
.join("linear_head.bin");
let config = xlm_config(BertConfig {
hidden_size: 4,
..tiny_config()
});
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
let model = M3Sparse::load(config, vb, &fixture).unwrap();
assert_eq!(
model
.weight
.to_dtype(DType::F32)
.unwrap()
.to_vec2::<f32>()
.unwrap(),
vec![vec![1.5f32, -2.0, 0.5, 0.25]]
);
assert_eq!(
model
.bias
.to_dtype(DType::F32)
.unwrap()
.to_vec1::<f32>()
.unwrap(),
vec![0.75f32]
);
let input_ids = Tensor::from_vec(vec![4u32, 5, 6, 7], (1, 4), &Device::Cpu).unwrap();
let attention_mask = Tensor::from_vec(vec![1u32; 4], (1, 4), &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: &[4],
};
model.forward(&input).unwrap();
}
#[test]
fn the_head_is_cast_from_f16_to_the_runs_dtype() {
let weight = Tensor::new(&[[0.5f32, -0.25, 0.1, 0.2]], &Device::Cpu)
.unwrap()
.to_dtype(DType::F16)
.unwrap();
let bias = Tensor::new(&[0.1f32], &Device::Cpu)
.unwrap()
.to_dtype(DType::F16)
.unwrap();
assert_eq!(weight.dtype(), DType::F16);
let config = xlm_config(BertConfig {
hidden_size: 4,
..tiny_config()
});
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
let model = M3Sparse::from_raw_head(config, vb, weight, bias).unwrap();
assert_eq!(model.weight.dtype(), DType::F32);
assert_eq!(model.bias.dtype(), DType::F32);
let input_ids = Tensor::from_vec(vec![4u32, 5, 6, 7], (1, 4), &Device::Cpu).unwrap();
let attention_mask = Tensor::from_vec(vec![1u32; 4], (1, 4), &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: &[4],
};
model.forward(&input).unwrap();
}