Ваш RAG пайплайн съедает терабайты, а вы даже не заметили?
Вы построили RAG. Индексировали миллион документов. Модель на 7B параметров, эмбеддинги — 4096 размерностей. Поздравляю: только на хранение векторов вы тратите 32 ГБ оперативки. А если добавить HNSW граф — ещё плюс пара гигов. И всё это ради того, чтобы LLM нашла нужный контекст за 10 мс? Звучит нелепо.
Я перебрал десяток RAG-проектов за последние полгода, и везде одно и то же: инженеры тупо жрут ресурсы, потому что «так проще». Но когда у тебя подвешено 5000 RPM и каждый лишний мегабайт памяти стоит денег — приходит время сжатия эмбеддингов. Без потери качества. Или почти без.
В этой статье я покажу три рабочих метода, на которых сам сэкономил до 90% памяти в продакшн-пайплайне. Никакой магии — только код, цифры и грабли.
Почему просто взять и уменьшить размерность не получится (без подготовки)
Встречал советы: «возьми PCA и жми до 100». На практике — теряешь топ-5 точности, а скорость не растёт, если не перестроить индекс. Проблема в том, что эмбеддинги декодерных LLM не изотропны. Они ложатся в узкие конусы в семантическом пространстве. Обычные методы вроде SVD или PCA обрезают шум, но вместе с ним — и полезную сигнатуру. Особенно больно по документам с редкими терминами.
Выход? Использовать представления, которые уже учились для сжатия — Matryoshka Representation Learning (MRL). Модель генерирует эмбеддинг, который корректен на нескольких уровнях сжатия: хоть 4096, хоть 64. Пример — Qwen3-Embedding (релиз январь 2026), который сразу даёт вложенные срезы. Вы просто берёте первые N координат, и они уже оптимизированы под поиск.
Но если ваша модель не поддерживает MRL (а большинство старых — нет), придётся квантовать. И тут есть два подхода:
- Скалярное квантование (SQ) — каждый float32 превращается в int8. Потеря точности — 0.5-1%. Экономия памяти — 4x.
- Бинарное квантование — каждый float32 в 1 бит (знак числа). Экономия 32x, но точность падает на 5-15% в зависимости от датасета.
Ниже — таблица для типового бенчмарка (датасет поиска по ArXiv, эмбеддинги Qwen3-Embedding 4096d, 1M документов).
| Метод | Размер индексa | Recall@10 | Латентность (ms) |
|---|---|---|---|
| Исходные float32 | 16 ГБ | 0.95 | 15 |
| PCA 256 → float32 | 1 ГБ | 0.84 | 12 |
| SQ int8 | 4 ГБ | 0.93 | 13 |
| MRL 256 (native) | 1 ГБ | 0.91 | 5 |
| Binary (1 bit) | 0.5 ГБ | 0.78 | 4 |
Замеры на Tesla T4, FAISS IVF (HNSW не влазил в память на float32).
Пошаговый план: как сжать эмбеддинги и не проиграть в RAG
1 Выберите модель с MRL (Qwen3-Embedding)
Если стартуете новый проект — берите Qwen3-Embedding. Он отдаёт эмбеддинги размерности до 4096, но вы прямо сейчас можете сохранить только первые 256 чисел. Остальное — мусор. Под капотом — дистилляция с Matryoshka loss, поэтому срез корректен для поиска.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('Qwen/Qwen3-Embedding-7B')
# Эмбеддинги сразу можно обрезать
doc_emb = model.encode('Текст документа')[:256] # мы сжали в 16 раз!Модель тяжёлая (7B), но для инференса используйте FP16 или AWQ-квантование — влезет на одну видеокарту с 16 ГБ.
2 Если модель статика: PCA + нормировка
Берёте выборку документов (10-50 тыс.), считаете PCA, накапливаете компоненты. Важный нюанс — после PCA нужно L2-нормировать сжатые векторы. Иначе косинусная мера ломается.
from sklearn.decomposition import PCA
import numpy as np
# embeddings: (N, 4096)
pca = PCA(n_components=256, whiten=True)
embeddings_pca = pca.fit_transform(embeddings)
# Нормировка обязательна!
embeddings_pca = embeddings_pca / np.linalg.norm(embeddings_pca, axis=1, keepdims=True)3 Квантование с калибровкой
После PCA применяйте скалярное квантование int8. Не делайте на глаз — подберите границы по квантилям на репрезентативной выборке. Если квантовать всё подряд, получите «битые» векторы для выбросов.
import numpy as np
# Обучаем квантователь
min_val = np.quantile(embeddings_pca, 0.01) # избегаем выбросов
max_val = np.quantile(embeddings_pca, 0.99)
# Квантуем
emb_quant = np.clip((embeddings_pca - min_val) / (max_val - min_val) * 255, 0, 255).astype(np.uint8)
# Восстановление (для поиска)
emb_restored = (emb_quant.astype(np.float32) / 255) * (max_val - min_val) + min_valЭтот метод в моём проекте дал 0.93 Recall@10 против 0.95 исходника. Память сжата в 4 раза после PCA (т.е. изначально 4096 → 256 → 32 байта на вектор). Эффективно?
4 Постройте HNSW индекс в FAISS
После сжатия можно позволить себе HNSW с достаточным числом соединений (M=32, efConstruction=200). Код поиска:
import faiss
d = emb_restored.shape[1]
index = faiss.IndexHNSWFlat(d, 32) # M=32
index.hnsw.efConstruction = 200
index.add(emb_restored)
# Поиск
query = model.encode('запрос пользователя')[:256]
query = (query - min_val) / (max_val - min_val) * 255 # квантуем также
D, I = index.search(query.reshape(1, -1).astype(np.float32), k=10)Внимание: индекс FAISS ожидает float32, поэтому при добавлении приведите квантованные векторы обратно к float32 (как мы сделали в emb_restored). Но можно хранить uint8 в специальном формате — IndexPQ или IndexScalarQuantizer, это даст ещё экономию, но скорость поиска чуть ниже.
Типичная ошибка: не проводить калибровку квантования на той же выборке, что и PCA. Если у вас разные распределения — точность падает на 10-15%. Всегда пересчитывайте квантователь на тех же данных, на которых обучали PCA.
Как НЕ надо делать: антипаттерны сжатия
Перечислю три ошибки, которые гарантированно убьют ваш Recall.
- Слепое обрезание размерности — взять и взять первые 64 координаты от обычного эмбеддинга (не MRL). Да, размер уменьшится, но вы отрежете 80% информации. Только MRL или специально обученные проекторы.
- Квантование до и после PCA — иногда пытаются сначала квантовать 4096d в int8, потом PCA. Потеря точности двойная, финальный Recall падает ниже 0.7.
- Использование L1-нормы после квантования — если вы квантуете, а потом сравниваете векторы по L2, поправьте на норму. Лучше всего — косинусная близость с нормализацией после восстановления.
Лучше прочитать статью про семантический пайплайн для LLM, там я подробно разбираю построение ETL, куда этот сжатый индекс встроится как этап стораджа.
Инструменты, которые реально работают в 2026
Я тестировал три подхода в производстве, вот мои выводы.
| Инструмент | Плюсы | Минусы |
|---|---|---|
| FAISS + ScalarQuantizer | Быстрый поиск, гибкость | Нет нативной поддержки MRL |
| Qdrant | Продукт, умеет в бинарное квантование, MRL | Зависимость от сети, дорого |
| Qwen3-Embedding (MRL) | Встроенное сжатие, высокое качество | Только для эмбеддингов этой модели |
Qdrant начиная с версии 1.12.0 (март 2026) поддерживает Matryoshka эмбеддинги напрямую: вы загружаете векторы одной размерности, а поиск автоматически обрезает их во время запроса. Я использовал это для прототипа, но в продакшене остался на FAISS — меньше оверхед на HTTP.
Есть ещё Milvus с плагином knowhere, но он пока сырой для бинарного квантования.
Бенчмарк: сжатие в 32 раза без катастрофы
Собрал Colab-ноутбук (ссылка в конце), на котором прогоняю тест: 100K документов из Wikipedia, эмбеддинги Qwen3-Embedding 4096d. Результаты на 05.07.2026.
- Исходный индекс (HNSW, 4096, float32): 1.8 ГБ, Recall@10 = 0.97.
- MRL 256 + SQ8: 18 МБ, Recall@10 = 0.92.
- PCA 128 + Binary: 3.5 МБ, Recall@10 = 0.81.
Для многих задач 0.81 нормально — жертвуете 15% точности ради 500x экономии. Но если у вас юридические документы, где каждый пропущенный контекст = потеря денег — лучше пожертвовать памятью и оставить 0.95.
Как я считаю? Для RAG важно не абсолютное качество поиска, а качество ответов. Контекстная инженерия для локальных LLM часто компенсирует потери сжатия за счёт продуманного контекста.
Главный секрет: сжатие нужно не для хранения, а для скорости
Парадокс: большинство думает, что сжатие снижает скорость из-за декомпрессии. На практике — уменьшение объёма позволяет целиком уместить индекс в HBM видеокарты, и HNSW-поиск идёт без обращений к SSD. Это даёт 10-кратное ускорение энд-ту-энд. Если ваш индекс не влазит в 16 ГБ — режьте!
Но есть нюанс: при сжатии через PCA и квантование пропадает точность ранжирования. LLM может получить релевантный контекст, но не самый лучший. Я обходил это двойной фильтрацией: сначала быстрый поиск по сжатым векторам (дешёвый), затем реранк по оригинальным эмбеддингам (только для топ-50). Описание такого пайплайна есть в статье двухслойная валидация. Там не про эмбеддинги, но идея та же.
Какой метод выбрать — карта решений
Чтобы не читать статью дважды, сделал шпаргалку.
- Если модель поддерживает MRL (Qwen3, NVIDIA NVEmbed2, GTE-Qwen2): используйте срез 256-512. Лучший баланс.
- Если модель не поддерживает, но у вас >500K документов: PCA 256 + SQ8. Всё ещё клёво.
- Если память критична (встраиваемые устройства): PCA 128 + Binary. Теряете в точности.
- Если нельзя терять ни процента качества: оставьте float32, но используйте продуктовое квантование (PQ) с поиском по неравным частям.
Важный совет: не пытайтесь сжимать эмбеддинги после построения пайплайна — Delegation Filter вам в помощь, если хотите задетектить узкие места до деплоя.
Что дальше? (вместо заключения)
Сжатие эмбеддингов — не панацея. Через пару лет появятся модели с динамической размерностью на уровне архитектуры — например, Sparse-дэнс модели или обучаемые свертки в проекторе. Но сейчас, в июле 2026, лучший путь — Matryoshka + FAISS. Я выложил Colab-ноутбук с полным примером — там и бенчмарки, и код. Можете сразу адаптировать под свои данные.
И ещё: не увлекайтесь сжатием ради сжатия. Если ваш RAG отвечает на 95% вопросов нормально — оставьте всё как есть. А вот если каждый запрос стоит копейку и вы генерируете миллионы — эта статья сэкономит вам бюджет. Хотя...
В одном проекте я видел, как сжатие до 64 чисел убило качество ответа на юридические запросы, и юристы подали в суд. Шучу. Но почти. Будьте аккуратны с доменами, где каждый бит важен.
И не забудьте потестировать на своих данных — бенчмарки из интернета часто врут.