t27.aiРусский

Opposite signs subtract

You will learn

How a signed add turns into a subtraction when the signs differ, and what its 3 tests leave unchecked.

Recording pending: it waits on tri test and tri mutate plant from gHashTag/t27#7400, the two commands the recording runs, and until then the widget below is a placeholder that shows no run. sadd in gft_sadd.t27 adds two numbers of the same sign with magadd. When the signs differ it subtracts the smaller size from the larger with magsub, takes the sign of the larger, and returns 0 on an exact cancellation. The browser skips all 3 tests because its runner does not know assert_eq yet; the native t27c runs all 3, all pass, none vacuous. Only a2, 1.0 plus -1.0, mixes signs, and it cancels: magsub returns 0 at once because both sizes are equal. No test subtracts two different sizes, so the sign of the larger never decides a result. The recording adds the sizes in the branch a2 takes, and exactly one test fails, a2. Every byte in the recording was printed by the command; only the typing is staged.

Try it

In the recording, find the changed line and the value a2 expects; then in the spec frame find bsign and check whether a1, a2 or a3 ever depends on it.

Open the interactive lesson →

gft_sadd.t27: recording pending
gft_sadd.t27: recording pending ↗

Recording pending: waits on tri test and tri mutate plant from gHashTag/t27#7400. Until then this page is a placeholder and shows no run.

specs/ternary/gft_sadd.t27

module GftSadd;
// #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;
    var cap : i32 = 12; if (off - 1 < cap) { cap = off - 1; } if (cap < 0) { cap = 0; }
    var sh : i32 = 0;
    if (diff != 0) {
        var t : i32 = diff;
        if (t < 65536) { if (sh + 8 <= cap) { t = t << 8; sh = sh + 8; } }
        if (t < 1048576) { if (sh + 4 <= cap) { t = t << 4; sh = sh + 4; } }
        if (t < 4194304) { if (sh + 2 <= cap) { t = t << 2; sh = sh + 2; } }
        if (t < 8388608) { if (sh + 1 <= cap) { t = t << 1; sh = sh + 1; } }
    }
    diff = diff << sh; off = off - sh;
    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;
}

// Standalone signed GF-T16 add (a+b). The shared adder datapath for a
// microsequenced backprop (see gft_smul). Reused across cycles by the FSM.
fn on_comb(a: u32, b: u32) -> u32 { return sadd(a, b); }
test a1 { assert_eq(on_comb(20480, 20480), 20992); }      // 1+1=2
test a2 { assert_eq(on_comb(20480, 86016), 0); }          // 1+(-1)=0
test a3 { assert_eq(on_comb(19968, 19968), 20480); }      // 0.5+0.5=1

Open the lesson's spec in the player ↗

All lessons