Milvus 避坑攻略
Milvus 是一款开源的、针对海量特征向量的相似性搜索引擎。自从 Milvus 开源以来,受到了业界朋友的广泛关注,还有一些朋友亲自体验了 Milvus 做向量检索。我们也收到了很多用户的反馈,其中相当一部分是在使用 Milvus 中踩到的坑。为了给更多的用户排雷,我们特地总结了 Milvus 使用过程中比较容易踩到的一些坑。
数据篇
数据是测试的主体,数据处理得好,坑就踩得少。
- 只有当数据做过归一化,Metric_type 的 L2 和 IP 计算的向量相似度结果才会等价。
细心的 Milvus 用户会发现,Milvus 的配置文件里面定义了两种计算向量相似度的方法,L2(欧氏距离)和 IP(点积)。当把数据导入 Milvus 之后,分别使用 L2 和 IP 的方法进行查询,两者查询出来的结果竟然不一致!
这是操作的不对还是 Milvus 自身出了问题?都不是,是数据出了问题!可以通过数学推理证明,只有当数据做过归一化,L2 和 IP 计算的向量相似度结果才会等价,具体推理过程可以参考:数据归一化。所以,当数据没有做归一化,使用 L2 和 IP 计算的向量相似度结果不一致也就不足为奇了。推荐使用 Milvus 做向量检索时,提前对数据做归一化,这样你就可以自由地选择 L2 和 IP 这两种计算向量相似度的方法了。
- 数据目录映射之前,应该提前计算需要的存储空间,保证磁盘空间充足。
Milvus 启动时要指定一个数据存储目录,即 db 目录。当你启动好 Milvus 的数据导入程序,出去喝完一杯咖啡,打算验收导入成果时,只见电脑屏幕上赫然显示着 no space left on device
一行小字。恍然意识到磁盘空间已经用尽,前面的工作功亏一篑。
为了避免踩进上面的坑里,建议每次在导入数据之前都提前计算一下需要占用的磁盘空间。以1亿条 512 维的单精度向量为例,如果对其建立 FLAT 或者 IVFLAT 索引,那么导入 Milvus 之后占用的存储空间大小为:
4B x 512 x 100,000,000 = 200GB
如果采用 IVF_SQ8 索引,则导入 Milvus 占用的存储空间为 FLAT 或 IVFLAT 的四分之一,即 50GB。
- Milvus 只能处理浮点数据,如果是整型,需要转换成浮点型。
raise ParamError('Vectors should be 2-dim array!')
对于经常使用 Milvus 的朋友来说,上面这个错误应该非常熟悉,导入 Milvus 的数据应该是形如 [[],[]...[]] 的二维列表,如果数据不是二维列表,就会报上面这个错误。但有时候,导入的数据已经是二维列表了,仍然会报上面的错误,很多朋友对此非常困惑。经过检查发现,导入的数据都是整型,如果将这些整型数据转换成浮点型,数据就可以正常导入了。所以,下次遇见这个错误,不妨先检查一下你的数据格式是否为浮点型。
配置篇
统计表明,踩坑的重灾区是在 Milvus 的配置上,深入理解配置参数的含义是避坑的不二法门。
- index_building_threshold,默认值为1024MB,导入数据量小于该值不会触发索引建立。
一位使用 Milvus 的朋友曾经给我描述了一个十分奇怪的现象。他利用 Milvus 的 IVFLAT 索引去搜索 40 万条 512 维的单精度向量时,花了500 ms。但是,当他利用同样的配置去搜索 60 万条 512 维的单精度向量时,仅花了 200 ms!为什么数据量越大,查询的时间反而越短?
我们从数据到配置再到启动命令依次检查了一遍,最终把问题锁定在了 index_building_threshold
这个配置参数上面,这位朋友在配置文件里面将这个参数设置的是默认值,1024MB。然后计算数据文件的大小发现,40 万条 512 维的单精度向量为 800 MB,小于触发索引的阈值;60 万条 512 维的单精度的向量为 1200 MB,大于触发索引的阈值。所以真相瞬间明了了,这位朋友在搜索 40 万向量的时候数据没有建索引,在搜索 60 万向量的时候数据建立索引了的。有索引的情况下,搜索 60 万向量比搜索 40 万向量快也就很正常了。
为了防止再次掉进这类怪坑,推荐大家每次导完数据之后利用 sqlite 查看一下元数据,检查一下索引是否已经全部建立完成。如果还没有全部建好索引,可以利用指令手动建立,具体操作方法可以参考 Milvus Bootcamp 的数据导入部分。
注意 :自 Milvus 0.4.0 以后,index_building_threshold
更名为 index_file_size
并在创建表时作为参数指定。
- cpu_cache_capacity,默认值为16GB,应保证该值大于导入数据的容量。
性能的坑也是被经常踩到。当用 Milvus 搜索多次,性能仍然差强人意的时候,不妨看一下配置文件里的 cpu_cache_capacity 参数。正常情况下,Milvus 做查询会将所有数据从磁盘读到内存,而配置文件里的 cpu_cache_capacity 代表系统允许 Milvus 使用的最大内存。只有当数据全部加载到内存之后,才能发挥出 Milvus 的最佳查询性能。cpu_cache_capacity 是性能测试时最应该关注的参数之一。
- nprobe,默认值为10,如果需要更高的查询准确率,可以将其设置得更大,取值范围为 1~ nlist。
前面讲过性能的坑,现在再说一个准确率的坑。当用 Milvus 搜索数次,准群率依旧不忍直视,那就应该检查配置文件里的 nprobe 参数。nprobe 代表查询所涉及的向量类的个数,数值越大,精度越高,但查询速度更慢。所以在 nprobe 值的设置上,应该权衡性能和准确率这两个指标。测试 sift1B 数据集发现,当 nprobe 的值设为 32 时,查询的准确率可以达到 90% 以上。
注意: 自 Milvus 0.4.0 以后,nprobe 作为一个外部参数在查询时指定。
- 修改完任何一个配置文件里的参数,都要重启 docker 才能使其生效。
很多 Milvus 用户都知道测试出现问题时去修改配置文件,但是修改完配置文件之后并没有达到自己预期的测试效果。一番折腾后重启了 Milvus docker,测试效果立马符合预期。下次改完任何一个配置文件里的参数,别再忘记敲一个docker restart <container ID>
命令了。
番外篇
除了数据和配置这两个大坑外,其他的一些小水洼偶尔也会被踩到。
- 客户端 pymilvus 与 Milvus server 的版本对应才能正常使用 Milvus。
有时会听到一些朋友踩进 pymilvus 的坑。他们按照用户手册安装完 Milvus 和 pymilvus,但是利用 pymilvus 对 Milvus 进行各类操作时,总是会报出一些奇怪的与 pymilvus 有关的错误。经过悉心排查,原来是 pymilvus 与 Milvus 的版本不匹配,真是万万没想到…… 所以下次使用 pymilvus 的时候记得查看 Milvus 与 pymilvus 的版本对应关系,可以参考 pymilvus介绍。
- Milvus 使用CPU类型索引进行查询时会把所有CPU资源都占用掉,查询时需要确保服务器上没有运行其他高负载应用。
还有时候,当你踩进性能的坑里并不是参数配置的问题,而是运行 Milvus 的服务器上还有其他高负载应用。Milvus 在进行向量搜索时会将服务器上的所有CPU资源占用掉,为了让 Milvus 的性能达到极致,在使用 Milvus 的时候需要确保服务器上没有其他高负载应用与 Milvus 争夺资源。
- Milvus docker 启动之后,利用 docker logs 查看 server 是否启动成功。
在 Milvus docker 启动的时候,有一条指令可以帮助你检查 Milvus 的启动状态,避免掉进怪坑。
$ docker logs <container ID>
执行完上述指令,如果你能看到 “Milvus server start successfully.” 的字样,那就说明你的 Milvus Server 已经正确启动,可以尽情使用 Milvus 了。