Работа с Web Workers для неблокирующего инференса

Keras.js предоставляет возможность запускать нейронные сети в браузере, используя WebGL для ускорения вычислений. Однако прямое выполнение инференса на основном потоке может блокировать интерфейс, особенно при работе с большими моделями. Для решения этой проблемы используется механизм Web Workers, позволяющий запускать вычисления в отдельном потоке.


Основы Web Workers

Web Worker — это отдельный поток выполнения JavaScript, который работает параллельно с основным потоком страницы. Основные особенности:

  • Асинхронность: основной поток не блокируется во время работы Worker.
  • Изоляция: Worker не имеет прямого доступа к DOM, взаимодействие осуществляется через postMessage и события onmessage.
  • Поддержка массивов: для передачи больших данных можно использовать Transferable Objects, что предотвращает копирование памяти.

Простейший пример создания Worker:

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

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

worker.postMessage({ inputData: [1, 2, 3] });

В файле worker.js:

onmess age = function(event) {
    const input = event.data.inputData;
    const output = input.map(x => x * 2); 
    postMessage(output);
};

Подключение Keras.js внутри Web Worker

Для выполнения инференса модели Keras.js необходимо загрузить скрипт библиотеки в контексте Worker. Обычно это делается с помощью importScripts:

importScripts('https://cdn.jsdelivr.net/npm/keras-js@0.3.0/dist/keras.min.js');

let model;

async function loadModel() {
    model = new KerasJS.Model({
        filepaths: {
            model: 'model.json',
            weights: 'model_weights.buf'
        },
        gpu: true
    });
    await model.ready();
    postMessage({ status: 'model_loaded' });
}

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

    if (command === 'load_model') {
        await loadModel();
    } else if (command === 'predict') {
        const input = { input_1: new Float32Array(inputData) };
        const output = await model.predict(input);
        postMessage({ result: output });
    }
};

Ключевые моменты:

  • gpu: true позволяет использовать WebGL для ускорения инференса.
  • В Worker необходимо дождаться готовности модели через await model.ready().
  • Передача данных в формате Float32Array оптимизирует производительность.

Передача данных и оптимизация

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

const buffer = new Float32Array([1,2,3,4]).buffer;

worker.postMessage({ inputData: buffer }, [buffer]);

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


Организация работы с несколькими воркерами

Для ускорения инференса на больших потоках данных используют пул воркеров:

const NUM_WORKERS = 4;
const workers = [];

for (let i = 0; i < NUM_WORKERS; i++) {
    const w = new Worker('worker.js');
    w.postMessage({ command: 'load_model' });
    workers.push(w);
}

// Распределение задач по воркерам
function predictBatch(batchData) {
    return new Promise((resolve) => {
        const results = [];
        let completed = 0;

        workers.forEach((worker, index) => {
            worker.onmess age = (event) => {
                results[index] = event.data.result;
                completed++;
                if (completed === workers.length) resolve(results);
            };
            worker.postMessage({ command: 'predict', inputData: batchData[index] });
        });
    });
}

Преимущества пулов:

  • Распределение нагрузки между потоками.
  • Параллельная обработка нескольких батчей данных.
  • Минимизация задержки отклика интерфейса.

Управление жизненным циклом воркеров

Для стабильной работы важно правильно управлять созданием и завершением воркеров:

workers.forEach(worker => worker.terminate());

Рекомендуется:

  • Загружать модель только один раз при инициализации воркера.
  • Использовать terminate() для освобождения ресурсов после завершения инференса.
  • Обрабатывать ошибки внутри воркеров, чтобы избежать необработанных исключений, которые могут зависнуть:
oner ror = function(error) {
    postMessage({ error: error.message });
};

Преимущества использования Web Workers с Keras.js

  1. Неблокирующий интерфейс: основной поток продолжает обрабатывать UI, а инференс выполняется асинхронно.
  2. Параллельная обработка: несколько воркеров могут обрабатывать данные одновременно.
  3. Использование GPU: с включением WebGL производительность инференса значительно повышается.
  4. Масштабируемость: легко расширять пул воркеров при увеличении объема данных.

Рекомендации по производительности

  • Использовать Float32Array для передачи входных данных.
  • Ограничить количество воркеров в зависимости от доступных ресурсов браузера.
  • Минимизировать объем передаваемых сообщений между потоком и воркером.
  • Поддерживать кэширование модели в воркере, чтобы не загружать её каждый раз.

Эта структура позволяет интегрировать Keras.js в веб-приложения с минимальной задержкой интерфейса и высокой производительностью, обеспечивая эффективный неблокирующий инференс на стороне клиента.