use std::collections::HashMap;
use std::marker::PhantomData;
use topos::checkpoint::named_restore;
use topos::{
Element, Module, Parameters, Path, RmsNorm, Segment, Sequential, Symbol, Tape, Tensor, Value,
Visitor, concat, named_parameters,
};
use crate::family::Family;
use crate::weights::Weights;
pub const CONTEXT_LEN: usize = 256;
pub const VOCABULARY_LEN: usize = 32000;
const ROPE_BASE: f64 = 10000.0;
struct Projection<E> {
weights: Symbol,
_marker: PhantomData<E>,
}
impl<E: Element + From<f32>> Projection<E> {
fn new(tape: &Tape<E>, inputs: usize, outputs: usize) -> Self {
Self {
weights: tape
.parameter(Tensor::filled([inputs, outputs], E::from(0.0)))
.symbol(),
_marker: PhantomData,
}
}
}
impl<E: Element> Module<E> for Projection<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let tape = input.tape();
input.matmul(tape.resolve(self.weights))
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.parameter("weights", self.weights);
}
}
#[derive(Clone, Copy)]
struct Rope {
cos: Symbol,
sin: Symbol,
head_dim: usize,
}
impl Rope {
fn new<E: Element + From<f32>>(tape: &Tape<E>, head_dim: usize) -> Self {
let half = head_dim / 2;
let mut cosines = Vec::with_capacity(CONTEXT_LEN * head_dim);
let mut sines = Vec::with_capacity(CONTEXT_LEN * head_dim);
for position in 0..CONTEXT_LEN {
for column in 0..head_dim {
let exponent = -2.0 * (column % half) as f64 / head_dim as f64;
let angle = position as f64 * libm::pow(ROPE_BASE, exponent);
cosines.push(E::from(libm::cos(angle) as f32));
sines.push(E::from(libm::sin(angle) as f32));
}
}
Self {
cos: tape
.leaf(Tensor::new([CONTEXT_LEN, head_dim], cosines))
.symbol(),
sin: tape
.leaf(Tensor::new([CONTEXT_LEN, head_dim], sines))
.symbol(),
head_dim,
}
}
fn rotate<'tape, E: Element>(
&self,
tape: &'tape Tape<E>,
value: Value<'tape, E>,
) -> Value<'tape, E> {
let half = self.head_dim / 2;
let cos = tape.resolve(self.cos);
let sin = tape.resolve(self.sin);
let flipped = concat(&[-value.narrow(1, half, half), value.narrow(1, 0, half)], 1);
value * cos + flipped * sin
}
}
struct Attention<E> {
family: Family,
query: Projection<E>,
key: Projection<E>,
value: Projection<E>,
output: Projection<E>,
rope: Rope,
mask: Symbol,
scale: Symbol,
}
impl<E: Element + From<f32>> Attention<E> {
fn new(tape: &Tape<E>, family: Family, rope: Rope, mask: Symbol, scale: Symbol) -> Self {
let key_value_dim = family.key_value_head_count * family.head_dim();
Self {
family,
query: Projection::new(tape, family.embed_dim, family.embed_dim),
key: Projection::new(tape, family.embed_dim, key_value_dim),
value: Projection::new(tape, family.embed_dim, key_value_dim),
output: Projection::new(tape, family.embed_dim, family.embed_dim),
rope,
mask,
scale,
}
}
}
impl<E: Element> Module<E> for Attention<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let tape = input.tape();
let head_dim = self.family.head_dim();
let mask = tape.resolve(self.mask);
let scale = tape.resolve(self.scale);
let queries = self.query.express(input);
let keys = self.key.express(input);
let values = self.value.express(input);
let keyed: Vec<Value<'tape, E>> = (0..self.family.key_value_head_count)
.map(|group| {
self.rope
.rotate(tape, keys.narrow(1, group * head_dim, head_dim))
.transpose()
})
.collect();
let heads: Vec<Value<'tape, E>> = (0..self.family.head_count)
.map(|head| {
let group = head / self.family.group_size();
let query = self
.rope
.rotate(tape, queries.narrow(1, head * head_dim, head_dim));
let scores = query.matmul(keyed[group]);
let weights = (scores * scale.broadcast_like(scores) + mask).softmax(1);
weights.matmul(values.narrow(1, group * head_dim, head_dim))
})
.collect();
self.output.express(concat(&heads, 1))
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.enter(Segment::Name("q_proj"));
self.query.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("k_proj"));
self.key.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("v_proj"));
self.value.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("o_proj"));
self.output.visit(visitor);
visitor.leave();
}
}
struct FeedForward<E> {
gate: Projection<E>,
up: Projection<E>,
down: Projection<E>,
one: Symbol,
}
impl<E: Element + From<f32>> FeedForward<E> {
fn new(tape: &Tape<E>, family: Family, one: Symbol) -> Self {
Self {
gate: Projection::new(tape, family.embed_dim, family.hidden_dim),
up: Projection::new(tape, family.embed_dim, family.hidden_dim),
down: Projection::new(tape, family.hidden_dim, family.embed_dim),
one,
}
}
}
impl<E: Element> Module<E> for FeedForward<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let tape = input.tape();
let one = tape.resolve(self.one);
let gated = self.gate.express(input);
let activated = gated / ((-gated).exp() + one.broadcast_like(gated));
self.down.express(activated * self.up.express(input))
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.enter(Segment::Name("gate_proj"));
self.gate.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("up_proj"));
self.up.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("down_proj"));
self.down.visit(visitor);
visitor.leave();
}
}
struct Block<E> {
attention_norm: RmsNorm<E>,
attention: Attention<E>,
hidden_norm: RmsNorm<E>,
feed_forward: FeedForward<E>,
}
impl<E: Element + From<f32>> Block<E> {
fn new(
tape: &Tape<E>,
family: Family,
rope: Rope,
mask: Symbol,
scale: Symbol,
one: Symbol,
) -> Self {
Self {
attention_norm: rms_norm(tape, family.embed_dim),
attention: Attention::new(tape, family, rope, mask, scale),
hidden_norm: rms_norm(tape, family.embed_dim),
feed_forward: FeedForward::new(tape, family, one),
}
}
}
impl<E: Element> Module<E> for Block<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let attended = self.attention.express(self.attention_norm.express(input));
let stream = input + attended;
let lifted = self.feed_forward.express(self.hidden_norm.express(stream));
stream + lifted
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.enter(Segment::Name("input_layernorm"));
self.attention_norm.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("self_attn"));
self.attention.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("post_attention_layernorm"));
self.hidden_norm.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("mlp"));
self.feed_forward.visit(visitor);
visitor.leave();
}
}
fn rms_norm<E: Element + From<f32>>(tape: &Tape<E>, embed_dim: usize) -> RmsNorm<E> {
RmsNorm::new(
tape,
Tensor::filled([embed_dim], E::from(1.0)),
Tensor::filled([], E::from(1e-5)),
)
}
pub struct Llama<E> {
embeddings: Symbol,
blocks: Sequential<E>,
final_norm: RmsNorm<E>,
head: Projection<E>,
}
impl<E: Element + From<f32> + 'static> Llama<E> {
pub fn new(tape: &Tape<E>, family: Family) -> Self {
let embeddings = tape
.parameter(Tensor::filled(
[VOCABULARY_LEN, family.embed_dim],
E::from(0.0),
))
.symbol();
let rope = Rope::new(tape, family.head_dim());
let mask_elements: Vec<E> = (0..CONTEXT_LEN * CONTEXT_LEN)
.map(|at| {
if at % CONTEXT_LEN <= at / CONTEXT_LEN {
E::from(0.0)
} else {
E::from(f32::NEG_INFINITY)
}
})
.collect();
let mask = tape
.leaf(Tensor::new([CONTEXT_LEN, CONTEXT_LEN], mask_elements))
.symbol();
let scale = tape
.leaf(Tensor::filled(
[],
E::from(1.0 / (family.head_dim() as f32).sqrt()),
))
.symbol();
let one = tape.leaf(Tensor::filled([], E::from(1.0))).symbol();
let mut blocks = Sequential::new();
for _ in 0..family.layer_count {
blocks = blocks.then(Block::new(tape, family, rope, mask, scale, one));
}
Self {
embeddings,
blocks,
final_norm: rms_norm(tape, family.embed_dim),
head: Projection::new(tape, family.embed_dim, VOCABULARY_LEN),
}
}
pub fn embeddings(&self) -> Symbol {
self.embeddings
}
pub fn predict<'tape>(&self, last: Value<'tape, E>) -> Value<'tape, E> {
self.head.express(last)
}
}
impl<E: Element> Module<E> for Llama<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
self.final_norm.express(self.blocks.express(input))
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.enter(Segment::Name("model"));
visitor.enter(Segment::Name("embed_tokens"));
visitor.parameter("weights", self.embeddings);
visitor.leave();
visitor.enter(Segment::Name("layers"));
self.blocks.visit(visitor);
visitor.leave();
visitor.enter(Segment::Name("norm"));
self.final_norm.visit(visitor);
visitor.leave();
visitor.leave();
visitor.enter(Segment::Name("lm_head"));
self.head.visit(visitor);
visitor.leave();
}
}
fn foreign_name(path: &Path) -> String {
let segments = path.segments();
let mut name = String::new();
for (position, segment) in segments.iter().enumerate() {
if position > 0 {
name.push('.');
}
if position + 1 < segments.len() {
name.push_str(&segment.to_string());
continue;
}
let leaf = match segment {
Segment::Name("weights") | Segment::Name("scale") => "weight",
other => panic!("no checkpoint spelling for the leaf `{other}`"),
};
name.push_str(leaf);
}
name
}
fn transposed(tensor: &Tensor<f32>) -> Tensor<f32> {
let rows = tensor.shape().axes()[0];
let columns = tensor.shape().axes()[1];
let elements = tensor.to_vec();
let mut flipped = vec![0.0; elements.len()];
for row in 0..rows {
for column in 0..columns {
flipped[column * rows + row] = elements[row * columns + column];
}
}
Tensor::new([columns, rows], flipped)
}
pub fn load<E: Element + From<f32>>(
parameters: &Parameters<E>,
model: &Llama<E>,
weights: &Weights,
) -> Parameters<E> {
let mut wanted: HashMap<String, Path> = named_parameters(model)
.into_iter()
.map(|(path, _)| (foreign_name(&path), path))
.collect();
let mut entries: Vec<(Path, Tensor<E>)> = Vec::with_capacity(wanted.len());
weights.for_each(|name, released| {
let Some(path) = wanted.remove(name) else {
return;
};
let payload = if name.ends_with("proj.weight") || name == "lm_head.weight" {
transposed(&released)
} else {
released
};
entries.push((path, payload.convert::<E>()));
});
named_restore(parameters, model, entries)
}