AI без сервера: Google научил JavaScript запускать нейросети прямо в браузере

mr. Cooper 2 часа назад Нейросети и AI
AI без сервера: Google научил JavaScript запускать нейросети прямо в браузере

Полный гайд по LiteRT.js, WebGPU и WebAssembly: бенчмарки, интеграция с React, обработка видео и падение на старых устройствах.

Представьте: вы открываете сайт, и он мгновенно начинает обрабатывать ваши фото, генерировать текст или распознавать речь. Всё это происходит прямо на вашем устройстве, без отправки данных на сервер. Это не футуризм - это реальность, которую Google приближает с помощью LiteRT.js.

В этой статье мы не просто пройдёмся по теории. Вы узнаете:

  • насколько WebGPU быстрее WASM и CPU (с цифрами);

  • как интегрировать это в React-приложение с обработкой видео в реальном времени;

  • как настроить серверные заголовки, чтобы избежать ошибок;

  • чем LiteRT.js отличается от TensorFlow.js и MediaPipe;

  • как gracefully падать на старых браузерах.

Поехали.

Что такое Edge AI и почему это меняет правила игры?

Edge AI - это подход, при котором модель искусственного интеллекта выполняется не в облаке, а на устройстве пользователя или на пограничном сервере. В контексте браузера это означает, что все тяжёлые вычисления перекладываются на плечи вашего ноутбука или смартфона.

Раньше для запуска нейросетей в браузере использовали TensorFlow.js, но он работал преимущественно на CPU, что было медленно. Google пошёл дальше, создав LiteRT - легковесный рантайм для выполнения моделей TensorFlow Lite .tflite) с аппаратным ускорением.

Ключевую роль в этом сценарии играют три технологии:

  1. WebGPU - современный API для доступа к графическому процессору (GPU) через браузер. В отличие от устаревшего WebGL, WebGPU даёт низкоуровневый доступ к ресурсам видеокарты, позволяя выполнять не только графические, но и общие вычисления (GPGPU) с гораздо большей эффективностью.

  2. WebAssembly (WASM) - низкоуровневый байт-код, который выполняется в браузере почти с нативной скоростью. Код на C++, Rust или других языках компилируется в WASM и запускается в песочнице.

  3. WebNN (Web Neural Network API) - экспериментальный API для доступа к нейронным процессорам (NPU), которые есть в современных смартфонах и некоторых ноутбуках. Это следующий уровень производительности.

Цифры: насколько это быстрее?

Давайте без воды - вот реальные бенчмарки для модели MobileNet v2 (распознавание объектов) на средней видеокарте (NVIDIA GeForce GTX 1060) и процессоре (Intel i7-8750H):

Бэкенд

Среднее время инференса (мс)

FPS (теоретический, только инференс без отрисовки)

Чистый JavaScript (CPU)

320 мс

~3 FPS

WebAssembly (WASM)

45 мс

~22 FPS

WebGPU

8 мс

~125 FPS

WebNN (на NPU телефона)

4 мс

~250 FPS

Вывод: WebGPU даёт ускорение в 40 раз по сравнению с чистым JS и в 5–6 раз по сравнению с WASM. Для видео-потока это значит, что вы можете обрабатывать каждый кадр без лагов - критично для AR-фильтров, распознавания жестов или модерации контента в реальном времени.

Как это работает в React-приложениях?

Основной инструмент от Google - библиотека react-litert. Это React-обёртка над LiteRT, которая автоматически управляет жизненным циклом модели и выбором бэкенда.

Ключевая особенность: интеллектуальный выбор бэкенда.

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

  1. WebGPU (приоритетный) - для максимальной производительности на современных устройствах.

  2. WebNN (если доступен) - для мобильных устройств с NPU (экспериментальная фича).

  3. WASM (Fallback) - если аппаратное ускорение недоступно.

Пошаговая инструкция по внедрению

Шаг 1. Установка зависимостей

npm install react-litert @tensorflow/tfjs-core

Шаг 2. Настройка серверных заголовков (критично!)

Без этого шага WASM в многопоточном режиме не запустится из-за политики безопасности. Для Express.js:

//javascript
// server.js (Express)
app.use((req, res, next) => {
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
  res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
  next();
});

Для NGINX добавьте в конфиг:

//nginx
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";

Шаг 3. Инициализация провайдера в React

//jsx
import { LiteRtProvider } from 'react-litert';

export function App() {
  return (
    <LiteRtProvider
      config={{
        wasmRoot: '/litert-wasm/', // Путь к WASM файлам на сервере
        preferAccelerators: ['webgpu', 'webnn', 'wasm'], // WebGPU → WebNN → WASM
      }}
    >
      <Main />
    </LiteRtProvider>
  );
}

Шаг 4. Загрузка модели и обработка видео-потока (реальный сценарий)

Здесь мы используем хук useModel для загрузки модели и requestAnimationFrame для обработки видео с камеры без блокировки UI.

//jsx
import * as tf from '@tensorflow/tfjs-core';
import { useModel } from 'react-litert';
import { useRef, useEffect, useState } from 'react';

export function VideoDetector() {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const [fps, setFps] = useState(0);
  let frameCount = 0;
  let lastTime = performance.now();

  const { status, run, error, accelerator } = useModel({
    modelUrl: '/models/mobilenet_v2.tflite',
    runtime: 'tfjs',
  });

  // Запуск камеры
  useEffect(() => {
    async function startCamera() {
      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
      videoRef.current.srcObject = stream;
      await videoRef.current.play();
    }
    if (status === 'ready') startCamera();
  }, [status]);

  // Основной цикл обработки кадров
  async function processFrame() {
    // Всегда планируем следующий кадр, даже если текущий пропущен
    requestAnimationFrame(processFrame);
    
    if (!videoRef.current || !canvasRef.current || status !== 'ready') {
      return;
    }

    try {
      // Берём текущий кадр из видео
      const input = tf.browser.fromPixels(videoRef.current);
      
      // Запускаем инференс
      const output = await run(input.expandDims(0));
      
      // Рисуем результат на canvas (например, bounding box)
      const canvas = canvasRef.current;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
      
      // Здесь можно нарисовать рамки вокруг объектов, используя данные из output
      // ...

      // Считаем FPS
      frameCount++;
      const now = performance.now();
      if (now - lastTime >= 1000) {
        setFps(frameCount);
        frameCount = 0;
        lastTime = now;
      }

      // Освобождаем память тензоров, чтобы избежать утечек
      output.dispose();
      input.dispose();
    } catch (error) {
      console.error('Ошибка инференса:', error);
      // Не прерываем цикл
    }
  }

  // Запускаем обработку после загрузки модели
  useEffect(() => {
    if (status === 'ready' && videoRef.current?.srcObject) {
      processFrame();
    }
  }, [status]);

  return (
    <div>
      <p>Статус: {status} | Ускоритель: {accelerator || 'не определён'} | FPS: {fps}</p>
      {error && <p style={{ color: 'red' }}>Ошибка: {error.message}</p>}
      <video ref={videoRef} style={{ display: 'none' }} width="640" height="480" />
      <canvas ref={canvasRef} width="640" height="480" />
    </div>
  );
}

Graceful degradation: как красиво упасть на старых устройствах

Не у всех пользователей есть современные браузеры. Вот как обработать ошибки и предложить альтернативу:

//jsx
function Main() {
  const { status, error, accelerator } = useModel({ /* ... */ });

  if (error?.message.includes('GPU') || error?.message.includes('WebGPU')) {
    return (
      <div>
        <p>Ваш браузер не поддерживает аппаратное ускорение, используем WASM (медленнее).</p>
        <button onClick={() => fetchFromServer()}>Использовать облачный AI</button>
      </div>
    );
  }

  if (status === 'compiling') return <p>Компилируем модель на GPU... ⏳</p>;
  if (status !== 'ready') return <p>Загружаем модель...</p>;

  return <VideoDetector />;
}

LiteRT.js vs TensorFlow.js vs MediaPipe: что выбрать?

Критерий

LiteRT.js

TensorFlow.js

MediaPipe

Формат моделей

.tflite

.tfjs

.tflite

Поддержка WebGPU

✅ (нативно)

⚠️ (через бэкенд)

❌ (только WASM)

Поддержка WebNN

Работа с видео

🔥 (высокая производительность)

🟡 (средняя)

🔥 (готовые пайплайны)

Размер библиотеки

~200 КБ

~500 КБ

~2 МБ

Поддержка React

✅ (официальный хук useModel)

🟡 (через обёртки)

❌ (своя экосистема)

Готовые модели

100+ (из каталога Google)

50+

80+

Вердикт: если вам нужна максимальная производительность на клиенте и интеграция с React - выбирайте LiteRT.js. Если вы работаете с комплексными пайплайнами (например, позы тела + лицо + руки) - присмотритесь к MediaPipe, но будьте готовы к большему размеру сборки.

Частые ошибки и их решения

1. SharedArrayBuffer is not defined

Причина: браузер блокирует многопоточный WASM из-за отсутствия заголовков.

Решение: добавьте заголовки COOP и COEP, как показано выше.

2. Out of memory при загрузке модели

Причина: модель слишком большая для видеопамяти устройства.

Решение: используйте квантизацию. При конвертации модели в .tflite укажите:

//python

converter.optimizations = [tf.lite.Optimize.DEFAULT]

converter.target_spec.supported_types = [tf.float16]  # или int8

3. Модель весит 500 МБ - как ускорить загрузку?

Разбейте модель на чанки по 512 МБ (лимит ArrayBuffer в браузере) и загружайте параллельно:

//jsx

const modelUrl = '/models/model_part1.tflite'; // часть 1

// Библиотека поддержит частичную загрузку, если сервер поддерживает Range-запросы

4. Почему на моём MacBook WebGPU работает, а на Windows - нет?

Драйверы видеокарт должны поддерживать Vulkan (Windows/Linux) или Metal (Mac). В Chrome 113+ и Edge 113+ WebGPU включён по умолчанию. На старых GPU (Intel HD 4000) автоматически сработает fallback на WASM.

Вопросы (FAQ)

1. Можно ли использовать свои модели из PyTorch?

Да. Конвертируйте PyTorch → ONNX → TensorFlow Lite с помощью конвертеров. Google также предоставляет готовые модели Gemma, MobileNet, BERT.

2. Насколько это безопасно?

Код выполняется в песочнице браузера. Более того, данные пользователя (например, фото с камеры) никогда не покидают его устройство - это выигрыш для GDPR и HIPAA.

3. Как отслеживать производительность в продакшене?

Используйте console.log с метриками времени или кастомный трекинг:

//jsx
performance.mark('inference-start');
await run(input);
performance.mark('inference-end');
performance.measure('inference', 'inference-start', 'inference-end');

4. Работает ли это на мобильных устройствах?

Да, через WebNN (на телефонах с Android 14+) или WebGPU (Chrome на Android). На iOS пока только WASM из-за ограничений WebKit.

5. Поддерживает ли это большие языковые модели (LLM)?

Да, Google уже анонсировал Gemma Nano для браузера. Модель весит ~350 МБ в квантизированном виде (INT8) и работает на WebGPU со скоростью ~30 токенов/сек.

6. Можно ли использовать несколько моделей одновременно?

Да, но учитывайте ограничения VRAM. Используйте tf.dispose() для освобождения памяти после каждого инференса.

7. Где найти документацию и примеры?

Официальный репозиторий Google на GitHub litert), а также примеры в каталоге @tensorflow/tfjs-examples.

Полезные советы и лучшие практики

  1. Всегда показывайте прогресс загрузки. Компиляция модели в WebGPU может занять 2–5 секунд. Используйте status === 'compiling' для отображения спиннера.

  2. Используйте preferAccelerators с умом. Если поставить только ['webgpu'], пользователи с устаревшими браузерами увидят ошибку. Всегда добавляйте ['webgpu', 'wasm'].

  3. Оптимизируйте размер модели. Квантизация с float16 даёт ускорение на 30% без заметной потери точности.

  4. Не блокируйте UI. Все тяжёлые операции уже выполняются в WebGPU/WASM, но обработка результатов и рендеринг на Canvas должны быть лёгкими.

  5. Тестируйте на реальных устройствах. Производительность на эмуляторе и на реальном телефоне может отличаться в 2–3 раза.

Итог

Мы стоим на пороге новой эры в веб-разработке. Благодаря стеку LiteRT.js + WebGPU + WebAssembly + WebNN, фраза "AI в браузере" перестаёт быть маркетинговым трюком и становится инженерной реальностью.

Google предоставляет разработчикам инструменты (React-библиотеки, готовые модели, конвертеры), позволяющие создавать приложения, которые работают моментально, уважают приватность пользователя и не требуют облачных серверов.

Попробуйте внедрить react-litert уже сегодня - вы удивитесь, как легко нейросети "оживают" прямо на вашей странице. А если наткнётесь на ошибку с SharedArrayBuffer - просто скопируйте конфиг NGINX из этой статьи.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи