Qdrant 1.10 - 通用查询、内置 IDF 及 ColBERT 支持
David Myriel
·2024 年 7 月 1 日

Qdrant 1.10.0 现已发布! 该版本引入了多项重大变更,让我们深入了解一下。
通用查询 API (Universal Query API):包括混合搜索在内的所有搜索 API 现在统一到一个 Query 接口中。
内置 IDF:我们在 Qdrant 的核心搜索和索引流程中加入了 IDF 机制。
多向量支持:通过 Query API 可实现对 ColBERT 后期交互(late interaction)的原生支持。
所有查询共用一个接口
Query API 将所有搜索 API 合并为单个请求。以前,您需要在此 API 之外处理不同搜索请求的组合。现在,这些方法被简化为单个请求的参数,无需再手动合并各个结果。
您现在可以使用以下参数配置 Query API 请求:
| 参数 | 描述 |
|---|---|
| 无参数 | 按 id 返回点位 |
nearest | 查询最近邻(搜索) |
fusion | 融合稀疏/稠密预取查询(混合搜索) |
discover | 查询带有附加 context 的 target(发现 API) |
context | 仅有 context 而无 target 的查询(上下文搜索) |
recommend | 基于 positive/negative 示例进行查询(推荐 API) |
order_by | 按 有效负载 (payload) 字段对结果进行排序 |
例如,您可以配置 Query API 来运行发现搜索 (Discovery search)。让我们看看具体效果。
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 调用中按顺序执行多个查询。这里有许多选项,您需要定义一个策略来使用新参数合并这些请求。例如,现在可以在混合搜索中包含重排序 (rescoring),这为通过 Matryoshka 嵌入实现迭代优化等策略提供了可能。
要了解更多信息,请阅读 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,该功能深受用户期待。我们决定将 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,
})
);
作为 BM42 核心的 IDF
本季度,Qdrant 还引入了 BM42,这是一种结合了 BM25 的 IDF 元素与基于 Transformer 的注意力矩阵的新颖算法,旨在提高文本检索效果。它利用来自嵌入模型的注意力矩阵,根据文档中每个 Token 获得的注意力值来确定其重要性。
我们准备了标准的 all-MiniLM-L6-v2 Sentence Transformer,使其输出注意力值。当然,只要您能访问模型的参数,几乎可以使用任何您选择的模型。这也是选择开源技术而非专有系统的一个理由。
实际上,BM42 方法解决了与 SPLADE 相关的 Token 化问题和计算成本。该模型在处理不同类型和长度的文档时既高效又有效,通过结合 BM25 和现代 Transformer 技术的优势,提供了增强的搜索性能。
要了解关于 IDF 和 BM42 的更多信息,请阅读我们专门的技术文章。
您可以预期 BM42 在以短文本为主的可扩展 RAG 场景中表现卓越。 BM42 的文档推理速度快得多,这对于搜索引擎、推荐系统和实时决策系统等大规模应用至关重要。
多向量支持
我们正在添加对多向量搜索的原生支持,它兼容如 ColBERT 模型等后期交互 (late-interaction) 技术。如果您正在处理高维相似度搜索,强烈建议将 ColBERT 作为通用查询搜索中的重排序步骤。 由于 ColBERT 的方法允许更深入的语义理解,您将获得更高质量的向量检索。
该模型在查询-文档交互期间保留了上下文信息,从而获得更好的相关性评分。在效率和可扩展性方面,文档和查询将分开编码,这为预计算和存储文档嵌入以实现更快的检索提供了机会。
注意: 该功能支持所有原始量化压缩方法,与普通搜索方法相同。
运行带有 ColBERT 向量的查询。
Query API 可以处理极其复杂的请求。以下示例使用 mrl_byte 命名向量预取与给定查询最相似的 1000 个条目,然后对其进行重排序以获取匹配度最高的 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 有用,它还可以以其他方式使用。
例如,在电商领域,您可以使用多向量来存储同一商品的多张图片。这是分组 (group-by) 方法的一种替代方案。
稀疏向量压缩
在 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
}
);
存储: 在后端,我们实现了位打包 (bit packing) 以最大限度地减少存储数据所需的位数,这对于处理机器学习和数据压缩等应用中的稀疏向量至关重要。对于大部分为零的稀疏向量,这专注于仅存储非零元素的索引和值。
您将受益于更紧凑的存储和更高的处理效率。这还可以减少数据集大小,从而在数据压缩中实现更快的处理速度和更低的存储成本。
全新的 Rust 客户端
Qdrant 的 Rust 客户端已全面重构,现在更易于使用。我们专注于构建简约的 API 接口。所有操作及其类型现在都使用构建器模式 (builder pattern),提供了一个易于扩展的接口,防止未来更新导致代码损坏。参见 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 通过各种存储类别和高效的数据传输方法支持性能优化,实现了快速且有效的快照检索与管理。
问题 API (Issues API)
问题 API (Issues API) 会通知您潜在的性能问题和配置错误。这一强大的新功能允许用户(例如数据库管理员)直接在系统内高效管理和跟踪问题,确保系统运行更加平稳并更快解决故障。
您可以在右上角找到“问题”按钮。点击铃铛图标,将打开一个侧边栏以显示当前存在的问题。
