Зачем тащить BERT в браузер? Потому что облака протекают
Представьте: вы делаете RAG-систему для внутренних документов компании. Каждый запрос улетает в облако, векторизуется через OpenAI API, а потом вы молитесь, чтобы данные не всплыли в ChatGPT. Знакомо? Именно поэтому локальный семантический поиск стал must-have для любого, кто работает с конфиденциальными данными.
Но локально - это же медленно, громоздко, требует Python-бэкенда, Torch, кучи зависимостей. Или нет? Оказывается, можно запустить полноценный BERT прямо в браузере, без сервера вообще. И сделать это на Rust, а не на Python.
Пока вы читаете это, ваш браузер способен на большее, чем показывает. WebAssembly + Rust дают производительность, близкую к нативному коду. А квантование сжимает модели в 4 раза без серьезной потери качества.
Rust и Candle: когда Python - это слишком жирно
Candle - это не свеча, а фреймворк для машинного обучения на Rust от Hugging Face. Да, те самые, кто сделал Transformers. Только здесь нет Python, нет GIL, нет гигантских зависимостей. Чистый Rust, который компилируется в WebAssembly и работает в браузере как родной.
Почему Rust, а не классический ONNX.js? Потому что ONNX - это еще один слой абстракции, который съедает память и тормозит. Candle работает напрямую с тензорами, оптимизирован под WASM и не требует рантайма Python. Разница в скорости - до 3x на CPU браузера.
Квантование: как впихнуть 400 МБ в 10 МБ
Оригинальная модель sentence-transformers/all-MiniLM-L6-v2 весит около 400 МБ. Для браузера это смерть. Но мы используем квантование до int8 - техника, которая сокращает размер модели в 4 раза, сохраняя 95% точности.
Как это работает? Вместо 32-битных чисел с плавающей точкой мы используем 8-битные целые. Математика становится проще, память экономится радикально. В итоге модель занимает ~10 МБ - вполне приемлемо для загрузки по сети.
| Подход | Размер модели | Скорость (ms) | Точность (STS-B) |
|---|---|---|---|
| Оригинальный BERT (Python) | 400 МБ | 120 | 85.4 |
| Квантованный BERT (Candle/WASM) | 10 МБ | 45 | 82.1 |
| ONNX.js в браузере | 100 МБ | 85 | 84.3 |
Сравнение с альтернативами: ONNX.js против Candle
Большинство пытается запустить модели в браузере через ONNX.js. Это работает, но со скрипом. ONNX - формат, созданный Microsoft, требует конвертации моделей, добавляет overhead. Candle же читает модели PyTorch напрямую (через safetensors).
Transformers.js - хорошая альтернатива, но она все равно использует ONNX рантайм под капотом. И она на JavaScript, а не на Rust. Разница в производительности заметна на больших документах.
- Candle/Rust: Нативная скорость WASM, минимальный размер бандла, прямое чтение PyTorch моделей
- ONNX.js: Универсальный формат, но с overhead, требует конвертации, больше памяти
- Transformers.js: Удобный API, но медленнее Rust-версии, зависит от ONNX
- Python бэкенд: Максимальная гибкость, но требует сервера, утечки данных, масштабирования
Если вам нужно ускорить семантический поиск в 20 раз на сервере, то Rust+Candle тоже подойдут. Но в браузере - это единственный способ получить near-native производительность.
Пошагово: от Rust кода до работающего WASM
1 Подготовка модели: квантование в int8
Сначала берем модель all-MiniLM-L6-v2 из Hugging Face и квантуем ее с помощью скрипта на Python. Да, здесь Python еще нужен, но только один раз для подготовки.
from transformers import AutoModelForSequenceClassification
import torch
model = AutoModelForSequenceClassification.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')
model_quantized = torch.quantization.quantize_dynamic(model, {torch.nn.Linear}, dtype=torch.qint8)
model_quantized.save_pretrained('./model_quantized')
2 Написание Rust кода для инференса
Создаем библиотеку на Rust с использованием Candle. Код удивительно простой - всего около 100 строк.
use candle_core::{DType, Device, Tensor};
use candle_nn::{Module, VarBuilder};
use candle_transformers::models::bert::{BertModel, Config};
pub fn encode(text: &str) -> anyhow::Result> {
let config = Config::mini_lm_l6_v2();
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&["model.safetensors"], DType::F32, &Device::Cpu)? };
let model = BertModel::load(vb, &config)?;
let tokens = tokenize(text);
let embeddings = model.forward(&tokens)?;
Ok(embeddings.to_vec1()?)
}
3 Компиляция в WebAssembly
Используем wasm-pack для сборки. Главное - правильно настроить Cargo.toml с crate-type = ["cdylib"].
wasm-pack build --target web --release
На выходе получаем .wasm файл и JavaScript-обвязку, которую можно импортировать в любом современном браузере.
4 Интеграция в веб-приложение
Подключаем сгенерированный WASM модуль и используем его для векторизации текста прямо на клиенте.
import init, { encode } from './pkg/bert_wasm.js';
await init();
const query = "поиск документов о приватности";
const embedding = await encode(query);
// Теперь ищем по локальной базе векторов
Важный нюанс: WASM файл с моделью весит ~10 МБ. Используйте lazy loading и прогрессивную загрузку, чтобы не блокировать старт приложения. Идеально - загружать модель только когда пользователь открывает поиск.
Пример: приватный поиск по документам без интернета
Представьте приложение для юристов, где все документы хранятся локально. Пользователь вводит "договор аренды с правом продления", и система находит все похожие документы, даже если в тексте нет точного совпадения.
С этим стеком вы можете сделать полностью оффлайновый семантический поиск. Браузер векторизует запрос, сравнивает с заранее подготовленными эмбеддингами документов (их можно хранить в IndexedDB) и выдает результаты за миллисекунды.
Если нужно комбинировать с традиционным поиском, посмотрите гибридный поиск для RAG. Только здесь весь пайплайн работает в браузере.
Кому это подойдет? (И кому нет)
Этот подход - не для всех. Но если вы попадаете в одну из этих категорий, Rust + Candle станут вашим спасением:
- Разработчики privacy-first приложений: Когда данные не должны покидать устройство пользователя. Как в Offloom, но для поиска.
- Создатели оффлайновых инструментов: Приложения, которые должны работать без интернета - полевые исследования, медицинские системы.
- Те, кто устал от масштабирования Python-бэкендов: Каждый запрос к BERT на сервере - это CPU нагрузка. В браузере нагрузка распределяется по клиентам.
- Энтузиасты WebAssembly: Кто хочет выжать максимум из браузера и не боится Rust.
Не подойдет если:
- Вам нужна максимальная точность (квантование все же теряет 2-3%)
- Вы работаете с очень длинными документами (BERT имеет ограничение в 512 токенов)
- Ваша целевая аудитория - старые телефоны с малым объемом памяти
- Вы ненавидите Rust и не хотите с ним связываться
Что дальше? Браузер как полноценная AI-платформа
Запуск BERT в браузере - только начало. Следующий шаг - запускать более крупные модели, комбинировать их с локальными LLM для полноценного RAG прямо на клиенте. Представьте: браузер сам индексирует ваши документы, сам отвечает на вопросы, ничего не отправляя в облако.
Проблема только в размере моделей. Но с квантованием и прогрессом в WebAssembly, даже 3B-модели скоро станут браузерными. Осталось подождать... или начать экспериментировать сейчас.
Совет напоследок: не пытайтесь переписать весь ваш AI-стек на Rust сразу. Начните с самого болезненного места - например, с векторизации запросов в поиске. Убедитесь, что выгода стоит затрат. А потом уже думайте о полноценном AI-браузере.