Квантование#

Квантование — необязательная функция в Platform V Vector DB (далее - Vector DB), которая позволяет эффективно хранить и искать многомерные векторы. Преобразовывая исходные векторы в новые представления, квантование сжимает данные при сохранении относительных расстояний между векторами, близкими к оригинальным. Различные методы квантования имеют разные механизмы работы и компромиссы, которые будут рассмотрены в данном разделе.

Квантование используется главным образом для уменьшения объема памяти и ускорения процесса поиска в пространствах многомерных векторов. В контексте Vector DB квантование позволяет оптимизировать поисковую систему под конкретные сценарии использования, находя баланс между точностью, эффективностью хранения данных и скоростью поиска.

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

Скалярное квантование#

Скалярное квантование, в контексте поисковых систем по векторным данным, представляет собой метод сжатия, который уменьшает число бит, используемых для представления каждого компонента вектора.

Например, Vector DB использует числа с плавающей запятой длиной 32 бита для представления компонентов исходного вектора. Скалярное квантование позволяет уменьшить число бит до 8. Другими словами, Vector DB выполняет преобразование float32 -> uint8 для каждого компонента вектора. Фактически это означает, что объем памяти, необходимый для хранения вектора, уменьшается в четыре раза.

Помимо сокращения объема используемой памяти, скалярное квантование также ускоряет процесс поиска. Vector DB применяет специальную инструкцию SIMD-процессора для быстрого сравнения векторов. Эта инструкция работает с целыми числами размером 8 бит, поэтому преобразование в формат uint8 позволяет Vector DB выполнять сравнение быстрее.

Основной недостаток скалярного квантования заключается в потере точности. Преобразование float32 -> uint8 вводит ошибку, которая может привести к незначительному снижению качества поиска. Однако эта ошибка обычно невелика и становится менее значимой для высокомерных векторов. По результатам экспериментов выяснилось, что ошибка, вызванная скалярным квантованием, обычно составляет менее 1%.

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

Бинарное квантование#

Бинарное квантование — крайняя форма скалярного квантования. Этот механизм позволяет представить каждый компонент вектора одним битом, тем самым уменьшая занимаемый объем памяти примерно в 32 раза.

Это самый быстрый способ квантования, поскольку он позволяет выполнить сравнение векторов всего несколькими инструкциями центрального процессора.

Бинарное квантование способно обеспечить ускорение вплоть до 40 раз по сравнению с исходными векторами.

Бинарное квантование эффективно только для многомерных векторов и требует центрированного распределения компонентов вектора.

На данный момент бинарное квантование показывает хорошие результаты точности со следующими моделями:

  • OpenAI text-embedding-ada-002 - протестировано на наборе данных dbpedia с размерностью 1536d, достигнуто 0.98 recall@100 при четырехкратном оверсемплинге.

  • Cohere AI embed-english-v2.0 - протестирована на встраиваниях из Википедии с размерностью 4096d, достигнут показатель 0.98 recall@50 при двукратном оверсемплинге.

Модели с меньшей размерностью или иным распределением компонентов могут потребовать дополнительных экспериментов для нахождения оптимальных параметров квантования.

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

Бинарное квантование как расстояние Хэмминга#

Дополнительным преимуществом данного метода является возможность эффективного моделирования расстояния Хэмминга с помощью скалярного произведения.

В частности, если исходные векторы содержат значения {-1, 1}, тогда скалярное произведение двух векторов будет равно расстоянию Хэмминга путем простой замены -1 на 0 и 1 на 1.

Пример таблицы истинности:

Вектор 1

Вектор 2

Скалярное произведение

1

1

1

1

-1

-1

-1

1

-1

-1

-1

1

Вектор 1

Вектор 2

Расстояние Хэмминга

1

1

0

1

0

1

0

1

1

0

0

0

Обе функции равны постоянному коэффициенту, что делает поиск сходства эквивалентным. Бинарное квантование позволяет эффективно сравнивать векторы с помощью этого представления.

Продуктовое квантование#

Продуктовое квантование — метод сжатия векторов, направленный на минимизацию требуемого объема памяти за счет разделения векторов на сегменты и последующего индивидуального квантования каждого сегмента. Каждый сегмент аппроксимируется индексом центроида, представляющим оригинальный компонент вектора. Положение центроидов определяется с помощью алгоритма кластеризации, например k-средних. На текущий момент Vector DB поддерживает использование только 256 центроидов, следовательно, каждый индекс центроида может быть представлен одним байтом.

Продуктовое квантование позволяет достичь более существенного коэффициента сжатия по сравнению со скалярным методом. Но здесь имеются свои компромиссы. Вычисления расстояний продуктового квантования плохо совместимы с SIMD-инструкциями, поэтому они медленнее, чем у скалярного квантования. Также продуктовое квантование сопровождается потерей точности, поэтому рекомендуется применять его исключительно для многомерных векторов.

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

Выбор подходящего метода квантования#

Преимуществ и недостатков каждого метода квантования:

Метод квантования

Точность

Скорость

Коэффициент сжатия

Скалярный

0.99

до х2

4

Бинарный

0.95*

до х40

32

Продуктовый

0.7

0.5

до 64

Примечание

* - для совместимых моделей

Итого:

  • Скалярное квантование — универсальный метод, обеспечивающий хороший компромисс между точностью, скоростью и степенью сжатия. Рекомендуемое квантование по умолчанию, если бинарное квантование неприменимо.

  • Бинарное квантование — самый быстрый и наиболее экономичный по памяти метод, но требует центрированное распределение компонентов вектора. Рекомендуется использовать только с проверенными моделями.

  • Продуктовое квантование — может предложить лучший коэффициент сжатия, однако имеет значительную потерю точности и медленнее, чем скалярное квантование. Рекомендуется, когда экономия памяти стоит на первом месте, а скорость поиска не критична.

Настройка квантования#

Можно настроить квантование для коллекции, указав параметры квантования в секции quantization_config конфигурации коллекции.

Квантование автоматически применяется ко всем векторам в процессе индексации. Квантованные векторы хранятся наряду с исходными векторами в коллекции, таким образом сохраняется доступ к исходным векторам, если они понадобятся.

Параметр quantization_config также может быть установлен индивидуально для каждого вектора, задав его в именованном векторе.

Настройка скалярного квантования#

Чтобы включить скалярное квантование, необходимо указать параметры квантования в секции quantization_config конфигурации коллекции.

При включении скалярного квантования для существующей коллекции используйте запрос PATCH или соответствующий метод update_collection и опустите конфигурацию вектора, так как она уже определена.

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine"
    },
    "quantization_config": {
        "scalar": {
            "type": "int8",
            "quantile": 0.99,
            "always_ram": true
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8,
            quantile=0.99,
            always_ram=True,
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
  },
  quantization_config: {
    scalar: {
      type: "int8",
      quantile: 0.99,
      always_ram: true,
    },
  },
});
use qdrant_client::qdrant::{
    CreateCollectionBuilder, Distance, QuantizationType, ScalarQuantizationBuilder,
    VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine))
            .quantization_config(
                ScalarQuantizationBuilder::default()
                    .r#type(QuantizationType::Int8.into())
                    .quantile(0.99)
                    .always_ram(true),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.QuantizationConfig;
import io.qdrant.client.grpc.Collections.QuantizationType;
import io.qdrant.client.grpc.Collections.ScalarQuantization;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .build())
                    .build())
            .setQuantizationConfig(
                QuantizationConfig.newBuilder()
                    .setScalar(
                        ScalarQuantization.newBuilder()
                            .setType(QuantizationType.Int8)
                            .setQuantile(0.99f)
                            .setAlwaysRam(true)
                            .build())
                    .build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
 collectionName: "{collection_name}",
 vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine },
 quantizationConfig: new QuantizationConfig
 {
  Scalar = new ScalarQuantization
  {
   Type = QuantizationType.Int8,
   Quantile = 0.99f,
   AlwaysRam = true
  }
 }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
  }),
  QuantizationConfig: qdrant.NewQuantizationScalar(
      &qdrant.ScalarQuantization{
            Type:      qdrant.QuantizationType_Int8,
          Quantile:  qdrant.PtrOf(float32(0.99)),
          AlwaysRam: qdrant.PtrOf(true),
      },
  ),
})

Есть три параметра, которые можно задать в секции quantization_config:

  • type - тип компонентов квантованного вектора. Сейчас Vector DB поддерживает только int8.

  • quantile - квантиль компонентов квантованного вектора. Квантиль используется для вычисления границ квантования. Например, если установить 0.99 в качестве квантиля, 1% экстремальных значений будут исключены из границ квантования.

    Использование квантиля ниже 1.0 может быть полезно, если среди компонентов вектора присутствуют выбросы. Этот параметр влияет только на итоговую точность результата и не оказывает влияния на объем потребляемой памяти. Возможно, стоит попробовать изменить этот параметр, если было замечено значительное ухудшение качества поиска.

  • always_ram - определяет, следует ли всегда кешировать квантованные векторы в оперативной памяти. По умолчанию квантованные векторы загружаются аналогично исходным векторам. В некоторых случаях можно возникнуть необходимость держать квантованные векторы в оперативной памяти для повышения скорости поиска. В таком случае установите always_ram в true для хранения квантованных векторов в оперативной памяти.

Настройка бинарного квантования#

Чтобы включить бинарное квантование, нужно указать параметры квантования в секции quantization_config конфигурации коллекции.

При включении бинарного квантования для существующей коллекции используйте запрос PATCH или соответствующий метод update_collection и пропустите конфигурацию вектора, так как она уже задана.

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 1536,
      "distance": "Cosine"
    },
    "quantization_config": {
        "binary": {
            "always_ram": true
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=1536, distance=models.Distance.COSINE),
    quantization_config=models.BinaryQuantization(
        binary=models.BinaryQuantizationConfig(
            always_ram=True,
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 1536,
    distance: "Cosine",
  },
  quantization_config: {
    binary: {
      always_ram: true,
    },
  },
});
use qdrant_client::qdrant::{
    BinaryQuantizationBuilder, CreateCollectionBuilder, Distance, VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(1536, Distance::Cosine))
            .quantization_config(BinaryQuantizationBuilder::new(true)),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.BinaryQuantization;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.QuantizationConfig;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(1536)
                            .setDistance(Distance.Cosine)
                            .build())
                    .build())
            .setQuantizationConfig(
                QuantizationConfig.newBuilder()
                    .setBinary(BinaryQuantization.newBuilder().setAlwaysRam(true).build())
                    .build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
 collectionName: "{collection_name}",
 vectorsConfig: new VectorParams { Size = 1536, Distance = Distance.Cosine },
 quantizationConfig: new QuantizationConfig
 {
  Binary = new BinaryQuantization { AlwaysRam = true }
 }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     1536,
      Distance: qdrant.Distance_Cosine,
  }),
  QuantizationConfig: qdrant.NewQuantizationBinary(
      &qdrant.BinaryQuantization{
          AlwaysRam: qdrant.PtrOf(true),
      },
  ),
})

always_ram - указывает, следует ли постоянно кешировать квантованные векторы в оперативной памяти. По умолчанию квантованные векторы загружаются аналогичным образом, как и оригинальные векторы. В некоторых сценариях может возникнуть необходимость сохранять квантованные векторы в оперативной памяти для увеличения скорости поиска. В таком случае можно установить always_ram в true для сохранения квантованных векторов в оперативной памяти.

Настройка продуктового квантования#

Чтобы включить продуктовое квантование, укажите параметры квантования в секции quantization_config конфигурации коллекции.

При включении продуктового квантования для существующей коллекции используйте запрос PATCH или соответствующий метод update_collection и пропускайте конфигурацию вектора, так как она уже указана.

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine"
    },
    "quantization_config": {
        "product": {
            "compression": "x16",
            "always_ram": true
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
    quantization_config=models.ProductQuantization(
        product=models.ProductQuantizationConfig(
            compression=models.CompressionRatio.X16,
            always_ram=True,
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
  },
  quantization_config: {
    product: {
      compression: "x16",
      always_ram: true,
    },
  },
});
use qdrant_client::qdrant::{
    CompressionRatio, CreateCollectionBuilder, Distance, ProductQuantizationBuilder,
    VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine))
            .quantization_config(
                ProductQuantizationBuilder::new(CompressionRatio::X16.into()).always_ram(true),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CompressionRatio;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.ProductQuantization;
import io.qdrant.client.grpc.Collections.QuantizationConfig;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .build())
                    .build())
            .setQuantizationConfig(
                QuantizationConfig.newBuilder()
                    .setProduct(
                        ProductQuantization.newBuilder()
                            .setCompression(CompressionRatio.x16)
                            .setAlwaysRam(true)
                            .build())
                    .build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
 collectionName: "{collection_name}",
 vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine },
 quantizationConfig: new QuantizationConfig
 {
  Product = new ProductQuantization { Compression = CompressionRatio.X16, AlwaysRam = true }
 }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
  }),
  QuantizationConfig: qdrant.NewQuantizationProduct(
      &qdrant.ProductQuantization{
          Compression: qdrant.CompressionRatio_x16,
          AlwaysRam:   qdrant.PtrOf(true),
      },
  ),
})

Имеется два параметра, которые можно задать в секции quantization_config:

  • compression - коэффициент сжатия. Коэффициент сжатия отражает отношение размера квантованного вектора в байтах к размеру исходного вектора в байтах. В данном случае квантованный вектор будет в 16 раз меньше исходного.

  • always_ram - указание, следует ли постоянно кешировать квантованные векторы в оперативной памяти. По умолчанию квантованные векторы загружаются аналогично исходным векторам. В некоторых ситуациях можно предпочесть хранение квантованных векторов в оперативной памяти для ускорения процесса поиска. Тогда установите always_ram в true.

Поиск с квантованием#

Vector DB автоматически воспользуется квантованными векторами, если они доступны.

Существуют несколько опций, которыми можно воспользоваться для управления процессом поиска:

POST /collections/{collection_name}/points/query
{
    "query": [0.2, 0.1, 0.9, 0.7],
    "params": {
        "quantization": {
            "ignore": false,
            "rescore": true,
            "oversampling": 2.0
        }
    },
    "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.query_points(
    collection_name="{collection_name}",
    query=[0.2, 0.1, 0.9, 0.7],
    search_params=models.SearchParams(
        quantization=models.QuantizationSearchParams(
            ignore=False,
            rescore=True,
            oversampling=2.0,
        )
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: [0.2, 0.1, 0.9, 0.7],
    params: {
        quantization: {
            ignore: false,
            rescore: true,
            oversampling: 2.0,
        },
    },
    limit: 10,
});
use qdrant_client::qdrant::{
    QuantizationSearchParamsBuilder, QueryPointsBuilder, SearchParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;
    
client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(vec![0.2, 0.1, 0.9, 0.7])
            .limit(10)
            .params(
                SearchParamsBuilder::default().quantization(
                    QuantizationSearchParamsBuilder::default()
                        .ignore(false)
                        .rescore(true)
                        .oversampling(2.0),
                ),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.QuantizationSearchParams;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.SearchParams;

import static io.qdrant.client.QueryFactory.nearest;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(
        QueryPoints.newBuilder()
                .setCollectionName("{collection_name}")
                .setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
                .setParams(
                        SearchParams.newBuilder()
                                .setQuantization(
                                        QuantizationSearchParams.newBuilder()
                                                .setIgnore(false)
                                                .setRescore(true)
                                                .setOversampling(2.0)
                                                .build())
                                .build())
                .setLimit(10)
                .build())
        .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
  collectionName: "{collection_name}",
  query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
  searchParams: new SearchParams
  {
      Quantization = new QuantizationSearchParams
      {
          Ignore = false,
          Rescore = true,
          Oversampling = 2.0
      }
  },
  limit: 10
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
  CollectionName: "{collection_name}",
  Query:          qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
  Params: &qdrant.SearchParams{
      Quantization: &qdrant.QuantizationSearchParams{
          Ignore:       qdrant.PtrOf(false),
          Rescore:      qdrant.PtrOf(true),
          Oversampling: qdrant.PtrOf(2.0),
      },
  },
})
  • ignore - переключатель, определяющий, игнорировать ли квантованные векторы во время поиска. По умолчанию Vector DB будет использовать квантованные векторы, если они доступны.

  • rescore - при наличии оригинальных векторов Vector DB способен повторно оценить топ-K результатов поиска, используя оригинальные векторы. Это может повысить качество поиска, хотя и немного замедлить его по сравнению с поиском без повторной оценки. Рекомендуется отключать повторную оценку только в тех случаях, когда оригинальные векторы хранятся на медленном носителе (например, HDD или сетевое хранилище). По умолчанию повторная оценка включена.

  • oversampling - определяет, сколько дополнительных векторов должно быть предварительно выбрано с использованием квантованного индекса, после чего производится повторная оценка с использованием оригинальных векторов. Например, если оверсемплинг равен 2.4, а лимит - 100, то сначала будет предварительно отобрано 240 векторов с использованием квантованного индекса, затем после повторной оценки вернутся верхние 100. Оверсемплинг полезен, если необходимо найти оптимальный баланс между скоростью поиска и качеством результатов во время выполнения запроса.

Советы по квантованию#

Настройка точности#

Самый быстрый способ понять влияние квантования на качество поиска — сравнить результаты поиска с квантованием и без него.

Чтобы отключить квантование, можно установить ignore в true в запросе поиска:

POST /collections/{collection_name}/points/query
{
    "query": [0.2, 0.1, 0.9, 0.7],
    "params": {
        "quantization": {
            "ignore": true
        }
    },
    "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.query_points(
    collection_name="{collection_name}",
    query=[0.2, 0.1, 0.9, 0.7],
    search_params=models.SearchParams(
        quantization=models.QuantizationSearchParams(
            ignore=True,
        )
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: [0.2, 0.1, 0.9, 0.7],
    params: {
        quantization: {
            ignore: true,
        },
    },
});
use qdrant_client::qdrant::{
    QuantizationSearchParamsBuilder, QueryPointsBuilder, SearchParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(vec![0.2, 0.1, 0.9, 0.7])
            .limit(3)
            .params(
                SearchParamsBuilder::default()
                    .quantization(QuantizationSearchParamsBuilder::default().ignore(true)),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.QuantizationSearchParams;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.SearchParams;

import static io.qdrant.client.QueryFactory.nearest;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(
        QueryPoints.newBuilder()
                .setCollectionName("{collection_name}")
                .setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
                .setParams(
                        SearchParams.newBuilder()
                                .setQuantization(
                                        QuantizationSearchParams.newBuilder().setIgnore(true).build())
                                .build())
                .setLimit(10)
                .build())
        .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
  collectionName: "{collection_name}",
  query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
  searchParams: new SearchParams
  {
      Quantization = new QuantizationSearchParams { Ignore = true }
  },
  limit: 10
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
  CollectionName: "{collection_name}",
  Query:          qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
  Params: &qdrant.SearchParams{
      Quantization: &qdrant.QuantizationSearchParams{
          Ignore: qdrant.PtrOf(false),
      },
  },
})
  • Изменить параметр квантили: параметр квантили в скалярном квантовании определяет границы квантования. Установив его значение ниже 1.0, можно исключить экстремальные значения (выбросы) из границ квантования. Например, установив квантиль равным 0.99, будет исключен 1% экстремальных значений. Подбирая квантиль, будет найдено оптимальное значение, которое обеспечит наилучшее качество поиска для коллекции.

  • Включить пересчет оценок: при наличии оригинальных векторов Vector DB может заново оценить топ-K результатов поиска, используя оригинальные векторы. На больших коллекциях это может улучшить качество поиска с минимальным влиянием на производительность.

Настройка памяти и производительности#

Существует три возможных режима размещения хранения векторов внутри коллекции Vector DB:

  • Все в оперативной памяти;

  • Оригинал на диске, квантованные в оперативной памяти;

  • Все на диске.

Режим «Все в оперативной памяти»#

Все векторы, как оригиналы, так и квантованные, загружаются и сохраняются в оперативной памяти. Это самый быстрый режим, но требует большого количества оперативной памяти. Включен по умолчанию.

Режим «Оригинал на диске, квантованные в оперативной памяти»#

Гибридный режим, позволяющий получить хорошее соотношение между быстротой и объемом используемой памяти. Рекомендован, если цель — уменьшение объема памяти при сохранении высокой скорости поиска.

Этот режим активируется путем установки always_ram в значение true в конфигурации квантования при использовании хранилища memmap:

PUT /collections/{collection_name}
{
    "vectors": {
        "size": 768,
        "distance": "Cosine",
        "on_disk": true
    },
    "quantization_config": {
        "scalar": {
            "type": "int8",
            "always_ram": true
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE, on_disk=True),
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8,
            always_ram=True,
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
    on_disk: true,
  },
  quantization_config: {
    scalar: {
      type: "int8",
      always_ram: true,
    },
  },
});
use qdrant_client::qdrant::{
    CreateCollectionBuilder, Distance, QuantizationType, ScalarQuantizationBuilder,
    VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine))
            .quantization_config(
                ScalarQuantizationBuilder::default()
                    .r#type(QuantizationType::Int8.into())
                    .always_ram(true),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.OptimizersConfigDiff;
import io.qdrant.client.grpc.Collections.QuantizationConfig;
import io.qdrant.client.grpc.Collections.QuantizationType;
import io.qdrant.client.grpc.Collections.ScalarQuantization;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .setOnDisk(true)
                            .build())
                    .build())
            .setQuantizationConfig(
                QuantizationConfig.newBuilder()
                    .setScalar(
                        ScalarQuantization.newBuilder()
                            .setType(QuantizationType.Int8)
                            .setAlwaysRam(true)
                            .build())
                    .build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
  collectionName: "{collection_name}",
  vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine, OnDisk = true },
  quantizationConfig: new QuantizationConfig
  {
      Scalar = new ScalarQuantization { Type = QuantizationType.Int8, AlwaysRam = true }
  }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
      OnDisk:   qdrant.PtrOf(true),
  }),
  QuantizationConfig: qdrant.NewQuantizationScalar(&qdrant.ScalarQuantization{
      Type:      qdrant.QuantizationType_Int8,
      AlwaysRam: qdrant.PtrOf(true),
  }),
})

В данной ситуации количество операций чтения с диска может существенно влиять на скорость поиска. В системе с высокой задержкой доступа к диску этап повторного ранжирования может стать узким местом.

Рассмотрите возможность отключения rescore, чтобы повысить скорость поиска:

POST /collections/{collection_name}/points/query
{
    "query": [0.2, 0.1, 0.9, 0.7],
    "params": {
        "quantization": {
            "rescore": false
        }
    },
    "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.query_points(
    collection_name="{collection_name}",
    query=[0.2, 0.1, 0.9, 0.7],
    search_params=models.SearchParams(
        quantization=models.QuantizationSearchParams(rescore=False)
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: [0.2, 0.1, 0.9, 0.7],
    params: {
        quantization: {
            rescore: false,
        },
    },
});
use qdrant_client::qdrant::{
    QuantizationSearchParamsBuilder, QueryPointsBuilder, SearchParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(vec![0.2, 0.1, 0.9, 0.7])
            .limit(3)
            .params(
                SearchParamsBuilder::default()
                    .quantization(QuantizationSearchParamsBuilder::default().rescore(false)),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.QuantizationSearchParams;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.SearchParams;

import static io.qdrant.client.QueryFactory.nearest;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(
        QueryPoints.newBuilder()
                .setCollectionName("{collection_name}")
                .setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
                .setParams(
                        SearchParams.newBuilder()
                                .setQuantization(
                                        QuantizationSearchParams.newBuilder().setRescore(false).build())
                                .build())
                .setLimit(3)
                .build())
        .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
  collectionName: "{collection_name}",
  query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
  searchParams: new SearchParams
  {
      Quantization = new QuantizationSearchParams { Rescore = false }
  },
  limit: 3
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
  CollectionName: "{collection_name}",
  Query:          qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
  Params: &qdrant.SearchParams{
      Quantization: &qdrant.QuantizationSearchParams{
          Rescore: qdrant.PtrOf(false),
      },
  },
})

Режим «Все на диске»#

Все векторы, исходные и квантованные, хранятся на диске. Этот режим позволяет добиться минимального объема использования памяти, но за счет снижения скорости поиска.

Рекомендуется использовать этот режим, если имеется большая коллекция данных и быстрое хранилище (например, SSD или NVMe).

Данный режим включается установкой параметра always_ram равным false в конфигурации квантования при использовании хранилища mmap:

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine",
      "on_disk": true
    },
    "quantization_config": {
        "scalar": {
            "type": "int8",
            "always_ram": false
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE, on_disk=True),
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8,
            always_ram=False,
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
    on_disk: true,
  },
  quantization_config: {
    scalar: {
      type: "int8",
      always_ram: false,
    },
  },
});
use qdrant_client::qdrant::{
    CreateCollectionBuilder, Distance, QuantizationType, ScalarQuantizationBuilder,
    VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine).on_disk(true))
            .quantization_config(
                ScalarQuantizationBuilder::default()
                    .r#type(QuantizationType::Int8.into())
                    .always_ram(false),
            ),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.OptimizersConfigDiff;
import io.qdrant.client.grpc.Collections.QuantizationConfig;
import io.qdrant.client.grpc.Collections.QuantizationType;
import io.qdrant.client.grpc.Collections.ScalarQuantization;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .setOnDisk(true)
                            .build())
                    .build())
            .setQuantizationConfig(
                QuantizationConfig.newBuilder()
                    .setScalar(
                        ScalarQuantization.newBuilder()
                            .setType(QuantizationType.Int8)
                            .setAlwaysRam(false)
                            .build())
                    .build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
 collectionName: "{collection_name}",
 vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine, OnDisk = true},
 quantizationConfig: new QuantizationConfig
 {
  Scalar = new ScalarQuantization { Type = QuantizationType.Int8, AlwaysRam = false }
 }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
      OnDisk:   qdrant.PtrOf(true),
  }),
  QuantizationConfig: qdrant.NewQuantizationScalar(
      &qdrant.ScalarQuantization{
          Type:      qdrant.QuantizationType_Int8,
          AlwaysRam: qdrant.PtrOf(false),
      },
  ),
})