use crate::cq::CqWeight;
use crate::hadamard::hada_n;
use crate::math;
use crate::norm::zc_rms_norm_vec;
use crate::ops::{sigmoid, softmax_inplace};
use crate::rope::RopeCache;
use crate::v2::config::V2Config;
use crate::v2::kernels::{
engram_index, engram_table_order, hadamard_mlp, rms_unit, rms_unit_to, sinkhorn,
};
use alloc::vec;
use alloc::vec::Vec;
pub struct V2Layer {
pub norm_in: Vec<f32>,
pub q_proj: CqWeight,
pub k_proj: CqWeight,
pub v_proj: CqWeight,
pub gate_proj: CqWeight,
pub out_proj: CqWeight,
pub q_norm: Vec<f32>,
pub k_norm: Vec<f32>,
pub post_norm: Vec<f32>,
pub attn_gate: f32,
pub pre_hada: Vec<f32>,
pub d1: Vec<f32>,
pub d2: Vec<f32>,
pub d3: Vec<f32>,
}
pub struct V2Mhc {
pub a_pre: Vec<f32>,
pub a_post: Vec<f32>,
pub a_res: Vec<f32>,
pub b_pre: Vec<f32>,
pub b_post: Vec<f32>,
pub b_res: Vec<f32>,
pub phi_pre: CqWeight,
pub phi_post: CqWeight,
pub phi_res: CqWeight,
}
pub struct V2Engram {
pub tables: CqWeight,
pub key_proj: CqWeight,
pub value_proj: CqWeight,
pub taps: Vec<f32>,
}
pub struct V2Model {
pub cfg: V2Config,
pub embedding: CqWeight,
pub layers: Vec<V2Layer>,
pub mhc: V2Mhc,
pub engrams: Vec<V2Engram>,
pub final_norm: Vec<f32>,
rope: RopeCache,
embed_scale: f32,
}
pub struct V2State {
k_cache: Vec<Vec<f32>>,
v_cache: Vec<Vec<f32>>,
history: Vec<u32>,
pos: usize,
kv_heads: usize,
head_dim: usize,
cache_len: usize,
kv_window: usize,
engram_v: Vec<f32>,
ring: usize,
lanes: Vec<f32>, lanes_prev: Vec<f32>, nx: Vec<f32>, nx_prep: Vec<f32>,
u: Vec<f32>, bx: Vec<f32>, y: Vec<f32>, h: Vec<f32>, h_prep: Vec<f32>,
hpre: Vec<f32>, hpost: Vec<f32>, hres: Vec<f32>, q: Vec<f32>, k: Vec<f32>, v: Vec<f32>, gate: Vec<f32>, attn_out: Vec<f32>, attn_prep: Vec<f32>,
attn_result: Vec<f32>, scores: Vec<f32>, hada: Vec<f32>, mlp_out: Vec<f32>, e_buf: Vec<f32>, e_prep: Vec<f32>,
engram_k: Vec<f32>, engram_val: Vec<f32>, alpha: Vec<f32>, lm: Vec<f32>, lm_prep: Vec<f32>,
tmp_a: Vec<f32>, tmp_b: Vec<f32>, }
impl V2Model {
pub fn new(
cfg: V2Config,
embedding: CqWeight,
layers: Vec<V2Layer>,
mhc: V2Mhc,
engrams: Vec<V2Engram>,
final_norm: Vec<f32>,
) -> Result<Self, &'static str> {
cfg.validate()?;
if layers.len() != cfg.num_layers {
return Err("layer count does not match num_layers");
}
if engrams.len() != cfg.engram.sites.len() {
return Err("engram count does not match site count");
}
if final_norm.len() != cfg.d_model {
return Err("final_norm width does not match d_model");
}
if hada_n(cfg.d_model) != cfg.hada_n {
return Err("hada_n is not d_model rounded up to a power of two");
}
if embedding.out_feat != cfg.vocab_size || embedding.in_feat != cfg.d_model {
return Err("embedding shape does not match geometry");
}
let n = cfg.mhc_lanes;
let l = cfg.num_layers;
if mhc.a_pre.len() != l || mhc.a_post.len() != l || mhc.a_res.len() != l {
return Err("mhc a_* length does not match num_layers");
}
if mhc.b_pre.len() != l * n || mhc.b_post.len() != l * n || mhc.b_res.len() != l * n * n {
return Err("mhc b_* length does not match num_layers x lanes");
}
if mhc.phi_pre.out_feat != l * n
|| mhc.phi_post.out_feat != l * n
|| mhc.phi_res.out_feat != l * n * n
{
return Err("mhc phi_* row count does not match num_layers x lanes");
}
if mhc.phi_pre.in_feat != cfg.mhc_width() {
return Err("mhc phi_* width does not match lanes x d_model");
}
for layer in &layers {
if layer.d1.len() != cfg.hada_n
|| layer.d2.len() != cfg.hada_n
|| layer.d3.len() != cfg.hada_n
{
return Err("HadamardMLP diagonal width does not match hada_n");
}
if layer.q_norm.len() != cfg.head_dim || layer.k_norm.len() != cfg.head_dim {
return Err("q_norm/k_norm width does not match head_dim");
}
}
for e in &engrams {
if e.taps.len() != cfg.engram.conv_taps * cfg.d_model {
return Err("engram taps shape does not match conv_taps x d_model");
}
if e.tables.in_feat != cfg.engram.sub_dim {
return Err("engram table width does not match sub_dim");
}
}
let rope = RopeCache::new(cfg.max_seq_len, cfg.head_dim, cfg.rope_theta);
let embed_scale = math::sqrt(cfg.d_model as f32);
Ok(Self {
cfg,
embedding,
layers,
mhc,
engrams,
final_norm,
rope,
embed_scale,
})
}
pub const ENGRAM_GATE_SOURCE: &'static str = "decode.py (_forward_cached), gate on u";
pub fn make_state(&self) -> V2State {
let cache_len = if self.cfg.kv_window == 0 {
self.cfg.max_seq_len
} else {
self.cfg.kv_window.min(self.cfg.max_seq_len)
};
self.make_state_with_cache(cache_len, self.cfg.kv_window)
}
pub fn make_state_full_causal(&self) -> V2State {
self.make_state_with_cache(self.cfg.max_seq_len, 0)
}
fn make_state_with_cache(&self, cache_len: usize, kv_window: usize) -> V2State {
let cfg = &self.cfg;
let (d, n) = (cfg.d_model, cfg.mhc_lanes);
let sites = cfg.engram.sites.len();
let ring = (cfg.engram.window() + 1).max(1);
let fetched = cfg.engram.fetched_dim().max(1);
let engram_prep = self
.engrams
.first()
.map_or(fetched, |e| e.value_proj.prepared_len());
V2State {
k_cache: (0..cfg.num_layers)
.map(|_| vec![0.0f32; cfg.num_kv_heads * cache_len * cfg.head_dim])
.collect(),
v_cache: (0..cfg.num_layers)
.map(|_| vec![0.0f32; cfg.num_kv_heads * cache_len * cfg.head_dim])
.collect(),
history: vec![0u32; cfg.max_seq_len],
pos: 0,
kv_heads: cfg.num_kv_heads,
head_dim: cfg.head_dim,
cache_len,
kv_window,
engram_v: vec![0.0; sites * ring * d],
ring,
lanes: vec![0.0; n * d],
lanes_prev: vec![0.0; n * d],
nx: vec![0.0; n * d],
nx_prep: vec![0.0; self.mhc.phi_pre.prepared_len()],
u: vec![0.0; d],
bx: vec![0.0; d],
y: vec![0.0; d],
h: vec![0.0; d],
h_prep: vec![0.0; self.layers[0].q_proj.prepared_len()],
hpre: vec![0.0; n],
hpost: vec![0.0; n],
hres: vec![0.0; n * n],
q: vec![0.0; cfg.attn_dim()],
k: vec![0.0; cfg.kv_dim()],
v: vec![0.0; cfg.kv_dim()],
gate: vec![0.0; cfg.attn_dim()],
attn_out: vec![0.0; cfg.attn_dim()],
attn_prep: vec![0.0; self.layers[0].out_proj.prepared_len()],
attn_result: vec![0.0; d],
scores: vec![0.0; cfg.max_seq_len],
hada: vec![0.0; cfg.hada_n],
mlp_out: vec![0.0; d],
e_buf: vec![0.0; fetched],
e_prep: vec![0.0; engram_prep],
engram_k: vec![0.0; sites * d],
engram_val: vec![0.0; sites * d],
alpha: vec![0.0; sites.max(1)],
lm: vec![0.0; d],
lm_prep: vec![0.0; self.embedding.prepared_len()],
tmp_a: vec![0.0; d],
tmp_b: vec![0.0; d],
}
}
pub fn step(
&self,
token: u32,
state: &mut V2State,
logits: &mut [f32],
) -> Result<(), &'static str> {
self.forward(token, state, Some(logits), &mut |_, _, _| {})
}
pub fn step_prefill(&self, token: u32, state: &mut V2State) -> Result<(), &'static str> {
self.forward(token, state, None, &mut |_, _, _| {})
}
pub fn step_traced<F: FnMut(&str, usize, &[f32])>(
&self,
token: u32,
state: &mut V2State,
logits: &mut [f32],
trace: &mut F,
) -> Result<(), &'static str> {
self.forward(token, state, Some(logits), trace)
}
fn forward<F: FnMut(&str, usize, &[f32])>(
&self,
token: u32,
state: &mut V2State,
logits: Option<&mut [f32]>,
trace: &mut F,
) -> Result<(), &'static str> {
const WHOLE: usize = usize::MAX;
let cfg = &self.cfg;
if state.pos >= cfg.max_seq_len {
return Err("sequence exceeds max_seq_len");
}
if token as usize >= cfg.vocab_size {
return Err("token id outside vocabulary");
}
if logits.as_ref().is_some_and(|l| l.len() != cfg.vocab_size) {
return Err("logits buffer is not vocab_size long");
}
let pos = state.pos;
let (d, n) = (cfg.d_model, cfg.mhc_lanes);
state.history[pos] = token;
self.embedding
.dequantize_row(token as usize, &mut state.tmp_a);
for v in state.tmp_a.iter_mut() {
*v *= self.embed_scale;
}
trace("embed", WHOLE, &state.tmp_a);
for lane in 0..n {
state.lanes[lane * d..(lane + 1) * d].copy_from_slice(&state.tmp_a);
}
self.engram_fill(state, pos);
if !cfg.engram.sites.is_empty() {
trace("engram_k0", WHOLE, &state.engram_k[..d]);
trace("engram_v0", WHOLE, &state.engram_val[..d]);
}
for li in 0..cfg.num_layers {
self.mhc_pre(state, li);
trace("hpre", li, &state.hpre);
trace("u", li, &state.u);
self.engram_gate(state, li);
trace("bx", li, &state.bx);
if let Some(site) = cfg.engram.site_of_layer(li) {
trace("alpha", li, &state.alpha[site..site + 1]);
}
self.block(state, li, pos);
trace("y", li, &state.y);
self.mhc_post(state, li);
trace("hpost", li, &state.hpost);
trace("hres", li, &state.hres);
trace("x", li, &state.lanes);
}
for c in 0..d {
let mut acc = 0.0f32;
for lane in 0..n {
acc += state.lanes[lane * d + c];
}
state.lm[c] = acc / n as f32;
}
trace("lane_mean", WHOLE, &state.lm);
zc_rms_norm_vec(&mut state.lm, &self.final_norm);
trace("final_norm", WHOLE, &state.lm);
if let Some(logits) = logits {
self.embedding.prepare_input(&state.lm, &mut state.lm_prep);
self.embedding.matvec_prepared(&state.lm_prep, logits);
trace("logits", WHOLE, logits);
}
state.pos += 1;
Ok(())
}
pub fn last_hidden(state: &V2State) -> &[f32] {
&state.lm
}
pub(crate) fn rope_apply(
&self,
x: &mut [f32],
seq_len: usize,
num_heads: usize,
head_dim: usize,
offset: usize,
) {
self.rope.apply(x, seq_len, num_heads, head_dim, offset);
}
fn mhc_pre(&self, state: &mut V2State, li: usize) {
let cfg = &self.cfg;
let (d, n) = (cfg.d_model, cfg.mhc_lanes);
rms_unit_to(&state.lanes, &mut state.nx);
self.mhc
.phi_pre
.prepare_input(&state.nx, &mut state.nx_prep);
self.mhc
.phi_pre
.matvec_rows_prepared(&state.nx_prep, li * n, &mut state.hpre);
let a = self.mhc.a_pre[li];
for lane in 0..n {
let z = a * state.hpre[lane] + self.mhc.b_pre[li * n + lane] + cfg.pre_off(li, lane);
state.hpre[lane] = sigmoid(z);
}
for c in 0..d {
let mut acc = 0.0f32;
for lane in 0..n {
acc += state.hpre[lane] * state.lanes[lane * d + c];
}
state.u[c] = acc;
}
state.bx.copy_from_slice(&state.u);
}
fn engram_gate(&self, state: &mut V2State, li: usize) {
let Some(site) = self.cfg.engram.site_of_layer(li) else {
return;
};
let d = self.cfg.d_model;
let base = site * d;
rms_unit_to(&state.u, &mut state.tmp_a);
state.tmp_b.copy_from_slice(&state.engram_k[base..base + d]);
rms_unit(&mut state.tmp_b);
let mut dot = 0.0f32;
for c in 0..d {
dot += state.tmp_a[c] * state.tmp_b[c];
}
let alpha = sigmoid(dot / math::sqrt(d as f32));
state.alpha[site] = alpha;
for c in 0..d {
state.bx[c] = state.u[c] + alpha * state.engram_val[base + c];
}
}
fn block(&self, state: &mut V2State, li: usize, pos: usize) {
let layer = &self.layers[li];
let d = self.cfg.d_model;
state.h.copy_from_slice(&state.bx);
zc_rms_norm_vec(&mut state.h, &layer.norm_in);
self.attention(state, li, pos);
zc_rms_norm_vec(&mut state.attn_result, &layer.post_norm);
let g = sigmoid(layer.attn_gate);
for c in 0..d {
state.y[c] = state.bx[c] + g * state.attn_result[c];
}
state.h.copy_from_slice(&state.y);
zc_rms_norm_vec(&mut state.h, &layer.pre_hada);
hadamard_mlp(
&state.h,
&layer.d1,
&layer.d2,
&layer.d3,
&mut state.hada,
&mut state.mlp_out,
);
for c in 0..d {
state.y[c] += state.mlp_out[c];
state.y[c] -= state.u[c];
}
}
fn attention(&self, state: &mut V2State, li: usize, pos: usize) {
let layer = &self.layers[li];
let cfg = &self.cfg;
let (h_n, kv_n, hd) = (cfg.num_heads, cfg.num_kv_heads, cfg.head_dim);
let reps = cfg.kv_repeat();
layer.q_proj.prepare_input(&state.h, &mut state.h_prep);
layer.q_proj.matvec_prepared(&state.h_prep, &mut state.q);
layer.k_proj.matvec_prepared(&state.h_prep, &mut state.k);
layer.v_proj.matvec_prepared(&state.h_prep, &mut state.v);
layer
.gate_proj
.matvec_prepared(&state.h_prep, &mut state.gate);
for head in 0..h_n {
zc_rms_norm_vec(&mut state.q[head * hd..(head + 1) * hd], &layer.q_norm);
}
for head in 0..kv_n {
zc_rms_norm_vec(&mut state.k[head * hd..(head + 1) * hd], &layer.k_norm);
}
self.rope.apply(&mut state.q, 1, h_n, hd, pos);
self.rope.apply(&mut state.k, 1, kv_n, hd, pos);
let k_src = core::mem::take(&mut state.k);
let v_src = core::mem::take(&mut state.v);
state.write_kv(li, pos, &k_src, &v_src);
state.k = k_src;
state.v = v_src;
let lo = state.window_lo(pos);
let span = pos - lo + 1;
let scale = 1.0 / math::sqrt(hd as f32);
let (cache_len, ring_hd) = (state.cache_len, state.head_dim);
let slot = |kv: usize, p: usize| (kv * cache_len + p % cache_len) * ring_hd;
for head in 0..h_n {
let kv = head / reps;
for s in 0..span {
let off = slot(kv, lo + s);
let krow = &state.k_cache[li][off..off + hd];
let qrow = &state.q[head * hd..(head + 1) * hd];
let mut acc = 0.0f32;
for i in 0..hd {
acc += qrow[i] * krow[i];
}
state.scores[s] = acc * scale;
}
softmax_inplace(&mut state.scores[..span]);
let obase = head * hd;
state.attn_out[obase..obase + hd].fill(0.0);
for s in 0..span {
let w = state.scores[s];
let off = slot(kv, lo + s);
for i in 0..hd {
state.attn_out[obase + i] += w * state.v_cache[li][off + i];
}
}
}
for (o, &g) in state.attn_out.iter_mut().zip(state.gate.iter()) {
*o *= sigmoid(g);
}
layer
.out_proj
.prepare_input(&state.attn_out, &mut state.attn_prep);
layer
.out_proj
.matvec_prepared(&state.attn_prep, &mut state.attn_result);
}
fn mhc_post(&self, state: &mut V2State, li: usize) {
let cfg = &self.cfg;
let (d, n) = (cfg.d_model, cfg.mhc_lanes);
self.mhc
.phi_post
.matvec_rows_prepared(&state.nx_prep, li * n, &mut state.hpost);
let a_post = self.mhc.a_post[li];
for lane in 0..n {
let z = a_post * state.hpost[lane]
+ self.mhc.b_post[li * n + lane]
+ cfg.post_off(li, lane);
state.hpost[lane] = 2.0 * sigmoid(z);
}
self.mhc
.phi_res
.matvec_rows_prepared(&state.nx_prep, li * n * n, &mut state.hres);
let a_res = self.mhc.a_res[li];
for k in 0..n * n {
state.hres[k] = a_res * state.hres[k] + self.mhc.b_res[li * n * n + k];
}
sinkhorn(&mut state.hres, n);
state.lanes_prev.copy_from_slice(&state.lanes);
for i in 0..n {
let hp = state.hpost[i];
for c in 0..d {
let mut acc = 0.0f32;
for j in 0..n {
acc += state.hres[i * n + j] * state.lanes_prev[j * d + c];
}
state.lanes[i * d + c] = acc + hp * state.y[c];
}
}
}
pub(crate) fn engram_fill(&self, state: &mut V2State, pos: usize) {
let cfg = &self.cfg;
if cfg.engram.sites.is_empty() {
return;
}
let d = cfg.d_model;
let eg = &cfg.engram;
let heads = eg.heads();
let dil = eg.conv_dilation;
let ring = state.ring;
for site in 0..self.engrams.len() {
self.engram_fetch(state, pos, site, heads);
let weights = &self.engrams[site];
weights
.value_proj
.prepare_input(&state.e_buf, &mut state.e_prep);
let slot = (site * ring + pos % ring) * d;
weights
.value_proj
.matvec_prepared(&state.e_prep, &mut state.engram_v[slot..slot + d]);
let kbase = site * d;
weights
.key_proj
.matvec_prepared(&state.e_prep, &mut state.engram_k[kbase..kbase + d]);
let vbase = site * d;
state.engram_val[vbase..vbase + d].fill(0.0);
for j in 0..eg.conv_taps {
let shift = j * dil;
if shift > pos {
continue;
}
let src = (site * ring + (pos - shift) % ring) * d;
let tap = &weights.taps[j * d..(j + 1) * d];
let v = &state.engram_v[src..src + d];
for (dst, (&t, &vc)) in state.engram_val[vbase..vbase + d]
.iter_mut()
.zip(tap.iter().zip(v.iter()))
{
*dst += t * vc;
}
}
}
}
fn engram_fetch(&self, state: &mut V2State, pos: usize, site: usize, heads: usize) {
let (hist, e_buf) = (&state.history, &mut state.e_buf);
self.engram_fetch_into(pos, site, heads, hist, e_buf);
}
pub(crate) fn engram_fetch_into(
&self,
pos: usize,
site: usize,
heads: usize,
history: &[u32],
out: &mut [f32],
) {
let eg = &self.cfg.engram;
let tables = &self.engrams[site].tables;
let sub = eg.sub_dim;
for table in 0..eg.num_tables {
let order = eg.orders[engram_table_order(table, heads)];
let idx = engram_index(table, order, eg.slots, |j| {
if j > pos {
0
} else {
history[pos - j]
}
});
let dst = &mut out[table * sub..(table + 1) * sub];
if order > 0 && order - 1 > pos {
dst.fill(0.0);
} else {
tables.dequantize_row(table * eg.slots + idx, dst);
}
}
}
}
impl V2State {
pub fn pos(&self) -> usize {
self.pos
}
pub fn history(&self) -> &[u32] {
&self.history[..self.pos]
}
#[inline]
pub(crate) fn window_lo(&self, pos: usize) -> usize {
if self.kv_window == 0 {
0
} else {
pos + 1 - self.kv_window.min(pos + 1)
}
}
pub fn kv_window(&self) -> usize {
self.kv_window
}
pub fn set_kv_window(
&mut self,
window: Option<usize>,
model: &V2Model,
) -> Result<(), &'static str> {
let w = window.unwrap_or(model.cfg.kv_window);
let needed = if w == 0 { model.cfg.max_seq_len } else { w };
if needed > self.cache_len {
return Err(
"requested attention window exceeds this state's KV capacity; \
allocate with make_state_full_causal",
);
}
self.kv_window = w;
Ok(())
}
pub(crate) fn set_history(&mut self, pos: usize, token: u32) {
self.history[pos] = token;
}
pub(crate) fn advance(&mut self, n: usize) {
self.pos += n;
}
#[inline]
fn slot(&self, kv_head: usize, pos: usize) -> usize {
(kv_head * self.cache_len + pos % self.cache_len) * self.head_dim
}
pub(crate) fn write_kv(&mut self, layer: usize, pos: usize, k: &[f32], v: &[f32]) {
let hd = self.head_dim;
for head in 0..self.kv_heads {
let off = self.slot(head, pos);
self.k_cache[layer][off..off + hd].copy_from_slice(&k[head * hd..(head + 1) * hd]);
self.v_cache[layer][off..off + hd].copy_from_slice(&v[head * hd..(head + 1) * hd]);
}
}
#[inline]
pub(crate) fn k_at(&self, layer: usize, kv_head: usize, pos: usize) -> &[f32] {
let off = self.slot(kv_head, pos);
&self.k_cache[layer][off..off + self.head_dim]
}
#[inline]
pub(crate) fn v_at(&self, layer: usize, kv_head: usize, pos: usize) -> &[f32] {
let off = self.slot(kv_head, pos);
&self.v_cache[layer][off..off + self.head_dim]
}
pub(crate) fn ring(&self) -> usize {
self.ring
}
pub(crate) fn history_slice(&self) -> &[u32] {
&self.history
}
pub(crate) fn engram_v_slot(&mut self, slot: usize, d: usize) -> &mut [f32] {
&mut self.engram_v[slot..slot + d]
}
pub(crate) fn engram_v_row(&self, slot: usize, d: usize) -> &[f32] {
&self.engram_v[slot..slot + d]
}
pub fn cache_len(&self) -> usize {
self.cache_len
}
pub fn kv_bytes(&self) -> usize {
self.k_cache
.iter()
.chain(self.v_cache.iter())
.map(|c| c.len() * 4)
.sum()
}
pub fn reset(&mut self) {
self.pos = 0;
for c in self.k_cache.iter_mut().chain(self.v_cache.iter_mut()) {
c.fill(0.0);
}
self.engram_v.fill(0.0);
self.history.fill(0);
}
}