☰
IVF-PQ 快了多少之前,先看少找回了谁
2026/10/25 20:58:40 网站建设 项目流程

向量搜索少算了距离,延迟可能下降;但对检索增强应用来说,漏掉证据也会改变回答。我不想再只画一张“比全量扫描快”的图,然后跳过正确性。

Faiss索引说明列出Flat、IVF、PQ等索引及存储方式,页面编辑日期为2025-07-28,不是今年十月新发布。它适合用来理解结构,不能替代当前版本、当前数据上的测量。本轮于2026-10-23重新阅读。

两处近似要分开看

IVF先把向量分到倒排列表,查询只探测一部分列表。最近邻如果位于未探测列表,后面的距离计算再精确也找不回来。PQ又对向量表示作压缩,候选中的排序也可能改变。因此“漏桶”和“桶内距离误差”要分别分析。

nprobe增加通常意味着检查更多列表,但并不保证每次查询延迟单调,也不能消除PQ的全部排序误差。相似度口径更不能混用:未归一化的内积和余弦相似度不是一回事。

先写一个召回验收

下面只用一维点做可手算的反例,不实现IVF-PQ。全量精确结果作为ground truth,模拟候选列表故意漏掉最近的id=1,再算recall@2。

use std::collections::BTreeSet; fn top(query: f32, points: &[(u32, f32)], k: usize) -> Vec<u32> { let mut scored: Vec<_> = points.iter() .map(|&(id,x)| { assert!(x.is_finite()); (id,(x-query).powi(2)) }).collect(); scored.sort_by(|a,b| a.1.total_cmp(&b.1).then(a.0.cmp(&b.0))); scored.into_iter().take(k).map(|p|p.0).collect() } fn recall(truth: &[u32], found: &[u32]) -> f64 { assert!(!truth.is_empty()); let t: BTreeSet<_> = truth.iter().collect(); let f: BTreeSet<_> = found.iter().collect(); t.intersection(&f).count() as f64 / t.len() as f64 } fn main() { let all = [(1,0.1),(2,0.2),(3,2.0),(4,3.0)]; let truth = top(0.0,&all,2); let selected = [all[1],all[2],all[3]]; assert_eq!(truth, vec![1,2]); assert_eq!(recall(&truth,&top(0.0,&selected,2)),0.5); assert_eq!(recall(&truth,&truth),1.0); assert_eq!(recall(&truth,&[2,2]),0.5); println!("recall checks passed; constructed recall@2=0.5"); }

rustc 1.98.0运行通过,构造例的recall@2为0.5。重复返回同一id不会多算一次命中。内图演示的是另一个recall@1漏检场景,两者都是教学示意,不是Faiss测评。

真正比较时的表格

我会固定训练集、查询集、索引训练样本与随机种子;Flat参考索引和近似索引采用同一距离定义。先记recall@k及其查询分布,再记延迟分位数、构建时间和实际内存。训练样本不应该偷偷包含专门为提高分数挑出的测试查询。

仅给出平均召回会藏住长尾失败。对RAG,可以额外挑一批需要准确法规条款、版本号或函数签名的查询,查看漏检是否造成证据缺失。但这属于应用层实验,不能从向量召回数字直接推导模型答案正确率。

本轮没有安装Faiss、训练PQ或运行真实向量库;已验证的是评估函数和一个反例。等数据与版本固定后才讨论速度。如果召回下降无法接受,先调整探测范围或索引设计,而不是把它包装成“免费加速”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询