Skip to content

perf: add SIMD-accelerated u8 dot product for SQ distance#6506

Merged
westonpace merged 11 commits into
lance-format:mainfrom
justinrmiller:feat/u8-dot-product-simd
Apr 16, 2026
Merged

perf: add SIMD-accelerated u8 dot product for SQ distance#6506
westonpace merged 11 commits into
lance-format:mainfrom
justinrmiller:feat/u8-dot-product-simd

Conversation

@justinrmiller

@justinrmiller justinrmiller commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add AVX-512 VNNI and AVX2 backends for unsigned int8 dot product with runtime CPU feature detection and automatic fallback to scalar
  • Replace the unoptimized Dot<u8> impl (which had an explicit TODO) with dispatched SIMD kernel
  • All existing callers including SQ distance computation benefit automatically with zero changes to lance-index

Details

New file: rust/lance-linalg/src/distance/dot_u8.rs

Three backends selected at runtime via OnceLock + is_x86_feature_detected!:

Backend Instruction Elements/iter CPU
AVX-512 VNNI VPDPBUSD + XOR-0x80 bias trick 64 Ice Lake+ / Zen 4+
AVX2 VPMADDWD on zero-extended u16 32 Haswell+ / Zen 1+
Scalar portable reference - any (including ARM)

The VNNI bias trick

VPDPBUSD expects one unsigned and one signed operand, but SQ vectors are u8×u8. We XOR one operand with 0x80 to map it to the signed domain, then correct by adding 128·Σa at the end. The correction uses VPSADBW which runs on execution port 5 while VPDPBUSD runs on port 0 — they execute in parallel every cycle, making the correction effectively free.

SQ integration (automatic)

SQDistCalculator::distance() already calls dot_distance()u8::dot() for Dot distance type. Replacing the Dot<u8> body is the only change needed.

Benchmarks

Ryzen 4500 (AVX2, no VNNI)

1M total u8 elements, varying vector dimension. Scalar baseline vs AVX2-dispatched path:

Dimension Scalar Dispatch (AVX2) Speedup
128 51.02 µs 58.25 µs 0.88x (dispatch overhead dominates)
256 44.96 µs 38.62 µs 1.16x
512 42.82 µs 28.27 µs 1.51x
1024 41.00 µs 25.17 µs 1.63x

AVX2 delivers up to 1.63x throughput at dim=1024. At dim=128 the OnceLock dispatch and AVX2 loop setup overhead exceeds the SIMD gains on short vectors. AVX-512 VNNI (Ice Lake+ / Zen 4+) is expected to show larger gains with 64 elements/iter.

Apple M4 (ARM64, scalar fallback)

On ARM64 the dispatch falls back to scalar, so both paths perform identically (~13 µs at dim=1024). A follow-up ARM NEON UDOT path would bring SIMD gains to Apple Silicon.

Out of scope (follow-up)

  • L2/Cosine u8 SIMD optimization (different kernel: Σ(a-b)²)
  • Native VPDPBUUD (unsigned×unsigned, Sierra Forest+) — too new for stable Rust
  • ARM NEON UDOT path
  • Precomputed norms for SQ L2/Cosine (requires storage format change)

Test plan

  • Unit tests: random inputs across 18 vector sizes (0-4097), boundary values (all 0s, all 255s, alternating), one-sided zeros, all-ones patterns
  • Each backend tested independently against scalar reference (with #[cfg] guards for missing CPU features)
  • Existing dot tests continue to pass (9/9)
  • cargo clippy -p lance-linalg --tests --benches -- -D warnings clean
  • Benchmark on x86_64 with AVX2: cargo bench --bench dot -p lance-linalg -- "Dot\(u8"

🤖 Generated with Claude Code

Add AVX-512 VNNI and AVX2 backends for unsigned int8 dot product,
with runtime CPU feature detection and automatic fallback to scalar.
This accelerates Scalar Quantization (SQ) dot-product distance
computation which operates on u8-encoded vectors.

Backends (selected at runtime, best available wins):
- AVX-512 VNNI: VPDPBUSD with XOR-0x80 bias trick, 64 elements/iter
- AVX2: zero-extend to u16 + VPMADDWD, 32 elements/iter
- Scalar: portable fallback

The VNNI path uses the algebraic trick of biasing one operand into
the signed domain (XOR 0x80) and correcting with 128*sum(a) at the
end. The correction uses VPSADBW which runs on a different execution
port than VPDPBUSD, making it effectively free.

Replaces the unoptimized scalar Dot<u8> impl (which had an explicit
TODO for SIMD optimization). All existing callers including SQ
distance computation benefit automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
justinrmiller and others added 8 commits April 14, 2026 01:27
Add two aarch64 backends to the u8 dot product dispatch:

- NEON dotprod (UDOT): native u8×u8→u32 via inline asm, 16 elem/iter.
  Available on Apple M1+, Graviton2+, Cortex-A76+ (ARMv8.2-A+dotprod).
  Uses inline asm because vdotq_u32 is not yet stable in Rust std::arch.

- Base NEON: widening multiply (vmull_u8→u16) + pairwise accumulate
  (vpadalq_u16→u32), 16 elem/iter. Always available on aarch64.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Trim per-function doc comments and inline comments to match the
concise style used in sibling distance modules, while preserving
the detailed module-level documentation explaining the VNNI bias
trick and backend selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
LLVM auto-vectorizes the scalar loop with NEON on aarch64, so
explicit NEON intrinsics provide no benefit on Apple Silicon.
The real win is AVX-512 VNNI on Intel where the compiler cannot
auto-generate VPDPBUSD.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Cast to *const __m512i instead of *const i32 for _mm512_loadu_si512,
fixing compilation on CI (MSRV, linux, windows). Also fix cargo fmt.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Compare scalar vs SIMD-dispatched u8 dot product across 128/256/512/1024
dimensions to measure gains from AVX2 and AVX-512 VNNI backends.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

@BubbleCal BubbleCal 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.

Nice work!

@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.49669% with 37 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-linalg/src/distance/dot_u8.rs 75.33% 35 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants