[multi vector] Add tiled MinMax MaxSim kernels - #1394
juchen-ms (partychen) wants to merge 12 commits into
Conversation
Implement MinMax8 query by MinMax4 document matrix kernels with Scalar, AVX2, AVX-512, and Neon paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use architecture intrinsics directly in the matrix kernel and restore unrelated distance and diskann-wide changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reuse diskann-wide operations where available and handle unsupported ISAs in tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pack queries with grouped slice copies, build document panels in one pass, and consume accumulators while borrowing metadata. Initialize scores within each query block and extend tail coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The public factory has a Reference ISA compatibility issue, and V3/V4 runtime paths remain unvalidated on x86-64.
Pull request overview
Adds prepared, tiled MaxSim kernels for MinMax8 queries against MinMax4 documents with ISA-specific dispatch.
Changes:
- Adds public prepared-kernel and factory APIs.
- Implements Scalar, AVX2/V3, AVX-512/V4, and ARM64 Neon kernels.
- Adds packing, fused compensation/reduction, validation, and reuse tests.
File summaries
| File | Summary | Final review note |
|---|---|---|
diskann-quantization/src/multi_vector/mod.rs |
Re-exports the factory. | — |
diskann-quantization/src/minmax/multi/mod.rs |
Registers and exports kernel modules. | — |
diskann-quantization/src/minmax/multi/kernel.rs |
Defines kernel interfaces. | — |
diskann-quantization/src/minmax/multi/factory.rs |
Provides ISA dispatch and prepared-kernel construction. | Moderate: MaxSimIsa::Reference is reported available but rejected by this factory. |
diskann-quantization/src/minmax/mod.rs |
Exposes MinMax APIs. | — |
diskann-quantization/src/matrix_kernels/maxsim/mod.rs |
Registers the MinMax matrix kernel. | — |
diskann-quantization/src/matrix_kernels/maxsim/minmax8_x_minmax4.rs |
Implements packing, tiling, compensation, SIMD kernels, and tests. | Nit: V3/V4 paths lack x86-64 runtime coverage. |
Review details
Suppressed comments (2)
diskann-quantization/src/matrix_kernels/maxsim/minmax8_x_minmax4.rs:785
- The V3/V4 implementations here are only compile-checked in the stated ARM64 validation: the ISA tests return early when the requested x86 ISA is unavailable, so the
pdep/VNNI path and its lane reduction are not exercised. Please add or run an x86-64 runtime test (or an equivalent testable emulation) covering these kernels before relying on this new code.
micro_kernel!(V3, 16, micro_kernel, {8, 7, 6, 5, 4, 3, 2, 1});
micro_kernel!(V4, 16, micro_kernel, {8, 7, 6, 5, 4, 3, 2, 1});
diskann-quantization/src/minmax/multi/factory.rs:171
MaxSimIsa::Referenceis documented as an always-available selector (is_available()returns true), and the existing MinMax implementation provides the reference MaxSim path, but this new public factory rejects it unconditionally. Callers that useis_available()to preflight an ISA therefore receiveNotSupportedfor a value reported as buildable. Please add a reference adapter (or make support for this factory explicit in the ISA API/documentation).
MaxSimIsa::Reference => Err(NotSupported {
isa,
reason: "reference kernel unavailable",
}),
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1394 +/- ##
==========================================
+ Coverage 92.65% 92.74% +0.08%
==========================================
Files 527 530 +3
Lines 103254 104352 +1098
==========================================
+ Hits 95674 96778 +1104
+ Misses 7580 7574 -6
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Mark Hildebrand (hildebrandmw)
left a comment
There was a problem hiding this comment.
There's a lot going on here. It does add a minmax8x4 kernel, but basically builds a second matrix-kernel decomposition architecture rather than extending the existing abstractions introduced in 1368.
The point of the machinery introduced there was to use enable better higher-level experimentation of cache tilings and ordering without fully relying on Miri or checked accesses for correctness debugging. All of these pieces could then be tested, reviewed, and reused independently.
Here, packing and traversal are all reimplemented manually with inconsistent bounds tracking applied. This is hard to test, validate, or reuse.
My suggestion would be to follow the design philosophy of 1368 and build the traversal mechanism out of lower-level parts (which we can then reuse for other quantization kernels). In addition, matrix kernels should not own any non-scratch state because that also makes them much more difficult to reuse.
I also have some other design concerns:
- The microkernel is currently doing a lot, and I feel that there has to be a more efficient unpacking than doing this much bit-twiddling in general purpose registers. One design worth considering incrementally unpacking B and reusing that for the whole A traversal (flipping the cache tiling order - which again is easier to do via paneled views). This can perhaps be coupled with a different permutation strategy of the A-side query for more efficiency.
ExtraWideshould as much as possible keep the associated types opaque to the caller (likeExtraWidein the f32 kernel). This makes it significantly easier to express architecture specific optimizations of coarser grained kernels without the profoundly heaverSIMDVectorand such constraints.- How many of the Miri exceptions are really needed? Miri can emulate a good number of Neon and AVX2 intrinsics.
- Directed testing of the various steps is pretty sparse. Especially given its ad-hoc mix of checked, unchecked, and
Bounds based indexing. UsingBounds based checking in a more disciplined way at least provides higher confidence of integration-based tests.
Have you considered making the A-side packing even more aggressive, splitting into groups of even and odd indices? The idea there is that it would naturally fit the order of unpacked nibbles much better and avoid a lot of the interleaving logic. Basically, a lot the micro-kernels are concerned about restoring a dimension ordering that we anyways control.
Reuse packed and unpacked views, prepare even-odd query groups in the factory, and unpack each document tile once for all query panels. Keep SIMD register types opaque and cover packing, conversion, bounds, and kernel behavior directly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Prefer complete document panels that fit 4 KiB of value scratch plus 32 metadata records on the stack, retaining cache-sized heap fallback for larger dimensions. Cover scratch boundaries and use the corrected 16 x 16 x 256 primary workload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use one Driver/PanelKernel/MicroKernel traversal for Grouped<4> and Grouped<8>, keeping packed document expansion inside the architecture backend. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mark Hildebrand (@hildebrandmw) Thanks for the detailed review. I reworked the implementation around the abstractions from #1368 in
I also added/ran directed packing, tail, bounds, driver/register, factory-equivalence, repeated-computation, empty-input, and NaN-compensation coverage. Native x86-64 V3/V4 tests, ARM64 cross-Clippy, and the V4 Miri test pass. For the 1,000-document 16x16 workload on a Xeon Platinum 8370C, the latest release measurements are:
The 250-dimensional Existing path has a scalar remainder, so I am keeping the 250- and 256-dimensional results explicitly separate. |
Reuse bounded stack scratch across query panels through the existing Driver, PanelKernel, and MicroKernel pipeline. Keep the direct path for single-panel queries and dimensions beyond the scratch budget, and isolate the scratch frame from that path. Name the input adapters BSource and LoadBGroup, and cover decoded groups, metadata, tile boundaries, and scratch-budget fallback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fold the single-use canonical BPanel constructor into BSource::panel and share full-panel and tail Visitor initialization through an always-inlined private helper. Preserve traversal, bounds checks, and computation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the Visitor source field beside b_stride in both declaration and initialization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reference Issues/PRs
Builds on the matrix-kernel abstractions introduced in #1368.
What does this implement/fix? Briefly explain your changes.
Summary
Adds prepared MaxSim kernels for MinMax8 queries against packed MinMax4 documents.
Driver -> PanelKernel -> MicroKerneltraversal for every architecture.BlockTransposed, packed/unpacked views, panel visitors, fixed-size panels, remainder handling, andBound-tracked pointers from the existing matrix-kernel infrastructure.Prepared; the matrix driver and kernels borrow query values, compensation metadata, canonical document rows, and output scores.ExtraWide.Grouped layout
Grouped<const N: usize>owns the grouping rules throughcount,from_query, andfrom_packed:Grouped<4>is used by Scalar, x86-64 V3, and ARM64 Neon. Every eight dimensions become[0, 2, 4, 6]and[1, 3, 5, 7], matching low/high nibble order.Grouped<8>is used by x86-64 V4. Query dimensions remain contiguous; four packed MinMax4 bytes are expanded with BMI2pdep_u64for 512-bit VNNI.The final group and incomplete query panels are zero padded. Compensation always uses the original, unpadded dimension.
Kernels
Grouped<4>Grouped<4>maddubs+madd+ accumulationGrouped<8>dpbusd; adjacent accumulator lanes are folded before compensationGrouped<4>UDOTThe micro-kernel enters the selected target-feature context through
run_inline, allowing LLVM to emit direct architecture instructions rather than intrinsic shim calls inside the contraction loop.API and compatibility
The API is additive. Existing MaxSim entry points are unchanged and no dependencies are added. Invalid output lengths or document dimensions return an error before modifying scores. Unavailable requested ISAs return
NotSupported.MaxSimIsa::Referenceremains unsupported by this factory.Any other comments?
Performance
Workload: 1,000 documents, 16 query vectors x 16 document vectors, with query preparation excluded. Both 250 and 256 dimensions are measured. Each executable measures 30 rounds and is run three times.
x86-64
Intel Xeon Platinum 8370C, Windows x86-64, Rust 1.97.1, release build with
-Ctarget-cpu=x86-64-v3. All compared outputs matched exactly.The existing x86-64 8-bit by 4-bit distance path has a scalar remainder at 250 dimensions but not at 256, so the 250-dimensional speedup should not be relabeled as a 256-dimensional result.
ARM64
Snapdragon X Elite X1E80100, 12 cores / 12 logical processors, Windows 11 Pro ARM64 (build 26200), Rust 1.97.1, native release build with
-Ctarget-feature=+neon,+dotprod. Measured at revisioned75906eaedef6c41e5155532ceaebee6e819c77with the Balanced power plan and no explicit CPU affinity.ARM64 methodology:
[-1, 1)usingStdRngseeded with0x1394_2026 + dimensions. Each implementation receives identical inputs.black_box.Neon's per-run medians ranged from 2.17 to 2.28 us/doc at 250 dimensions and 2.26 to 2.52 us/doc at 256 dimensions. These ranges reflect observed run-to-run variation rather than confidence intervals.
The ARM64 measurements use the same workload dimensions and document count as the x86-64 measurements, but independently generated inputs. Speedups are relative to the existing implementation on each host, not a controlled comparison between processors.
Validation
The x86-64 validation reports 47 passing MinMax tests, with Scalar, V3, V4, and Auto executing natively. The V4 Miri test passes using
diskann-wideemulation, while the native release benchmark exercises BMI2 and AVX-512 VNNI. ARM64 all-target Clippy passes.Native ARM64 validation at
ed75906additionally rancargo test -p diskann-quantization --release minmax: 46 tests passed, including Neon driver/register coverage, equivalence with the existing implementation, grouped query packing, panel tails, shape validation, empty inputs, prepared-query ownership, and NaN compensation. Scalar, Neon, and Auto execute natively; V3/V4 factory tests exercise unsupported-ISA handling on this host.