Перенос вычислений в Web Worker

ONNX Runtime Web (ORT Web) предоставляет возможность запускать модели машинного обучения прямо в браузере. Для сложных моделей и больших объёмов данных основной поток исполнения может блокироваться, что негативно влияет на отзывчивость интерфейса. Решением является использование Web Worker — отдельного потока исполнения JavaScript, позволяющего переносить ресурсоёмкие вычисления вне основного потока.

Основы работы с Web Worker

Web Worker создаётся через объект Worker, которому передаётся путь к скрипту. Внутри воркера доступ к DOM отсутствует, поэтому взаимодействие с основным потоком происходит исключительно через передачу сообщений с помощью postMessage и обработку событий onmessage.

Пример создания воркера:

const worker = new Worker('onnx-worker.js');

worker.onmess age = (event) => {
    console.log('Результат из воркера:', event.data);
};

worker.postMessage({ command: 'runModel', inputData: inputArray });

Внутри файла onnx-worker.js содержатся вычисления с моделью ONNX Runtime Web:

importScripts('https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js');

onmess age = async (event) => {
    const { command, inputData } = event.data;

    if (command === 'runModel') {
        const session = await ort.InferenceSession.create('model.onnx');
        const tensor = new ort.Tensor('float32', inputData, [1, inputData.length]);
        const feeds = { input: tensor };
        const results = await session.run(feeds);
        postMessage(results.output.data);
    }
};

Инициализация и работа с моделью

  1. Создание сессииort.InferenceSession.create(path) возвращает объект сессии, через который выполняются инференсы. В Web Worker сессия создаётся один раз и может использоваться многократно для нескольких вызовов модели, что экономит ресурсы.

  2. Формирование тензоров — входные данные преобразуются в объект ort.Tensor. Важно корректно указывать тип (float32, int32) и форму (shape).

  3. Выполнение модели — метод session.run(feeds) принимает объект feeds, где ключи соответствуют именам входных узлов модели. Метод возвращает промис с результатами, что идеально сочетается с асинхронной природой воркеров.

Передача данных между потоками

Web Worker не имеет прямого доступа к памяти основного потока. Поэтому для передачи больших массивов данных рекомендуется использовать Transferable Objects, чтобы избежать лишнего копирования:

const buffer = new Float32Array(inputData).buffer;
worker.postMessage({ command: 'runModel', buffer }, [buffer]);

В воркере:

const inputArray = new Float32Array(event.data.buffer);

Это позволяет значительно ускорить передачу данных, особенно при работе с изображениями или аудио в виде массивов.

Асинхронная обработка и очередь запросов

Часто возникает необходимость запускать несколько инференсов подряд. В воркере реализуется очередь запросов через массив промисов или обработку событий onmessage с идентификаторами:

let requestId = 0;
const pendingRequests = new Map();

worker.onmess age = (event) => {
    const { id, result } = event.data;
    pendingRequests.get(id)?.resolve(result);
    pendingRequests.delete(id);
};

function runModelInWorker(inputData) {
    return new Promise((resolve) => {
        const id = requestId++;
        pendingRequests.set(id, { resolve });
        worker.postMessage({ command: 'runModel', id, inputData });
    });
}

Такой подход позволяет безопасно управлять параллельными запросами к воркеру и обрабатывать результаты по мере готовности.

Оптимизация работы ONNX Runtime Web в воркере

  1. Использование WebAssembly (WASM) — основной движок ORT Web работает через WASM, что обеспечивает переносимость и высокую производительность.

  2. Предзагрузка модели — сессия создаётся один раз при инициализации воркера, что исключает повторную загрузку модели при каждом запросе.

  3. Выбор подходящего бэкенда — ORT Web поддерживает WebGL и WebAssembly. В воркере WebGL может работать только в случае OffscreenCanvas, поэтому для большинства задач лучше использовать WASM.

  4. Минимизация передачи данных — передавать только необходимый буфер, использовать Transferable Objects, избегать копирования больших массивов.

Пример комплексного использования

В сложной веб-приложении, где требуется обработка потокового видео, воркер позволяет выполнять инференс кадр за кадром, не блокируя интерфейс:

const videoFrame = getVideoFrame(); // Float32Array
const result = await runModelInWorker(videoFrame);
processResult(result); // обновление UI

Все ресурсоёмкие операции (предобработка кадра, инференс, постобработка) выполняются в отдельном потоке, что сохраняет плавность интерфейса и снижает нагрузку на главный поток.

Заключение по архитектуре

Использование Web Worker с ONNX Runtime Web позволяет разделять вычислительные задачи и интерфейс, повышая отзывчивость приложений. Архитектура с одной предзагруженной сессией, очередью запросов и передачей Transferable Objects обеспечивает максимальную производительность и стабильность при работе с моделями глубокого обучения в браузере.