Skip to content

feat(python): let IvfIndex.set_centroids take num_threads - #231

Merged
gouyt13 merged 1 commit into
mainfrom
feat/ivf-set-centroids-threads
Nov 13, 2026
Merged

gouyt13 merged 1 commit into
mainfrom
feat/ivf-set-centroids-threads

Conversation

@gouyt13

@gouyt13 gouyt13 commented Nov 13, 2026

Copy link
Copy Markdown
Member

IvfIndex.set_centroids now takes a keyword-only num_threads, as train and add do. The C++ IVF::set_centroids already took it; the binding did not pass it through. The 0.7 build passed its thread count to the routing build, and the 0.8 split into set_centroids plus add lost it. #230 loosened two routing tests because of that, and this PR restores them.

Changes

  • num_threads on IvfIndex.set_centroids. It sets the workers that rotate the centroids and build the routing. Only the HNSW routing graph varies with it: one thread and a fixed seed give the same graph on every run. HnswIndex.set_centroids is unchanged; it rotates serially.
  • Default now honors OMP_NUM_THREADS. Without the argument, the binding used the C++ default (SIZE_MAX), which starts an OpenMP team of every CPU and ignores OMP_NUM_THREADS. That happened for every initializer, not just HNSW. None and 0 now select the available CPUs capped by OMP_NUM_THREADS, like every other Python default. Processes that share OpenMP runtimes on macOS rely on that cap (v0.8.0 causing segfault 11 on macOS #207). The C++ default is unchanged.
  • Docs. The docstring, __init__.pyi, index/ivf.md and index/usage.md describe the argument. The migration guide notes that 0.7 build built IVF HNSW routing with one thread by default, and that it is multithreaded now unless num_threads=1 is passed (marked as 0.8.5).

Tests

  • New: two single-thread set_centroids calls with the same seed give identical saved files, including the .hnsw sidecar.
  • New: in a subprocess with OMP_NUM_THREADS=1, two default set_centroids calls over 1,024 centroids give identical files. Before this change, every 2-, 4-thread or unlimited build differed (10 of 10 trials each), so the test guards the cap on any runner with at least 2 CPUs.
  • Restored from test(python): migrate the last seven test files off the 0.7 call forms #230:
    • test_hnsw_centroid_routing_roundtrip (128 centroids) is seeded, builds the routing with one thread, and checks exact matches again instead of aggregate self-recall.
    • test_hnsw_parallel_build_routes_and_reloads passes its threads to set_centroids, so the parallel routing build is what it tests.

Checks

On Linux x86-64, Xeon Gold 6418H, GCC 14.3, Release, native optimization off; the package was built from this branch.

  • Full Python suite, including the mypy and Pyright tests: 1,153 passed, 2 skipped. Full check-tidy.sh is clean.
  • An independent review found no correctness problems. Its findings on documentation wording and the missing test seed are fixed here. After those fixes, which change only docstrings, docs and one test:
    • test_ivf.py, test_ivf_initializers.py and test_thread_counts.py under -W error::DeprecationWarning: 185 passed;
    • the editor-support, mypy and Pyright tests: 19 passed;
    • check-python.sh, check-format.sh, mkdocs build --strict and git diff --check pass.
  • Routing tests in a loop: 304 runs before the fixes, then 50 runs of the seeded roundtrip test, 0 failures.

Release notes

Main has no unreleased notes page, so for the next release:

  • Added: IvfIndex.set_centroids(..., num_threads=None).
  • Fixed: IvfIndex.set_centroids without num_threads used every CPU and ignored OMP_NUM_THREADS; it now follows the same default as train and add.

IvfIndex.set_centroids takes a keyword-only num_threads for rotating the
centroids and building the routing, as train and add do. Only the HNSW
routing graph varies with it; one thread and a fixed seed build a
reproducible graph. The 0.7 build passed its thread count through, but
the 0.8 split into set_centroids and add lost it.

Without the argument, set_centroids used the C++ default, which runs on
every CPU and ignores OMP_NUM_THREADS, for every initializer. None and 0
now select the available CPUs capped by OMP_NUM_THREADS, like every
other default. That is what processes sharing OpenMP runtimes on macOS
rely on.

Tests check that a single-thread set_centroids gives identical files
and that OMP_NUM_THREADS=1 caps the default. The routing tests that
#230 loosened build with explicit thread counts again: the 128-centroid
test is seeded and exact once more, and the parallel test's thread count
applies to the routing build. The migration guide notes that IVF HNSW
routing is no longer single-threaded by default.
@github-actions github-actions Bot added the release:added Release category managed from the PR title label Nov 13, 2026
@gouyt13
gouyt13 merged commit ecc594c into main Nov 13, 2026
39 checks passed
@gouyt13
gouyt13 deleted the feat/ivf-set-centroids-threads branch November 13, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:added Release category managed from the PR title

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant