Parallel bulk insert (AddEntries) and EcoQoS opt-out in benchmarks - #29
Merged
Merged
Conversation
QvecDatabase.AddEntries(IReadOnlyList<QvecInsert>, maxDegreeOfParallelism) inserts a batch with every core linking nodes into the HNSW graph at once. Each chunk of 8,192 entries is prepared serially (slot, layer, vector/metadata/guid on disk, guid index) and then linked in parallel using hnswlib-style striped node locks (1,024 stripes, never nested) plus a reader/writer lock guarding the entry point. The whole call holds the database write lock, so readers see either the old or the new state. dop = 1 produces a byte-identical graph to serial AddEntry. Measured back to back, power throttling disabled: Cohere 100K float 370.9 s -> 58.6 s (6.3x, 1,707 inserts/s), recall@100 -0.09 pp SIFT-1M 1,145.6 s -> 142.1 s (8.1x, 7,037 inserts/s), recall@10 within 0.1 pp Parallel builds are not byte-reproducible (layers are seeded, link order is not); documented in README and docs/design-insert-parallel.md. While measuring, 12 threads only got ~4 cores' worth of CPU time. Root cause is Windows 11 EcoQoS throttling background console processes on the Balanced power plan. The benchmark now calls SetProcessInformation(ProcessPowerThrottling) at startup to opt out (--allow-throttling restores the old behaviour). All earlier README figures were measured throttled; the SIFT-1M and Cohere 100K tables are re-measured and marked as such. Benchmark gains --threads N (0 = all cores), building via AddEntries in batches of 50,000. Tests: 8 new ParallelInsertTests (recall vs serial, byte identity at dop=1, well-formed neighbour lists with bounded orphan count, duplicate ids within and across batches, growth and deleted-slot reuse, empty batch, wrong dimension). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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
Adds
QvecDatabase.AddEntries(IReadOnlyList<QvecInsert>, int maxDegreeOfParallelism = 0)— multi-threaded HNSW index construction — and fixes a measurement problem that had been silently throttling every benchmark so far.Parallel insert
Parallel.Forlinking nodes with hnswlib-style striped node locks (1,024 stripes, never nested) and a reader/writer lock around the entry point.AddEntry.dop = 1produces a byte-identical graph to a loop ofAddEntry(tested viaRawGraph).AddEntry: ids returned in input order, duplicateExternalIds skipped (within a batch and against existing data), dimension mismatch throwsQvecDimensionException.docs/design-insert-parallel.md.Measured (same machine, back to back, throttling disabled)
Trade-off, stated in README: parallel builds are not byte-reproducible (layers are seeded, link order is not). Scaling is sub-linear because ~20 % of thread time on Cohere is spent waiting on hub-node locks; the rest is memory latency.
EcoQoS discovery
12 threads initially got only ~4 cores' worth of CPU time with no kernel time, GC pauses or lock contention to explain it. Root cause: Windows 11 (Balanced plan) applies EcoQoS power throttling to console processes that are not in the foreground. The benchmark now opts out via
SetProcessInformation(ProcessPowerThrottling)at startup (--allow-throttlingrestores the old behaviour). Serial Cohere 100K went from 589 s throttled to 371 s exempt, which also explains the historic ±40 % QPS variance. All earlier README figures were measured throttled; SIFT-1M and Cohere 100K tables are re-measured and marked as such.Benchmark
--threads N(0 = all cores) builds throughAddEntriesin batches of 50,000. Report line shows build threads.Tests
8 new
ParallelInsertTests: stores every entry & recall vs serial (float + int8), byte identity at dop = 1, well-formed neighbour lists with bounded layer-0 orphan count vs serial, duplicates within batch and across calls, growth + deleted-slot reuse, empty batch no-op, wrong dimension throws.Fast suite 333 tests + Slow suite 7 green locally; build 0 warnings.
Not in scope
PartitionedQvecDatabasedoes not getAddEntriesyet.