fix(gpu_hnsw): bound visited-bitmap VRAM via nq-chunked layer-0 search#9
Open
devin-ai-integration[bot] wants to merge 1 commit into
Open
fix(gpu_hnsw): bound visited-bitmap VRAM via nq-chunked layer-0 search#9devin-ai-integration[bot] wants to merge 1 commit into
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
The layer-0 search scratch is grow-only per pool slot: the visited bitmap is nq * ceil(N/32) * 4 bytes and, once grown, is never released. Under high search concurrency (one bitmap per concurrent pool slot) on large segments this exhausted device memory -- 16 concurrent batch=512 searches on a ~538M-row segment grew the pool to ~97 of ~98 GB and OOM'd every subsequent allocation. Process queries in nq-chunks so the bitmap is bounded regardless of batch size / concurrency. gpu_hnsw_bitmap_chunk() derives the per-launch query count from a VRAM cap (default 256 MiB, env-tunable via GPU_HNSW_BITMAP_MB / GPU_HNSW_BITMAP_BYTES); GpuHnswSearchScratch::ensure() sizes the bitmap for one chunk and the search launches per chunk with caller-offset base pointers. Queries are independent in HNSW search and every per-query buffer is indexed by the chunk-local block index, so chunking is result-invariant (no kernel changes). Small segments yield chunk == nq -> a single pass, identical to prior behavior. Signed-off-by: premal <premal@6sense.com> Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This was referenced Jul 20, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a device-memory OOM in GPU HNSW search caused by the grow-only search scratch. The per-slot visited bitmap is
nq * ceil(N/32) * 4bytes and, once grown, is never released. Under high search concurrency (one bitmap per concurrent pool slot) on large segments this exhausts VRAM — 16 concurrentbatch=512searches on a ~538M-row segment grew the pool to ~97 of ~98 GB and OOM'd every subsequent allocation (surfaced during the v90 Blackwell validation).Fix: process queries in nq-chunks so the bitmap is bounded regardless of batch size / concurrency. No kernel changes — every per-query buffer (queries, entry points, neighbors, distances, visited bitmap, BF worklist) is already indexed by the chunk-local block index (
blockIdx.x/ worklist entry), so the host just launches per chunk against caller-offset base pointers. Queries are independent in HNSW, so chunking is result-invariant.gpu_hnsw_bitmap_chunk(nq, N)derives the per-launch query count from a VRAM cap (default 256 MiB, env-tunable viaGPU_HNSW_BITMAP_MB, orGPU_HNSW_BITMAP_BYTESfor byte-precise control in tests). Always in[1, nq]; returnsnqwhen the batch already fits, so small segments run a single pass — behavior identical to before.GpuHnswSearchScratch::ensure()now sizes the bitmap for one chunk (bm_nq) instead of the full batch — this is the actual allocation that no longer grows unbounded. The cap only ever lowers the chunk vs. whatensure()sized, so the buffer is always large enough for the launched chunk.effinalization block is query-count independent and is hoisted to run once before the chunk loop; thecudaFuncSetAttributehigh-water tracking (per<DataT,QueryT,USE_DP4A,HAS_FILTER>) is unchanged.All existing semantics preserved: FP32 / FP16 / BF16 / generic-INT8 / native-INT8-DP4A, L2 / IP / COSINE, filtered search (two-tier beam + alpha gate + per-query BF fallback), up-front brute force, valid IDs / sentinels, CPU-HNSW parity.
Parity coverage lands with the knowhere re-vendor (this faiss tree only builds inside knowhere): a
[gpu_hnsw_chunk_parity]test runs each search twice (default cap = single pass vs.GPU_HNSW_BITMAP_BYTESforced tiny = many chunks) and asserts identical ids/distances across fp32 & int8-DP4A, unfiltered / mid-ratio filtered / up-front-BF (99%) for L2/IP/COSINE.Related PRs
Link to Devin session: https://6sense.devinenterprise.com/sessions/55dab0bd33c346df99b5818b30059342
Requested by: @premal