use candle_core::Module;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::VarBuilder;
use candle_nn::linear;
use super::config::BertConfig;
use crate::error::Result;
use crate::layers::attention::scaled_dot_product;
use crate::layers::attention::scaled_dot_product_with_weights;
use crate::layers::layer_norm::LayerNorm;
use crate::layers::mask::AttentionMask;
pub struct BertAttention {
query: Linear,
key: Linear,
value: Linear,
output: Linear,
layer_norm: LayerNorm,
num_heads: usize,
head_dim: usize,
}
impl BertAttention {
pub fn load(config: &BertConfig, vb: VarBuilder) -> Result<Self> {
let hidden = config.hidden_size;
Ok(Self {
query: linear(hidden, hidden, vb.pp("self.query"))?,
key: linear(hidden, hidden, vb.pp("self.key"))?,
value: linear(hidden, hidden, vb.pp("self.value"))?,
output: linear(hidden, hidden, vb.pp("output.dense"))?,
layer_norm: LayerNorm::load(vb.pp("output.LayerNorm"), hidden, config.layer_norm_eps)?,
num_heads: config.num_attention_heads,
head_dim: config.head_dim(),
})
}
fn project(&self, hidden: &Tensor) -> Result<(Tensor, Tensor, Tensor, usize, usize)> {
let (batch, seq, _) = hidden.dims3()?;
let split = |xs: Tensor| -> Result<Tensor> {
Ok(
xs.reshape((batch, seq, self.num_heads, self.head_dim))?
.transpose(1, 2)?
.contiguous()?,
)
};
let q = split(self.query.forward(hidden)?)?;
let k = split(self.key.forward(hidden)?)?;
let v = split(self.value.forward(hidden)?)?;
Ok((q, k, v, batch, seq))
}
fn finish(&self, context: Tensor, hidden: &Tensor, batch: usize, seq: usize) -> Result<Tensor> {
let context = context
.transpose(1, 2)?
.reshape((batch, seq, self.num_heads * self.head_dim))?;
let projected = self.output.forward(&context)?;
Ok(self.layer_norm.forward(&(projected + hidden)?)?)
}
pub fn forward(&self, hidden: &Tensor, mask: &AttentionMask) -> Result<Tensor> {
let (q, k, v, batch, seq) = self.project(hidden)?;
let scale = 1.0 / (self.head_dim as f64).sqrt();
let context = scaled_dot_product(&q, &k, &v, mask, scale)?;
self.finish(context, hidden, batch, seq)
}
pub fn forward_with_weights(
&self,
hidden: &Tensor,
mask: &AttentionMask,
) -> Result<(Tensor, Tensor)> {
let (q, k, v, batch, seq) = self.project(hidden)?;
let scale = 1.0 / (self.head_dim as f64).sqrt();
let (context, weights) = scaled_dot_product_with_weights(&q, &k, &v, mask, scale)?;
Ok((self.finish(context, hidden, batch, seq)?, weights))
}
}