Transformers.js в Chrome: как запустить LLM прямо в браузере
Когда речь заходит о языковых моделях в браузере, обычно вспоминают WebLLM — Chromium-ориентированное решение через WebGPU. У Hugging Face свой путь: Transformers.js работает через WebAssembly и бэкенды ONNX Runtime, без привязки к конкретному GPU-бенчмарку. Результат — расширение Chrome, которое запускает Gemma 4 E2B локально, без единого запроса к внешнему API.
Что построили
Демо-расширение Transformers.js Gemma 4 Browser Assistant делает три вещи: отвечает на вопросы о странице, ищет в истории браузера и подсвечивает элементы на сайте. Всё работает на двух локальных моделях в ONNX-формате. Первая — onnx-community/gemma-4-E2B-it-ONNX для генерации текста и принятия решений об инструментах. Вторая — onnx-community/all-MiniLM-L6-v2-ONNX для векторных эмбеддингов семантического поиска.
Ключевое ограничение Manifest V3 — фоновый скрипт не имеет постоянного доступа к CPU и памяти. Chrome может приостановить service worker в любой момент. Это означает, что модель должна уметь переинициализироваться после каждого перезапуска, а состояние агента должно сохраняться отдельно от runtime-сессии.
Архитектура Manifest V3
Расширение определяет три точки входа в manifest.json. Первая — background.service_worker (собирается из background.ts), управляет жизненным циклом моделей, агента и исполнением инструментов. Вторая — side_panel.default_path (собирается из sidebar.html), обеспечивает UI чата с потоковой передачей токенов и индикаторами загрузки моделей. Третья — content_scripts[].js с маской http(s)://*/*, работает как мост к DOM страницы для извлечения контента и подсветки элементов.
Разделение严格执行: фоновый скрипт — единственный координатор, UI-компоненты только запрашивают действия и рендерят результаты. conversation history хранится в фоновом скрипте в Agent.chatMessages, а не в UI. Это предотвращает дублирование загрузок модели и сохраняет отзывчивость интерфейса.
Между компонентами — typed messaging contract через enum в types.ts. Side panel шлёт фоновому скрипту команды вроде INITIALIZE_MODELS, AGENT_GENERATE_TEXT, CHECK_MODELS. Фоновый скрипт возвращает DOWNLOAD_PROGRESS для индикации загрузки и MESSAGES_UPDATE при изменении состояния чата. Content script получает команды EXTRACT_PAGE_DATA, HIGHLIGHT_ELEMENTS, CLEAR_HIGHLIGHTS.
Transformers.js: два пайплайна, одна песочница
Инференс обоих моделей происходит в фоновом скрипте. Генерация текста через pipeline("text-generation", ...) с включённым KV Caching. Эмбеддинги — через pipeline("feature-extraction", ...)" с нормировкой векторов. KV Caching особенно важен в контексте MV3: при каждом перезапуске service worker модель переинициализируется, и кэшированные ключ-значение для attention-блоков теряются. DynamicCache из Transformers.js позволяет восстанавливать состояние.
Обе модели работают в ONNX Runtime, что даёт независимость от WebGPU. Это критично для覆盖率: WebLLM поддерживает только Chromium-based браузеры с WebGPU. Transformers.js через ONNX работает в Firefox и Safari (где WebAssembly поддерживается шире). Квантование Q4F16 обеспечивает приемлемый баланс размера модели и точности.
После загрузки модели кэшируются под origin расширения (chrome-extension://<extension-id>), а не под origin отдельных сайтов. Это означает одну общую копию модели для всех установок расширения, но также — что модель доступна только внутри песочницы расширения, без утечки данных на внешние серверы.
Agentic loop: от промпта до инструмента
Когда пользователь пишет сообщение в side panel, поток данных выглядит так. Side panel отправляет AGENT_GENERATE_TEXT в фоновый скрипт. Фоновый скрипт добавляет сообщение в chatMessages, вызывает generator() с текущим контекстом и списком доступных инструментов. Модель может вернуть текстовый ответ, запрос на вызов инструмента или оба.
Если модель запрашивает инструмент, фоновый скрипт выполняет его через NormalizeTools: get_open_tabs, go_to_tab, open_url, close_tab, find_history, ask_website, highlight_website_element. Результат добавляется в историю как tool-сообщение, и цикл повторяется. Это продолжается пока модель не решит, что дальше делать ничего не нужно, и не вернёт финальный текст.
Инструменты реализованы как webMcp — слой нормализации, который приводит API расширения к формату, ожидаемому Gemma. Когда модель решает вызвать, например, find_history, она генерирует специальный токен-блок. Парсер extractToolCalls извлекает имя инструмента и аргументы из этого блока.
Персистентность: где что живёт
Состояние в MV3 разделено по жизненному циклу и паттерну доступа. conversation history — в памяти фонового скрипта (Agent.chatMessages), для быстрой оркестрации между turn'ами. Настройки инструментов — в chrome.storage.local, чтобы сохраняться между сессиями расширения. Векторы для семантического поиска — в IndexedDB через VectorHistoryDB. Извлечённый контент страниц — в кэше фонового скрипта, ключ — URL страницы.
Это архитектурное решение имеет конкретные последствия для разработчиков. Если conversation history живет в памяти — она теряется при перезапуске service worker. Если пользователь открывает расширение после таймаута Chrome, агент переинициализируется, и история пуста. Расширение решает это сохранением chatMessages в storage при каждом обновлении, но это создаёт задержку на запись и ограничено квотами Chrome storage API.
Ограничения и что это значит на практике
KV Caching в Transformers.js использует новый класс DynamicCache. Без него каждый новый токен требовал бы пересчёта attention для всей истории, что при 4K+ контексте делает инференс неприемлемо медленным. DynamicCache сохраняет кэш между вызовами внутри одной сессии, но не между перезапусками service worker.
Q4F16-квантование Gemma 4 E2B даёт модель ~1.3B параметров в INT4, что умещается в ~700MB ОЗУ. Это приемлемо для фонового скрипта Chrome на большинстве машин, но на маломощных устройствах (Chromebook с 4GB RAM) загрузка модели может занять 20–40 секунд или вызвать OOM kill расширения.
Парсер tool calls специфичен для Gemma-шаблона. Когда модель генерирует <|tool_call|>call:getWeather{location:<|"|>Bern<|"|>}<tool_call|>, это формат, специфичный для Gemma. Для других моделей (Llama, Mistral) формат tool call отличается, и парсер придётся адаптировать.
Сравнение с WebLLM
WebLLM от MLC использует WebGPU для инференса непосредственно на GPU пользователя. Преимущество — скорость на современных GPU (Apple M-series, дискретные NVIDIA). Transformers.js через ONNX работает на CPU, что медленнее, но не требует WebGPU-поддержки. WebLLM поддерживает Llama 3, Mistral, Phi-3; Transformers.js — Gemma, BERT-подобные модели, и другие архитектуры с ONNX-экспортом.
Для Chrome extension контекста Transformers.js даёт практическое преимущество: одна кодовая база работает в Firefox и Safari без изменений. WebLLM изначально заточен под Chromium. Если цель — максимальная скорость на GPU-девайсах — WebLLM. Если цель — кросс-браузерное расширение с разумной скоростью на CPU — Transformers.js.
Что можно построить на этой базе
Паттерн архитектуры расширения применим далеко за пределами Gemma-ассистента. Фоновый скрипт как единая точка координации, side panel как UI-слой, content script как DOM-доступ — это каркас для любого локального AI-расширения. Можно заменить Gemma на более специализированную модель для кода, переводов или анализа текста. Можно добавить инструменты для работы с закладками, заметками, почтой. Можно встроить retrieval-augmented generation с локальным векторным хранилищем.
Конкретные возможности, открывающиеся с локальной моделью в расширении: приватный ассистент, который не отправляет данные на сервер; семантический поиск по истории браузера; автоматическая категоризация и тегирование сохранённых страниц; офлайн-ассистент для чтения длинных статей без интернета.
Почему это важно сейчас
Тренд на локальный AI набирает обороты. Llama.cpp доказал, что 7B-модель можно запустить на ноутбуке. WebLLM показал, что браузер — легитимная платформа для инференса. Transformers.js делает следующий шаг: кросс-браузерное расширение без зависимости от WebGPU.
Для пользователей это означает выбор между скоростью и приватностью. Облачный ассистент (ChatGPT, Claude) работает быстро, но отправляет данные на сервер. Локальная Gemma в расширении работает медленнее на CPU, но не передаёт ничего третьим лицам. Это не академическое различие — корпоративные пользователи всё чаще ищут решения, которые не поднимают корпоративные данные на внешние API.
EToro недавно заявили о планах использовать AI-агентов для автоматической торговли портфелями. Western Union тестируют AI для фрод-детекции. Это сценарии, где данные финансовых транзакций не должны покидать периметр компании. Локальная модель в браузере — одно из возможных решений этой проблемы.
Ресурсы и ограничения
Service worker в MV3 имеет жёсткие resource limits. Chrome может убить фоновый скрипт после 30 секунд без activity или при нехватке памяти. На устройствах с 4GB RAM (типичный Chromebook) загрузка Gemma 4 (~700MB в Q4F16) может занять 20–40 секунд при первом запуске и вызвать OOM kill.
Решения существуют. Во-первых, использование меньших моделей для простых задач: MiniLM для эмбеддингов работает в фоне и потребляет значительно меньше памяти, чем Gemma для генерации. Во-вторых, предварительная загрузка модели при установке расширения, а не при первом запросе — пользователь видит splash screen один раз, но получает мгновенные ответы потом. В-третьих, стриминг токенов — пользователь видит первые токены через 2–3 секунды, пока остальные генерируются.
Эти ограничения не делают расширение бесполезным. Они определяют границы применимого сценария: не real-time ассистент для heavy-duty задач, а быстрый локальный инструмент для простых запросов с гарантией приватности. Это осознанный tradeoff, который разработчики приняли в пользу кросс-браузерной совместимости.
Сравнение с WebLLM
WebLLM от MLC использует WebGPU для инференса непосредственно на GPU пользователя. Преимущество — скорость на современных GPU (Apple M-series, дискретные NVIDIA). Transformers.js через ONNX работает на CPU, что медленнее, но не требует WebGPU-поддержки. WebLLM поддерживает Llama 3, Mistral, Phi-3; Transformers.js — Gemma, BERT-подобные модели, и другие архитектуры с ONNX-экспортом.
Для Chrome extension контекста Transformers.js даёт практическое преимущество: одна кодовая база работает в Firefox и Safari без изменений. WebLLM изначально заточен под Chromium. Если цель — максимальная скорость на GPU-девайсах — WebLLM. Если цель — кросс-браузерное расширение с разумной скоростью на CPU — Transformers.js.
Промежуточное решение — гибридное расширение, которое определяет доступность WebGPU и выбирает бэкенд динамически. Это добавляет complexity, но даёт лучший UX на всех платформах. Transformers.js из коробки этого не предоставляет, но архитектура расширения это позволяет.
Часто задаваемые вопросы
Можно ли использовать эту архитектуру для коммерческого расширения? Да, обе модели (Gemma 4 E2B и MiniLM) имеют открытую лицензию. Transformers.js — Apache 2.0. Архитектура Manifest V3 подходит для публикации в Chrome Web Store.
Как расширение защищает данные пользователя? Инференс выполняется целиком локально. Модель загружается в память браузера и работает внутри песочницы расширения. Ни промпты, ни ответы, ни извлечённый контент страниц не передаются на внешние серверы. Это отличается от облачных ассистентов, где данные уходят на сервер API.
Почему не WebGPU? WebGPU даёт прямой доступ к GPU и значительно ускоряет инференс. Но поддержка WebGPU в браузерах ограничена: Chrome полностью, Firefox частично (только на GPU с explicit device API), Safari экспериментально. Transformers.js через ONNX работает на любом браузере с WebAssembly.
С какими моделями работает Transformers.js? Transformers.js поддерживает модели, для которых существует ONNX-экспорт. Это включает Gemma, Llama (через LlamaEdge), Mistral, Phi-3, BERT, RoBERTa, Whisper, и другие. Полный список моделей с ONNX-весами доступен на Hugging Face.
Код расширения открыт на GitHub — nico-martin/gemma4-browser-extension. Это рабочая отправная точка для разработчиков, которые хотят добавить локальный AI в своё Chrome-расширение без зависимости от облачных API.