SharedArrayBuffer и параллельные вычисления

Kлючевой особенностью современных веб-приложений для машинного обучения является возможность использовать многопоточность для повышения производительности. В среде JavaScript это достигается через Web Workers и объект SharedArrayBuffer, позволяющий нескольким потокам совместно использовать область памяти.

Основы SharedArrayBuffer

SharedArrayBuffer — это тип объекта, который создаёт область памяти, доступную одновременно нескольким Web Workers без копирования данных. В отличие от обычного ArrayBuffer, который передаётся между потоками по принципу клонирования, SharedArrayBuffer обеспечивает прямой доступ к одной и той же памяти, что критично для производительных вычислений, таких как нейросетевые операции.

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

const buffer = new SharedArrayBuffer(1024); // 1024 байта общей памяти
const sharedArray = new Float32Array(buffer); // представление как массив float

Ключевое преимущество — отсутствие затрат на копирование больших массивов данных между потоками.

Использование в Keras.js

Keras.js реализует нейронные сети в браузере с поддержкой WebGL и CPU. Для CPU-вычислений интенсивные операции, например матричные умножения и свёртки, могут быть распараллелены. SharedArrayBuffer позволяет хранить веса сети и промежуточные тензоры в общей памяти, доступной для всех воркеров.

Пример параллельного вычисления матричного умножения:

// В основном потоке создаём общий буфер
const size = 1024;
const sharedBuffer = new SharedArrayBuffer(size * Float32Array.BYTES_PER_ELEMENT);
const matrixA = new Float32Array(sharedBuffer);
const matrixB = new Float32Array(size); // отдельная матрица для примера

// Запуск Web Worker для вычислений
const worker = new Worker('matrixWorker.js');
worker.postMessage({ buffer: sharedBuffer, size: size });

В воркере:

onmess age = function(e) {
    const { buffer, size } = e.data;
    const matrixA = new Float32Array(buffer);

    // Выполнение параллельных вычислений с использованием Atomics
    for (let i = 0; i < size; i++) {
        Atomics.add(matrixA, i, 1); // пример атомарной операции
    }

    postMessage('done');
}

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

Управление многопоточностью

Keras.js не управляет потоками автоматически, но разработчик может создать пул воркеров для распараллеливания операций. Общие рекомендации:

  • Выделять SharedArrayBuffer для весов и больших тензоров.
  • Минимизировать синхронизацию между потоками, использовать атомарные операции только там, где это необходимо.
  • Разделять вычислительные задачи на блоки, чтобы каждый воркер обрабатывал отдельный сегмент тензора.
  • Использовать transferable objects для временных данных, которые не нужно держать в общей памяти.

Взаимодействие с WebGL

Keras.js поддерживает вычисления на GPU через WebGL. В этом случае SharedArrayBuffer используется преимущественно для CPU-части, например:

  • Предварительная обработка входных данных.
  • Агрегация результатов с нескольких потоков.
  • Обработка промежуточных тензоров до загрузки на GPU.

Пример интеграции в Keras.js

import KerasJS from 'keras-js';

const model = new KerasJS.Model({
  filepaths: {
    model: 'model.json',
    weights: 'weights.buf',
    metadata: 'metadata.json'
  },
  gpu: false
});

// Создание общего буфера для входного тензора
const sharedInputBuffer = new SharedArrayBuffer(224*224*3*Float32Array.BYTES_PER_ELEMENT);
const inputTensor = new Float32Array(sharedInputBuffer);

// Распараллеливание заполнения входного тензора
const numWorkers = 4;
const chunkSize = inputTensor.length / numWorkers;

for (let i = 0; i < numWorkers; i++) {
    const worker = new Worker('inputWorker.js');
    worker.postMessage({ buffer: sharedInputBuffer, start: i*chunkSize, end: (i+1)*chunkSize });
}

// После заполнения данных можно выполнить инференс
model.ready().then(() => {
    model.predict({ input: inputTensor }).then(output => {
        console.log(output);
    });
});

Практические советы

  • Размер SharedArrayBuffer должен быть заранее известен, так как его нельзя динамически расширять.
  • Разделение данных между воркерами по блокам уменьшает задержки и повышает кэш-эффективность.
  • Следует учитывать ограничения браузеров на использование SharedArrayBuffer — они доступны только в безопасных контекстах с включённой COOP/COEP политикой.

Вывод: использование SharedArrayBuffer в сочетании с Web Workers и Keras.js позволяет добиться значительного ускорения CPU-вычислений нейросетей в браузере, особенно для больших тензоров и многослойных моделей. Атомарные операции и корректное разделение данных между потоками критически важны для предотвращения гонок данных и обеспечения стабильности вычислений.