- PVSM.RU - https://www.pvsm.ru -
Полгода назад я начал портировать нейросеть EdgeFace-XS из ONNX в чистый C. Думал — граф небольшой, 1.77M параметров, что может пойти не так? Первый наивный порт выдал 24мс. ONNX Runtime — 3.9мс. В 6 раз медленнее. А потом началась оптимизация.
|
|
FaceX |
ONNX Runtime 1.23 |
|---|---|---|
|
Медиана |
3.0 мс |
3.9 мс |
|
Минимум |
2.87 мс |
3.18 мс |
|
Размер библиотеки |
148 КБ |
28 МБ |
|
Зависимости |
нет |
Python + onnxruntime |
|
Точность LFW |
99.73% |
99.73% |
Чистый C с SIMD интринсиками обгоняет ONNX Runtime на 23%. Один и тот же CPU (i5-11500), одна модель, одни входные данные.
Замерил каждую операцию отдельно. Главный сюрприз:
Матричное умножение — всего 6% от общего времени инференса.
Настоящие убийцы производительности:
|
Операция |
Доля |
Проблема |
|---|---|---|
|
Depthwise conv |
~30% |
Транспозы HWC↔CHW на каждом блоке |
|
LayerNorm × 17 |
~16% |
Скалярный mean/variance |
|
GELU × 17 |
~10% |
Наивный |
|
Транспозы памяти |
~8% |
Лишние копирования |
|
MatMul |
~6% |
Уже быстро |
Написал AVX2 версии для каждой операции:
LayerNorm — fused mean+variance в одном проходе. Вместо двух циклов по памяти — один с mm256fmadd_ps для накопления суммы и суммы квадратов
GELU — выкинул tanh(). Реализовал exact erf через полиномиальную аппроксимацию Абрамовица-Стегуна (формула 7.1.26) с кастомным mm256exp_ps на 8 элементов за такт
Depthwise conv — перевёл весь движок на нативный HWC layout. Ни одного транспоза во всём forward pass
FP32 packed column-panel: веса перепакованы в формат [ceil(N/8), K, 8] — каждый столбец-панель помещается в 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
Убрал все транспозы — данные в HWC от входа до выхода
Статический workspace вместо malloc на каждом вызове
Pre-computed position embedding — это константа, не зависит от входа
Pre-packed веса — транспозиция и паковка при загрузке, не при инференсе
|
Фаза |
Время |
Длительность работы |
|---|---|---|
|
Наивный порт |
24 мс |
2 недели |
|
SIMD ядра |
8 мс |
3 недели |
|
MatMul + INT8 |
5 мс |
1 месяц |
|
Финальная полировка |
3 мс |
4 месяца |
Последние 2мс заняли 4 месяца. Первые 16мс — 2 недели. Вот что такое оптимизация.
Самая болезненная часть. Cosine similarity с ONNX reference начиналась на 0.067 (мусор). Должно быть 1.0. Нашёл 7 багов через послойные дампы — каждый из 286 тензоров сравнивался с NumPy эталоном.
Stage 0, block 2: использовал W(38) — это bias
Правильно: W(39) — это gamma
Было: attention_residual = original_input
mlp_residual = original_input
Надо: attention_residual = DW_output + pos_embed
mlp_residual = original_input
Было: conv0(x_split0), conv1(x_split1) — независимо
Надо: r0 = conv0(x_split0), conv1(r0 + x_split1) — каскадно
Было: pos = Conv1x1(INPUT, W) — пересчитывается каждый раз
Надо: pos = Conv1x1(CONSTANT, W) — вычисляется один раз при загрузке
Было: attn = softmax(Q @ K^T / τ) — полная [C × C] матрица
Надо: attn_h = softmax(Q_h @ K_h^T / τ) — per-head [dim × dim]
Stage 3, head_dim=48: attn_buf начинался по адресу,
перекрывающему конец V_nhd. Сдвиг буфера решил проблему.
tanh-аппроксимация GELU: ошибка ε на каждом блоке.
17 блоков × ε = заметное расхождение.
Фикс: заменил на exact erf (A&S 7.1.26).
Каждый фикс: +0.1 cosine similarity → Семь фиксов: 1.000
// Инициализация (~100мс, один раз)
FaceX* fx = facex_init("edgeface_xs_fp32.bin", NULL);
// Эмбеддинг (3мс на вызов)
float face[112 * 112 * 3]; // RGB, HWC layout, [-1, 1]
float embedding[512];
facex_embed(fx, face, embedding);
// Сравнение двух лиц
float sim = facex_similarity(emb_a, emb_b);
// sim > 0.3 → один и тот же человек
facex_free(fx);
4 функции · 148 КБ · Ноль зависимостей · Apache 2.0
Один человек может написать inference движок быстрее продукта Microsoft — если оптимизирует под одну конкретную модель. ONNX Runtime рассчитан на тысячи моделей. FaceX — на одну. Специализация бьёт универсальность.
Исходники: github.com/facex-engine/facex [1]
Автор: bauratynov
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/raspoznavanie-obrazov/450610
Ссылки в тексте:
[1] github.com/facex-engine/facex: https://github.com/facex-engine/facex
[2] Источник: https://habr.com/ru/articles/1029338/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1029338
Нажмите здесь для печати.