use crate::gguf::Gguf;
use nalgebra::{
vector, DMatrix, DVector, DVectorViewMut, Dyn, OMatrix, OVector, Rotation2, Storage,
StorageMut, Vector,
};
use std::ffi::c_int;
type Dim = Dyn;
type HiddenDim = Dyn;
type NumHeads = Dyn;
type SeqLen = Dyn;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RawConfig {
dim: c_int,
hidden_dim: c_int,
n_layers: c_int,
n_q_heads: c_int,
n_kv_heads: c_int,
vocab_size: c_int,
seq_len: c_int,
}
#[derive(Copy, Clone, Debug)]
pub struct Llama2Config {
pub dim: usize,
pub hidden_dim: usize,
pub n_layers: usize,
pub n_q_heads: usize,
pub n_kv_heads: usize,
pub vocab_size: usize,
pub seq_len: usize,
pub shared_weights: bool,
}
impl Llama2Config {
pub fn read(bytes: &[u8]) -> Self {
let elts: &[RawConfig] = bytemuck::cast_slice(&bytes[..std::mem::size_of::<RawConfig>()]);
elts[0].into()
}
pub fn from_gguf(gguf: &Gguf) -> Self {
Self {
dim: gguf.metadata["llama.embedding_length"].unwrap_u32() as usize,
hidden_dim: gguf.metadata["llama.feed_forward_length"].unwrap_u32() as usize,
n_layers: gguf.metadata["llama.block_count"].unwrap_u32() as usize,
n_q_heads: gguf.metadata["llama.attention.head_count"].unwrap_u32() as usize,
n_kv_heads: gguf.metadata["llama.attention.head_count_kv"].unwrap_u32() as usize,
vocab_size: gguf.metadata["tokenizer.ggml.tokens"].unwrap_array_len(),
seq_len: gguf.metadata["llama.context_length"].unwrap_u32() as usize,
shared_weights: true, }
}
}
impl From<RawConfig> for Llama2Config {
fn from(c: RawConfig) -> Self {
Self {
dim: c.dim as usize,
hidden_dim: c.hidden_dim as usize,
n_layers: c.n_layers as usize,
n_q_heads: c.n_q_heads as usize,
n_kv_heads: c.n_kv_heads as usize,
vocab_size: c.vocab_size.unsigned_abs() as usize,
seq_len: c.seq_len as usize,
shared_weights: c.vocab_size > 0,
}
}
}
pub struct TransformerLayerWeights {
pub attn_k: DMatrix<f32>,
pub attn_norm: DVector<f32>,
pub attn_q: DMatrix<f32>,
pub attn_v: DMatrix<f32>,
pub ffn_down: DMatrix<f32>,
pub ffn_gate: DMatrix<f32>,
pub ffn_norm: DVector<f32>,
pub ffn_up: DMatrix<f32>,
pub attn_output: DMatrix<f32>,
}
pub struct TransformerWeights {
pub layers: Vec<TransformerLayerWeights>,
pub token_embd: DMatrix<f32>,
pub output: DMatrix<f32>,
pub output_norm: DVector<f32>,
}
impl TransformerWeights {
pub fn from_gguf(config: &Llama2Config, gguf: &Gguf) -> Self {
let head_size = config.dim / config.n_q_heads;
let num_kv_heads_times_head_size = config.n_kv_heads * head_size;
let mut layers = vec![];
for i_layer in 0..config.n_layers {
let attn_q = format!("blk.{}.attn_q.weight", i_layer);
let attn_k = format!("blk.{}.attn_k.weight", i_layer);
let attn_v = format!("blk.{}.attn_v.weight", i_layer);
let attn_output = format!("blk.{}.attn_output.weight", i_layer);
let ffn_down = format!("blk.{}.ffn_down.weight", i_layer);
let ffn_gate = format!("blk.{}.ffn_gate.weight", i_layer);
let ffn_up = format!("blk.{}.ffn_up.weight", i_layer);
let ffn_norm = format!("blk.{}.ffn_norm.weight", i_layer);
let attn_norm = format!("blk.{}.attn_norm.weight", i_layer);
let attn_q = &gguf.tensors[&attn_q].data().dequantize().unwrap();
let attn_k = &gguf.tensors[&attn_k].data().dequantize().unwrap();
let attn_v = &gguf.tensors[&attn_v].data().dequantize().unwrap();
let attn_output = &gguf.tensors[&attn_output].data().dequantize().unwrap();
let ffn_down = &gguf.tensors[&ffn_down].data().dequantize().unwrap();
let ffn_gate = &gguf.tensors[&ffn_gate].data().dequantize().unwrap();
let ffn_up = &gguf.tensors[&ffn_up].data().dequantize().unwrap();
let ffn_norm = gguf.tensors[&ffn_norm].data().as_f32().unwrap();
let attn_norm = gguf.tensors[&attn_norm].data().as_f32().unwrap();
let ffn_norm = DVector::from_row_slice(ffn_norm);
let attn_norm = DVector::from_row_slice(attn_norm);
let attn_q = DMatrix::from_row_slice(config.dim, config.dim, attn_q);
let attn_k = DMatrix::from_row_slice(num_kv_heads_times_head_size, config.dim, attn_k);
let attn_v = DMatrix::from_row_slice(num_kv_heads_times_head_size, config.dim, attn_v);
let attn_output = DMatrix::from_row_slice(config.dim, config.dim, attn_output);
let ffn_down = DMatrix::from_row_slice(config.dim, config.hidden_dim, ffn_down);
let ffn_gate = DMatrix::from_row_slice(config.hidden_dim, config.dim, ffn_gate);
let ffn_up = DMatrix::from_row_slice(config.hidden_dim, config.dim, ffn_up);
layers.push(TransformerLayerWeights {
attn_q,
attn_k,
attn_v,
attn_output,
ffn_down,
ffn_gate,
ffn_up,
ffn_norm,
attn_norm,
});
}
let token_embd = "token_embd.weight";
let output = "output.weight";
let output_norm = "output_norm.weight";
let token_embd = &gguf.tensors[token_embd].data().dequantize().unwrap();
let output = gguf
.tensors
.get(output)
.map(|v| v.data().dequantize().unwrap());
let output_norm = gguf.tensors[output_norm].data().as_f32().unwrap();
let token_embd = DMatrix::from_column_slice(config.dim, config.vocab_size, token_embd);
let output = output
.map(|data| DMatrix::from_row_slice(config.vocab_size, config.dim, &data))
.unwrap_or_else(|| token_embd.transpose());
let output_norm = DVector::from_row_slice(output_norm);
Self {
layers,
token_embd,
output,
output_norm,
}
}
}
struct RunState {
x: OVector<f32, Dim>,
xb: OVector<f32, Dim>,
xb2: OVector<f32, Dim>,
hb: OVector<f32, HiddenDim>,
hb2: OVector<f32, HiddenDim>,
q: OVector<f32, Dim>,
att: OMatrix<f32, SeqLen, NumHeads>,
logits: OVector<f32, SeqLen>,
key_cache: Vec<OMatrix<f32, Dim, SeqLen>>,
value_cache: Vec<OMatrix<f32, Dim, SeqLen>>,
}
pub struct Transformer {
config: Llama2Config,
weights: TransformerWeights,
state: RunState,
}
impl Transformer {
pub fn new(config: Llama2Config, weights: TransformerWeights) -> Self {
Self {
state: RunState::new(&config),
config,
weights,
}
}
pub fn logits_mut(&mut self) -> &mut OVector<f32, SeqLen> {
&mut self.state.logits
}
}
impl RunState {
pub fn new(config: &Llama2Config) -> Self {
let kv_dim = (config.dim * config.n_kv_heads) / config.n_q_heads;
Self {
x: DVector::zeros(config.dim),
xb: DVector::zeros(config.dim),
xb2: DVector::zeros(config.dim),
hb: DVector::zeros(config.hidden_dim),
hb2: DVector::zeros(config.hidden_dim),
q: DVector::zeros(config.dim),
key_cache: (0..config.n_layers)
.map(|_| DMatrix::zeros(kv_dim, config.seq_len))
.collect(),
value_cache: (0..config.n_layers)
.map(|_| DMatrix::zeros(kv_dim, config.seq_len))
.collect(),
att: DMatrix::zeros(config.seq_len, config.n_q_heads),
logits: DVector::zeros(config.vocab_size),
}
}
}
fn rms_norm<SW: Storage<f32, Dyn>>(
out: &mut DVector<f32>,
a: &DVector<f32>,
w: &Vector<f32, Dyn, SW>,
) {
const NUDGE_FACTOR: f32 = 1.0e-5;
let rms = 1.0 / (a.norm_squared() / (a.nrows() as f32) + NUDGE_FACTOR).sqrt();
out.zip_zip_apply(a, w, |o, a, w| *o = (a * rms) * w);
}
pub fn softmax<S: StorageMut<f32, Dyn>>(vals: &mut Vector<f32, Dyn, S>) {
let max_val = vals.max();
let mut sum = 0.0;
vals.apply(|x| {
*x = (*x - max_val).exp();
sum += *x;
});
*vals /= sum;
}
fn matmul<SOut: StorageMut<f32, Dyn>>(
out: &mut Vector<f32, Dyn, SOut>,
x: &DVector<f32>,
w: &DMatrix<f32>,
) {
out.gemv(1.0, w, x, 0.0);
}
impl Transformer {
pub fn forward(&mut self, token: usize, pos: usize) {
let config = &self.config;
let w = &self.weights;
let s = &mut self.state;
let dim = config.dim;
let kv_dim = (config.dim * config.n_kv_heads) / config.n_q_heads;
let head_size = dim / config.n_q_heads;
s.x.copy_from(&w.token_embd.column(token));
for l in 0..config.n_layers {
let wl = &w.layers[l];
rms_norm(&mut s.xb, &s.x, &wl.attn_norm);
let mut k_cache = s.key_cache[l].column_mut(pos);
let mut v_cache = s.value_cache[l].column_mut(pos);
matmul(&mut s.q, &s.xb, &wl.attn_q);
matmul(&mut k_cache, &s.xb, &wl.attn_k);
matmul(&mut v_cache, &s.xb, &wl.attn_v);
Self::rotary_positional_encoding(&mut s.q, &mut k_cache, head_size, dim, kv_dim, pos);
Self::attention(config, s, w, pos, l);
s.x += &s.xb2;
rms_norm(&mut s.xb, &s.x, &wl.ffn_norm);
Self::ffn_silu(s, wl);
s.x += &s.xb2;
}
rms_norm(&mut s.xb, &s.x, &w.output_norm);
matmul(&mut s.logits, &s.xb, &w.output);
}
pub fn rotary_positional_encoding(
q: &mut DVector<f32>,
k: &mut DVectorViewMut<f32>,
head_size: usize,
dim: usize,
kv_dim: usize,
pos: usize,
) {
for i in (0..dim).step_by(2) {
let head_dim = (i % head_size) as f32;
let theta = 10000.0_f32.powf(-head_dim / head_size as f32);
let m_theta = pos as f32 * theta;
let rot = Rotation2::new(m_theta);
let qi = vector![q[i], q[i + 1]];
let mut out_q = q.fixed_rows_mut::<2>(i);
out_q.copy_from(&(rot * qi));
if i < kv_dim {
let ki = vector![k[i], k[i + 1]];
let mut out_k = k.fixed_rows_mut::<2>(i);
out_k.copy_from(&(rot * ki));
}
}
}
fn attention(
config: &Llama2Config,
s: &mut RunState,
w: &TransformerWeights,
pos: usize,
l: usize,
) {
let head_size = config.dim / config.n_q_heads;
let kv_mul = config.n_q_heads / config.n_kv_heads;
for h in 0..config.n_q_heads {
let q = s.q.rows(h * head_size, head_size);
let mut att = s.att.column_mut(h);
for t in 0..=pos {
let k = s.key_cache[l].column(t);
let k_head = k.rows((h / kv_mul) * head_size, head_size);
let mut score = q.dot(&k_head);
score /= (head_size as f32).sqrt();
att[t] = score;
}
softmax(&mut att.rows_mut(0, pos + 1));
let mut xb = s.xb.rows_mut(h * head_size, head_size);
xb.fill(0.0);
for t in 0..=pos {
let v = s.value_cache[l].column(t);
let v_head = v.rows((h / kv_mul) * head_size, head_size);
xb.axpy(att[t], &v_head, 1.0);
}
}
matmul(&mut s.xb2, &s.xb, &w.layers[l].attn_output);
}
fn ffn_silu(s: &mut RunState, wl: &TransformerLayerWeights) {
s.hb.gemv(1.0, &wl.ffn_gate, &s.xb, 0.0);
s.hb2.gemv(1.0, &wl.ffn_up, &s.xb, 0.0);
fn swish(x: f32, beta: f32) -> f32 {
x / (1.0 + (-beta * x).exp())
}
s.hb.zip_apply(&s.hb2, |h, h2| *h = h2 * swish(*h, 1.0));
matmul(&mut s.xb2, &s.hb, &wl.ffn_down);
}
}