Skip to content

Batch the IVF ScalarQuantizer scan four codes at a time - #5456

Open
blazingphoenix7 wants to merge 1 commit into
facebookresearch:mainfrom
blazingphoenix7:ivfsq-scan-batch4
Open

Batch the IVF ScalarQuantizer scan four codes at a time#5456
blazingphoenix7 wants to merge 1 commit into
facebookresearch:mainfrom
blazingphoenix7:ivfsq-scan-batch4

Conversation

@blazingphoenix7

Copy link
Copy Markdown
Contributor

The IVF ScalarQuantizer inverted-list scan distances one code at a time through run_scan_codes1, even though the SQ distance computers already expose a four-at-a-time kernel, query_to_codes_batch_4 (the one HNSW uses through distances_batch_4). This adds run_scan_codes4, a batched inner loop that distances four consecutive codes per step and then applies the same threshold compare, add_result, and threshold refresh as run_scan_codes1, in id order, so the sequence of heap updates is identical. The two SQ scanners route their no-selector path through it; the selector path stays on run_scan_codes1.

Four independent accumulator chains let the out-of-order core overlap the latency-bound reduction that a single code cannot hide. Results are unchanged: the batched kernel reconstructs each code, which is bit-identical to the single-code path for every quantizer except the uniform ones, whose single-code path predecodes the query, so a compile-time check keeps those on the scalar scan. Verified bit-identical distances and ids against the scalar scan for QT_8bit, QT_8bit_uniform, QT_6bit, QT_4bit, QT_fp16, and QT_8bit_direct, in both metrics, at d in {64, 128, 256}.

Measured on AVX2, end-to-end IndexIVFScalarQuantizer search of QT_8bit, single thread, batched against the scalar scan:

metric d scalar batched speedup
L2 128 60.4 ms 40.3 ms 1.50x
L2 256 170.9 ms 105.7 ms 1.62x
L2 256, out of cache 403.5 ms 262.3 ms 1.54x
IP 128 46.5 ms 38.4 ms 1.21x
IP 256 108.7 ms 83.8 ms 1.30x

The L2 gain is the larger one, around 1.5-1.6x, and it holds when the index spills out of cache. Inner product gains less since its shorter chain is already partly overlapped by the core. When batching does not help it matches the scalar scan, so there is no regression.

The SQ inverted-list scanners call distance_to_code one code at a time via
run_scan_codes1, even though the SQ distance computers already expose
query_to_codes_batch_4 (the batched kernel HNSW uses). Add run_scan_codes4,
which distances four consecutive codes per step and applies the threshold and
heap bookkeeping in id order so the heap-update sequence matches run_scan_codes1
exactly, then route the SQ scanners' no-selector path through it.

The batched kernel reconstructs each code, which is bit-identical to the
single-code path for every quantizer except the uniform ones, whose single-code
path predecodes the query. A compile-time check keeps the uniform quantizers on
the scalar scan, so search results are unchanged for every quantizer type. The
selector path and all non-SQ scanners are unchanged.
@meta-cla meta-cla Bot added the CLA Signed label Jul 22, 2026
@blazingphoenix7

Copy link
Copy Markdown
Contributor Author

@mnorris11 could I ask you to take a look at this one when you get a chance? I opened it the same day as #5457, which was imported and merged last week, so I think this one may have just been missed rather than held up on anything.

It routes the no-selector IVF ScalarQuantizer scan through the four-at-a-time distance kernel the SQ computers already expose and HNSW already uses, applying the threshold compare and heap updates in the same id order as the scalar scan. Distances and ids come out bit-identical to that scan for QT_8bit, QT_8bit_uniform, QT_6bit, QT_4bit, QT_fp16 and QT_8bit_direct in both metrics, and end to end L2 search is about 1.5x faster at d=128 and 1.6x at d=256.

Happy to rebase it or split it up if that would make review easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant