Квантование и Matryoshka Embeddings: экономия 80% на векторных базах | AiManual
AiManual Logo Ai / Manual.
12 Мар 2026 Гайд

Квантование и Matryoshka Embeddings: как сократить расходы на векторные базы данных на 80%

Практическое руководство по квантованию и Matryoshka Representation Learning для уменьшения размера эмбеддингов. Расчёт экономии памяти и стоимости. Примеры код

Ваши векторы съедают бюджет? Проблема в 4 байтах

Представьте: вы построили умный поиск по документам, используя векторную базу. Всё работает, но счёт от облачного провайдера приходит на $5000 в месяц. Вы смотрите на график использования памяти - 500 ГБ под векторы. И это только начало.

Каждый эмбеддинг в формате Float32 занимает 4 байта на измерение. Возьмём популярную размерность 1536 (как у text-embedding-ada-002) и 1 миллион документов:

1_000_000 * 1536 * 4 = 6_144_000_000 байт ≈ 6.14 GB

Но это только сырые данные. Добавьте индекс HNSW или IVF, и объём удвоится. А если у вас 10 миллионов документов? 60 GB. А если размерность 4096, как у новейших моделей на 12.03.2026? 163 GB. И это без учёта репликации.

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

Хуже того, стоимость хранения в облаке - это только часть. Оперативная память для поиска, IOPS, сеть - всё масштабируется с размером векторов. Уменьшив размер в 4-5 раз, вы сэкономите не только на диске, но и на процессоре, и на задержках.

В статье Токенизация в LLM я уже показывал, как мелкие детали влияют на стоимость. С векторами - та же история.

Квантование: когда точность не нужна, но деньги жалко

Квантование - это преобразование чисел с плавающей точкой в целочисленные. Проще: вместо Float32 (4 байта) использовать Int8 (1 байт). Экономия - 75% сразу.

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

На практике, потеря точности при квантовании с Float32 до Int8 часто менее 1% по метрикам как Recall@10. Но это если сделать правильно.

Совет: не квантуйте случайно. Используйте калибровку на репрезентативной выборке, чтобы определить оптимальный масштаб. Иначе рискуете потерять информацию на краях распределения.

Вот как это выглядит в коде:

import numpy as np

def quantize_embeddings(embeddings: np.ndarray) -> tuple[np.ndarray, float]:
    """
    Квантование массива Float32 в Int8.
    Возвращает квантованные эмбеддинги и масштаб для деквантования.
    """
    # Находим максимальное абсолютное значение для масштабирования
    abs_max = np.max(np.abs(embeddings))
    scale = 127.0 / abs_max  # 127, а не 128, потому что симметричный диапазон [-127, 127]
    
    # Масштабируем и округляем до целого
    quantized = np.round(embeddings * scale).astype(np.int8)
    
    return quantized, scale

def dequantize_embeddings(quantized: np.ndarray, scale: float) -> np.ndarray:
    """Деквантование обратно в Float32."""
    return quantized.astype(np.float32) / scale

Но это наивный подход. В реальности лучше использовать per-tensor или per-channel квантование, как в TensorRT или ONNX Runtime. Для векторных баз, как FAISS или Qdrant, есть встроенная поддержка Int8.

Кстати, в статье Как ускорить семантический поиск в 20 раз я уже рассказывал про использование Int8 для ресоринга на CPU. Там же про бинарные индексы - ещё более агрессивное сжатие.

Matryoshka: вложенные эмбеддинги как русская матрёшка

Matryoshka Representation Learning (MRL) - это техника обучения моделей, при которой эмбеддинг строится как матрёшка: внутри полного вектора из N измерений лежат вложенные подвекторы меньшей размерности.

Идея: первые k измерений несут основную семантическую нагрузку. Для простых задач хватит 256 измерений, для сложных - все 1536. Вы сами выбираете баланс между точностью и размером.

💡
MRL не требует переобучения модели с нуля. Современные фреймворки, как Sentence Transformers, позволяют дообучить существующую модель с добавлением Matryoshka-лосса.

Практический пример: у вас есть модель с размерностью 768. Вы используете только первые 128 измерений для индексации - экономия памяти в 6 раз. При поиске сначала фильтруете по усечённым векторам, затем переранжируете полными.

Как это выглядит в коде:

from sentence_transformers import SentenceTransformer

# Загрузка модели с поддержкой MRL (например, nomic-embed)
model = SentenceTransformer('nomic-ai/nomic-embed-text-v1.5')

# Кодирование текста с указанием целевой размерности
embeddings_full = model.encode(['Ваш текст здесь'], output_dim=768)
embeddings_small = model.encode(['Ваш текст здесь'], output_dim=128)

# Теперь embeddings_small занимает в 6 раз меньше места

Но не все модели поддерживают MRL из коробки. На 12.03.2026, среди популярных - nomic-embed, и некоторые версии pplx-embed. Проверяйте документацию.

Кстати, MRL - не единственный способ уменьшить размерность. В статье Sparsity: как прототип для разреженных эмбеддингов экономит 15-50x памяти я рассказывал про разреженные эмбеддинги - ещё более экстремальное сжатие.

1 Оцените текущие расходы

Прежде чем что-то менять, посчитайте, сколько вы тратите сейчас. Возьмите количество документов, размерность эмбеддингов и тип данных (Float32, Float16).

def calculate_storage_size(num_docs: int, dim: int, dtype: str = 'float32') -> float:
    """Рассчёт размера хранения в гигабайтах."""
    bytes_per_element = 4 if dtype == 'float32' else 2 if dtype == 'float16' else 1
    total_bytes = num_docs * dim * bytes_per_element
    return total_bytes / (1024 ** 3)  # в GB

# Пример: 1M документов, размерность 1536, Float32
size_gb = calculate_storage_size(1_000_000, 1536, 'float32')
print(f'Текущий размер: {size_gb:.2f} GB')

Теперь умножьте на стоимость хранения GB/месяц в вашем облаке. Добавьте стоимость оперативной памяти для индекса. Ужаснётесь.

2 Выберите метод квантования

Решите, какое квантование использовать: постобработку или встроенное в модель. Если используете API, как OpenAI или Perplexity, проверьте, есть ли у них квантованные эмбеддинги.

Например, pplx-embed от Perplexity предлагает версии с Int8. Подробнее в статье pplx-embed от Perplexity.

Если генерируете эмбеддинги локально, используйте библиотеки как ONNX Runtime для квантования на лету.

# Пример квантования с калибровкой
import onnxruntime as ort
from onnxruntime.quantization import quantize_dynamic, QuantType

# Загрузка модели ONNX
onnx_model_path = 'model.onnx'
quantized_model_path = 'model_quantized.onnx'

# Динамическое квантование (проще, но менее точно)
quantize_dynamic(onnx_model_path, quantized_model_path, weight_type=QuantType.QInt8)

3 Примените Matryoshka Embeddings

Если ваша модель поддерживает MRL, просто укажите меньшую размерность при инференсе. Если нет - придётся дообучать или выбрать другую модель.

Для дообучения существующей модели на MRL, можно использовать библиотеку Sentence Transformers с MatryoshkaLoss. Но это тема для отдельной статьи.

Совет: начните с уменьшения размерности в 2-4 раза. Например, с 768 до 256. Протестируйте точность.

4 Протестируйте точность

Без тестирования - никуда. Возьмите тестовый набор запросов и документов, посчитайте метрики: Recall@k, MRR, NDCG. Сравните точность до и после оптимизации.

from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

def test_accuracy(original_embeddings, compressed_embeddings, queries, relevant_docs):
    """
    Сравнение точности оригинальных и сжатых эмбеддингов.
    queries: индексы запросов в массиве
    relevant_docs: список списков индексов релевантных документов для каждого запроса
    """
    recalls = []
    for q_idx, rel_docs in zip(queries, relevant_docs):
        # Косинусная близость для оригинальных эмбеддингов
        sim_orig = cosine_similarity([original_embeddings[q_idx]], original_embeddings)[0]
        top_k_orig = np.argsort(sim_orig)[-10:][::-1]  # топ-10
        
        # Для сжатых
        sim_comp = cosine_similarity([compressed_embeddings[q_idx]], compressed_embeddings)[0]
        top_k_comp = np.argsort(sim_comp)[-10:][::-1]
        
        # Считаем recall@10
        recall_orig = len(set(top_k_orig) & set(rel_docs)) / len(rel_docs)
        recall_comp = len(set(top_k_comp) & set(rel_docs)) / len(rel_docs)
        
        recalls.append((recall_orig, recall_comp))
    
    avg_recall_orig = np.mean([r[0] for r in recalls])
    avg_recall_comp = np.mean([r[1] for r in recalls])
    
    return avg_recall_orig, avg_recall_comp

Если потеря точности менее 3-5%, можно спать спокойно.

5 Разверните в продакшене

Теперь разверните оптимизированные эмбеддинги в векторной базе. Убедитесь, что база данных поддерживает Int8 или меньшую размерность.

Например, Qdrant позволяет указывать тип данных для векторов. В FAISS нужно использовать индекс, совместимый с Int8.

import faiss

# Создание индекса для Int8 эмбеддингов
dimension = 128  # после MRL
index = faiss.IndexFlatIP(dimension)  # Inner Product индекс
# Преобразование в Int8 (если нужно)
# FAISS работает с Float32, но вы можете хранить Int8 и преобразовывать при поиске

Не забудьте обновить пайплайн индексации: теперь эмбеддинги нужно квантовать и обрезать перед сохранением.

Ошибки, которые сведут экономию на нет

Я видел, как команды теряли 80% экономии из-за глупых ошибок. Вот топ-3:

  1. Квантование без калибровки: брать random scale factor - всё равно что сжать картинку в JPEG с качеством 1%. Используйте калибровочный набор, чтобы найти оптимальный масштаб.
  2. Слепое уменьшение размерности в Matryoshka: отрезать 90% измерений для сложных задач - получите мусор. Тестируйте на ваших данных. Для простого поиска по новостям хватит 128 измерений, для медицинских документов - все 768.
  3. Игнорирование переобучения: если вы дообучаете модель с MRL, не забудьте про аугментацию данных и правильный лосс. Иначе модель забудет, что учила раньше.

Ещё одна ошибка - не считать TCO (total cost of ownership). Экономия на хранении может увеличить затраты на CPU при поиске, если индекс станет менее эффективным. Всегда измеряйте end-to-end производительность.

Проверка: после оптимизации запустите нагрузочный тест. Убедитесь, что latency и throughput остались в пределах SLA. Иногда лучше потратить на 10% больше памяти, но сохранить скорость ответа 50 мс.

FAQ: ответы на вопросы, которые вы боялись задать

Вопрос Ответ
Насколько квантование влияет на точность? В среднем, потеря 1-3% по Recall@10 для семантического поиска. Для ресоринга - до 5%. Но если калибровать правильно, часто разница статистически незначима.
Все ли модели поддерживают Matryoshka? Нет. На 12.03.2026 поддержка есть у nomic-embed, некоторых версий pplx-embed и кастомных моделей, обученных с MRL. Проверяйте документацию.
Можно ли комбинировать квантование и Matryoshka? Да, и это даёт синергию. Сначала уменьшаем размерность в 3 раза с MRL, затем квантуем в Int8 - общая экономия 12x (с 4 байт до 0.33 байта на измерение).
Как рассчитать экономию для моего случая? Используйте формулу: (размер_до - размер_после) / размер_до. Где размер = num_docs * dim * bytes_per_element. Не забудьте про индекс и репликацию.

Что дальше? Будущее сжатых эмбеддингов

К 2027 году, я уверен, стандартом станут эмбеддинги с адаптивной размерностью и автоматическим квантованием. Модель будет выдавать ровно столько измерений, сколько нужно для конкретной задачи, и сразу в Int4.

Уже сейчас появляются модели, как nomic-embed-text-v1.5, которые поддерживают MRL из коробки. А фреймворки, как ONNX Runtime, позволяют квантовать до Int4 с минимальной потерей точности.

Но главный тренд - это специализированные hardware для векторного поиска. Графические процессоры с поддержкой Int8, а скоро и Int4, ускорят поиск в разы. И тогда экономия на памяти превратится в экономию на инфраструктуре вообще.

А пока - начните с квантования и Matryoshka. Ваш CFO скажет спасибо.

P.S. Если вы работаете с огромными объёмами документов, как в статье Локальный RAG для 4 миллионов PDF, эти методы не просто рекомендуются, они обязательны.

Подписаться на канал