Add GPU-accelerated IVFPQ search to the Metal backend - #5449
Evandabest wants to merge 34 commits into
Conversation
|
Following up on #5288, here are the isolated PQ lookup-table construction results from the GPU-scan work (same as shown in first PR). All measurements were collected on an M3 Pro with d=128, 8-bit PQ, and FP32 lookup tables. PQ LUT computation
LUT speedup saturates at ~3.7-3.8x once the batch is large enough to hide kernel-launch overhead (nq ≥ 10k); ~1.2x for tiny batches. |
|
@mnorris11 has imported this pull request. If you are a Meta employee, you can view this in D113864779. |
|
Sorry for the delay. We are a bit behind in PR/issue review. Can you take a look at these when you have time?
|
4639f82 to
06e2be4
Compare
|
Hi @mnorris11, no worries. I cleared up the dead code and collapsed ivf_tg_bitonic_int / _long into one template. I am still investigating the M ≤ 16 constraint. It isn't a IVFPQ limitation, it comes from the current kernel's threadgroup memory usage. The lookup table uses 16KB at M=16, and another 8KB for top-k scratch space. I tested a separate path for M > 16 that reads lookup terms from device memory instead. It works correctly through M=64, but performance is mixed and generally only improves for fine grained indexes. I’m still evaluating whether to include this path or keep and clearly document the existing M <= 16 limit. I will get back to you by the end of this week |
0a7ad80 to
b5a2db0
Compare
|
Hi @mnorris11, following up on the M <= 16 limitation. I pushed a new optimized Metal IVFPQ scan path supporting M <= 64. Instead of expanding the M * 256 threadgroup LUT, which would exceed Metal’s threadgroup memory limit at larger M, the new path computes distance contributions only for PQ codes actually encountered in each list. Short lists use a 32 thread kernel with about 2 KiB of scratch; longer lists use segmented exact top-k selection. The existing cached LUT kernel remains available for suitable M <= 16 workloads. |
|
Benchmark results on an Apple M3 Pro, 18GB RAM are below. Times are milliseconds per 1000 query search, each value is the median of three repeat medians. d=128, nb=100000, nlist=4096, nprobe=32
|
Summary
This is a follow-up to #5288, which added
MetalIndexIVFPQwith GPU-resident storage while delegating search to the CPU.Adds GPU-accelerated IVFPQ search to the Metal backend, including optimized scanning through
M <= 64.M <= 64d <= 256M <= 16workloadsM * 256threadgroup LUTnprobe * kcandidate limitkresultsChanges
MetalDistance.metal- on demand PQ scans, large-M precomputed scan, short-list specialization, segmented top-k, and compacting grouped mergeMetalDistance.h/.mm- IVFPQ search orchestration and support forM <= 64MetalKernels.h/.mm- dispatch for cached, on-demand, short-list, and large-M scan kernelsMetalIndexIVFPQ.h/.mm- GPU scan selection, reusable buffers, centroid uploads, precomputed terms, and CPU fallbackMetalIndex.h- add theuseFloat16LUT configurationTestMetalIndexIVFPQ.mm- direct-GPU scan validation and search-parameter fallback coverageDifferences from CUDA IVFPQ
Training:
MetalIndexIVFPQ::traindelegates training to its CPU index, as introduced in #5288. CUDA can train the coarse and product quantizers on GPU. Training is a one-time cost.Add path: Coarse assignment, residual computation, and PQ encoding remain on the CPU. The encoded PQ codes are stored in GPU-resident Metal buffers. CUDA performs these operations on GPU.
Coarse quantization: The coarse quantizer search runs on the CPU. The selected lists and coarse distances are passed to the Metal scan.
Distance computation: For
d <= 256, the preferred Metal path evaluates only the PQ codes encountered while scanning a list. This avoids constructing or loading all 256 lookup entries for every subquantizer, which is especially beneficial for short IVF lists.For other supported dimensions, Metal uses the CPU IVFPQ precomputed-table decomposition. The query-independent centroid/PQ term is computed once per trained index and the query term once per batch.
Threadgroup memory: The existing cached lookup-table kernel remains available for suitable
M <= 16workloads. LargerMvalues do not expand itsM * 256threadgroup LUT, which would exceed Metal threadgroup-memory limits. Instead, they use the on-demand or streamed-precomputed scan paths.List scanning: Short lists use a 32-thread cooperative kernel with approximately 2 KiB of threadgroup scratch. Longer lists use segmented selection to maintain an exact running top-k.
Top-k merge: Per-list results are merged in groups over multiple rounds. Invalid padding is compacted when lists contain fewer than
kresults.Fallback: The optimized scan supports
M <= 64,d / M <= 256, andk <= 512. Unsupported configurations fall back to the legacy Metal path or CPU search. Search options that the GPU path cannot preserve, including selectors, scan budgets, and polysemous filtering, are forwarded to the CPU with their original parameters.Build and test
All six IVFPQ tests pass. Direct-GPU regression coverage exercises 800 combinations across
M=8,16,17,32,48,64, L2 and inner product,kthrough 512, short and long lists, both merge modes, and 64-bit IDs.