use candle_core::Module;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::VarBuilder;
use candle_nn::linear_no_bias;
use super::config::ModernBertConfig;
use crate::error::Result;
use crate::layers::attention::scaled_dot_product;
use crate::layers::attention::split_fused_qkv;
use crate::layers::mask::AttentionMask;
use crate::layers::rope::RotaryEmbedding;
pub struct ModernBertAttention {
q: Linear,
k: Linear,
v: Linear,
out: Linear,
heads: usize,
head_dim: usize,
scale: f64,
}
impl ModernBertAttention {
pub fn load(config: &ModernBertConfig, vb: VarBuilder) -> Result<Self> {
let hidden = config.hidden_size;
let fused = linear_no_bias(hidden, 3 * hidden, vb.pp("attn.Wqkv"))?;
let (q, k, v) = split_fused_qkv(fused.weight(), hidden)?;
Ok(Self {
q: Linear::new(q, None),
k: Linear::new(k, None),
v: Linear::new(v, None),
out: linear_no_bias(hidden, hidden, vb.pp("attn.Wo"))?,
heads: config.num_attention_heads,
head_dim: config.head_dim(),
scale: 1.0 / (config.head_dim() as f64).sqrt(),
})
}
pub fn forward(
&self,
hidden: &Tensor,
mask: &AttentionMask,
rope: &RotaryEmbedding,
) -> Result<Tensor> {
let (batch, seq, _) = hidden.dims3()?;
let split = |xs: Tensor| -> Result<Tensor> {
Ok(
xs.reshape((batch, seq, self.heads, self.head_dim))?
.transpose(1, 2)?
.contiguous()?,
)
};
let q = split(self.q.forward(hidden)?)?;
let k = split(self.k.forward(hidden)?)?;
let v = split(self.v.forward(hidden)?)?;
let (q, k) = rope.apply(&q, &k)?;
let context = scaled_dot_product(&q, &k, &v, mask, self.scale)?
.transpose(1, 2)?
.reshape((batch, seq, self.heads * self.head_dim))?;
Ok(self.out.forward(&context)?)
}
}