Found during #39 implementation (E2E bench, real EmbeddingGemma d=768, n=2000, 100 queries, release)
| mode |
B/vec |
r@1 |
r@10 |
ms/q |
| plain 4-bit (NEON) |
514 |
0.910 |
0.958 |
0.92 |
| plain 5-bit (scalar) |
642 |
0.970 |
0.979 |
4.27 |
| plain 6-bit (scalar) |
770 |
0.960 |
0.980 |
4.29 |
| 4-bit+residual (NEON) |
1028 |
0.990 |
0.984 |
1.80 |
Recall/size goals of #39 are met, but 5/6-bit scoring currently runs the portable scalar unpack loop instead of the batch-4 NEON kernel, costing ~4.6x query latency vs 4-bit.
Scope
Notes
Found during #39 implementation (E2E bench, real EmbeddingGemma d=768, n=2000, 100 queries, release)
Recall/size goals of #39 are met, but 5/6-bit scoring currently runs the portable scalar unpack loop instead of the batch-4 NEON kernel, costing ~4.6x query latency vs 4-bit.
Scope
score_neon_wideon aarch64: unpack 5/6-bit codes to bytes (or 16-bit indices), LUT gather from 32/64-entry centroid tables, widen+accumulate — keep bit-identity withscore_wide_scalar(existing bitwise parity test pattern).score_neon4whenbits != 4.use_avx2 && bits != 4gate already exists).neon_matches_scalar_bitwise-style parity test extended to 5/6-bit.Notes