镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[muilt-vetor] Add standalone MinMax8 x MinMax4 MaxSim kernel - #1494

Merged
juchen-ms (partychen) merged 5 commits into
microsoft:mainfrom
partychen:minmax_kernel
Oct 23, 2026
Merged

juchen-ms (partychen) merged 5 commits into
microsoft:mainfrom
partychen:minmax_kernel

Conversation

@partychen

@partychen juchen-ms (partychen) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
  • Does this PR have a descriptive title that could go in our release notes?
  • Does this PR add any new dependencies?
  • Does this PR modify any existing APIs?
  • Is the change to the API backwards compatible?
  • Should this result in any changes to our documentation, either updating existing docs or adding new ones?

No new dependencies. Public additions are backward-compatible SIMD trait implementations. The matrix kernel and BitSlice::decode_into remain pub(crate); existing distance APIs and canonical MinMax storage are unchanged. Contracts and safety requirements are documented inline.

Reference Issues/PRs

Reworks the matrix-kernel portion of #1394 into a standalone implementation. Uses SIMD reinterpretation and maddubs support from #1445 and #1479.

What does this implement/fix? Briefly explain your changes.

Adds an internal matrix MaxSim kernel for MinMax A/B inputs with 1-8 bits per code, supporting all 64 input-width combinations through the available numeric layouts. A is packed once and reused across B comparisons; each call writes one compensated maximum inner product per A row without materializing the full pairwise score matrix.

Preparation and execution

  • Architecture-independent A packing uses From<u8> and stores coefficients per physical panel. Internal A/B views bind codes, coefficients, and dimensions together instead of passing unrelated slices through the kernel.
  • The existing Drive -> PanelKernel -> MicroKernel structure prepares each B tile once and reuses it across A panels.
  • B decoding is shared through BitSlice<'_, NBITS, Unsigned, Dense>::decode_into, not MinMax-local bit-width special cases. It applies conversion while writing, ignores unused trailing bits, and rejects a length mismatch before writing or invoking the callback.
  • B scratch stores only the three compensation coefficients needed for computation and zero-pads code rows to PACK. Cache estimates use the prepared payload sizes. Physical stride is separate from logical K, so contraction can load complete packs while compensation uses the original dimension.
  • ExtraWide<MR, PACK> exposes numeric backend operations without input-width parameters. Concrete load/store operations establish register extents. The Driver derives K and A's width from the packed input and retains the integer-range and B-width checks.
  • Driver panel widths are NR=2, 4, or 8, expressed by concrete PanelKernel implementations. Each handles only its applicable remainders; unsupported NR configurations do not implement the required trait.

Available layouts

All layouts accept A_BITS=1-8. MR counts A rows per microkernel; PACK counts contraction elements processed together. These are available layouts, not an automatic selection policy.

Backend/path MR PACK Prepared A / B Supported B_BITS
Scalar byte 16 4 u8 / i8 1-7
Scalar general 8, 16 2 i16 / i16 1-8
V3 byte 8, 16 4 u8 / i8 1-6
V3 general 8, 16 2 i16 / i16 1-8
V4 byte 16 4 u8 / i8 1-7
V4 general 16 2 i16 / i16 1-8
Neon + dotprod 8, 16 4 u8 / u8 1-8

The byte limits prevent signed-byte misinterpretation and, on V3, saturation of intermediate i16 sums. They guarantee correctness for the full A code range, not just a particular A_BITS. Full-range eight-bit B uses the i16 path on Scalar/x86 and unsigned byte dots on Neon; widening does not requantize the inputs.

The original V3 byte layout remains alongside the general path. MR=16 V3 compensation finishes one native eight-row half before processing the other, and MR=8 alternatives are available. These changes are not a claim that generated code is spill-free or that one layout is optimal for every workload.

Behavior and tests

  • Each drive overwrites C with unnegated maximum inner products. NaN candidates are ignored; empty B or all-NaN comparisons leave negative infinity.
  • The internal Driver requires nonempty A, positive K, and matching dimensions/output length.
  • K is bounded by i32::MAX / ((2^A_BITS - 1) * (2^B_BITS - 1)): 561,433 for 8x4/4x8 and 33,025 for 8x8. Conversion to f32 may still round.
  • Tests separate format preparation from numeric execution: all input widths are covered in packing/decoding, all 64 width pairs have arithmetic-limit checks, and representative end-to-end cases cover tiling, tails, compensation, empty B, and smaller MR layouts.
  • Direct microkernel cases cover NaNs and preservation of existing maxima. Numeric references are cross-checked against the existing pairwise implementations. The former per-width/per-backend full-Driver sweep and redundant ownership/reuse test were removed to reduce test execution and monomorphization cost; necessary layout/remainder specializations remain.

Any other comments?

Scope: Factory/ISA selection, public prepared-query distance integration, distance negation/no-match adaptation, externally owned/FFI layouts, relocation of the pairwise implementation into fallback, and benchmark integration remain follow-up work. No temporary performance or code-generation tooling is included.

Validation at 0977338a -- Windows x86-64:

Command Result
cargo fmt --all --check Passed
cargo test -p diskann-quantization --lib --quiet -- bits::slice::tests::test_decode_into matrix_kernels::maxsim::packed_minmax_x_unpacked_minmax Passed: 28 test functions (3 decoder tests and 25 kernel tests)
cargo clippy --workspace --all-targets --quiet -- -D warnings Passed before committing the same source tree
cargo clippy -p diskann-quantization -p diskann-wide --all-targets --target aarch64-pc-windows-msvc --quiet -- -D warnings Passed; ARM cross-compilation/lint only

Scalar and V3 execute locally; V4 test bodies are conditionally skipped on this host but still compiled. Neon execution, SDE/Miri, and coverage were not rerun for this head. Earlier CI/coverage results and exploratory performance timings are not claimed as validation of this revision.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new unsafe SIMD paths require cross-platform validation and final human review.

0 open findings

What changed in this PR

Adds a standalone quantized MaxSim kernel to DiskANN’s matrix kernels, leaving distance API integration for a follow-up.

Changes:

  • Implements packed-A, B-tiled computation for Scalar, V3, V4, and Neon.
  • Adds compensated inner products and NaN-aware maximum reduction.
  • Adds supporting SIMD conversions and regression coverage.
File Description
diskann-wide/​src/​reference.rs Adds reference u32-to-f32 conversion.
diskann-wide/​src/​emulated.rs Adds and tests 16-lane i32-to-f32 conversion.
diskann-wide/​src/​arch/​x86_64/​v4/​conversion.rs Adds and tests AVX-512 conversion.
diskann-wide/​src/​arch/​x86_64/​v3/​conversion.rs Adds and tests split-vector conversion.
diskann-wide/​src/​arch/​aarch64/​u32x4_.rs Adds and tests Neon conversion and reinterpretation.
diskann-quantization/​src/​matrix_kernels/​maxsim/​packed_minmax8_x_unpacked_minmax4.rs Implements the kernel, backends, and regression tests.
diskann-quantization/​src/​matrix_kernels/​maxsim/​mod.rs Registers the kernel pending integration.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.83538% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.11%. Comparing base (2ff131f) to head (76189d3).

Files with missing lines Patch % Lines
..._kernels/maxsim/packed_minmax_x_unpacked_minmax.rs 94.63% 77 Missing ⚠️
diskann-quantization/src/bits/slice.rs 97.32% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1494      +/-   ##
==========================================
+ Coverage   91.06%   91.11%   +0.05%     
==========================================
  Files         591      592       +1     
  Lines      122196   123745    +1549     
==========================================
+ Hits       111275   112754    +1479     
- Misses      10921    10991      +70     
Flag Coverage Δ
miri 91.11% <94.83%> (+0.05%) ⬆️
unittests 90.82% <94.77%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
diskann-wide/src/arch/x86_64/v3/conversion.rs 100.00% <100.00%> (ø)
diskann-wide/src/arch/x86_64/v4/conversion.rs 0.00% <ø> (ø)
diskann-wide/src/emulated.rs 100.00% <ø> (ø)
diskann-quantization/src/bits/slice.rs 97.32% <97.32%> (-0.01%) ⬇️
..._kernels/maxsim/packed_minmax_x_unpacked_minmax.rs 94.63% <94.63%> (ø)

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Support all 1-8 bit A/B combinations through shared packing, tiled
execution, compensation and maximum-inner-product reduction.

Retain the tuned byte backends alongside full-range i16 paths, keep
quantization widths out of the numeric microkernels, and add the
required SIMD conversions and correctness coverage.

Leave factory, fallback and public distance integration to follow-up PRs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74814a43-c576-493a-8783-585ef48c3f1c
@partychen juchen-ms (partychen) changed the title Add standalone MinMax8 x MinMax4 MaxSim kernel [muilt-vetor] Add standalone MinMax8 x MinMax4 MaxSim kernel Oct 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Junkui. This is a simplification over the earlier PR! There still is some coupling between the lower-level kernels and some of the higher data preparation stuff.

A few other things that stand out to me:

  • It makes heavy use of post-monomorphization errors via const { assert!(...) }. These aren't necessarily wrong, but they are kind of hard to debug when they trigger, don't compose with higher level code well, and to me often indicate that there is another way of structuring the code so they aren't needed. I'm not saying get rid of them, but that over reliance could indicate a missing abstraction.
  • Why are 16-wide accumulators used for all architectures? This seems like it could be problematic for Neon or Scalar. Have you checked that the generated code isn't spilling too much and is reasonable?

I know a few other folks are planning on reviewing this as well - let's see what they have to say.

@partychen
juchen-ms (partychen) force-pushed the minmax_kernel branch 3 times, most recently from e360602 to 2538cc7 Compare October 17, 2026 07:45
@partychen

Copy link
Copy Markdown
Contributor Author

Thanks Junkui. This is a simplification over the earlier PR! There still is some coupling between the lower-level kernels and some of the higher data preparation stuff.

A few other things that stand out to me:

  • It makes heavy use of post-monomorphization errors via const { assert!(...) }. These aren't necessarily wrong, but they are kind of hard to debug when they trigger, don't compose with higher level code well, and to me often indicate that there is another way of structuring the code so they aren't needed. I'm not saying get rid of them, but that over reliance could indicate a missing abstraction.
  • Why are 16-wide accumulators used for all architectures? This seems like it could be problematic for Neon or Scalar. Have you checked that the generated code isn't spilling too much and is reasonable?

I know a few other folks are planning on reviewing this as well - let's see what they have to say.

Thanks for the feedback. I’ve moved SIMD-specific operations behind ExtraWide, removing the lane-count const assertions from the generic microkernel. The arithmetic-safety checks remain.

I’ve also added MR=8 alternatives for Neon, V3, and Scalar’s i16 path rather than requiring 16-wide accumulators everywhere. Earlier V3 assembly inspection showed register-resident accumulators in the full contraction loop, but stack traffic around compensation and tails; I’m not claiming all configurations are spill-free.

PackedA remains pub(crate) for now. I’ll incorporate the other reviewers’ feedback as well.

Move bulk decoding into BitSlice and zero-pad prepared B rows for a unified pack loop. Decouple backend representations behind fixed-size operations, retain smaller row-panel choices, and replace redundant test sweeps with directed coverage.

Make B preparation independent of backend and row-panel types by accepting a code converter. Keep backend capability validation at the Driver construction boundary.

Give each backend the full microkernel compensation and reduction stage through max_into with borrowed coefficients. Keep MR16 contraction intact while V3 reduces native eight-row halves. Define backend operations explicitly and share the compensation formula; preserve floating-point order and NaN semantics.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use From<u8> for architecture-independent A packing. Project canonical B metadata into the existing Coefficients type at preparation, preserving dimension checks and updating cache footprints and focused regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have structural maintenance concerns. The lowering infrastructure either reverifies constraints that the caller should have already established (via asserts), or through slicing, or through tests rather than making the structure easy to follow.

There's also a pretty hefty monomorphization tax for all the different parameters run under test that I'm not thrilled about.

If your team is willing to accept all maintenance associated with this code, I'll sign off.

Comment thread diskann-quantization/src/bits/slice.rs Resolved
Comment thread diskann-quantization/src/bits/slice.rs Resolved
Comment thread diskann-quantization/src/bits/slice.rs Resolved
@partychen

Copy link
Copy Markdown
Contributor Author

Thanks, Mark. I pushed another cleanup. K now comes from packed A instead of being passed separately, A’s coefficients are prepared per panel, and B reuses the existing unpacked traversal. I also reduced the full-driver test combinations and removed duplicate test setup.

Clarify BitSlice decode_into test names without changing test behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74814a43-c576-493a-8783-585ef48c3f1c
Reuse the u32x4-to-f32x4 conversion and tests from main, retain the unsigned-byte reinterpretation and composed casts, and remove duplicate conversion definitions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74814a43-c576-493a-8783-585ef48c3f1c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience - I think there's still a lot of work that can be done within all of matrix_kernels to make these less spooky. But let's unblock the work gated behind this.

@partychen
juchen-ms (partychen) merged commit 8a9ff2d into microsoft:main Oct 23, 2026
31 checks passed
Mark Hildebrand (hildebrandmw) added a commit that referenced this pull request Oct 26, 2026
# DiskANN v0.61.0

## Highlights

- **Quantization:** Added 3-bit spherical quantization with AVX2
acceleration, faster
  spherical encoding, and NEON float32 MinMax kernels.
- **In-memory indexing (experimental):** Added product quantization and
1-8-bit scalar
quantization, unified quantized representations, and AArch64
prefetching.
- **Multi-vector search:** Added `i8 x i8` and MinMax8 x MinMax4 MaxSim
kernels.
- **Fixes:** Preserve virtual start-point out-edges when writing disk
indexes, correctly
round-trip scalar quantization configurations without standard
deviation, and support
  zero-column mutable matrix iterators.

## Breaking changes and upgrading

Changes are concentrated in quantization APIs and experimental/tooling
crates.

* PQ training (#1467) - Training now returns `BasicTable`.
`SimplePivots` was removed.
Replace `flatten()` with `view_pivots().as_slice()` and use
`pivots_for(chunk)` for
  per-chunk access.
* PQ distance tables (#1458) - Change `ProcessInto` bounds to
`ProcessInto<f32>` and calls
from `process_into::<Metric>(...)` to `process_into::<Metric,
f32>(...)`.
* MaxSim kernels (#1429) - Generic kernel/erasure code now requires `T:
MaxSimElement` and
score buffers of `T::Score` (`f32` for floats, `i32` for `i8`). Update
`build_max_sim`
  error handling from `NotSupported` to `BuildMaxSimError`.
* `diskann-inmem` (#1485) - Replace `repr::Spherical` with
`repr::Quantized`. Wrap the
quantizer in `repr::quantized::spherical::Spherical::new(...)` when
configuring it.
  Configuration and `Rerank` types now live under `repr::quantized`.
* `diskann-benchmark-runner` (#1475) - `input::Registered` and public
`Registry::input` were
removed. Use the concrete `Input` implementation's `tag()` and
`example()` methods instead.

## All Changes
* Fix serialization round trip for QuantizationType::SQ without standard
deviation by @arrayka in #1461
* i8 x i8 MaxSim Kernels by @suri-kumkaran in
#1429
* Adding `subview_mut` to `MatrixMut` by @arkrishn94 in
#1463
* Add InsertResult::SuccessUpdate by @metajack in
#1449
* [diskann-quantization] PQ Infrastructure for `diskann-inmem` by
@hildebrandmw in #1458
* [diskann-quantization] Teach PQ training to return a `BasicTable`. by
@hildebrandmw in #1467
* [deps] Run `cargo-update` by @hildebrandmw in
#1452
* Support zero-column mutable matrix iterators by @arrayka in
#1462
* [diskann-quantization] Use `f32::NAN` instead of `core::f32::NAN`. by
@hildebrandmw in #1471
* [diskann-wide] Add `V4::new_checked_miri_uncached`. by @hildebrandmw
in #1480
* [diskann-wide] Add maddubs intrinsic wrappers to V3 and V4 by
@partychen in #1479
* Bump the github-actions group across 1 directory with 5 updates by
@dependabot[bot] in #1482
* [benchmark-runner] Simplify Input representation by @hildebrandmw in
#1475
* [diskann-quantization] Optimize spherical encoding scale search by
@xinyuwen2 in #1478
* [diskann-garnet] Implement XVCREATE, XVIMPORT by @metajack in
#1455
* [diskann-garnet] Support configurable start points and allow importing
start points by @metajack in
#1464
* [diskann-garnet] Add spherical 2-bit and 4-bit quantization by
@metajack in #1465
* [deps] Address depandabot issue 32 by @hildebrandmw in
#1496
* [diskann-disk] Preserve virtual start point out-edges when writing the
disk index by @doliawu in #1483
* [diskann-quantization] Add Neon F32 Minmax Kernels by @pfoxARM in
#1431
* [diskann-garnet] Add macOS arm64 support to NuGet publishing by
@metajack in #1495
* [diskann-disk] Require start points at index initialization. by
@hildebrandmw in #1497
* Expose metric-aware quantizer centroid distance by @cherryrhea in
#1395
* [diskann-inmem] Add support for Product Quantization by @hildebrandmw
in #1469
* Adds aarch64 prefetching into diskann-vector and diskann-inmem by
@pfoxARM in #1408
* [diskann-inmem] Add scalar quantization by @hildebrandmw in
#1484
* Bump the github-actions group with 3 updates by @dependabot[bot] in
#1505
* [diskann-inmem] Spherical Quantization Round 2 by @hildebrandmw in
#1485
* [diskann-quantization] Add SphericalQ 3-bit support and AVX2 kernel by
@xinyuwen2 in #1474
* [muilt-vetor] Add standalone MinMax8 x MinMax4 MaxSim kernel by
@partychen in #1494

## New Contributors
* @cherryrhea made their first contribution in
#1395

**Full Changelog**:
v0.60.0...v0.61.0

Co-authored-by: Mark Hildebrand <mhildebrand@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants