Содержание
В статье про 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 — к «виртуальной машине / JIT под конкретное железо», 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, рекуррентные блоки, нестандартное объединение признаков — частые источники несовместимых или неоптимальных операций. Постобработку CTC часто оставляют вне графа (в Python и в JS отдельно) — так проще сохранить паритет и не тащить в ONNX хрупкую логику алфавита.
Детекторы (YOLO и семейство). Головы, декодирование рамок, подавление немаксимумов (NMS): часть команд экспортирует только основу сети, а постобработку держит снаружи; часть пытается вшить всё в граф. Для браузера второй путь тяжелее по операциям и отладке. Инженерный контекст детектора — в разборе YOLO.
Кастомные CUDA-ядра / нестандартные модули. Если слоя нет в ONNX opset, экспорт не «чуть подправит» — нужна замена на состав из стандартных ops или отказ от этого пути для клиента.
Практичный критерий готовности: на контрольном наборе кропов совпадают строки OCR или пересечение рамок (IoU) между PyTorch и onnxruntime в Python. Пока нет — в WebGPU рано.
Паритет: как доказать, что экспорт честный
Минимальный стенд:
- Один и тот же препроцесс (нормализация, масштабирование, порядок каналов) в функциях, общих для теста.
- Прогон
PyTorch→ логиты / строки. - Прогон
onnxruntime.InferenceSession→ те же метрики. - Допуски: для логитов —
allcloseс разумнымatol/rtol; для OCR — доля полного совпадения строк на эталонном наборе. - Фиксация версий:
torch,onnx,onnxruntime, opset, хеш файла модели.
Расхождение «в Colab 0.98, во вкладке мусор» почти всегда здесь: другое масштабирование, RGB vs BGR, деление на 255 против среднего ImageNet, другое квантование. 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в пределах допуска - Препроцесс и постпроцесс описаны рядом с артефактом (язык-агностично)
- Для браузера: проверка на целевом провайдере (
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/провайдер и на котором зелёный паритет. Гнаться за максимальным номером ради новизны не стоит.
Почему экспорт успешен, а браузер падает?
Часто операции есть на 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' не спасёт.



Комментарии