Repository navigation
feat(python): train, add and search for SymqgIndex - #205
Merged
Merged
Conversation
SymqgIndex follows the same train -> add -> search workflow as IvfIndex and HnswIndex. - SymqgIndex(dim, *, nbits=32, max_degree=32, ef_construction=200, init="pipnn", metric="l2", seed=None). nbits replaces quantization_bits, with 32 instead of 0 for raw vectors. dim is checked at construction. seed drives the rotation and graph construction; None draws one, which index.seed reports. - train(x) checks x as add would and changes nothing; is_trained is always True. add(data, *, num_threads=0) builds the graph once and a second call raises, as Faiss's NSG index does. search(queries, k, ef=None) uses max(k, 10) for a missing or zero ef and raises smaller values to k. ef_construction, init and seed are None after load. - The 0.8 compatibility layer keeps SymqgIndex(dim, max_degree, metric, quantization_bits), quantization_bits=, build(), search_batch() and the quantization_bits and is_built properties with DeprecationWarning. The deprecated build replaces any graph and, without a user seed, draws a new seed each time, as in 0.7. - HnswIndex.search also accepts ef=None. - Docs, samples and the migration page use the new API.
txtai now reads num_stored_points for its id offset and delete bounds and num_points for its count, and no longer counts deletes itself. Exercise those calls, as neuml/txtai 4d5e05f makes them, instead of max_elements.
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.
Summary
Third of three PRs moving the Python indexes to a Faiss-style
train→add→searchworkflow, after #203 (C++) and #204 (IvfIndex/HnswIndex). This PR bringsSymqgIndexinto the same workflow. The C++QuantizedGraphandQGBuilderare unchanged.New API
SymqgIndex(dim, *, nbits=32, max_degree=32, ef_construction=200, init="pipnn", metric="l2", seed=None). Everything afterdimis keyword-only.nbits(4, 8 or 32) replacesquantization_bits; 32 means raw vectors, which 0.7 called 0.max_degreekeeps its name: it is a hard bound and a multiple of 32, unlike HNSW'sM.dimis now checked at construction, as IVF/HNSW already do, rather than only inadd.seeddrives both the rotation and graph construction.Nonedraws one seed at construction, matching 0.7's random default while making it reproducible, andindex.seedreports it.train(x, *, num_threads=None)checksxasaddwould (shape and coordinates) and changes nothing;is_trainedis alwaysTrue.add(data, *, num_threads=0)builds the graph from all vectors, using the constructor'sef_construction,initandseed. A secondadd, oraddon a loaded index, raisesRuntimeError("SymqgIndex does not support incremental addition; ..."), as Faiss's NSG index does.addreturnsNone, as in Faiss.IvfIndex.addandHnswIndex.addkeep returning the new IDs, a public behavior since 0.5.1/0.5.2 that the docs teach; changing them toNonewould break existing code without any possible warning, so they are unchanged.search(queries, k, ef=None, num_threads=1): a missing or zeroefusesmax(k, 10), and a smallerefis raised tok, as in HnswIndex.HnswIndex.searchnow also acceptsef=None.len(index)and thenbits,ef_construction,initandseedproperties. Afterload,ef_construction,initandseedareNone, because files do not store them.searchandsaveraise "SymqgIndex has no vectors; add them or load an index before ...".0.8 compatibility layer (removed in 0.9)
SymqgIndex(dim, max_degree, metric, quantization_bits)andquantization_bits=are mapped tonbitsinrabitqlib/__init__.py, so there is no private native overload. The other deprecated names arebuild(data, ef_construction, num_threads=1, init="pipnn"),search_batch(...), and thequantization_bits(0/4/8) andis_builtproperties. Each raises aDeprecationWarningat the caller's line.buildkeeps its 0.7 behavior: it replaces any existing graph, and without a user-given seed it draws a new seed each time. Mixed or invalid old forms raiseTypeError/ValueError.@deprecated.Behavior changes for existing code
searchwithef=0oref < kno longer raises: the zero case uses the default, and a smaller value is raised tok.reprshowsnbitsand no longerquantization_bits/is_built.Other changes
index/qg.md,usage.md(SymphonyQG'strainonly reads, so it may run alongside searches),hnsw.md(ef=None), and a SymphonyQG section on the migration page, which is reorganized into IVF/HNSW, SymphonyQG, and files/C++ sections.symqg_indexing.pyuses the constructor andadd, and its--quantization-bitsflag is now--nbits(4/8/32).symqg_querying.pyusessearch.benchmarks/run.pyare left for the 0.8.0 release, and existing tests stay on the 0.7 forms as coverage of the compatibility layer.No saved-index format changes and no new dependencies.
Testing
All checks below ran on
45049b4(ona4d0c8a), on Linux x86-64, GCC 14.3,RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF. The follow-up273c0c8changes onlytest_txtai_compat.py, which passes and is clean undercheck-python.sh../scripts/check-format.sh,./scripts/check-python.sh, and full./scripts/check-tidy.sh(library and bindings) passed.test_symqg_api.py: signatures, defaults and keyword-only arguments; argument errors, includingdimat construction; seeds;train(shape and NaN checks,num_threads=None); andaddonce per index, including after load. Also: settings beingNoneafter load,addusing the constructor settings (it matches a deprecatedbuildwith the same settings byte for byte), theefdefaulting and raising rules, operations beforeadd, and type errors not mentioning private overloads.test_compat_08.py: each SymphonyQG 0.7 form warns once at the caller, andquantization_bits=gives the same file as the matchingnbits. Also covered: mixed or invalid old forms,build/search_batch/is_built, rebuilding withbuild, and a fresh seed perbuildunless one was given.test_train_api.py:HnswIndex.search(ef=None)equalsef=0.test_txtai_compat.pynow mirrors txtai upstream (neuml/txtai4d5e05f): it readsnum_stored_pointsfor the ID offset and delete bounds andnum_pointsfor the count, and no longer counts deletes itself. txtai ignoresadd's return value and does not useSymqgIndex.main:mainbefore feat(index): train centroids inside IVF and HNSW indexes #203 (6ba97a8) and this branch were built in scratch copies, with the random seeds pinned by temporary patches that are not part of this PR. 108 SymphonyQG arrays and 302 IVF/HNSW arrays from the 0.7 call forms were compared bit for bit, and none differ. The SymphonyQG cases cover L2/IP, 0/4/8 bits, PiPNN and random init,search,search_batch, saved bytes and loaded searches.DeprecationWarningas an error.mkdocs build --strictpassed, and theqg.mdsnippet was executed withDeprecationWarningas an error.num_threads=1.