08_为什么3563条向量我用暴力全量而不是ANN索引
为什么 3563 条向量我用暴力全量而不是 ANN 索引几乎每篇 RAG 教程的默认第一步都是上向量数据库、建 HNSW 索引。我把这件事当成了必须做的一步直到我停下来实测——然后决定不上索引。一、三种索引实测对比3563 条 × 1024 维同一批查询三种方案方案延迟召回率暴力全量0.24 ms100%IVFnprobe10.12 ms0.657Milvus Lite HNSW291 ms—结果很反直觉唯一比暴力快的是 IVF快了一半0.12ms但召回掉到 0.657丢了三分之一的正确答案号称最先进的 HNSW在 3563 这个规模上反而要 291 ms比暴力慢一千多倍——因为建索引、加载索引的开销在数据量不够大时远超少算几次内积省下的时间。索引在这个规模上是负优化。它没有让检索更快反而更慢、更不准。二、索引是规模工具不是质量工具关键认知就一句话ANN 索引解决的是规模大了算不过来不是检索质量不够好。我外推了一下规模曲线暴力全量约0.04 ms / 千条。数据量暴力耗时要不要上索引3563 条0.24 ms不上10 万条6.7 ms可上可不上100 万条67 ms该上了所以正确的决策顺序是先量自己的规模再决定上不上索引。我 3563 条暴力 0.24ms 远低于任何 LLM 调用的几百毫秒到几秒——检索根本不是瓶颈生成才是。在检索只占整个链路千分之一时间的时候去优化检索是典型的优化错了环节。别为了显得高级提前上索引那是负优化。三、比索引更值钱的是 small-to-big真正影响答得对不对的不是索引结构是分块结构。我用的是 small-to-big 父子块块大小用途子块≤ 1000 字进向量库负责找得准父块超大块原文检索命中后回查负责答得全为什么要拆开因为检索和生成对块的诉求是冲突的检索要小块——块越小命中的那一块噪声越少排名越准生成要大块——块太小模型读到的是一段被切断的话上下文不全。small-to-big 让两者各取所需检索用 137 个父块对应的 1426 个子块命中后把整个父块原文喂给模型。这比换任何索引结构对最终答案的影响都大。四、这一路的四个坑上索引这件事我没做成但踩的坑一个不少都记着IVF 的self._mat没赋值——__init__里写成了局部变量search里hasattr恒为假结果每次查询现场重建候选矩阵加速比算成 0.3x整张对比表作废。自采样查询不加噪声——拿块自己的向量当查询IVF 召回虚高到近 1.0。不加perturb()的扫描结果全是自欺。Milvus 注册表是进程级的——换 db 文件没用同一个进程里第二次建同名 collection 报 already exists索引同理。建 HNSW 的顺序有坑——create_collection自带默认索引直接create_index报已存在已加载的不能drop_index。正确顺序是load → release → drop → create → load。这些坑的共同点是每条都不是索引原理问题是真跑一遍才会暴露的工程细节。这也是为什么我坚持看文档不算产出跑通才算。小结结论数字 / 事实3563 × 1024 暴力检索0.24 ms召回 100%IVFnprobe10.12 ms但召回掉到 0.657Milvus HNSW291 ms比暴力慢千倍规模曲线0.04 ms/千条 → 100 万条才 67 ms什么时候上索引数据量大到算不过来才上不是质量不够上比索引更值钱small-to-big 父子块137 父 / 1426 子上一篇Embedding 三个后端怎么选。下一篇混合检索为什么用 RRF 而不是加权分数。