Commit Graph

33 Commits

Author SHA1 Message Date
Jojii
e7d63ed8a4 Refactor TurboQuantizer (#9095)
* Refactor TurboQuant into common/

# Conflicts:
#	lib/common/turboquant/src/math.rs
#	lib/quantization/src/encoded_vectors_tq.rs
#	lib/quantization/tests/integration/test_tq.rs

* [ai] fix docs

* [ai] tighten function visibilities

* Review remark

* include turboquant in amalgamate

* Rebase

* Move common/turboqunat back (but keep the refactor)

* Move vector_stats.rs back
2026-05-28 00:32:08 +02:00
qdrant-cloud-bot
d0b84ef907 Fix clippy wildcard_enum_match_arm lint in test_tq.rs (#9101)
Replace wildcard match `_` with explicit enum variants
`DistanceType::Dot | DistanceType::L1 | DistanceType::L2` to satisfy
clippy's wildcard_enum_match_arm lint.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 09:32:21 +02:00
Srimon Danguria
327551dec3 Validate TurboQuant internal scoring (#9080)
* Validate TurboQuant internal scoring

* feat: cargo +nightly fmt --all

* feat: enhance TurboQuant scoring with error correction and mode handling
2026-05-20 01:48:55 +02:00
Luis Cossío
757a168167 make quantized_vector_size method static (#9060) 2026-05-15 18:31:23 -04:00
Ivan Pleshkov
665b91d587 Apply p square for TQ+ (dont use std+mean) (#8877)
* apply p square

* review remarks and reduce ram

* review remarks

* clean tmp logs

* review remarks

* review remarks

* trigger ci
2026-05-06 09:36:00 +02:00
Ivan Pleshkov
eb2e3b8416 Tq plus (#8836)
* Add TQ+ ErrorCorrection on top of renorm

Per-coordinate shift+scale fits each rotated, length-rescaled coord onto
the codebook's N(0, 1) grid before quantization. EncodedVectorsTQ::encode
runs a first pass to fit the stats when TQMode::Plus.

Scoring stays correct under renorm's `scaling_factor` framework:
- Asymmetric: precompute_query scales `Q .* D'` and stashes `qm = ⟨Q, M⟩`
  on EncodedQueryTQ; score_precomputed adds qm to raw_dot before applying
  scaling_factor.
- Symmetric: scalar slow path computes `Σ X+_a X+_b D'_i² + xm_a + xm_b
  − ⟨M, M⟩` (xm stored per vector in extras, mm_const cached on
  ErrorCorrection). Result feeds the existing `* v1_scale * v2_scale` arms.
  SIMD reuse for this path is a follow-up.

Storage layout: TQMode::Plus extras are 4 bytes longer (xm appended after
scaling_factor). Zero-vector inputs skip EC application so renorm's
existing zero-norm guard keeps producing score ≈ 0 within tolerance.

VectorStats refactor: streaming `VectorStatsBuilder` so the Plus first
pass can feed Welford with a reused buffer; `build` now takes `dim`
directly and is generic over `T: Into<f64>`.

Integration tests run on both Normal and Plus via rstest cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix TQ+ recall regression on non-uniform per-coord variance data

Two compounding bugs in the renorm + TQ+ composition:

1. centroid_norm was measured on `X+` centroids, which have chi-squared
   norm distribution across vectors (~10% spread for d=256). renorm
   assumed `cn` should be deterministic `sqrt(d)` (just quantization
   drift), so the per-vector correction ended up amplifying intrinsic
   chi-squared noise into ranking error. Fix: revert EC per coord before
   measuring (`c · D' + M`), matching llama-turbo-quant. The reverted
   centroids approximate `rescaled` which has length `sqrt(d)` exactly
   by construction. dequantize follows the same convention so the
   stored `scaling_factor = l2/cn` round-trips back to the original l2.

2. Asymmetric query path pre-scaled `Q* = R_q · D'` before SIMD encoding.
   The SIMD encoder normalizes by `max(|input|)`, so a query whose coords
   span 5× magnitude (which `R_q · D'` does on real data) loses precision
   on the small-D' coords. Fix: keep `rotated` unscaled, store it as a
   side field on `EncodedQueryTQ`, and use a scalar decode-and-dot path
   for TQ+ (`Σ R_q_i · c_i · D'_i + qm`). SIMD support for this is a
   follow-up.

Catches both via `recall_skewed_data` test on data with 8 spike-variance
input coords. Without the fixes, Bits4 Plus dropped to 0.93 vs Normal
0.98; after the fixes, Plus tracks Normal within 2%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TQ+ asymmetric: skip the SIMD encoding entirely

The TQ+ asymmetric path doesn't use the SIMD-encoded query — it goes
through `score_precomputed_ec` with `rotated_query`. So building the
SIMD form was wasted work + memory. Make `data` an `Option` and only
populate it for the cases that actually use it (Normal mode any distance,
TQ+ L1 via dequantize fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert TQ+ to SIMD scoring path

The whole point of TQ+ is that scoring code-paths stay identical between
Normal and Plus modes — only the query precomputation changes. We
pre-scale `Q* = R_q · D'` so the existing SIMD raw_dot computes
`⟨Q · D', X+⟩` directly, then add `qm` and apply renorm's scaling_factor.

Drops `score_precomputed_ec` and `EncodedQueryTQ::rotated_query`. The
recall regression that motivated the scalar fallback was entirely from
the `compute_centroid_norm` bug (measuring `‖X+‖` instead of `‖rescaled‖`)
fixed in the prior commit; SIMD precision was a red herring.

`recall_skewed_data` confirms: Bits4 Normal=0.984 / Plus=0.978, Bits2
Normal=0.902 / Plus=0.908.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TQ+ Bits1: widen query encoding to 12 bits

For 1-bit storage with TQ+, the per-coord `D' = 1/scale` pre-scaling on
the query can push some coords toward the small end of the SIMD encoder's
integer range (which normalizes by `max(|input|)`). At 8 bits those small
coords lose precision; at 12 bits the rounding error drops ~10× per the
existing `test_query_dotprod_matches_reference` parity test.

`Query1bitSimd` is already generic over BITS so this is just a new
`EncodedQueryTQData::Bits1Wide(Query1bitSimd<12>)` variant + a TQ+/Bits1
dispatch in `precompute_query`. Bits2/Bits4 don't need this — their
storage is fine-grained enough that query precision isn't the bottleneck,
and their SIMD encoders aren't generic over BITS today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* are you happy fmt

* TQ+ Bits1Wide: bump to 16-bit query quantization (kernel max)

12 bits helped on real datasets but not enough — push to the kernel's
ceiling of 16. `Query1bitSimd<BITS>` asserts `BITS ∈ [2, 16]`, so this
is the most precision the existing SIMD path can give us before needing
a wider integer kernel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TQ+: shift by per-coord median, not mean

For 1-bit storage the codebook boundary sits at 0, so post-shift values
are quantized purely by sign. Median is the sign-balance point of the
distribution; mean isn't (skewed coords pull mean off the median).

On anisotropic embeddings — dbpedia-openai being the reference case —
mean-based shift produced a ~60/40 biased sign distribution per coord,
losing 1-bit's representational capacity. Median-based shift restores
50/50 and matches llama-turbo-quant's behavior. Higher bit-widths are
less sensitive but still benefit; the codebook boundaries still lie at
distribution-percentile-aware positions when the data is centered on
the median.

Median requires per-coord samples in memory, so cap the stats pass at
10K vectors. Estimates converge fast (~√N) — 10K is plenty even for
million-vector indexes. The encoding pass still processes every vector.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TQ+ Bits1Wide: revert to 12-bit query quantization

The recall regression on anisotropic data was the mean-vs-median shift,
not query precision. 12 bits is enough headroom for the per-coord D'
pre-scaling and avoids the extra storage of the 16-bit form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TQ+ Bits1Wide: bump back to 16-bit query quantization

12 bits helped a bit but not enough on the real dataset. Bump to the
kernel's ceiling. If 16 still isn't enough, the next step is checking
whether the gap is real (re-measure llama branch) before widening the
SIMD integer kernel itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* are you happy clippy

* use mean

* 1bit error correction

* review remarks

* are you happy clippy

* review remarks

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 13:39:05 +02:00
Jojii
9bd24411c3 TurboQuant E2E segment-level HNSW tests (#8799)
* [ai + manual] HNSW Quantization tests for TQ + better error calculation

* Compare TQ 1/1.5 bits to binary quantization
2026-04-28 11:16:08 +02:00
Ivan Pleshkov
aaa7ed5771 Tq metrics (#8794)
* start L2 metric

* l2 tests

* L1 metric

* are you happe clippy
2026-04-27 16:26:01 +02:00
Jojii
f821735b8e [ai] e2e tests in quantization/tests (#8788) 2026-04-27 09:20:22 +02:00
Luis Cossío
5403ea68ab Chunked vectors with UniversalWrite storage (#8233)
* use CowMultiVector as return type from storages

* add advice to OpenOptions

* Implement ChunkedVectors with generic storage

* rename ChunkedVectors->VolatileChunkedVectors and ChunkedMmapVectors-> ChunkedVectors

* propagate everywhere

fix tests

* [auto] rename BytesRange -> ElementsRange

* [auto] rename BytesOffset -> ElementOffset

* coderabbit nits

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2026-02-27 12:47:15 -03:00
dependabot[bot]
18a7587d4b build(deps): bump rand_distr from 0.5.1 to 0.6.0 (#8148)
* build(deps): bump rand_distr from 0.5.1 to 0.6.0

Bumps [rand_distr](https://github.com/rust-random/rand_distr) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/rust-random/rand_distr/releases)
- [Changelog](https://github.com/rust-random/rand_distr/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand_distr/compare/0.5.1...0.6.0)

---
updated-dependencies:
- dependency-name: rand_distr
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Migrate main code base to rand 0.10

* Migrate tests

* Migrate benches

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: timvisee <tim@visee.me>
2026-02-25 14:15:04 +01:00
Ivan Pleshkov
c14f83eff9 use enum for SQ query and meta (#7669)
fix CI

review remarks

review remarks

review remarks

review remarks

simplify encoding of internal vector

fix ci

fix ci

review remarks

review remarks
2025-12-17 22:35:14 +01:00
Luis Cossío
366ab07ba2 fix typo Uint8 -> Int8 (#7666) 2025-12-02 09:54:34 +03:00
Ivan Pleshkov
8df3dda006 Scalar quantization encoding parameter (#7602)
* scalar quantization encoding parameter

fix ci

remove method

review remarks

fix ci

* uint8 - int8
2025-12-01 12:03:36 +01:00
Ivan Pleshkov
a2be9e7fff refactor sq score_bytes (#7613) 2025-11-27 10:49:33 +01:00
Ivan Pleshkov
879da486af Remove quantization load from trait (#7113)
* remove quantization load

* refactor load

* fix fmt error

* fix clippy
2025-08-22 14:37:32 +02:00
Ivan Pleshkov
3f75fae516 EncodedStorage upsert vector (#7048)
* EncodedStorage upsert vector
2025-08-18 17:39:23 +02:00
Ivan Pleshkov
8961eeaf0a Remove quantization save functions (#7043)
* remove quantization save

* revert convert_binary_encoding fn

* review remarks

* review remark

* review remarks
2025-08-18 13:55:07 +02:00
Ivan Pleshkov
a3e457d387 Wrap quantization Vec<u8> in a separate structure (#7013)
* separate struct for vec u8 quantization storage

* cgf testing for test storage
2025-08-12 13:20:48 +02:00
Ivan Pleshkov
766b527ac8 Appendable quantization storage (#6935)
* appendable qunatization storage

fmt

deprecate count in quantization config

fix arm tests build

qunatized storage vectors count

fix ci

are you happy clippy

fix compat tests

undo rename to `deprecated_count`

remove flusher

remove obsolete functions, don't use hardware counter in ram storage

are you happy clippy

fix gpu build

check ci when revert option

revert last commit

* debug ci

* log offsets

* log offsets

* fix compatibility tests

* deprecate count

* fix arm tests

* fix benches

* Update lib/quantization/src/encoded_vectors_binary.rs

Co-authored-by: xzfc <5121426+xzfc@users.noreply.github.com>

---------

Co-authored-by: xzfc <5121426+xzfc@users.noreply.github.com>
2025-08-06 13:51:01 +02:00
Ivan Pleshkov
bac28164ed use vector statistics for scalar bq query (#6835)
* use vector statistics for scalar bq query

* fix minor error

* remove test_binary_scalar_internal test

* do NOT use special file for storing vector stats

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2025-07-11 10:15:28 +02:00
Ivan Pleshkov
b414a402bb Merge pull request #6728
* # This is a combination of 7 commits.

* SameAsStorage default value

* fix coderabbit warnings

* neon for u8 bq

* sse for u8 bq

* fix windows build

* rename function

* add comments

* fmt

* fix arm build

* review remarks
2025-07-03 14:09:12 +02:00
Ivan Pleshkov
5895877577 Fix sq internal vector encoding (#6763)
* Fix SQ internal vector encoding

* add test

* clippy warning
2025-06-26 15:08:50 +02:00
Ivan Pleshkov
fe5becdadd Merge pull request #6663
* bq encodings

* are you happy clippy

* are you happy clippy

* are you happy clippy

* are you happy clippy

* gpu tests

* update models

* are you happy fmt

* move additional bits to the end

* fix tests

* Welford's Algorithm

* review remarks

* are you happy clippy

* remove debug println in test

* coderabit nitpicks

* remove unnecessary clone and partialeq

* Use f64 for Welford's Algorithm

* try fix ci

* revert cargo-nextest

* add debug assertions
2025-06-19 18:21:00 +02:00
Arnaud Gourlay
33d062a3a7 Minor cleanups (#6291) 2025-04-01 13:01:10 +02:00
Tim Visée
3e536347e1 Bump Rust edition to 2024 (#6042)
* Bump Rust edition to 2024

* gen is a reserved keyword now

* Remove ref mut on references

* Mark extern C as unsafe

* Wrap unsafe function bodies in unsafe block

* Geo hash implements Copy, don't reference but pass by value instead

* Replace secluded self import with parent

* Update execute_cluster_read_operation with new match semantics

* Fix lifetime issue

* Replace map_or with is_none_or

* set_var is unsafe now

* Reformat
2025-02-25 11:21:25 +01:00
Kumar Shivendu
38e1f4720b Quantization test improvements (#5976)
* minor change in test

* apply to other tests as well

* imporve var name

* add test for loading empty EncodedVectorsBin from disk

* fmt
2025-02-12 09:45:18 +01:00
Luis Cossío
af74d1b96a bump and migrate to rand 0.9.0 (#5892)
* bump and migrate to rand 0.9.0

also bump rand_distr to 0.5.0 to match it

* Migrate AVX2 and SSE implementations

* Remove unused thread_rng placeholders

* More random migrations

* Migrate GPU tests

* bump seed

---------

Co-authored-by: timvisee <tim@visee.me>
Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>
2025-01-28 16:19:11 +01:00
Jojii
e0d0e233d8 Timeout aware hardware counter (#5555)
* Make hardware counting timeout aware

* improve test

* rebuild everything

* fmt

* post-rebase fixes

* upd tests

* fix tests

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-12-10 12:12:36 +01:00
xzfc
4548ebe4bf Reduce debug size (#5556)
* debug size: RawScorerImpl::peek_top_iter

* debug size: TypedMultiDenseVector::multi_vectors() uses

* debug size: stop_condition -> stopped
2024-12-02 14:33:35 +00:00
Arnaud Gourlay
5bce09a1ed Merge quantization integration tests (#5473) 2024-11-19 13:10:01 +01:00
Jojii
7b677fb7fa Hardware counting for quantization (#5369)
* add cpu measurement for quantization

* clippy

* fix tests

* discard hardware counters in benchmarks

* Forwarding hardware counter in distributed setup (#5371)

* make hardware counter available in distributed setup

* clippy

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-11-06 16:23:02 +01:00
Ivan Pleshkov
f27b6909bd Move quantization repo (#5096)
* move quantization repo

* are you happy fmt

* are you happy clippy

* remove dumping pq to image

* workspace deps

* are you happy clippy
2024-09-17 16:30:26 +02:00