IndexShardsIVF silently drops SearchParametersIVF.sel
Environment
- Faiss:
faiss-cpu==1.15.0 - Interface: Python CPU runtime
- Index:
IndexShardsIVF - Metric:
METRIC_L2
Summary
IndexShardsIVF.search() accepts SearchParametersIVF, reads nprobe, but does not forward the search parameters to the child IVF indexes.
As a result, SearchParametersIVF.sel is silently ignored. A selector that should restrict results to ID 100 is not applied to the second shard, and disallowed ID 200 is returned.
Minimal reproduction
import faiss
import numpy as np
x0 = np.array([[0.0]], dtype="float32")
x1 = np.array([[1.0]], dtype="float32")
def make_child(vector, external_id):
child_quantizer = faiss.IndexFlatL2(1)
child_quantizer.add(np.array([[0.0]], dtype="float32"))
child = faiss.IndexIVFFlat(child_quantizer, 1, 1)
child.add_with_ids(
vector,
np.array([external_id], dtype="int64"),
)
child.nprobe = 1
return child
left = make_child(x0, 100)
right = make_child(x1, 200)
outer_quantizer = faiss.IndexFlatL2(1)
outer_quantizer.add(np.array([[0.0]], dtype="float32"))
shards = faiss.IndexShardsIVF(
outer_quantizer,
1,
False, # threaded
False, # successive_ids: child IDs are already global
)
shards.add_shard(left)
shards.add_shard(right)
params = faiss.SearchParametersIVF(nprobe=1)
params.sel = faiss.IDSelectorArray(
np.array([100], dtype="int64")
)
D_child, I_child = left.search(x0, 2, params=params)
D_shards, I_shards = shards.search(x0, 2, params=params)
print("child:", D_child, I_child)
print("shards:", D_shards, I_shards)Expected behavior
The selector allows only ID 100.
The direct child control returns:
child labels: [[100, -1]]The sharded index should return only ID 100:
shards labels: [[100, -1]]The exact ordering of empty-result sentinels is not important.
Actual behavior
child labels: [[100, -1]]
shards labels: [[100, 200]]The second shard returns ID 200 even though it is not included in params.sel.
The result is deterministic with two exact one-list IVF children and nprobe=1.
Root cause
IndexShardsIVF::search() validates and reads the concrete IVF parameters:
const IVFSearchParameters* params = nullptr;
if (params_in) {
params = dynamic_cast<const IVFSearchParameters*>(params_in);
}
idx_t nprobe = params ? params->nprobe : index0->nprobe;However, each child is called as follows:
index->search_preassigned(
n,
x,
k,
Iq.data(),
Dq.data(),
distances,
labels,
false);The params argument is omitted. Therefore, the child receives no selector and scans all eligible IDs.
Relevant source:
[IndexShardsIVF.cpp](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/IndexShardsIVF.cpp)[IndexShardsIVF.h](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/IndexShardsIVF.h)[Index.h](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/Index.h)
SearchParameters.sel is documented as restricting the search to selected IDs.
Impact
Applications using selectors for filtering, tenant isolation, authorization boundaries, or result scoping may receive IDs that were explicitly excluded by the caller.
The request succeeds and returns plausible distances, so the filtering failure is silent.
Suggested fix
For configurations where child IDs already share the global ID space, forward the IVF parameters:
index->search_preassigned(
n,
x,
k,
Iq.data(),
Dq.data(),
distances,
labels,
false,
params);For successive_ids=True, a selector cannot necessarily be forwarded unchanged because child IDs may be translated after search. Faiss should either:
- translate the selector into each shard's local ID space before searching; or
- reject selector searches with an explicit error until selector translation is supported.
The implementation should also audit forwarding of other SearchParametersIVF fields such as max_codes.
Duplicate search
A bounded search found no exact issue or PR for IndexShardsIVF silently dropping SearchParametersIVF.sel.
Issue #5523 concerns IndexIDMap.range_search rejecting IVF parameters. Issue #5581 and PR #5582 concern selector ID-space translation in ordinary IndexShards; PR #5582 explicitly leaves the separate IndexShardsIVF::search() override unchanged.
Source: facebookresearch/faiss