• 文章
  • 什么是向量量化?
返回向量搜索手册

什么是向量量化?

Sabrina Aquino

·

2024年9月25日

What is Vector Quantization?

向量量化是一种数据压缩技术,用于减小高维数据的大小。压缩向量可以在保持几乎所有必要信息的同时减少内存使用。这种方法使得大规模数据集的存储和搜索操作更加高效。

在使用高维向量(例如来自 OpenAI 等提供商的嵌入向量)时,单个 1536 维的向量需要 6 KB 的内存

1536-dimensional vector size is 6 KB

当拥有 100 万个向量时,大约需要 6 GB 的内存。随着数据集增长到数百万个向量,内存和处理需求会显著增加。

为了理解为什么这个过程计算量如此巨大,让我们看看 HNSW 索引的本质。

HNSW(分层可导航小世界)索引将向量组织成一个分层图,将每个向量连接到其最近的邻居。在每一层,算法都会缩小搜索范围,直到到达较低层,从而有效地找到与查询最相似的结果。

HNSW Search visualization

每当添加新向量时,系统都必须确定其在现有图中的位置,这个过程类似于搜索。这使得向量的插入和搜索都变成了复杂的操作。

HNSW 索引的主要挑战之一是它需要大量的随机读取顺序遍历图。这使得该过程计算成本很高,尤其是在处理数百万个高维向量时。

系统必须以不可预测的方式在图中的各个点之间跳转。这种不可预测性使得优化变得困难,并且随着数据集的增长,内存和处理需求会显著增加。

HNSW Search visualization

由于向量需要存储在高速存储器(如 RAMSSD)中以实现低延迟搜索,随着数据量的增加,高效存储和处理数据的成本也随之增加。

量化通过将向量压缩为较小的内存大小来提供解决方案,从而使过程更加高效。

实现这一目标有多种方法,这里我们将重点介绍三种主要方法

Types of Quantization: 1. Scalar Quantization, 2. Product Quantization, 3. Binary Quantization

1. 什么是标量量化?

在 Qdrant 中,每个维度由一个 float32 值表示,占用 4 字节内存。使用 标量量化 时,我们将向量映射到较小的 int8 类型可以表示的范围内。int8 仅占 1 字节,可以表示 256 个值(从 -128 到 127,或 0 到 255)。这使得内存大小减少了 75%

例如,如果我们的数据在 -1.0 到 1.0 的范围内,标量量化会将这些值转换为 int8 可以表示的范围,即 -128 到 127 之间。系统将 float32映射到该范围内。

这是一个关于该过程外观的简单线性示例

Scalar Quantization example

要在 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. 什么是二进制量化?

Astronaut in surreal white environment

二进制量化是您希望减少内存使用同时获得显著速度提升时的绝佳选择。它通过将高维向量转换为简单的二进制(0 或 1)表示来工作。

  • 大于零的值转换为 1。
  • 小于或等于零的值转换为 0。

让我们考虑最初 1536 维向量的示例,它需要 6 KB 的内存(每个 float32 值占 4 字节)。

经过二进制量化后,每个维度减少到 1 位(1/8 字节),因此所需的内存为

$$ \frac{1536 \text{ 维度}}{8 \text{ 位/字节}} = 192 \text{ 字节} $$

这导致内存减少了 32 倍

Binary Quantization example

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 指令(例如 XORPopcount)来进行快速距离计算。

根据数据集和硬件的不同,它可以将搜索操作的速度提高高达 40 倍

并非所有模型都同样兼容二进制量化,在上面的比较中,我们仅使用了兼容的模型。某些模型在量化时可能会经历更大的精度损失。我们建议将二进制量化与至少 1024 维的模型结合使用,以最大限度地减少精度损失。

显示与该方法最佳兼容性的模型包括

  • OpenAI text-embedding-ada-002 (1536 维)
  • Cohere AI embed-english-v2.0 (4096 维)

这些模型展示了极小的精度损失,同时仍然受益于显著的速度和内存提升。

尽管二进制量化速度极快且内存效率高,但其权衡在于精度模型兼容性,因此您可能需要使用过采样(oversampling)和重打分(rescoring)等技术来确保搜索质量。

如果您有兴趣更详细地探索二进制量化——包括实现示例、基准测试结果和使用建议——请查阅我们关于 二进制量化 - 向量搜索,速度提升 40 倍 的专题文章。

3. 什么是乘积量化?

乘积量化 (Product Quantization) 是一种通过用一组较小的代表点来表示高维向量从而压缩它们的方法。

该过程首先将原始高维向量拆分为较小的子向量。每个子向量代表原始向量的一个片段,捕获数据的不同特征。

Creation of the Sub-vector

对于每个子向量,都会创建一个单独的码本 (codebook),表示数据空间中常见模式发生的区域。

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,
        ),
    ),
)

码本中的每个区域都由一个质心 (centroid) 定义,它作为概括该区域特征的代表点。我们无需将每个数据点视为同等重要,而是可以将相似的子向量分组在一起,并用一个捕获该组一般特征的单一质心来表示它们。

乘积量化中使用的质心是使用 K-means 聚类算法 确定的。

Codebook and Centroids example

在 Qdrant 的实现中,始终选择 K = 256 作为质心数量,基于 256 是单个字节可以表示的唯一值的最大数量这一事实。

这使得压缩过程变得高效,因为每个质心索引都可以存储在一个字节中。

原始高维向量通过将每个子向量映射到其各自码本中最接近的质心来进行量化。

Vectors being mapped to their corresponding centroids example

压缩向量存储每个子向量最接近的质心的索引。

以下是一个 1024 维向量(最初占用 4096 字节)如何通过将其表示为 128 个索引(每个指向子向量的质心)而减少到仅 128 字节的过程

Product Quantization example

设置好量化并添加向量后,您可以照常进行搜索。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.70.5最高 64 倍
二进制0.95*最高 40 倍32

* - 针对兼容模型

如需更深入了解您可以预期的基准测试,请查阅我们关于 向量搜索中的乘积量化 的专题文章。

重打分、过采样和重排序

当我们使用标量、二进制或乘积量化等量化方法时,我们是在压缩向量以节省内存并提高性能。然而,这种压缩会删除原始向量中的一些细节。

这可能会略微降低相似度搜索的精度,因为量化向量是原始数据的近似值。为了减轻这种精度损失,您可以使用过采样 (oversampling)重打分 (rescoring),这有助于提高最终搜索结果的准确性。

在此过程中,原始向量永远不会被删除,您可以随时通过更新集合配置轻松地在不同量化方法或参数之间切换。

以下是该过程的逐步说明

执行搜索时,Qdrant 会根据量化数据确定的相似度,使用量化向量检索前几名候选者。由于我们使用的是量化向量,这一步很快。

ANN Search with Quantization

2. 过采样

过采样是一种有助于弥补因量化而丢失的精度的技术。由于量化简化了向量,初始搜索可能会错过一些相关的匹配项。为了避免这种情况,您可以检索更多的候选者,增加最相关的向量进入最终结果的机会。

您可以通过设置 oversampling 参数来控制额外候选者的数量。例如,如果您想要的搜索结果数量 (limit) 为 4,并且设置了 2 倍的 oversampling 因子,Qdrant 将检索 8 个候选者(4 × 2)。

ANN Search with Quantization and Oversampling

您可以调整过采样因子来控制 Qdrant 在初始池中包含多少额外向量。更多的候选者意味着有更好的机会获得高质量的 Top-K 结果,特别是在使用原始向量进行重打分之后。

3. 使用原始向量重打分

在通过过采样收集更多潜在匹配项后,会根据额外标准对每个候选者进行重新评估,以确保更高的准确性和与查询的相关性。

重打分过程将量化向量映射回其对应的原始向量,允许您考虑初始搜索中未包含的内容、元数据或其他相关性因素,从而得出更准确的结果。

Rescoring with Original Vectors

在重打分期间,过采样中排名较低的候选者之一可能最终比原始 Top-K 候选者中的某些人匹配得更好。

尽管重打分使用了更大的原始向量,但该过程仍然快得多,因为读取的向量数量非常少。初始量化搜索已经确定了需要读取、重打分和重排序的具体向量。

4. 重排序

利用重打分后的新相似度分数,重排序 (reranking) 是最终 Top-K 候选者根据更新后的相似度分数确定下来的过程。

例如,在我们限制为 4 的情况下,在初始量化搜索中排名第 6 的候选者在重打分后可能会提高其分数,因为原始向量捕获了更多的内容或元数据。因此,该候选者可能会在重排序后进入最终的前 4 名,取代初始搜索中相关性较低的选项。

Reranking with Original Vectors

以下是设置方法

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 的采访,了解更多关于通过过采样优化二进制量化中的实时精度的信息

了解 向量搜索 和量化的最新动态,分享您的项目,提出问题,加入我们的向量搜索社区

此页面有用吗?

感谢您的反馈!🙏

听到这个消息我们很难过。😔 您可以在 GitHub 上编辑此页面,或创建一个 GitHub 问题。