通过量化、窄数据类型和存储选项来减小矢量大小

本文介绍如何使用矢量量化和其他技术来减少 Azure AI 搜索中的矢量大小。 搜索索引指定矢量字段定义,包括存储和窄数据类型的属性。 在索引中还指定了量化,并通过其向量配置文件分配给矢量字段。

这些功能在 2024-07-01 REST API 和面向该版本的 Azure SDK 包中正式发布。 本文末尾的示例展示了本文所介绍的每种方法的矢量大小变化。

评估选项

第一步,检查用于减少向量字段使用的存储量的三种方法。 这些方法不是相互排斥的,可以组合来实现矢量大小的最大减少

我们建议使用内置量化,因为它会尽量压缩内存和磁盘中的矢量大小,并且往往在大多数情况下提供最大的好处。 相比之下,窄类型(float16 除外)需要特意创建,stored 可以节省磁盘上的存储空间,而存储空间不像内存那么昂贵。

方法 为什么使用此选项
添加标量或二进制量化 使用量化将本机 float32 或 float16 嵌入内容压缩到 int8(标量)或字节(二进制)。 此选项可以减少内存和磁盘上的存储,而不会降低查询性能。 Int8 或 Byte 等较小的数据类型生成的矢量索引,其内容的丰富程度不如较大嵌入的矢量索引。 为了弥补信息丢失,内置压缩包括使用未压缩嵌入和过采样进行查询后处理的选项,以返回更相关的结果。 重新排序和过采样是 float32 或 float16 字段内置量化的特定功能,不能用于经过自定义量化的嵌入。
将较小的基元数据类型分配给向量字段 窄数据类型(例如 float16、int16、int8 和 Byte [二进制])在内存和磁盘上消耗的空间更少,但必须具有嵌入模型,该模型以窄数据格式输出向量。 或者,必须有输出小数据的自定义量化逻辑。 需要较少工作量的第三个用例是大多数模型生成到 float16 的重新生成本机 float32 嵌入。 有关二进制矢量的详细信息,请参阅索引二进制矢量
消除可检索向量的可选存储 查询响应中返回的向量与查询执行期间使用的向量分开存储。 如果不需要返回向量,则可以关闭可检索存储,从而将每个字段的总体磁盘存储减少高达 50%。

所有这些选项都是在空索引上定义的。 若要实现其中任何一项,请使用 Azure 门户、REST API 或面向该 API 版本的 Azure SDK 包。

定义索引后,可以单独加载文档和索引。

选项 1:配置量化

建议使用量化来减少矢量大小,因为它降低了 float16 和 float32 嵌入的内存和磁盘存储要求。 若要抵消较小索引的影响,可以添加过度采样,并在未压缩的向量上重新调整。

量化适用于接收浮点类型向量矢量的向量字段。 在本文中的示例中,字段的数据类型对于传入 float32 嵌入为 Collection(Edm.Single),但也支持 float16。 在配置了压缩的字段中接收矢量时,引擎会自动执行量化以减少内存中和磁盘上的向量数据的占用空间。

支持两种类型的量化:

  • 标量量化将浮点值压缩为更窄的数据类型。 AI 搜索目前支持 int8,即 8 位,减少了向量索引大小四倍。

  • 二进制量化将浮点转换为二进制位,其占用 1 位。 这会导致矢量索引大小减少多达 28 倍。

若要使用内置量化,请执行以下步骤:

  • 使用创建索引创建或更新索引指定矢量压缩
  • vectorSearch.compressions 添加到搜索索引
  • 添加 scalarQuantizationbinaryQuantization 配置,并为其命名
  • 设置可选属性以减轻有损索引的影响
  • 创建使用命名配置的新矢量配置文件
  • 创建具有新向量配置文件的新向量字段
  • 使用使用定义的配置进行索引编制期间量化的 float32 或 float16 数据加载索引
  • (可选)如果要重写默认值,请使用过度采样参数查询量化数据

将“压缩”添加到搜索索引

以下示例显示了包含矢量字段的字段集合和 vectorSearch.compressions 节的部分索引定义。

此示例包括 scalarQuantizationbinaryQuantization。 可以根据需要指定任意数量的压缩配置,然后将所需配置分配给矢量配置文件。

POST https://[servicename].search.azure.cn/indexes?api-version=2024-07-01

{
  "name": "my-index",
  "fields": [
    { "name": "Id", "type": "Edm.String", "key": true, "retrievable": true, "searchable": true, "filterable": true },
    { "name": "content", "type": "Edm.String", "retrievable": true, "searchable": true },
    { "name": "vectorContent", "type": "Collection(Edm.Single)", "retrievable": false, "searchable": true },
  ],
  "vectorSearch": {
        "profiles": [ ],
        "algorithms": [ ],
        "compressions": [
          {
            "name": "use-scalar",
            "kind": "scalarQuantization",
            "scalarQuantizationParameters": {
              "quantizedDataType": "int8"
            },
            "rerankWithOriginalVectors": true,
            "defaultOversampling": 10
          },
          {
            "name": "use-binary",
            "kind": "binaryQuantization",
            "rerankWithOriginalVectors": true,
            "defaultOversampling": 10
          }
        ]
    }
}

要点

  • kind 必须设置为 scalarQuantizationbinaryQuantization

  • rerankWithOriginalVectors 使用原始的未压缩向量来重新计算相似性,并对初始搜索查询返回的顶部结果进行重新排名。 即使 stored 为 false,未压缩的向量也存在于搜索索引中。 此属性是可选的。 默认值为 true。

  • defaultOversampling 将考虑更广泛的潜在结果,以抵消量化导致的信息减少。 潜在结果的公式由查询中的 k 和过采样乘数组成。 例如,如果查询指定 k 为 5,并且过采样为 20,则查询实际上会请求 100 个文档用于重新排序,并为此使用原始未压缩向量。 仅返回前 k 个重新排序的结果。 此属性是可选的。 默认值为 4。

  • quantizedDataType 是可选的,仅适用于标量量化。 如果添加它,则必须将其设置为 int8。 这是目前唯一支持标量量化的基元数据类型。 默认值为 int8

添加 HNSW 算法

确保索引具有分层导航小型世界 (HNSW) 算法。 详尽的 KNN 不支持内置量化。

"vectorSearch": {
    "profiles": [ ],
    "algorithms": [
      {
          "name": "use-hnsw",
          "kind": "hnsw",
          "hnswParameters": {
              "m": 4,
              "efConstruction": 400,
              "efSearch": 500,
              "metric": "cosine"
          }
      }
    ],
     "compressions": [ <see previous section>] 
}

创建并分配新的矢量配置文件

若要使用新的量化配置,必须创建新的矢量配置文件。 要在内存中构建压缩索引,必须创建新的向量配置文件。 新配置文件使用 HNSW。

  1. 在同一索引定义中,创建新的矢量配置文件,并添加压缩属性和算法。 下面是两个配置文件,每种量化方法更一个。

    "vectorSearch": {
        "profiles": [
           {
              "name": "vector-profile-hnsw-scalar",
              "compression": "use-scalar", 
              "algorithm": "use-hnsw",
              "vectorizer": null
           },
           {
              "name": "vector-profile-hnsw-binary",
              "compression": "use-binary", 
              "algorithm": "use-hnsw",
              "vectorizer": null
           }
         ],
         "algorithms": [  <see previous section> ],
         "compressions": [ <see previous section> ] 
    }
    
  2. 将向量配置文件分配给的向量字段。 字段的数据类型为 float32 或 float16。

    在 Azure AI 搜索中,float32 和 float16 类型的实体数据模型 (EDM) 等效项分别 Collection(Edm.Single)Collection(Edm.Half)

    {
       "name": "vectorContent",
       "type": "Collection(Edm.Single)",
       "searchable": true,
       "retrievable": true,
       "dimensions": 1536,
       "vectorSearchProfile": "vector-profile-hnsw-scalar",
    }
    
  3. 使用用于拉取模型索引的索引器或用于推送模型索引的 API 加载索引

标量量化降低了每个向量嵌入中每个数字的分辨率。 它没有将每个数字描述为 16 位或 32 位浮点数,而是使用 8 位整数。 它标识一系列数字(通常是第 99 个百分位数的最小值和最大值),将它们划分为有限数量的级别或容器,并为每个容器分配一个标识符。 在 8 位标量量化中,有 2^8 或 256 个可能的 bin。

向量的每个分量都被映射到这组量化级别内最接近的代表值,其过程类似于将实数舍入到最接近的整数。 在量化的 8 位向量中,标识符号代替原始值。 量化后,每个向量都由其分量所属的 bin 的标识符数组表示。 与原始向量相比,这些量化向量需要存储的位数要少得多,从而减少所需存储和内存占用。

二进制量化通过将每个组件表示为一个位(0 或 1)来压缩高维向量。 此方法可大幅减少内存占用,并加速矢量比较操作,这对于搜索和检索任务至关重要。 基准测试的矢量索引大小最多可减少 96%。

这对于维度大于 1024 的嵌入尤其有效。 对于较小的维度,我们建议测试二进制量化的质量,或改为尝试标量。 此外,我们发现当嵌入集中在零附近时,BQ 表现得非常好。 最常见的嵌入模型,如 OpenAI、Cohere 和 Mistral 都是以零为中心的。

选项 2:将窄数据类型分配给向量字段

减少矢量大小的一种简单方法是以较小的数据格式存储嵌入内容。 大多数嵌入模型输出 32 位浮点数,但如果量化矢量,或者嵌入模型原生支持它,则输出可能是 float16、int16 或 int8,这明显小于 float32。 可以通过矢量字段分配较窄的数据类型来容纳这些较小的矢量大小。 在矢量索引中,窄数据类型会消耗较少的存储。

  1. 查看用于矢量字段的数据类型的建议用法:

    • Collection(Edm.Single) 32 位浮点(默认)
    • Collection(Edm.Half) 16 位浮点(窄)
    • Collection(Edm.Int16) 16 位有符号整数(窄)
    • Collection(Edm.SByte) 8 位有符号整数(窄)
    • Collection(Edm.Byte) 8 位无符号整数(仅允许使用打包的二进制数据类型)
  2. 从该列表中,确定哪个数据类型对嵌入模型的输出有效,或用于接受自定义量化的矢量。

    下表提供了指向多个嵌入模型的链接,这些模型可以使用窄数据类型 (Collection(Edm.Half)),而无需进行额外的量化。 可以从 float32 转换为 float16(使用 Collection(Edm.Half)),无需额外工作。

    嵌入模型 本机输出 在 Azure AI 搜索中分配此类型
    text-embedding-ada-002 Float32 Collection(Edm.Single)Collection(Edm.Half)
    text-embedding-3-small Float32 Collection(Edm.Single)Collection(Edm.Half)
    text-embedding-3-large Float32 Collection(Edm.Single)Collection(Edm.Half)
    具有 int8 embedding_type 的 Cohere V3 嵌入模型 Int8 Collection(Edm.SByte)

    如果模型以较小的数据格式发出嵌入内容,或者具有将矢量转换为较小格式的自定义量化,则可以使用其他窄数据类型。

  3. 确保了解狭窄数据类型的利弊。 Collection(Edm.Half) 的信息较少,因此分辨率较低。 如果数据是同质或密集的,则丢失额外的细节或细微差别可能会导致查询时出现不可接受的结果,因为可用于区分附近向量的细节较少。

  4. 定义并生成索引。 可以使用 Azure 门户、创建或更新索引 (REST API)或 Azure SDK 包执行此步骤。

  5. 检查结果。 假设向量字段标记为可检索,请使用搜索资源管理器Search - POST 验证字段内容是否与数据类型匹配。

    若要检查矢量索引大小,请使用 Azure 门户或 GET 统计信息 (REST API)

注意

字段的数据类型用于创建物理数据结构。 如果以后想要更改数据类型,请删除并重建索引,或者使用新定义创建第二个字段。

选项 3:设置 stored 属性以移除可检索存储

stored 属性是矢量字段定义上的一个布尔值,用于确定是否为可检索的矢量字段内容分配存储空间。 默认情况下,stored 属性为 true。 如果查询响应中不需要向量内容,则可以通过将 stored 设置为 false 来为每个字段节省最多 50% 的存储空间。

stored 设置为 false 的注意事项:

  • 由于矢量不是人类可读的,因此可在 RAG 方案中在发送到 LLM 的结果中省略这些标量,并在搜索页面上呈现的结果中省略它们。 然而,如果在使用矢量内容的下游进程中使用矢量,请保留它们。

  • 但是,如果索引策略包括部分文档更新(如文档中的“merge”或“mergeOrUpload”),请注意,将 stored 设置为 false 会导致在合并期间省略非存储字段中的矢量。 在每个“merge”或“mergeOrUpload”操作中,除了要更新的其他非矢量字段外,还必须提供矢量字段,否则将删除矢量。

请记住,stored 属性是不可逆的。 该属性是在创建物理数据结构时在矢量字段上创建索引期间设置的。 如果以后需要可检索的矢量内容,则必须删除并重新生成索引,或创建并加载具有新属性的新字段。

下示例显示搜索索引的字段集合。 将 stored 设置为 false 以永久删除向量字段的可检索存储。

PUT https://[service-name].search.azure.cn/indexes/demo-index?api-version=2024-07-01 
   Content-Type: application/json  
   api-key: [admin key]  

     { 
       "name": "demo-index", 
       "fields": [ 
         { 
           "name": "vectorContent", 
           "type": "Collection(Edm.Single)", 
           "retrievable": false, 
           "stored": false, 
           "dimensions": 1536, 
           "vectorSearchProfile": "vectorProfile" 
         } 
       ] 
     } 

要点

  • 仅适用于向量字段

  • 影响磁盘上的存储,而不是内存,并且对查询没有影响。 查询执行使用不受 stored 属性影响的单独向量索引。

  • stored 属性是在向量字段上创建索引期间设置的,并且是不可逆的。 如果以后想要检索内容,则必须删除并重建索引,或者创建并加载具有新属性的新字段。

  • 默认情况下,stored 设置为 true,retrievable 设置为 false。 在默认配置中,会存储可检索的副本,但不会在结果中自动返回。 当 stored 为 true 时,可以随时在 true 和 false 之间切换可检索 retrievable,而无需重建索引。 当 stored 为 false 时,retrievable 也必须为 false 并且不能更改。

示例:矢量压缩技术

以下 Python 代码演示了量子化、窄数据类型以及存储属性的使用:代码示例:使用 Python 矢量量化和存储选项

此代码为每个选项创建和比较存储和矢量索引大小:

****************************************
Index Name: compressiontest-baseline
Storage Size: 21.3613MB
Vector Size: 4.8277MB
****************************************
Index Name: compressiontest-compression
Storage Size: 17.7604MB
Vector Size: 1.2242MB
****************************************
Index Name: compressiontest-narrow
Storage Size: 16.5567MB
Vector Size: 2.4254MB
****************************************
Index Name: compressiontest-no-stored
Storage Size: 10.9224MB
Vector Size: 4.8277MB
****************************************
Index Name: compressiontest-all-options
Storage Size: 4.9192MB
Vector Size: 1.2242MB

搜索 API 在索引级别报告存储和矢量大小,因此索引和字段不能是比较的基础。 在 Azure SDK 中使用 GET 索引统计信息或等效的 API 来获取矢量大小。

使用过采样查询量化向量字段

压缩或量化矢量字段的查询语法与非压缩矢量字段的语法相同,除非想要重写与过度采样或与原始向量重新调整关联的参数。

回想一下,索引中的矢量压缩定义具有用于 rerankWithOriginalVectorsdefaultOversampling 的设置,以缓解较小向量索引的影响。 可以重写默认值,以在查询时改变行为。 例如,如果 defaultOversampling 为 10.0,可以在查询请求中将其更改为其他值。

即使索引没有显式 rerankWithOriginalVectorsdefaultOversampling 定义,也可以设置过采样参数。 在查询时提供过采样会替代该查询的索引设置,并将 oversampling 设置为 rerankWithOriginalVectors 来执行查询。

POST https://[service-name].search.azure.cn/indexes/demo-index/docs/search?api-version=2024-07-01   
  Content-Type: application/json   
  api-key: [admin key]   

    {    
       "vectorQueries": [
            {    
                "kind": "vector",    
                "vector": [8, 2, 3, 4, 3, 5, 2, 1],    
                "fields": "myvector",
                "oversampling": 12.0,
                "k": 5   
            }
      ]    
    }

要点

  • 适用于根据向量配置文件分配进行矢量压缩的向量字段。

  • 即使索引的压缩配置未指定过采样或重新排名选项,也会在查询时替代 defaultOversampling 值或引入过采样。

另请参阅