Different Language

[Запуск ИИ-моделей локально в браузере с помощью WebGPU и WebAssembly]

Анализировать с AI

Получите инсайты с помощью AI из этой технической статьи Mad Devs:

В разговорах о фронтенд-разработке постоянно возникает один и тот же момент. Кто-то предлагает реализовать ИИ-фичу: автодополнение, классификацию изображений в реальном времени, локальный чат-ассистент – и первый порыв: взять готовый API. Поднять бэкенд, выбрать хостинговую модель, подключить вызовы. Но потом наступает реальность: задержки, стоимость каждого инференса, данные, покидающие устройство пользователя, и растущий счёт за сервер, который масштабируется с каждым взаимодействием.

Неудобная правда состоит в том, что для определенного и всё расширяющегося класса ИИ-задач сервер вам вообще не нужен. Браузер пользователя способен запускать модель на его собственном GPU и выдавать результаты локально. Без лишних запросов. Без платы за каждый вызов. Без утечки данных.

Это руководство – практическое пособие о том, как этого добиться. Мы разберем, почему WebGPU изменил расстановку сил, как WASM заполняет пробелы там, где GPU недоступен, и как написать реальный код инференса, который работает в продакшне уже сегодня.

Небольшая оговорка перед тем, как вы начнете:

Автор этого руководства профессионал, но не технический специалист. Текст написан копирайтером с помощью Claude, ChatGPT, и Antigravity, от теории до полностью рабочего кода, а затем его проверили настоящие инженеры. Так что вы в надёжных руках. Читайте свободно, судите снисходительно.

WebGPU, WASM и расцвет браузерного ИИ

Типичные ИИ-фичи на основе API несут скрытые издержки, которые нарастают с масштабом:

  • Задержки – сетевые round trip плюс троттлинг при высокой нагрузке.
  • Стоимость каждого инференса – растет линейно с каждым взаимодействием пользователя.
  • Соответствие требованиям и доверие – данные пользователя покидают устройство. Для медицинских карт, юридических документов или всего, что пользователи ожидают хранить в тайне, это не просто проблема задержек – это проблема ответственности.
  • Лишняя бэкенд-инфраструктура – инфраструктура, которая существует исключительно для того, чтобы пересылать промпты хостинговой модели.

Современные браузеры умеют запускать модели локально – на GPU пользователя через WebGPU или на CPU через WASM, а файлы модели кешируются после первой загрузки. Никакой платы за каждый вызов, никаких round trip, и конфиденциальные данные остаются на устройстве.

WebGPU против WebGL: зачем ML потребовался новый GPU-API

Большую часть истории веба реализовывать серьёзные вычисления в браузере означало работать только на JavaScript. Когда машинное обучение начало набирать обороты, вопрос об инференсе в браузере возник сам собой, но честный ответ тогда звучал так: инструменты ещё не готовы.

WebGL существовал, но был спроектирован для рендеринга треугольников, а не тензорных операций.

WebAssembly появился в 2017 году и открыл возможность запускать скомпилированный C++ и Rust в браузере, что сделало лёгкие модели жизнеспособными. Но без реального доступа к GPU всё, что выходило за рамки небольших сверточных сетей, работало мучительно медленно. Классифицировать изображение возможно. Запустить языковую модель? Нет.

Перелом произошел постепенно, а потом – все сразу. Браузеры начали поставляться с WebGPU – современным вычислительным API, открывающим JavaScript-программам настоящий доступ к GPU. Не обертка над графикой, а полноценный вычислительный интерфейс с шейдерными конвейерами, созданными для таких задач, как матричное умножение. Параллельно ML-экосистема пошла в сторону меньших, агрессивно квантованных моделей: Phi-3.5 Mini с 3,8 млрд параметров умещается в 2 ГБ VRAM в формате int4. Whisper Small транскрибирует аудио в реальном времени на среднем ноутбуке. Модели опустились до ограничений браузера в тот самый момент, когда возможности браузера поднялись до уровня моделей.

В результате в 2025 году получился стек, который действительно работает. Библиотеки вроде Transformers.js, WebLLM и ONNX Runtime Web убирают сложность на уровне абстракции, но понимать, что находится под капотом; WebGPU для GPU-инференса и WASM как резервный вычислительный слой, – важно, когда что-то идёт не так, когда вы отлаживаете производительность или решаете, какие модели вообще стоит пробовать.

// Вычислительный шейдер WebGPU для простого матричного умножения
// WGSL — шейдерный язык, используемый WebGPU

@group(0) @binding(0) var<storage, read> A : array<f32>;
@group(0) @binding(1) var<storage, read> B : array<f32>;
@group(0) @binding(2) var<storage, read_write> C : array<f32>;

struct Dims { M: u32, N: u32, K: u32 }
@group(0) @binding(3) var<uniform> dims : Dims;

@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
    let row = gid.x;
    let col = gid.y;
    if (row >= dims.M || col >= dims.N) { return; }

    var sum = 0.0;
    for (var k = 0u; k < dims.K; k++) {
        sum += A[row * dims.K + k] * B[k * dims.N + col];
    }
    C[row * dims.N + col] = sum;
}

Этот шейдер вы бы не писали вручную, библиотеки всё делают сами, но он иллюстрирует ключевую идею. Рабочая группа запускает 256 потоков одновременно (16×16), каждый вычисляет одну выходную ячейку результирующей матрицы. Именно эта вычислительная модель делает слои нейронных сетей быстрыми. WebGL не мог выразить это напрямую. WebGPU был создан именно для этого.

WASM + WebGPU: архитектура для ИИ-фронтенда с приоритетом приватности

В обычных разговорах о браузерном ИИ акцент делается на возможностях: а он вообще запустится? Куда более интересный вопрос для продакшн-приложений – приватность: куда уходят данные? Если вы строите инструмент, обрабатывающий медицинские записи, юридические документы или всё, что пользователь обоснованно ожидает оставить на своём устройстве, отправка данных в хостинговый API – это не просто проблема задержек. Это проблема соответствия требованиям и доверия.

Браузерный ИИ полностью меняет направление потока данных. Файл модели загружается один раз и кешируется локально. После этого каждый инференс выполняется на оборудовании пользователя: на его CPU через WASM или на GPU через WebGPU. Ничего не покидает устройство.

Браузер пользователя
│
├── Код вашего приложения (React / Vanilla / что угодно)
│
├── ML-библиотека (Transformers.js / WebLLM / ONNX Runtime)
│   │
│   ├── WebGPU-бэкенд ──► GPU пользователя (быстро, требует поддержки)
│   └── WASM-бэкенд ──► CPU пользователя (медленнее, работает везде)
│
└── Cache API / IndexedDB
    └── Файлы модели (кешируются после первой загрузки, больше не передаются)

В архитектуре предусмотрены два пути выполнения. WebGPU – основной: прямые GPU-вычисления, в 3-10 раз быстрее WASM-альтернативы. WASM – резервный: скомпилированная ML-среда выполнения (обычно порт ONNX Runtime или llama.cpp) на CPU. Современный WASM с расширениями SIMD может обрабатывать 4-8 значений за инструкцию, что делает малые и средние модели жизнеспособными на CPU без ощущения тормозов.

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

Один инфраструктурный нюанс, который часто ловит людей врасплох: и WebGPU, и многопоточный WASM требуют SharedArrayBuffer, который браузер включает только при условии Cross-Origin Isolation. Это означает два HTTP-заголовка, которые должны присутствовать в каждом ответе вашего origin:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Без этих заголовков SharedArrayBuffer недоступен, инициализация WebGPU либо завершится ошибкой, либо будет сильно ограничена, а многопоточный WASM молча откатится в однопоточный режим. В локальной отладке это неочевидно: Chrome DevTools не всегда явно показывает это ограничение. Простейшая проверка:

console.log('Cross-Origin Isolated:', window.crossOriginIsolated);
// Must be true. If false, check your server headers.

Для Vite добавьте заголовки и настройте формат воркера в vite.config.ts:

import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
  worker: {
    format: 'es',
  },
});

Настройка worker.format: 'es' гарантирует, что Web Workers будут собираться как ES-модули – это необходимо для корректной работы импортов Transformers.js внутри воркер-файлов.

Для Nginx в продакшне:

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

Флаг always важен: без него Nginx добавляет заголовки только в ответах со статусом 200, а значит, страницы ошибок не будут изолированы и некоторые браузеры могут отказаться сохранять контекст изоляции.

COEP может заблокировать загрузку моделей с внешних источников

Это место, где люди обычно спотыкаются в продакшне. При включенном Cross-Origin-Embedder-Policy: require-corp браузер блокирует любой кросс-origin-ресурс, в ответе которого отсутствуют совместимые заголовки CORS/CORP. Это касается и файлов весов модели, загружаемых с CDN или напрямую с Hugging Face.

Если вы получаете модели с huggingface.co или другого стороннего источника, их ответы должны включать Access-Control-Allow-Origin и, желательно, Cross-Origin-Resource-Policy: cross-origin. Некоторые CDN устанавливают эти заголовки, но многие нет.

Наиболее надежный путь для продакшна: размещайте файлы моделей на собственном origin. Скачайте веса ONNX, раздавайте их с того же домена, что и приложение, и ограничение COEP перестает быть проблемой. Это также дает вам контроль над заголовками кеширования, версионированием и доступностью, что немаловажно, когда файлы модели весят сотни мегабайт.

Проверка WebGPU: определение поддержки и запуск первой модели

Прежде чем писать код инференса, стоит разобраться, что именно вы проверяете. "WebGPU поддерживается" – это не бинарный ответ. API может быть доступен, но адаптер может быть программным. Адаптер может быть реальным аппаратным, но с очень ограниченными размерами буферов. Эти различия важны при выборе модели.

Определение WebGPU (без устаревших API)

Утилита для определения, которая сообщает не просто "WebGPU есть", а с каким именно GPU вы работаете:

// src/utils/webgpu.ts

export interface GPUCapabilities {
    available: boolean;
    backend?: string;
    vendor?: string;
    device?: string;
    maxBufferSize?: number;
    maxStorageBinding?: number;
    isSoftware?: boolean;
}

export async function detectWebGPU(): Promise<GPUCapabilities> {
    if (!('gpu' in navigator)) return { available: false };

    const adapter = await navigator.gpu.requestAdapter({
        powerPreference: 'high-performance',
    });
    if (!adapter) return { available: false };

    const device = await adapter.requestDevice();

    const info = (adapter as any).info ?? {};
    const vendor = info.vendor ?? undefined;
    const deviceName = info.device ?? undefined;
    const backend = info.backend ?? undefined;

    const isSoftware =
        typeof vendor === 'string' && vendor.toLowerCase().includes('software');

    return {
        available: true,
        vendor,
        device: deviceName,
        backend,
        maxBufferSize: device.limits.maxBufferSize,
        maxStorageBinding: device.limits.maxStorageBufferBindingSize,
        isSoftware,
    };
}

Не используйте adapter.requestAdapterInfo() – этот метод удалён из спецификации. В ранних черновиках WebGPU метаданные адаптера были доступны через этот асинхронный метод, но в текущих реализациях они перенесены в синхронное свойство adapter.info. Код выше использует (adapter as any).info с запасным вариантом, чтобы обработать оба случая и избежать ошибок типизации с определениями @webgpu/types, которые могут отставать от реализаций в браузерах.

Выбор бэкенда и типа данных

Располагая информацией о возможностях, можно принять обоснованное решение о бэкенде и размере модели. Прагматичный набор правил: если доступен реальный WebGPU – используйте webgpu + q4f16 (хороший баланс скорости и качества). Иначе откатывайтесь на wasm + q8 при наличии SIMD, или на fp32 как самый надежный вариант по умолчанию.

export async function chooseBackend(): Promise<{ backend: 'webgpu' | 'wasm'; dtype: any }> {
    const gpu = await detectWebGPU();

    if (gpu.available && !gpu.isSoftware) {
        return { backend: 'webgpu', dtype: 'q4f16' };
    }

    const simdAvailable = WebAssembly.validate(
        new Uint8Array([
            0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3,
            2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11
        ])
    );

    return { backend: 'wasm', dtype: simdAvailable ? 'q8' : 'fp32' };
}

Проверка SIMD заслуживает пояснения. Этот непрозрачный массив байтов – минимально валидный WebAssembly-модуль, использующий инструкцию SIMD (v128.const). Если WebAssembly.validate() возвращает true, браузер поддерживает SIMD, и вы можете использовать INT8-квантованные модели на CPU-пути, что примерно в 2-4 раза быстрее FP32-WASM без SIMD. Все современные браузеры это поддерживают, но старые устройства и некоторые встроенные WebView – нет.

Запуск первой модели с Transformers.js

Transformers.js – самая простая точка входа: это порт библиотеки Hugging Face Transformers на JavaScript с поддержкой WebGPU, загружающий модели напрямую с HF Hub. Следующий код запускает классификацию тональности от начала до конца:

// src/classifier.ts
import { pipeline, env } from '@huggingface/transformers';

if (env.backends.onnx.wasm) {
    env.backends.onnx.wasm.proxy = false;
}

export async function classify(text: string) {
    const classifier = await pipeline(
        'text-classification',
        'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
        { device: 'webgpu', dtype: 'q8' }
    );

    return classifier(text);
}

// First call takes ~3-5 seconds (model load + first inference)
// Subsequent calls: ~20ms on GPU, ~120ms on CPU WASM
const result = await classify("The documentation for this library is actually quite good.");
// [{ label: 'POSITIVE', score: 0.9987 }]

Обратите внимание на проверку env.backends.onnx.wasm: в некоторых конфигурациях бандлера объект ONNX WASM-бэкенда может быть не инициализирован во время импорта, и прямой доступ к .proxy может выбросить исключение. Условная проверка предотвращает ошибку во время выполнения, не меняя поведения – когда бэкенд доступен, режим прокси отключается, чтобы модель запускалась напрямую в текущем контексте, не порождая дополнительный воркер.

Генерация текста в Web Worker

Для более тяжелых задач (генерация текста, локальный чат) модель лучше запускать в Web Worker, чтобы не блокировать основной поток. Схема проста: генерация в воркере, токены стримятся обратно.

// src/worker.ts
import { pipeline, TextStreamer, env } from '@huggingface/transformers';

if (env.backends.onnx.wasm) {
    env.backends.onnx.wasm.proxy = false;
}

let generator: any = null;

self.onmessage = async ({ data }) => {
    if (data.type === 'load') {
        try {
            generator = await pipeline('text-generation', data.model, {
                device: 'webgpu',
                dtype: 'q4',
                progress_callback: (info: any) => {
                    if (info.status === 'downloading') {
                        self.postMessage({
                            type: 'progress',
                            file: info.file,
                            progress: Math.round((info.loaded / info.total) * 100),
                        });
                    }
                },
            });

            self.postMessage({ type: 'ready' });
        } catch (e) {
            self.postMessage({ type: 'error', error: e });
        }
        return;
    }

    if (data.type === 'generate') {
        if (!generator) {
            self.postMessage({ type: 'error', error: 'Model not loaded' });
            return;
        }

        const streamer = new TextStreamer(generator.tokenizer, {
            skip_prompt: true,
            callback_function: (token: string) => self.postMessage({ type: 'token', token }),
        });

        await generator(data.prompt as string, {
            max_new_tokens: 512,
            temperature: 0.7,
            streamer,
        });

        self.postMessage({ type: 'done' });
    }
};

Обработчик load обернут в try/catch – загрузка модели может завершиться ошибкой по десяткам причин (таймаут сети, неподдерживаемый оператор, нехватка памяти GPU), а необработанный reject внутри воркера остаётся незамеченным, если его явно не поймать. Обработчик generate проверяет, что модель действительно загружена, прежде чем запускать инференс, и отправляет типизированное сообщение об ошибке в основной поток вместо краша.

На стороне основного потока:

// src/main.ts (excerpt)
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });

worker.onmessage = (e) => {
    const { type, token, progress, file, error } = e.data;

    if (type === 'progress') {
        updateLoadingUI(`Downloading ${file}: ${progress}%`);
    } else if (type === 'ready') {
        enableGenerateButton();
    } else if (type === 'token') {
        appendToOutput(token);
    } else if (type === 'done') {
        finalizeResponse();
    } else if (type === 'error') {
        showError(error);
    }
};

worker.postMessage({ type: 'load', model: 'Xenova/gpt2' });

На этом этапе у вас есть рабочий конвейер: модель загружается один раз, кешируется в браузере, запускается локально через WebGPU. Если WebGPU недоступен, Transformers.js автоматически откатывается на WASM. Пользователь получает инференс, ни разу не обратившись к вашему серверу.

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

Честное ограничение браузерного ИИ – память. Вкладка браузера не получает весь GPU. WebGPU-адаптер, как правило, имеет доступ к 1-4 ГБ VRAM на десктопе, меньше на мобильных устройствах. Это значит, что выбор модели и квантование – не факультативные вопросы. Они определяют, будет ли ваша фича работать вообще.

Квантование

Квантование – главный рычаг управления. Модель в формате FP32 занимает четыре байта на параметр. INT8 (q8) – один байт: в 4 раза меньше при минимальной потере качества. INT4 (q4) – полбайта: в 8 раз меньше, с некоторой деградацией на сложных задачах рассуждения, но, как правило, достаточно для классификации, суммаризации и диалоговых задач.

Таблица ниже показывает память только для весов при модели в 3 млрд параметров. Общая память во время выполнения также включает KV-кеш и промежуточные буферы – оставляйте запас сверх этих цифр.

ФОРМАТ ПАМЯТЬ (3B ПАРАМЕТРОВ) КОМПРОМИСС ПО КАЧЕСТВА ПОДХОДИТ ДЛЯ
fp32 ~12 ГБ Эталон Не практично в браузере
fp16 ~6 ГБ Минимальная потеря Набольшие модели на мощных GPU
q8 ~3 ГБ Очень малая потеря Классификация, кодирование
q4 ~1.5 ГБ Заметна в рассуждениях Чат, генерация
q4f ~1.7 GB Хороший баланс Продакшн-инференс LLM

Квантование со смешанной точностью (меньшая точность для весов, большая – для активаций) даёт лучшее из обоих миров. Transformers.js и WebLLM поддерживают настройку типов данных на уровне отдельных компонентов:

import { pipeline } from '@huggingface/transformers';

const generator = await pipeline('text-generation', 'Xenova/Qwen2.5-1.5B-Instruct', {
  device: 'webgpu',
  dtype: {
    embed_tokens: 'fp16',   // embeddings are sensitive -- keep precision
    lm_head: 'fp32',        // output layer -- keep full precision for output quality
    default: 'q4',          // all other weights -- aggressively quantize
  },
});

Кеширование моделей: избегайте катастрофы при первой загрузке

Кеширование модели заслуживает такого же внимания, как и сам инференс. При первой загрузке пользователь скачивает сотни мегабайт. При каждом следующем визите он должен попадать в локальный кеш. Transformers.js использует Cache API автоматически, но вам нужно выстроить окружающий UX и заранее обработать несколько проблем, иначе они превратятся в продакшн-инцидент:

  • UX загрузки с прогрессом – показывайте прогресс, не оставляйте пользователей смотреть на крутящийся индикатор.
  • Проверка квоты – убедитесь в наличии места перед началом загрузки.
  • Сценарий вытеснения – браузеры могут очищать кешированные данные под давлением хранилища, и пользователи должны знать, когда происходит повторная загрузка.
// src/utils/storage.ts
export async function checkStorageBeforeLoad(modelSizeMB: number): Promise<boolean> {
    const estimate = await navigator.storage.estimate();
    const availableMB = ((estimate.quota ?? 0) - (estimate.usage ?? 0)) / 1024 / 1024;

    if (availableMB < modelSizeMB * 1.3) {
        console.warn(`Not enough storage. Need ~${modelSizeMB}MB, have ${Math.round(availableMB)}MB`);
        return false;
    }
    return true;
}

Когда модели обновляются между версиями, старые кешированные веса накапливаются. Функция очистки не даёт кешу расти бесконтрольно:

async function clearOldModelVersions(currentCacheName: string) {
    const keys = await caches.keys();
    await Promise.all(
        keys
            .filter(k => k.startsWith('transformers-cache-') && k !== currentCacheName)
            .map(k => caches.delete(k))
    );
}

Нижняя граница производительности

Нижнюю границу производительности стоит обозначить явно. На MacBook Pro M2 с WebGPU модель Phi-3.5 Mini в формате q4 генерирует около 25-35 токенов в секунду. На среднем ноутбуке с Windows и интегрированным Intel GPU та же модель выдает 8-12 токенов в секунду. На старом iPhone через WASM (без WebGPU) – 3-6 токенов в секунду. Это реальные цифры, а не результаты тестов в идеальных условиях. Ваш UX должен быть рассчитан на медленный случай, а не оптимизирован под быстрый.

Практическое следствие: стримьте токены по мере генерации, никогда не ждите полного ответа. Пользователь, наблюдающий за появлением текста со скоростью 8 токенов в секунду, ощущает это как реальное время. Пользователь, смотрящий на крутящийся индикатор 10 секунд, а потом получающий ответ целиком – нет.

Что дальше для браузерного ИИ на WebGPU (2026 и далее)

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

Во-первых, модели продолжают уменьшаться без потери качества. C Phi-4 Mini, Gemma 3 2B, Qwen 2.5-0.5B исследовательское сообщество получает по-настоящему хорошие результаты от моделей, достаточно малых для комфортной работы в 1-2 ГБ памяти браузера. Год назад для запуска полезной языковой модели локально требовалось 7 млрд параметров. Сегодня можно получить удивительно способные ответы от модели в 500 млн параметров, если она хорошо дистиллирована. Нижняя граница продолжает снижаться.

Во-вторых, поддержка WebGPU расширяется. Chrome и Edge – надежны. Firefox включил WebGPU по умолчанию в середине 2024 года. Safari отстаёт, но догоняет, а у Apple есть сильные стимулы хорошо реализовать WebGPU с учетом их оборудования. Раздел "проверьте поддержку сначала" в этом руководстве существует потому, что мы все еще в той фазе, когда нельзя принимать поддержку как данность. Эта фаза закончится в течение двух лет.

В-третьих, инструментальный слой созревает. WebLLM уже реализует PagedAttention и FlashAttention в WGSL-шейдерах, что повышает эффективность использования GPU-памяти и делает более длинные контекстные окна реальностью. ONNX Runtime Web добавляет поддержку всё большего числа типов операторов, расширяя спектр моделей, которые можно запускать без специальной адаптации. Разрыв между "что работает на сервере" и "что работает в браузере" сокращается.

Что это значит практически: сценарии использования, актуальные сегодня – локальные чат-ассистенты, транскрипция аудио в реальном времени, классификация изображений, текстовые эмбеддинги, – будут расширяться. Мультимодальные модели с поддержкой зрения появляются в Transformers.js. Диффузионные модели уже работают в браузере через ONNX, но пока медленно. Автодополнение кода на уровне IDE может стать разумным на стороне браузера, если качество моделей сохранится.

Ограничение, которое никуда не денется, – память. Вкладки браузера имеют лимиты, которых у десктопных приложений нет. Модель на 70 млрд параметров не будет работать локально во вкладке в обозримом будущем. Но актуальный вопрос — не "можно ли запустить GPT-4 в браузере", а "можно ли запустить модель, достаточно хорошую для конкретной задачи". Для всё большего числа задач ответ в 2026 году – да.

Для фронтенд-разработчиков фреймворк принятия решений проще, чем кажется. Если ваша фича работает с конфиденциальными данными, или вы обслуживаете пользователей с постоянным доступом к GPU, или API-расходы уже стали проблемой при вашем масштабе – начинайте с браузерного инференса. Инструменты стабильны, модели дееспособны, история о приватности настоящая. Для всего остального хостинговый API по-прежнему имеет смысл: проще, предсказуемее, без штрафа за первую загрузку.

Интересная "серая зона" – гибридный подход: небольшая локальная модель для задач с малой задержкой (автодополнение, классификация), эскалация к хостинговой модели только для сложной генерации или рассуждений. Такая архитектура даёт преимущества по задержкам от локального инференса, не ставя весь продукт в зависимость от браузерного ИИ в каждом сценарии.

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

Чеклист для продакшна

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

  1. COOP/COEP включеныwindow.crossOriginIsolated === true и в разработке, и в продакшне.
  2. Стратегия хостинга моделей совместима с COEP – если получаете модели со стороннего CDN, убедитесь в наличии заголовков CORS/CORP. В большинстве случаев самохостинг на собственном origin – самый надежный путь.
  3. Определение WebGPU использует adapter.info – не устаревший requestAdapterInfo().
  4. Генерация выполняется в Worker – со стримингом вывода и обработкой ошибок.
  5. Настройки квантования выбраны под каждый бэкенд – q4f16 для WebGPU, q8 для WASM+SIMD, fp32 как крайний вариант.
  6. UX кеша и хранилища – проверка квоты перед загрузкой, индикация прогресса при первой загрузке, осознание того, что браузеры могут вытеснять кешированные данные при нехватке места.
ПРОВЕРЕНО ТЕХНИЧЕСКИМИ ЭКСПЕРТАМИ: