G2 — optimize the CPU vector path (SIMD + int8 VNNI)
Status: ✅ kernels + bench + tests landed (feat/g-series), benchmarked on the dev box. VNNI is structural-only there (Zen 2 has no AVX-512). See the 2026-07-02 update below — the int8 path is now an exact rerank, the approximate IVF is gone, and the kernels moved to znippy_zoomies::vann.
Goal
Shrink the GPU↔CPU embedding/search gap. The vector index (src/vector/mod.rs) is an exact-flat ANN: every query is scored against every stored vector by cosine (a dot product over L2-normalized f32). The score loop is the hot path. We attack it with wider SIMD and a denser numeric format, runtime-detected so one binary runs everywhere.
Layers (each independently dispatched)
| layer | kernel | when |
| baseline | dot_scalar |
always sound; the correctness oracle |
| f32 SIMD | dot_avx512 (16-wide FMA) → dot_avx2 (8-wide FMA) |
runtime is_x86_feature_detected! |
| int8 (new) | dot_i8_vnni (vpdpbusd, 64 int8 MAC/instr) → dot_i8_scalar |
vnni_available() |
| multicore | scoped-thread split → per-thread top-k merge | corpus > 2·1024 rows |
The f32 SIMD + multicore layers pre-existed; G2 added the int8/VNNI layer, the batched per-query setup, and the bench.
int8 quantization (why it's cheap and accurate)
Stored vectors are L2-normalized, so every component is in [-1, 1]. Quantizing by Q = 127 round-to-nearest (clamped to [-127,127]) maps them to i8 with no scale to track per vector. The cosine of two quantized vectors ÷ Q² recovers the f32 cosine to ≲4e-2 absolute — and crucially the ranking is preserved (the test asserts identical top-1 and bounded score error). Two payoffs:
- 4× less memory traffic. The score loop is memory-bound on a big corpus;
i8rows are a quarter the bytes off32, and a cache line covers 4× the components (cache-blocking falls out for free). - VNNI density. AVX-512
vpdpbusddoes 64 int8 multiply-accumulates into int32 per instruction vs 16 f32 FMA lanes.vpdpbusdis unsigned×signed, so we keep the rowi8and bias the query tou8(q+128), correcting withΣ q·r = Σ (q+128)·r − 128·Σ r(the row's integer sumΣ ris precomputed once at quantization time).
Benchmark — nornir vector bench / bench_kernels
Pure-Rust timer (no criterion dep). It builds a deterministic n×dim corpus, scores one query through every kernel, and measures the int8 error against the scalar f32 reference — so the bench doubles as a correctness gate.
Dev box: AMD Threadripper PRO 3975WX (Zen 2 — AVX2+FMA, NO AVX-512/VNNI), 768-dim, n=200k, 30 iters:
| kernel | throughput | speedup vs scalar | max_err |
| scalar | 1.5 Mdps | 1.00× | — |
| simd (avx2+fma) | 6.7 Mdps | 4.48× | 9.5e-7 |
| int8 (scalar) | 5.7 Mdps | 3.79× | 4.2e-2 |
Stable across corpus sizes (20k cache-warm → 1M memory-bound: 4.4–4.5× SIMD, 3.8× int8).
GPU↔CPU ratio shrinking
A CUDA/ROCm GPU embeds/searches on the order of ~100× a scalar CPU. The G2 CPU path turns that scalar baseline into 4.48× (AVX2) — closing the gap to ~22×. On AVX-512 silicon dot_avx512 is 16-wide (another ~2× over AVX2) and vpdpbusd would push the int8 path past the AVX2 f32 number, shrinking the ratio further toward single digits. That VNNI step is structural-only here — the dev CPU lacks AVX-512, so the code compiles and is unit-tested via the scalar-i8 fallback, but the VNNI instruction can't be timed on this box.
What is benchmarked vs structural-only
- Benchmarked: scalar, AVX2+FMA f32, and scalar-int8 — real numbers above.
- Structural-only:
dot_avx512anddot_i8_vnni— compiled and correct (the runtime dispatch + unit tests cover the fallback), but not executable on the Zen 2 dev box.
Correctness (inject-assert law)
simd_kernel_matches_scalar_dot— SIMD f32 dot == scalar within 1e-5 on a real 768-dim pair.int8_matches_f32_within_tolerance— int8 search ranks the same top-1 and scores within 4e-2 of the f32 path on an injected 200×768 corpus.bench_kernels_reports_real_numbers— every kernel returns nonzero µs/Mdps and stays within tolerance.
2026-07-02 — exact-only, exact int8 rerank, and where we stand vs academia
Two approximate paths removed. CozoDB HNSW and the Ragnar-style int8 IVF (ragnars_emb) are both gone. As of 2026-07-02 nornir's vector search was exact-only: the flat VectorIndex oracle plus a provably-lossless int8 prefilter → f32 exact rerank. (Update: a vendored, rayon-free HNSW ANN in znippy_zoomies::vann later landed as a scale-gated live fast path — built only at ≥ HNSW_MIN_ROWS (4096) and disable-able via NORNIR_VECTOR_HNSW, with the flat scan kept as the exact oracle. That is the vendored znippy HNSW, not the removed external CozoDB engine; see vector.md.) The int8 stage no longer merely "preserves ranking to ≲4e-2" — it keeps a candidate band (int8_score ≥ τ − 2E, E = quantization error bound) that provably contains the true top-k, then the f32 rerank returns byte-identical results to the flat oracle (test wired_i8_search_matches_exact_f32). The kernels now live in znippy_zoomies::vann (VectorIndex, score_i8_batch, quantize_i8), fork-join parallel across physical cores via gatling_forkjoin; nornir re-exports them.
Embedding vs search are two different speedups — do not conflate them:
- Embedding (running the model to make the vectors) got ~2–4× from batched length-bucketed forwards, bit-identical (
max_abs_diff = 0e0). Seeembedder-selection.md. This is a transformer forward pass, not a scan. - Search (scanning the stored vectors) is the int8/VNNI + fork-join path documented here.
Where we stand vs the academic state of the art (exact CPU kNN)
A web sweep (2026-07-02) positioned us against the published exact-search CPU literature. CWI Amsterdam is effectively the only institution publishing exact CPU kNN numbers, so they are the peer to beat:
- CWI "Bang for the Buck: Vector Search on Cloud CPUs" (Kuffo & Boncz, 2025, arXiv:2505.07621): exact CPU search is "mostly data-access bound". USearch full-scan f32 SIFT 1M×128 ≈ 108 qps (Zen4/SPR); OpenAI ~1M×1536 ≈ 5–7 qps. Effective ceiling ~10–34 GB/s/core — a memory-bandwidth wall, not a FLOP wall.
- CWI PDX (SIGMOD 2025, arXiv:2503.04422): a vertical dimension-partitioned layout beats hand-written horizontal SIMD by ~40% (scalar auto-vectorized).
- Google TPU-KNN (NeurIPS 2022, arXiv:2206.14286): single-query distance is O(1) arithmetic intensity ⇒ memory-bandwidth-bound; batching into GEMM is the only route to hardware peak.
- Stanford DAWN, exact MIPS via blocked matmul (ICDE 2019, arXiv:1706.01449): ~40× over naïve inner products.
Verdict: on the kernel we are top-notch — parity-or-better with every published hand-SIMD baseline (FAISS/USearch/SimSIMD), and our int8 prefilter → lossless f32 rerank is the same architecture as FAISS IndexRefine / ScaNN reorder / Elastic-Lucene, but with a stronger guarantee (provably exact, not approximate). int8 cuts bytes 4× exactly where the bandwidth bound bites.
Two levers left (neither started; they stack; NEITHER is Ragnar)
Both are exact — unlike the removed IVF, they never skip a vector.
- int8 VNNI accumulators 4 → 8–12 (the one that helps us).
VPDPBUSDhas 5-cycle latency and dual-issues on server silicon (Cascade Lake / Sapphire Rapids / Emerald Rapids). 4 accumulators saturate a single-VNNI-port client (~80%) but only ~40% of a dual-port server. Bumping to 8–12 (Little's law: latency ÷ recip-throughput) saturates both ports. Bit-exact (integer add is associative). Biggest effect in the cache-resident regime — nornir's common case (tens of thousands of chunks). Helps every single-query search. Source: uops.infoVPDPBUSD. - Batch queries into a blocked GEMM (situational). Same math, byte-identical result — it reads the corpus once per batch instead of once per query, flipping bandwidth-bound → compute-bound. Documented 25–64× on CPU (Stanford 40×, McCormick 25×, droyed 64–86×). Only helps when many queries are issued at once — nornir search is usually one query, so this is lower priority than (1). Composes with int8 (int8 GEMM via VNNI/AMX). It is not the Ragnar IVF: IVF was approximate (skips cells, recall < 100%); GEMM batching scans every vector and is exact.
Files
znippy_zoomies::vann(src/vann/mod.rs) —dot_i8_scalar,dot_i8_vnni(4 accumulators today; lever 1 = 8–12),quantize_i8,score_i8_batch,search_i8_rerank/search_auto(exact int8→f32 rerank),VectorIndex,vnni_available,bench_kernels/BenchReport/KernelTiming, + tests.nornir/src/vector/mod.rs— re-exports thevannkernels; embedder glue (chunk/store/embed*/registry) stays in nornir.nornir/src/bin/nornir.rs—nornir vector bench(CLI parity with the bench).