Зміст
У статті про WebGPU і Transformers.js ланцюжок виглядає коротко: модель → ONNX → Runtime → GPU в браузері. На практиці саме середня ланка з’їдає дні: експорт із PyTorch падає на незнайомому операторі, у браузері цифри не збігаються з Colab, а «просто збережи .onnx» не пояснює, що всередині файлу. Нижче — повний розбір: чим формат відрізняється від рушія, як влаштований граф і opset, як експортувати й перевіряти паритет, що ламається в CRNN і детекторів, і куди веде шлях до ONNX Runtime Web.
Ключові висновки
ONNX — формат графа, не фреймворк навчання. Навчаєте в PyTorch (чи іншому каркасі), в ONNX експортуєте переносний артефакт.
ONNX Runtime — рушій виконання. Він читає граф і ганяє його на CPU, CUDA, WebAssembly, WebGPU та інших провайдерах.
Opset фіксує набір операцій. Новий opset ≠ автоматично краще для браузера: важлива підтримка цільового провайдера виконання.
Паритет важливіший за «зелений» експорт. Файл створився — ще не успіх. Звіряйте логіти й метрики з Python на тому самому препроцесі.
Браузерний шлях починається тут. Без чесного ONNX-артефакту клієнтський AI на WebGPU не працюватиме стабільно.
Навіщо потрібен ONNX
Навчання і прод живуть у різних світах. У лабораторії зручний PyTorch: автоград, налагодження, екосистема. У проді потрібен передбачуваний вивід на сервері, на краю або у вкладці. Тягнути цілком інтерпретатор PyTorch у браузер нереалістично. Потрібна спільна мова: опис обчислювального графа, який розуміють різні рантайми.
ONNX (Open Neural Network Exchange) саме така мова: відкрита специфікація графа нейромережі. Модель із PyTorch, іноді з інших каркасів, перетворюється на файл (часто .onnx плюс зовнішні ваги), який потім читає ONNX Runtime, конвеєри на кшталт TensorRT або браузерний ONNX Runtime Web.
Без ONNX типові глухі кути:
- «Модель є лише як
state_dict— як віддати фронту?» - «На сервері CUDA, у браузері WebGPU — два різні збірки коду?»
- «У
Transformers.jsмодель із каталогу Hub уже в ONNX — а свою CRNN куди?»
Формат vs Runtime vs навчання
Три сутності, які плутають найчастіше:
| Поняття | Що це | Чого це не робить |
|---|---|---|
| ONNX | Специфікація й серіалізація графа (операції, тензори, константи) | Не навчає модель; не обирає GPU сам |
ONNX Runtime |
Рушій, що виконує граф через провайдери (CPU, CUDA, Web…) | Не замінює цикл навчання |
PyTorch / TF |
Каркаси навчання й дослідження | Не зобов’язані збігатися з браузерним API |
Аналогія: ONNX ближчий до «проміжного представлення програми», Runtime — до «віртуальної машини під конкретне залізо», PyTorch — до мови, якою ви писали вихідник.
В екосистемі Hugging Face для браузера моделі часто вже лежать як ONNX-артефакти. Transformers.js підтягує їх і віддає в ONNX Runtime Web. Свою модель ви готуєте тим самим шляхом: експорт → перевірка → лише потім WebGPU.
Як виглядає файл на диску
На практиці «модель в ONNX» — не завжди один файл. Часто це:
- один
.onnx, де граф і ваги всередині; - або граф плюс зовнішні файли ваг (зручніше для великих мереж і часткового підвантаження).
У картці моделі на Hugging Face для браузера зазвичай лежать уже підготовлені артефакти під ONNX Runtime Web. Свою CRNN ви кладете на свій CDN або статику застосунку й самі відповідаєте за версію й кеш. Має сенс поруч тримати короткий MODEL.md: opset, форми входу, препроцес, алфавіт (якщо OCR), хеш файлу, дата експорту.
Корисний ритуал перед злиттям: відкрити граф у Netron, переконатися, що вхід називається так, як чекає фронт, і що на виході очікувані логіти, а не «сюрприз» із проміжного шару.
Граф, оператори й opset
Граф ONNX — набір вузлів. Вузол — операція (Conv, MatMul, Softmax, LSTM, …) із входами й виходами-тензорами. Версія набору операторів називається opset (наприклад, opset 17). Експорт із PyTorch обирає opset; рантайм має вміти реалізувати всі вузли цього набору на обраному провайдері.
Звідси сюрпризи:
- Експорт на свіжий opset пройшов, а
ONNX Runtime Web+ WebGPU не знає рідкісний op → падіння або тихий відхід на інший шлях. - Кастомний шар у
PyTorchбез стандартного аналога → експорт вимагає переписати через примітиви або символьні функції експортуtorch.onnx. - Та сама математика може розкластися в різні послідовності операцій — поведінка чисельно близька, але не біт-у-біт.
Інструменти на кшталт Netron допомагають побачити граф: які входи, які вузли, де «чорна скринька». Для налагодження експорту це обов’язковий крок, не прикраса.
Експорт із PyTorch
Типовий шлях (спрощено):
import torch
model.eval()
dummy = torch.randn(1, 1, 32, 128) # BCHW під ваш препроцес
torch.onnx.export(
model,
dummy,
"crnn.onnx",
input_names=["input"],
output_names=["logits"],
opset_version=17,
dynamo=False, # уточнюйте API своєї версії PyTorch
)
На практиці важливіші за параметри «краси» три рішення:
model.eval()і без проріджування (dropout) / шумних гілок — інакше граф і числа пливуть.dummyтієї самої форми й семантики, що прод (канали, висота кропу, довжина).- Явні імена входів/виходів — щоб препроцес у JS не гадав порядок тензорів.
У нових версіях PyTorch шлях експорту еволюціонує (torch.onnx.export, режими на базі Dynamo). Дивіться документацію вашої версії й фіксуйте її у файлі опису моделі: «експортовано на torch X.Y, opset Z».
Після експорту одразу:
python -c "import onnx; onnx.checker.check_model(onnx.load('crnn.onnx'))"
І прогін через onnxruntime у Python до розмов про React.
Динамічні осі й форми
Багато моделей приймають змінний розмір пакета або довжину послідовності. В ONNX це динамічні осі (dynamic_axes під час експорту): наприклад, розмір пакета і ширина кропу вільні, висота фіксована під архітектуру CRNN.
Помилки тут типові:
- Заекспортували лише розмір пакета
1,W=128— у браузері інший кроп падає. - Зробили все динамічним — частина провайдерів повільніша або гірше оптимізує.
- У JS створили тензор у порядку NHWC, а граф чекає NCHW — «модель тупить», хоча ваги вірні.
Правило: зафіксуйте контракт форми в картці моделі (як у API). Браузерний код зобов’язаний повторити його один в один.
Що ламається на CRNN, YOLO і кастомних шарах
CRNN / послідовності. CTC, рекурентні блоки, нестандартне об’єднання ознак — часті джерела несумісних або неоптимальних ops. Постобробку CTC часто лишають поза графом (у Python і в JS окремо) — так простіше зберегти паритет і не тягнути в ONNX крихку логіку алфавіту.
Детектори (YOLO і родина). Голови, декодування рамок, придушення немаксимумів (NMS): частина команд експортує лише основу мережі, а постобробку тримає зовні; частина намагається вшити все в граф. Для браузера другий шлях важчий за ops і налагодженням. Інженерний контекст детектора — у розборі YOLO.
Кастомні CUDA-ядра / нестандартні модулі. Якщо шару немає в ONNX opset, експорт не «трохи підправить» — потрібна заміна на склад зі стандартних ops або відмова від цього шляху для клієнта.
Практичний критерій готовності: на контрольному наборі кропів збігаються рядки OCR або перетин рамок (IoU) між PyTorch і onnxruntime у Python. Поки немає — у WebGPU рано.
Паритет: як довести, що експорт чесний
Мінімальний стенд:
- Той самий препроцес (нормалізація, resize, порядок каналів) у функціях, спільних для тесту.
- Прогін
PyTorch→ логіти / рядки. - Прогін
onnxruntime.InferenceSession→ ті самі метрики. - Допуски: для логітів —
allcloseз розумнимatol/rtol; для OCR — частка повного збігу рядків на еталонному наборі. - Фіксація версій: torch, onnx, onnxruntime, opset, хеш файлу моделі.
Розбіжність «у Colab 0.98, у вкладці сміття» майже завжди тут: інше масштабування, RGB vs BGR, /255 vs ImageNet mean, інше квантування. WebGPU ні до чого, поки вивід через onnxruntime у Python уже не збігся з PyTorch.
Зв’язка з польовою практикою компактного OCR — CRNN на замірах УЗТ.
Міні-сценарій: від .pt до цифри в UI
Зберіть вузький контур на одному типі кропу (наприклад, комірка заміру):
- Навчена модель у
PyTorch,eval(), фіксований препроцес. - Скрипт експорту в ONNX з іменами входів/виходів і обраним opset.
- Скрипт порівняння: 50–200 еталонних кропів → рядки або логіти в
PyTorchі вonnxruntime. - Той самий препроцес на TypeScript, завантаження через
ONNX Runtime Webу робочому потоці. - Спочатку провайдер WASM, потім спроба WebGPU із запасним шляхом.
- У UI — час холодного завантаження, теплого виводу й прапорець провайдера.
Так ви отримуєте вимірюваний «вертикальний зріз» замість абстрактного «перенесемо всі моделі в браузер». Саме цей зріз зазвичай переконує замовника краще за слайд про WebGPU.
Квантування й розмір артефакту
Квантування (половинна точність, цілі 8 біт тощо) зменшує файл і часто прискорює вивід, ціною якості. Його можна робити до експорту, під час експорту або інструментами ORT — важливо, який шлях обрали і що саме потрапить у браузер.
Для клієнта розмір — продуктова метрика: сотні мегабайт на перший візит б’ють по холодному старту, про що докладно в статті про ШІ в браузері. Правило те саме: після квантування знову проженіть еталонний набір.
Провайдери Runtime і шлях у браузер
ONNX Runtime обирає провайдер виконання: CPU, CUDA, TensorRT (де налаштовано), у браузері — WASM і WebGPU. Один .onnx теоретично переносний; на практиці список підтриманих операцій і продуктивність відрізняються.
Ланцюжок у продукт:
PyTorch → export ONNX → ORT Python (паритет)
↓
`ONNX Runtime Web` + WASM (широке охоплення)
↓
`ONNX Runtime Web` + WebGPU (прискорення)
↓
`Transformers.js` або прямий ORT API
Прямий виклик ORT у робочому потоці доречний для своєї моделі без конвеєра Hugging Face. Transformers.js зручний, коли модель уже в каталозі моделей і потрібен стандартний конвеєр. Обидва варіанти описані в контексті UI в WebGPU + Transformers.js.
Чекліст поставки .onnx у прод
- Зафіксовано версії
torch/onnx/onnxruntime/ opset - Скрипт експорту в репозиторії, не «руками з ноутбука»
-
model.eval(), детермінований приклад входу під прод-форми - Динамічні осі усвідомлені й задокументовані
-
onnx.checkerі завантаження в ORT Python проходять - Еталонний набір: паритет із
PyTorchу межах допуску - Препроцес і постпроцес описані поруч з артефактом (мовно-агностично)
- Для браузера: перевірка на цільовому provider (WASM / WebGPU)
- Хеш файлу й канал оновлення моделі (CDN / скидання кешу)
- Зрозуміло, що не входить у граф (декодування CTC,
NMS, правила)
Зв’язок із серверним виводом
Навіть якщо кінцева мета — браузер, серверний ORT лишається корисним еталоном. На CPU або CUDA простіше зловити розбіжності операцій, порівняти швидкість і вирішити, чи варто взагалі тягнути модель на клієнт. Інколи підсумок чесний: модель лишається на сервері, а в браузері живе лише легкий класифікатор або перевірка якості кадру.
Гібрид виглядає так: важкий детект або велика мережа — API; компактний OCR кропу — локально після того, як сервер (або легкий клієнтський детектор) віддав рамку. ONNX тут спільна мова для обох контурів: один експорт, два провайдери, одна таблиця паритету.
Типові помилки
Вважати ONNX прискорювачем. Формат сам по собі не прискорює; прискорює залізо й провайдер Runtime.
Експортувати режим навчання. Dropout і гілки навчання псують граф і метрики.
Ігнорувати препроцес. Найчастіша причина «браузер бреше».
Вшивати всю постобробку в граф без потреби. Ускладнює експорт і перенесення алфавіту/порогів.
Не фіксувати opset і версії. Через пів року «той самий скрипт» дає інший файл.
Іти в WebGPU до паритету в Python. Налагодження в DevTools дорожче, ніж numpy.allclose.
Окремо домовтеся з командою фронтенду про канал оновлення: зміна opset або квантування — це новий артефакт, а не «тихий» перезапись на CDN. Інакше в частини користувачів у кеші старий граф, у частини — новий, а налагодження перетворюється на суперечку «в мене працює». Семантичне версіонування файлу моделі (crnn-v3-opset17.onnx) і явний скидання кешу в адресі економлять тижні.
Часті питання
ONNX — це бібліотека Python?
Ні. Це стандарт формату графа. У Python ставлять пакети onnx (робота з моделлю) і onnxruntime (виконання).
Чим ONNX відрізняється від ONNX Runtime?
Формат vs рушій. Можна мати .onnx і виконувати його різними рантаймами; ORT — найпоширеніший рушій у цьому стеку.
Чи потрібен Transformers.js, якщо є свій .onnx?
Ні. Можна вантажити модель напряму через ONNX Runtime Web. Transformers.js зручний для конвеєр Hugging Face і готових карток моделей.
Який opset обрати?
Той, який стабільно підтримує ваш цільовий Runtime/провайдер і на якому зелений паритет. Ганятися за максимальним номером заради новизни не варто.
Чому експорт успішний, а браузер падає?
Часто ops є на CPU-провайдері ORT, але немає або інакше реалізовані на WebGPU. Перевіряйте саме web-провайдер.
Чи потрібно конвертувати модель знову після кожного навчання?
Так, якщо змінилися ваги або архітектура. Той самий скрипт експорту має проганятися в CI або принаймні в чеклісті релізу: новий чекпоінт → новий .onnx → паритет → публікація артефакту. Інакше прод довго живе на старому графі, поки в репозиторії вже інша точність.
Чи можна навчати в ONNX?
Звичайний сценарій — ні: навчають у PyTorch тощо, ONNX використовують для виводу. Є експериментальні суміжні потоки, але для прод-пайплайну «навчання → експорт → ORT» лишається нормою.
Далі за темою
- WebGPU +
Transformers.js: нейромережі в браузері — куди дівається.onnxна клієнті. - Інженерія YOLO — детект і зв’язка з OCR.
- Компактна CRNN для замірів — польовий контур якості OCR.
- Рукописні цифри: CNN, CTC, TrOCR — коли вистачає простої моделі.
- Три шари стеку LLM — де граф і рантайм у великій картині інференсу.
Висновок
ONNX — це спосіб зафіксувати обчислювальний граф моделі так, щоб його могли виконувати різні рушії. ONNX Runtime — найпряміший шлях від цього файлу до CPU, GPU сервера або WebGPU у вкладці. Експорт із PyTorch — інженерний контракт: opset, форми, препроцес, паритет, версії. Закрийте цей контракт — і клієнтський AI перестає бути лотереєю; поки граф нечесний, жоден device: 'webgpu' не врятує.



Коментарі