use crate::arithmetic::Fx;
use crate::model::{Encoding, Tensor};
use super::dialect::Dialects;
use super::index::Edge;
use super::svd::dense_svd;
struct DialectAdj {
n: usize,
out: Vec<Vec<(u32, Fx)>>,
}
impl DialectAdj {
fn build(edges: &[Edge], assign: &[usize], head: usize, n: usize, maxw: u128) -> Self {
let mut acc: Vec<std::collections::HashMap<u32, i128>> =
vec![std::collections::HashMap::new(); n];
for (k, e) in edges.iter().enumerate() {
if assign[k] == head && e.stake > 0 {
*acc[e.src as usize].entry(e.tgt).or_insert(0) += e.stake;
}
}
let out = acc
.into_iter()
.map(|row| {
row.into_iter()
.map(|(j, w)| (j, Fx::ratio_u128(w as u128, maxw)))
.collect()
})
.collect();
DialectAdj { n, out }
}
fn apply(&self, x: &[Fx]) -> Vec<Fx> {
let mut out = vec![Fx::ZERO; self.n];
for i in 0..self.n {
let mut s = Fx::ZERO;
for &(j, w) in &self.out[i] {
s = s + w * x[j as usize];
}
out[i] = s;
}
out
}
fn apply_pow(&self, x: &[Fx], p: usize) -> Vec<Fx> {
let mut v = x.to_vec();
for _ in 0..p {
v = self.apply(&v);
}
v
}
}
struct Embed<'a> {
data: &'a [Fx],
n: usize,
d: usize,
}
impl Embed<'_> {
fn col(&self, c: usize) -> Vec<Fx> {
(0..self.n).map(|i| self.data[i * self.d + c]).collect()
}
fn t_apply(&self, y: &[Fx]) -> Vec<Fx> {
(0..self.d)
.map(|c| (0..self.n).fold(Fx::ZERO, |a, i| a + self.data[i * self.d + c] * y[i]))
.collect()
}
}
fn project(a: &DialectAdj, e: &Embed, l_eff: usize) -> Vec<Vec<Fx>> {
let cols: Vec<Vec<Fx>> = (0..e.d)
.map(|c| e.t_apply(&a.apply_pow(&e.col(c), l_eff)))
.collect();
(0..e.d)
.map(|i| (0..e.d).map(|j| cols[j][i]).collect())
.collect()
}
fn pinv(p: &[Vec<Fx>], rank_cap: usize, iters: usize) -> Vec<Vec<Fx>> {
let d = p.len();
let svd = dense_svd(p, d.min(rank_cap).max(1), iters);
let thresh = Fx::from_ratio(1, 100_000);
let mut out = vec![vec![Fx::ZERO; d]; d];
for c in 0..svd.sigma.len() {
if svd.sigma[c] > thresh {
let inv = Fx::ONE.div(svd.sigma[c]);
for i in 0..d {
for j in 0..d {
out[i][j] = out[i][j] + inv * svd.v[c][i] * svd.u[c][j];
}
}
}
}
out
}
fn dense_tensor(name: String, m: &[Vec<Fx>]) -> Tensor {
let d = m.len();
let mut data = Vec::with_capacity(d * d);
for row in m {
data.extend_from_slice(row);
}
Tensor {
name,
shape: vec![d as u64, d as u64],
encoding: Encoding::U16,
data,
}
}
#[allow(clippy::too_many_arguments)]
pub fn attention(
edges: &[Edge],
dialects: &Dialects,
e_data: &[Fx],
phi: &[Fx],
d: usize,
h: usize,
l: usize,
diam: usize,
) -> Vec<Tensor> {
let n = phi.len();
let e = Embed { data: e_data, n, d };
let d_h = d.checked_div(h).unwrap_or(d);
let maxw = edges
.iter()
.filter(|e| e.stake > 0)
.map(|e| e.stake as u128)
.max()
.unwrap_or(1)
.max(1);
let dialect_adj: Vec<DialectAdj> = (0..h)
.map(|head| DialectAdj::build(edges, &dialects.assign, head, n, maxw))
.collect();
let mut tensors = Vec::new();
for layer in 0..l {
let l_eff = 1 + (layer * diam) / l.max(1);
let mut q = vec![vec![Fx::ZERO; d]; d];
let mut k = vec![vec![Fx::ZERO; d]; d];
let mut v = vec![vec![Fx::ZERO; d]; d];
for (head, a) in dialect_adj.iter().enumerate() {
let base = head * d_h;
let p = project(a, &e, l_eff);
let svd = dense_svd(&p, d_h.min(n).max(1), 60);
for c in 0..d_h {
let ss = if c < svd.sigma.len() {
svd.sigma[c].sqrt()
} else {
Fx::ZERO
};
for i in 0..d {
if c < svd.u.len() {
q[i][base + c] = svd.u[c][i] * ss;
k[i][base + c] = svd.v[c][i] * ss;
}
}
}
for c in 0..d_h {
let ecol = e.col(base + c);
let ae = a.apply(&ecol);
let phi_ae: Vec<Fx> = (0..n).map(|i| phi[i] * ae[i]).collect();
let wv = e.t_apply(&phi_ae); for i in 0..d {
v[i][base + c] = wv[i];
}
}
}
let o = pinv(&v, n, 60);
tensors.push(dense_tensor(
format!("model.layers.{layer}.self_attn.q_proj.weight"),
&q,
));
tensors.push(dense_tensor(
format!("model.layers.{layer}.self_attn.k_proj.weight"),
&k,
));
tensors.push(dense_tensor(
format!("model.layers.{layer}.self_attn.v_proj.weight"),
&v,
));
tensors.push(dense_tensor(
format!("model.layers.{layer}.self_attn.o_proj.weight"),
&o,
));
tensors.push(Tensor {
name: format!("model.layers.{layer}.self_attn.alpha_beta.weight"),
shape: vec![2],
encoding: Encoding::U16,
data: vec![Fx::ONE, Fx::ZERO],
});
}
tensors
}
#[cfg(test)]
mod tests {
use super::super::{arch, dialect, embed, index::build};
use super::*;
use crate::graph::Cyberlink;
fn hash(b: u8) -> [u8; 32] {
let mut h = [0u8; 32];
h[0] = b;
h
}
fn link(from: u8, to: u8) -> Cyberlink {
Cyberlink {
neuron: hash(from),
from: hash(from),
to: hash(to),
token: 0,
amount: 100,
valence: 1,
block: 0,
}
}
fn setup() -> (Vec<Tensor>, usize) {
let mut links = Vec::new();
for (a, b) in [(1u8, 2u8), (2, 3), (3, 4), (4, 1), (1, 3)] {
links.push(link(a, b));
links.push(link(b, a));
}
let (_v, edges, adj) = build(&[], &links);
let dialects = dialect::discover(&_v, &edges);
let a = arch::compute(&adj, dialects.len(), 0);
let d = 16.min(a.d.max(4)); let e = embed::embed(&adj, &a.phi, d);
let t = attention(&edges, &dialects, &e.data, &a.phi, d, a.h, a.l, a.diameter);
(t, a.l)
}
#[test]
fn emits_five_tensors_per_layer_with_right_shapes() {
let (t, l) = setup();
assert_eq!(t.len(), 5 * l, "q,k,v,o,alpha_beta per layer");
let q0 = t
.iter()
.find(|t| t.name == "model.layers.0.self_attn.q_proj.weight")
.unwrap();
assert_eq!(q0.shape.len(), 2);
assert_eq!(q0.data.len() as u64, q0.shape[0] * q0.shape[1]);
let ab = t
.iter()
.find(|t| t.name == "model.layers.0.self_attn.alpha_beta.weight")
.unwrap();
assert_eq!(ab.shape, vec![2]);
assert_eq!(ab.data[0].raw(), Fx::ONE.raw());
assert_eq!(ab.data[1].raw(), Fx::ZERO.raw());
}
#[test]
fn attention_is_deterministic() {
let (a, _) = setup();
let (b, _) = setup();
assert!(a.iter().zip(&b).all(|(x, y)| x
.data
.iter()
.zip(&y.data)
.all(|(p, q)| p.raw() == q.raw())));
}
fn pearson(x: &[f64], y: &[f64]) -> f64 {
let n = x.len() as f64;
let (mx, my) = (x.iter().sum::<f64>() / n, y.iter().sum::<f64>() / n);
let (mut sxy, mut sxx, mut syy) = (0.0, 0.0, 0.0);
for i in 0..x.len() {
let (dx, dy) = (x[i] - mx, y[i] - my);
sxy += dx * dy;
sxx += dx * dx;
syy += dy * dy;
}
if sxx == 0.0 || syy == 0.0 {
0.0
} else {
sxy / (sxx.sqrt() * syy.sqrt())
}
}
#[test]
fn p_attn_qk_reconstructs_the_projection() {
let mut links = Vec::new();
for (a, b) in [(1u8, 2u8), (2, 3), (3, 4), (4, 1), (1, 3)] {
links.push(link(a, b));
links.push(link(b, a));
}
let (v, edges, adj) = build(&[], &links);
let dialects = dialect::discover(&v, &edges);
let ar = arch::compute(&adj, dialects.len(), 0);
let d = 16.min(ar.d.max(4));
let n = ar.phi.len();
let e_t = embed::embed(&adj, &ar.phi, d);
let tensors = attention(
&edges,
&dialects,
&e_t.data,
&ar.phi,
d,
ar.h,
ar.l,
ar.diameter,
);
let e = Embed {
data: &e_t.data,
n,
d,
};
let maxw = edges
.iter()
.filter(|x| x.stake > 0)
.map(|x| x.stake as u128)
.max()
.unwrap_or(1)
.max(1);
let a0 = DialectAdj::build(&edges, &dialects.assign, 0, n, maxw);
let p = project(&a0, &e, 1);
let q = &tensors
.iter()
.find(|t| t.name == "model.layers.0.self_attn.q_proj.weight")
.unwrap()
.data;
let k = &tensors
.iter()
.find(|t| t.name == "model.layers.0.self_attn.k_proj.weight")
.unwrap()
.data;
let (mut xs, mut ys) = (Vec::new(), Vec::new());
for i in 0..d {
for j in 0..d {
let mut s = 0.0;
for c in 0..d {
s += q[i * d + c].to_f64() * k[j * d + c].to_f64();
}
xs.push(s);
ys.push(p[i][j].to_f64());
}
}
let r = pearson(&xs, &ys);
assert!(r >= 0.7, "P-ATTN Pearson {r} < 0.7");
}
}