use burn::{
Tensor,
config::Config,
module::Module,
nn::{
LayerNorm,
LayerNormConfig,
attention::{
MultiHeadAttention,
MultiHeadAttentionConfig,
},
},
prelude::{
Backend,
Bool,
},
};
use super::WHISPER_DEFAULT_D_MODEL;
use crate::{
blocks::transformers::{
attention::{
layer_norm_cross_attn,
layer_norm_self_attn,
},
mlp::{
Mlp,
MlpConfig,
layer_norm_mlp,
},
},
burner::module::ModuleInit,
errors::BunsenResult,
};
pub trait ResidualDecoderAttentionBlockMeta {
fn d_model(&self) -> usize;
fn n_heads(&self) -> usize;
fn dropout(&self) -> f64;
}
#[derive(Config, Debug)]
pub struct ResidualDecoderAttentionBlockConfig {
pub d_model: usize,
#[config(defaul_value = "WHISPER_DEFAULT_D_MODEL")]
pub d_head: usize,
#[config(default = "0.0")]
pub dropout: f64,
}
impl ResidualDecoderAttentionBlockMeta for ResidualDecoderAttentionBlockConfig {
fn d_model(&self) -> usize {
self.d_model
}
fn n_heads(&self) -> usize {
self.d_model / self.d_head
}
fn dropout(&self) -> f64 {
self.dropout
}
}
impl<B: Backend> ModuleInit<B, ResidualDecoderAttentionBlock<B>>
for ResidualDecoderAttentionBlockConfig
{
fn try_init(
&self,
device: &B::Device,
) -> BunsenResult<ResidualDecoderAttentionBlock<B>> {
let mha_cfg =
MultiHeadAttentionConfig::new(self.d_model, self.n_heads()).with_dropout(self.dropout);
let ln_cfg = LayerNormConfig::new(self.d_model);
let mut attn = mha_cfg.init(device);
let mut cross_attn = mha_cfg.init(device);
attn.key.bias = None;
cross_attn.key.bias = None;
Ok(ResidualDecoderAttentionBlock {
attn_ln: ln_cfg.init(device),
attn,
cross_attn_ln: ln_cfg.init(device),
cross_attn,
mlp_ln: ln_cfg.init(device),
mlp: MlpConfig::new(self.d_model).try_init(device)?,
})
}
}
#[derive(Module, Debug)]
pub struct ResidualDecoderAttentionBlock<B: Backend> {
pub attn_ln: LayerNorm<B>,
pub attn: MultiHeadAttention<B>,
pub cross_attn_ln: LayerNorm<B>,
pub cross_attn: MultiHeadAttention<B>,
pub mlp_ln: LayerNorm<B>,
pub mlp: Mlp<B>,
}
impl<B: Backend> ResidualDecoderAttentionBlockMeta for ResidualDecoderAttentionBlock<B> {
fn d_model(&self) -> usize {
self.attn.d_model
}
fn n_heads(&self) -> usize {
self.attn.n_heads
}
fn dropout(&self) -> f64 {
self.attn.dropout.prob
}
}
#[derive(Debug, Clone)]
pub struct DecodeRecord<B: Backend> {
pub output: Tensor<B, 3>,
pub ca_weights: Tensor<B, 4>,
}
impl<B: Backend> DecodeRecord<B> {
pub fn batch_size(&self) -> usize {
self.output.shape()[0]
}
pub fn d_model(&self) -> usize {
self.output.shape()[2]
}
pub fn seq_len(&self) -> usize {
self.output.shape()[1]
}
}
impl<B: Backend> ResidualDecoderAttentionBlock<B> {
pub fn forward(
&self,
x: Tensor<B, 3>,
xa: Tensor<B, 3>,
mask: Option<Tensor<B, 3, Bool>>,
) -> DecodeRecord<B> {
let self_attn = layer_norm_self_attn(&self.attn_ln, &self.attn, x.clone(), mask);
let x = x + self_attn.context;
let cross_attn =
layer_norm_cross_attn(&self.cross_attn_ln, &self.cross_attn, x.clone(), xa);
let x = x + cross_attn.context;
let mlp = layer_norm_mlp(&self.mlp_ln, &self.mlp, x.clone());
let x = x + mlp;
DecodeRecord {
output: x,
ca_weights: cross_attn.weights,
}
}
}
#[cfg(test)]
mod tests {
use burn::{
prelude::Shape,
tensor::Distribution,
};
use super::*;
use crate::contracts::assert_shape_contract;
#[test]
#[serial_test::serial]
fn test_residual_decoder_forward() {
type B = crate::support::testing::PerformanceBackend;
let device = Default::default();
let d_model = 128;
let cfg = ResidualDecoderAttentionBlockConfig::new(d_model);
let n_heads = cfg.n_heads();
assert_eq!(cfg.d_model(), d_model);
assert_eq!(cfg.n_heads(), n_heads);
let block: ResidualDecoderAttentionBlock<B> = cfg.init(&device);
assert_eq!(block.d_model(), d_model);
assert_eq!(block.n_heads(), n_heads);
let batch = 2;
let seq_len = 10;
let shape: Shape = [batch, seq_len, d_model].into();
let x: Tensor<B, 3> = Tensor::random(shape.clone(), Distribution::Default, &device);
let xa: Tensor<B, 3> = Tensor::random(shape.clone(), Distribution::Default, &device);
let mask: Tensor<B, 3> =
Tensor::random([1, seq_len, seq_len], Distribution::Bernoulli(0.5), &device);
let mask = mask.bool();
let result = block.forward(x.clone(), xa.clone(), mask.clone().into());
assert_eq!(result.batch_size(), batch);
assert_eq!(result.d_model(), d_model);
assert_eq!(result.seq_len(), seq_len);
let expected = {
let self_attn =
layer_norm_self_attn(&block.attn_ln, &block.attn, x.clone(), mask.clone().into());
let x = x + self_attn.context;
let cross_attn = layer_norm_cross_attn(
&block.cross_attn_ln,
&block.cross_attn,
x.clone(),
xa.clone(),
);
let x = x + cross_attn.context;
let mlp = layer_norm_mlp(&block.mlp_ln, &block.mlp, x.clone());
let x = x + mlp;
DecodeRecord::<B> {
output: x,
ca_weights: cross_attn.weights,
}
};
result
.output
.clone()
.into_data()
.assert_approx_eq::<f64>(&expected.output.clone().into_data(), Default::default());
result
.ca_weights
.clone()
.into_data()
.assert_approx_eq::<f64>(&expected.ca_weights.clone().into_data(), Default::default());
assert_shape_contract!(
["batch", "seq_len", "d_model"],
&result.output,
&[("batch", batch), ("seq_len", seq_len), ("d_model", d_model),],
);
assert_shape_contract!(
["batch", "n_heads", "seq_len", "seq_len"],
&result.ca_weights,
&[("batch", batch), ("n_heads", n_heads), ("seq_len", seq_len)],
);
}
}