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:
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.
Environment
faiss-cpu==1.15.0IndexShardsIVFMETRIC_L2Summary
IndexShardsIVF.search()acceptsSearchParametersIVF, readsnprobe, but does not forward the search parameters to the child IVF indexes.As a result,
SearchParametersIVF.selis silently ignored. A selector that should restrict results to ID100is not applied to the second shard, and disallowed ID200is returned.Minimal reproduction
Expected behavior
The selector allows only ID
100.The direct child control returns:
The sharded index should return only ID
100:The exact ordering of empty-result sentinels is not important.
Actual behavior
The second shard returns ID
200even though it is not included inparams.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:However, each child is called as follows:
index->search_preassigned( n, x, k, Iq.data(), Dq.data(), distances, labels, false);The
paramsargument 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.selis 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:The implementation should also audit forwarding of other
SearchParametersIVFfields such asmax_codes.Duplicate search
A bounded search found no exact issue or PR for
IndexShardsIVFsilently droppingSearchParametersIVF.sel.Issue #5523 concerns
IndexIDMap.range_searchrejecting IVF parameters. Issue #5581 and PR #5582 concern selector ID-space translation in ordinaryIndexShards; PR #5582 explicitly leaves the separateIndexShardsIVF::search()override unchanged.