Skip to content

Recall cliff (up to 65pp) on sparse data at weak-block dims (dim = 8·odd or 16·odd) #309

Description

@RyanCodrai

Found by bug-hunt wave 11 (recall-quality lens). Severity: high for sparse/bag-of-words workloads; dense embeddings unaffected.

turbovec/src/rotation.rs:92: block_size(dim) is the largest power-of-two divisor of dim, and the Walsh-Hadamard mixes only within B-sized blocks (2 rounds, rotation.rs:30-48). When B is 8 or 16, the transform cannot Gaussianize inputs that actually need mixing — and recall falls off a cliff between adjacent dims.

Recipe: n=20,000, 10-nonzero random sparse (N(0,1) values), L2-normalized, 300 held-out queries, seeds 0 and 1. Metric matches benchmarks/suite/recall_*.py (control: real openai-1536 n=100k 2-bit → R@1 0.886 vs published 0.891; GloVe d=200 4-bit → 0.840 vs 0.8498).

dim B bits R@1 (s0 / s1) R10@10
1024 1024 2 0.883 / 0.850 0.919 / 0.915
1016 8 2 0.453 / 0.427 0.557 / 0.547
1032 8 2 0.393 / 0.410 0.533 / 0.553
1040 16 2 0.780 / 0.760 0.839 / 0.833
1088 64 2 0.883 / 0.860 0.923 / 0.918
2048 2048 2 0.913 / 0.917 0.942 / 0.944
2056 8 2 0.270 / 0.263 0.453 / 0.449

Monotone recovery in B, reproducible across seeds, and the gap widens with dim (−47pp at 1032, −65pp at 2056). Present at 4-bit too (d=2048 R@1 0.990 vs d=2056 0.707). Also reproduced on a tf-idf-like recipe (nz=20, |N(0,1)|): d=1024 0.847 vs d=1032 0.483 at 2-bit.

Scope: dense real embeddings are fine — openai-1536 truncated to 1528/1032/520/264 (all B=8) and nytimes-256→248 all matched their power-of-2 neighbours within ±1.5pp. This bites sparse / bag-of-words / SPLADE-style / hashed-feature vectors — exactly what the rotation exists for.

Undocumented: rotation.rs internally acknowledges "weak-block dims (B = 8)", but no user-facing doc (README, docs/api.md) warns that dim choice affects recall. Someone picking dim 1000, 1032, 2056 or 3000 with sparse features silently loses half their recall; padding to 1024/1088 fixes it. At minimum this needs a doc warning; better would be a constructor warning or more mixing rounds when B is small.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-human-decisionBlocked on a maintainer decision: scope, public claims, or dependency/release policy

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions