• 文章
  • What is Vector Quantization?
返回到向量搜索手册

什么是向量量化?

萨布rina 阿基诺

·

2024年9月25日

What is Vector Quantization?

矢量量化是一种数据压缩技术,用于减少高维数据的大小。压缩向量降低了内存使用,同时保持几乎所有的基本信息。这种方法允许更高效的存储和更快的搜索操作,特别是在大型数据集中。

当处理高维向量时,例如来自OpenAI等提供者的嵌入,一个1536维的向量需要6 KB的内存

1536-dimensional vector size is 6 KB

对于需要大约 6 GB 内存的 100 万个向量,当您的数据集增长到多个 百万个向量 时,内存和处理需求显著增加。

为了理解为什么这个过程如此计算密集,我们来看看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。

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

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

$$ \frac{1536 \text{ dimensions}}{8 \text{ bits per byte}} = 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指令,如XOR人口计数,以进行快速距离计算。

根据数据集和硬件,它可以加快搜索操作,速度最快可达40倍

并非所有模型都与二进制量化同样兼容,在上述比较中,我们只使用兼容的模型。某些模型在量化时可能会经历更大的准确性损失。我们建议使用具有至少1024维的模型进行二进制量化,以最小化准确性损失。

与此方法表现出最佳兼容性的模型包括:

  • OpenAI 文本嵌入 ada-002 (1536 维度)
  • Cohere AI embed-english-v2.0 (4096 维度)

这些模型在仍然获得可观的速度和内存优势的同时,表现出最小的准确性损失。

尽管二进制量化非常快捷且内存高效,但其权衡在于精度模型兼容性,因此您可能需要使用如过采样和重新评分等技术来确保搜索质量。

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

3. 什么是产品量化?

产品量化 是一种通过用一组更小的代表性点来表示高维向量的方法,以进行压缩。

该过程通过将原始高维向量分割成更小的 子向量。 每个子向量代表原始向量的一个部分,捕捉数据的不同特征。

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

代码本中每个区域由一个中心点定义,它作为一个代表性点,总结该区域的特征。我们可以将相似的子向量分组在一起,用一个单一的中心点来表示它们,从而捕捉该组的一般特征,而不是将每个数据点视为同等重要。

在产品量化中使用的质心是通过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最高可达 x24
产品0.70.5最多 64
二进制0.95*最多 x4032

* - 适用于兼容的型号

要深入了解您可以期待的基准测试,请查看我们关于 向量搜索中的产品量化 的专门文章。

重新评分、过采样和重新排序

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

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

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

以下是该过程的工作方式,逐步说明:

当您执行搜索时,Qdrant 通过量化向量根据它们与查询向量的相似性检索顶级候选者,具体由量化数据决定。这个步骤很快,因为我们使用了量化向量。

ANN Search with Quantization

2. 过采样

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

您可以通过设置 oversampling 参数来控制额外候选人的数量。例如,如果您所需的结果数量 (limit) 是 4 且您设置的 oversampling 因子为 2,则 Qdrant 将检索 8 个候选人(4 × 2)。

ANN Search with Quantization and Oversampling

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

3. 使用原始向量重新评分

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

重新评分过程映射量化向量到其对应的原始向量,使您能够考虑上下文、元数据或初始搜索中未包含的额外相关性等因素,从而得到更精确的结果。

Rescoring with Original Vectors

在重新评分时,来自过采样的较低排名候选者可能会比一些原始的前K候选者更匹配。

尽管重新评分使用原始的大型向量,但由于只读取非常少量的向量,因此过程仍然快得多。初始量化搜索已经识别出要读取、重新评分和重新排序的特定向量。

4. 重新排序

通过重新评分获得的新相似性分数,重新排序 是根据更新的相似性分数确定最终的前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,你将不会看到任何内存节省,即使启用了量化。因此,请确保根据你的内存和性能需求配置存储和量化选项。如果你的存储具有高磁盘延迟,请考虑禁用重新评分以保持速度。

使用 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 与传统 I/O 方法如 mmap,请查看 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上,或者 create 一个 GitHub 问题。