STM32 з нуля без HAL: Я змусив Luckfox думати: NPU, RetinaFace і перший inference. Частина 9
Серія «STM32 з нуля без HAL» · Частина 9 · Місяць 3
Колись GPU була лише іграшкою для геймерів. Сьогодні без них не існує сучасного AI.
А тепер з’явився ще один клас процесорів — NPU. Вузькоспеціалізовані чіпи, які роблять тільки одну річ: шалено швидко множать матриці.
І парадокс у тому, що цього виявилось достатньо, щоб запускати нейромережі прямо на tiny edge-платах з кількома ватами споживання.
RV1106 якраз має такий блок — RKNPU від Rockchip.
Третій місяць добігає кінця. Ми вже пройшли Device Tree, UART між Linux і STM32, налаштували GStreamer і підняли стрімінг з Luckfox. Залишився останній великий шматок пазла — NPU.
◆ Що таке NPU і навіщо він потрібен
CPU вміє робити все, але дорого з точки зору енергії і часу. Нейромережа — це мільйони операцій множення матриць. CPU робить їх послідовно, витрачаючи такти і енергію.
NPU (Neural Processing Unit) — це спеціалізований блок який робить тільки одне: матричні операції. Швидко і з мінімальним енергоспоживанням. Саме тому його ставлять у смартфони, відеокамери, і embedded плати для edge AI.
На RV1106 стоїть RKNPU від Rockchip. Перевіряємо що він живий:
dmesg | grep -i npu [ 3.441191] RKNPU ff660000.npu: RKNPU: rknpu iommu device-tree entry not found!, using non-iommu mode [ 3.441685] RKNPU ff660000.npu: RKNPU: Initialized RKNPU driver: v0.9.2 for 20230825 [ 3.441774] RKNPU ff660000.npu: dev_pm_opp_set_regulators: no regulator (rknpu) found: -19
Драйвер версії v0.9.2 від /dev/rknpu є. Але це тільки ядро — userspace частина інша історія.
◆ Знаходимо частини пазлу
Для роботи з NPU потрібні три речі: драйвер (є), runtime бібліотека (librknnmrt.so), і модель (.rknn файл). Шукаємо:
find / -name "librknnmrt*" 2>/dev/null /oem/usr/lib/librknnmrt.so
Є — але не там де очікуєш. Стандартний /usr/lib порожній. Бібліотека тут /oem/usr/lib.
⚠ Якщо будеш шукати через
strings /usr/lib/librknnmrt.so— отримаєшNo such file or directory. Не лякайся, шукай в/oem/usr/lib.
Перевіряємо версію:
strings /oem/usr/lib/librknnmrt.so | grep "librknnmrt version" librknnmrt version: 1.6.0 (2de554906@2024-01-17T14:53:41)
І одразу бачимо в тих самих рядках, щось не так:
please try updating to the latest version of the toolkit2 and runtime E RKNN: incompatible model version E RKNN: Mismatch driver version
Rockchip вбудував попередження про версійний конфлікт прямо в бібліотеку, натяк — версії драйвера і runtime мають збігатись. Запам’ятовуємо: драйвер v0.9.2, runtime v1.6.0.
Чи знайде її лінкер? Перевіряємо:
echo $LD_LIBRARY_PATH /oem/usr/lib:/oem/lib:
Пощастило — наш кастомний Buildroot образ вже має /oem/usr/lib в LD_LIBRARY_PATH. Нічого додатково налаштовувати не треба.
Шукаємо моделі:
find / -name "*.rknn" 2>/dev/null
Порожньо. Моделей на платі немає. Вони є в SDK на PC ми ще там поколупаємось.
◆ Перша спроба — rksmartdoor
В SDK Luckfox є папка project/app/rk_smart_door. Там лежать готові .rknn моделі: detection.rknn, recognition.rknn, headpose.rknn, fas_ir.rknn. Здається — ось воно, бери і запускай.
find ~/luckfox-pico/project/app/rk_smart_door -name "*.rknn" 2>/dev/null .../algo/models/detection.rknn .../algo/models/recognition.rknn .../algo/models/headpose.rknn .../algo/models/fas_ir.rknn
Відкриваємо CMakeLists.txt і бачимо:
target_link_libraries(${PROJECT_NAME}
libpthread.a librt.a librockit.a librockchip_mpp.a librkaiq.a
librtsp.a librkaudio_detect.a libaec_bf_process.a libm.a
librockiva.a libstdc++.a librknnmrt.a librga.a libsmartIr.a)
⚠
rk_smart_door— це повноцінний продакшн додаток для розумного дверного замка. З UVC, ISP pipeline, RTSP стрімінгом, аудіо детекцією і SmartIR підсвічуванням. 10+ залежностей, збірка тільки в контексті повного SDK build.
Нам не потрібен готовий дверний замок ми зробимо свій. Зараз нам потрібен лише мінімальний inference. Йдемо далі.
◆ Просто — luckfoxpicorknn_example
Rockchip і LuckfoxTECH мають окремий репозиторій з мінімальними прикладами:
git clone https://github.com/LuckfoxTECH/luckfox_pico_rknn_example ls luckfox_pico_rknn_example/example/ luckfox_pico_retinaface_facenet luckfox_pico_retinaface_facenet_spidev luckfox_pico_yolov5
Три приклади. Нас цікавить перший — RetinaFace + FaceNet. RetinaFace детектує обличчя (знаходить bounding box), FaceNet розпізнає (порівнює з еталоном).
Перевіряємо версію бібліотеки в репо:
strings luckfox_pico_rknn_example/lib/uclibc/librknnmrt.so | grep "librknnmrt version" librknnmrt version: 1.6.0 (9a7b5d24c@2023-12-13T17:33:10)
⚠ Версія
1.6.0збігається з платою. Але білди різні: репо2023-12-13, плата2024-01-17. Той самий мажорний реліз але дати різниці. Запам’ятовуємо як потенційне джерело проблем — перевіримо при запуску.
◆ Збираємо
CMakeLists бере toolchain з змінної оточення LUCKFOX_SDK_PATH. Встановлюємо і збираємо:
export LUCKFOX_SDK_PATH=/home/alex/luckfox-pico cd luckfox_pico_rknn_example ./build.sh
Скрипт запитає інтерактивно:
uclibcабоglibc→ вибираємо 1 (uclibc — наш Buildroot образ)- який приклад → вибираємо 1 (
luckfox_pico_retinaface_facenet)
[100%] Built target luckfox_pico_retinaface_facenet -- Installing: .../install/uclibc/luckfox_pico_retinaface_facenet_demo/luckfox_pico_retinaface_facenet -- Up-to-date: .../model/RetinaFace.rknn -- Up-to-date: .../model/mobilefacenet.rknn -- Up-to-date: .../model/test.jpg -- Up-to-date: .../lib/librga.so
Зібралось з першого разу. Якщо цікаво більше то порівняй з rk_smart_door де треба весь SDK build — різниця відчутна.
Що отримали:
install/uclibc/luckfox_pico_retinaface_facenet_demo/ ├── luckfox_pico_retinaface_facenet ← бінарник ├── model/ │ ├── RetinaFace.rknn ← детекція облич │ ├── mobilefacenet.rknn ← розпізнавання │ └── test.jpg ← еталонне фото └── lib/ └── librga.so
Копіюємо на плату:
scp -o PubkeyAuthentication=no -r \ install/uclibc/luckfox_pico_retinaface_facenet_demo \ [email protected]:/root/
⚠ Прапор
-o PubkeyAuthentication=noобов’язковий якщо SSH видаєToo many authentication failures. Це відбувається коли в~/.ssh/накопичилось багато ключів і сервер відхиляє з’єднання до перебору всіх.
◆ Запускаємо. І одразу перший сюрприз
Заходимо на плату і запускаємо:
cd /root/luckfox_pico_retinaface_facenet_demo ./luckfox_pico_retinaface_facenet model/ test.jpg ./luckfox_pico_retinaface_facenet <retinaface model_path> <facenet model_path> <reference pic_path>
⚠ Програма очікує три окремі аргументи, а не папку і фото. З’ясувалось, що
test.jpgце не вхідне зображення для детекції, це еталонне фото для FaceNet (reference face для порівняння). Детекція іде з камери в реальному часі. І достатньо замінити фото на свій фейс

Правильний синтаксис:
./luckfox_pico_retinaface_facenet \ model/RetinaFace.rknn \ model/mobilefacenet.rknn \ model/test.jpg
Але перед запуском — обов’язковий крок.
◆ RkLunch-stop.sh — завжди перед NPU
RkLunch-stop.sh Stop Application ... 872 root rkipc -a /oem/usr/share/iqfiles rkipc active ... rkipc exit
⚠
rkipc— це процес який керує камерою і ISP при звичайному завантаженні. Він тримає/dev/video*і може блокувати доступ до NPU або камери. Завжди зупиняй його перед запуском NPU демо черезRkLunch-stop.sh.
◆ NPU живе
Запускаємо з правильними аргументами і наводимо камеру на обличчя:
init retinaface init facenet Retinaface Info model input num: 1, output num: 3 input tensors: index=0, name=input.1, n_dims=4, dims=[1, 640, 640, 3], ... output tensors: index=0, name=515 ← bounding boxes index=1, name=553 ← confidence scores index=2, name=592 ← landmarks (5 точок обличчя) Facenet Info model input num: 1, output num: 1 input tensors: index=0, name=input.1, dims=[1, 160, 160, 3] output tensors: index=0, name=313, dims=[1, 128] ← 128-мірний embedding вектор Init success
Моделі завантажились без помилок. Версійного конфлікту не сталось — 1.6.0 з репо і 1.6.0 з плати спрацювали разом.
Камера ініціалізується через rkaiq ISP, і починається детекція:
@ (105 63 176 134) 1.345 @ (105 60 176 130) 1.291 @ (99 60 170 130) 1.360 @ (102 60 173 130) 1.397
Кожен рядок — знайдене обличчя:
@ (x1 y1 x2 y2 ) score @ (105 63 176 134) 1.345
x1 y1 — верхній лівий кут bounding box. x2 y2 — нижній правий. score — впевненість моделі. Значення
Детекція стабільна, реальний час. NPU робить те для чого створений.
◆ Що далі — Стаття 10
Зараз програма виводить координати в консоль. У Статті 10 ці координати стануть командами:
Камера → RetinaFace на NPU (детекція) → вирізаємо ROI обличчя → HTTP POST на сервер з базою → сервер порівнює через FaceNet embeddings → відповідь: OK / UNKNOWN → якщо OK: UART3 → STM32 → реле спрацьовує
«Luckfox думає, STM32 діє» — повний пайплайн.
Питання! До речі, поки розбирався з підключенням Luckfox по ethernet — задумався: а як в реальних автономних системах з’єднують vision модулі з мозком? CAN? Automotive Ethernet? Окремий SPI? Якщо є досвід з роботизованих систем або automotive — цікаво почути в коментарях.
◆ Гайд-шпаргалка
Всі команди в одному місці.
На PC:
# Клонуємо репо з прикладами git clone https://github.com/LuckfoxTECH/luckfox_pico_rknn_example cd luckfox_pico_rknn_example # Встановлюємо шлях до SDK export LUCKFOX_SDK_PATH=/home/alex/luckfox-pico # Збираємо (вибираємо uclibc → retinaface_facenet) ./build.sh # Копіюємо на плату scp -o PubkeyAuthentication=no -r \ install/uclibc/luckfox_pico_retinaface_facenet_demo \ [email protected]:/root/
На платі:
# Зупиняємо rkipc — обов'язково RkLunch-stop.sh # Запускаємо cd /root/luckfox_pico_retinaface_facenet_demo ./luckfox_pico_retinaface_facenet \ model/RetinaFace.rknn \ model/mobilefacenet.rknn \ model/test.jpg # Наводимо камеру на обличчя # Очікуємо рядки виду: @ (x1 y1 x2 y2) score
Перевірка NPU і бібліотеки:
# Драйвер dmesg | grep -i npu | grep -i version # Бібліотека (увага — вона в /oem/usr/lib, не в /usr/lib) strings /oem/usr/lib/librknnmrt.so | grep "librknnmrt version" # LD_LIBRARY_PATH має включати /oem/usr/lib echo $LD_LIBRARY_PATH
Репозиторій: github.com/pipicosim800-maker/stm32F103
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівДякую за серію і окремо за цю частину — рідко хто доводить NPU до живого inference і чесно показує граблі по дорозі. Пошук бібліотеки не там, де очікуєш, і збіг версій драйвера й runtime — впізнаю до болю.
Ми йшли схожою дорогою, тільки під Windows і на іншому залізі: ноутбучний Intel Core Ultra 5 135U із вбудованим NPU покоління Meteor Lake (Intel AI Boost, він же NPU 3720 / «NPU 3»). Підключали його не напряму, а через ONNX Runtime з модулем виконання OpenVINO, і лише для обрахунку векторів — невелика модель all-MiniLM-L6-v2, не для генерації тексту.
Граблі майже один в один з твоїми: модуль виконання мусить точно збігатися версією з бібліотекою, інакше мовчазний відкат на CPU; збій ініціалізації на рівні драйвера (залізо є, сесія не створюється); плюс бібліотека намагалась слати телеметрію, яку довелось гасити ще до старту.
Але головне — очікуваного приросту швидкості від NPU ми поки не отримали. Та сама модель на CPU давала10–17 мс на батч у прогрітому стані, і NPU цього виграшу не перебив. Тому лишили його опційним прискорювачем для вузької задачі (вектори) із гарантованим відкатом на процесор.
Тому твій підхід виглядає здоровіше за наш на старті: вузька задача, готова модель, перевірений інструментарій від виробника — і воно працює. Дякую, що ділишся попередженнями, а не лише успіхами.
Дякую!!! 🔥🤗🤗🤗🤗. Супер, що ви поділились досвідом. Бо гадаю, що NPU до кінця ще не розкрита тема, особисто мною то точно, але точно варта увага. Сьогодні прочитав статтю що, — «Компанія Raspberry Pi прогнозує значне зростання прибутку, очікуючи скориговані прибутки щонайменше 38 мільйонів доларів (28,2 мільйона фунтів стерлінгів) за першу половину 2026 року. Цей оптимістичний прогноз зумовлений зростанням попиту на їхні продукти в галузі штучного інтелекту. Стрімкий фінансовий зліт Raspberry Pi у 2026 році дійсно пов’язаний із тим, що вони вчасно та дуже вдало застрибнули у поїзд локального штучного інтелекту (Edge AI). Оскільки сам процесор Raspberry Pi 5 не має вбудованого ШІ-прискорювача, компанія пішла шляхом створення спеціалізованих модулів розширення (HAT), які підключаються через швидкісну шину PCIe.
Для реалізації цих завдань Raspberry Pi уклала стратегічне партнерство з компанією Hailo, яка розробляє надефективні нейропроцесори (NPU). Замість універсальних обчислень, ці мікросхеми „заточені“ суто під матричні математичні операції, що використовуються в нейромережах.»
Raspberry Pi + Hailo — гарний кейс вдалого позиціонування! Hailo-8 (~26 TOPS при ~2.5W) — це справді «заточений» NPU: фіксована архітектура dataflow, statically compiled graph, і саме тому він такий ефективний на inference production-моделей.
З досвіду Infineon — аналогічний підхід, але від сторони мікроконтролера: лінійка PSoC™ Edge AI (зокрема PSoC™ E84) з вбудованимML-прискорювачем + інтеграція з Imagimob (яку Infineon придбала 2023 року). Imagimob — це тулчейн для тренування і деплою на MCU: ви описуєте тип сигналу, вибираєте target latency, і тулчейн генерує оптимізований C-код під конкретний MCU без HAL-магії.
Ключова різниця підходів:
- Hailo/Raspberry Pi: PCIe HAT, потужна модель (~MobileNet, YOLO), ~10W загалом
- PSoC Edge AI / Imagimob: inference прямо на MCU (<100mW), але класи задач — keyword detection, anomaly detection, gesture recognition — тобто не FaceNet, а «чи є тут аномальний звук мотора»
Це два різні ешелони edge, і вибір залежить від power budget і розміру моделі. Для вашого RetinaFace + FaceNet — однозначно потрібен Hailo-рівень. Для «виявлення дотику» чи «розпізнавання вібрації» — MCU-рівень справляється без зовнішнього NPU взагалі.Тобто Raspberry Pi + Hailo — це «edge cloud» (потужно, але від розетки). PSoC Edge — це «edge dust» (мінімальна модель, батарейка, 5 років роботи). Обидва правильні, але для різних завдань.
Апдейт — знайшли багу в попередньому тесті що скидала розрахунки на CPU, пофіксали.3-хвилинного прогона вийшло: CPU 62.0 inf/s, NPU 267.97 inf/s, тобто 4.32x speedup на цьому конкретному workload.
Запустив бенчмарк тест на HP Elite x360 830 G11 з Intel Core Ultra 5 135U (12C/14T) і вбудованим Intel AI Boost (Meteor Lake NPU 3720 / NPU 3).
Для NPU узято ONNX Runtime + OpenVINO Execution Provider (тобто інференс запускається через ONNX Runtime, а виконання на NPU дає саме OpenVINO Runtime/драйвер), без прямого виклику окремого NPU SDK.
Конфігурація бенчмарку: одна й та сама embedding-модель all-MiniLM-L6-v2.onnx, precision FP16, duration 180 секунд, порівняння CPUExecutionProvider проти OpenVINOExecutionProvider(device=NPU), метрика inf/s.
За результатом
Важливий нюанс з практики: приріст сильно залежить від точного збігу версій драйвера, OpenVINO runtime і execution provider; при розсинхроні легко отримати тихий fallback на CPU, тому ми окремо перевіряємо фактичний provider у рантаймі.
Підсумок: NPU для нас не «магічна заміна всього», але всеж опційний прискорювач для вузької задачі ембеддінгів, з обов’язковим fallback на CPU. Практикуємо далі.