Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/NOW.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# NOW — feat(igla): Wave Loop 889 (2026-08-06)
# NOW — feat: GF-T trainer size optimizations (2026-08-07)

Last updated: 2026-08-06
Last updated: 2026-08-07

## feat: GF-T trainer compression (scale_q + signmul) (Refs #1764)

- **NEW** spec `specs/ternary/gft_xorpercep3.t27` — fully-on-chip 2-layer XOR trainer with two reusable size optimizations, both numerically identical to `gft_xorpercep` (in-spec test PASS):
- `scale_q(x,k)` = x*2^-k via exponent-offset shift, replacing `smul(eta,.)` for power-of-2 eta (removes multipliers)
- `signmul(g,h)` = sign/zero mux, valid because the perceptron error g is exactly {-1,0,+1}, replacing `smul(g,.)`
- Shrinks the design 19.5M -> 17.86M fasm (6 magmuls -> 2). Honest note: still > the ~17M correctness ceiling -- the bulk is `magsub` (normalize loop in every `sadd`), not the magmuls, so multiplier optimizations do not clear the ceiling; a full 2-layer train step is irreducibly ~17.9M. Working on-chip path stays the split (cycle 53)
- The scale_q/signmul techniques are reusable for any GF-T trainer near the budget
- Spec-only; no `gen/`/`coq/` edits; no new `*.sh`; Refs #1764

## feat(igla): Wave Loop 888 close-out — [595][2]^6 Pt packed AoS witness (Refs #1836)

Expand Down
150 changes: 150 additions & 0 deletions specs/ternary/gft_xorpercep3.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
module GftXorPercep3;
// #1764 + GF-T: a GF-T SGD weight update -- w' = w - eta * g, the final brick of an
// on-device training step (forward softmax -> loss -> gradient g -> THIS update).
// eta is the (positive) learning rate; g the gradient (signed); w the weight (signed).
// Composes the verified primitives: signed multiply (smul over the RNE magnitude
// mul) + subtract (sadd + neg). Bit-exact to the integer oracle; accuracy is to
// GF-T16 precision (<=1 ULP; ~0.03 abs at the largest magnitudes).
//
// Inputs: w, g, eta signed GF-T16 (u32). Output: updated weight w' GF-T16 (u32).

fn magadd(a: i32, b: i32) -> i32 {
var ao : i32 = a >> 9; var am : i32 = a & 511;
var bo : i32 = b >> 9; var bm : i32 = b & 511;
var ho : i32 = bo; var hm : i32 = bm; var lo : i32 = ao; var lm : i32 = am;
if (ao >= bo) { ho = ao; hm = am; lo = bo; lm = bm; }
var hs : i32 = 512 + hm; var ls : i32 = 512 + lm;
var d : i32 = ho - lo; if (d > 11) { d = 11; }
var losh : i32 = ls >> d; var rem : i32 = ls - (losh << d);
var s : i32 = hs + losh; var off : i32 = ho; var mant : i32 = s - 512;
if (s >= 1024) {
var g : i32 = s & 1; var pre : i32 = s >> 1; mant = pre - 512;
if (g == 1) { if (rem > 0) { mant = mant + 1; } else { if ((pre & 1) == 1) { mant = mant + 1; } } }
off = ho + 1; if (off >= 80) { off = 80; }
} else {
var t : i32 = rem << 1; var hf : i32 = 1 << d;
if (t > hf) { mant = mant + 1; } else { if (t == hf) { if ((s & 1) == 1) { mant = mant + 1; } } }
}
if (mant >= 512) { mant = 0; off = off + 1; if (off >= 80) { off = 80; } }
return (off << 9) | mant;
}

fn magsub(hi: i32, lo: i32) -> i32 {
if (hi == lo) { return 0; }
var ho : i32 = hi >> 9; var hm : i32 = hi & 511;
var lo_o : i32 = lo >> 9; var lm : i32 = lo & 511;
var d : i32 = ho - lo_o; var hs : i32 = (512 + hm) << 14;
var la : i32 = 0; var sticky : i32 = 0;
if (d >= 26) { la = 0; sticky = 1; }
else { var ls : i32 = (512 + lm) << 14; la = ls >> d; if ((ls - (la << d)) > 0) { sticky = 1; } }
var diff : i32 = hs - la; var off : i32 = ho;
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
var q : i32 = diff >> 14; var rem : i32 = diff - (q << 14); var half : i32 = 8192; var mant : i32 = q - 512;
if (rem > half) { mant = mant + 1; }
else { if (rem == half) { if (sticky == 1) { mant = mant + 1; } else { if ((q & 1) == 1) { mant = mant + 1; } } } }
if (mant >= 512) { mant = 0; off = off + 1; if (off >= 80) { off = 80; } }
return (off << 9) | mant;
}

fn sadd(a: u32, b: u32) -> u32 {
if (a == 0) { return b; }
if (b == 0) { return a; }
var sa : i32 = (a >> 16) as i32; var ma : i32 = (a & 65535) as i32;
var sb : i32 = (b >> 16) as i32; var mb : i32 = (b & 65535) as i32;
if (sa == sb) { return ((sa << 16) | magadd(ma, mb)) as u32; }
var bsign : i32 = sa;
var r : i32 = magsub(ma, mb);
if (ma < mb) { r = magsub(mb, ma); bsign = sb; }
if (r == 0) { return 0; }
return ((bsign << 16) | r) as u32;
}

fn neg(v: u32) -> u32 {
if (v == 0) { return 0; }
return v ^ 65536;
}

fn magmul(a16: i32, b16: i32) -> i32 {
var ao : i32 = a16 >> 9; var am : i32 = a16 & 511;
var bo : i32 = b16 >> 9; var bm : i32 = b16 & 511;
var prod : i32 = (512 + am) * (512 + bm);
var carry : i32 = 0; if (prod >= 524288) { carry = 1; }
var q : i32 = prod >> 9; var r : i32 = prod & 511; var half : i32 = 256;
if (carry == 1) { q = prod >> 10; r = prod & 1023; half = 512; }
var mant : i32 = q - 512;
if (r > half) { mant = mant + 1; }
if (r == half) { if ((q & 1) == 1) { mant = mant + 1; } }
var sm : i32 = ao + bo + carry;
var out_off : i32 = 0;
if (sm >= 40) { var res : i32 = sm - 40; if (res >= 80) { out_off = 80; } else { out_off = res; } }
if (mant >= 512) { mant = 0; out_off = out_off + 1; if (out_off >= 80) { out_off = 80; } }
return (out_off << 9) | mant;
}

// softmax: p_sel = 2^(l_sel - M) / sum_i 2^(l_i - M), M = max logit.

// signed GF-T multiply: sign = xor of signs, magnitude = RNE magnitude mul.
fn smul(a: u32, b: u32) -> u32 {
if (a == 0) { return 0; }
if (b == 0) { return 0; }
var sgn : i32 = ((a >> 16) & 1) as i32;
var sb : i32 = ((b >> 16) & 1) as i32;
if (sgn != sb) { sgn = 1; } else { sgn = 0; }
var mag : i32 = magmul((a & 65535) as i32, (b & 65535) as i32);
if (mag == 0) { return 0; }
return ((sgn << 16) | mag) as u32;
}

fn relu(z: u32) -> u32 {
if (z == 0) { return 0; }
if (((z >> 16) & 1) == 1) { return 0; }
return z;
}
// x * 2^-k via a cheap exponent shift (NO multiplier). Used for a power-of-2
// learning rate (eta = 0.25 = 2^-2), replacing smul(eta, .) -> saves 2 magmuls.
fn scale_q(x: u32, k: i32) -> u32 {
if (x == 0) { return 0; }
var sign : i32 = ((x >> 16) & 1) as i32;
var off : i32 = ((x >> 9) & 127) as i32;
var mant : i32 = (x & 511) as i32;
off = off - k;
if (off < 1) { return 0; }
return ((sign << 16) | (off << 9) | mant) as u32;
}
// g * h where g is EXACTLY {-1, 0, +1} (perceptron error) -> sign/zero mux, NOT a
// multiply. Removes the gradient magmuls. Valid only because g in {-1,0,+1}.
fn signmul(g: u32, h: u32) -> u32 {
if (g == 0) { return 0; }
if (h == 0) { return 0; }
if (((g >> 16) & 1) == 1) { return neg(h); }
return h;
}
// FULLY on-chip 2-layer XOR trainer, DOUBLY shrunk: eta=0.25 via scale_q (2^-2)
// AND g*h via signmul (g in {-1,0,+1}). 6 magmuls -> 2 (only the forward z keeps
// real multiplies). Target: fit under the ~17M correctness ceiling.
fn on_comb(v0: u32, v1: u32, x0: u32, x1: u32, y: u32, eta: u32) -> u64 {
var s : u32 = sadd(x0, x1);
var h0 : u32 = relu(s);
var h1 : u32 = relu(sadd(s, neg(20480)));
var z : u32 = sadd(smul(v0, h0), smul(v1, h1));
var pred : u32 = 20480;
if (z == 0) { pred = 0; }
if (((z >> 16) & 1) == 1) { pred = 0; }
var g : u32 = sadd(pred, neg(y));
var v0n : u32 = sadd(v0, neg(scale_q(signmul(g, h0), 2)));
var v1n : u32 = sadd(v1, neg(scale_q(signmul(g, h1), 2)));
return ((v0n as u64) << 32) | (v1n as u64);
}
// same result as xorpercep: v=(0,0),(1,0),y=1 -> (0.25,0).
test upd { assert_eq(on_comb(0,0,20480,0,20480,19456), 83562883710976); }
Loading