Нейрон по кускам
Вы узнаете
Как нейрон с числом входов больше 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 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
Все уроки
Модуль 1 · Лаборатория: наши исследования
Свой формат чисел, честная таблица результатов и таблицы модели, перемноженные на плате.
Модуль 2 · ИИ-числа: блок MX
Как ИИ-чипы хранят веса в нескольких битах: один общий масштаб на блок, сам байт масштаба и что один выброс делает с соседями.
Модуль 3 · Тернарные веса
Веса, которые бывают только минус масштаб, ноль или плюс масштаб, пять правил, которые должен пройти тернарный алфавит, и прогон тестов, который ничего не проверил.
Модуль 4 · Тернарный формат Ternary Network Float
Правило, которое компилятор проверяет до запуска любого теста, и 17-битное число с плавающей точкой, порядок которого — четыре сбалансированных трита.
Модуль 5 · Арифметика чисел со знаком
Умножить два числа со знаком, сложить их, когда знаки разные, и сделать то и другое сразу в умножении с накоплением.
Модуль 6 · Части нейрона
ReLU с изломом в нуле, степень двойки для softmax и argmax, который называет ответ.
Модуль 7 · Обучение на ошибке
Потеря, которая оценивает неверную догадку в битах, один шаг, который сдвигает вес против градиента, и скрытый слой, который нужен XOR.
Модуль 8 · BitNet: тернарные сети
Порог, который сжимает сумму обратно до трёх значений, один нейрон, который становится другой функцией при смене весов, и нейрон, который читает входы по 27 тритов за раз.
Модуль 9 · Тернарный MAC как чип
Скалярное произведение 27 тритов как провода без регистра, та же сумма, которую регистр копит на каждом такте, и небольшая целая сеть в конце курса.