Танцы с ONNX: 600 МБ ради пары ключевых слов
Представьте: вы пишете десктопное приложение на Tauri. Local-first, приватное, всё на Rust. Пользователь импортирует десяток документов, а система должна сама вытащить из них ключевые слова и построить поиск. Логичный шаг — взять готовую ONNX-модель для keyword extraction. BERT, DistilBERT, что-то из HuggingFace. Экспорт в ONNX, прогон через ONNX Runtime. 600 мегабайт веса. 200–400 мс на один документ. И это в приложении, которое задумывалось как молниеносный блокнот.
Я не шучу. Полгода назад мы зашили so-называемую tiny-модель (6 слоёв, 768 hidden) в бинарник. Размер вырос с 15 МБ до 615 МБ. Первый запуск на машине пользователя — подгрузка ONNX Runtime, создание сессии, аллокация тензоров. Потом ещё и предобработка токенизатора. Всё это ради того, чтобы вытащить из заголовка «Купить молоко и хлеб» слова «молоко» и «хлеб».
Бесит? Ещё как. Но главный вопрос: а обязательно ли для этой задачи вообще использовать нейросеть? Ключевые слова — это не семантика предложения, это частотность, дисперсия и небольшой процент редких терминов. Задача, с которой отлично справляются статистические алгоритмы. YAKE! — один из них.
YAKE! — бензопила для ключевых слов
YAKE! (Yet Another Keyword Extractor) — это алгоритм без машинного обучения. Он анализирует текст на основе пяти статистических признаков: позиция слова, частота, связь с другими словами, длина, дисперсия. Никаких моделей, никаких весов. Чистая математика. И он встраивается в любое приложение буквально парой файлов.
На Rust есть крейт yake-rust (форк от yake-rs, обновлён под последний stable). Он весит около 200 KB с зависимостями. Время экстракции — 5–15 мс на документ. Да, точность чуть ниже BERT на датасетах типа Inspec. Но в реальном продакшене пользователи не замечают разницы, если вы не строите семантический поиск с нуля.
Сравнение: ONNX (600 MB) vs YAKE! (0.2 MB)
| Параметр | ONNX-модель | YAKE! |
|---|---|---|
| Размер дистрибутива | ~615 MB | ~0.2 MB |
| Скорость (1 документ) | 200–400 мс (включая токенизацию) | 5–15 мс |
| Потребление памяти | ~900 MB (ONNX Runtime + сессия) | <10 MB |
| Точность (F1 на Inspec) | 0.52–0.58 | 0.47–0.53 |
| Зависимости | onnxruntime, tokenizers, ndarray | только std + lazy_static |
Разница в точности — 5–10 пунктов. Но для задачи автоматического тегирования заметок в локальном приложении это некритично. Пользователь всё равно хранит ключевые слова только для поиска, а не для NLP-пайплайна.
Пошаговый план: вырезаем ONNX, ставим YAKE!
1 Чистим зависимости проекта Tauri
В Cargo.toml удаляем всё, что связано с ONNX Runtime и tokenizers. Если использовали ort (crate for ONNX Runtime), удаляем и его. Заодно проверяем src-tauri/Cargo.toml:
# Было
ort = "2.0"
tokenizers = "0.15"
# Стало – ничего, только yake
2 Добавляем крейт yake-rust
В Cargo.toml:
[dependencies]
yake = { package = "yake-rust", version = "0.3" }
serde = { version = "1", features = ["derive"] }
На момент апреля 2026 стабильная версия — 0.3.1. Она использует Rayon для параллельной обработки, что идеально для батчей.
3 Пишем функцию извлечения ключевых слов в Rust
Пример кода, который вызывается из Tauri command:
use yake::{Yake, YakeParams};
#[tauri::command]
pub fn extract_keywords(text: &str) -> Vec {
let params = YakeParams::new()
.dedup_algo(yake::Dedup::Seq)
.window_size(2)
.top_n(10); // берём 10 лучших
let yake = Yake::with_params(params);
let result = yake.extract_keywords(text);
result.into_iter().map(|k| k.word).collect()
}
Ошибка, которую я видел у коллег: передача текста без удаления стоп-слов. YAKE! сам их учитывает, но если в стоп-листе нет русского «и», «в», «на» — он может выдать эти слова как ключевые. Обязательно проверьте стоп-слова для вашего языка.
4 Подключаем результат к SQLite FTS5
Мы храним извлечённые ключевые слова в отдельном столбце таблицы документов и используем SQLite FTS5 для полнотекстового поиска. Такой гибридный подход (ключевые слова + полнотекст) даёт хороший recall. В Tauri для SQLite отлично подходит крейт rusqlite с фичей bundled. Пример индекса:
CREATE VIRTUAL TABLE docs_fts USING fts5(title, body, keywords, content='docs');
При добавлении документа сохраняем его в docs, потом вставляем в FTS с теми же ID. Поле keywords заполняем результат работы YAKE! (через пробел).
5 Обновляем фронтенд
В TypeScript-части Tauri убираем вызов ONNX Runtime (если он был). Теперь команда — invoke('extract_keywords', { text }). Всё. Никаких громоздких бинарников.
Нюансы и грабли, на которые я наступил
⚠ YAKE! не понимает контекст
Если в документе слово «сеть» встречается одинаково часто и в значении IT-инфраструктура, и в значении рыболовная, YAKE! выдаст его как одно ключевое слово. Для нашего приложения это нормально. Но если вы строите медицинскую или юридическую систему — могут быть казусы. Здесь лучше комбинировать YAKE! с семантическими эмбеддингами, например, через fastembed. Но это уже утяжеление. Если хотите олдовый, но лёгкий путь — YAKE! + FTS5 даёт 90% потребностей.
⚠ Русский язык: морфология
YAKE! не стеммит и не лемматизирует. Слова «дом», «дома», «дому» считаются разными. Для ключевых слов это грустно: recall падает, если пользователь ищет «дом», а в тексте «дома». Решение — стемминг перед YAKE! Поднимаем крейт rust-stemmers и прогоняем термы. Сделайте это на Rust перед вызовом экстракции:
use rust_stemmers::{Algorithm, Stemmer};
let stemmer = Stemmer::create(Algorithm::Russian);
let stemmed_text: String = text
.split_whitespace()
.map(|w| stemmer.stem(w).to_string())
.collect::>()
.join(" ");
⚠ Размер дистрибутива — не единственный бонус
После удаления ONNX Runtime мы перестали зависеть от его версий. Больше никаких «проклятий» с dll/so/dylib. Сборка Tauri стала занимать 40 секунд, а не 4 минуты. И да, пользователи на старых Linux-дистрибутивах перестали жаловаться на segfault при запуске ONNX сессии.
Где ONNX всё-таки нужен?
Не советую использовать YAKE! для:
- Семантического поиска (тут нужны эмбеддинги, как в этом гайде по Qwen3-0.6B INT8).
- Классификации текстов (ONNX + DistilBERT справится точнее).
- Извлечения именованных сущностей (NER).
Но для простого keyword extraction — YAKE! убивает ONNX всухую.
Частые вопросы
Сколько памяти реально освободилось?
После удаления ONNX Runtime и модели: ~800 MB (раньше ~1.2 GB на старте). YAKE! занимает около 5 MB.
Можно ли вообще не использовать YAKE!, а просто брать частые слова?
Можно, но YAKE! даёт лучшую дисперсию и убирает слишком общие слова. Простая частотность выдаёт «и», «в», «это». YAKE! с дедупликацией и весами даёт информативные слова.
Что по скорости на 1000 документов?
С YAKE! — около 2–3 секунд. С ONNX — больше минуты. При этом процессор не греется.
Совет под конец (неочевидный)
Если вы всё же хотите семантику, но не хотите тащить тяжёлую ONNX, посмотрите на гибридный поиск в Newelle: YAKE! для быстрых ключевых слов + легковесные эмбеддинги от fastembed (модель all-MiniLM-L6-v2 в ONNX весит 80 MB). Так вы получаете лучшее от двух миров без 600 MB.
И помните: нейросеть — не серебряная пуля. Иногда старый добрый YAKE! решает задачу быстрее, проще и без боли в бюджете. Ваши пользователи скажут спасибо, когда приложение не будет жрать полгигабайта ради двух тегов.