use burn::{
config::Config,
module::Module,
nn::{
Embedding, EmbeddingConfig, Linear, LinearConfig, RmsNorm, RmsNormConfig, RotaryEncoding,
SwiGlu, SwiGluConfig,
},
tensor::{Bool, Device, Int, Tensor, activation::softmax, backend::Backend},
};
use crate::cache::AutoregressiveCache;
#[derive(Config, Debug)]
pub struct TransformerConfig {
pub vocab_size: usize,
pub n_layers: usize,
pub d_model: usize,
pub hidden_size: usize,
pub n_heads: usize,
pub n_kv_heads: usize,
#[config(default = "512")]
pub max_seq_len: usize,
#[config(default = "1e-5")]
pub norm_eps: f64,
}
impl TransformerConfig {
pub fn init<B: Backend>(&self, device: &Device<B>) -> Transformer<B> {
let tok_embeddings = EmbeddingConfig::new(self.vocab_size, self.d_model).init(device);
let layers = (0..self.n_layers)
.map(|_| {
TransformerBlockConfig::new(
self.n_layers,
self.d_model,
self.hidden_size,
self.n_heads,
self.n_kv_heads,
self.norm_eps,
)
.init(device)
})
.collect::<Vec<_>>();
let norm = RmsNormConfig::new(self.d_model)
.with_epsilon(self.norm_eps)
.init(device);
let output = LinearConfig::new(self.d_model, self.vocab_size)
.with_bias(false)
.init(device);
Transformer {
tok_embeddings,
layers,
norm,
output,
}
}
}
#[derive(Module, Debug)]
pub struct Transformer<B: Backend> {
tok_embeddings: Embedding<B>,
layers: Vec<TransformerBlock<B>>,
norm: RmsNorm<B>,
output: Linear<B>,
}
impl<B: Backend> Transformer<B> {
pub fn forward(
&self,
input: Tensor<B, 2, Int>,
cache: &mut [KeyValueCache<B>],
rope: &RotaryEncoding<B>,
) -> Tensor<B, 3> {
let mut h = self.tok_embeddings.forward(input);
for (layer, c) in self.layers.iter().zip(cache.iter_mut()) {
h = layer.forward(h, c, rope);
}
let h = self.norm.forward(h);
self.output.forward(h)
}
}
#[derive(Config, Debug)]
pub struct TransformerBlockConfig {
pub n_layers: usize,
pub d_model: usize,
pub hidden_size: usize,
pub n_heads: usize,
pub n_kv_heads: usize,
pub norm_eps: f64,
}
impl TransformerBlockConfig {
pub fn init<B: Backend>(&self, device: &Device<B>) -> TransformerBlock<B> {
let attention =
MultiHeadAttentionConfig::new(self.d_model, self.n_heads, self.n_kv_heads).init(device);
let feed_forward = FeedForwardConfig::new(self.d_model, self.hidden_size).init(device);
let attention_norm = RmsNormConfig::new(self.d_model)
.with_epsilon(self.norm_eps)
.init(device);
let ffn_norm = RmsNormConfig::new(self.d_model)
.with_epsilon(self.norm_eps)
.init(device);
TransformerBlock {
attention,
feed_forward,
attention_norm,
ffn_norm,
}
}
}
#[derive(Module, Debug)]
pub struct TransformerBlock<B: Backend> {
attention: MultiHeadAttention<B>,
feed_forward: FeedForward<B>,
attention_norm: RmsNorm<B>,
ffn_norm: RmsNorm<B>,
}
impl<B: Backend> TransformerBlock<B> {
pub fn forward(
&self,
input: Tensor<B, 3>,
cache: &mut KeyValueCache<B>,
rope: &RotaryEncoding<B>,
) -> Tensor<B, 3> {
let h = input.clone()
+ self
.attention
.forward(self.attention_norm.forward(input), cache, rope);
h.clone() + self.feed_forward.forward(self.ffn_norm.forward(h))
}
}
#[derive(Config, Debug)]
pub struct FeedForwardConfig {
pub d_model: usize,
pub hidden_size: usize,
}
impl FeedForwardConfig {
pub fn init<B: Backend>(&self, device: &Device<B>) -> FeedForward<B> {
let swiglu = SwiGluConfig::new(self.d_model, self.hidden_size)
.with_bias(false)
.init(device);
let w2 = LinearConfig::new(self.hidden_size, self.d_model)
.with_bias(false)
.init(device);
FeedForward { swiglu, w2 }
}
}
#[derive(Module, Debug)]
pub struct FeedForward<B: Backend> {
swiglu: SwiGlu<B>,
w2: Linear<B>,
}
impl<B: Backend> FeedForward<B> {
pub fn forward(&self, input: Tensor<B, 3>) -> Tensor<B, 3> {
self.w2.forward(self.swiglu.forward(input))
}
}
pub struct KeyValueCache<B: Backend> {
key: AutoregressiveCache<B>,
value: AutoregressiveCache<B>,
}
impl<B: Backend> KeyValueCache<B> {
pub fn new(
max_batch_size: usize,
num_heads: usize,
max_seq_len: usize,
d_model: usize,
device: &Device<B>,
) -> Self {
Self {
key: AutoregressiveCache::new(max_batch_size, num_heads, max_seq_len, d_model, device),
value: AutoregressiveCache::new(
max_batch_size,
num_heads,
max_seq_len,
d_model,
device,
),
}
}
pub fn forward(
&mut self,
key: Tensor<B, 4>,
value: Tensor<B, 4>,
) -> (Tensor<B, 4>, Tensor<B, 4>) {
let k = self.key.forward(key);
let v = self.value.forward(value);
(k, v)
}
pub fn len(&self) -> usize {
self.key.len()
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.key.reset();
self.value.reset();
}
}
#[derive(Config, Debug)]
pub struct MultiHeadAttentionConfig {
pub d_model: usize,
pub n_heads: usize,
pub n_kv_heads: usize,
}
impl MultiHeadAttentionConfig {
pub fn init<B: Backend>(&self, device: &Device<B>) -> MultiHeadAttention<B> {
let head_dim = self.d_model / self.n_heads;
let wq = LinearConfig::new(self.d_model, self.n_heads * head_dim)
.with_bias(false)
.init(device);
let wk = LinearConfig::new(self.d_model, self.n_kv_heads * head_dim)
.with_bias(false)
.init(device);
let wv = LinearConfig::new(self.d_model, self.n_kv_heads * head_dim)
.with_bias(false)
.init(device);
let wo = LinearConfig::new(self.n_heads * head_dim, self.d_model)
.with_bias(false)
.init(device);
MultiHeadAttention {
wq,
wk,
wv,
wo,
n_heads: self.n_heads,
n_kv_heads: self.n_kv_heads,
head_dim,
}
}
}
#[derive(Module, Debug)]
pub struct MultiHeadAttention<B: Backend> {
wq: Linear<B>,
wk: Linear<B>,
wv: Linear<B>,
wo: Linear<B>,
n_heads: usize,
n_kv_heads: usize,
head_dim: usize,
}
impl<B: Backend> MultiHeadAttention<B> {
pub fn forward(
&self,
input: Tensor<B, 3>,
cache: &mut KeyValueCache<B>,
rope: &RotaryEncoding<B>,
) -> Tensor<B, 3> {
let device = input.device();
let [batch_size, seq_len, hidden_size] = input.dims();
let q = self.wq.forward(input.clone());
let k = self.wk.forward(input.clone());
let v = self.wv.forward(input);
let q = q
.reshape([batch_size, seq_len, self.n_heads, self.head_dim])
.swap_dims(1, 2);
let k = k
.reshape([batch_size, seq_len, self.n_kv_heads, self.head_dim])
.swap_dims(1, 2);
let v = v
.reshape([batch_size, seq_len, self.n_kv_heads, self.head_dim])
.swap_dims(1, 2);
let cache_seq_len = cache.len();
let q = rope.apply(q, cache_seq_len);
let k = rope.apply(k, cache_seq_len);
let (k, v) = cache.forward(k, v);
let k = self.repeat_kv(k);
let v = self.repeat_kv(v);
let mut scores = q
.matmul(k.swap_dims(2, 3))
.div_scalar((self.head_dim as f32).sqrt());
if seq_len > 1 {
let cache_seq_len = cache.len();
let mask = Tensor::<B, 2, Bool>::tril_mask(
[seq_len, cache_seq_len],
(cache_seq_len - seq_len) as i64, &device,
);
scores = scores.mask_fill(mask.unsqueeze::<4>(), f32::NEG_INFINITY);
}
let scores = softmax(scores, 3);
let output = scores.matmul(v);
let output = output
.swap_dims(1, 2)
.reshape([batch_size, seq_len, hidden_size]);
self.wo.forward(output)
}
fn repeat_kv(&self, x: Tensor<B, 4>) -> Tensor<B, 4> {
let n_rep = self.n_heads / self.n_kv_heads;
if n_rep == 1 {
x
} else {
let [batch_size, num_kv_heads, seq_len, head_dim] = x.dims();
x.unsqueeze_dim::<5>(2)
.expand([batch_size, num_kv_heads, n_rep, seq_len, head_dim])
.reshape([batch_size, num_kv_heads * n_rep, seq_len, head_dim])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
use burn::tensor::TensorData;
#[test]
fn test_rms_norm() {
let device = Default::default();
let rms = RmsNormConfig::new(4).with_epsilon(1e-5).init(&device);
let input = TestTensor::<3>::from([[
[0.0025997162, 0.0030002594, -0.006000519, 0.006000519],
[0.0010004044, 0.00080013275, 0.0015001297, -0.01600647],
]]);
let output = rms.forward(input);
let expected = TensorData::from([[
[0.45996094, 0.5307617, -1.0615234, 1.0615234],
[0.11553955, 0.09240723, 0.17321777, -1.8486328],
]]);
output.into_data().assert_approx_eq(
&expected,
burn::tensor::Tolerance::<f32>::rel_abs(1e-3, 1e-3),
);
}
}