Milvus向量检索实战:稀疏、密集、二进制向量的选型与调参指南
聊到向量检索这两年最常被问到的三个问题几乎绕不开稀疏向量“密集向量”和“二进制向量”。很多人一上来就对着 Milvus 的文档发懵三种向量到底有什么区别我的场景该用哪一种检索时选余弦值还是内积更麻烦的是很多人照着网上的例子把数据怼进 Collection 里结果召回效果稀烂或者查询慢得没法忍。这篇文章我就把自己在 Milvus 里折腾这三种向量的完整过程整理出来。不会只讲概念而是从环境安装到 Schema 设计从向量写入到检索调参尽量把每个环节的关键决策点都拆开说清楚。如果你正在做 RAG、图片去重、推荐召回或者多模态检索这份实践指南可以帮你省掉不少试错的弯路。1. 向量检索必读三种向量类型到底在解决什么问题1.1 从“向量检索”说起向量检索的本质是把图片、文本、音频这类非结构化数据映射成高维空间里的坐标点然后通过计算坐标点之间的距离来度量它们的相似度。这个过程里最重要的是两件事一是用什么模型产出向量二是用什么引擎把向量存下来并快速搜索。Milvus 就是那个引擎角色它本身不负责生成向量只负责把向量索引好、搜索快。在 Milvus 里向量字段的类型直接决定了你能用哪种索引、哪种度量方式、以及大概要花多少内存。很多人一开始不重视这个选择等到数据量上来再改 Schema那代价是非常痛苦的。所以在写代码之前先把三种向量的特性和适用边界搞清楚。1.2 密集向量Dense Vector密集向量是最常见的向量形式几乎所有深度学习的 embedding 输出都是密集向量。它的特点很直白向量绝大多数维度都不为零长度固定比如 BERT 的 768 维、OpenAI 的 1536 维、CLIP 的 512 维等。因为密集向量包含的信息密度高所以在语义检索、相似图片匹配、推荐召回这类任务里效果非常好。Milvus 对密集向量的支持也是最成熟的默认的 AUTOINDEX 可以自动选择合适索引HNSW、IVF 这些经典索引也都能直接用。但密集向量的问题也明显维度一旦高起来内存开销和计算量都会跟着涨。1536 维的向量一亿条数据就是大概 600GB 的裸内存再算上索引的额外开销对硬件是不小的考验。1.3 稀疏向量Sparse Vector稀疏向量和密集向量正好相反绝大部分维度都是零只有少数维度有值。经典的例子是 BM25 这类词频统计特征词的维度就是整个词表的大小可能有好几万甚至几十万维但一篇文章里真正出现过的词就那么几百个所以非零维度非常少。在 Milvus 中稀疏向量用SPARSE_VECTOR类型表示存储和计算时只处理非零维度内存和计算量都大大降低。它对精确关键词匹配、专有名词识别、以及那些语义模型容易翻车的场景特别有用。比如一个医疗术语缩写用 Dense Embedding 检索可能因为语义相近而召回一堆不相关的东西但稀疏向量能精确命中词项。所以现在很多生产级 RAG 系统会把 Dense 和 Sparse 两条路都走最后做融合排序兼顾语义泛化和关键词精确性。1.4 二进制向量Binary Vector二进制向量是另一种极端每个维度只能取 0 或 1存储时按位压缩。比如一个 512 维的二进制向量实际只占 64 字节只有同样维度 Float32 向量的十六分之一。Milvus 里添加二进制向量字段时维度必须填能被 8 整除的数因为底层是按字节存的。二进制向量非常适合对存储极度敏感的场景比如大规模图片哈希去重、视频指纹识别、快速近似去重。你可以用感知哈希算法把图片转成一个 256 位的二进制向量在海量图库里快速找到完全相同或非常接近的图。当然二进制向量牺牲了精度不适合做细粒度语义匹配。它更擅长粗排或者过滤先用二进制向量快速圈定一个候选集再用密集向量做精排这是很常见的工程组合拳。1.5 三种向量的适用场景对比维度密集向量稀疏向量二进制向量表现形式绝大多数维度非零绝大多数维度为零每个维度是 0 或 1典型维度512 / 768 / 1536词表大小几万到几十万64 / 128 / 256 位代表算法BERT、CLIP、OpenAI EmbeddingBM25、SPLADE、Learned Sparse感知哈希、SimHash擅长任务语义相似、推荐召回关键词精确匹配、术语检索去重、指纹、粗筛磁盘与内存高较低最低Milvus 常用索引AUTOINDEX、HNSW、IVFSPARSE_INVERTED_INDEXBIN_IVF_FLAT这张表是一个快速入口。实际项目里不用死守某一类很多高流量场景是三类向量共存的后面我会讲怎么在一个 Collection 里同时管理它们。2. 环境准备Milvus Standalone 模式安装与连接2.1 为什么选 Standalone 模式Milvus 有 Standalone 和 Cluster 两种部署形态。很多人第一次接触就被各种组件名吓到etcd、MinIO、Pulsar、coordinator 等等。其实做学习和中小型项目Standalone 模式足够用了它把所有组件打包在一个进程组里通过 Docker Compose 一条命令就能拉起来。我建议不要一上来就折腾 Cluster。先把 Standalone 玩熟练理解 Collection、Index、Partition 这些核心概念后面真有扩容需求再平滑迁移到 Cluster。Standalone 内部也包含 etcd 和 MinIO只是以依赖组件的形式一起跑数据持久化和元数据管理都没问题。2.2 安装步骤Docker Compose 拉起 Milvus以 v2.4 以上版本为例我使用的是milvusdb/milvus官方镜像。先在项目目录创建docker-compose.yml内容核心是几个服务etcd 负责元数据minio 负责对象存储milvus 是主服务。version: 3.5 services: etcd: image: quay.io/coreos/etcd:v3.5.5 environment: - ETCD_AUTO_COMPACTION_MODErevision - ETCD_AUTO_COMPACTION_RETENTION1000 - ETCD_QUOTA_BACKEND_BYTES4294967296 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urlshttp://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd minio: image: minio/minio:RELEASE.2023-03-20T20-16-18Z environment: MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data healthcheck: test: [CMD, curl, -f, http://localhost:9000/minio/health/live] interval: 30s timeout: 20s retries: 3 milvus: image: milvusdb/milvus:v2.4.5 command: [milvus, run, standalone] ports: - 19530:19530 - 9091:9091 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus depends_on: - etcd - minio文件准备好后在终端执行docker compose up -d第一次拉镜像会比较久耐心等。启动完成后检查一下进程状态docker compose ps看到 milvus 正常 runningetcd 和 minio 的 health 也都是 healthy说明服务已经是可用的状态。2.3 连接与初始化代码服务起来之后用 PyMilvus 连接。先装依赖pip install pymilvus然后写一段最基础的连接测试from pymilvus import connections connections.connect( aliasdefault, host127.0.0.1, port19530 ) print(Milvus connection established)这里 host 和 port 就是我们映射出来的参数。如果是在服务器上部署记得在你的云安全组里放行 19530 端口否则外部连不上。2.4 资源规划与注意事项Standalone 模式默认配置适合小规模数据但有几个坑我要提前说第一Swap 要控制好。Milvus 部署后如果发现查询很慢先看看是不是物理内存不够导致内存和磁盘交换频繁。建议给 Milvus 容器至少 8GB 内存数据量大时再往上加。第二MinIO 的数据不要随便删。volumes目录里的/milvus是数据持久化目录MinIO 里存的是 segment 和 index 文件。如果你把容器删了重建卷还在那就没问题如果卷也被清掉等于从头再来。第三生产环境不要长期用 root 权限跑容器。我在实际部署时吃过这个亏容器内写出来的文件属主是 root后续想备份目录时权限处理特别麻烦。可以在 docker-compose 里指定 user或者容器起来后统一 chown。3. 实操核心在 Milvus 中为三种向量建 Schema、写数据3.1 创建 Collection 前要想清楚的事Milvus 的设计里Collection 可以类比成关系型数据库的表。在创建之前你需要明确几个问题主键用自增还是自定义需要哪些标量字段做过滤向量字段需要支持什么度量方式是否需要多个向量字段共存一个常见误区是很多人把所有向量都塞进一个字段里。实际上 Milvus 从 2.4 开始支持一个 Collection 里同时存在多个向量字段而且可以是不同类型的向量。这意味着你完全可以在一个 Collection 里同时维护 Dense、Sparse、Binary 三种向量检索时分别算分最后再做融合。3.2 密集向量 Collection 示例我们先创建一个最经典的密集向量 Collection。假设我们做一个商品图片检索向量由 CLIP 模型产出维度是 512。from pymilvus import ( connections, CollectionSchema, FieldSchema, DataType, Collection ) connections.connect(host127.0.0.1, port19530) # 商品ID item_id FieldSchema( nameitem_id, dtypeDataType.INT64, is_primaryTrue, auto_idFalse ) # 商品图片路径作为标量字段保存 image_path FieldSchema( nameimage_path, dtypeDataType.VARCHAR, max_length500 ) # CLIP 产出的 512 维密集向量 dense_embedding FieldSchema( namedense_embedding, dtypeDataType.FLOAT_VECTOR, dim512 ) schema CollectionSchema( fields[item_id, image_path, dense_embedding], descriptionProduct image retrieval collection ) collection Collection( nameproduct_images, schemaschema )创建好 Collection 之后需要创建索引。密集向量用 HNSW 或者 AUTOINDEX 都行。我倾向于先用 HNSW因为它在召回率和查询延迟的平衡上表现最好。index_params { index_type: HNSW, metric_type: COSINE, params: {M: 16, efConstruction: 200} } collection.create_index( field_namedense_embedding, index_paramsindex_params )这里metric_type我选了 COSINE对应的就是大家常说的余弦值。如果你的向量已经做过归一化用内积 IP 和余弦效果等价但 IP 在某些索引上计算更快。3.3 稀疏向量 Collection 示例接下来是稀疏向量。这里我演示一个基于 BM25 风格特征的场景。你可以用文本分词工具先把文本变成稀疏字典格式是{token_id: weight}的映射。from pymilvus import ( FieldSchema, CollectionSchema, DataType, Collection ) doc_id FieldSchema( namedoc_id, dtypeDataType.INT64, is_primaryTrue ) doc_text FieldSchema( namedoc_text, dtypeDataType.VARCHAR, max_length1000 ) sparse_vector FieldSchema( namesparse_embedding, dtypeDataType.SPARSE_FLOAT_VECTOR ) schema CollectionSchema( fields[doc_id, doc_text, sparse_vector], descriptionText retrieval with sparse vectors ) collection Collection( nametext_sparse, schemaschema )稀疏向量字段不需要指定 dim因为它的维度由实际数据的最大 token id 决定。这点和密集向量很不一样很多人刚开始会纠结不填维度会不会报错放心不会。稀疏向量的索引类型是SPARSE_INVERTED_INDEX度量方式通常用IP因为稀疏向量经常是由词频或模型权重组成内积能直接反映共同词项的得分。sparse_index_params { index_type: SPARSE_INVERTED_INDEX, metric_type: IP } collection.create_index( field_namesparse_embedding, index_paramssparse_index_params )写入数据时稀疏向量要传稠密的字典结构而不是列表data [ { doc_id: 1, doc_text: Milvus is a vector database, sparse_embedding: {0: 0.5, 10: 1.2, 35: 0.8} }, { doc_id: 2, doc_text: Vector databases are powerful, sparse_embedding: {10: 0.6, 20: 0.9, 35: 1.1, 40: 0.3} } ] collection.insert(data)3.4 二进制向量 Collection 示例二进制向量的 Schema 设计和密集向量很像只是dtype换成BINARY_VECTOR而且维度必须是 8 的倍数。image_hash FieldSchema( nameimage_hash, dtypeDataType.BINARY_VECTOR, dim256 ) hash_collection Collection( nameimage_dedup, schemaCollectionSchema( fields[ FieldSchema(nameimage_id, dtypeDataType.INT64, is_primaryTrue), image_hash ] ) )写入二进制向量时Milvus 接收的是字节串不是 0/1 列表。我这里用bytes对象来表示 256 位的哈希值# 假设用感知哈希算出 256 位每 8 位转成一个字节 hash_bytes bytes([0b10110110, 0b11001011, ...]) # 总共 32 字节 row { image_id: 1, image_hash: hash_bytes } hash_collection.insert([row])注意当二进制向量数据量大时如果把 0/1 的 int 列表直接传进去性能会非常差。正确做法是先从感知哈希库拿到字符串或 int 列表再按字节打包。3.5 混合向量的一体化设计真实系统里三种向量往往同时存在。我以一套合同文档检索系统为例既要语义召回又要匹配合同编号这类精确词还要快速过滤重复合同。这种情况下我会建一个 Collection里面包含多个向量字段hybrid_schema CollectionSchema( fields[ FieldSchema(namecontract_id, dtypeDataType.INT64, is_primaryTrue), FieldSchema(namecontract_no, dtypeDataType.VARCHAR, max_length100), FieldSchema(namedense_embedding, dtypeDataType.FLOAT_VECTOR, dim768), FieldSchema(namesparse_embedding, dtypeDataType.SPARSE_FLOAT_VECTOR), FieldSchema(namebinary_signature, dtypeDataType.BINARY_VECTOR, dim128) ] )这样一个 Collection 里的每一条记录可以同时拥有语义向量、关键词稀疏向量和去重签名。检索时你可以分别创建索引然后针对不同召回策略做独立查询也可以把多个字段的相似度分数加权合并。多向量字段的代价是内存上升、写入变慢。尤其是 dense 字段768 维已经不小。所以混合设计一定要结合自己的数据量别为了全面把不需要的字段也加上。4. 相似度计算余弦值、内积、H

相关新闻

用UDP协议在局域网实现远程关机与音量控制(可后台运行)

用UDP协议在局域网实现远程关机与音量控制(可后台运行)

简介:压缩包内含一套基于UDP协议的局域网远程控制工具,适用于需要在办公网络或家庭网络中集中管理多台电脑、快速执行远程关机、重启以及音量调节的网管人员与技术爱好者。包内共3个文件,以可执行的exe程序为核心,配合xml配置文件…

2026/10/10 11:23:42 阅读更多 →
数据库实战避坑:死锁、连接池与数据同步的排查手册

数据库实战避坑:死锁、连接池与数据同步的排查手册

做了这么多年后端,我越来越觉得,数据库领域真正让人头疼的,不是那种需要啃论文才能搞懂的高深原理,反而是那些天天冒出来的“小问题”:一张表的连接数悄悄打满、一条SQL突然不走索引、一个ALTER TABLE把整个业务卡了十…

2026/10/10 11:14:22 阅读更多 →
VMD分解+小波阈值滤波:信号去噪与故障特征提取完整流程

VMD分解+小波阈值滤波:信号去噪与故障特征提取完整流程

做故障诊断这些年,我最常被问到的问题不是"算法怎么写",而是"信号这么脏,怎么处理才能看到特征"。传感器采回来的振动信号、电流信号,噪声大、干扰多,直接丢进FFT就是一片糊,特征频率完…

2026/10/10 2:39:06 阅读更多 →

最新新闻

Jev设计哲学实践:类型安全、概率校准与代码掌舵

Jev设计哲学实践:类型安全、概率校准与代码掌舵

1. 从一个“反直觉”的设计选择说起第一次接触 Jev 这套设计思路的时候,我其实是有点抗拒的。原因很简单——它把“概率”这件事摆到了台面上,而且要求开发者主动去“校准”它。这跟我们过去十几年写业务代码的习惯完全相反。以前我们写代码,…

2026/10/10 21:20:07 阅读更多 →
分布式任务调度核心原理与实战:从定时任务到分片、幂等与选型

分布式任务调度核心原理与实战:从定时任务到分片、幂等与选型

1. 从单机定时任务说起:为什么需要分布式任务调度1.1 你曾经写过的那些定时任务很多人的分布式任务调度之路,都是从一段简单的cron表达式开始的。我自己刚工作那会儿,项目里最常见的就是 SpringScheduled注解,或者干脆在服务器上挂…

2026/10/10 21:20:07 阅读更多 →
「比 Codex 省 40% token」刷屏 GitHub:Unreal Agent 性能零损耗是真香还是 PPT?

「比 Codex 省 40% token」刷屏 GitHub:Unreal Agent 性能零损耗是真香还是 PPT?

「比 Codex 省 40% token」刷屏 GitHub:Unreal Agent 性能零损耗是真香还是 PPT? 【免费下载链接】unreal-agent Async-first agent harness 项目地址: https://gitcode.com/gh_mirrors/un/unreal-agent 九月底,一个名为 Unreal Agent…

2026/10/10 21:20:07 阅读更多 →
uni-app x `uni.getAppBaseInfo` 应用基本信息获取指南:API 用法、多端字段与源码实现解析

uni-app x `uni.getAppBaseInfo` 应用基本信息获取指南:API 用法、多端字段与源码实现解析

示例工程前端移动开发跨平台 【免费下载链接】uni-app A cross-platform framework using Vue.js 项目地址: https://gitcode.com/gh_mirrors/un/uni-app 点击查看 免费下载 uni.getAppBaseInfo 是 uni-app x(以及 uni-app)中用于获取应用基…

2026/10/10 21:20:07 阅读更多 →
端侧小模型双子星选型指南:星火 X2.5 的 1.7B 与 4B,该带哪个上生产

端侧小模型双子星选型指南:星火 X2.5 的 1.7B 与 4B,该带哪个上生产

端侧小模型双子星选型指南:星火 X2.5 的 1.7B 与 4B,该带哪个上生产 【免费下载链接】Spark-X2.5-4B Spark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智…

2026/10/10 21:20:07 阅读更多 →
647回文子串与516最长回文子序列:区间DP两种典型玩法全解析

647回文子串与516最长回文子序列:区间DP两种典型玩法全解析

各位打卡代码随想录的伙计们,第四十五天来了。今天这两道题——647 回文子串、516 最长回文子序列——看起来名字只差两个字,实际上一个是把字符串切成一段段判断"是不是回文",另一个是允许跳跃地凑出"最长回文有多长"。…

2026/10/10 21:19:06 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →