Add ggml-vector-index C API (turbovec POC)#153
Draft
dev-nid wants to merge 3 commits into
Draft
Conversation
This was referenced Jun 15, 2026
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.
Context
Part of the turbovec ANN POC (3-repo coordinated change: this repo +
tetherto/qvacpackagesembed-llamacppandrag). Goal of the POC is to land the final cross-package architecture for a TurboQuant-style ANN vector index inside the QVAC stack, even with naive internals — so subsequent quantization / SIMD / GPU work can be swapped in behind a stable C API without touching downstream packages.This PR is the fabric-side slice: a new public C API + scalar reference implementation + standalone C-API test. Strictly additive — no existing ggml type, llama symbol, or quantization code path is modified.
What was done
ggml/include/ggml-vector-index.h— new public C API. Lifecycle (create/free), mutation (add/remove/contains/prepare),search, persistence (write/load), stats (len/dim/bit_width), error enum. Documented little-endian.tvim v1on-disk format. Added toGGML_PUBLIC_HEADERSso consumers can#include "ggml-vector-index.h".ggml/src/ggml-vector-index.cpp— naive scalar reference implementation. Flatstd::vector<float>storage,std::unordered_map<uint64_t, size_t>id-to-slot map with swap-with-lastremove,std::priority_queuemin-heap top-k.add()is atomic (rejects duplicate ids — both vs existing entries and within the batch — without mutating state). Compiled intoggml-base.tests/test-vector-index.cpp— standalone C-API smoke. Exercises create/stats, add with non-trivial 64-bit ids (42,(1ULL<<40)+7,(1ULL<<62)+11,UINT64_MAX-13) to flush out sign-extension / typed-array pitfalls, atomic duplicate rejection, top-1 self-match search (score ≈ 1.0 on L2-normalized inputs), sentinel-padded tail whenk > len(UINT64_MAXids), remove, and write→free→load→re-search round-trip with the deletion preserved. Wired into the existingllama_build_and_testtarget so it runs under fabric'sLLAMA_BUILD_TESTS=ONtest suite.Why these choices
GGML_TYPE_TBQVEC*tensor type? Top-k search is not a tensor op; the existingGGML_TYPE_TBQ*family is for matmul-shaped operations on the KV-cache path. Index ops have different lifecycle (long-lived mutable store, file-backed) and different consumers (a JS addon, not a graph node). Keeping them as a sibling C module avoids pollutingggml.hwith non-tensor surface and keeps the index entirely off the LLM inference path.bit_widthbyte in the header is already reserved for the quantized v2 layout — POC accepts any[1, 32]value, validates the range, and stores full f32 regardless).bit_widthfield? So the quantized v2 implementation lands as a version bump rather than a breaking change. v1 readers stay valid; v2 readers honorbit_width != 32.O(N log k), but heap-based top-k is the shape the SIMD / Vulkan optimization phase will inherit (scratch arrays match what fused matmul+top-k kernels expect).add? Mirrors the upstreamturbovecRust crate'sadd_with_idssemantics so the JS-sideaddWithIdscan rely on "either all inserted or none" for the duplicate-id case. Also catches in-batch duplicates, not just collisions against the existing index.What's left (explicitly out of POC scope, all behind this same C API)
std::vector<float>storage with packed TBQ codes (fabric already has the codebook generator atscripts/compute_tq_codebooks.py). Honors thebit_widthfield that is currently ignored.None of the above require changes to
ggml-vector-index.h, the persistence format's structural fields, the addon binding, or any downstream JS code. They live entirely insideggml/src/ggml-vector-index.cppand (optionally) sibling files likeggml-vector-index-neon.cpp,ggml-vector-index.metal, etc.Verification
test-vector-indexis built viallama_build_and_testand runs under fabric's standalone test suite (cmake -DLLAMA_BUILD_TESTS=ON).embed-llamacpp) passes 3 tests / 69 assertions, including the sign-bit case ((1n << 63n) + 7n) that the C test deliberately leaves to the JS layer where the BigInt↔uint64_tboundary actually lives.Companion PRs
tetherto/qvac#2589
tetherto/qvac#2590