Repository navigation
feat(core): portable seeded rotations and a frozen fast-mode scale table - #199
Merged
Merged
Conversation
TamimEhsan
force-pushed
the
core-quant-cpp-preq
branch
from
October 31, 2026 15:16
d409e6a to
d296f69
Compare
TamimEhsan
marked this pull request as ready for review
October 31, 2026 15:17
Member
|
Thanks for the PR! The table matches the computed values on our side, and the seeded FHT rotations look stable across standard-library implementations. I'm merging a docs update first, so this may need a rebase afterwards. I'll follow up with a full review after that. |
FrozenValuesMatchTheComputation compares only 32 of the 16384 entries, so an edit elsewhere in the table would pass on every platform. Pin all entries with an FNV-1a hash of their IEEE-754 bits, read through frozen_const_scaling_factor so the index mapping is covered too. Also update the QG seed docs: the graph seed now seeds MatrixRotator.
The libstdc++ uniform_int_distribution bytes match the new FHT/Kac mapping only from GCC 11, which adopted Lemire's algorithm; older libstdc++ occasionally drew different bytes. The frozen scale table forbids changing its values, not editing the lookup code beside them. List the table in the development code map and the quantization audit list.
The rotator and scale-table goldens hashed from 1469598103934665603, which drops the last digit of the FNV-1a 64-bit offset basis 14695981039346656037, so standard FNV-1a tools could not reproduce them. Recompute the goldens; the hashed bytes are unchanged.
Say that faster_config computes the factor for dimensions outside the table, and that math libraries also change its last bits.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
These are the C++ prerequisites for the provisional Python quantization module (
rabitqlib.quant). The goal is that the same arguments give the same rotation and the same fast-mode scale on every platform, so that codes and saved bytes produced by the module can be reproduced. Both changes are additive. C++ signatures, defaults and saved-index formats are unchanged.Promoting
combined_codeandcode_factorsto a public API will ship with the Python module or in a later PR.Seeded rotations (
fix(rotator))std::mt19937output (gen() >> 24). This replacesstd::uniform_int_distribution<int>(0, 255), whose output the C++ standard does not specify. The new mapping gives the same bytes as libstdc++'s distribution (checked on 100,000 draws, 0 mismatches), so existing Linux rotations are unchanged. Every standard library now agrees.MatrixRotatoruses the seed. Before, it calledrandom_gaussian_matrixwithout a seed, which fell back tostd::random_device, sochoose_rotator'sseedargument was ignored for matrix rotations.MatrixRotatornow takes aseed(defaulting tostd::random_device, asFhtKacRotatordoes), andchoose_rotatorpasses it through. A seeded matrix is reproducible only with the same standard library and build, becausestd::normal_distributionand QR rounding differ, so the docs still say to persist matrix rotators withsave().Frozen fast-mode scale table (
feat(quant))faster_configcomputed its scale (t_const) withstd::normal_distributionsamples on every call. That value differs between standard libraries by several percent, and between FMA and non-FMA builds in the last bits. It also cost time at every HNSW construction and load (13 ms at 768 dimensions, 50 ms at 3072; Xeon Gold 6418H, GCC 14.3,-O3).src/core/fast_scale_table.cppholds the scale for every padded dimension that is a multiple of 32 up to 65536 and everyex_bitsfrom 1 to 8 (total bits 2–9). Each value is stored as an exact hexadecimal literal. The table is 2048 × 8 doubles (128 KiB).faster_configreads the table throughfrozen_const_scaling_factor(padded_dim, ex_bits), which returnsstd::optional<double>. Dimensions outside the table fall back to the existing computation.scripts/generate_fast_scale_table.cpprecords how the table was generated. Its guards refuse to build with libraries other than libstdc++, or with AVX or FMA. The table is frozen data: never regenerate or edit it. A different table would be a new, separately named table.Behaviour changes
faster_confignow returns the table value, which changes fast-mode codes and HNSW query quantization slightly, toward the Linux results.MatrixRotator: the index now passes itsseed(default 42), so the rotation is deterministic where it used to be random. IVF passes no seed and stays random.faster_configno longer computes the scale at padded dimensions in the table.Docs
docs/docs/rabitq/rotator.mddescribes the portable FHT/Kac seeding and the seeded matrix rotator.docs/docs/rabitq/quantizer.mddescribes the frozen table and how much the fallback can differ between platforms.Testing
FhtRotatorTest.SeededSignFlipsAreFrozen: golden sign bytes (FNV-1a hash and an 8-byte prefix) for four(dim, padded_dim, seed)cases, captured from the libstdc++ path this replaces.MatrixRotatorFactoryTest.SeedReproducesTheMatrix: the same seed gives the same matrix and a different seed a different one, through bothchoose_rotatorand the constructor.FastScaleTableTest.FasterConfigReadsTheFrozenTable: pinned table entries; one bit keeps the default configuration.FastScaleTableTest.FrozenValuesMatchTheComputation: the table equals the computation exactly in the generating configuration (libstdc++, x86-64, no AVX or FMA). Elsewhere it checks the value is plausible within 10%. I measured a 1.8–4.1% spread across different sample streams, which is what MSVC and libc++ produce.FastScaleTableTest.DimensionsOutsideTheTableFallBackToTheComputation: table bounds and the fallback.Results:
RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF:ctest --no-tests=errorpasses 439 of 439 tests. One test is skipped, as on main../scripts/check-format.shandgit diff --check: clean.python -m mkdocs build --strict --config-file docs/mkdocs.yml: clean.-mavx.