use candle_core::DType;
use candle_core::Device;
use candle_nn::Linear;
use candle_nn::Module;
use candle_nn::VarBuilder;
use candle_nn::VarMap;
use super::*;
use crate::layers::attention::scaled_dot_product;
use crate::layers::mask::AttentionMask;
use crate::layers::rope::RotaryEmbedding;
use crate::models::xlm_roberta_flash::attention::XlmRobertaFlashAttention;
#[test]
fn the_fused_qkv_projection_splits_in_query_key_value_row_order() {
let hidden = 4;
let heads = 2;
let head_dim = 2;
let config = XlmRobertaFlashConfig {
hidden_size: hidden,
num_attention_heads: heads,
..tiny_config()
};
let mut varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
let attention = XlmRobertaFlashAttention::load(&config, vb.pp("mixer")).unwrap();
#[rustfmt::skip]
let q_block = [
0.7f32, -0.3, 0.2, 0.5,
-0.1, 0.9, -0.4, 0.2,
0.3, 0.1, 0.6, -0.2,
-0.5, 0.4, 0.1, 0.8,
];
#[rustfmt::skip]
let k_block = [
0.2f32, 0.6, -0.3, 0.1,
0.9, -0.2, 0.4, -0.1,
-0.4, 0.3, 0.7, 0.2,
0.1, -0.5, 0.2, 0.6,
];
#[rustfmt::skip]
let v_block = [
1.1f32, 0.2, -0.3, 0.4,
-0.2, 1.3, 0.1, -0.4,
0.3, -0.1, 1.2, 0.2,
-0.4, 0.3, -0.2, 1.4,
];
let mut rows = Vec::with_capacity(3 * hidden * hidden);
rows.extend_from_slice(&q_block);
rows.extend_from_slice(&k_block);
rows.extend_from_slice(&v_block);
let weight = Tensor::from_vec(rows, (3 * hidden, hidden), &Device::Cpu).unwrap();
let bias = Tensor::from_vec(
vec![
0.1f32, -0.2, 0.3, 0.05, 0.4, 0.1, -0.3, 0.2, -0.15, 0.25, 0.05, -0.1,
],
3 * hidden,
&Device::Cpu,
)
.unwrap();
varmap.set_one("mixer.Wqkv.weight", weight.clone()).unwrap();
varmap.set_one("mixer.Wqkv.bias", bias.clone()).unwrap();
varmap
.set_one(
"mixer.out_proj.weight",
Tensor::eye(hidden, DType::F32, &Device::Cpu).unwrap(),
)
.unwrap();
varmap
.set_one(
"mixer.out_proj.bias",
Tensor::zeros(hidden, DType::F32, &Device::Cpu).unwrap(),
)
.unwrap();
let hidden_input = Tensor::from_vec(
vec![
1.0f32, 0.5, -0.5, 0.2, -0.3, 0.8, 0.1, -0.6, 0.4, -0.2, 0.9, 0.3,
],
(1, 3, hidden),
&Device::Cpu,
)
.unwrap();
let mask = AttentionMask::none();
let rope = RotaryEmbedding::new(head_dim, 8, 10_000.0, DType::F32, &Device::Cpu).unwrap();
let actual = attention
.forward(&hidden_input, &mask, Some(&rope))
.unwrap();
let slice = |t: &Tensor, third: usize| t.narrow(0, third * hidden, hidden).unwrap();
let q_lin = Linear::new(slice(&weight, 0), Some(slice(&bias, 0)));
let k_lin = Linear::new(slice(&weight, 1), Some(slice(&bias, 1)));
let v_lin = Linear::new(slice(&weight, 2), Some(slice(&bias, 2)));
let split = |xs: Tensor| {
xs.reshape((1, 3, heads, head_dim))
.unwrap()
.transpose(1, 2)
.unwrap()
.contiguous()
.unwrap()
};
let q = split(q_lin.forward(&hidden_input).unwrap());
let k = split(k_lin.forward(&hidden_input).unwrap());
let v = split(v_lin.forward(&hidden_input).unwrap());
let (q, k) = rope.apply(&q, &k).unwrap();
let scale = 1.0 / (head_dim as f64).sqrt();
let expected = scaled_dot_product(&q, &k, &v, &mask, scale)
.unwrap()
.transpose(1, 2)
.unwrap()
.reshape((1, 3, hidden))
.unwrap();
let diff = (actual - expected)
.unwrap()
.abs()
.unwrap()
.flatten_all()
.unwrap()
.max(0)
.unwrap()
.to_scalar::<f32>()
.unwrap();
assert!(
diff < 1e-6,
"max abs diff between the module and the independently-narrowed shadow computation: {diff}"
);
}