
Как я написал движок распознавания лиц на C, который обогнал ONNX Runtime
bauratynov 6 часов назад Как я написал движок распознавания лиц на C, который обогнал ONNX Runtime Уровень сложности Простой Время на прочтение 3 мин Охват и читатели 1.9K C * CGI (графика) * GitHub * Аналитика Из...
Anthropic — What company has the best second artificial intelligence model at the end of June?
В сфере искусственного интеллекта произошло заметное событие. bauratynov 6 часов назад Как я написал движок распознавания лиц на C, который обогнал ONNX Runtime Уровень сложности Простой Время на прочтение 3 мин Охват и читатели 1. 9K C * CGI (графика) * GitHub * Аналитика Из песочницы Полгода назад я начал портировать нейросеть EdgeFace-XS из ONNX в чистый C. Думал — граф небольшой, 1.
77M параметров, что может пойти не так? Первый наивный порт выдал 24мс . А потом началась оптимизация.
Технические детали
Результат FaceX ONNX Runtime 1. 18 мс Размер библиотеки 148 КБ 28 МБ Зависимости нет Python + onnxruntime Точность LFW 99. 73% Чистый C с SIMD интринсиками обгоняет ONNX Runtime на 23% .
Один и тот же CPU (i5-11500), одна модель, одни входные данные. Путь оптимизации: 24мс → 3мс Этап 0: Профилирование Замерил каждую операцию отдельно. Главный сюрприз: Матричное умножение — всего 6% от общего времени инференса.
Настоящие убийцы производительности: Операция Доля Проблема Depthwise conv ~30% Транспозы HWC↔CHW на каждом блоке LayerNorm × 17 ~16% Скалярный mean/variance GELU × 17 ~10% Наивный tanh() через math. h Транспозы памяти ~8% Лишние копирования MatMul ~6% Уже быстро Этап 1: SIMD ядра (24мс → 8мс) Написал AVX2 версии для каждой операции: LayerNorm — fused mean+variance в одном проходе. Вместо двух циклов по памяти — один с mm256 fmadd_ps для накопления суммы и суммы квадратов GELU — выкинул tanh() .
Отраслевые последствия
Реализовал exact erf через полиномиальную аппроксимацию Абрамовица-Стегуна (формула 7. 26) с кастомным mm256 exp_ps на 8 элементов за такт Depthwise conv — перевёл весь движок на нативный HWC layout. Ни одного транспоза во всём forward pass Этап 2: MatMul (8мс → 5мс) FP32 packed column-panel : веса перепакованы в формат — каждый столбец-панель помещается в L1 кэш INT8 GEMM микроядро с per-channel квантизацией: AVX2: vpmaddubsw с ±63 clamping для предотвращения s16 насыщения AVX-512 VNNI: vpdpbusd — нативные INT8 dot products без насыщения Thread pool — lock-free с work-stealing через атомарный счётчик и WaitOnAddress / futex Этап 3: Последние миллисекунды (5мс → 3мс) Убрал все транспозы — данные в HWC от входа до выхода Статический workspace вместо malloc на каждом вызове Pre-computed position embedding — это константа, не зависит от входа Pre-packed веса — транспозиция и паковка при загрузке, не при инференсе Хронология Фаза Время Длительность работы Наивный порт 24 мс 2 недели SIMD ядра 8 мс 3 недели MatMul + INT8 5 мс 1 месяц Финальная полировка 3 мс 4 месяца Последние 2мс заняли 4 месяца.
Первые 16мс — 2 недели. Вот что такое оптимизация. 7 багов точности Самая болезненная часть.
Cosine similarity с ONNX reference начиналась на 0. Нашёл 7 багов через послойные дампы — каждый из 286 тензоров сравнивался с NumPy эталоном.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.





