Skip to content

IndexHNSW ignores SearchParameters.sel when bounded_queue=False #5504

Description

@mmnhgo

Summary

When searching an IndexHNSW index with SearchParameters.sel, the ID selector works correctly when bounded_queue=True.

However, when bounded_queue=False, the selector is ignored and the search may return IDs that are explicitly excluded.

This violates the contract of SearchParameters.sel: when it is non-null, only IDs accepted by the selector should be returned.

Bug type

Incorrect result

Environment

FAISS version: 1.14.2
Python: 3.x
OS: Windows
Installation: pip faiss-cpu
Index: IndexHNSWFlat
Metric: METRIC_L2

Minimal reproducer

import faiss
import numpy as np

xb = np.array(
    [
        [0.0, 0.0],
        [10.0, 0.0],
        [20.0, 0.0],
        [30.0, 0.0],
    ],
    dtype=np.float32,
)

xq = np.array([[0.0, 0.0]], dtype=np.float32)

index = faiss.IndexHNSWFlat(2, 4, faiss.METRIC_L2)
index.hnsw.efConstruction = 40
index.add(xb)

# Only ID 1 is allowed.
selector = faiss.IDSelectorRange(1, 2)

params = faiss.SearchParametersHNSW(
    efSearch=40,
    bounded_queue=False,
    sel=selector,
)

D, I = index.search(xq, 1, params=params)

print("labels:", I.tolist())
print("distances:", D.tolist())

assert I.tolist() == [[1]]
assert D.tolist() == [[100.0]]

Expected behavior

Because the selector only allows ID 1, the result should be:

labels: [[1]]
distances: [[100.0]]

Every returned label must satisfy:

sel->is_member(label)

This requirement should not depend on the value of bounded_queue.

Actual behavior

With bounded_queue=False, the result is:

labels: [[0]]
distances: [[0.0]]

ID 0 is not accepted by the selector, but it is still returned.

Comparison

Using the same index, data, query, and selector, but changing the parameter to:

bounded_queue=True

returns the correct result:

labels: [[1]]
distances: [[100.0]]

Why this is a bug

This is not normal approximate-nearest-neighbor recall behavior.

Even if HNSW fails to find the best allowed candidate, it must not return an ID that is explicitly rejected by the selector. The selector is a hard validity constraint on returned results, not a ranking preference.

In this example:

  • ID 0 is explicitly excluded;
  • ID 1 is the only allowed ID;
  • filtering works when bounded_queue=True;
  • filtering is ignored when bounded_queue=False.

This indicates a queue-path-specific ID filtering bug.

Impact

Applications that rely on SearchParameters.sel for permission filtering, tenant isolation, metadata filtering, or deleted-record filtering may receive IDs that should not be returned when bounded_queue=False.

This can cause filtering rules to be bypassed and restricted or cross-tenant data to appear in search results.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions