Блог
[измерено на FPGA: все 42 обученные матрицы, проверена каждая квитанция; активации — тестовые векторы, а не прямой проход] AX7203 посчитала 28 416 из 28 416 строк матриц весов обученной модели tern_tc бит-точно, а её харнесс сообщил о 284 160 аутентифицированных квитанциях, не сравнив ни одного тега. Исправленный харнесс падает на семи видах неисправной ячейки. На плате он затем проверил все 403 200 квитанций по всем 42 тернарным матрицам, 33 792 из 33 792 строк бит-точно, и ещё 51 840 для w_down с int8-активациями. Первые длинные прогоны остановились, когда канал UART терял байты при 64 задачах в полёте. Проверка, зарегистрированная до запуска, поместила порог потерь на приёмный буфер USB-моста в 512 байт, а при 24 задачах в полёте не потерялось ни одного байта.
Модель IGLA размером с плату, tern_tc, — это 9,08M параметров. Из них 6 451 200 — тернарные веса блоков: 42 матрицы в шести слоях. Модель обучена на 2,0 млрд токенов кода до 0,7613 бит на байт на валидации. Плата — ALINX AX7203 (XC7A200T) с ячейкой узла TRI-NET, собранной открытым тулчейном openXC7. Ячейка считает тернарное скалярное произведение на 32 трита без умножителя и без DSP48. На каждую задачу она отвечает результатом, nonce задачи, id узла и тегом SipHash-2-4 по всему этому. Хост режет каждую строку шириной 320 на десять задач по 32 трита и складывает ответы.
| Прогон (AX7203, 1 144 744 бод) | Задач | Строк бит-точно | Теги квитанций сверялись? | Время |
|---|---|---|---|---|
| Случайный тернарный matvec 320x320 | 3 200 | 320 / 320 | да | 0,67 с |
| Слой 0, wq, обученные веса | 12 800 | 1 280 / 1 280 | нет | 2,7 с |
| wq, wo, gate, up во всех шести слоях | 284 160 | 28 416 / 28 416 | нет | 62,4 с |
Активации были тернарными векторами, а не int8-активациями модели. Это показывает, что обученные матрицы весов считаются точно. Прямым проходом модели это не является.
Харнесс слоя собирал прообраз каждой квитанции и выбрасывал его, а загруженный ключ не использовался вовсе. Аутентификацией он считал байт статуса 0x01. Возврат nonce он не проверял и сравнивал только суммы строк, так что две ошибки в кусках могли взаимно погаситься. Мы подключили к нему программную модель ячейки, которая подписывает каждый ответ ключом, которого у хоста нет. Он напечатал «receipts authenticated 160/160» и PASS.
Что остаётся в силе: каждое значение строк в таблице совпало с CPU-оракулом, а прогон случайной матрицы свои теги сверял. Что отозвано: «284 160 квитанций аутентифицировано под ключом node0», потому что эти квитанции никто не проверял. С тех пор исправленный харнесс измерил на плате все 42 матрицы, включая эти 24, с проверкой каждой квитанции (ниже).
Теперь ответ засчитывается, только если выполнены все пять условий. Статус равен 0x01. Nonce выдан этим прогоном, и ответ на него пришёл один раз. Id узла совпадает с первым ответом. Тег SipHash пересчитывается под ключом. y равен скалярному произведению этого куска. Строка проходит, только если прошли все её куски и их сумма равна скалярному произведению строки, посчитанному прямо по int8-весам модели, а не по упакованным байтам провода. Для каждой проверки показано, что она умеет падать:
| Что ячейка делает не так | Вердикт харнесса |
|---|---|
| подписывает чужим ключом | отвергнуто: tag |
| отдаёт неверный ответ с корректной подписью | отвергнуто: lie |
| портит один бит тега | отвергнуто: tag |
| отвечает nonce, которого прогон не выдавал | отвергнуто: fabricated |
| отвечает от имени другого узла | отвергнуто: node |
| теряет ответ | отвергнуто: short read, прогон останавливается |
| ключ так и не установлен (статус 0x04) | не засчитано, хотя y верный |
После прогона на плате, описанного ниже, добавлена восьмая проверка, уже для канала, а не для ячейки. Это поток, из середины которого пропадают 16 байт, как было в канале платы. Харнесс засчитывает ответы до дыры, отвергает повреждённый ответ и останавливается.
Собственные исходники ячейки, trinet_node_core.v и trinet_siphash24.v, без изменений, симулированы в Icarus Verilog на уровне битов UART. Ключ ставился по проводу ровно так же, как на плате. Веса — случайные тернарные значения точно в формах tern_tc, потому что файла обученной модели в этом окружении нет.
| Прогон RTL | Задач | Строк бит-точно | Квитанций проверено |
|---|---|---|---|
| Слой 0, все семь матриц, w_down по 27 кускам | 67 200 | 5 632 / 5 632 | 67 200 / 67 200 |
| Слой 5, w_down, int8-активации (6 разрядов) | 51 840 | 320 / 320 | 51 840 / 51 840 |
| Слой 0, wk, ключ не установлен | 640 | 0 / 64 | 0 / 640 (статус 0x04) |
| Слой 0, wk, проверка под другим ключом | 1 280 | 0 / 128 | 0 / 1 280 |
27 сентября 2026 года исправленный харнесс прогнали на AX7203 с файлом обученной модели. Хостом был M1 Pro, связанный с UART платы (CP2102N) через USB-хаб. Векторы x — случайные тестовые векторы из фиксированного seed, а не настоящие активации модели.
| Прогон на плате (1 144 744 бод) | Задач в полёте | Квитанций проверено | Строк бит-точно | Итог |
|---|---|---|---|---|
| Случайный тернарный matvec 320x320 | 64 | 3 200 / 3 200 | 320 / 320 | PASS, 0,79 с |
| Все 42 матрицы, тернарный x | 64 | 18 984 из 403 200, потом канал потерял 16 байт | 1 898 / 33 792 | FAIL, остановлен |
| Слой 5, w_down, int8 x | 64 | 9 886 из 51 840, потом канал потерял 4 байта | 61 / 320 | FAIL, остановлен |
| Слой 5, w_down, int8 x | 8 | 51 840 / 51 840 | 320 / 320 | PASS, 11,9 с |
| Все 42 матрицы, тернарный x | 24 | 403 200 / 403 200 | 33 792 / 33 792 | PASS, 85,5 с |
| Все 42 матрицы, тернарный x (контроль) | 64 | 6 679 из 403 200, потом канал потерял 59 байт | 667 / 33 792 | FAIL, остановлен |
| Все 42 матрицы, тернарный x (зарегистрирован) | 26 | 403 200 / 403 200 | 33 792 / 33 792 | PASS, 85,3 с |
| Все 42 матрицы, тернарный x (зарегистрирован) | 30 | 149 986 из 403 200, потом канал потерял 7 байт | 12 822 / 33 792 | FAIL, остановлен |
Строка с окном 24 — результат, которого ждал этот пост. Все 42 тернарные матрицы обученной модели, все 6 451 200 весов, отработали на текущем битстриме. Хост проверил каждую из 403 200 квитанций: статус, nonce, id узла, тег SipHash, пересчитанный под ключом, и y. Все 33 792 строки совпали с оракулом по int8-весам. Прогон занял 85,5 с, 4 716 ответов в секунду.
Прогон w_down при 8 задачах в полёте — первый прогон обученных весов с int8-активациями на плате, в котором проверена каждая квитанция: 51 840 тегов и 320 строк. Это одна матрица из 42 и один случайный вектор активаций.
Все четыре провала — не неверные ответы. У всех 185 535 ответов, пришедших целыми до дыры, был верный y и сходящийся тег. Ответ, который рвала каждая дыра, не прошёл проверку и был отвергнут. Сломался канал. Из потока ответов пропадало 16, 4, 59 и 7 байт, и по обе стороны дыры байты целые. Ячейка такую картину дать не может: каждый ответ она отправляет целиком из одного буфера. Харнесс сделал то, что должен: остановился на первом нераспознанном кадре и после него ничего не засчитал.
Окно имеет значение, и это показал контроль. Тот же полный прогон на том же харнессе, установке и сессии прошёл чисто при 24 задачах в полёте и потерял байты после 6 744 задач при 64. Значит, успех дало окно, а не замер времени и вывод hex, добавленные в новый харнесс. При 64 три длинных прогона сорвались три раза.
Первое объяснение контроль не выдержало. Предполагалось, что ответы копятся, пока процесс харнесса отвлёкся. Исправленный харнесс эти паузы измеряет. В провальном контроле при 64 самая долгая была 4,2 мс, примерно за 1 700 ответов до дыры. Успешные прогоны пережили паузы 22,4 мс при 24 задачах в полёте и 25,8 мс при 26.
Зато подходит USB-мост. UART платы идёт к хосту через CP2102N; по его даташиту у него приёмный буфер на 512 байт, а выше 1 Мбод нужно аппаратное управление потоком, иначе приёмник переполняется. Узел работает на 1 144 744 бод, и линий управления потоком у него нет, так что придержать его нечем. При W задачах в полёте к хосту идёт не больше 19 x W байт ответов. Следующую проверку зарегистрировали до запуска: при 26 задачах в полёте (494 байта) — успех, при 30 (570 байт) — срыв. Так и вышло. Окно 26 провело все 403 200 задач, окно 30 потеряло 7 байт после 150 017 задач. Порог потерь лежит между 494 и 570 байтами, около буфера на 512.
Одно побочное предсказание не сбылось. Срыв при 30 пришёл после 150 017 задач, а не в первые 20 000, как при 64, так что задержки, заполняющие буфер, реже, чем объяснил бы один фиксированный простой. Что задерживает USB-передачи моста, не измерено, и хаб не исключён. Правило харнесса теперь — держать 19 x W меньше 512 байт, поэтому по умолчанию окно 24 (456 байт). Скорости это заметно не стоит: 4 716 ответов в секунду против примерно 4 660–4 750 при 64 до сбоев.
w_down пропустили, потому что у неё вход шириной 864, и по плану для неё нужна была ячейка шире. Не нужна: 864 = 27 x 32, то есть это 27 задач на строку на той же ячейке. У wk и wv вход шириной 320, их просто пропустили. Вместе это все 42 тернарные матрицы и все 6 451 200 весов на текущем битстриме.
int8-активации числились за Stage B.3, где планировался новый RTL. Любое целое от -364 до 364 — это сумма шести сбалансированно-троичных разрядов: q = сумма 3^k d_k, где каждый d_k из {-1, 0, +1}. Значит, w.q = сумма 3^k (w.d_k), и каждое w.d_k — обычная тернарная задача. Шесть задач на кусок заменяют новый тракт данных. Ячейка остаётся тернарной, а степени тройки хост применяет открыто.
Поработаем вместе
Я аудирую RTL и строю независимые побитово точные модели, затем провожу результат через синтез и, когда это полезно, проверяю на плате Artix-7. Первый модуль проверки — бесплатно.