ФронтендData и ИИJavaScriptHTML / CSS

Локальный ИИ прямо в браузере: первый чат на JavaScript и WebLLM

Запускаем WebLLM через WebGPU, показываем прогресс загрузки модели, отправляем чат-запрос локально и разбираем требования к памяти и браузеру.

Кодик

Автор

7 мин чтения

WebLLM запускает языковую модель внутри браузера через WebGPU, поэтому текст запроса не обязан уходить на ваш сервер. Соберём минимальный чат с прогрессом первой загрузки, заблокированной кнопкой и локальным completion. Честно разберём главный предел: модель занимает память и скачивается значительно дольше обычного JavaScript-бандла.

Локальный ИИ звучит как одна библиотека, но пользователь сначала сталкивается с совместимостью WebGPU, загрузкой весов и занятым интерфейсом. В этой статье вы соберёте веб-страница с одним полем и локальным ответом. Финальный результат можно проверить без догадок: поддерживаемый браузер показывает прогресс до ready, после чего модель отвечает без API-ключа.

1Проверяем GPU

navigator.gpu отделяет неподдерживаемый браузер до загрузки модели.

2Грузим модель

CreateMLCEngine сообщает progress, а кнопка остаётся disabled.

3Запрашиваем

chat.completions.create получает messages и возвращает локальный ответ.

Что WebLLM делает внутри браузера

Vite отдаёт HTML и JavaScript, WebLLM загружает runtime и веса выбранной модели, WebGPU выполняет вычисления на устройстве. UI хранит engine, показывает progress и разрешает запрос только после успешной инициализации. Для WebLLM держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.

ЭтапДействиеПроверяемый результат
ПроверкаКод ищет navigator.gpuСовместимость известна сразу
ЗагрузкаCreateMLCEngine скачивает и компилирует модельProgress меняется до 100%
ЗапросMessages передаются в local engineGPU начинает генерацию
Ответchoices[0].message.content попадает в UIКнопка снова доступна

Самая тяжёлая операция происходит до первого ответа, поэтому прогресс является частью продукта, а не украшением. У WebLLM каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

Путь WebLLM от download до ответа
От входных данных до видимого результата. Engine создаётся один раз на модель

Не обещайте полную приватность без оговорок. Инференс выполняется локально, но сама страница, CDN, аналитика и загрузка модели всё равно создают сетевые запросы. Проверяйте архитектуру целиком.

Пишем минимальный локальный чат

Создайте Vite-проект npm create vite@latest webllm-chat -- --template vanilla, установите npm install @mlc-ai/web-llm и запустите dev-сервер. Открывайте страницу в актуальном браузере с WebGPU. Модель выбирается из prebuiltAppConfig.model_list, а не выдумывается строкой. Для первого опыта берите небольшой instruct-вариант, который помещается в память устройства.

import {
  CreateMLCEngine,
  prebuiltAppConfig,
} from "@mlc-ai/web-llm";

const status = document.querySelector("#status");
const form = document.querySelector("#chat-form");
const input = document.querySelector("#message");
const output = document.querySelector("#output");
const button = form.querySelector("button");

if (!navigator.gpu) {
  status.textContent = "WebGPU is not available in this browser";
  throw new Error("WebGPU unavailable");
}

const model = prebuiltAppConfig.model_list.find((item) =>
  item.model_id.includes("1B-Instruct")
);
if (!model) throw new Error("No small instruct model in config");

const engine = await CreateMLCEngine(model.model_id, {
  initProgressCallback: ({ progress, text }) => {
    status.textContent = Math.round(progress * 100) + "% " + text;
  },
});
status.textContent = "Ready: " + model.model_id;
button.disabled = false;

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const message = input.value.trim();
  if (!message) return;
  button.disabled = true;
  output.textContent = "Generating...";
  try {
    const reply = await engine.chat.completions.create({
      messages: [{ role: "user", content: message }],
    });
    output.textContent = reply.choices[0].message.content || "Empty reply";
  } finally {
    button.disabled = false;
  }
});
Что должно произойти после запуска
  • Неподдерживаемый браузер останавливается до скачивания.
  • Статус показывает реальный progress и имя модели.
  • Кнопка активируется только после готовности engine.
  • Запрос выполняется без серверного API-ключа.

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

Скрытая загрузка и честный интерфейс
Что отличает устойчивый проект от случайного запуска. Progress является частью сценария

Почему первая загрузка долгая

WebGPU даёт JavaScript доступ к вычислениям на GPU через безопасный API браузера. WebLLM содержит runtime, преобразует модель в поддерживаемый формат и использует Cache API браузера для повторных загрузок. CreateMLCEngine асинхронен: вкладка не должна принимать prompt до завершения. chat.completions использует знакомую структуру messages, но вычисление проходит на устройстве. Память видеоустройства, общая RAM и ограничения вкладки определяют, какая модель запустится.

ЧастьЗа что отвечаетЧто проверить
navigator.gpuПроверяет WebGPUОтказ показан до download
model_listХранит поддерживаемые моделиВыбран instruct-вариант
progress callbackСообщает этап загрузкиUI не выглядит зависшим
engineВыполняет локальный inferenceСоздан ровно один раз
form handlerОтправляет promptКнопка блокируется на запрос

Большая модель может закрыть вкладку из-за памяти, даже если WebGPU доступен. Размер download не равен потреблению RAM. На мобильном устройстве нагрев и батарея тоже являются ограничениями. Worker-режим полезен, чтобы тяжёлая логика меньше мешала интерфейсу, но он не уменьшает веса. История сообщений увеличивает контекст и время ответа, поэтому храните ограниченное число реплик. Локальная модель не получает свежие данные из интернета сама по себе и может уверенно ошибаться.

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

Проверяем WebGPU, память и повторный запуск

Запустите страницу в поддерживаемом браузере и запишите время первой готовности. Перезагрузите вкладку и сравните повторный запуск. Откройте DevTools, выключите WebGPU флагом или проверьте условие вручную, чтобы увидеть отказ. Добавьте массив из последних четырёх messages и кнопку «Новый чат». Ограничьте длину prompt. Затем перенесите генерацию в Web Worker по официальному примеру. На слабом устройстве выберите меньшую модель вместо попытки скрыть ошибку.

Проверка своими руками
  1. Проверьте navigator.gpu.
  2. Дождитесь progress=100%.
  3. Получите один локальный ответ.
  4. Перезагрузите и сравните кеш.
  5. Добавьте сброс истории.
  6. Проверьте ограничение длины prompt.
Готово, если выполняются все пункты
  • Совместимость проверена.
  • Загрузка объясняется.
  • Кнопка не дублирует запросы.
  • Память учитывается при выборе модели.

Локальный inference полезен только тогда, когда интерфейс честно показывает стоимость первой загрузки и ограничения устройства. После этой проверки измените одно правило WebLLM и заранее запишите ожидаемый результат. Так копирование превращается в понимание.

Четыре проверки локального чата
Четыре проверки перед следующим шагом. Устройство задаёт реальный потолок

Как не перегрузить локальное ИИ-приложение

Для реального приложения перенесите engine в worker, добавьте потоковый вывод, отмену и ограниченную историю. Храните выбранную модель в настройках, но проверяйте её наличие в текущем model_list. Не запускайте загрузку автоматически на мобильной сети без согласия. Для чувствительных сценариев проведите сетевой аудит страницы. Если нужен стабильный ответ на слабых устройствах, предусмотрите серверный режим как отдельную архитектуру, а не скрытую подмену.

Как изучить тему в Кодике

Закрепите async/await, события формы и обновление DOM, затем разберите WebGPU как среду выполнения. В Кодике сначала восстановите чистую логику без фреймворка, затем верните библиотеку и интерфейс WebLLM.

ШагЧто разобратьМини-проверка
1События формыОстановить reload через preventDefault
2PromisesДождаться CreateMLCEngine
3Состояния UIРазвести loading, ready и error
4Массив messagesОграничить историю
5Web WorkerУвести engine из UI-потока

Сначала сделайте честные loading и error, только потом добавляйте красивую анимацию ответа. Финальная проверка для WebLLM: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.

Не проверяют navigator.gpu

Покажите совместимость до загрузки runtime.

Выбирают слишком большую модель

Начните с малого instruct-варианта и измерьте память.

Создают engine на каждый prompt

Инициализируйте модель один раз и повторно используйте её.

Хранят бесконечную историю

Ограничьте контекст и добавьте явный новый чат.

Сверяйтесь с первичной документацией. Команды и ограничения примера проверены по официальной документации WebLLM Getting Started. Если версия инструмента изменилась, официальный источник важнее старого скриншота.

Что получится в итоге

Готовый результат проекта: Локальный ИИ прямо в браузере: первый чат на JavaScript и WebLLM
Страница показывает прогресс модели, активирует форму после ready и получает ответ локально через WebGPU без API-ключа.

Короткие ответы
Что нужно браузеру для WebLLM?

WebLLM нужен браузер с WebGPU и достаточно памяти для выбранной модели.

Уходит ли prompt WebLLM на ваш сервер?

Сам inference WebLLM выполняется локально, но страницу и загрузку модели всё равно нужно анализировать отдельно.

Почему engine нельзя создавать на каждый запрос?

Создание engine загружает и готовит модель, поэтому его повторяют только при смене модели.

Как понять, что учебный проект WebLLM действительно работает?

Проверьте основной сценарий WebLLM, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода WebLLM из статьи?

Да. Сначала добейтесь результата WebLLM, затем измените одно правило и восстановите ключевой файл без копирования.

Сначала измерьте устройство, потом выбирайте модель

JavaScript, async и события закрепите в курсе JavaScript, а основы ИИ в курсе по генеративным моделям.

Сравните локальный браузерный режим с Ollama и Python, интерфейс с ИИ-расширением Chrome и инструментальный подход с MCP-сервером.