t27.aiEnglish

Умножить, потом сложить

Вы узнаете

Что умножение с накоплением делает с двумя произведениями со знаком и как его ломает замена из урока 12.

Запись ещё не готова: она ждёт команд tri test и tri mutate plant из gHashTag/t27#7400, которые запись запускает, а до тех пор виджет ниже -- заглушка без запуска. Нейрон суммирует произведения весов на входы, и on_comb в gft_signed_mac.t27 — такая сумма из двух слагаемых: smul(a1, b1) плюс smul(a2, b2). Здесь smul ставит знак через XOR и не проверяет ноль. Нативный t27c запускает 4 теста, и все проходят; один из них, zero_clause, ждёт, что 0 x 1 + 0 x 1 даст 512, то есть 2^-39, а не 0, и комментарий называет это задуманным поведением. Браузер пропускает свои тесты, потому что его исполнитель пока не знает assert_eq. Запись повторяет ошибку урока 12 и меняет XOR на OR. Падает ровно один тест, pp: (-1.0) x (-1.0) теперь выходит отрицательным, и сумма равна 0 вместо 2.0. Каждый байт в записи напечатала команда; инсценирован только набор текста.

Попробуйте

Найдите в записи тест, который ждёт 512, и тест, который ломает подложенная ошибка; затем сами замените в спеке XOR в smul на OR и посмотрите, поймает ли это браузер.

Открыть интерактивный урок →

gft_signed_mac.t27: recording pending
gft_signed_mac.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_signed_mac.t27

module GftSignedMac;
// #1764 + GF-T: a spec-first SIGNED GF-T16 MAC (y = a1*b1 + a2*b2) with
// round-to-nearest-even, bit-exact to the ideal oracle gft16_ref.py (with sign).
// This is the piece real NN inference needs -- negative weights/activations and
// cancellation in the accumulator.
//
// Signed GF-T16: [ sign(1) : offset(7) : mant(9) ] (17 bits, held in a u32),
// magnitude value = (1+mant/512)*2^(offset-40). Signed mul = sign XOR + RNE
// magnitude mul. Signed add = same-sign RNE add, or (different sign) subtract the
// smaller magnitude from the larger: align, subtract with G guard bits, left-
// normalize, round to nearest even; result takes the larger operand's sign, or is
// zero on exact cancellation.
// All intermediates non-negative small ints -> i32 (signed shift/compare/mul on
// positives == unsigned, bit-identical to the oracle).

// --- RNE magnitude multiply (matches gft_mul_rne) ---
fn magmul(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 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 s : i32 = ao + bo + carry;
    var off : i32 = 0;
    if (s >= 40) { var rr : i32 = s - 40; if (rr >= 80) { off = 80; } else { off = rr; } }
    if (mant >= 512) { mant = 0; off = off + 1; if (off >= 80) { off = 80; } }
    return (off << 9) | mant;
}

// --- RNE magnitude same-sign add (matches gft_add_rne) ---
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;
}

// --- RNE magnitude subtract, hi >= lo. Returns 0 on exact cancellation. G=14 guard bits. ---
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 smul(a: u32, b: u32) -> u32 {
    var sa : i32 = (a >> 16) as i32;
    var sb : i32 = (b >> 16) as i32;
    var mag : i32 = magmul((a & 65535) as i32, (b & 65535) as i32);
    return (((sa ^ sb) << 16) | mag) as u32;
}

fn sadd(a: u32, b: u32) -> u32 {
    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; }
    // different signs: a top-level r==0 guard (NOT nested inside the ma>=mb
    // branch -- gen-verilog does not lower that as an early return, so it fell
    // through to (sign<<16)|0 = a wrong negative zero on exact cancellation).
    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;
}

// The signed MAC: y = a1*b1 + a2*b2.
fn on_comb(a1: u32, b1: u32, a2: u32, b2: u32) -> u32 {
    return sadd(smul(a1, b1), smul(a2, b2));
}

// +1.0 = offset 40 mant 0 sign 0 = 20480. -1.0 = sign 1 = 0x15000 = 86016.
// (+1)*(+1) + (-1)*(-1) = 1 + 1 = 2.0 (+) = offset 41 = 20992.
test pp { assert_eq(on_comb(20480, 20480, 86016, 86016), 20992); }
// (+1)*(+1) + (+1)*(-1) = 1 - 1 = 0.
test cancel { assert_eq(on_comb(20480, 20480, 20480, 86016), 0); }
endmodule

Открыть спеку урока в плеере ↗

Все уроки