向量量化是一种数据压缩技术,用于减小高维数据的大小。压缩向量可以减少内存使用,同时保留几乎所有基本信息。这种方法可以实现更高效的存储和更快的搜索操作,尤其是在大型数据集中。
在使用高维向量时,例如来自OpenAI等提供商的嵌入,单个1536维向量需要6 KB的内存。

100万个向量需要大约6 GB的内存,随着数据集增长到数百万个向量,内存和处理需求会显著增加。
为了理解为什么这个过程对计算要求如此之高,让我们来看看HNSW索引的本质。
HNSW(分层可导航小世界)索引以分层图的形式组织向量,将每个向量与其最近的邻居连接起来。在每一层,算法都会缩小搜索区域,直到到达较低层,从而有效地找到与查询最匹配的结果。

每次添加新向量时,系统都必须确定其在现有图中的位置,这个过程类似于搜索。这使得向量的插入和搜索都成为复杂的操作。
HNSW索引的关键挑战之一是它需要大量的随机读取和通过图的顺序遍历。这使得该过程的计算成本很高,尤其是在处理数百万个高维向量时。
系统必须以不可预测的方式在图的各个点之间跳转。这种不可预测性使得优化变得困难,并且随着数据集的增长,内存和处理需求会显著增加。

由于向量需要存储在RAM或SSD等快速存储中以实现低延迟搜索,随着数据大小的增长,高效存储和处理数据的成本也会随之增加。
量化通过将向量压缩到更小的内存大小来提供解决方案,从而使过程更高效。
有几种方法可以实现这一点,这里我们将重点介绍三种主要方法

1. 什么是标量量化?

在Qdrant中,每个维度由一个float32值表示,它使用4字节的内存。当使用标量量化时,我们将向量映射到较小的int8类型可以表示的范围。int8只有1字节,可以表示256个值(从-128到127,或0到255)。这导致内存大小减少75%。
例如,如果我们的数据范围是-1.0到1.0,标量量化将这些值转换为int8可以表示的范围,即在-128到127之间。系统将float32值映射到此范围。
这是一个关于这个过程的简单线性示例

要在Qdrant中设置标量量化,您需要在创建或更新集合时包含quantization_config部分
PUT /collections/{collection_name}
{
"vectors": {
"size": 128,
"distance": "Cosine"
},
"quantization_config": {
"scalar": {
"type": "int8",
"quantile": 0.99,
"always_ram": true
}
}
}
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=128, distance=models.Distance.COSINE),
quantization_config=models.ScalarQuantization(
scalar=models.ScalarQuantizationConfig(
type=models.ScalarType.INT8,
quantile=0.99,
always_ram=True,
),
),
)
quantile参数用于计算量化边界。例如,如果您指定0.99分位数,则最极端的1%的值将从量化边界中排除。
此参数仅影响结果精度,而不影响内存占用。如果搜索质量显著下降,您可以调整它。
如果您希望在不损失太多精度的情况下提高搜索速度和压缩率,标量量化是一个不错的选择。它还可以略微提高性能,因为使用int8值进行距离计算(例如点积或余弦相似度)在计算上比使用float32值更简单。
虽然标量量化的性能提升可能无法与二值量化(我们稍后讨论)所实现的性能提升相媲美,但当二值量化不适合您的用例时,它仍然是一个出色的默认选择。
2. 什么是二值量化?

二值量化是您希望减少内存使用量并显著提高速度的绝佳选择。它通过将高维向量转换为简单的二值(0或1)表示来工作。
- 大于零的值转换为1。
- 小于或等于零的值转换为0。
让我们考虑我们最初的示例,一个1536维向量需要6 KB内存(每个float32值4字节)。
二值量化后,每个维度减少到1位(1/8字节),因此所需的内存为
$$ \frac{1536 \text{ 维度}}{8 \text{ 位/字节}} = 192 \text{ 字节} $$
这导致内存减少32倍。

Qdrant在索引过程中自动化二值量化过程。当向量添加到您的集合中时,每个32位浮点分量都根据您定义的配置转换为二值。
您可以这样设置它
PUT /collections/{collection_name}
{
"vectors": {
"size": 1536,
"distance": "Cosine"
},
"quantization_config": {
"binary": {
"always_ram": true
}
}
}
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,
),
),
)
与标量和乘积量化相比,二值量化是迄今为止提供最显著处理速度提升的量化方法。这是因为二值表示允许系统使用高度优化的CPU指令,例如XOR和Popcount,用于快速距离计算。
根据数据集和硬件,它可以将搜索操作加速高达40倍。
并非所有模型都与二值量化同样兼容,在上面的比较中,我们只使用兼容的模型。某些模型在量化时可能会经历更大的精度损失。我们建议将二值量化与至少1024维的模型一起使用,以最大程度地减少精度损失。
与此方法显示出最佳兼容性的模型包括
- OpenAI text-embedding-ada-002 (1536 维)
- Cohere AI embed-english-v2.0 (4096 维)
这些模型在受益于显著的速度和内存增益的同时,表现出最小的精度损失。
尽管二值量化速度快且内存效率高,但权衡在于精度和模型兼容性,因此您可能需要使用过采样和重新评分等技术来确保搜索质量。
如果您有兴趣更详细地探索二值量化——包括实现示例、基准测试结果和使用建议——请查看我们关于二值量化 - 向量搜索,加速40倍的专门文章。
3. 什么是乘积量化?

乘积量化是一种通过使用较小的代表点集来表示高维向量以压缩它们的方法。
该过程首先将原始高维向量分成较小的子向量。每个子向量代表原始向量的一个片段,捕获数据的不同特征。

对于每个子向量,都会创建一个单独的码本,表示数据空间中常见模式出现的区域。
Qdrant中的码本在索引过程中自动训练。当向量添加到集合中时,Qdrant使用您在quantization_config中指定的量化设置来构建码本并量化向量。您可以这样设置它
PUT /collections/{collection_name}
{
"vectors": {
"size": 1024,
"distance": "Cosine"
},
"quantization_config": {
"product": {
"compression": "x32",
"always_ram": true
}
}
}
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE),
quantization_config=models.ProductQuantization(
product=models.ProductQuantizationConfig(
compression=models.CompressionRatio.X32,
always_ram=True,
),
),
)
码本中的每个区域都由一个质心定义,该质心作为代表点,总结该区域的特征。我们不将每个数据点都视为同等重要,而是将相似的子向量分组在一起,并用一个捕获该组一般特征的质心来表示它们。
乘积量化中使用的质心是使用K-均值聚类算法确定的。

Qdrant在其实现中始终选择K = 256作为质心数量,因为256是单个字节可以表示的最大唯一值数量。
这使得压缩过程高效,因为每个质心索引都可以存储在一个字节中。
通过将每个子向量映射到其各自码本中最近的质心来量化原始高维向量。

压缩向量存储每个子向量最近质心的索引。
这是一个1024维向量的示例,最初占用4096字节,通过将其表示为128个索引(每个索引指向一个子向量的质心)而减少到仅128字节

设置量化并添加向量后,您可以像往常一样执行搜索。Qdrant将自动使用量化向量,优化速度和内存使用。您可以选择启用重新评分以获得更好的准确性。
POST /collections/{collection_name}/points/search
{
"query": [0.22, -0.01, -0.98, 0.37],
"params": {
"quantization": {
"rescore": true
}
},
"limit": 10
}
client.query_points(
collection_name="my_collection",
query_vector=[0.22, -0.01, -0.98, 0.37], # Your query vector
search_params=models.SearchParams(
quantization=models.QuantizationSearchParams(
rescore=True # Enables rescoring with original vectors
)
),
limit=10 # Return the top 10 results
)
乘积量化可以显著减少内存使用,在某些配置下可能提供高达64倍的压缩。但是,值得注意的是,这种级别的压缩可能会导致质量显著下降。
如果您的应用程序需要高精度或实时性能,乘积量化可能不是最佳选择。但是,如果节省内存至关重要且可以接受一些精度损失,它仍然可能是一个理想的解决方案。
以下是所有三种方法的搜索速度、准确性和压缩率的比较,改编自Qdrant文档
| 量化方法 | 精度 | 速度 | 压缩率 |
|---|---|---|---|
| 标量 | 0.99 | 高达2倍 | 4 |
| 乘积 | 0.7 | 0.5 | 高达64倍 |
| 二值 | 0.95* | 高达40倍 | 32 |
* - 对于兼容模型
要更深入地了解您可以预期的基准测试,请查看我们关于向量搜索中的乘积量化的专门文章。
重新评分、过采样和重新排序
当我们使用标量、二值或乘积量化等量化方法时,我们正在压缩向量以节省内存并提高性能。但是,这种压缩会从原始向量中删除一些细节。
这可能会略微降低我们相似性搜索的准确性,因为量化向量是原始数据的近似值。为了减轻这种准确性损失,您可以使用过采样和重新评分,它们有助于提高最终搜索结果的准确性。
在此过程中,原始向量永远不会被删除,您可以随时通过更新集合配置在量化方法或参数之间轻松切换。
以下是该过程的工作原理,一步一步
1. 初始量化搜索
当您执行搜索时,Qdrant会根据量化数据确定的量化向量与查询向量的相似性,检索排名靠前的候选对象。此步骤很快,因为我们正在使用量化向量。

2. 过采样
过采样是一种有助于弥补由于量化而导致的任何精度损失的技术。由于量化简化了向量,因此在初始搜索中可能会错过一些相关匹配。为了避免这种情况,您可以检索更多候选对象,增加最相关的向量进入最终结果的机会。
您可以通过设置oversampling参数来控制额外候选对象的数量。例如,如果您希望的结果数量(limit)为4,并且您将oversampling因子设置为2,则Qdrant将检索8个候选对象(4 × 2)。

您可以调整过采样因子来控制Qdrant在初始池中包含的额外向量数量。更多的候选对象意味着获得高质量Top-K结果的机会更大,尤其是在使用原始向量重新评分后。
3. 使用原始向量重新评分
在过采样以收集更多潜在匹配后,每个候选对象都会根据额外的标准进行重新评估,以确保更高的准确性和与查询的相关性。
重新评分过程将量化向量映射到其相应的原始向量,允许您考虑初始搜索中未包含的上下文、元数据或其他相关性等因素,从而获得更准确的结果。

在重新评分过程中,过采样中排名较低的候选对象之一可能比一些原始Top-K候选对象更好地匹配。
即使重新评分使用原始的、更大的向量,该过程仍然快得多,因为只读取了非常少量的向量。初始量化搜索已经识别出要读取、重新评分和重新排序的特定向量。
4. 重新排序
根据重新评分后的新相似性分数,重新排序是根据更新后的相似性分数确定最终Top-K候选对象的地方。
例如,在我们限制为4的情况下,在初始量化搜索中排名第6的候选对象在重新评分后可能会提高其分数,因为原始向量捕获了更多的上下文或元数据。结果,该候选对象在重新排序后可能会进入最终Top 4,取代初始搜索中不太相关的选项。

您可以这样设置它
POST /collections/{collection_name}/points/search
{
"query": [0.22, -0.01, -0.98, 0.37],
"params": {
"quantization": {
"rescore": true,
"oversampling": 2
}
},
"limit": 4
}
client.query_points(
collection_name="my_collection",
query_vector=[0.22, -0.01, -0.98, 0.37],
search_params=models.SearchParams(
quantization=models.QuantizationSearchParams(
rescore=True, # Enables rescoring with original vectors
oversampling=2 # Retrieves extra candidates for rescoring
)
),
limit=4 # Desired number of final results
)
您可以调整oversampling因子,以在搜索速度和结果准确性之间找到适当的平衡。
如果量化正在影响需要高精度的应用程序的性能,则将过采样与重新评分结合是一个不错的选择。但是,如果您需要更快的搜索并且可以容忍一些准确性损失,则可以选择在不重新评分的情况下使用过采样,或者将过采样因子调整为较低的值。
在磁盘和内存之间分配资源
Qdrant存储量化向量和原始向量。默认情况下,当您启用量化时,原始向量和量化向量都存储在RAM中。您可以将原始向量移动到磁盘以显著减少RAM使用并降低系统成本。仅仅启用量化是不够的——您需要通过设置on_disk=True明确地将原始向量移动到磁盘。
以下是一个示例配置
PUT /collections/{collection_name}
{
"vectors": {
"size": 1536,
"distance": "Cosine",
"on_disk": true # Move original vectors to disk
},
"quantization_config": {
"binary": {
"always_ram": true # Store only quantized vectors in RAM
}
}
}
client.update_collection(
collection_name="my_collection",
vectors_config=models.VectorParams(
size=1536,
distance=models.Distance.COSINE,
on_disk=True # Move original vectors to disk
),
quantization_config=models.BinaryQuantization(
binary=models.BinaryQuantizationConfig(
always_ram=True # Store only quantized vectors in RAM
)
)
)
如果不明确设置on_disk=True,即使启用了量化,您也不会看到任何RAM节省。因此,请务必根据您的内存和性能需求配置存储和量化选项。如果您的存储具有高磁盘延迟,请考虑禁用重新评分以保持速度。
使用io_uring加速重新评分
在处理大量量化向量集合时,需要频繁进行磁盘读取以检索原始数据和压缩数据以进行重新评分操作。尽管mmap通过减少用户到内核的转换有助于高效I/O,但在磁盘上处理大型数据集时,由于需要频繁进行磁盘读取,重新评分仍然会变慢。
在基于Linux的系统中,io_uring允许并行处理多个磁盘操作,从而显著降低I/O开销。这种优化在重新评分期间特别有效,因为在初始搜索之后需要重新评估多个向量。通过io_uring,Qdrant可以以最有效的方式从磁盘检索和重新评分向量,从而提高整体搜索性能。
当您执行向量量化并将数据存储在磁盘上时,Qdrant通常需要并行访问多个向量。如果没有io_uring,由于系统在处理许多磁盘访问方面的限制,这个过程可能会变慢。
要在Qdrant中启用io_uring,请在存储配置中添加以下内容
storage:
async_scorer: true # Enable io_uring for async storage
如果没有此配置,Qdrant将默认使用mmap进行磁盘I/O操作。
有关io_uring与mmap等传统I/O方法进行比较的更多信息和基准测试,请查看Qdrant的io_uring实现文章。
量化数据与非量化数据的性能
如果可用,Qdrant默认使用量化向量。如果您想评估量化如何影响搜索结果,您可以暂时禁用它以比较量化搜索和非量化搜索的结果。为此,请在查询中设置ignore: true
POST /collections/{collection_name}/points/query
{
"query": [0.22, -0.01, -0.98, 0.37],
"params": {
"quantization": {
"ignore": true,
}
},
"limit": 4
}
client.query_points(
collection_name="{collection_name}",
query=[0.22, -0.01, -0.98, 0.37],
search_params=models.SearchParams(
quantization=models.QuantizationSearchParams(
ignore=True
)
),
)
在量化方法之间切换
不确定您是否选择了正确的量化方法?在Qdrant中,您可以灵活地移除量化并仅依赖原始向量,调整量化类型,或随时更改压缩参数,而不会影响原始向量。
例如,要切换到二值量化并调整压缩率,您可以使用update_collection方法更新集合的量化配置
PUT /collections/{collection_name}
{
"vectors": {
"size": 1536,
"distance": "Cosine"
},
"quantization_config": {
"binary": {
"always_ram": true,
"compression_rate": 0.8 # Set the new compression rate
}
}
}
client.update_collection(
collection_name="my_collection",
quantization_config=models.BinaryQuantization(
binary=models.BinaryQuantizationConfig(
always_ram=True, # Store only quantized vectors in RAM
compression_rate=0.8 # Set the new compression rate
)
),
)
如果您决定关闭量化并仅使用原始向量,您可以使用quantization_config=None完全移除量化设置
PUT /collections/my_collection
{
"vectors": {
"size": 1536,
"distance": "Cosine"
},
"quantization_config": null # Remove quantization and use original vectors only
}
client.update_collection(
collection_name="my_collection",
quantization_config=None # Remove quantization and rely on original vectors only
)
总结

标量、乘积和二值量化等量化方法提供了强大的方式来优化内存使用并提高处理大量高维向量数据集时的搜索性能。每种方法在内存节省、计算速度和准确性之间都有其自身的权衡。
以下是一些最终建议,可帮助您选择适合您需求的量化方法
| 量化方法 | 主要特点 | 何时使用 |
|---|---|---|
| 二值量化 | • 最快的方法和最节省内存 • 搜索速度提高高达40倍,内存占用减少32倍 | • 与OpenAI的text-embedding-ada-002和Cohere的embed-english-v2.0等经过测试的模型一起使用• 当速度和内存效率至关重要时 |
| 标量量化 | • 最小的精度损失 • 内存占用减少高达4倍 | • 大多数应用程序的安全默认选择 • 在准确性、速度和压缩率之间取得良好平衡 |
| 乘积量化 | • 最高的压缩比 • 内存占用减少高达64倍 | • 当最小化内存使用是首要任务时 • 如果可以容忍一些精度损失和较慢的索引速度,则可以接受 |
了解更多
如果您想了解更多关于在使用Qdrant进行量化时提高准确性、内存效率和速度的信息,我们的文档中有专门的量化技巧部分,解释了所有可以用来增强结果的量化技巧。
通过观看Qdrant首席技术官Andrey Vasnetsov的采访,了解更多关于通过二值量化中的过采样优化实时精度的信息
及时了解向量搜索和量化的最新信息,分享您的项目,提问,加入我们的向量搜索社区!
