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

Qdrant 1.10.0 发布了!这个版本引入了一些重大变化,让我们深入了解一下
通用查询 API:所有搜索 API,包括混合搜索,现已整合到一个 Query 端点中。
内置 IDF:我们将 IDF 机制添加到了 Qdrant 的核心搜索和索引过程中。
多向量支持:通过 Query API 可访问后期交互 ColBERT 的原生支持。
一个用于所有查询的端点
Query API 将所有搜索 API 整合到一个请求中。以前,您必须在 API 之外进行操作来组合不同的搜索请求。现在,这些方法都被简化为单个请求的参数,因此您可以避免合并单个结果。
您现在可以使用以下参数配置 Query API 请求
| 参数 | 描述 |
|---|---|
| 无参数 | 按 id 返回点 |
nearest | 查询最近邻 (搜索) |
fusion | 融合稀疏/密集预取查询 (混合搜索) |
discover | 查询带有附加 context 的 target (发现) |
context | 仅使用 context 而无 target (上下文) |
recommend | 根据 positive/negative 示例进行查询。 (推荐) |
order_by | 按payload 字段对结果进行排序 |
例如,您可以配置 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 通过各种存储类别和高效的数据传输方法支持性能优化,从而实现快速有效的快照检索和管理。
问题 API
问题 API 会通知您潜在的性能问题和配置错误。这个强大的新功能允许用户(例如数据库管理员)直接在系统中高效管理和跟踪问题,从而确保更流畅的操作和更快的解决方案。
您可以在右上角找到“问题”按钮。当您点击铃铛图标时,将打开一个侧边栏显示当前问题。
