fix(util): scale the TurboQuant norm, and repair three tests that could not fail - #757
Merged
sroussey merged 2 commits intoAug 13, 2026
Conversation
…ization `normalizeToUnit` accumulated `sumSquares += v * v` on raw coordinates, which squares the input's exponent: the running sum left the double range long before the vector did. Above ~1e154 it overflowed to Infinity, so a finite input was rejected as "containing NaN or Infinity"; below ~1e-162 it underflowed to 0, so a finite input was recorded as a zero vector, decoded to all zeroes and scored 0 against itself. Replaced with a max-scaled two-pass norm. The per-element finiteness check moved into pass 1 and is load-bearing under max-scaling: an Infinity input would otherwise set maxAbs = Infinity and every scaled coordinate would become NaN. The final division is by maxAbs and then by rootS, never by their product, which is what keeps subnormal inputs alive. Also hardened the decode path: - `turboDequantize` rejects a norm above the Float32 maximum. The output is a Float32Array whose L2 norm IS `norm`, so an out-of-range one decoded to all-Infinity silently. The check is on the recorded scalar, so it is O(1) and confined to the decode — cosine similarity is scale-free and keeps working on such records. - `assertQuantizeResultShape` rejects a negative norm. It is always a Math.sqrt result, and the decode multiplies by it, so a negative one sign-flipped the whole reconstruction with nothing reported. TURBO_QUANTIZE_VERSION stays at 1: the code-to-value mapping is untouched. [1,2,3,4] still records norm === Math.sqrt(30), codes [175,104,163,116] and a bit-identical decode, and both golden-byte vectors are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomTUtZSTgUbFCYqFs4pcu
…s teeth The suite imported `getTestingLogger` from `../../binding/TestingLogger`, which does not exist, so the file failed to load and NONE of its 66 tests ran. Fixed to `@workglow/util/test`, matching every other test in the package. With the suite running, three of its tests could not fail: - the two magnitude tests: `turboDequantize` unconditionally rescales by `norm / croppedNorm`, so the magnitude ratio is exactly 1 for every input, bit width and grid; - the self-similarity test: `quantizedCosine` divides by each side's own `codeNorm`, so a record scores 1 against itself by algebra. Verified by swapping the loading-factor table for a fixed 3-sigma array: all three stayed green while the grid regression they appear to guard was live. Replaced with measurements that move: - magnitude folded into the existing relative-L2 test as a one-line invariant, plus a new cropped (d=768) relative-L2 test with per-bit ceilings and a `relativeL2[i+1] < relativeL2[i] * 0.85` step. The cropped path renormalizes against the first 768 of 1024 coordinates, so the padded test does not cover it. Ceilings measured on both grids; the 3-sigma row is BETTER at 6-8 bits there, so the step assertion alone passes it and the 2/3-bit ceilings are what reject it. - self-similarity replaced by RMSE of (quantized cosine - exact cosine) over 24 seeded pairs at d=1024, per bit width, against ceilings with >=21% headroom on the shipped grid that reject the 3-sigma one at 2, 3 and 8 bits. The `<= 1.0` self-similarity line survives as one line of the existing range test. The "higher dimensions" test additionally used `Math.random()`. It is now seeded, but NOT with the `sim256 > sim64` assertion its name implied: measured over 200 seeded draws at 4 bits, mean reconstruction cosine is 0.99496 at d=64 versus 0.99441 at d=256 — slightly worse, with d=256 winning only 63 of 200. What does improve with dimension is pairwise similarity-estimation error (RMSE 0.0186 / 0.0101 / 0.0047 at d=64/256/1024), so that is what it now asserts and what it is now named for. Also pins the norm fix: overflow, underflow, the Float32 decode ceiling, the negative-norm guard, and a bit-identical [1,2,3,4] round-trip. Re-verified under the 3-sigma grid: 6 tests now fail where 3 did before, and all three replacements are among them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomTUtZSTgUbFCYqFs4pcu
Coverage Report
File CoverageNo changed files found. |
sroussey
merged commit Aug 13, 2026
1cc499e
into
claude/integrate-arxiv-paper-VF55c
11 checks passed
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.
Fixes on top of #354. Two things that have to ship together — a numeric fix, and the tests that would have caught it — plus a repair that makes the suite run at all.
0. The suite was not running
packages/test/src/test/util/TurboQuantize.test.tsimportedgetTestingLoggerfrom../../binding/TestingLogger, which does not exist. The file failed to load, so none of its 66 tests ran. The correct path is@workglow/util/test, which every other test in the package already uses. Everything below is measured with the suite actually executing.1.
normalizeToUnitoverflows and underflows (MEDIUM x2, one root cause)packages/util/src/vector/TurboQuantize.tsaccumulatedsumSquares += v * von raw coordinates. Squaring squares the exponent, so the running sum leaves the double range long before the vector itself does:[1e200, 2e200, 3e200, 4e200]Cannot quantize a vector containing NaN or Infinity— the input contains neithernorm 5.477225575051661e200, codes identical to[1,2,3,4], self-cosine0.9999999999999999[1e-200, 2e-200, 3e-200, 4e-200]norm 0, decodes to all zeroes,turboQuantizedCosineSimilarityreturns0norm 5.477225575051661e-200, codes identical to[1,2,3,4], self-cosine0.9999999999999999Replaced with a max-scaled two-pass norm:
maxAbs = max|v[i]|, thens = sum((v[i]/maxAbs)^2),norm = maxAbs * sqrt(s).Two details that are load-bearing rather than stylistic, and are commented as such in the source:
Number.isFinitecheck moves into pass 1. Under max-scaling anInfinityinput would setmaxAbs = Infinityand every scaled coordinate would becomeInf / Inf = NaN, which no later check catches. The explicit check is what preserves the existing NaN/Infinity rejection.maxAbsand then byrootS, never by their product. For a subnormal input the product is exactly what underflows; each factor separately is representable.Decode-path hardening, same area
turboDequantizenow rejectsnorm > 3.4028234663852886e38. The output is aFloat32Arraywhose L2 norm isnorm, so an out-of-range one decoded to all-Infinitywith nothing reported. The guard reads the recorded scalar, so it is O(1) with no scan of the result, and it lives inturboDequantizeonly — cosine similarity is scale-free and must keep working on such records (asserted).assertQuantizeResultShapenow rejectsnorm < 0.normis always aMath.sqrtresult; the decode multiplies by it, so a negative one sign-flipped the entire reconstruction silently.TURBO_QUANTIZE_VERSIONstays at 1Nothing here touches the grid, loading factors, rotation or packing. Confirmed rather than asserted:
[1,2,3,4]still recordsnorm === Math.sqrt(30)exactly, codes[175,104,163,116], and a decode of[0.9718117117881775, 1.9858760833740234, 2.9999403953552246, 4.014004707336426]— byte-for-byte what the branch produced before the change. Both pre-existing golden-byte vectors are unchanged too. A new test pins this as the no-regression proof.2. Three tests could not fail (MEDIUM)
~302,~320):turboDequantizeunconditionally rescales bynorm / croppedNorm, so the magnitude ratio is exactly 1 for every input, bit width and grid.~498):quantizedCosinedivides by each side's owncodeNorm, soqvsqis 1 by algebra.Verified, not assumed. Swapping
GAUSSIAN_LOADING_FACTORSfor a fixed 3-sigma array — the exact regression these tests read as guarding — left all three green; only 3 unrelated tests failed.Replacements, with the numbers they were tuned on
Every ceiling below was measured on both grids before being committed, and the measured rows are in the test comments.
Relative L2, cropped d=768 (new test; the cropped path renormalizes against the first 768 of 1024 coordinates, so the padded test does not cover it):
Note the 3-sigma row is better at 6–8 bits here, so the
relativeL2[i+1] < relativeL2[i] * 0.85step passes it — the 2- and 3-bit ceilings are what reject it. Magnitude is folded in as a one-line invariant next to a measurement that can actually fail.Cosine RMSE vs the exact cosine, 24 seeded pairs at d=1024, per bit width (replaces the self-similarity test):
Ceilings clear the shipped row by ≥21% and reject the 3-sigma row at 2, 3 and 8 bits. (The two agree exactly at 1 bit: a 2-level grid is ±a, the codes carry only sign, and the cosine is invariant to the scale of
a.) The<= 1.0self-similarity line survives as one line of the existing range test rather than as a test of its own.Re-verified end to end: under the 3-sigma grid the suite now fails 6 tests, and all three replacements are among them. Before this PR it failed 3, none of them these.
One deviation from the brief, on measurement
The
~369"higher dimensions" test usedMath.random(); it is now seeded. It does not assertsim256 > sim64, because that property is false. Measured over 200 seeded draws at 4 bits, mean reconstruction cosine is 0.99496 at d=64 vs 0.99441 at d=256 — slightly worse — and d=256 beat d=64 in only 63 of 200 draws. The same holds at 2 bits and out to d=4096. The old test asserted the false premise in a comment while asserting only> 0.8floors, so it never had to hold.What genuinely improves with dimension, roughly as
1/sqrt(d), is pairwise similarity-estimation error — which is what this quantizer exists to serve: RMSE 0.0186 / 0.0101 / 0.0047 at d=64/256/1024. The test now asserts that and is named for it, with the false property recorded in a comment so it is not reinstated.Verification
Run in a worktree off this PR's base,
bun install+bun run use-source:bun scripts/test.ts util vitest— 54 files, 827 passed, 10 skipped, 0 failed (was 53 passed / 1 failed, with the TurboQuantize file unable to load).bun run build:types— 41/41 tasks successful. (Note: this repo has nobun run typesscript;build:typesis the equivalent.)prettier --checkclean on both changed files.Environment note: Node 22.22.2, not the Node 24
CLAUDE.mdasks for. Nothing here touches native deps, but CI on Node 24 is the authority.Follow-ups, deliberately not in this PR
turboPrepareQuery) — additive API design that deserves its own review.Math.exp(optimalLoadingFactor), whose last ulp is implementation-defined. Node, Bun and browsers can therefore disagree by ±1 code on the same persisted collection. Worth hard-coding the solved constants and keeping the solver as a test oracle.Generated by Claude Code