Repository navigation
Route the AVX2 search kernel through the ndarray::simd polyfill - #4
Conversation
Re-applies this fork's work on top of upstream 1.1.2, as one commit. The
backup branches backup/main-pre-upstream-sync-2026-10-29{,-b} keep the
original history.
search_multi_query_avx2 now uses only the ndarray::simd facade, 64 bytes
wide: one U8x64 load covers two adjacent byte groups (codes and LUT),
shuffle_bytes does the lookups, U16x32::from_u8x64_lo/hi widen into the
u16 accumulators (replacing the SUB trick), U16x32::to_f32x16_lo/hi +
F32x16::mul_add flush each FLUSH_EVERY batch, and F32x16::simd_gt prunes
against the heap minimum. Scores are bit-identical to the AVX-512 kernel.
Also:
- FORCE_AVX2_PATH forces the AVX2 kernel on AVX-512 hosts (tests and the
bench-internals feature); avx2_ndarray_simd_parity compares it with the
default kernel bit for bit, including a two-batch dim and a partial
block.
- examples/kernel_speed.rs times the native, AVX2 and scalar paths.
- ndarray fork dependency (x86_64 only, no BLAS), currently on the
claude/u16x32-facade-gaps branch until AdaWorldAPI/ndarray#385 merges.
- Toolchain 1.99.0, which the ndarray fork requires: rust-toolchain.toml,
rust-version in both crates, and the CI clippy pin, with the two lint
classes 1.99 adds to existing code allow-listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
…he changelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
c0c2dad to
b447edc
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
The index-level parity test only runs where the classic layout is picked, so on hosts that take the vector-major path it never reaches the kernel. This drives search_multi_query_avx2 on synthetic codes and LUTs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
cargo-mutants only recognises a bare #[cfg(test)]; under cfg(all(test, target_arch = "x86_64")) it mutated the test helpers themselves (about 110 extra mutants). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Both kernels return bit-identical scores, so the parity test could not tell a forced AVX2 search from a second AVX-512 one. A test-only call counter in search_multi_query_avx2 makes the routing observable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
…ing-free Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
The Generated by Claude Code Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d957d1dae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… is merged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Related issue
None. This re-applies the fork's own work after
mainwas reset to upstream 1.1.2 (the old history is kept onbackup/main-pre-upstream-sync-2026-10-29and-b).Summary
search_multi_query_avx2now uses only thendarray::simdfacade and no raw intrinsics. It works 64 bytes wide:U8x64load covers two adjacent byte groups, for both the codes and the LUT. In the blocked layout the two groups are contiguous, so nothing is copied.shuffle_bytesdoes the lookups.U16x32::from_u8x64_lo/hiwiden into the u16 accumulators, which replaces the SUB trick.FLUSH_EVERYbatch is flushed withU16x32::to_f32x16_lo/hiandF32x16::mul_add.F32x16::simd_gtprunes against the heap minimum.FORCE_AVX2_PATHforces the AVX2 kernel on AVX-512 hosts. It is available in tests and under the newbench-internalsfeature.examples/kernel_speed.rstimes the native, AVX2 and scalar paths on one index.default-features = false, no BLAS.masterbranch, which has theU16x32methods this kernel uses (zero,to_f32x16_lo/hi, …) since Complete the U16x32 facade surface on every backend ndarray#385.deny.tomlallows that one git source.rust-toolchain.toml;rust-versionin both crates (the MSRV job requires the two to match);chunks_exact_to_as_chunks,manual_isolate_lowest_one), so both are added to the allow-list, as the CI comment asks for a pin bump.build.rschanges are dropped because upstream no longer depends on ndarray or BLAS.Motivation
The AVX2 body is the slot where the polyfill replaces hand-written intrinsics. Which realization runs is left to
ndarray::simd.Test plan
avx2_kernel_referencedrivessearch_multi_query_avx2directly on synthetic codes and LUTs. It checks the results bit for bit against a scalar reference that reads the blocked layout independently. The cases cover:It does not depend on which code layout the host picks.
avx2_ndarray_simd_parity(withTURBOVEC_NO_VECTOR_MAJOR=1) compares forced-AVX2 search with the default kernel. It checks top-k ids and scores bit for bit at dim 256 and at dim 1024 with n=1000. A test-only call counter asserts that the forced search really reached the AVX2 kernel and that the default one did not. Otherwise the test could compare the AVX-512 kernel with itself.A disable run (writing scores in byte order instead of slot order) turns the parity test red.
cargo mutants --in-difflocally: both dispatch mutants (!force_avx2 &&) are caught. They survived before the call counter was added. cargo-mutants does not mutate the kernel itself, since it is anunsafe fn. The other 16 mutants replace the wholescan_with_lutsbody, which any search test catches.cargo test -p turbovec --release: every lib test passes exceptv7_delta_tests::a_failed_sync_recovers_via_full_write, both with and withoutTURBOVEC_NO_VECTOR_MAJOR. That test relies on a read-only file refusing writes, which does not happen when running as root, as in this sandbox. It passes in CI.The CI clippy command with its flags passes on x86_64 (
--workspace) and aarch64. It also passes on x86_64 with--features bench-internals,turbovec9-lab.CI is green, including:
The
reviewworkflow is the exception. It needsCLAUDE_BOT_TOKENandCLAUDE_CODE_OAUTH_TOKEN, which this fork does not have.Speed, from
kernel_speed(n=20000, dim=512, nq=256, k=10, 4-bit, AVX-512 host, ns/query):x86-64-v2)x86-64-v2)-Ctarget-cpu=x86-64-v3-Ctarget-cpu=x86-64-v4Recall@10 is 0.6434 on every path. The AVX-512 kernel, which production uses on AVX-512 hosts, is unchanged. On a default or v3 build, the polyfilled AVX2 kernel is about 12× slower than the raw kernel it replaces. With AVX-512 enabled it is about 15% slower. The difference comes from how the facade's 64-byte types are realized below AVX-512, which is
ndarray::simd's concern. This PR changes nothing there.🤖 Generated with Claude Code
https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v