use std::collections::HashMap;
use candle_core::DType;
use candle_core::Device;
use candle_core::Tensor;
pub(crate) const HIDDEN: usize = 4;
pub(crate) const VOCAB: usize = 16;
pub(crate) const ROWS: usize = 8;
pub(crate) const PAD: usize = 1;
pub(crate) fn config_json(position_embedding_type: &str) -> String {
format!(
r#"{{"vocab_size":{VOCAB},"hidden_size":{HIDDEN},"num_hidden_layers":0,"num_attention_heads":1,
"intermediate_size":1,"max_position_embeddings":{ROWS},"type_vocab_size":1,"layer_norm_eps":1e-5,
"pad_token_id":{PAD},"position_embedding_type":"{position_embedding_type}"}}"#
)
}
fn word_row(id: usize) -> [f32; HIDDEN] {
let x = id as f32;
[0.1 * x, -0.05 * x, 0.02 * x * x, 0.3]
}
fn position_row(row: usize) -> [f32; HIDDEN] {
let x = row as f32;
[x, 0.1 * x * x, -0.5 * x, 1.0 / (x + 1.0)]
}
const TOKEN_TYPE_ROW: [f32; HIDDEN] = [0.3, -0.2, 0.1, 0.05];
const DENSE: [[f32; HIDDEN]; HIDDEN] = [
[0.9, -0.3, 0.2, 0.1],
[0.4, 0.8, -0.5, 0.3],
[-0.2, 0.6, 0.7, -0.4],
[0.1, -0.7, 0.3, 0.5],
];
const DENSE_BIAS: [f32; HIDDEN] = [0.05, -0.1, 0.2, 0.0];
const OUT_PROJ: [f32; HIDDEN] = [0.7, -0.4, 0.25, 1.1];
const OUT_PROJ_BIAS: f32 = 0.1;
fn table(rows: usize, row: impl Fn(usize) -> [f32; HIDDEN]) -> Tensor {
let values: Vec<f32> = (0..rows).flat_map(row).collect();
Tensor::from_vec(values, (rows, HIDDEN), &Device::Cpu).unwrap()
}
pub(crate) fn tensors() -> HashMap<String, Tensor> {
let vector = |values: &[f32]| Tensor::new(values, &Device::Cpu).unwrap();
let mut map = HashMap::new();
map.insert(
"roberta.embeddings.word_embeddings.weight".to_string(),
table(VOCAB, word_row),
);
map.insert(
"roberta.embeddings.position_embeddings.weight".to_string(),
table(ROWS, position_row),
);
map.insert(
"roberta.embeddings.token_type_embeddings.weight".to_string(),
table(1, |_| TOKEN_TYPE_ROW),
);
map.insert(
"roberta.emb_ln.weight".to_string(),
Tensor::ones(HIDDEN, DType::F32, &Device::Cpu).unwrap(),
);
map.insert(
"roberta.emb_ln.bias".to_string(),
Tensor::zeros(HIDDEN, DType::F32, &Device::Cpu).unwrap(),
);
map.insert(
"classifier.dense.weight".to_string(),
table(HIDDEN, |row| DENSE[row]),
);
map.insert("classifier.dense.bias".to_string(), vector(&DENSE_BIAS));
map.insert(
"classifier.out_proj.weight".to_string(),
table(1, |_| OUT_PROJ),
);
map.insert(
"classifier.out_proj.bias".to_string(),
vector(&[OUT_PROJ_BIAS]),
);
map
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Numbering {
FromPadPlusOne,
FromZero,
NoTable,
}
pub(crate) fn expected_logit(first_id: u32, numbering: Numbering) -> f64 {
let position = match numbering {
Numbering::FromPadPlusOne if first_id as usize == PAD => position_row(PAD),
Numbering::FromPadPlusOne => position_row(PAD + 1),
Numbering::FromZero => position_row(0),
Numbering::NoTable => [0.0; HIDDEN],
};
logit(first_id, position)
}
fn logit(first_id: u32, position: [f32; HIDDEN]) -> f64 {
let word = word_row(first_id as usize);
let summed: [f64; HIDDEN] =
std::array::from_fn(|k| word[k] as f64 + position[k] as f64 + TOKEN_TYPE_ROW[k] as f64);
let mean = summed.iter().sum::<f64>() / HIDDEN as f64;
let variance = summed.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / HIDDEN as f64;
let normalized = summed.map(|v| (v - mean) / (variance + 1e-5).sqrt());
let projected: [f64; HIDDEN] = std::array::from_fn(|r| {
let dot: f64 = (0..HIDDEN)
.map(|k| DENSE[r][k] as f64 * normalized[k])
.sum();
(dot + DENSE_BIAS[r] as f64).tanh()
});
(0..HIDDEN)
.map(|k| OUT_PROJ[k] as f64 * projected[k])
.sum::<f64>()
+ OUT_PROJ_BIAS as f64
}
#[test]
fn the_three_numberings_score_apart() {
for first_id in [0, 1, 2] {
let right = expected_logit(first_id, Numbering::FromPadPlusOne);
for wrong in [Numbering::FromZero, Numbering::NoTable] {
let moved = (right - expected_logit(first_id, wrong)).abs();
assert!(moved > 0.05, "first id {first_id}, {wrong:?}: {moved}");
}
}
let by_length = logit(PAD as u32, position_row(PAD + 1));
let moved = (expected_logit(PAD as u32, Numbering::FromPadPlusOne) - by_length).abs();
assert!(
moved > 0.05,
"the pad id at row pad against row pad + 1: {moved}"
);
}