DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Faiss GPU 并发查询报 p + size == head_,检查资源的线程归属

Faiss 1.7.1 的并发查询报告出现临时显存栈断言,贡献者提示资源可能被多个线程共用。核对实际线程归属,用固定工作线程作对照;独立资源仍需显存预算。

Faiss GPU 单次查询正常,增加请求线程后出现临时显存栈断言,例如:

Faiss assertion 'p + size == head_' failed

官方 issue #2175记录了 Ubuntu、Python、faiss-gpu 1.7.1 并行调用 GPU 索引时的这个错误。贡献者指出,很可能有多个线程同时使用同一个 GpuResources;报告者随后表示通过互斥解决了该问题。这是历史案例的结果,同一断言也不能单独证明现场根因。先核对当前版本与调用线程,再检查资源是否被共用。

Faiss 官方线程说明明确区分:CPU 索引的并发只读搜索可以安全使用,而 GPU 索引即使只读,也不承诺线程安全;GPU resources 管理的临时区一次只供一个调用者使用。下面采用固定工作线程避免同时调用同一份资源;并行调用需要单独设计。

让资源和所有调用归同一个线程

对于一份 GPU 索引,最简单的隔离对照是让一个固定工作线程负责创建资源、持有索引和执行查询。其他请求提交到队列,不直接调用它。下面使用合成二维数据,要求安装了支持 GPU 的 Faiss,并有权使用 GPU 0:

from concurrent.futures import ThreadPoolExecutor
import threading
import faiss
import numpy as np

owned = threading.local()

def initialize_gpu():
    resources = faiss.StandardGpuResources()
    cpu_index = faiss.IndexFlatL2(2)
    cpu_index.add(np.array([[0.0, 0.0], [1.0, 0.0]], dtype=np.float32))
    owned.resources = resources
    owned.index = faiss.index_cpu_to_gpu(resources, 0, cpu_index)

def search_on_owner(values):
    query = np.ascontiguousarray(values, dtype=np.float32)
    return owned.index.search(query, 1)

with ThreadPoolExecutor(max_workers=1, initializer=initialize_gpu) as worker:
    future = worker.submit(search_on_owner, [[0.2, 0.0]])
    distances, ids = future.result()
    assert ids.shape == (1, 1)
    assert ids[0, 0] == 0

代码只展示线程归属,真实服务还需要队列容量、请求超时与关闭流程。Python 调用释放 GIL 不等于资源可以跨线程共享;给异步 handler 加 async 也不会提供这个保证。

如果其他代码仍能直接取得 owned.index 或保留另一份索引引用,所有权约束就没有落实。构建、add、search 及释放路径都要一起检查。

真要多线程,重新预算资源和索引

官方要求每个活跃调用 GPU 索引的 CPU 线程有自己的资源。不能仅创建多份 resources,就继续让多线程无锁操作同一份可变 GPU 索引;需要相容的索引布局和明确所有权,并评估复制索引与临时区的显存成本。

先保留固定输入的串行基线,再增加受控并发。比较有效 ID、距离容差、错误率和显存,不把修复并发错误顺带写成吞吐提升承诺。运行期更新索引时,还需独立的读写协调。

固定线程方案能作为诊断和运行选择;如果异常仍存在,继续查输入、GPU 内存和其他调用路径,不因并发降低后一次成功就宣布根因已确认。