Целая сеть
Вы узнаете
Как части этого курса складываются в одну небольшую сеть и что её тесты и её Verilog ещё оставляют открытым.
mlp2 в bitnet_mlp.t27 — целая сеть: 3 нейрона читают вход, pack3 упаковывает их 3 трита в скрытый кусок, и 2 нейрона превращают его в 2 выходных трита. Курс прошёл от форматов чисел и арифметики к нейрону, обучению, тернарным весам и чипу; здесь есть нейрон, веса и вход on_comb, а обучения нет. Нативный t27c запускает все 4 теста, все проходят, ни один не пустой; браузер их пропускает, и ни один не вызывает mlp2. Сгенерированный Verilog помечает цикл по кускам в neuronN как NOT UNROLLED, и его примечание говорит, что yosys такое отвергает, так что это ещё не чип. Запись переносит третий скрытый трит из бита 4 в бит 3 в строке 39, и падает ровно один тест, pack3_zzz. Каждый байт в записи напечатала команда; инсценирован только набор текста.
Попробуйте
Найдите в записи строку pack3 до и после изменения; затем откройте в спеке вкладку Code, выберите verilog и найдите в neuronN цикл с пометкой NOT UNROLLED.

t27c on the t27c lab (Railway), spec at t27 5ff0ec512: 4 tests pass natively; pack3 writing its third trit one bit low fails exactly one test, pack3_zzz; git restores the spec.
specs/ternary/bitnet_mlp.t27
module BitnetMlp;
fn tmul(ta: u8, tb: u8) -> i8 {
if (ta == 1) { return 0; }
if (tb == 1) { return 0; }
if (ta == tb) { return 1; }
return -1;
}
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;
}
fn quantize(v: i16, threshold: i16) -> u8 {
if (v > threshold) { return 2; }
if (v < -threshold) { return 0; }
return 1;
}
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);
}
fn neuron1(act: u64, weight: u64, threshold: i16) -> u8 {
return quantize(dot27(act, weight), threshold);
}
fn pack3(t0: u8, t1: u8, t2: u8) -> u64 {
var z : u64 = 6004799503160661;
var cleared : u64 = z & 18446744073709551552;
return cleared | (t0 as u64) | ((t1 as u64) << 2) | ((t2 as u64) << 4);
}
// 2-layer BitNet inference. Layer 1: 3 neurons over the input activations
// (l1chunks chunks) -> 3 trits packed into one hidden chunk. Layer 2: 2
// single-chunk neurons over that hidden chunk -> 2 packed output trits.
pub fn mlp2(acts: [8]u64, wa0: [8]u64, wa1: [8]u64, wa2: [8]u64, wb0: u64, wb1: u64, l1chunks: u32, threshold: i16) -> u8 {
var t0 : u8 = neuronN(acts, wa0, l1chunks, threshold);
var t1 : u8 = neuronN(acts, wa1, l1chunks, threshold);
var t2 : u8 = neuronN(acts, wa2, l1chunks, threshold);
var h : u64 = pack3(t0, t1, t2);
var o0 : u8 = neuron1(h, wb0, threshold);
var o1 : u8 = neuron1(h, wb1, threshold);
return (o1 << 2) | o0;
}
test dot27_all_n { assert_eq(dot27(0, 0), 27); }
test neuron1_p_p { assert_eq(neuron1(12009599006321322, 12009599006321322, 10), 2); }
test pack3_zzz { assert_eq(pack3(1, 1, 1), 6004799503160661); }
test quantize_band { assert_eq(quantize(5, 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, wa0: [8]u64, wa1: [8]u64, wa2: [8]u64, wb0: u64, wb1: u64, l1chunks: u32, threshold: i16) -> u8 { return mlp2(acts, wa0, wa1, wa2, wb0, wb1, l1chunks, threshold); }
endmodule
Все уроки
Модуль 1 · Лаборатория: наши исследования
Свой формат чисел, честная таблица результатов и таблицы модели, перемноженные на плате.
Модуль 2 · ИИ-числа: блок MX
Как ИИ-чипы хранят веса в нескольких битах: один общий масштаб на блок, сам байт масштаба и что один выброс делает с соседями.
Модуль 3 · Тернарные веса
Веса, которые бывают только минус масштаб, ноль или плюс масштаб, пять правил, которые должен пройти тернарный алфавит, и прогон тестов, который ничего не проверил.
Модуль 4 · Тернарный формат Ternary Network Float
Правило, которое компилятор проверяет до запуска любого теста, и 17-битное число с плавающей точкой, порядок которого — четыре сбалансированных трита.
Модуль 5 · Арифметика чисел со знаком
Умножить два числа со знаком, сложить их, когда знаки разные, и сделать то и другое сразу в умножении с накоплением.
Модуль 6 · Части нейрона
ReLU с изломом в нуле, степень двойки для softmax и argmax, который называет ответ.
Модуль 7 · Обучение на ошибке
Потеря, которая оценивает неверную догадку в битах, один шаг, который сдвигает вес против градиента, и скрытый слой, который нужен XOR.
Модуль 8 · BitNet: тернарные сети
Порог, который сжимает сумму обратно до трёх значений, один нейрон, который становится другой функцией при смене весов, и нейрон, который читает входы по 27 тритов за раз.
Модуль 9 · Тернарный MAC как чип
Скалярное произведение 27 тритов как провода без регистра, та же сумма, которую регистр копит на каждом такте, и небольшая целая сеть в конце курса.