城市图书馆的周师傅遇到了职业生涯最大的难题:馆里藏书涨到了十亿册,可经费却被砍了大半。过去,图书馆把每本书的“完整摘要”和“推荐卡网络”(哪些书相似、哪些书值得互相推荐)全部摆在阅览室里——读者进门翻一翻就能找到书,这就是 HNSW 的做法,找书飞快。但十亿册书的推荐卡和摘要加起来要占上千 GB 的阅览室,而“阅览室”(内存)的价格是“地下仓库”(SSD 磁盘)的十倍以上,根本买不起。 DiskANN 的解法朴素而巧妙:把推荐卡网络连同书本身全部搬进地下大仓库(SSD),阅览室里只留一个“迷你索引卡盒”——每本书用几十个比特的缩写代码记录内容要点(这叫乘积量化压缩)。找书时,周师傅先翻索引卡锁定大致方向,再去仓库按推荐卡走几步,最后把几本候选书抱回阅览室精读。虽然每次坐仓库电梯要花点时间,但他每次多带几张卡出来(预取),几十趟就能摸到目标。这篇文章就用“小目录 + 大仓库”的比喻,带你读懂 DiskANN 的原理、Vamana 图、乘积量化压缩、搜索流程与工程选型。
掌握 DiskANN 的“图放磁盘 + PQ 码进内存”思路,是解决十亿级向量检索内存瓶颈的硬技能,直接决定搜索、推荐、RAG 系统的召回架构选型与成本控制。
可视化演示
模拟Vamana有向图的贪婪路由搜索、SSD节点对齐存储与内存缓存预取策略,展示DiskANN在内存受限场景的检索过程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
模式1:Vamana图贪婪路由 青色节点为已访问路径 橙色为目标查询点 next = argmin dist(q, neighbors) 长程边加速远距离跳转。
一、内存受限困境
周师傅的图书馆正在经历一场“书灾”:总馆藏书达到十亿册,而阅览室却因经费削减缩水到原来的十分之一。最让他头疼的不是书多,而是“书架”贵——服务器内存每 GB 的价格是 SSD 磁盘的十倍以上,想靠加内存解决问题,预算是天文数字。他站在空荡荡的阅览室中央,看着堆不下的搬运箱,第一次认真考虑:是不是不该让所有书都待在阅览室里?
在向量检索的世界里,HNSW 就是那个“把什么都放阅览室”的激进方案。它既要存每条商品、文档的 embedding 向量(一串描述内容特征的数字),又要存一张“推荐卡网络”(图的邻接关系),全部常驻内存。十亿条 128 维 float32 向量本身就是 512GB,再算上图结构的邻居指针,轻松逼近 1TB。一台 1TB 内存的服务器贵得离谱,于是团队只能被迫降维、降采样、或者拆成几十台机器分布式部署,成本和复杂度双双飙升。
HNSW 的内存账可以精确算出来:向量本体占大头,图的邻接表占小头。
周师傅看着这个数字直摇头:与其花十倍价钱买书架,不如让大部分书在地下仓库睡觉,阅览室只留一张“迷你索引卡”。这个朴素的念头,正是 DiskANN 的出发点——内存放不下,不是问题,问题是你非要把一切都塞进内存。
- 向量本体每条向量有 d 个维度,每个 float32 数值占 4 字节,N 条向量合计占用的内存。
- 图邻接开销HNSW 里每个节点平均连向 R 个邻居,每条邻居关系用 8 字节的指针存储。
- 两项相加把向量本体和邻接表相加,就是 HNSW 的全部常驻内存。
二、DiskANN设计
周师傅的新方案很快就定了:把整套推荐卡网络和全部藏书搬进地下大仓库(SSD),阅览室只留一个比鞋盒还小的“迷你索引卡盒”。他算过一笔账:NVMe SSD 的随机读延迟只有几十微秒,坐仓库电梯一趟虽说不便宜,但整个找书流程只需要坐几十趟,加起来也就几毫秒——完全在读者可接受的范围内。
DiskANN 的工程师们做了和三步走完全一样的决策:第一,图结构和完整向量放到 SSD 上,利用 SSD 优秀的随机读性能;第二,内存里只保留乘积量化(PQ)压缩后的短码,每条向量从 512 字节缩到 8 字节左右;第三,查询到达时,先在内存里用 PQ 码算出粗略距离,找到起点,再去磁盘上沿着图走,最后把候选的完整向量拉回内存做精确重排序。三步环环相扣,缺一不可。
内存账本一下子清爽了:
周师傅的第一版方案顺利上线:读者借书的时间从原来的“书架前徘徊”变成了“翻卡、下楼、走几步、抱书回来”。读者没觉得变慢,财务部却乐开了花——省下的钱够买三倍容量的 SSD。这个“小目录 + 大仓库”的架构,就是 DiskANN 能在十亿级数据上落地的根本原因。
- 单条 PQ 码的位宽一条向量被切成 m 段,每段用 k 个质心之一表示,每段需要 log₂k 个比特。
- 换算成字节N 条向量的码加起来,除以 8 从比特换算成字节。
- 与 HNSW 对比以 128 维、m=16、k=256 为例,单条码只有 16×8=128 比特,是原向量 512 字节的 1/32,再叠加省掉的图结构,总量约为 HNSW 的 1/10~1/20。
三、Vamana图结构
仓库里的推荐卡怎么贴,是周师傅最花心思的地方。他起初想照搬 HNSW 的“分层书架”:顶层放最热门的书,往下逐层细分。但在仓库里,层与层之间要坐不同的电梯,每换一层都是一次昂贵的磁盘读。周师傅决定反其道而行:仓库里只建一层书架,但每本书的推荐卡贴得又密又精——这一层的图结构就是 DiskANN 的 Vamana 图。
Vamana 图是一个单层有向图,每个节点有固定的出度 R(典型值 32~64),比 HNSW 每层的邻居数大得多。构建时先通过随机投影或聚类初始化一个粗略的图骨架,然后逐点插入:用当前的图找到近似最近邻作为候选,再对候选做“多样性剪枝”。剪枝的规则很讲究——与其把所有近邻都连上,不如只保留那些“方向不同”的邻居,让推荐卡覆盖不同的知识区域,避免邻居扎堆。
与 HNSW 相比,Vamana 放弃了多层跳跃,用单层大度数换来了两个关键收益:图直径更小——从任意点出发到达目标所需的跳数更少;磁盘友好——每一跳都只是顺序地读一个节点的邻居列表,没有层级间的跳转开销。周师傅的体会是:在仓库里少坐电梯,比书架分得多精美更重要。Vamana 正是“用更大的单层图,换更短的路程”。
- 候选与中心的距离先算新候选 yⱼ 与中心节点 x 的距离,这是它作为邻居的基本资格。
- 与已选邻居逐一比较把 yⱼ 与集合 Sₓ 中每个已保留邻居 xᵢ 的距离都算出来。
- 全称条件判定只有当 yⱼ “比谁都离中心更近”时才保留它,从而保证邻居在方向上彼此分散、覆盖更广。
四、乘积量化压缩
迷你索引卡到底怎么记录一本书的内容?周师傅发明了一套“缩写编码”:把一本书的 128 个特征数值切成 4 段,每段 32 个数;对每一段,从 16 种“典型片段”里挑最接近的一种,记下它的编号。这样一来,一本书的摘要就变成了 4 个编号,每个编号 4 个比特,总共 16 比特——而原来的完整摘要要 512 字节。这个把向量切成小段、逐段量化压缩的方法,就是乘积量化(Product Quantization,PQ)。
PQ 的码本(codebook)怎么来?靠聚类。对每一段子向量,用 k-means 聚出 k 个质心作为“典型片段”,所有向量在某一维段上都用最近的质心编号表示。查询时,DiskANN 会预计算“查询向量 vs 每个质心”的距离查找表(LUT),然后把码字对应的距离累加,得到近似的向量距离。整个过程全是查表和小整数加法,快得惊人。
压缩比可以精确表达:
周师傅很清楚:索引卡上的缩写只够“猜方向”,不够“定结果”。所以 DiskANN 的搜索严格分成两段——先用 PQ 码在内存里快速粗筛、导航,最后一定把候选的完整向量从磁盘拉回来做精确距离计算。压缩负责省内存,重排序负责保精度,两人各司其职。索引卡永远只是地图,真正的答案永远在书里。
- 原向量比特数每条向量 d 个 float32 数值,每个数 32 比特。
- 压缩后比特数切成 m 段,每段用 log₂k 个比特的质心编号表示。
- 两者相除得压缩比比值就是信息量的缩减倍数,也是内存和带宽节省的倍数。
五、搜索性能优化
周师傅的日常工作形成了一套固定流程:先翻迷你索引卡,找出与读者需求最像的几本书作为起点;然后攥着一张“推荐卡清单”坐电梯下仓库,每到一个书架就取出推荐卡,看看它指向谁;他每次最多同时跟踪 L 个候选(这叫波束宽度),一批批地扩展;最后把搜集到的候选书抱回阅览室,对照完整摘要精读,挑出最合适的几本。整个过程,就是 DiskANN 的波束搜索(Beam Search)。
要让这个流程跑得快,有三个关键手段。第一是热点缓存:仓库里最常被翻的书架,周师傅直接把它们的推荐卡复印件揣在口袋里,内存里缓存热点节点,能省掉大量重复的磁盘读。第二是批量预取:每次下仓库不单独取一张卡,而是一次性把当前所有候选的邻居列表都读出来,把几十次随机小 IO 合并成几次大 IO,充分利用 SSD 的并发能力。第三是控制跳数:Vamana 图保证了从起点到目标通常只需要 40~80 跳。
一次查询的整体延迟可以建模为:
周师傅倚在仓库门口总结道:所谓性能优化,无非是“少跑路、一次多带、该记的记在兜里”。缓存让热门书架不再反复折腾电梯,预取让每一趟电梯都满载,而 Vamana 图保证了路本身不远。三者合起来,让“坐仓库电梯的图书馆”在读者体验上几乎追平了“全在阅览室的图书馆”。
- 单跳的 IO 代价每读一个节点的邻居列表,就是一次磁盘随机读,花费 T_io。
- 全路程的总 IO 时间h 跳的路程乘以单跳时间,是延迟的绝对大头。
- 加上内存计算时间PQ 距离查表和候选维护只占零点几毫秒,但必须计入总账。
六、工程实践要点
把方案从纸面变成现实,周师傅踩了不少坑。首先是新书上架:十万册新书到馆,如果逐本搬入并现场贴推荐卡,构建过程会阻塞读者借书。他的做法是“深夜赶工”——先在离线集群上把新书和新旧推荐关系批量算好,生成增量索引文件,再在低峰期热切换上线。这就是 DiskANN 的批量构建:充分并行、分片处理,构建期间在线查询完全不受影响。
日常运营中,增量更新是另一门手艺。每本新书到馆,先算它的 PQ 码放进索引卡盒,再在图上找到它的近邻、连上新边,同时按出度 R 做裁剪;下架的书不直接删除,而是打上“失效标记”(tombstone),等积累到一定量再触发一次局部重建。参数调优更是周师傅的必修课:出度 R 越大图越“密”、跳数越少但构建越贵;波束宽度 L 越大搜索越稳但每次扩展的开销越高;PQ 的段数 m 越大距离越精确但码更长。
上线效果如何,要用一把客观的尺子来量:
周师傅把图书馆的运营心得记在小本上:构建要离线并行、更新要增量轻量、上线要指标说话。磁盘上的图可以慢慢长,但每一步都要可测量、可回滚。正是这套“白天服务读者、夜里悄悄整架”的工程节奏,让 DiskANN 在真实生产环境里既稳又省。
- 真值集合用暴力全量计算得到的、与查询 q 真正最相似的 K 个点的集合。
- 检索结果集合DiskANN 实际返回的 K 个候选点的集合。
- 交集与比值两个集合重合的元素个数除以 K,就是召回率,取值 0 到 1。
七、算法对比选型
不同的图书馆有不同的活法。社区小图书馆藏书几百万册,全部放在阅览室(HNSW),读者进门三秒钟就能找到书,但规模天花板明显;中型档案馆用卡片目录柜(IVF-PQ),先按大类粗筛再在类内细找,内存适中、规模中等;而国家总馆这样十亿册的庞然大物,只能走 DiskANN 的“小目录 + 大仓库”路线。周师傅的经验是:没有最好的算法,只有最合适的规模。
三者的适用边界其实很清晰。HNSW 面向千万级以内、要求亚毫秒延迟、内存预算充足的场景,比如在线广告的实时召回;IVF-PQ 面向千万到亿级、内存中度紧张的场景,用倒排粗分桶 + PQ 压缩做折中;DiskANN 面向亿到十亿级、内存预算紧绷的场景,接受 1~10 毫秒延迟换取极低的内存成本和庞大的容量。选型的核心判据是一道算术题:
周师傅最后在馆务会上说了一句话,成了图书馆界的名言:“先算账,再选型。”量级、内存、延迟预算三笔账算清楚,方案自然浮出水面。DiskANN 不是用来取代谁的银弹,而是在“内存装不下”这个现实约束下,用 SSD 的容量和毫秒级延迟换回十亿级检索能力的正确答案。
- 全内存方案的内存需求把向量和邻接表都放进内存所需的字节数,是选型的第一道门槛。
- 可用资源约束单机或集群实际能提供的内存上限,是硬约束。
- 比较决策M_need 显著大于 M_avail 时选择 DiskANN;放得下且要求极致低延迟则选 HNSW;中等规模预算紧可选 IVF-PQ。
AI 应用场景
场景一:电商平台的十亿级商品向量召回
阶段:在线召回(海量候选生成)
解决的问题:电商平台将商品图文特征编码成 embedding 向量,商品量达到十亿级。传统 HNSW 方案需要约 1TB 内存常驻,服务器成本极高;降采样又牺牲召回精度。DiskANN 把图和向量放在 SSD,内存只保留 PQ 码,将单机内存需求压到 100GB 以内,让十亿级召回从“买不起”变成“单机可跑”。
具体案例:某头部电商的“相似商品”功能,接入 DiskANN 后召回延迟稳定在 3~8ms,p99 控制在 15ms 以内,内存占用仅为原 HNSW 方案的 1/12,硬件成本下降约 70%。遇到大促新增海量商品时,利用增量化更新在低峰期平滑上线,无需全量重建。
场景二:短视频推荐的增量候选池
阶段:召回池构建与更新
解决的问题:短视频平台每天新增数千万条新视频,如果全量重建 HNSW 索引,计算和 IO 开销巨大。DiskANN 支持轻量增量插入:新视频算好 PQ 码插入索引卡盒,并在 Vamana 图上就近连边,分钟级即可对全网可见。这让“新内容秒级进入召回池”成为可能,显著提升推荐的新鲜度。
具体案例:某短视频平台的“你可能喜欢”召回链路,使用 DiskANN 管理 50 亿条视频向量,配合热点缓存策略,热门视频的命中缓存率超过 60%,整体 QPS 提升 3 倍,新视频从发布到可被召回的时间从小时级缩短到分钟级。
场景三:私有化部署的亿级向量数据库
阶段:离线建库 + 在线查询
解决的问题:银行、政务等场景要求数据不出内网,服务器配置受限,无法提供超大内存。DiskANN 让亿级向量库跑在普通配置的单机上:SSD 存全量,内存只放压缩码,既满足安全合规,又把硬件门槛降到普通机架服务器即可承载。
具体案例:某金融机构的合同语义检索系统,1.2 亿条合同片段向量部署在单台 64GB 内存 + 2TB NVMe SSD 的服务器上,支持内部员工语义检索,平均查询延迟 6ms,召回率保持在 95% 以上,通过了等保合规审计。
Python 演示
import numpy as np class ProductQuantizer: """Product quantization: compress vectors into short codes.""" def __init__(self, M=4, K=16, seed=42): self.M = M self.K = K self.seed = seed self.codebooks = [] self.sub_d = 0 def fit(self, X): """Learn codebooks by k-means on each sub-space.""" rng = np.random.RandomState(self.seed) d = X.shape[1] self.sub_d = d // self.M for m in range(self.M): sub = X[:, m * self.sub_d:(m + 1) * self.sub_d] centroids = sub[rng.choice(len(sub), self.K, replace=False)] for _ in range(12): dists = ((sub[:, None, :] - centroids[None, :, :]) ** 2).sum(-1) labels = dists.argmin(1) for k in range(self.K): if (labels == k).any(): centroids[k] = sub[labels == k].mean(0) self.codebooks.append(centroids) def encode(self, X): """Map each vector to its nearest centroid ids (the PQ code).""" codes = [] for m in range(self.M): sub = X[:, m * self.sub_d:(m + 1) * self.sub_d] dists = ((sub[:, None, :] - self.codebooks[m][None, :, :]) ** 2).sum(-1) codes.append(dists.argmin(1)) return np.stack(codes, axis=1) def pq_dist(self, codes, q): """Approximate distance via lookup tables of centroid distances.""" total = np.zeros(codes.shape[0], dtype=np.float32) for m in range(self.M): sub = q[m * self.sub_d:(m + 1) * self.sub_d] lut = ((self.codebooks[m] - sub) ** 2).sum(1) total += lut[codes[:, m]] return total class MiniDiskANN: """Simplified DiskANN: k-NN graph on 'disk' + PQ codes in memory.""" def __init__(self, M=4, K=16, R=6): self.pq = ProductQuantizer(M, K) self.R = R self.points = None self.codes = None self.adj = None def build(self, X): self.points = X self.pq.fit(X) self.codes = self.pq.encode(X) n = len(X) self.adj = [] for i in range(n): dists = ((X - X[i]) ** 2).sum(1) nn = dists.argsort()[1:self.R + 1] self.adj.append(nn.tolist()) def search(self, q, topk=5, beam=6): """Beam search over the graph, then re-rank with full vectors.""" dists = self.pq.pq_dist(self.codes, q) frontier = [int(dists.argmin())] visited = set() for _ in range(8): new = set() for node in frontier: if node in visited: continue visited.add(node) for nb in self.adj[node]: if nb not in visited: new.add(nb) if not new: break cand = list(new) cand_d = self.pq.pq_dist(self.codes[cand], q) order = sorted(zip(cand_d, cand))[:beam] frontier = [c for _, c in order] exact = sorted(visited, key=lambda i: ((self.points[i] - q) ** 2).sum()) return exact[:topk] if __name__ == "__main__": rng = np.random.RandomState(0) X = rng.randn(3000, 32).astype(np.float32) model = MiniDiskANN(M=4, K=16, R=6) model.build(X) q = X[123] hits = model.search(q, topk=5) truth = ((X - q) ** 2).sum(1).argsort()[:5].tolist() overlap = len(set(hits) & set(truth)) print("retrieved:", hits) print("ground truth:", truth) print("recall@5 =", overlap / 5)
labels = dists.argmin(1)每个子向量分配到最近的质心,得到码字编号,这是 PQ 编码的核心一步。self.codebooks.append(centroids)每一段子空间都学出一个码本,码本质量直接决定粗筛精度。lut = ((self.codebooks[m] - sub) ** 2).sum(1)预计算查询向量与所有质心的距离,形成查找表,避免逐点算距离。total += lut[codes[:, m]]通过查表把每段的质心距离累加,得到近似的查询-向量距离,速度远快于完整距离。dists = ((X - X[i]) ** 2).sum(1)构建图时用完整向量计算真实近邻(对应 Vamana 建图时的精确距离),保证图质量。frontier = [int(dists.argmin())]波束搜索的起点是 PQ 距离最小的点,相当于“先翻索引卡找最相似的书”。visited.add(node)维护已访问集合,防止图搜索走回头路,也构成重排序的候选池。cand_d = self.pq.pq_dist(self.codes[cand], q)对每轮新扩展出的候选按 PQ 距离排序,只保留波束宽度内的精英。exact = sorted(visited, key=lambda i: ((self.points[i] - q) ** 2).sum())最后用完整向量对访问过的所有候选精确排序,这是召回率的关键兜底。overlap = len(set(hits) & set(truth))把检索结果与暴力真值求交集,量化召回质量。
常见误区
误区我总觉得“凡是检索必须全放内存才快”,磁盘一参与就是慢的象征,DiskANN 的查询延迟肯定比 HNSW 差一个数量级以上。
正解这个直觉停留在机械硬盘时代。现代 NVMe SSD 的随机读延迟只有 50~100 微秒,而 DiskANN 借助 Vamana 图只需要 40~80 次随机读,总延迟 2~8 毫秒。更重要的是,当数据量到十亿级时,HNSW 要么内存放不下,要么被迫分布式分片,跨机通信的延迟和成本反而更高。DiskANN 用“毫秒级延迟 + 极低内存”换来了单机承载十亿级数据的能力,这不是变慢,而是换了一种更划算的代价结构。
为何常见:人们容易把“内存”和“快”绑定、“磁盘”和“慢”绑定,却忽略了 SSD 随机读性能的飞跃,以及内存放不下时不得不分布式带来的隐性开销。
误区把向量从几百字节压成几个字节,信息丢了一大半,召回率肯定崩盘,感觉 PQ 只是个“省内存但没用”的花架子。
正解DiskANN 是两阶段设计——PQ 码只在内存里做粗筛和导航,决定“往哪个方向走”;真正打分时,会把候选的完整向量从磁盘读回来精确计算距离。压缩只负责缩小搜索范围,不负责最终排名。实验中,PQ 导航 + 完整向量重排序的精度损失通常可以控制在 1~2 个百分点以内。迷你目录只帮你找书架,最终借哪本书还是要翻原书——精度由重排序兜底。
为何常见:只盯着压缩这一步的信息损失,忽略了检索管线的最后一步重排序设计,把“粗筛手段”误当成了“最终决策”。
误区既然 DiskANN 是“更先进”的方案,那所有向量检索场景都该无脑上 DiskANN,HNSW 迟早被淘汰。
正解两者是互补关系而非替代关系。HNSW 全内存、亚毫秒延迟,在千万级以下、QPS 要求极高的场景依然是首选;DiskANN 有磁盘 IO 开销,延迟在毫秒级,适合亿级以上、内存紧张的场景。选型的唯一标准是“内存账算不算得过来”。就像社区图书馆和国家总馆各有各的活法,你不能因为国家总馆的仓库方案先进,就让社区图书馆也去修地下仓库。
为何常见:把技术的演进线性外推成“新的必然取代旧的”,忽视了不同量级和延迟预算下各有最优解。
误区DiskANN 的核心不就是“把图放到磁盘上”吗?那内存里除了 PQ 码应该什么都不用管,缓存什么的都不重要。
正解DiskANN 的目标是“极小内存”而非“零内存”。真实场景中,热点节点和热门子图的访问高度集中,把高热度节点缓存在内存能命中一半以上的图访问,显著减少磁盘 IO。配合批量预取,缓存策略对 QPS 的影响可达数倍。周师傅的口袋里永远揣着最常被翻的推荐卡复印件——缓存不是可有可无,而是性能优化的第一杠杆。
为何常见:把“图存磁盘”理解成“完全抛弃内存”,忽略了检索热度分布的不均匀性,也忽略了缓存是 IO 型系统最经典的优化手段。
面试题
Q1. 十亿条 128 维 float32 向量仅存储向量本体需要多少内存?
- A.约 50 GB
- B.约 512 GB← 正确答案
- C.约 5 TB
- D.约 128 GB
答案:B。1e9 × 128 × 4 字节 = 512 GB,即每十亿条 128 维向量光“书的内容”就要 512GB“阅览室”,还没算图结构。这正是 HNSW 在十亿级场景下内存爆掉的直接原因,也是 DiskANN 用 PQ 码压缩的动机。
Q2. DiskANN 把主要数据结构分别放在哪里?
- A.全部放在显卡显存
- B.全部放在内存运行
- C.图放磁盘 PQ 码在内存← 正确答案
- D.数据在内存图在磁盘
答案:C。DiskANN 的核心分工是“图 + 完整向量上 SSD,PQ 压缩码驻内存”。D 把数据放内存是反的——完整向量 512GB 放不进去才需要 PQ 码,而 C 才是“小目录(内存)+ 大仓库(SSD)”的正确姿势。
Q3. DiskANN 搜索时在内存中用什么完成粗筛导航?
- A.完整浮点向量
- B.PQ 码距离表← 正确答案
- C.倒排索引列表
- D.内存哈希索引
答案:B。DiskANN 在内存里维护的是 PQ 压缩码和对应的距离查找表(LUT),查询时用查表累加得到近似距离来导航。完整浮点向量在磁盘上,倒排索引是 IVF 的机制,哈希表不提供近似最近邻能力。
Q4. PQ 压缩中,一条向量被切成 m 段、每段用 k 个质心量化,压缩后每条向量占用多少比特?
- A.32d 比特
- B.m×log₂k 比特← 正确答案
- C.m×k 比特
- D.k×log₂m 比特
答案:B。每段只需要记录“选了哪个质心”,所以每段占 log₂k 比特,m 段合计 m×log₂k。32d 是压缩前的位数,m×k 是码本大小而非码长。压缩比的公式 r = 32d / (m×log₂k) 正是由这个码长推导而来。
Q5. 关于 Vamana 图与 HNSW 的区别,下列说法正确的有?
- A.Vamana 是单层大度数图← 正确答案
- B.Vamana 需要多样性剪枝← 正确答案
- C.HNSW 是单层小度数图
- D.Vamana 跳数更多且每跳更省
答案:AB。Vamana 是单层图且出度 R 更大,构建时用“比已有邻居更靠近中心”的规则做多样性剪枝,保证邻居方向分散。HNSW 是多层跳表结构。Vamana 的图直径更小、跳数更少而非更多,D 说反了。
Q6. 以下哪些是 DiskANN 常用的性能优化手段?
- A.热点节点内存缓存← 正确答案
- B.批量预取邻居列表← 正确答案
- C.全部节点载入显存
- D.完整向量重排序← 正确答案
答案:ABD。热点缓存减少重复磁盘读,批量预取把多次随机读合并为大 IO,重排序用完整向量保证精度,三者都是 DiskANN 的标配。把全部节点载入显存违背了“省内存”的初衷,不现实也没必要。
Q7. DiskANN 为何选择 SSD 而非 HDD 作为图的存储介质?
- A.顺序读写吞吐更高
- B.随机读延迟远低于 HDD← 正确答案
- C.随机读与 HDD 差不多
- D.容量比 HDD 大很多
答案:B。图遍历的本质是大量随机读——每次跳转都要读任意位置的邻居列表。HDD 随机读延迟高达数毫秒,SSD 只有 50~100 微秒,差两个数量级。容量上 HDD 反而更大,关键差异是随机访问性能。
Q8. 某库有 1 亿条 128 维向量,HNSW 出度 R=32,估算总内存需求(含向量与邻接表)?
- A.内存约 8 GB
- B.内存约 25 GB
- C.内存约 77 GB← 正确答案
- D.内存约 128 GB
答案:C。代入公式 M = N×(d×4 + R×8) = 1e8×(512 + 256) = 7.68e10 字节 ≈ 77GB。其中向量本体约 51GB,图邻接表约 26GB。这个量级在单机内存里已经是“很贵”的存在,也是 DiskANN 想省掉的部分。
Q9. 哪些场景更适合选择 DiskANN 而非 HNSW?
- A.十亿级商品向量召回← 正确答案
- B.单机内存预算紧张← 正确答案
- C.千万级亚毫秒延迟需求
- D.图数据量极小可常驻内存
答案:AB。DiskANN 的定位是“大容量 + 小内存”,十亿级和内存紧张正是它的主场。千万级且要求亚毫秒延迟时 HNSW 优势明显;图小到能常驻内存时直接全内存方案即可,没必要引入磁盘 IO 的毫秒级开销。
Q10. 以下哪些因素会导致 DiskANN 召回率下降?
- A.PQ 码本设置过小← 正确答案
- B.波束宽度 L 过小← 正确答案
- C.图出度 R 设置过大
- D.重排候选集太小← 正确答案
答案:ABD。码本太小则质心粗糙,粗筛方向容易偏;波束宽度太小会漏掉正确路径;重排候选集太小把正确答案挡在门外。出度 R 过大反而让图更密、跳数更少,通常提升召回但增加构建开销,不会导致召回下降。