0

Qdrant 1.10 - 通用查询、内置 IDF 和 ColBERT 支持

David Myriel

·

2024 年 7 月 1 日

Qdrant 1.10 - Universal Query, Built-in IDF & ColBERT Support

Qdrant 1.10.0 发布了!这个版本引入了一些重大变化,让我们深入了解一下

通用查询 API:所有搜索 API,包括混合搜索,现已整合到一个 Query 端点中。
内置 IDF:我们将 IDF 机制添加到了 Qdrant 的核心搜索和索引过程中。
多向量支持:通过 Query API 可访问后期交互 ColBERT 的原生支持。

一个用于所有查询的端点

Query API 将所有搜索 API 整合到一个请求中。以前,您必须在 API 之外进行操作来组合不同的搜索请求。现在,这些方法都被简化为单个请求的参数,因此您可以避免合并单个结果。

您现在可以使用以下参数配置 Query API 请求

参数描述
无参数id 返回点
nearest查询最近邻 (搜索)
fusion融合稀疏/密集预取查询 (混合搜索)
discover查询带有附加 contexttarget (发现)
context仅使用 context 而无 target (上下文)
recommend根据 positive/negative 示例进行查询。 (推荐)
order_bypayload 字段对结果进行排序

例如,您可以配置 Query API 来运行发现搜索。让我们看看它是怎样的

POST collections/{collection_name}/points/query
{
  "query": {
    "discover": {
      "target": <vector_input>,
      "context": [
        {
          "positive": <vector_input>,
          "negative": <vector_input>
        }
      ]
    }
  }
}

我们将在文档和我们新的API 规范中发布代码示例。
如果您在使用这个新方法时需要额外支持,我们的Discord 值班工程师可以为您提供帮助。

原生混合搜索支持

Query API 现在也原生支持稀疏/密集融合。在此之前,您必须自己组合稀疏和密集搜索的结果。现在这已在后端处理妥当,您只需将它们配置为 Query API 的基本参数即可。

POST /collections/{collection_name}/points/query
{
    "prefetch": [
        {
            "query": { 
                "indices": [1, 42],    // <┐
                "values": [0.22, 0.8]  // <┴─sparse vector
             },
            "using": "sparse",
            "limit": 20
        },
        {
            "query": [0.01, 0.45, 0.67, ...], // <-- dense vector
            "using": "dense",
            "limit": 20
        }
    ],
    "query": { "fusion": "rrf" }, // <--- reciprocal rank fusion
    "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.query_points(
    collection_name="{collection_name}",
    prefetch=[
        models.Prefetch(
            query=models.SparseVector(indices=[1, 42], values=[0.22, 0.8]),
            using="sparse",
            limit=20,
        ),
        models.Prefetch(
            query=[0.01, 0.45, 0.67],
            using="dense",
            limit=20,
        ),
    ],
    query=models.FusionQuery(fusion=models.Fusion.RRF),
)
import { QdrantClient } from "@qdrant/js-client-rest";

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

client.query("{collection_name}", {
    prefetch: [
        {
            query: {
                values: [0.22, 0.8],
                indices: [1, 42],
            },
            using: 'sparse',
            limit: 20,
        },
        {
            query: [0.01, 0.45, 0.67],
            using: 'dense',
            limit: 20,
        },
    ],
    query: {
        fusion: 'rrf',
    },
});
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{Fusion, PrefetchQueryBuilder, Query, QueryPointsBuilder};

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

client.query(
    QueryPointsBuilder::new("{collection_name}")
        .add_prefetch(PrefetchQueryBuilder::default()
            .query(Query::new_nearest([(1, 0.22), (42, 0.8)].as_slice()))
            .using("sparse")
            .limit(20u64)
        )
        .add_prefetch(PrefetchQueryBuilder::default()
            .query(Query::new_nearest(vec![0.01, 0.45, 0.67]))
            .using("dense")
            .limit(20u64)
        )
        .query(Query::new_fusion(Fusion::Rrf))
).await?;
import static io.qdrant.client.QueryFactory.nearest;

import java.util.List;

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

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.Fusion;
import io.qdrant.client.grpc.Points.PrefetchQuery;
import io.qdrant.client.grpc.Points.QueryPoints;

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

client.queryAsync(
    QueryPoints.newBuilder()
    .setCollectionName("{collection_name}")
    .addPrefetch(PrefetchQuery.newBuilder()
      .setQuery(nearest(List.of(0.22f, 0.8f), List.of(1, 42)))
      .setUsing("sparse")
      .setLimit(20)
      .build())
    .addPrefetch(PrefetchQuery.newBuilder()
      .setQuery(nearest(List.of(0.01f, 0.45f, 0.67f)))
      .setUsing("dense")
      .setLimit(20)
      .build())
    .setQuery(fusion(Fusion.RRF))
    .build())
  .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

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

await client.QueryAsync(
  collectionName: "{collection_name}",
  prefetch: new List < PrefetchQuery > {
    new() {
      Query = new(float, uint)[] {
          (0.22f, 1), (0.8f, 42),
        },
        Using = "sparse",
        Limit = 20
    },
    new() {
      Query = new float[] {
          0.01f, 0.45f, 0.67f
        },
        Using = "dense",
        Limit = 20
    }
  },
  query: Fusion.Rrf
);

Query API 现在可以为请求预取向量,这意味着您可以在同一 API 调用中顺序运行查询。这里有很多选项,因此您需要定义一个策略来使用新参数合并这些请求。例如,您现在可以在混合搜索中包含重新评分,这为通过套娃式嵌入(matryoshka embeddings)进行迭代细化等策略打开了大门。

要了解更多信息,请阅读Query API 文档

逆文档频率 [IDF]

IDF 是 TF-IDF (术语频率-逆文档频率) 加权方案的关键组成部分,该方案用于评估一个词在文档相对于文档集合(语料库)中的重要性。计算 IDF 的方法有很多种,但最常用的公式是

$$ \text{IDF}(q_i) = \ln \left(\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5}+1\right) $$

其中
N 是集合中的文档总数。
n 是包含给定向量非零值的文档数量。

此变体也用于 BM25,我们的用户强烈要求支持 BM25。我们决定将 IDF 计算移至 Qdrant 引擎本身。这种分离允许稀疏嵌入的流式更新,同时保持 IDF 计算的最新状态。

之前,IDF 的值必须在客户端使用所有文档进行计算。然而,现在 Qdrant 开箱即用,您无需在其他地方实现它,并且如果在某些文档被删除或新添加时无需重新计算该值。

您可以在集合配置中启用 IDF 修饰符

PUT /collections/{collection_name}
{
    "sparse_vectors": {
        "text": {
            "modifier": "idf"
        }
    }
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_collection(
    collection_name="{collection_name}",
    sparse_vectors={
        "text": models.SparseVectorParams(
            modifier=models.Modifier.IDF,
        ),
    },
)
import { QdrantClient } from "@qdrant/js-client-rest";

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

client.createCollection("{collection_name}", {
    sparse_vectors: {
        "text": {
            modifier: "idf"
        }
    }
});
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{CreateCollectionBuilder, sparse_vectors_config::SparseVectorsConfigBuilder, Modifier, SparseVectorParamsBuilder};

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

let mut config = SparseVectorsConfigBuilder::default();
config.add_named_vector_params(
    "text",
    SparseVectorParamsBuilder::default().modifier(Modifier::Idf),
);

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .sparse_vectors_config(config),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Modifier;
import io.qdrant.client.grpc.Collections.SparseVectorConfig;
import io.qdrant.client.grpc.Collections.SparseVectorParams;

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

client
  .createCollectionAsync(
    CreateCollection.newBuilder()
    .setCollectionName("{collection_name}")
    .setSparseVectorsConfig(
      SparseVectorConfig.newBuilder()
      .putMap("text", SparseVectorParams.newBuilder().setModifier(Modifier.Idf).build()))
    .build())
  .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

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

await client.CreateCollectionAsync(
  collectionName: "{collection_name}",
  sparseVectorsConfig: ("text", new SparseVectorParams {
    Modifier = Modifier.Idf,
  })
);

IDF 作为 BM42 的一部分

本季度,Qdrant 还引入了 BM42,这是一种新颖的算法,它将 BM25 的 IDF 元素与基于 Transformer 的注意力矩阵相结合,以改进文本检索。它利用嵌入模型中的注意力矩阵,根据接收到的注意力值来确定文档中每个 token 的重要性。

我们准备了标准的 all-MiniLM-L6-v2 Sentence Transformer,以便它输出注意力值。不过,您实际上可以使用任何您选择的模型,只要您可以访问其参数。这只是选择开源技术而非专有系统的又一个原因。

在实际应用中,BM42 方法解决了与 SPLADE 相关的分词问题和计算成本。该模型在不同文档类型和长度下都高效且有效,通过结合 BM25 和现代 Transformer 技术的优势,提供了增强的搜索性能。

要了解有关 IDF 和 BM42 的更多信息,请阅读我们的技术专文

您可以期待 BM42 在可扩展的、短文本更常见的 RAG 场景中表现出色。使用 BM42 时文档推理速度要高得多,这对于搜索引擎、推荐系统和实时决策系统等大规模应用程序至关重要。

多向量支持

我们正在添加对多向量搜索的原生支持,该支持与后期交互ColBERT 模型兼容。如果您正在处理高维相似性搜索,强烈建议将 ColBERT 用作通用查询搜索中的重排步骤。由于 ColBERT 的方法允许更深层的语义理解,您将体验到更高质量的向量检索。

该模型在查询-文档交互过程中保留了上下文信息,从而提高了相关性评分。在效率和可伸缩性方面,文档和查询将分别进行编码,这为预计算和存储文档嵌入以实现更快检索提供了机会。

注意: 此功能支持所有原始的量化压缩方法,与常规搜索方法相同。

使用 ColBERT 向量运行查询

Query API 可以处理极其复杂的请求。以下示例使用名为 mrl_byte 的向量预取与给定查询最相似的 1000 个条目,然后使用名为 full 的向量对其进行重排以获得最佳的 100 个匹配项,最终再次重排以使用名为 colbert 的向量提取前 10 个结果。现在,一个 API 调用即可实现复杂的重排方案。

POST /collections/{collection_name}/points/query
{
    "prefetch": {
        "prefetch": {
            "query": [1, 23, 45, 67], // <------ small byte vector
            "using": "mrl_byte",
            "limit": 1000
        },
        "query": [0.01, 0.45, 0.67, ...], // <-- full dense vector
        "using": "full",
        "limit": 100
    },
    "query": [           // <─┐
        [0.1, 0.2, ...], // < │
        [0.2, 0.1, ...], // < ├─ multi-vector
        [0.8, 0.9, ...]  // < │
    ],                   // <─┘       
    "using": "colbert",
    "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.query_points(
    collection_name="{collection_name}",
    prefetch=models.Prefetch(
        prefetch=models.Prefetch(query=[1, 23, 45, 67], using="mrl_byte", limit=1000),
        query=[0.01, 0.45, 0.67],
        using="full",
        limit=100,
    ),
    query=[
        [0.1, 0.2],
        [0.2, 0.1],
        [0.8, 0.9],
    ],
    using="colbert",
    limit=10,
)
import { QdrantClient } from "@qdrant/js-client-rest";

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

client.query("{collection_name}", {
    prefetch: {
        prefetch: {
            query: [1, 23, 45, 67],
            using: 'mrl_byte',
            limit: 1000
        },
        query: [0.01, 0.45, 0.67],
        using: 'full',
        limit: 100,
    },
    query: [
        [0.1, 0.2],
        [0.2, 0.1],
        [0.8, 0.9],
    ],
    using: 'colbert',
    limit: 10,
});
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{PrefetchQueryBuilder, Query, QueryPointsBuilder};

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

client.query(
    QueryPointsBuilder::new("{collection_name}")
        .add_prefetch(PrefetchQueryBuilder::default()
            .add_prefetch(PrefetchQueryBuilder::default()
                .query(Query::new_nearest(vec![1.0, 23.0, 45.0, 67.0]))
                .using("mrl_byte")
                .limit(1000u64)
            )
            .query(Query::new_nearest(vec![0.01, 0.45, 0.67]))
            .using("full")
            .limit(100u64)
        )
        .query(Query::new_nearest(vec![
            vec![0.1, 0.2],
            vec![0.2, 0.1],
            vec![0.8, 0.9],
        ]))
        .using("colbert")
        .limit(10u64)
).await?;
import static io.qdrant.client.QueryFactory.nearest;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.PrefetchQuery;
import io.qdrant.client.grpc.Points.QueryPoints;

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

client
    .queryAsync(
        QueryPoints.newBuilder()
            .setCollectionName("{collection_name}")
            .addPrefetch(
                PrefetchQuery.newBuilder()
                    .addPrefetch(
                        PrefetchQuery.newBuilder()
                            .setQuery(nearest(1, 23, 45, 67)) // <------------- small byte vector
                            .setUsing("mrl_byte")
                            .setLimit(1000)
                            .build())
                    .setQuery(nearest(0.01f, 0.45f, 0.67f)) // <-- dense vector
                    .setUsing("full")
                    .setLimit(100)
                    .build())
            .setQuery(
                nearest(
                    new float[][] {
                      {0.1f, 0.2f}, // <─┐
                      {0.2f, 0.1f}, // < ├─ multi-vector
                      {0.8f, 0.9f}  // < ┘
                    }))
            .setUsing("colbert")
            .setLimit(10)
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

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

await client.QueryAsync(
  collectionName: "{collection_name}",
  prefetch: new List <PrefetchQuery> {
    new() {
      Prefetch = {
          new List <PrefetchQuery> {
            new() {
              Query = new float[] { 1, 23, 45, 67 }, // <------------- small byte vector
                Using = "mrl_byte",
                Limit = 1000
            },
          }
        },
        Query = new float[] {0.01f, 0.45f, 0.67f}, // <-- dense vector
        Using = "full",
        Limit = 100
    }
  },
  query: new float[][] {
    [0.1f, 0.2f], // <─┐
    [0.2f, 0.1f], // < ├─ multi-vector
    [0.8f, 0.9f]  // < ┘
  },
  usingVector: "colbert",
  limit: 10
);

注意: 多向量功能不仅对 ColBERT 有用;它还可以用于其他方面。
例如,在电子商务中,您可以使用多向量来存储同一商品的多个图像。这可以作为分组方法的替代方案。

稀疏向量压缩

在 1.9 版本中,我们为稀疏向量引入了 uint8 向量数据类型,以便支持 JinaAI 和 Cohere 等公司预量化的嵌入。本次,我们将为稀疏向量和密集向量引入新的数据类型,以及一种不同的向量存储方式。

数据类型:稀疏向量和密集向量之前以较大的 float32 值表示,但现在可以转换为 float16。与 float32 相比,float16 向量的精度较低,这意味着向量值的数值精度较低——但这对于实际用例来说可以忽略不计。

这些向量将使用常规向量一半的内存,这可以显著减少大型向量数据集的占用空间。由于减少了内存带宽需求和更好的缓存利用率,操作可以更快。这可以加快向量搜索操作,尤其是在内存受限的场景中。

创建集合时,您需要提前指定 datatype

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 1024,
      "distance": "Cosine",
      "datatype": "float16"
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.create_collection(
    "{collection_name}",
    vectors_config=models.VectorParams(
        size=1024, distance=models.Distance.COSINE, datatype=models.Datatype.FLOAT16
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

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

client.createCollection("{collection_name}", {
    vectors: {
        size: 1024,
        distance: "Cosine",
        datatype: "float16"
    }
});
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Datatype;
import io.qdrant.client.grpc.Collections.Distance;
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(1024)
        .setDistance(Distance.Cosine)
        .setDatatype(Datatype.Float16)
        .build())
      .build())
    .build())
  .get();
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{CreateCollectionBuilder, Datatype, Distance, VectorParamsBuilder};

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

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}").vectors_config(
            VectorParamsBuilder::new(1024, Distance::Cosine).datatype(Datatype::Float16),
        ),
    )
    .await?;
using Qdrant.Client;
using Qdrant.Client.Grpc;

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

await client.CreateCollectionAsync(
  collectionName: "{collection_name}",
  vectorsConfig: new VectorParams {
    Size = 1024,
    Distance = Distance.Cosine,
    Datatype = Datatype.Float16
  }
);

存储:在后端,我们实现了位打包,以最大限度地减少存储数据所需的位数,这对于处理机器学习和数据压缩等应用中的稀疏向量至关重要。对于大部分为零的稀疏向量,此方法专注于仅存储非零元素的索引和值。

您将受益于更紧凑的存储和更高的处理效率。这还可以减小数据集大小,从而在数据压缩中实现更快的处理和更低的存储成本。

新的 Rust 客户端

Qdrant 的 Rust 客户端已全面重塑。现在它更易于访问和使用。我们专注于构建一个极简的 API 接口。所有操作及其类型现在都使用 builder 模式,提供了一个简单且可扩展的接口,防止未来的更新造成中断。请参阅 Rust ColBERT 查询作为很好的例子。此外,Rust 支持安全的并发执行,这对于高效处理多个并发请求至关重要。

文档也得到了显著改进。它组织得更好,并提供了全面的使用示例。所有内容都链接回我们的主文档,使导航和查找所需信息更加容易。

访问我们的客户端操作文档

S3 快照存储

Qdrant 的集合分片存储可以使用快照进行备份,并在数据丢失或其他数据传输目的时进行保存。这些快照可能相当大,维护它们所需的资源可能会导致更高的成本。AWS S3 和其他兼容 S3 的实现(如 min.io)是一个很好的低成本替代方案,可以在不产生高成本的情况下保存快照。它具有全球可靠性、可扩展性并能抵御数据丢失。

您可以在 config.yaml 中配置 S3 存储设置,特别是使用 snapshots_storage

例如,使用 AWS S3

storage:
  snapshots_config:
    # Use 's3' to store snapshots on S3
    snapshots_storage: s3

    s3_config:
      # Bucket name
      bucket: your_bucket_here

      # Bucket region (e.g. eu-central-1)
      region: your_bucket_region_here

      # Storage access key
      # Can be specified either here or in the `AWS_ACCESS_KEY_ID` environment variable.
      access_key: your_access_key_here

      # Storage secret key
      # Can be specified either here or in the `AWS_SECRET_ACCESS_KEY` environment variable.
      secret_key: your_secret_key_here

阅读更多关于S3 快照存储配置的信息。

此集成使得快照的分发更加便捷。任何 S3 兼容对象存储的用户现在都可以从其他平台服务中获益,例如自动化工作流和灾难恢复选项。S3 的加密和访问控制确保了存储安全和法规遵从性。此外,S3 通过各种存储类别和高效的数据传输方法支持性能优化,从而实现快速有效的快照检索和管理。

问题 API

问题 API 会通知您潜在的性能问题和配置错误。这个强大的新功能允许用户(例如数据库管理员)直接在系统中高效管理和跟踪问题,从而确保更流畅的操作和更快的解决方案。

您可以在右上角找到“问题”按钮。当您点击铃铛图标时,将打开一个侧边栏显示当前问题。

issues api

次要改进

  • 预配置集合参数;量化、向量存储和复制因子 - #4299

  • 覆盖集合的全局优化器配置。允许您在单个 Qdrant 集群内分离索引和搜索的角色 - #4317

  • 稀疏向量的 Delta 编码和位打包压缩将稀疏向量的内存消耗降低高达 75% - #4253, #4350

免费开始使用 Qdrant

开始使用