Milvus 向量检索
Milvus相似度检索入门和部署。
Milvus 负责存储、索引和检索向量,原文、类别等字段可与向量保存在同一条记录中。 Embedding 模型把文本转换成向量,Milvus 根据向量相似度查找内容,并按业务字段限定检索范围。
1. 向量与集合
向量是一组数字,维度就是这组数字的数量。 语义检索根据向量间的接近程度寻找相关内容。
文档入库: 文档批量编码,向量与原文、业务字段一起写入集合。
| |
查询检索: 用户输入单独编码,Milvus 在已有文档向量中搜索,返回匹配记录。
| |
文档和查询需要使用兼容的模型与编码方式。更换模型后,即使维度相同,旧文档向量也未必能与新查询向量比较,需要重新编码文档。
Milvus 用 Collection(集合) 组织记录,Schema(字段定义) 规定字段名称、类型等属性。
文档检索示例包含以下字段,名称由应用程序定义:
| 字段 | 类型 | 用途 |
|---|---|---|
id | INT64 | 文档主键 |
vector | FLOAT_VECTOR | 1024 维文档向量 |
text | VARCHAR | 原始文本 |
category | VARCHAR | 文档类别 |
vector 用于相似度检索;text、category 保存原文和类别,用于返回内容或执行条件过滤。这类不存储向量的字段称为标量字段。
文档向量由 Python 程序调用模型生成并传入 vector,写入 text 字段本身不会触发编码。
2. 文档编码
示例使用 Python 3.13 和 CPU。以下命令在 macOS、Linux 中创建环境,python3 应指向 Python 3.13:
| |
faiss-cpu是 Milvus Lite 3.2.2 的底层依赖,安装命令将其固定为 1.12.0。该安装包要求 Apple Silicon 使用 macOS 14 及以上,Intel Mac 使用 macOS 13 及以上。安装包平台
模型采用 BAAI/bge-m3,通过 Sentence Transformers 将文档和查询编码为 1024 维稠密向量。查询无需添加检索指令,首次加载会下载模型文件。
第 2~6 节代码在同一 Python 会话中依次执行,示例包含六条文档:
| |
vectors的每一行对应一篇文档。normalize_embeddings=True将向量归一化为单位长度,数组使用float32保存。
np.savez在当前工作目录生成vectors.npz,保存文档向量、ID、原文和类别,读取后能重建完整记录。
数据量较大时,文档编码和数据库写入可分别分批处理。每批编码多少条取决于显存或内存,每批写入多少条取决于请求大小和数据库的写入能力,两者不必相同。
3. 集合与索引
3.1 定义字段
Milvus Lite 提供本地向量数据库,Python 客户端通过数据路径连接它。
下面定义集合名称和字段:
| |
数据目录由
uri指定为./milvus_bge_m3.db,集合名由collection指定为article_demo。
dim 必须与模型输出维度一致。auto_id=False 表示主键由程序提供;max_length 按 UTF-8 字节计算,不是汉字个数。
3.2 字段与度量
字段名、距离度量和索引类型分别对应不同的配置项:
| 概念 | 配置项 | 含义与示例 |
|---|---|---|
| 向量字段 | 建索引时的 field_name、搜索时的 anns_field | 指向 Schema 中存储向量的字段,如 vector |
| 距离度量 | metric_type | 规定向量之间的比较方式,如 L2、IP、COSINE |
| 索引类型 | index_type | 规定向量的组织和检索方式,如 FLAT、IVF_FLAT、HNSW |
具体使用哪种度量,要看 metric_type,不能只看索引名称。 IVF_FLAT 支持 L2、IP、COSINE,建索引时通过 metric_type 选定其中一种:
L2:欧氏距离。 比较向量在空间中的距离,数值越小越接近。Milvus 返回的是欧氏距离的平方,不执行开平方。IP:内积。 对应维度相乘后求和,受向量长度和方向影响,数值越大越相似。COSINE:余弦相似度。 比较向量方向,取值范围为 -1 到 1,数值越大越相似。
选择度量时应结合模型的使用说明。
文档与查询都归一化为单位长度后,
IP与COSINE的结果相等。相似度分数不等于答案正确的概率。
3.3 索引类型
Top-K 指按所选度量排序最靠前的 K 条记录。索引的检索方式分为两类:
- 精确检索:找出真实的 Top-K。
- 近似检索:减少搜索范围或压缩向量,节省计算和内存,但可能漏掉部分近邻。
常见的浮点向量索引如下:
| 索引 | 检索方式 | 主要取舍 |
|---|---|---|
FLAT | 逐个比较原始向量,精确检索 | 不需要聚类或建图,查询计算量随数据量增长 |
IVF_FLAT | 向量聚成若干组,查询只搜索选中的组 | 保存原始向量,可能漏掉未访问组中的近邻 |
IVF_SQ8 | 在 IVF 分组基础上,把每个维度量化为 8 位 | 降低向量内存占用,额外引入量化误差 |
IVF_PQ | 在 IVF 分组基础上,把向量分段并用码本编码 | 需要训练码本,压缩率和精度受参数影响 |
HNSW | 建立多层近邻图,沿图寻找候选 | 常用于低延迟检索,图结构增加内存和构建成本 |
DISKANN | 将主要图结构放在磁盘,结合内存中的压缩表示搜索 | 适合数据量超过内存的场景,依赖 SSD 和磁盘 I/O |
AUTOINDEX | 由当前部署按默认策略选择索引 | 减少初始配置,实际实现随版本和配置变化 |
索引还要匹配字段类型和运行环境:
- 稀疏向量:
SPARSE_INVERTED_INDEX。 - 二进制向量:
BIN_FLAT、BIN_IVF_FLAT等。 - GPU 部署:另有
GPU_CAGRA、GPU_IVF_FLAT等索引。 - 标量字段:
category等字段能建立INVERTED索引,加速条件过滤。
Lite 3.2.2 支持
FLAT、IVF_FLAT、IVF_SQ8、HNSW等索引,不支持 PQ、二进制向量、DISKANN和 GPU 索引。上表包含服务端能力,不能在 Lite 中任意替换类型。
3.4 索引参数
建索引参数和搜索参数作用不同。 前者决定索引结构,后者控制每次查询的搜索范围:
| 索引 | 建索引时设置 | 搜索时设置 |
|---|---|---|
| IVF 系列 | nlist:聚类分组数 | nprobe:一次查询访问多少组 |
| HNSW | M:节点连接规模;efConstruction:建图时的候选规模 | ef:查询时的候选规模 |
IVF_PQ另有分段数m和编码位数nbits,其中m必须整除向量维度。
以 IVF_FLAT 为例,nlist=100 表示将向量分成 100 组:
nprobe=10:每次搜索其中 10 组,减少比较量,但可能漏掉其他组里的近邻。nprobe=100:搜索全部分组。由于保存了原始向量,此时可得到精确结果,也失去了跳过分组带来的收益。
增大 nprobe 或 ef 通常能提高召回率,也会增加查询耗时。近邻检索的召回率衡量找回了多少真实近邻,文档是否回答了问题仍需单独评价。
选择索引时,需要同时比较召回率、查询延迟、内存和构建时间。六条文档适合验证流程,无法体现大规模检索的性能差异。
3.5 建立索引与写入
在 article_demo 的 vector 字段上建立 FLAT 索引,使用 COSINE 度量。六条文档直接逐个比较即可:
| |
传入
index_params创建集合时,会建立索引并加载集合。Strong使后续查询能读取已完成的写入。
创建前,确认 article_demo 尚不存在。重复实验时更换集合名,并同步替换后续读取示例中的 article_demo。只读已有数据不需要再次创建或插入。
4. 搜索与过滤
4.1 查询编码
查询到来时,只需编码查询文本,已入库的文档向量直接复用:
| |
query_vector有一行,对应一次查询;维度和归一化方式与文档向量一致。
4.2 相似度搜索
search 接收查询向量,anns_field 指定检索字段。搜索沿用 vector 字段索引的 COSINE 度量,limit=3 表示返回相似度最高的三条记录:
| |
hits[0]对应第一个查询。每条结果的id是主键,entity包含output_fields指定的字段。
distance 是所选度量的分数。 使用 COSINE 时,distance 越大越相似。
不同索引的搜索参数也不同:
- IVF:通过
search_params={"params": {"nprobe": 10}}设置访问的分组数。 - HNSW:使用
ef控制候选规模,普通 Top-K 搜索中它应至少等于limit。 - FLAT:不需要设置
nprobe或ef。
4.3 字段过滤
只搜索“数据库”类别时,将条件传入 filter:
| |
Milvus 在符合条件的记录中检索。 六条文档中,“数据库”类别的候选记录是 101、102、103,返回其中最相似的两条;104、105、106 不属于“数据库”,不会出现在结果中。
对全量 Top-K 再筛类别,可能得不到限定类别的 Top-K:
假设全量 Top-2 是“机器学习”文档 A 和“数据库”文档 B:
- 全量检索后筛类别:删掉 A,只剩 B。
- 限定“数据库”检索:返回 B 和该类别中排名第二的 C。
C 没进入全量 Top-2,却应出现在类别 Top-2 中。
5. 查询、更新与删除
5.1 按字段查询
query 按条件取记录,不计算向量相似度。指定 id in [101, 102],即可读取这两条记录的原文和类别:
| |
5.2 更新记录
upsert 更新已有主键,对不存在的主键执行插入。下面提交主键 101 的完整记录,只修改类别:
| |
类别变化不影响这条记录的文本向量。修改正文时,需要重新编码正文,并将新向量与新正文一起更新。 否则返回的文字已经变化,检索仍使用旧内容的表示。
5.3 删除记录
| |
data: []表示已经查不到 ID 为 106 的记录。删除只影响数据库,不会修改内存中的docs、records或之前保存的vectors.npz。
6. 本地数据保存
Lite 将集合、向量和标量字段保存在 milvus_bge_m3.db 目录中。close() 关闭连接,数据仍然保留;重新连接同一路径即可读取:
| |
load_collection加载已有集合,不会重新创建集合或插入数据。
重新连接后,ID 101 的类别仍是更新后的“检索工具”。vectors.npz 中该记录的类别仍是编码时的“数据库”。
7. Docker 部署
7.1 部署形式
| 形式 | 运行方式 | 常见用途 |
|---|---|---|
| Lite | Python 程序连接本地存储 | Notebook、功能验证、本地小型应用 |
| Standalone | 独立的 Milvus 服务及其依赖 | 单机部署、多个客户端访问 |
| Distributed | 多节点分布式服务 | 分担数据和查询负载,扩展计算资源 |
本节部署 Milvus Standalone 3.0.2。Linux 需要 Docker Engine 和 Compose V2,macOS 使用 Docker Desktop。
按官方环境要求配置资源:
- Docker Desktop 虚拟机:至少分配 2 个 CPU 核心、8 GB 内存。
- Milvus Standalone:推荐 4 核、16 GB 内存。
实际资源需求还取决于向量数量、维度、索引和查询负载。
7.2 启动服务
下载 Compose 配置并启动:
| |
配置中有三个常驻服务:
| 服务 | 职责 | 宿主机端口 | 数据目录 |
|---|---|---|---|
etcd | 保存元数据 | 不映射 | volumes/etcd |
minio | 保存对象数据 | 9000、9001 | volumes/minio |
standalone | 提供 Milvus 服务 | 19530、9091 | volumes/milvus |
milvus-volume-init是初始化数据目录的一次性任务,正常完成时显示Exited (0)。
三个常驻服务显示 healthy,表示各自的状态检查已通过;刚启动时可能显示 health: starting。
查看启动日志和服务状态:
| |
19530 是客户端端口;9091 提供状态检查和 WebUI,浏览器地址为 http://localhost:9091/webui/。
7.3 Python 连接
Python 程序运行在 Docker 宿主机上时,连接 localhost:19530:
| |
第 2 节的
pymilvus==3.0.2与服务版本对应。其他容器中的localhost指向容器自身,需要改成该容器能够访问的 Milvus 服务地址。
Lite 和 Docker 服务是两个独立的数据存储,改变连接地址不会迁移数据。 向新服务写入六条文档时:
- 使用已连接 Docker 中 Milvus 服务的
client,准备好docs和vectors。 - 执行第 3.1 节代码,跳过连接 Lite 的
MilvusClient(uri="./milvus_bge_m3.db")。 - 执行第 3.5 节的建索引和写入代码。
同一 Python 会话中可复用已有的 docs 和 vectors。换了会话时,在第 1 步从 vectors.npz 恢复它们,无需重新加载模型编码文档:
| |
在
vectors.npz所在目录执行这段代码,或把路径改为该文件的实际位置。
恢复的数据包含原始六条文档,不含第 5 节的类别更新和删除。
如果要在新会话中继续第 4 节的相似度搜索,还需要执行第 2 节中 SentenceTransformer 的导入和 model 的初始化语句。查询编码使用这个模型,已恢复的文档向量不需要重新生成。
7.4 停止与重启
在 docker-compose.yaml 所在目录执行:
| |
该命令停止并移除容器,数据保留在 volumes 目录中。重新启动会继续挂载这些数据:
| |
读取已有集合时不需要再次创建或插入:
| |
8. 客户端复用
实际调用中,集合名、向量字段、度量和返回字段通常保持不变,可以在客户端初始化时集中配置。每次检索只传入查询向量、过滤条件和返回条数。
- 集合初始化:按调用方提供的 Schema 和索引参数创建集合,已有集合显式加载。
- 分批写入与更新:
insert()、upsert()自动拆分批次,返回累计写入数。 - 检索与记录管理:
search()接受一条向量,支持标量过滤和分数筛选;query()按条件查询,delete()按主键删除。
以下调用使用配套的 milvus_client.py 模块。服务中已有第 7.3 节写入的 article_demo 集合,query_vector 沿用第 4.1 节生成的查询向量:
| |
MilvusCollectionClient是配套示例定义的类,内部调用官方MilvusClient。它只接收已经生成的向量,不绑定某个模型;search()已去掉单条查询结果的最外层,调用方直接遍历命中列表。
需要按距离或相似度筛选结果时,向 search() 传入 threshold。COSINE、IP 保留不低于阈值的结果,L2 保留不高于阈值的结果。筛选发生在 Top-K 返回后,因此最终条数可能少于 limit;阈值需要结合实际数据确定。
评论
使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。
站长管理删除会同步到 GitHub,无法撤销。
阅读到这里时加载评论。