t27.aiEnglish

Нейрон по кускам

Вы узнаете

Как нейрон с числом входов больше 27 складывает скалярные произведения кусок за куском и почему важен тест без кусков.

Один упакованный кусок вмещает 27 тритов, поэтому широкому нейрону нужно несколько. neuronN в bitnet_neuron_nchunk.t27 принимает до 8 кусков активаций и весов, складывает dot27 для nchunks пар, начиная с куска 0, и квантует сумму. Браузер пропускает все 11 тестов, потому что пока не знает assert_eq; нативный t27c запускает все 11, все проходят, ни один не пустой. Запись меняет границу цикла в строке 36 с nchunks на 1, и нейрон всегда читает ровно один кусок. Остальные тесты нейрона заполняют все куски одинаково, и одного куска хватает, чтобы оказаться по ту же сторону порога. Падает ровно один тест, neuron_zero_chunks, который не даёт ни одного куска и ждёт Z. Каждый байт в записи напечатала команда; инсценирован только набор текста.

Попробуйте

Найдите в записи строку цикла до и после изменения; затем найдите в спеке neuron_two_chunks и объясните, почему та же ошибка его не ломает.

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

t27c on bitnet_neuron_nchunk.t27 -- A neuron over N chunks, native
t27c on bitnet_neuron_nchunk.t27 -- A neuron over N chunks, native ↗

t27c on the t27c lab (Railway), spec at t27 5ff0ec512: 11 tests pass natively; stopping the chunk loop after one chunk fails exactly one test, neuron_zero_chunks; git restores the spec.

specs/ternary/bitnet_neuron_nchunk.t27

module BitnetNeuronN;
// Sign-only ternary multiply of two packed trits {N=0b00, Z=0b01, P=0b10}.
fn tmul(ta: u8, tb: u8) -> i8 {
    if (ta == 1) { return 0; }
    if (tb == 1) { return 0; }
    if (ta == tb) { return 1; }
    return -1;
}
// 27-trit ternary dot product of two 54-bit packed vectors, via a real loop
// (gen-verilog local-decl hoist, #1741). Result in [-27, +27].
fn dot27(a: u64, b: u64) -> i16 {
    var acc : i16 = 0;
    var i : u32 = 0;
    while (i < 27) {
        var ta : u8 = ((a >> (i << 1)) & 3) as u8;
        var tb : u8 = ((b >> (i << 1)) & 3) as u8;
        acc = acc + tmul(ta, tb) as i16;
        i = i + 1;
    }
    return acc;
}
// Ternary activation re-quantizer: v > +t -> P(2), v < -t -> N(0), else Z(1).
fn quantize(v: i16, threshold: i16) -> u8 {
    if (v > threshold) { return 2; }
    if (v < -threshold) { return 0; }
    return 1;
}
// A BitNet neuron over an arbitrary chunk count: loop the ternary dot product
// across the first `nchunks` (activation, weight) chunk pairs of the packed
// arrays, then re-ternarize with the threshold. Packed-array element indexing
// (#1748) and the loop (#1741) make this fully parameterized. Cross-checked
// against a reference over random packed inputs in tests/bitnet_neuron_nchunk.rs.
pub fn neuronN(acts: [8]u64, weights: [8]u64, nchunks: u32, threshold: i16) -> u8 {
    var acc : i16 = 0;
    var c : u32 = 0;
    while (c < nchunks) {
        acc = acc + dot27(acts[c], weights[c]);
        c = c + 1;
    }
    return quantize(acc, threshold);
}
test dot27_all_n { assert_eq(dot27(0, 0), 27); }
test dot27_all_p_x_n { assert_eq(dot27(12009599006321322, 0), -27); }
test dot27_all_z { assert_eq(dot27(6004799503160661, 6004799503160661), 0); }
test quantize_pos { assert_eq(quantize(100, 10), 2); }
test quantize_neg { assert_eq(quantize(-100, 10), 0); }
test quantize_band { assert_eq(quantize(5, 10), 1); }
// Full-neuron coverage over packed-array inputs. Chunk constants: P (+1 lanes) =
// 12009599006321322, N (-1 lanes) = 0, Z (0 lanes) = 6004799503160661. Uses the
// [N]Type{...} array-literal syntax.
test neuron_all_p_x_p { assert_eq(neuronN([8]u64{12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322}, [8]u64{12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322}, 8, 10), 2); }
test neuron_all_p_x_n { assert_eq(neuronN([8]u64{12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 0, 0, 0, 0}, [8]u64{0, 0, 0, 0, 0, 0, 0, 0}, 4, 10), 0); }
test neuron_all_z { assert_eq(neuronN([8]u64{6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661}, [8]u64{6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661, 6004799503160661}, 8, 10), 1); }
test neuron_two_chunks { assert_eq(neuronN([8]u64{12009599006321322, 12009599006321322, 0, 0, 0, 0, 0, 0}, [8]u64{12009599006321322, 12009599006321322, 0, 0, 0, 0, 0, 0}, 2, 10), 2); }
test neuron_zero_chunks { assert_eq(neuronN([8]u64{12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322}, [8]u64{12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322, 12009599006321322}, 0, 10), 1); }
// W699: the hardware boundary, with a width the EMITTER can produce.
//
// W698 accepted `[8]u64` while the port emitter still sized entry ports with
// `type_to_width`, whose last arm is `_ => 32`. This parameter became
// `input wire [31:0]` -- a silent 16x narrowing, and the banner, the census,
// the corpus column and yosys all reported success. It was retracted the same
// wave.
//
// W699 replaced that call with `entry_port_width`, which returns None rather
// than a plausible number and makes the entry point refuse LOUDLY in the
// generated source. Verified end to end: this parameter now emits
// `input wire [511:0]`, and `on_comb` and the function it forwards to both
// take [511:0], so nothing is truncated between the boundary and the body.
fn on_comb(acts: [8]u64, weights: [8]u64, nchunks: u32, threshold: i16) -> u8 { return neuronN(acts, weights, nchunks, threshold); }

endmodule

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

Все уроки