Skip to content

Zvec 0.8.1: uniform/PQ quantizers, refiner, relaxed ids, hnsw-rotation guard - #6

Merged
TheAli711 merged 4 commits into
mainfrom
feat/zvec-0.8.1
Nov 9, 2026
Merged

TheAli711 merged 4 commits into
mainfrom
feat/zvec-0.8.1

Conversation

@TheAli711

@TheAli711 TheAli711 commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Upgrades the engine to Zvec 0.8.1 and exposes its new storage-layer features. Intended for v0.3.0 (breaking changes are listed below). Spec: specs/SPEC-015-zvec-0-8-upgrade.md.

Upgrade

  • Require zvec>=0.8.1. Picks up upstream fixes for crashes and missing rows when fetch/query race a flush, several filter bugs, and collection locks held by inherited file descriptors.
  • On-disk compatibility: collections built with 0.7.0 (plain, int8, and int8 + rotation × hnsw/ivf/flat) open on 0.8.1, keep full recall, accept writes, and re-optimize correctly.

Features

  • New quantizers: uniform_uint4/7/8 (hnsw, l2) and pq (hnsw, ivf; FP32), with num_chunk, num_bits, fast_scan (ivf), and OPQ via enable_rotate + opq_iter/opq_pq_iter. Every combination is validated server-side with a specific 422, because Zvec's own errors are generic, and some values crashed its constructors.
  • Refiner search params: scale_factor on hnsw/hnsw_rabitq, and is_using_refiner + scale_factor on ivf.
  • Document ids: any UTF-8 up to 1,024 bytes (no NUL or newline). GET /docs/{doc_id} routes ids that contain /. Field names can be up to 64 bytes.
  • RaBitQ now works on macOS ARM64 / Intel Mac, as well as Linux.
  • Benchmarks: the quant sweep covers the new variants and has a refiner axis.

Guard for an upstream regression

A new HNSW index with int8/int4 + enable_rotate returns near-random results after optimize on Zvec 0.8.0/0.8.1 (recall@10 = 0.000 on SIFT1M; 0.7.0 is fine). Reported as alibaba/zvec#886. The server rejects that combination with a 422 that links the issue. tests/unit/test_upstream_regressions.py reproduces it as a strict xfail, so CI goes red once an upstream release fixes it, which is the cue for ZS-076 to lift the guard. CI confirms the bug on Linux x86_64 too: the canary xfails there, so the guard applies to the production image.

Fixes (found while reviewing)

  • is_using_refiner: true on an unquantized hnsw field returned 500 after optimize. It now returns 400 (this bug predates the upgrade).
  • scale_factor that is negative, above 1000, or NaN/Infinity returned 500. It now returns 400.
  • fp16 + enable_rotate created collections that rejected every insert (0.7.0 and 0.8.1). It now returns 422.
  • Shutdown flushed and closed each collection under two separate exclusive-lock holds, so a queued optimize could slip in between (CI-only failure on 3.12). Both now happen under one hold (ZS-080).
  • Backlogged rather than fixed here: ZS-078 (int params ≥ 2³¹ return 500) and ZS-079 (VECTOR_INT8 inserts return 500).

Measured (SIFT1M, hnsw, ef=200, c=16, Apple M5, engine tier)

quantize_type disk vs fp32 recall@10 + refiner QPS vs fp32
fp32 687 MB 0.996 — 21.3k
int8 +20% 0.987 0.995 +25%
uniform_uint8 +20% 0.995 0.995 +20%
uniform_uint4 +11% 0.834 0.994 +16%
pq (32×8) +7% 0.717 0.991 +20%

Disk never shrinks, because Zvec keeps the FP32 vectors. The refiner at scale_factor 0 halves throughput; scale_factor 5 gives pq 0.984 recall at +11% QPS. Full table in docs/API.md.

Breaking

Test plan

  • ruff check, ruff format --check, mypy, pytest (303 passed + 1 expected xfail), tracker --check, run locally on macOS arm64 / Python 3.13
  • Benchmark tests (32) and SIFT1M sweeps on the engine and http tiers
  • CI: Python 3.12 + 3.13 on Linux, and the canary still xfails there
  • Deps: pillow 13.0.0 and certifi 2026.10.23 (bench extra only) clear the 42 Dependabot alerts

Closes ZS-068 – ZS-075, ZS-077 and ZS-080. Refs ZS-076, ZS-078, ZS-079.

🤖 Generated with Claude Code

TheAli711 and others added 4 commits November 9, 2026 18:43
The quant sweep gains uniform_uint{4,7,8}, pq, pq4, pq+opq and IVF pq4+fs
variants, a --refine scale-factor axis (never applied to fp32 hnsw), and
--index/--ef/--nprobe/--concurrency/--pq-chunks flags. Variants a tier
rejects (e.g. the server's 422 for hnsw + int8 + rotation) are recorded as
skipped instead of aborting the sweep. The http optimize timeout is raised
to 30 min for PQ codebook training on 1M vectors, and capture_env reports
the real CPU name on macOS.

Closes: ZS-074
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…relaxed ids

Require zvec>=0.8.1 (SPEC-015). Collections built on 0.7.0 open, search,
and re-optimize unchanged.

- quantize_type accepts uniform_uint4/7/8 (hnsw, l2) and pq (hnsw, ivf;
  FP32) with num_chunk / num_bits / fast_scan / opq_iter / opq_pq_iter,
  all validated server-side with specific 422s (Zvec's own errors are
  generic, and some values crashed its constructors).
- Search params gain scale_factor on hnsw/hnsw_rabitq, and
  is_using_refiner + scale_factor on ivf.
- Document ids follow Zvec 0.8 (UTF-8, up to 1,024 bytes); GET
  /docs/{doc_id} routes ids containing "/".
- Guard: hnsw + int8/int4 + enable_rotate is rejected (422) because Zvec
  0.8 returns near-random results for new such indexes after optimize
  (alibaba/zvec#886; recall@10 = 0.000 on SIFT1M). A strict-xfail canary
  test flags when upstream fixes it (ZS-076).
- Fixes: refiner on an unquantized hnsw field, and out-of-range or
  non-finite scale_factor, returned 500 (now 400); fp16 + enable_rotate
  created collections that rejected every insert (now 422).
- docs/API.md reports measured SIFT1M trade-offs for every quantizer, with
  and without the refiner.

Breaking: hnsw + int8/int4 + enable_rotate and fp16 + enable_rotate now
return 422, and index_param.quantizer_param lists all six settings.

Closes: ZS-068, ZS-069, ZS-070, ZS-071, ZS-072, ZS-073, ZS-075, ZS-077
Refs: ZS-076, ZS-078, ZS-079
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… shutdown

close() flushed every collection (exclusive lock, released) and then took
the exclusive lock again to close the handle, so an optimize queued behind
shutdown could run in between against a collection being shut down. Flush
and close now happen in the same gen_wlock() block. Surfaced as a CI-only
failure of test_drop_or_close_waits_for_a_running_optimize[close] on
Python 3.12 with Zvec 0.8.1. Removes the now-unused flush_all().

Closes: ZS-080
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both come only through the bench extra (matplotlib, httpx) and are not in
the server image, but the bump clears the 42 open Dependabot advisories
(6 critical). Bench tests and matplotlib PNG output verified on Pillow 13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheAli711
TheAli711 merged commit 92a80d8 into main Nov 9, 2026
9 checks passed
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