WebLLM запускает языковую модель внутри браузера через WebGPU, поэтому текст запроса не обязан уходить на ваш сервер. Соберём минимальный чат с прогрессом первой загрузки, заблокированной кнопкой и локальным completion. Честно разберём главный предел: модель занимает память и скачивается значительно дольше обычного JavaScript-бандла.
Локальный ИИ звучит как одна библиотека, но пользователь сначала сталкивается с совместимостью WebGPU, загрузкой весов и занятым интерфейсом. В этой статье вы соберёте веб-страница с одним полем и локальным ответом. Финальный результат можно проверить без догадок: поддерживаемый браузер показывает прогресс до ready, после чего модель отвечает без API-ключа.
navigator.gpu отделяет неподдерживаемый браузер до загрузки модели.
CreateMLCEngine сообщает progress, а кнопка остаётся disabled.
chat.completions.create получает messages и возвращает локальный ответ.
Что WebLLM делает внутри браузера
Vite отдаёт HTML и JavaScript, WebLLM загружает runtime и веса выбранной модели, WebGPU выполняет вычисления на устройстве. UI хранит engine, показывает progress и разрешает запрос только после успешной инициализации. Для WebLLM держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.
| Этап | Действие | Проверяемый результат |
|---|---|---|
| Проверка | Код ищет navigator.gpu | Совместимость известна сразу |
| Загрузка | CreateMLCEngine скачивает и компилирует модель | Progress меняется до 100% |
| Запрос | Messages передаются в local engine | GPU начинает генерацию |
| Ответ | choices[0].message.content попадает в UI | Кнопка снова доступна |
Самая тяжёлая операция происходит до первого ответа, поэтому прогресс является частью продукта, а не украшением. У WebLLM каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

От входных данных до видимого результата. Engine создаётся один раз на модель
Пишем минимальный локальный чат
Создайте 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 по официальному примеру. На слабом устройстве выберите меньшую модель вместо попытки скрыть ошибку.
- Проверьте navigator.gpu.
- Дождитесь progress=100%.
- Получите один локальный ответ.
- Перезагрузите и сравните кеш.
- Добавьте сброс истории.
- Проверьте ограничение длины prompt.
- Совместимость проверена.
- Загрузка объясняется.
- Кнопка не дублирует запросы.
- Память учитывается при выборе модели.
Локальный inference полезен только тогда, когда интерфейс честно показывает стоимость первой загрузки и ограничения устройства. После этой проверки измените одно правило WebLLM и заранее запишите ожидаемый результат. Так копирование превращается в понимание.

Четыре проверки перед следующим шагом. Устройство задаёт реальный потолок
Как не перегрузить локальное ИИ-приложение
Для реального приложения перенесите engine в worker, добавьте потоковый вывод, отмену и ограниченную историю. Храните выбранную модель в настройках, но проверяйте её наличие в текущем model_list. Не запускайте загрузку автоматически на мобильной сети без согласия. Для чувствительных сценариев проведите сетевой аудит страницы. Если нужен стабильный ответ на слабых устройствах, предусмотрите серверный режим как отдельную архитектуру, а не скрытую подмену.
Как изучить тему в Кодике
Закрепите async/await, события формы и обновление DOM, затем разберите WebGPU как среду выполнения. В Кодике сначала восстановите чистую логику без фреймворка, затем верните библиотеку и интерфейс WebLLM.
| Шаг | Что разобрать | Мини-проверка |
|---|---|---|
| 1 | События формы | Остановить reload через preventDefault |
| 2 | Promises | Дождаться CreateMLCEngine |
| 3 | Состояния UI | Развести loading, ready и error |
| 4 | Массив messages | Ограничить историю |
| 5 | Web Worker | Увести engine из UI-потока |
Сначала сделайте честные loading и error, только потом добавляйте красивую анимацию ответа. Финальная проверка для WebLLM: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.
Покажите совместимость до загрузки runtime.
Начните с малого instruct-варианта и измерьте память.
Инициализируйте модель один раз и повторно используйте её.
Ограничьте контекст и добавьте явный новый чат.
Что получится в итоге

Страница показывает прогресс модели, активирует форму после ready и получает ответ локально через WebGPU без API-ключа.
Что нужно браузеру для WebLLM?
WebLLM нужен браузер с WebGPU и достаточно памяти для выбранной модели.
Уходит ли prompt WebLLM на ваш сервер?
Сам inference WebLLM выполняется локально, но страницу и загрузку модели всё равно нужно анализировать отдельно.
Почему engine нельзя создавать на каждый запрос?
Создание engine загружает и готовит модель, поэтому его повторяют только при смене модели.
Как понять, что учебный проект WebLLM действительно работает?
Проверьте основной сценарий WebLLM, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.
Можно ли начать с готового кода WebLLM из статьи?
Да. Сначала добейтесь результата WebLLM, затем измените одно правило и восстановите ключевой файл без копирования.
JavaScript, async и события закрепите в курсе JavaScript, а основы ИИ в курсе по генеративным моделям.
Сравните локальный браузерный режим с Ollama и Python, интерфейс с ИИ-расширением Chrome и инструментальный подход с MCP-сервером.