requestAnimationFrame и синхронизация инференса с рендером

При работе с ONNX Runtime Web в браузере важной задачей является оптимизация производительности и обеспечение плавности интерфейса. Часто модели глубокого обучения используются для обработки кадров в реальном времени, например, в задачах компьютерного зрения. Прямой вызов инференса в основном потоке может привести к задержкам рендеринга и заметным «тормозам» интерфейса. Для решения этой проблемы используется функция requestAnimationFrame.

requestAnimationFrame предоставляет механизм для синхронизации работы с частотой обновления дисплея. В отличие от таймеров setTimeout или setInterval, этот метод гарантирует вызов функции перед следующей перерисовкой браузера, что минимизирует визуальные «разрывы» и задержки.

function renderFrame() {
    // код инференса и визуализации
    requestAnimationFrame(renderFrame);
}

requestAnimationFrame(renderFrame);

Интеграция ONNX Runtime Web с циклом рендеринга

ONNX Runtime Web предоставляет интерфейс для выполнения моделей в браузере с помощью JavaScript. Основной объект — InferenceSession, который загружает и выполняет модель. Синхронизация с requestAnimationFrame позволяет запускать инференс на каждом кадре, не блокируя UI.

Пример инициализации сессии и запуска инференса:

import * as ort from 'onnxruntime-web';

async function initModel() {
    const session = await ort.InferenceSession.create('model.onnx');
    return session;
}

async function runInference(session, inputTensor) {
    const feeds = { input: inputTensor };
    const results = await session.run(feeds);
    return results.output;
}

Для синхронизации с рендерингом:

let latestFrame = null;

async function processFrame(session) {
    if (!latestFrame) return;
    const output = await runInference(session, latestFrame);
    renderOutput(output);
    latestFrame = null;
}

function loop(session) {
    requestAnimationFrame(() => loop(session));
    processFrame(session);
}

В данном подходе latestFrame хранит последний доступный кадр. Функция processFrame выполняет инференс асинхронно, а requestAnimationFrame гарантирует вызов цикла до перерисовки, обеспечивая плавность анимации.

Асинхронная обработка инференса

ONNX Runtime Web поддерживает асинхронные вычисления через async/await, что критично для совместимости с requestAnimationFrame. Поскольку рендер не должен блокироваться, инференс рекомендуется запускать с использованием Promise, а результаты применять только после завершения вычислений.

async function loopWithInference(session, frame) {
    requestAnimationFrame(() => loopWithInference(session, frame));
    if (!frame) return;

    runInference(session, frame).then(output => {
        renderOutput(output);
    });
}

Такой подход позволяет браузеру продолжать перерисовку, не дожидаясь завершения инференса. Это особенно важно для моделей с высокой вычислительной сложностью, где время выполнения одного кадра может превышать 16–17 мс (частота 60 FPS).

Управление частотой инференса

В задачах реального времени не всегда требуется обработка каждого кадра. Для экономии ресурсов можно реализовать пропуск кадров, выполняя инференс, например, через каждые два или три вызова requestAnimationFrame.

let frameCounter = 0;
const frameSkip = 2;

function loopWithFrameSkip(session, frame) {
    requestAnimationFrame(() => loopWithFrameSkip(session, frame));
    frameCounter++;
    if (frameCounter % frameSkip !== 0) return;
    if (!frame) return;

    runInference(session, frame).then(output => renderOutput(output));
}

Этот метод снижает нагрузку на CPU/GPU и предотвращает накопление задержек при инференсе сложных моделей.

Обработка данных и подготовка тензоров

Для эффективного использования ONNX Runtime Web данные кадров необходимо преобразовать в тензоры (ort.Tensor) перед передачей в модель. Использование Uint8Array, Float32Array и других массивов позволяет минимизировать накладные расходы на копирование данных.

function createInputTensor(imageData) {
    const { data, width, height } = imageData;
    const floatData = new Float32Array(width * height * 3);
    for (let i = 0; i < width * height; i++) {
        floatData[i * 3 + 0] = data[i * 4 + 0] / 255.0;
        floatData[i * 3 + 1] = data[i * 4 + 1] / 255.0;
        floatData[i * 3 + 2] = data[i * 4 + 2] / 255.0;
    }
    return new ort.Tensor('float32', floatData, [1, 3, height, width]);
}

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

Взаимодействие с веб-графикой

Для визуализации результатов инференса, например, наложения сегментации или отображения координат объектов, данные можно передавать в Canvas API или WebGL. Интеграция с requestAnimationFrame обеспечивает синхронизацию обновления данных модели с перерисовкой сцены, что исключает мерцания и несогласованность кадров.

function renderOutput(output) {
    const ctx = canvas.getContext('2d');
    // отрисовка на основе output
}

Рекомендации по оптимизации

  • Использовать WebAssembly (wasm) или WebGPU (webgl) бэкенды ONNX Runtime Web для ускорения инференса.
  • Минимизировать копирование данных при создании тензоров.
  • Пропускать кадры при высоких вычислительных затратах.
  • Асинхронно обрабатывать инференс, не блокируя основной поток браузера.
  • Сохранять последний обработанный кадр, чтобы избежать повторного инференса для одного и того же изображения.

Такой подход обеспечивает плавный пользовательский опыт, поддерживает высокую частоту обновления интерфейса и эффективное использование ресурсов браузера при работе с ONNX Runtime Web.