Repository navigation
Conversation
This was referenced Oct 23, 2026
Open
MayCXC
force-pushed
the
diskann-index-photostructure
branch
from
October 23, 2026 17:03
6a7ff94 to
05562a9
Compare
This was referenced Oct 23, 2026
Member
A vector column can name the index that answers its KNN queries, as in asg017/sqlite-vec@0de765f. flat() is the exhaustive search a column without the clause gets. The scanner reads '(', ')' and ',' for the index's options, and the clause's keywords match exactly even when an old table's definition is parsed leniently, since no old table has one.
A float vector column declared `indexed by rescore(quantizer=bit)` or `indexed by rescore(quantizer=int8)`, with an optional `oversample=N` from 1 to 128 (8 by default), also keeps its vectors quantized in _rescore_chunksNN, in the layout of _vector_chunksNN, and its float32 vectors by rowid in _rescore_vectorsNN. A KNN query scans the quantized vectors for the k * oversample nearest rows, then ranks those candidates by the distances of their float32 vectors. The `oversample=N` command sets the oversample of a connection's queries. A full scan reads a rescore column's vectors row by row through vec0_get_vector_data(), and a flat column's by chunk as before. Ported from asg017#276 (ba0db0b) as asg017's main has it at 04d28bd: with the int8 quantizer that 575371d fixed, the fixes 5e4c557, 82f4eb0 and 5522e86, auxiliary columns (b7fc459), the oversample=N command and its unknown-command message (6e2c4c6), and the distance constraints of the unmerged asg017#309. SQLITE_VEC_ENABLE_RESCORE=0 builds without it. Where this port differs from upstream: - rescore_knn() takes vec0Filter_knn_chunks_iter()'s arguments and results, so vec0Filter_knn() picks between the two and the MMR rerank runs after either. It reports errors as the chunk scan does, so an interrupt stays an interrupt. It picks each chunk's candidates with this fork's min_idx(), whose heap needs no bitmap of taken slots. - Distance constraints with a NULL, TEXT or BLOB value act as they do for a flat column. - optimize, which upstream lacks, writes a moved row's quantized vectors to its new slot and leaves its row of _rescore_vectorsNN, whose rowid does not change, and deletes the old _rescore_chunksNN rows with the old chunks. A DELETE that empties a chunk deletes its _rescore_chunksNN row too. - A point query for a rowid the table does not have returns no row, since vec0_get_vector_data() finds the row's position first, as for a flat column; upstream fails with "Could not fetch vector data". - _rescore_chunksNN declares "rowid INTEGER PRIMARY KEY", as this fork's new tables do, and an oversample=N value must be all digits. The tests are upstream's test-rescore.py and test-rescore-mutations.py, the rescore cases of its test-auxiliary.py and test-rename.py, its rescore fuzz targets, and test-unit.c's bit quantizer, byte size and parser cases. Upstream's int8 quantizer unit tests, and the uniform-vector case of rescore-quantize-edge.c, test the min/max quantizer that 575371d replaced, so they are written here for the current one. Modes 1 and 2 of rescore-quantize-edge.c copy the fuzz bytes out to float alignment before reading them as floats, as asg017#343 has them; upstream's read floats one byte past the mode byte, a misaligned load UBSan reports. New tests check that each row's quantized slot holds the quantized form of its vector through inserts, updates, deletes that empty chunks, and optimize, and rescore-optimize.c fuzzes optimize. test-fullscan.py reads a rescore column beside a flat one, and runs its delete and optimize cases on a rescore column too; read by chunk, a rescore column fails all three with "Could not fetch vector data".
test_interrupt_is_reported_as_interrupt runs a statement on a copy of a database, interrupted at each progress handler call, and expects SQLITE_INTERRUPT. A rescore table joins its database, with a KNN query, a full scan, a point query, an insert, an update, a delete, a delete that empties a chunk, and optimize, so the rescore code's internal statements are interrupted too. A build whose rescore insert returns SQLITE_ERROR in place of vec0_error_rc()'s code fails the insert case.
A float or int8 vector column declared `indexed by diskann(neighbor_quantizer=binary)` or `indexed by diskann(neighbor_quantizer=int8)` keeps a graph of its rows, after the LM-DiskANN paper: each row links to up to n_neighbors others (72 by default) and keeps their vectors quantized. A KNN query walks the graph from its medoid, keeping the search_list_size nearest rows it has seen (128 by default), and ranks each row by its exact distance once it reaches it. With buffer_threshold=N, inserted rows wait in a buffer and join the graph N at a time, and a query reads the buffer in full. The graph is in _diskann_nodesNN, the vectors by rowid in _vectorsNN, the buffer in _diskann_bufferNN and the medoid in _info, declared as upstream declares them, so a database opens with either. A table whose every vector column is indexed by diskann() keeps no chunks. The search_list_size=N, search_list_size_search=N and search_list_size_insert=N commands set the sizes of a connection's searches. Ported from asg017's sqlite-vec-diskann.c as its main has it at 04d28bd (575371d), with the refusal of a vector UPDATE (9df59b4), the confirmed flag that keeps deleted nodes out of results (b008654), the delete repair's error (c36a995) and scrub (01b4b2a), the NULL blob checks (82f4eb0), auxiliary columns (b7fc459), the rename of its shadow tables (c560710, 8105eee), the finalizing of every cached statement on commit (fe94171), and the text primary key tests (f2c9fb8). SQLITE_VEC_ENABLE_DISKANN=0 builds without it. Where this port differs from upstream: - diskann_knn() takes rescore_knn()'s arguments and results: a rowid filter ranks the rows it names by their exact distances, distance constraints filter the rows the search found, and MMR reranks them. Upstream reads only the query vector and k, so it returns rows outside a rowid filter or a distance constraint. - An int8 column's vectors are quantized as int8 values: under the int8 quantizer a vector is its own quantized form, and under binary each value above 0 sets its bit. Upstream reads an int8 query as floats, 4 times its bytes, and takes an int8 vector's first bytes as its binary form. - Re-ranking replaces a row's quantized distance with its exact one whether it is larger or smaller, so every result's distance is exact; upstream keeps the quantized distance when the exact one is larger, as it often is under the int8 quantizer with cosine. - A deleted medoid is replaced by a node of the graph. Upstream picks a row of _vectorsNN, which can be one still in the buffer, with no node, so no search reaches the graph until the buffer is flushed. - The visited set grows as a search fills it and holds rowid 0; upstream's has 4 * search_list_size slots, answers "not visited" once they are full, and cannot hold rowid 0. - Errors reach the statement, so an interrupt stays an interrupt, through the search, the prune, the reverse edges, a delete's repair and scrub, the buffer and its flush, and the KNN query's read of the buffer; upstream skips the failed step or reports SQLITE_ERROR. A node blob whose size the column's options do not give fails with a message naming the node. - A cached statement is reset as soon as it has run, so VACUUM works after a KNN query; upstream's stay in progress until the next commit, and VACUUM fails with "SQL statements in progress". - diskann_robust_prune() runs diskann_prune_select(), the selection the unit tests and diskann-prune-direct.c exercise, which reads the distance between two candidates through a callback; upstream's insert runs a copy of the selection inline, and its diskann_prune_select() takes a matrix of distances only the tests build. - The int8 quantizer clamps to [-128, 127] and takes NaN to 127, as the rescore int8 quantizer does; upstream casts out-of-range floats to int8. - CREATE options and their values must be spelled in full and their numbers fit an int; upstream accepts any prefix, as in `n_neigh=16` or `neighbor_quantizer=b`. The commands take digits only, as oversample=N does, and set every DiskANN column of the table; upstream reads them with atoi() and sets only the first DiskANN column. - optimize, which upstream lacks, leaves a DiskANN column's rows, which are keyed by rowid, as they are. The tests are upstream's test-diskann.py, the DiskANN cases of its test-auxiliary.py, test-rename.py and test-cache-finalize.py, its DiskANN fuzz targets with their dictionary, renamed diskann-create.dict for the target the fuzz workflow passes it to, and test-unit.c's DiskANN cases, which call the functions through tests/sqlite-vec-internal.h. New tests check that a search covering the table answers as a flat column does, int8 columns under both quantizers, exact distances, the medoid's replacement from the graph, a point query for a rowid the table does not have, a corrupt node blob, the parsing of options and commands, optimize beside a flat column, and VACUUM after a KNN query. test-fullscan.py reads a DiskANN column beside a flat one.
test_interrupt_is_reported_as_interrupt runs a statement on a copy of a database, interrupted at each progress handler call, and expects SQLITE_INTERRUPT. Two DiskANN tables join its database, one holding three rows in its buffer, with KNN queries on the graph, on a rowid filter and over the buffer, a full scan, a point query, an insert, an insert that flushes the buffer, an auxiliary column update, deletes of a node, of the medoid and of a buffered row, and optimize, so the DiskANN code's internal statements are interrupted too. A build whose node write or node read returns SQLITE_ERROR in place of vec0_error_rc()'s code fails 8 or 12 of the cases.
MayCXC
force-pushed
the
diskann-index-photostructure
branch
from
October 24, 2026 01:58
05562a9 to
8c5e60c
Compare
This branch has not been deployed
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.
Ports asg017's DiskANN index, the second of its approximate-nearest-neighbour indexes, as asg017's main has it at 04d28bd. This builds on #8: its three commits come first here, and the last two are this PR's.
What it adds. A
floatorint8vector column can be declaredindexed by diskann(neighbor_quantizer=binary)orindexed by diskann(neighbor_quantizer=int8). The column keeps a graph of its rows, after LM-DiskANN: each row links to up ton_neighborsothers (72 by default) and keeps their vectors quantized. A KNN query walks the graph from its medoid, keeping thesearch_list_sizenearest rows it has seen (128 by default), and ranks each row by its exact distance once it reaches it. Withbuffer_threshold=N, inserted rows wait in a buffer and join the graph N at a time, and a query reads the buffer in full.The graph is in
_diskann_nodesNN, the vectors by rowid in_vectorsNN, the buffer in_diskann_bufferNNand the medoid in_info, declared as upstream declares them, so a database opens with either. A table whose every vector column is indexed bydiskann()keeps no chunks.SQLITE_VEC_ENABLE_DISKANN=0builds without it.What it ports. asg017's
sqlite-vec-diskann.cas of 575371d, with the refusal of a vectorUPDATE(9df59b4), the confirmed flag that keeps deleted nodes out of results (b008654), the delete repair's error (c36a995) and scrub (01b4b2a), the NULL blob checks (82f4eb0), auxiliary columns (b7fc459), the rename of its shadow tables (c560710, 8105eee), the finalizing of every cached statement on commit (fe94171), and the text primary key tests (f2c9fb8).Where it differs from upstream.
diskann_knn()takesrescore_knn()'s arguments and results: a rowid filter ranks the rows it names by their exact distances, distance constraints filter the rows the search found, and MMR reranks them. Upstream reads only the query vector andk, whilexBestIndexhas told SQLite the other constraints are handled, so it returns rows outside them:int8column's vectors are quantized asint8values: under theint8quantizer a vector is its own quantized form, and underbinaryeach value above 0 sets its bit. Upstream reads anint8query as floats, 4 times its bytes, so AddressSanitizer reports a heap-buffer-overflow indiskann_quantize_vector()on inserts into anint8[2048]column, and it takes anint8vector's first bytes as its binary form.int8quantizer with cosine._vectorsNN, which can be one still in the buffer, with no node, so no search reaches the graph until the buffer is flushed.4 * search_list_sizeslots, answers "not visited" once they are full, and cannot hold rowid 0.SQLITE_ERROR. A node blob whose size the column's options do not give fails with a message naming the node.VACUUMworks after a KNN query; upstream's stay in progress until the next commit, andVACUUMfails withcannot VACUUM - SQL statements in progress.diskann_robust_prune()runsdiskann_prune_select(), the selection the unit tests anddiskann-prune-direct.cexercise, which reads the distance between two candidates through a callback; upstream's insert runs a copy of the selection inline, and itsdiskann_prune_select()takes a matrix of distances only the tests build.int8quantizer clamps to [-128, 127] and takes NaN to 127, as the rescoreint8quantizer does; upstream casts out-of-range floats toint8.int; upstream accepts any prefix, as inn_neigh=16orneighbor_quantizer=b. The commands take digits only, asoversample=Ndoes, and set every DiskANN column of the table; upstream reads them withatoi()and sets only the first DiskANN column.optimize, which upstream lacks, leaves a DiskANN column's rows, which are keyed by rowid, as they are.As with rescore, a table with a DiskANN column refuses metadata and partition key columns, and a DiskANN column's vector cannot be updated. ARCHITECTURE.md lists the new tables with the other backing tables, the README has a section on the index, and the commit adds an
### Addedentry under[Unreleased]in CHANGELOG.md.Tests.
diskann-create.dictfor the target the fuzz workflow passes it to, and test-unit.c's DiskANN cases, which call the functions throughtests/sqlite-vec-internal.h.int8columns under both quantizers; exact distances; the medoid's replacement from the graph; a point query for a rowid the table does not have; a corrupt node blob; the parsing of options and commands;optimizebeside a flat column;VACUUMafter a KNN query. test-fullscan.py reads a DiskANN column beside a flat one. Built from asg017's main, 6 of the 12 option cases fail, and so does theVACUUMcase.test_interrupt_is_reported_as_interrupt, one holding three rows in its buffer: KNN queries on the graph, on a rowid filter and over the buffer, a full scan, a point query, an insert, an insert that flushes the buffer, an auxiliary column update, deletes of a node, of the medoid and of a buffered row, andoptimize, each interrupted at every progress handler call. A build whose node write or node read returnsSQLITE_ERRORin place ofvec0_error_rc()'s code fails 8 or 12 of the cases.make test-loadable(680 passed, 3 skipped),make test-unitandmake testpass, and so does the Python suite against an AddressSanitizer and UndefinedBehaviorSanitizer build. GCC 14.2 and Clang 19.1 with-Wall -Wextraprint nothing for the loadable build with any combination ofSQLITE_VEC_ENABLE_RESCOREandSQLITE_VEC_ENABLE_DISKANN, and with-DSQLITE_COREonly main's three unusedpApiwarnings, which #9 removes. clang-format 21 leavessqlite-vec.cunchanged, and before this rebase each of the ten DiskANN fuzz targets ran 90 seconds without a finding.Rebased with #8 onto main at f6a3244, it merges cleanly with main and with #4. On that main, a DELETE learns from
vec0Update_Delete_ClearValidity()whether it emptied the chunk; a table whose every vector column is indexed bydiskann()keeps no chunks, so it clears no validity bit and deletes no chunk.