Skip to content

refactor(simd): share quantizer, reduction, rescale-search and dispatch boilerplate - #191

Merged
gouyt13 merged 6 commits into
mainfrom
refactor/simd-dedup
Oct 31, 2026
Merged

gouyt13 merged 6 commits into
mainfrom
refactor/simd-dedup

Conversation

@gouyt13

@gouyt13 gouyt13 commented Oct 31, 2026

Copy link
Copy Markdown
Member

Summary

Remove duplicated non-kernel logic around the SIMD backends. Results are unchanged, and there are no public API changes; the removed transfer_lut_hacc_avx2 was an installed detail declaration. One commit per area:

  • Quantizers: the AVX2, AVX-512 and NEON uint8 and uint16 scalar quantizers share one template per backend. Only the final narrowing and store differ.
  • AVX2 horizontal sum: three copies become one internal-linkage helper. This also replaces an external-linkage inline copy in an AVX2 translation unit.
  • Rescale search: the AVX2 and AVX-512 evaluators share the exact event-scale recovery and the scalar tail, and the four wrappers share the -1 fallback convention. Everything stays in rescale_search.hpp with internal linkage.
  • AVX2 HACC LUT transfer: the AVX2 backend uses transfer_lut_hacc_generic, as NEON already does. The removed AVX2 version contained no vector instructions and wrote the same Pair32 layout; the outputs are byte-identical.
  • Dispatch: RABITQ_RESOLVE_NEON replaces 13 copies of the ARM64-NEON-or-RABITQ_RESOLVE #if block, whose ARM64 guards were inconsistent. Each target still references only its own ISA's symbols. The excode IP table keeps its explicit block, because it resolves whole tables.

Tried and dropped, because each changed the code generated on a hot path for a small deduplication:

  • A shared chunked-accumulation helper for the four AVX2/AVX-512 FastScan accumulators. It added a tail call per scored batch.
  • A shared dim % 16 check. It changed register allocation in the public fastscan::accumulate wrappers.

Testing

Linux x86-64 host: Xeon Gold 6418H, 2 sockets; GCC 14.3; RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF.

  • CTest (--no-tests=error):

    • Release: 424 passed, 1 skipped (the placement test, which requires OpenMP binding).
    • ASan/UBSan Debug: 424 passed, 1 skipped.
    • Core-only RABITQ_USE_OPENMP=OFF: 173 passed.
    • Each commit builds on its own.
  • Static checks: check-format.sh, full check-tidy.sh build-tidy (clang-tidy 23) and git diff --check pass.

  • Object code compared with main (0734ec2), function by function:

    • Identical: all 98 functions in dispatch.cpp; all FastScan accumulate, pack and estimator functions; space_excode_avx2/avx512; and the AVX2 HNSW search kernels.
    • The uint16 quantizers differ by at most one instruction.
    • The rescale evaluators now call the shared tail instead of inlining it.
  • Benchmarks (single thread, pinned to one idle core and its NUMA node, alternating with main, 6–12 pairs):

    • AVX2 HACC transfer, called directly (it is not selected on this AVX-512 host), generic/AVX2: 0.989 at 128 dimensions and 1.010 at 960 dimensions, with byte-identical output.
    • SplitBatchQuery::reset, d=128/960, standard and HACC: 0.95–1.01, same checksums.
    • rabitq_core_benchmark at 4 and 9 bits, encode_packed and prepare_query (which exercise the rescale search and quantizers): 0.94–1.02, same checksums.

    No speedup is claimed. Shared host: load average 4–11; CPU frequency was not locked.

  • NEON: query_neon.cpp and the macro's ARM64 branch in dispatch.cpp pass a clang --target=aarch64-linux-gnu syntax and semantic check, with no first-party errors. They were not built into object code or run.

Not run locally: NEON execution, Windows and macOS builds.

The AVX2, AVX-512 and NEON uint8 and uint16 scalar quantizers repeated the zero-range fill, reciprocal setup and scalar tail, differing only in the final narrowing and store. Each backend now has one template over the code type.
Three translation-unit copies of the same tree reduction become one internal-linkage helper. It replaces an external-linkage inline copy in an AVX2 translation unit. The generated kernels are unchanged.
The AVX2 and AVX-512 evaluators repeated the exact event-scale recovery and scalar tail, and four wrappers repeated the -1 fallback convention. Both now live in rescale_search.hpp with internal linkage.
transfer_lut_hacc_avx2 used no vector instructions and wrote the same Pair32 layout as transfer_lut_hacc_generic, which the NEON backend already uses. The outputs are byte-identical.
Thirteen dispatch wrappers repeated an #if block choosing resolve_optional_kernel on ARM64 or RABITQ_RESOLVE elsewhere, with inconsistent ARM64 guards. RABITQ_RESOLVE_NEON keeps each target referencing only its own ISA's symbols; the x86 dispatch code is unchanged.
@github-actions github-actions Bot added the release:maintenance Release category managed from the PR title label Oct 31, 2026
@gouyt13
gouyt13 merged commit e2c99c7 into main Oct 31, 2026
48 checks passed
@gouyt13
gouyt13 deleted the refactor/simd-dedup branch October 31, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:maintenance Release category managed from the PR title

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant