Client-side AI: новые возможности вашего браузера

AI WebGPU WebAssembly Web Worker Cache Storage BrowserAI @mlc-ai/webllm Transformers.js Wllama LLM.js

Эта статья — дополнение к моему докладу на «Я 💛 Фронтенд» 2026 Локальные модели: как запустить ИИ в браузере. Это был мой первый доклад, так что я рада, что все прошло хорошо, и я получила много поддержки от команды конференции и слушателей, за что очень благодарна. Теперь я хочу подробнее раскрыть тему локальных моделей, потому что она довольно обширная, и за полчаса доклада, конечно, было сложно рассказать все.

Часть 1. Основные принципы и примеры работы локальных моделей

Введение

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

  • Использовать провайдеров моделей (например, Openrouter) или обращаться напрямую в API моделей (например, API Chatgpt или Deepseek).
  • Поднимать на своих серверах модель, искать под нее мощности, инфраструктуру, специалистов.

И это нормальный выбор, когда вам нужен полноценный ИИ-функционал для генерации больших текстов, обработки большого количества данных.

Но… Что, если вам бы хотелось встроить в свое приложение небольшую интерактивную функциональность, например, распознавание голосового ввода, перевод выбранных участков текста, распознавание объектов на изображении, но при этом у вас нет ресурсов на то, чтобы поднять модель на своем сервере, а отправлять данные в сторонние API запрещено политикой компании (например, вы разрабатываете корпоративное приложение), или вы просто не хотите платить за вызовы сторонних API. Что, если есть вариант, где возможности ИИ в небольшом объеме могут быть реализованы полностью на клиенте? Это может быть полезно, когда вы не хотите или не можете использовать серверные модели, и при этом ваша задача требует интерактивности.

Итак, мы приходим к термину client-side AI (client-side models) — ИИ, инференс которого происходит на клиенте. Этот термин мне кажется наиболее точным, по аналогии с клиентскими и серверными компонентами. Альтернативный термин — local-first AI (local-first models). Инференс (inference) в искусственном интеллекте — это процесс использования обученной нейросети или ML-модели для обработки новых данных и получения результатов в реальном времени.

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

Схема работы с серверными моделями

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

    sequenceDiagram
	    participant U as Пользователь
	    participant B as Браузер
	    participant S as Сервер
	    participant AI as AI-модель<br>на сервере
	
	    U->>B: Ввод запроса
	    B->>S: HTTP-запрос
	    S->>AI: Инференс
	    AI-->>S: Результат
	    S-->>B: Ответ
	    B-->>U: Отображение результата

Схема работы с client-side моделями

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

    sequenceDiagram
	    participant U as Пользователь
	    participant B as Браузер
	    participant C as Кэш браузера
	    participant S as Сервер
	    participant AI as AI-модель<br>в браузере
	
	    B->>C: Проверка кэша
	    alt Модель не в кэше
	        B->>S: Загрузка файлов модели
	        S-->>B: Файлы модели
	        B->>C: Сохранение в кэш
	    else Модель в кэше
	        C-->>B: Файлы модели из кэша
	    end
	    B->>AI: Инициализация модели
	
	    U->>B: Ввод запроса
	    B->>AI: Инференс (WebGPU / WASM)
	    AI-->>B: Результат
	    B-->>U: Отображение результата

WebGPU и client-side AI

Совсем недавно API WebGPU получило поддержку во всех основных браузерах, что дало возможность использовать мощности GPU через веб-API браузера. Благодаря этому теперь можно намного более эффективно встраивать AI в интерфейсы без увеличения нагрузки на сервер. В стандартной схеме, когда пользователь в браузере только пишет запрос, а модель расположена на сервере компании и отдает ответ по API, рост затрат на мощности сервера (или затраты на API провайдеров сторонних моделей) растут соответственно увеличению количества пользователей. Запуск моделей через WebGPU на мощностях GPU пользователя позволяет избежать этого.

Поддержка WebGPU пока не 100%, и в Linux WebGPU доступен только за флагами, но скоро поддержка станет больше, и поэтому мне бы хотелось рассказать, как связан WebGPU и client-side AI, и какие возможности мы получаем уже сейчас.

Что нужно сделать, чтобы получить client-side AI? На самом деле, не так много.

  1. Выбрать библиотеку, которая возьмет на себя всю сложную работу (загрузка модели, кэширование, оптимизация).
  2. Подключить ее.
  3. Использовать.

Вуаля, вы великолепны! Ладно, на самом деле для некоторых кейсов этот путь будет сложнее, но в целом путь такой. Сначала я приведу несколько примеров, чтобы было понятно, что client-side AI вообще работает.

Запуск локальных моделей

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

  1. WebGPU – современный низкоуровневый веб-стандарт и API для JavaScript, обеспечивающий прямой высокопроизводительный доступ к графическому процессору (GPU) в браузере.
  2. WebAssembly (WASM) — бинарный формат, который позволяет запускать в браузере код написанный на языках вроде C++, Rust или Go с почти нативной скоростью. В контексте client-side AI именно в WASM компилируются некоторые рантаймы для запуска моделей (например, llama.cpp или Apache TVM), а также выполняются вспомогательные операции — токенизация, управление памятью, планирование вычислений на GPU.

Также существует API WebNN — это новый веб-стандарт, позволяющий веб-приложениям и фреймворкам ускорять глубокие нейронные сети с помощью GPU, CPU или специально созданных акселераторов ИИ, таких как нейронные процессоры (NPUs). Это API пока находится в разработке, актуальный статус поддержки можно посмотреть здесь: WebNN requirements.

WebGPU работает в:

  • последних версиях браузеров на Chromium (Chrome, Edge, etc.) на Windows, Android;
  • в последних версиях Firefox на Windows, Mac (macOS 26+, Apple Silicon);
  • в последних версиях Safari (macOS, iOS/iPadOS, visionOS).

На остальных платформах разработка пока в процессе. Чтобы использовать с Linux, необходимо включить флаги:

Для браузеров на Chromium:

  • chrome://flags/#enable-unsafe-webgpu
  • chrome://flags/#ignore-gpu-blocklist

Для Firefox:

  • dom.webgpu.enabled
  • gfx.webgpu.ignore-blocklist

Актуальный статус поддержки WebGPU можно посмотреть здесь: https://github.com/gpuweb/gpuweb/wiki/Implementation-Status

WebAssembly сейчас поддерживается всеми основными браузерами, поэтому это стабильный фолбек для использования локальных моделей, но инференс через него будет медленнее, так как WebGPU использует мощности GPU устройства пользователя, что дает прирост в скорости, тогда как через WebAssembly доступны только мощности CPU, что значительно медленнее.

Демо

Разные библиотеки дают доступ либо к WebGPU, либо к WebAssembly, либо к тому и другому сразу. Поэтому я приведу несколько примеров, которые будут разделены на две группы: с поддержкой и без поддержки WebGPU, чтобы вы могли посмотреть демо, даже если под рукой нет браузера с поддержкой. Для демо я добавлю ссылки на репозиторий — можно поднять демо у себя и подробнее посмотреть код (иногда демо с huggingface могут плохо загружаться без VPN).

В большинстве демо реализован такой алгоритм:

  1. Вы выбираете модель, нажимаете “Load”
  2. Загружаются веса и ресурсы модели — первый раз с сервера, в последующих из локального хранилища (обычно Cache Storage)
  3. Инициализируется бэкенд (WASM или WebGPU) — выделяются буферы, загружаются тензоры в память/на GPU, при WebGPU создаются pipeline/шэйдеры (compilation) и GPU буферы.
  4. Прогрев (warming up) (опционально) — перед первым реальным запросом в модель отправляется короткий тестовый ввод. Это позволяет браузеру заранее скомпилировать и закэшировать GPU-шейдеры, чтобы первый настоящий запрос пользователя не был медленным.
  5. Вы выбираете задачу или начинаете чат — после этого формируется prompt и токенизируется вход.
  6. Происходит инференс (генерация) — пошаговая генерация токенов (может быть streaming, когда токены выводятся динамически по мере генерации), с поведением, зависящим от backend (WASM — CPU; WebGPU — GPU)
  7. Пост-обработка и вывод ответа — детокенизация, рендер в UI; часто происходит в Web Worker для не блокирующего UI.

Локальные модели, как и серверные, могут работать с разными типами данных, например с текстом (LLM — Large Language Model), аудио (Speech-to-Text/ASR – автоматическое распознавание речи, Text-to-Speech), изображениями (VLM — Vision Language Model), видео (Video Models), а также с несколькими типами данных одновременно (мультимодальные модели).

Демо для браузеров с поддержкой WebGPU

Работа с текстом

Интерфейс демо BrowserAI

Демонстрация интерфейса демо BrowserAI

Это демо довольно подробно показывает статистику использования модели, поэтому я поставила его первым. Доступны такие параметры, как:

  • Memory Usage: 35.9MB of 2144MB — сколько оперативной памяти сейчас использует модель и какой приблизительный лимит доступен в браузере.
  • Last Response Time: 16535ms — сколько времени заняла генерация последнего ответа от момента отправки запроса до получения полного текста.
  • Tokens per Second: 26.6 tokens/s — скорость генерации модели, сколько токенов текста она создаёт в секунду (основной показатель производительности).
  • Selected Model: smollm2-135m-instruct — текущая загруженная языковая модель, небольшая инструкционная модель примерно на 135 млн параметров.
  • Model Load Time: 26.8s — сколько времени заняла загрузка и инициализация модели (скачивание весов, распаковка и подготовка к инференсу).
  • Peak Memory Usage: 36.4MB — максимальный объём памяти, который модель занимала с момента запуска.
  • Response Time History — история времени ответа предыдущих запросов, используется для анализа производительности.
  • Processing Mode: Web Worker (Background Thread) — режим выполнения, где модель работает в отдельном потоке браузера (Web Worker), чтобы не блокировать основной UI-поток страницы.

Конец чата в демо BrowserAI

В конце чата отображается итоговое количество токенов ответа — 440

Статистика параметров использования модели:

  • Prompt: 27 — количество токенов во входном запросе (ваш текст, который отправляется модели).
  • Completion: 302 — количество токенов, которые модель сгенерировала в ответе.
  • Total: 329 — общее количество токенов, обработанных моделью (Prompt + Completion).
  • Prefill: 15 tok/s — скорость обработки входного текста (prompt) перед началом генерации ответа. На этом этапе модель пропускает все токены запроса через слои модели, чтобы сформировать внутренние представления и заполнить KV-cache, который затем используется при генерации.
  • Decoding: 7 tok/s — скорость генерации ответа, когда модель по одному токену предсказывает следующий токен (это обычно медленнее и именно эта скорость определяет, как быстро появляется текст).

Интерфейс webllm

Демонстрация работы модели Llama-3.2-1B-Instruct через WebLLM

Интерфейс LiquidAI

Демонстрация интерфейса Liquid AI

LFM2.5-1.2B-Thinking — модель общего назначения с 1.2 миллиарда параметров. В данном демо используется эта модель в формате ONNX — LFM2.5-1.2B-Thinking-ONNX, она подключена через библиотку Transformers.js и работает через WebGPU.

Интерфейс LiquidAI

Информация о демо Liquid AI

Доступные языки: English, Arabic, Chinese, French, German, Japanese, Korean, Spanish. На русском языке модель не обучена, поэтому генерация ответа занимает намного больше времени и не всегда корректна.

Чат в LiquidAI

Демонстрация чата Liquid AI

Работа с аудио

WhisperWebgpu

Демонстрация Whisper Webgpu

Работа с изображениями

Интерфейс демо для удаления фона на WebGPU после загрузки модели, состояние “Ready” (модель готова к использованию):

Удаление фона на WebGPU

Интерфейс демо для удаления фона на WebGPU

Процесс обработки изображения:

Удаление фона на WebGPU

Процесс удаления фона на WebGPU

Результат обработки изображения (заняло ~2,6s):

Удаление фона на WebGPU

Результат удаления фона на WebGPU

Результат обработки изображения и генерации описания (заняло ~4,5s):

Демо florence2 WebGPU

Результат распознавание объектов на изображении с использованием WebGPU

Демо для браузеров без поддержки WebGPU (работают через WebAssembly)

Работа с текстом

Работа с аудио

Работа с изображениями

Другие демо

Обзор возможностей библиотек

Разные библиотеки работают по-разному, так как поддерживают либо работу моделей либо на WebGPU, либо WebAssembly, либо все вместе. Также у них разные API и способы интеграции.

Файлы моделей довольно большие, поэтому, чтобы не загружать их при каждой загрузке страницы, их кэшируют. Для кэширования обычно используется Cache Storage — хранилище для объектов Cache.

Чтобы не блокировать main thread, используются workers:

  • Web Worker — фоновый поток JavaScript, который запускается со страницы и работает параллельно с main thread;
  • Service Worker — специальный воркер, который работает как сетевой посредник между страницей и интернетом.

Общая схема работы библиотек:

    flowchart TD
	
	A[Веб-приложение] --> B[Web Worker]
	
	subgraph model_storage["Хранилище&nbsp;модели&nbsp;(сервер)"]
	L[Веса модели<br/>GGUF / ONNX / MLC]
	M[Файлы токенизатора]
	W[WASM-бинарь рантайма]
	end
	
	subgraph browser_cache["Кэш&nbsp;браузера"]
	N[Cache Storage]
	end
	
	L --> N
	M --> N
	W --> N
	
	B --> C{Проверка поддержки WebGPU}
	
	C -->|WebGPU доступен| D[Загрузка WASM Runtime<br/>GPU backend]
	C -->|WebGPU недоступен| H[Загрузка WASM Runtime<br/>CPU backend]
	
	N -.->|веса + токенизатор + WASM| D
	N -.->|веса + токенизатор + WASM| H
	
	D --> E[Инициализация модели]
	E --> F[Загрузка весов в VRAM]
	F --> G[Инференс на GPU через WebGPU<br/>matmul, attention, softmax, layernorm]
	
	H --> E2[Инициализация модели]
	E2 --> F2[Загрузка весов в RAM]
	F2 --> K[Инференс на CPU<br/>WASM SIMD]
	
	G --> T[Токенизация ввода в рантайме]
	K --> T
	
	T --> I[Генерация следующего токена]
	I --> J[Потоковая передача токенов в UI]

Пояснения к схеме:

  • WASM-бинарь рантайма — у каждой библиотеки свой: WebLLM скачивает TVM-рантайм, Wllama — скомпилированный llama.cpp, Transformers.js — ONNX Runtime Web. Формат весов и рантайм всегда идут в паре.
  • Форматы весов — определяются библиотекой: GGUF для Wllama (llama.cpp), ONNX для Transformers.js, MLC compiled для WebLLM. Одну и ту же модель нужно конвертировать в формат конкретной библиотеки.
  • GPU vs CPU backend — не все библиотеки поддерживают оба пути. Например, Wllama работает только через WASM на CPU (без WebGPU), WebLLM — только через WebGPU (без CPU-фолбэка), а Transformers.js поддерживает оба варианта.
  • Кэш браузера — после первой загрузки веса модели, токенизатор и WASM-бинарь сохраняются в Cache Storage (большинство случаев), IndexedDB или Origin Private File System. При повторном запуске модель загружается из кэша без обращения к серверу.
  • Токенизация — выполняется внутри WASM-рантайма, а не в JavaScript. Файлы токенизатора (словарь, правила) загружаются вместе с моделью.

Сравнение библиотек

Ниже таблица сравнения некоторых популярных библиотек для client-side AI, демо работы которых я уже рассмотрела: BrowserAI, @mlc-ai/webllm, Transformers.js, Wllama и LLM.js. В ней представлены некоторые параметры — какой backend используется, доступен ли WebGPU, доступно ли выполнение на WASM, как устроено кеширование и какие форматы моделей поддерживаются.

БиблиотекаBackend вычисленийВычисления на WebGPUВычисления на WASMWorkersФорматы моделейТипы моделейКешированиеОсобенности
BrowserAIиспользует движки WebLLM или Transformers.js для движка WebLLM
для движка Transformers.js с WASM
Web Workerзависит от backend (MLC compiled / ONNX)LLM, embeddings, чатCache Storage / IndexedDBhigh-level SDK, встроенная БД для чатов и эмбеддингов
@mlc-ai/webllmMLC runtimeWeb Worker + Service WorkerMLC compiled modelsLLM (Llama, Mistral, Gemma, Phi, Qwen)Cache Storage / IndexedDBOpenAI-подобный API, высокая производительность
Transformers.jsONNX Runtime Webможно использовать Web WorkerONNXNLP, Vision, Audio, EmbeddingsCache Storageпорт HuggingFace Transformers в JS
Wllamallama.cpp WASM bindingWeb WorkerGGUF / GGMLLLM (Llama-семейство и совместимые)Origin Private File Systemпорт llama.cpp в браузер
LLM.jswasm-binding llama2.cppWeb WorkerGGUF / GGMLLLMCache Storageлёгкая библиотека для quantized моделей

При этом важно понимать, что даже когда мы говорим “библиотека работает на WebGPU”, это не значит, что все вычисления выполняются только на GPU. Например, в библиотеке @mlc-ai/webllm вычисления выполняются через WebGPU, потому что GPU значительно быстрее для матричных операций трансформеров. Однако при этом используется и WebAssembly, который служит рантаймом, который выполняет вспомогательную логику модели — например, токенизацию, управление KV-кэшем, выделение памяти, планирование вычислений, управление буферами, отправку команд на GPU. Когда мы запускаем модель через WebLLM API, вызовы проходят в WASM-рантайм. Он работает примерно так:

  1. Подключение к GPU — через WebGPU API запрашивается адаптер и устройство, создаётся очередь команд.
  2. Загрузка ядер вычислений — операции модели (матричные умножения, attention, layernorm) заранее скомпилированы в оптимизированные WGSL‑шейдеры и упакованы в WASM‑бинарь. При инициализации они загружаются на GPU как compute‑пайплайны.
  3. Выделение буферов — в видеопамяти создаются GPU‑буферы для весов модели, активаций и KV-cache.
  4. Шаг инференса — на каждом шаге WASM‑рантайм подготавливает входные данные, копирует их в GPU, запускает цепочку вычислительных ядер (prefill или decode) и возвращает результат обратно в WASM/JS.

То есть фактически WASM — это менеджер вычислений, а GPU — исполнитель всех тяжёлых операций.

    sequenceDiagram
	    participant JS as WebLLM API
	    participant WASM as WASM-рантайм
	    participant API as WebGPU API
	    participant GPU as GPU
	
	    JS->>WASM: управление передаётся<br>в WASM-рантайм<br>(engine.chat.completions.create())
	
	    rect rgba(137, 90, 255, 0.1)
	    Note over WASM,GPU: Инициализация
	    WASM->>API: navigator.gpu.requestAdapter()
	    API->>GPU: Выбор<br>физического устройства
	    GPU-->>API: GPUAdapter
	    API-->>WASM: GPUAdapter
	    WASM->>API: adapter.requestDevice()
	    API-->>WASM: GPUDevice + GPUQueue
	    WASM->>API: device.createShaderModule(WGSL)
	    Note right of API: matmul, attention, layernorm, softmax
	    API->>GPU: Компиляция WGSL<br>→ машинный код GPU
	    GPU-->>API: Скомпилированные<br>шейдеры
	    WASM->>API: device.createComputePipeline()
	    API-->>WASM: GPUComputePipeline
	    WASM->>API: device.createBuffer() × N
	    Note right of API: веса, активации, KV-cache
	    API->>GPU: Выделение VRAM
	    GPU-->>API: GPUBuffer handles
	    WASM->>API: queue.writeBuffer(weights)
	    API->>GPU: Копирование весов CPU<br>→ VRAM
	    end
	
	    rect rgba(31, 83, 224, 0.1)
	    Note over WASM,GPU: Инференс (каждый шаг)
	    WASM->>WASM: Токенизация<br>→ token IDs
	    WASM->>API: queue.writeBuffer(inputTokens)
	    API->>GPU: Копирование входных данных<br>→ VRAM
	
	    WASM->>API: commandEncoder.beginComputePass()
	    Note right of API: Запись команд в буфер
	    WASM->>API: pass.setPipeline() + pass.dispatch()
	    Note right of API: embedding → attention<br>→ FFN → layernorm (× N слоёв)
	    WASM->>API: queue.submit([commandBuffer])
	    API->>GPU: Отправка батча<br>команд на исполнение
	    GPU->>GPU: Параллельное<br>выполнение ядер
	
	    WASM->>API: device.createBuffer(MAP_READ)<br>+ copyBufferToBuffer()
	    API->>GPU: Копирование результата VRAM<br>→ staging buffer
	    GPU-->>API: Результат
	    WASM->>API: stagingBuffer.mapAsync(READ)
	    API-->>WASM: Логиты следующего токена
	    WASM->>WASM: Сэмплирование токена,<br>обновление KV-кэша
	    end
	
	    WASM-->>JS: Ответ модели

Как использовать библиотеки

Использование и синтаксис библиотек довольно простые. Например, для BrowserAI это будет выглядеть так:

Установка: npm install @browserai/browserai

Использование:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Импортируем библиотеку BrowserAI
import { BrowserAI } from '@browserai/browserai';

// Создаём экземпляр
const browserAI = new BrowserAI();

// Загружаем модель Llama 3.2 1B с квантизацией q4f16_1
// onProgress — колбэк для отображения прогресса загрузки
await browserAI.loadModel('llama-3.2-1b-instruct', {
  quantization: 'q4f16_1',
  onProgress: (progress) => console.log('Loading:', progress.progress + '%')
});

// Отправляем запрос в модель и получаем ответ
const response = await browserAI.generateText('Hello, how are you?');
// Выводим текст ответа
console.log(response.choices[0].message.content);

Вместо заключения

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

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

Я планирую еще несколько частей из этого цикла статей про client-side AI:

  • в следующей подробнее расскажу про квантизацию и форматы моделей (.wasm, .onnx, .gguf и др.), как они соотносятся с WebGPU/WASM и разными библиотеками, приведу примеры кода и расскажу, как это работает ближе к железу;
  • также я хочу рассказать про то, как конвертировать форматы моделей под рантаймы, которые могут работать в браузере;
  • сделаю обзор плюсов, минусов и угроз безопасности при использовании client-side AI;
  • в конце я планирую полноценное приложение с функционалом, реализованным на локальных моделях, чтобы показать их возможности для решения практических задач.

Хм, думаю, это будет весело. Enjoy!

Если вы обнаружили ошибку или неточность в статье, пожалуйста, сообщите мне.