Skip to content

fix: portable rotation sampler; exact reflection sign; pin output in CI - #11

Merged
amikhail48 merged 1 commit into
mainfrom
fix/portable-rotation
Sep 14, 2026
Merged

amikhail48 merged 1 commit into
mainfrom
fix/portable-rotation

Conversation

@amikhail48

@amikhail48 amikhail48 commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Codes were not reproducible across platforms. The rotation Π and the QJL projection S were drawn with std::normal_distribution, whose algorithm the C++ standard leaves unspecified. From the same std::mt19937(42):

first three samples
libc++ (macOS, Apple clang 21) -0x1.08af86p-1, 0x1.38cfd6p+0, 0x1.715284p-1
libstdc++ (Linux, g++ 11.4) 0x1.38cfd6p+0, -0x1.08af86p-1, 0x1.bd40ecp-1

mt19937 itself emits identical integers on both. So QuantizerProd(dim, bitwidth, rng(seed)) built a different quantizer on macOS than on Linux, and quantize() output differed at d=128 and d=1536. Anything that persists codes keyed by (dim, bitwidth, seed) — e.g. recall's MediaIndex — read garbage after moving between platforms.

Changes

  • Portable sampler (src/rotation.cpp): Box–Muller in double over 53-bit uniforms built from two mt19937 draws. Integer handling is exact; log/sqrt/sin/cos were measured bit-identical on both platforms.
  • Exact reflection sign: det(Q) = (−1)^(nonzero Householder taus), O(dim). The old Q.determinant() < 0 cost another O(dim³) and, in float, returned -0 from dim ≈ 384 up (|det| = 1, log|det| ≈ 1e-5 from the LU pivots), so the flip silently never ran at practical dimensions.
  • kAlgorithmVersion = 2 (types.h): bumped whenever output for a given (dim, bitwidth, seed) changes, so consumers that persist codes can refuse other versions.
  • tests/determinism_test.cpp + CI bazel test //...: golden hashes of the Gaussian matrix and of quantize() codes at d=64 and d=256. CI runs on ubuntu-latest (libstdc++) and macos-latest (libc++).
  • README: Determinism section.

Breaking: codes for a given seed differ from version 1.

Verification

  • Goldens identical on macOS arm64 (libc++, Apple clang 21) and Linux arm64 (libstdc++, g++ 11.4, Jetson Orin), each in fastbuild, -c opt, and -c opt --cxxopt=-std=c++20.
  • Before writing this, a probe with the same sampler compared full matrices across the two platforms at d=1536: G and S bit-identical; Q after Eigen's QR differs by at most 1.5e-6 (entries ~2.5e-2); codes for 100 test vectors identical at d=128 and d=1536. The residual QR drift is why the test pins codes, not Q.
  • bazel build //..., bazel test //... and turboquant_example pass locally. Through recall's adapter: 18/18 tests pass plain, ASan and UBSan.

Performance (construction, -c opt)

dim Apple Silicon before → after Jetson Orin before → after
1024 62.4 → 57.1 ms 248.4 → 227.9 ms
1536 177.2 → 166.7 ms 743 → 668 ms

Dropping determinant() saved ~134 ms at d=1536 on the Orin, but Box–Muller (log, sin, cos per pair) is slower than libstdc++'s built-in sampler, so the net is −10%. A portable Marsaglia polar sampler (no sin/cos) could recover some of that; left as a follow-up. Encode, scoring and search are unchanged.

🤖 Generated with Claude Code

The rotation and QJL projection were drawn with std::normal_distribution,
whose algorithm the C++ standard leaves unspecified. From the same
mt19937(42), libc++ returns -0x1.08af86p-1, 0x1.38cfd6p+0, 0x1.715284p-1 and
libstdc++ returns the first two swapped, then 0x1.bd40ecp-1. So a quantizer
built from one (dim, bitwidth, seed) produced different codes on macOS and
Linux, and anything that persisted codes keyed by those parameters read
garbage when moved across platforms.

- rotation.cpp: Box-Muller over mt19937's 32-bit outputs (53-bit uniforms,
  exact integer handling). Measured bit-identical G and S on macOS arm64
  (libc++, Apple clang 21) and Linux arm64 (libstdc++, g++ 11.4).
- The proper-rotation sign now comes from the Householder coefficients:
  det(Q) = (-1)^(nonzero taus), exact and O(dim). Q.determinant() cost an
  extra O(dim^3) and, in float, returned -0 from dim ~384 up, so the flip
  never ran at practical dimensions. It was ~18% of construction at d=1536.
- types.h: kAlgorithmVersion = 2, bumped whenever output for a given
  (dim, bitwidth, seed) changes; consumers that persist codes should record it.
- tests/determinism_test.cpp: golden hashes of the Gaussian matrix and of
  quantize() codes at d=64 and d=256. Identical on both platforms above in
  fastbuild, -c opt, and -c opt -std=c++20. CI now runs `bazel test //...`
  on ubuntu-latest (libstdc++) and macos-latest (libc++).
- README: Determinism section.

Breaking: codes for a given seed differ from version 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@amikhail48
amikhail48 merged commit b8fd6d4 into main Sep 14, 2026
5 checks passed
@amikhail48
amikhail48 deleted the fix/portable-rotation branch September 14, 2026 16:55
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.

1 participant