Зачем вообще париться с самодельным NPU в 2026?
Ты видел цены на Intel AI Max 395? Или на NVIDIA H200? Правильно, они космические. А теперь посмотри на свой домашний сервер - там наверняка валяется парочка FPGA-плат или даже ASIC-прототипов. Вот именно для этого и нужен Tiny-NPU.
Это не очередной "демо-проект" с мигающими светодиодами. Tiny-NPU - полностью синтезируемый дизайн на SystemVerilog, который реально ускоряет матричные умножения, сердце любых современных LLM вроде GPT-4, LLaMA 3 или Qwen3. И да, он открытый. Весь код, вся документация, все тесты - на GitHub.
Важно: Tiny-NPU работает с актуальными на февраль 2026 моделями, включая LLaMA 3.2 и Qwen3-8B. Не пытайся запихнуть сюда GPT-5 - для этого нужны другие архитектуры.
Что внутри: архитектура systolic array
Systolic array - это не магия, а просто умная организация вычислений. Представь себе конвейер на заводе: каждая операция выполняется на своем участке, данные текут как вода. В Tiny-NPU используется классическая 8x8 systolic array с поддержкой INT8 и FP16.
| Компонент | Назначение | Особенности |
|---|---|---|
| Matrix Engine | Systolic array 8x8 | Поддержка INT8/FP16, 256 MAC/cycle |
| Vector Unit | Активации, нормализация | GELU, LayerNorm, Softmax |
| Memory Controller | Управление памятью | DDR4/LPDDR4, 128-bit шина |
| DMA Engine | Перемещение данных | Scatter-gather, 2D transfers |
Почему именно systolic array? Потому что это эффективно. Данные переиспользуются, минимизируются перемещения, а энергопотребление падает в разы по сравнению с классическими подходами. Если ты когда-то пробовал запускать Mistral на Intel NPU, то поймешь разницу.
Собираем окружение: что нужно перед началом
1 Установка инструментов
SystemVerilog - не Python. Тут нужны серьезные инструменты. Минимальный набор:
- Verilator 5.0+ (симулятор)
- Yosys 0.24+ (синтез)
- GTKWave (анализ временных диаграмм)
- Python 3.11+ с NumPy
- CMake 3.20+
# Установка на Ubuntu 24.04+
sudo apt-get install verilator yosys gtkwave cmake
pip install numpy torch --index-url https://download.pytorch.org/whl/cpu
2 Клонируем репозиторий
git clone https://github.com/tiny-npu/tiny-npu.git
cd tiny-npu
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
Вот и все. Теперь у тебя есть симулятор Tiny-NPU. Но это только начало.
Архитектура глазами: разбираем ключевые модули
Открой файл src/matrix_engine.sv. Это сердце NPU. Посмотри на этот код:
module systolic_array #(
parameter int WIDTH = 8,
parameter int HEIGHT = 8
) (
input logic clk,
input logic rst_n,
input logic [WIDTH-1:0][7:0] a_in,
input logic [HEIGHT-1:0][7:0] b_in,
output logic [WIDTH-1:0][HEIGHT-1:0][15:0] c_out
);
logic [WIDTH-1:0][HEIGHT-1:0][7:0] a_reg, b_reg;
logic [WIDTH-1:0][HEIGHT-1:0][15:0] c_reg;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a_reg <= '0;
b_reg <= '0;
c_reg <= '0;
end else begin
// Распространение данных по массиву
for (int i = 0; i < WIDTH; i++) begin
for (int j = 0; j < HEIGHT; j++) begin
if (i == 0) a_reg[i][j] <= a_in[j];
else a_reg[i][j] <= a_reg[i-1][j];
if (j == 0) b_reg[i][j] <= b_in[i];
else b_reg[i][j] <= b_reg[i][j-1];
// MAC операция
c_reg[i][j] <= c_reg[i][j] + (a_reg[i][j] * b_reg[i][j]);
end
end
end
end
assign c_out = c_reg;
endmodule
Видишь эти вложенные циклы? Это и есть systolic array. Данные "текут" слева направо и сверху вниз, умножаясь и складываясь на каждом PE (Processing Element).
Ошибка новичка: пытаться оптимизировать каждый PE отдельно. Работай с массивом как с целым - так эффективнее.
3 Тестируем базовую функциональность
# Запускаем unit-тесты
./test/unit_tests --gtest_filter="*SystolicArray*"
# Запускаем симуляцию с тестовыми векторами
./sim/tiny_npu_sim -c config/test.cfg -i data/test_input.bin
Если все зеленое - можно двигаться дальше. Если нет - смотри логи. Скорее всего, проблема с инициализацией памяти или тактированием.
Интеграция с реальными моделями: от матриц к LLM
Матричные умножения - это хорошо, но как заставить работать целую модель? Вот тут начинается магия.
Tiny-NPU использует совместимый с llama.cpp формат весов. Конвертируем любую модель:
# Конвертируем LLaMA 3.2 4B в формат Tiny-NPU
python tools/convert_model.py \
--model meta-llama/Llama-3.2-4B \
--output models/llama-3.2-4b-tiny.bin \
--quantize int8
А теперь самое интересное - запуск инференса:
#!/usr/bin/env python3
import numpy as np
from tiny_npu import TinyNPU
# Инициализируем NPU
npu = TinyNPU(config='config/llama.cfg')
npu.load_model('models/llama-3.2-4b-tiny.bin')
# Запускаем генерацию
prompt = "Кто написал 'Войну и мир'?"
tokens = np.array([npu.tokenizer.encode(prompt)], dtype=np.int32)
for i in range(100):
logits = npu.forward(tokens)
next_token = np.argmax(logits[:, -1, :], axis=-1)
tokens = np.concatenate([tokens, next_token], axis=-1)
if next_token[0] == npu.tokenizer.eos_token_id:
break
print(npu.tokenizer.decode(tokens[0]))
Да, это работает. Нет, это не так быстро, как AI Max 395. Но это твое. И ты понимаешь каждый такт работы.
Оптимизации: заставляем летать
Базовая версия дает 2-3 токена в секунду на LLaMA 3.2 4B. Это смешно. Давай исправим.
1. Пайплайнинг операций
Пока systolic array умножает матрицы, vector unit должен готовить следующие данные. Добавляем буферы и предвыборку:
// Добавляем в matrix_engine.sv
logic [2:0][7:0] prefetch_buffer;
always_ff @(posedge clk) begin
if (!busy) begin
prefetch_buffer[0] <= mem_read(addr_a);
prefetch_buffer[1] <= mem_read(addr_b);
prefetch_buffer[2] <= mem_read(addr_c);
end
end
2. Квантование на лету
Храним веса в INT4, вычисляем в INT8. Экономия памяти в 2 раза:
# В конвертере моделей
weights_int4 = (weights_fp16 * 127 / max_weight).astype(np.int8)
weights_int4 = np.right_shift(weights_int4, 4) # INT8 -> INT4
3. Батчинг
Один из самых эффективных трюков. Обрабатываем несколько запросов одновременно:
// Поддержка batch size = 4
input logic [3:0][7:0] a_in_batch;
input logic [3:0][7:0] b_in_batch;
// Каждый PE теперь обрабатывает 4 операции параллельно
always_comb begin
for (int b = 0; b < 4; b++) begin
c_acc[b] = a_in_batch[b] * b_in_batch[b];
end
end
После этих оптимизаций получаем 15-20 токенов в секунду. Уже что-то. Для сравнения: llama.cpp на CPU дает похожие цифры на хорошем процессоре.
Развертывание на реальном железе
Симуляция - это хорошо, но настоящее веселье начинается на FPGA. Вот как залить Tiny-NPU на Xilinx Zynq:
# Генерация битстрима
make fpga BOARD=zynq7020
# Загрузка на плату
scp bitstream/tiny_npu.bit root@fpga:/boot/
ssh root@fpga "fpgautil -b /boot/tiny_npu.bit"
# Запуск драйвера
ssh root@fpga "insmod driver/tiny_npu.ko"
Теперь у тебя есть настоящий NPU, подключенный через AXI к ARM-процессору. Можно запускать модели прямо на железе.
Чего не хватает и куда развивать
Tiny-NPU - это база. Проект открыт для контрибьютеров. Вот что нужно доделать:
- Поддержка sparse матриц (60% весов в LLM - нули)
- Аппаратная реализация attention механизма
- Поддержка mixed precision (FP8, BF16)
- Интеграция с vLLM-подобным окружением
- Драйверы для OpenVINO и других фреймворков
FAQ: частые вопросы и ошибки
Почему такая низкая производительность по сравнению с коммерческими NPU?
Потому что у них systolic array 64x64 или 128x128, а у нас 8x8. Плюс они используют HBM3 память с пропускной способностью 1TB/s. Но наш дизайн потребляет в 100 раз меньше энергии.
Можно ли запустить Tiny-NPU на GPU через Verilator?
Технически да, но смысла мало. Verilator компилирует RTL в C++, который можно скомпилировать CUDA. Но производительность будет хуже, чем у нативного CUDA кода.
Какой следующий шаг после освоения Tiny-NPU?
Изучи архитектуру коммерческих NPU, попробуй добавить поддержку новых инструкций, оптимизируй memory hierarchy. Или возглавь разработку Tiny-NPU 2.0 с array 16x16.
Итог: зачем все это было нужно
Потому что понимать, как работает железо под капотом LLM - это суперсила в 2026. Когда все бегут за очередным API вызовом к GPT-7, ты можешь собрать свой ускоритель. Медленный, неэффективный, но свой.
И кто знает - может именно твой форк Tiny-NPU станет основой для следующего прорыва в аппаратном ускорении AI. Или просто поможет понять, почему оптимизация LLM - это не только про software.
Код ждет. Плата ждет. Время собирать свой NPU.