docs(util,ai): correct TurboQuant's accuracy claims against an honest int8 baseline - #801
Merged
sroussey merged 1 commit intoAug 15, 2026
Conversation
… int8 baseline
The "8x more accurate than linear" claim was measured against a defective
baseline. `VectorQuantizeTask`'s linear int8 path divides by the vector's L2
norm before scaling by 127, so on a well-spread vector every coordinate lands
near 1/sqrt(d) and the largest code it emits is 8 at d=768, 6 at d=1536, 4 at
d=3072 — three of its eight bits are gone before any comparison happens.
Beating it measured that defect, not the rotation.
Against a max-abs int8 quantizer (divide by max|v|, use the full code range),
padded turbo is 1.4x-1.8x WORSE on well-conditioned inputs. What the rotation
actually buys is independence from the input distribution: turbo stays in a
0.0001-0.0004 band whatever the input looks like, while max-abs swings with the
tail — on Gaussian vectors with 2 dimensions at 20x ("massive activations"),
turbo 0.00039 vs max-abs 0.00174 at d=768.
The module doc, the two rejection messages and the task's `method` schema
description now carry all three columns and say which is which. The cropping
paragraph keeps its figures but is re-anchored to padded turbo and max-abs
rather than to the L2 path.
`quantizeToInt8` itself is deliberately NOT repaired here — see #796. The
divisor change rescales every stored coordinate ~16x at d=768; cosine survives
it (positive scalar multiple) but `l2` and `ip` do not, and a stored int8
vector carries no marker of which scaling produced it. That is a
migration-relevant change and does not belong in an "add TurboQuant" PR.
Also in this change:
- `optimalLoadingFactor`'s "within 0.35%" was the 16-level figure quoted as if
it were the worst; the real curve runs 4.03% at 2 levels down to ~0.12% at
256. Replaced with the curve plus why it does not matter (the solver is only
ever called at 255 and 65535 levels, past the fine end of the table).
- `nextPowerOf2` -> `turboPaddedLength`. The guard is right; the name promised a
general-purpose helper while the function rejects anything over 2^20 with a
message naming this module.
- `turboQuantizeToTypedArray`'s third parameter drops its default and its
`number | Options` union. `{ seed: 42 }` alone was a compile error, since the
interface uses `T | undefined` rather than `T?`. Breaking change to a function
that has not shipped.
- `DEFAULT_SEED` is exported, so the task's schema default and destructuring
default cite it instead of repeating the literal twice.
- New required output `originalDimensions`, so a consumer can tell a widened
vector from a model that genuinely emits that many dimensions.
Tests: the accuracy test now scores against a max-abs reference computed in the
test file, so repairing the quantizer cannot move its bounds. A second case
pins the outlier-dimension claim; together they mean the corrected wording
cannot silently rot in either direction. A third records the shipped linear
path's max emitted code of 8 as a recorded defect, so its repair is a
deliberate, reviewed change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UW1Qr5mxetAQr61YKEY9nz
This was referenced Aug 15, 2026
Collaborator
Author
Full section verificationTargeted runs on the two changed test files: Lint: This repo has no New named tests in this PR: Generated by Claude Code |
sroussey
merged commit Aug 15, 2026
5e8cb99
into
claude/integrate-arxiv-paper-VF55c
10 of 11 checks passed
sroussey
added a commit
that referenced
this pull request
Aug 16, 2026
… int8 baseline (#801) The "8x more accurate than linear" claim was measured against a defective baseline. `VectorQuantizeTask`'s linear int8 path divides by the vector's L2 norm before scaling by 127, so on a well-spread vector every coordinate lands near 1/sqrt(d) and the largest code it emits is 8 at d=768, 6 at d=1536, 4 at d=3072 — three of its eight bits are gone before any comparison happens. Beating it measured that defect, not the rotation. Against a max-abs int8 quantizer (divide by max|v|, use the full code range), padded turbo is 1.4x-1.8x WORSE on well-conditioned inputs. What the rotation actually buys is independence from the input distribution: turbo stays in a 0.0001-0.0004 band whatever the input looks like, while max-abs swings with the tail — on Gaussian vectors with 2 dimensions at 20x ("massive activations"), turbo 0.00039 vs max-abs 0.00174 at d=768. The module doc, the two rejection messages and the task's `method` schema description now carry all three columns and say which is which. The cropping paragraph keeps its figures but is re-anchored to padded turbo and max-abs rather than to the L2 path. `quantizeToInt8` itself is deliberately NOT repaired here — see #796. The divisor change rescales every stored coordinate ~16x at d=768; cosine survives it (positive scalar multiple) but `l2` and `ip` do not, and a stored int8 vector carries no marker of which scaling produced it. That is a migration-relevant change and does not belong in an "add TurboQuant" PR. Also in this change: - `optimalLoadingFactor`'s "within 0.35%" was the 16-level figure quoted as if it were the worst; the real curve runs 4.03% at 2 levels down to ~0.12% at 256. Replaced with the curve plus why it does not matter (the solver is only ever called at 255 and 65535 levels, past the fine end of the table). - `nextPowerOf2` -> `turboPaddedLength`. The guard is right; the name promised a general-purpose helper while the function rejects anything over 2^20 with a message naming this module. - `turboQuantizeToTypedArray`'s third parameter drops its default and its `number | Options` union. `{ seed: 42 }` alone was a compile error, since the interface uses `T | undefined` rather than `T?`. Breaking change to a function that has not shipped. - `DEFAULT_SEED` is exported, so the task's schema default and destructuring default cite it instead of repeating the literal twice. - New required output `originalDimensions`, so a consumer can tell a widened vector from a model that genuinely emits that many dimensions. Tests: the accuracy test now scores against a max-abs reference computed in the test file, so repairing the quantizer cannot move its bounds. A second case pins the outlier-dimension claim; together they mean the corrected wording cannot silently rot in either direction. A third records the shipped linear path's max emitted code of 8 as a recorded defect, so its repair is a deliberate, reviewed change. Claude-Session: https://claude.ai/code/session_01UW1Qr5mxetAQr61YKEY9nz Co-authored-by: Claude <noreply@anthropic.com>
sroussey
deleted the
claude/optimistic-goldberg-onotd9-turboquant-claims
branch
August 24, 2026 18:48
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.
Blocks merge of #354. Docs, one rename, one unshipped signature change, one new output field, and tests — no numerics changed. #354's core numerics (Max 1960 loading factors, the clipping integral, the orthonormal self-inverse WHT, inverse rotation composition, the exact 1-bit angle correction, bit packing, edge cases, no caller mutation) were verified correct and are not touched.
The problem: an 8x win measured against a defective baseline
The branch claimed padded turbo was "substantially MORE accurate than linear", quoting a ~8x gap. Re-measured on the branch module with the test's own generator (
makeRandom(d),a[i] = rnd() - 0.5, 40 pairs, int8, seed 42) — cosine RMSE against the exact similarity:The middle column is not turbo's achievement.
quantizeToInt8divides by the vector's L2 norm before scaling by 127, so on a well-spread vector every coordinate sits near1/√dand the largest code it emits is 8 of 127 at d=768 — three of its eight bits are gone before any comparison happens. Beating it measures that defect, not the rotation.Against
max|v|, padded turbo is 1.4×/1.6×/1.75× worse, not 8× better.But "turbo is worse" is not the honest story either
Turbo's error is ~distribution-invariant (0.0001–0.0004 everywhere); max-abs swings ~20× with the input's tail, because its divisor is a single order statistic. On Gaussian vectors with 2 dimensions at 20× — "massive activations", which real embedding models exhibit:
So the corrected guidance is: prefer padded turbo when the corpus may contain outlier dimensions or is not under your control; max-abs int8 is simpler, keeps the input length, and is slightly more accurate when it does not. What the rotation buys is independence from the input distribution, not a lower error floor.
Scope decision:
quantizeToInt8is deliberately NOT fixed hereFollow-up filed as #796.
quantizeToInt8isprivate(packages/ai/src/task/VectorQuantizeTask.ts:322, called only from:286) — not an exported function. It is a pre-existing defect onmain, unrelated to TurboQuant. Changing its divisor from‖v‖₂tomax|v|rescales every stored coordinate ~16× at d=768:cosineis safe — a positive scalar multiple per vector, and cosine is scale-invariant.l2andipare not.VectorDistanceMetricincludes both, distances under them scale with magnitude, and there is no version marker on a stored int8 vector. A corpus written partly before and partly after is incoherent under those metrics and undetectable.It needs a migration note and probably a
linearScale: "l2" | "max-abs"input rather than a silent flip. Landing that inside an "add TurboQuant" PR buries a migration-relevant change where no reviewer is looking.The hard requirement that follows: the corrected claims and tests are worded and computed so that repairing
quantizeToInt8later cannot invalidate them. The max-abs baseline is the quoted reference, and the tests compute it inside the test file rather than calling the task's linear path.Changes
packages/util/src/vector/TurboQuantize.tsoptimalLoadingFactor's "within 0.35%" quoted the 16-level figure as if it were the worst. Measured deviation is 4.03% / 1.64% / 0.86% / 0.35% / 0.14% / 0.00% / 0.13% / 0.12% at 2…256 levels. Replaced with the curve plus why it does not matter — the solver is only ever called at 255 and 65535 levels, past the fine end, and the tabulated widths never reach it.nextPowerOf2→turboPaddedLength. The guard is right; the name promised a general-purpose helper while the function throwsTurboQuant dimensions must be at most 1048576. New on this branch (git grep nextPowerOf2 origin/mainis empty), so renaming is free.DEFAULT_SEEDexported with JSDoc.turboQuantizeToTypedArray's third parameter drops its default and itsnumber | Optionsunion →options: TurboQuantizeToTypedArrayOptions | undefined.{ seed: 42 }alone was a compile error, since the interface usesT | undefinedrather thanT?. Breaking change to a function that has not shipped — free now, expensive later.packages/ai/src/task/VectorQuantizeTask.tsmethoddescription and the throw both rewritten with the honest accuracy story.DEFAULT_SEEDimported and used for the schemadefaultand the destructuring default (was a hardcoded42in both).originalDimensions, fromvectors[0].length— the output length alone cannot distinguish a 768-dim model widened to 1024 from a model that genuinely emits 1024 dimensions.Tests (
packages/test/src/test/rag/VectorQuantizeTask.test.ts)turboRmse < 0.001andturboRmse < maxAbsRmse * 2. NotlinearRmse / 4, and not the task's linear path at any ratio: repairing the quantizer would have turned the old assertion red for exactly the right reason, which is the definition of a test pinned to the wrong thing. Turbo is legitimately the worse of the two on this generator (i.i.d. uniform — the best possible case for max-abs); the bound admits that and pins the magnitude.turboRmse < maxAbsRmse / 3with Gaussian + 2 dims at 20×. Together these two mean the new wording cannot silently rot — one pins that turbo is not dramatically better in the benign case, the other that it is better in the heavy-tailed case.quantizeToInt8is repaired the expectation changes to 127 and the comment goes away. This is what makes the deferred fix safe.Verification
All figures in this PR were re-measured independently against the branch module before any edit and matched the review's numbers exactly, including the solver-deviation curve and the cropped-turbo ratios.
bunx vitest run --project test packages/test/src/test/rag/VectorQuantizeTask.test.ts→ 24 passedbunx vitest run --project test packages/test/src/test/util/TurboQuantize.test.ts→ 72 passedbun scripts/test.ts util rag vitest→ full section run, result in a follow-up commentbunx eslintover the four changed files → exit 0bunx prettier "packages/{util,ai,test}/src/**/*.{ts,tsx}" --check→All matched files use Prettier code style!Note: this repo has no
lintscript — the root script isformat(eslint --fix && prettier --check --write), so eslint and prettier were run directly in check mode.