Current section

Files

Jump to
vettore CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to Vettore are documented here. The project follows
Semantic Versioning.
## [Unreleased]
## [0.3.5] - 2026-08-25
### Added
- Added `Vettore.Vector` as a validated interchange layer for numeric lists,
little-endian f32 binaries, dimensioned vector wrappers, and host-provided Nx
tensors.
- Added native metrics, normalization, and row-selective mean pooling directly
over little-endian f32 binaries, including Model2Vec-style token-table
pooling without materializing the full matrix on the BEAM.
- Added the zero-dependency `Vettore.Interop.Nx` runtime adapter. Vettore does
not declare Nx as a dependency; applications that already use Nx can opt
into tensor conversion without changing Vettore's core runtime.
- Added native wgpu compute kernels for every dense metric, L2/z-score/min-max
normalization, and row-selective mean pooling. CPU SIMD remains the default;
`gpu: true` and `gpu: :auto` can be configured globally or per call with an
explicit CPU/error fallback policy.
- Added `Vettore.gpu_detected?/0`, `Vettore.gpu_info/0`, and
`Vettore.Compute.info/0` for hardware detection and runtime diagnostics.
- Added shape-preserving wrappers and Nx conversion, backend transfer/type
helpers, plus matrix `stack/2`, `take_rows_f32/4`, shape, and full-validation
APIs.
- Added an exact GPU Flat search path with a generation-aware resident embedding
matrix, one-query-by-all-rows scoring shaders for every metric, device-side
two-stage top-k reduction, and readback of only the final ids and scores.
- Added batched GPU scoring for exact adaptive/hybrid reranks and a dedicated
cold-upload versus warm-query Flat benchmark.
### Changed
- Replaced the Flat index's fragmented vector map with a contiguous row-major
matrix plus id-to-row map. CPU exact scans retain portable SIMD kernels with
improved locality, while mutations atomically invalidate the optional GPU
snapshot.
- Extended every `Vettore.Distance` dense metric and normalization helper with
per-call compute options while preserving existing arities.
- Nx remains runtime-only and absent from both Mix and Cargo dependencies; GPU
execution is implemented entirely in Rust and does not use Nx.
- Vectorized the f64 accumulation used by stable cosine scoring and exceptional
overflow recovery, so numerical safeguards no longer force those hot loops
back to scalar iteration.
- Flat index options now accept `:gpu`, `:gpu_min_size`, and `:gpu_fallback`.
Automatic selection uses the real `rows * dimensions` search workload.
- GPU Flat queries reuse bounded pools of query, score, top-k, uniform, bind
group, and staging resources. Concurrent warm searches do not hold the cache
construction lock while dispatching.
- GPU calls run concurrently without a process-wide execution mutex. Runtime
failures invalidate the cached device, initialization failures use a bounded
retry window, and readback waits default to a configurable 10-second timeout.
### Fixed
- Stabilized GPU metrics across the finite f32 range by scaling operands before
shader reduction and rescaling checked results on the host. This fixes cosine
overflow/underflow, cancelling extreme dot products, and large L1/L2 inputs.
- Replaced one-pass f32 normalization statistics with two-pass f64 host
preparation, fixing tiny L2 vectors and catastrophic z-score cancellation.
- Guarded dispatch counts and storage bindings against the selected device
limits before submitting work, preventing wgpu validation panics on oversized
vectors and matrices.
- Made `gpu: :auto` use CPU when no adapter is available even when the forced-GPU
fallback policy is `:error`, and normalized runtime failures to stable atoms.
- Added direct GPU NIF paths for little-endian f32 binaries, structural binary
dimension checks, GPU-backed CI with lavapipe, Vector/Compute doctests, and an
explicit Rust 1.91 MSRV check.
- Aligned transient CPU and GPU top-k overflow semantics with Flat search: only
the unrepresentable row is skipped, while valid candidates are retained.
- Added explicit per-score validity in resident shaders so overflow cannot be
mistaken for a zero-valued candidate, including at the finite f32 boundary.
- Made hardware-dependent parity tests and benchmark preflights aware of f32
reduction-order ties. Scores and unambiguous ranks remain checked, while ids
may permute inside a numerically tied boundary group.
- Made the resident GPU benchmark parse metrics through a closed lookup table,
fixing its CI preflight in a fresh BEAM where the requested metric atom did
not already exist.
- Added strict resident-GPU parity coverage over committed and freshly inferred
384-dimensional `BAAI/bge-small-en-v1.5` document/query embeddings, including
semantic retrieval and resident-cache reuse assertions.
- Rebuilt resident buffers after every effective mutation and cached
deterministic build failures by index generation, preventing repeated full
snapshots for an unchanged unsupported matrix.
### Performance
- GPU devices and compiled pipelines are initialized lazily and reused. CPU
calls skip GPU detection entirely, and GPU mean pooling uploads only selected
rows rather than the complete model matrix.
- Flat snapshot sorting, device upload, dispatch, and readback now happen
outside the index read lock; writers are blocked only while the immutable host
snapshot is copied.
- Replaced the single-thread-per-chunk resident top-k pass with a 16-lane local
reduction followed by the compact final merge.
- Parallelized GPU mean pooling across 256 lanes per output column and scaled
each selected column before accumulation, reducing drift and avoiding
representable means overflowing during intermediate f32 sums.
- Cached failed GPU runtime initialization for ten seconds so hosts without an
adapter do not enumerate and request a device on every fallback call.
## [0.3.4] - 2026-08-23
### Fixed
- Updated the local Rust toolchain pin to the crate's Rust 1.91 MSRV.
- Corrected ExDoc's landing page and source links for versioned releases.
- Replaced panic-prone HNSW graph lookups with recoverable errors and made
flat/HNSW searches skip individual rows whose score overflows.
- Serialized ETS snapshots with writes so snapshots represent one consistent
point in time.
- Aligned collection metric aliases with the compatibility constructor and
made empty-id reads and deletes return `{:error, :invalid_id}` consistently.
### Changed
- Changed the `Vettore.new/1` default from `score: :raw` to
`score: :similarity`, matching the compatibility API. This changes
`Result.score` values for callers that omitted the option; pass
`score: :raw` to preserve the previous scale. Existing snapshots retain the
score mode stored in their configuration.
- Collections and compatibility databases are now reclaimed automatically when
the process that created them exits. Long-lived resources must be created by
a long-lived owner rather than handed off from a short-lived task or request
process.
### Security and reliability
- Capped implicit adaptive-search candidate counts at 1,000,000 and reject
adaptive searches whose result limit exceeds that bound. Explicit candidate
counts above the bound, previously accepted, now return
`{:error, :invalid_candidates}`.
- Added Hex and RustSec dependency audits to CI.
## [0.3.3] - 2026-08-12
### Fixed
- Rejected non-UTF-8 ids before ETS writes and Rustler decoding, preventing a
failed insert from leaving the canonical store and native index inconsistent.
- Made cosine collection ranking compute true cosine even when normalization is
explicitly disabled or uses a non-L2 transform.
- Kept quantized Hamming and Jaccard candidate selection consistent with their
non-zero truth semantics.
- Serialized ETS-backed store/index mutations so concurrent puts and deletes of
the same id cannot leave phantom native entries.
- Corrected compatibility insert and batch return ids when an empty `id` falls
back to `value`.
### Changed
- Centralized index lifecycle, mutation, and search boundaries to remove cyclic
module dependencies and keep flat and HNSW behavior aligned.
- Added reverse HNSW edges, diversified pruning, local reconnection after
deletes, and fresh search bounds to improve recall and deletion cost.
- MMR now runs in one native batch instead of one NIF transition per pair.
- Hybrid search reuses one ETS snapshot across adaptive generators, multi-stage
funnel search narrows candidates progressively, and `close/1` clears native
index memory immediately.
### Tests and release engineering
- Raised enforced Elixir line coverage to 99%; the full suite reaches 99.36%
with 175 passing checks.
- Added a committed embedding fixture generated by the real `ex_fastembed`
backend, plus deterministic offline integration tests and an opt-in fixture
regeneration script.
- Updated Elixir and Rust dependency locks, including `wide` 1.6.1.
## [0.3.2] - 2026-07-21
### Fixed
- Fixed HNSW reciprocal-link pruning that left almost every newly inserted node
unreachable and caused severe recall loss.
- Hydrated HNSW and flat results from canonical ETS records so `value` and
`metadata` are preserved, while stale native ids are ignored safely.
- Normalized representative vectors derived from multi-vector records before
cosine indexing.
- Corrected `:negative_inner_product` score conversion and MMR similarity
semantics, including malformed or missing MMR ids.
- Replaced raising input paths with tagged errors for collection options,
vectors, adaptive search, multi-vector scoring, and MUVERA configuration.
- Validated custom-store records before adaptive NIF calls so malformed ids,
duplicate rows, dimensions, and out-of-f32 values return tagged errors rather
than raising during Rustler decoding.
- Made snapshot index overrides persistent when a loaded collection is
snapshotted again; added schema, record, checksum, and corruption validation.
- Loaded legacy public-table snapshots under protected ownership instead of
preserving unsafe table permissions.
- Prevented collection-table leaks when index construction, snapshot restore,
duplicate compatibility creation, or database deletion fails.
- Made compatibility-database shutdown atomic with concurrent collection
creation and removed the unsafe timeout on large owner-mediated batches.
- Deleted drained ETS tables before acknowledging database shutdown so callers
consistently observe `{:error, :closed}` immediately after `close/1`.
- Added overflow-safe L2, cosine, z-score, and min-max normalization for extreme
finite f32 values.
- Recovered valid large L2 and cancelling dot-product results with f64 fallback,
while rejecting genuinely unrepresentable squared distances and MaxSim sums.
- Propagated metric failures through MMR and late-interaction reranking instead
of silently substituting misleading zero scores.
- Bounded MUVERA seed decoding and rejected non-finite encoding accumulation
instead of returning corrupted vectors.
- Kept Rust panics unwindable in release NIFs so Rustler can contain them
instead of aborting the BEAM VM.
### Changed
- ETS tables are now owned by supervised temporary workers instead of the
process that creates a collection. Tables survive caller exit and are
protected against out-of-band writes.
- Added idempotent `Vettore.close/1` for deterministic collection and
compatibility-database cleanup.
- Collection construction and search reject unknown, duplicated, or malformed
options instead of silently ignoring them.
- Snapshot files are written through a temporary file and include ETS object
count and MD5 integrity metadata.
- Local Rust builds are now explicitly enabled with `VETTORE_BUILD=1`; normal
dependency builds use the published precompiled NIFs.
### Performance
- Protected ETS tables keep concurrent-read optimization enabled while avoiding
unused concurrent-writer bookkeeping behind their single supervised owner.
- Direct ETS reads no longer perform redundant owner/table liveness probes;
closed-table races are handled on the actual operation instead.
- Default cosine helper calls now use one overflow-safe native kernel instead
of two normalization calls followed by a third metric call.
- Exact flat search computes each metric once and retains top-k results with a
bounded heap instead of sorting every record.
- HNSW batches inserts under one native write lock.
- Funnel, quantized, hybrid, and multi-vector paths batch candidate scoring into
dirty CPU NIF calls, avoiding one NIF transition per record or vector pair.
### Tests and release engineering
- Added 50 Rust unit tests covering every metric, scalar/SIMD differential
checks, all top-k limits, HNSW graph invariants and recall, packed-bit word
boundaries, multi-vector scoring, and MUVERA safety. Algorithm modules exceed
99% Rust line coverage.
- Expanded the Elixir suite to 161 passing checks, including 60 doctests,
fault-injected failure paths, concurrent readers and writers, and 98%+ line
coverage (excluding unmeasurable NIF fallback stubs).
- Added real `BAAI/bge-small-en-v1.5` exact/HNSW/hybrid integration coverage.
- Added deterministic latency and overlap benchmarks for every search mode,
direct MaxSim, MUVERA encodings, and the ETS read/write ownership boundary.
- Added pull-request CI for enforced Elixir and Rust algorithm coverage, Credo,
Dialyzer, docs, Hex package contents, Rust formatting, tests, and Clippy.
- Added explicit Cargo feature forwarding and CI checks for Rustler NIF 2.15 and
2.16 precompiled artifact builds.
- Updated Rustler to 0.38, RustlerPrecompiled to 0.9, and `wide` to 1.5. Local
native builds now require Rust 1.91 or newer.
- Isolated the real `ex_fastembed` integration dependency from published Hex
metadata so its older Rustler constraint cannot block application updates.
- Made the complete precompiled-NIF matrix manually runnable from a release
branch without creating or publishing a GitHub Release.
- The tag release workflow now validates version/tag parity and publishes a
generated Rustler checksum file with the native archives.
## [0.3.1]
- Previous release.