Skip to content

[Bug]: IVFQueryParam nprobe has no effect on IVF results or latency #598

Description

@SeanPedersen

Description

IVFQueryParam(nprobe=...) appears to have no effect on IVF search in zvec
0.5.1. For both unquantized FP32 and INT8 100-list indexes, changing nprobe
from 1 through 100 produced identical top-10 IDs for every query and essentially
unchanged latency.

Expected: increasing nprobe should search more lists and normally affect
candidates, recall, or latency. In particular, unquantized nprobe=n_list
should search all lists and recover the one-list/exact FP32 result.

This report does not assume the root cause. The observations are consistent
with a fixed/default probe count being used internally, but the defect could be
elsewhere in parameter forwarding or IVF search configuration.

Steps to reproduce

Standalone PEP 723 script (OpenML MNIST; does not use private data or an
existing benchmark):

https://gist.github.com/SeanPedersen/dbb00e44566cbb44fbbb2219ada80d8e

uv run --python 3.11 reproduce_zvec_ivf_nprobe.py

The script:

  • downloads and locally caches OpenML mnist_784 v1;
  • indexes exactly 10,000 contiguous normalized float32 vectors;
  • uses 100 deterministic, disjoint queries from the MNIST test partition;
  • computes exact NumPy cosine top-10 ground truth;
  • builds indexes only after loading the vectors and calls collection.optimize();
  • tests cosine IVF with n_list=100, n_iters=15, use_soar=False, with no
    quantization and with QuantizeType.INT8;
  • tests nprobe=1,5,10,25,50,100 and FP32/INT8 one-list controls;
  • asserts IVFQueryParam(nprobe=N).nprobe == N and that the native pybind11
    parameter attached to Query retains N;
  • removes temporary indexes in finally blocks and writes machine-readable JSON.

Results

All 100 returned top-10 ID lists were identical at every nprobe within both
100-list indexes:

index nprobe recall@10 mean ms p50 ms p95 ms IDs differ vs 1
IVF FP32, no quantization 1 0.9820 0.418 0.402 0.552 False
IVF FP32, no quantization 5 0.9820 0.405 0.392 0.496 False
IVF FP32, no quantization 10 0.9820 0.413 0.394 0.570 False
IVF FP32, no quantization 25 0.9820 0.385 0.371 0.520 False
IVF FP32, no quantization 50 0.9820 0.395 0.377 0.525 False
IVF FP32, no quantization 100 0.9820 0.412 0.396 0.553 False
IVF INT8 1 0.6660 0.370 0.366 0.493 False
IVF INT8 5 0.6660 0.419 0.405 0.548 False
IVF INT8 10 0.6660 0.386 0.397 0.510 False
IVF INT8 25 0.6660 0.383 0.370 0.492 False
IVF INT8 50 0.6660 0.384 0.384 0.515 False
IVF INT8 100 0.6660 0.368 0.358 0.513 False
one-list FP32 control 1 1.0000 2.375 2.308 3.082 n/a
one-list INT8 control 1 0.9980 0.962 0.927 1.198 n/a

The one-list FP32 result is exact, and the one-list INT8 result suggests INT8
quantization alone does not explain the low multi-list INT8 recall. IVF training
appears nondeterministic (the absolute INT8 recall varied between fresh index
builds), but within every build tested, IDs remained identical across all
nprobe values.

Operating system

Linux 7.0.11-zen1-1-zen x86_64 (glibc 2.43)

Build and runtime environment

  • zvec 0.5.1 (published PyPI wheel)
  • Python 3.11.13, installed/run by uv
  • NumPy 2.4.6
  • CPU architecture: x86_64
  • Dataset: OpenML mnist_784 v1, 10,000 indexed vectors, dimension 784

Additional context

The issue involves the Python/native pybind11 boundary. The public Python
parameter and the native parameter attached to the query both display the
requested nprobe; no monkey-patching is used.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions