mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
1 Commits
hnsw-adapt
...
hnsw-read-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67c72ac8c3 |
14
README.md
14
README.md
@@ -29,7 +29,7 @@ make install # may need sudo
|
||||
|
||||
See the [installation notes](#installation-notes---linux-and-mac) if you run into issues
|
||||
|
||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), [pkg](#pkg), [APK](#apk), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
|
||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), [pkg](#pkg), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
|
||||
|
||||
### Windows
|
||||
|
||||
@@ -772,7 +772,6 @@ Use pgvector from any language with a Postgres client. You can even generate and
|
||||
Language | Libraries / Examples
|
||||
--- | ---
|
||||
Ada | [pgvector-ada](https://github.com/pgvector/pgvector-ada)
|
||||
Algol | [pgvector-algol](https://github.com/pgvector/pgvector-algol)
|
||||
C | [pgvector-c](https://github.com/pgvector/pgvector-c)
|
||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
||||
C#, F#, Visual Basic | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||
@@ -795,10 +794,8 @@ OCaml | [pgvector-ocaml](https://github.com/pgvector/pgvector-ocaml)
|
||||
Pascal | [pgvector-pascal](https://github.com/pgvector/pgvector-pascal)
|
||||
Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
||||
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
||||
Prolog | [pgvector-prolog](https://github.com/pgvector/pgvector-prolog)
|
||||
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
||||
R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
||||
Racket | [pgvector-racket](https://github.com/pgvector/pgvector-racket)
|
||||
Raku | [pgvector-raku](https://github.com/pgvector/pgvector-raku)
|
||||
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
||||
@@ -1232,14 +1229,6 @@ cd /usr/ports/databases/pgvector
|
||||
make install
|
||||
```
|
||||
|
||||
### APK
|
||||
|
||||
Install the Alpine package with:
|
||||
|
||||
```sh
|
||||
apk add postgresql-pgvector
|
||||
```
|
||||
|
||||
### conda-forge
|
||||
|
||||
With Conda Postgres, install from [conda-forge](https://anaconda.org/conda-forge/pgvector) with:
|
||||
@@ -1282,7 +1271,6 @@ Thanks to:
|
||||
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
||||
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
||||
- [Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs](https://arxiv.org/ftp/arxiv/papers/1603/1603.09320.pdf)
|
||||
- [Distance Adaptive Beam Search for Provably Accurate Graph-Based Nearest Neighbor Search](https://arxiv.org/pdf/2505.15636)
|
||||
|
||||
## History
|
||||
|
||||
|
||||
11
src/hnsw.h
11
src/hnsw.h
@@ -362,6 +362,13 @@ typedef union
|
||||
ItemPointerData indextid;
|
||||
} HnswUnvisited;
|
||||
|
||||
typedef struct HnswReadStreamData
|
||||
{
|
||||
HnswUnvisited *unvisited;
|
||||
int unvisitedLength;
|
||||
int visited;
|
||||
} HnswReadStreamData;
|
||||
|
||||
typedef struct HnswScanOpaqueData
|
||||
{
|
||||
const HnswTypeInfo *typeInfo;
|
||||
@@ -417,13 +424,13 @@ bool HnswCheckNorm(HnswSupport * support, Datum value);
|
||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||
void HnswInitPage(Buffer buf, Page page);
|
||||
void HnswInit(void);
|
||||
List *HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples);
|
||||
List *HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples, bool maintenance);
|
||||
HnswElement HnswGetEntryPoint(Relation index);
|
||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing);
|
||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing, bool maintenance);
|
||||
HnswSearchCandidate *HnswEntryCandidate(char *base, HnswElement em, HnswQuery * q, Relation rel, HnswSupport * support, bool loadVec);
|
||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
||||
|
||||
@@ -461,7 +461,7 @@ InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
||||
}
|
||||
|
||||
/* Find neighbors for element */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, NULL, support, m, efConstruction, false);
|
||||
HnswFindElementNeighbors(base, element, entryPoint, NULL, support, m, efConstruction, false, true);
|
||||
|
||||
/* Update graph in memory */
|
||||
UpdateGraphInMemory(support, element, m, entryPoint, buildstate);
|
||||
|
||||
@@ -729,7 +729,7 @@ HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPo
|
||||
}
|
||||
|
||||
/* Find neighbors for element */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, false);
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, false, building);
|
||||
|
||||
/* Update graph on disk */
|
||||
UpdateGraphOnDisk(index, support, element, m, entryPoint, building);
|
||||
|
||||
@@ -37,11 +37,11 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
|
||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||
{
|
||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, support, m, false, NULL, NULL, NULL, true, NULL);
|
||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, support, m, false, NULL, NULL, NULL, true, NULL, false);
|
||||
ep = w;
|
||||
}
|
||||
|
||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, support, m, false, NULL, &so->v, hnsw_iterative_scan != HNSW_ITERATIVE_SCAN_OFF ? &so->discarded : NULL, true, &so->tuples);
|
||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, support, m, false, NULL, &so->v, hnsw_iterative_scan != HNSW_ITERATIVE_SCAN_OFF ? &so->discarded : NULL, true, &so->tuples, false);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -72,7 +72,7 @@ ResumeScanItems(IndexScanDesc scan)
|
||||
ep = lappend(ep, sc);
|
||||
}
|
||||
|
||||
return HnswSearchLayer(base, &so->q, ep, batch_size, 0, index, &so->support, so->m, false, NULL, &so->v, &so->discarded, false, &so->tuples);
|
||||
return HnswSearchLayer(base, &so->q, ep, batch_size, 0, index, &so->support, so->m, false, NULL, &so->v, &so->discarded, false, &so->tuples, false);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
117
src/hnswutils.c
117
src/hnswutils.c
@@ -19,6 +19,10 @@
|
||||
#include "varatt.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
#include "storage/read_stream.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 170000
|
||||
static inline uint64
|
||||
murmurhash64(uint64 data)
|
||||
@@ -529,14 +533,12 @@ HnswGetDistance(Datum a, Datum b, HnswSupport * support)
|
||||
* Load an element and optionally get its distance from q
|
||||
*/
|
||||
static void
|
||||
HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance, HnswElement * element)
|
||||
HnswLoadElementImpl(Buffer buf, OffsetNumber offno, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance, HnswElement * element)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
HnswElementTuple etup;
|
||||
|
||||
/* Read vector */
|
||||
buf = ReadBuffer(index, blkno);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
|
||||
@@ -557,7 +559,7 @@ HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, Hns
|
||||
if (distance == NULL || maxDistance == NULL || *distance < *maxDistance)
|
||||
{
|
||||
if (*element == NULL)
|
||||
*element = HnswInitElementFromBlock(blkno, offno);
|
||||
*element = HnswInitElementFromBlock(BufferGetBlockNumber(buf), offno);
|
||||
|
||||
HnswLoadElementFromTuple(*element, etup, true, loadVec);
|
||||
}
|
||||
@@ -571,7 +573,9 @@ HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, Hns
|
||||
void
|
||||
HnswLoadElement(HnswElement element, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance)
|
||||
{
|
||||
HnswLoadElementImpl(element->blkno, element->offno, distance, q, index, support, loadVec, maxDistance, &element);
|
||||
Buffer buf = ReadBuffer(index, element->blkno);
|
||||
|
||||
HnswLoadElementImpl(buf, element->offno, distance, q, index, support, loadVec, maxDistance, &element);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -811,11 +815,31 @@ HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *u
|
||||
}
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
/*
|
||||
* Get next block number for read stream
|
||||
*/
|
||||
static BlockNumber
|
||||
HnswReadStreamNextBlock(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
|
||||
{
|
||||
HnswReadStreamData *streamData = callback_private_data;
|
||||
OffsetNumber *offno = per_buffer_data;
|
||||
HnswUnvisited *uv;
|
||||
|
||||
if (streamData->visited == streamData->unvisitedLength)
|
||||
return InvalidBlockNumber;
|
||||
|
||||
uv = &streamData->unvisited[streamData->visited++];
|
||||
*offno = ItemPointerGetOffsetNumber(&uv->indextid);
|
||||
return ItemPointerGetBlockNumber(&uv->indextid);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Algorithm 2 from paper
|
||||
*/
|
||||
List *
|
||||
HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples)
|
||||
HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples, bool maintenance)
|
||||
{
|
||||
List *w = NIL;
|
||||
pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL);
|
||||
@@ -829,7 +853,27 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
||||
HnswUnvisited *unvisited = palloc(lm * sizeof(HnswUnvisited));
|
||||
int unvisitedLength;
|
||||
bool inMemory = index == NULL;
|
||||
double lambda = 0.12;
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
HnswReadStreamData streamData;
|
||||
ReadStream *stream = NULL;
|
||||
|
||||
if (!inMemory)
|
||||
{
|
||||
int flags = READ_STREAM_DEFAULT;
|
||||
|
||||
if (maintenance)
|
||||
{
|
||||
flags |= READ_STREAM_MAINTENANCE;
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 180000
|
||||
flags |= READ_STREAM_USE_BATCHING;
|
||||
#endif
|
||||
|
||||
stream = read_stream_begin_relation(flags, NULL, index, MAIN_FORKNUM, HnswReadStreamNextBlock, &streamData, sizeof(OffsetNumber));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (v == NULL)
|
||||
{
|
||||
@@ -885,7 +929,7 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
||||
HnswSearchCandidate *f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
||||
HnswElement cElement;
|
||||
|
||||
if (c->distance > f->distance * (1 + lambda))
|
||||
if (c->distance > f->distance)
|
||||
break;
|
||||
|
||||
cElement = HnswPtrAccess(base, c->element);
|
||||
@@ -893,43 +937,73 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
||||
if (inMemory)
|
||||
HnswLoadUnvisitedFromMemory(base, cElement, unvisited, &unvisitedLength, v, lc, localNeighborhood, neighborhoodSize);
|
||||
else
|
||||
{
|
||||
HnswLoadUnvisitedFromDisk(cElement, unvisited, &unvisitedLength, v, index, m, lm, lc);
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
read_stream_resume(stream);
|
||||
|
||||
streamData.unvisited = unvisited;
|
||||
streamData.unvisitedLength = unvisitedLength;
|
||||
streamData.visited = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* OK to count elements instead of tuples */
|
||||
if (tuples != NULL)
|
||||
(*tuples) += unvisitedLength;
|
||||
|
||||
for (int i = 0; i < unvisitedLength; i++)
|
||||
for (int i = 0;; i++)
|
||||
{
|
||||
HnswElement eElement;
|
||||
HnswSearchCandidate *e;
|
||||
double eDistance;
|
||||
bool alwaysAdd = wlen < ef;
|
||||
double fDistance;
|
||||
|
||||
f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
||||
fDistance = f->distance * (1 + lambda);
|
||||
|
||||
if (inMemory)
|
||||
{
|
||||
if (i == unvisitedLength)
|
||||
break;
|
||||
|
||||
eElement = unvisited[i].element;
|
||||
eDistance = GetElementDistance(base, eElement, q, support);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPointer indextid = &unvisited[i].indextid;
|
||||
BlockNumber blkno = ItemPointerGetBlockNumber(indextid);
|
||||
OffsetNumber offno = ItemPointerGetOffsetNumber(indextid);
|
||||
Buffer buf;
|
||||
OffsetNumber offno;
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
void *offnoPtr;
|
||||
|
||||
buf = read_stream_next_buffer(stream, &offnoPtr);
|
||||
|
||||
if (!BufferIsValid(buf))
|
||||
break;
|
||||
|
||||
offno = *((OffsetNumber *) offnoPtr);
|
||||
#else
|
||||
ItemPointer indextid;
|
||||
|
||||
if (i == unvisitedLength)
|
||||
break;
|
||||
|
||||
indextid = &unvisited[i].indextid;
|
||||
buf = ReadBuffer(index, ItemPointerGetBlockNumber(indextid));
|
||||
offno = ItemPointerGetOffsetNumber(indextid);
|
||||
#endif
|
||||
|
||||
/* Avoid any allocations if not adding */
|
||||
eElement = NULL;
|
||||
HnswLoadElementImpl(blkno, offno, &eDistance, q, index, support, inserting, alwaysAdd || discarded != NULL ? NULL : &fDistance, &eElement);
|
||||
HnswLoadElementImpl(buf, offno, &eDistance, q, index, support, inserting, alwaysAdd || discarded != NULL ? NULL : &f->distance, &eElement);
|
||||
|
||||
if (eElement == NULL)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eElement == NULL || !(eDistance < fDistance || alwaysAdd))
|
||||
if (eElement == NULL || !(eDistance < f->distance || alwaysAdd))
|
||||
{
|
||||
if (discarded != NULL)
|
||||
{
|
||||
@@ -979,6 +1053,11 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
||||
w = lappend(w, sc);
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 170000
|
||||
if (!inMemory)
|
||||
read_stream_end(stream);
|
||||
#endif
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
@@ -1274,7 +1353,7 @@ PrecomputeHash(char *base, HnswElement element)
|
||||
* Algorithm 1 from paper
|
||||
*/
|
||||
void
|
||||
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing)
|
||||
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing, bool maintenance)
|
||||
{
|
||||
List *ep;
|
||||
List *w;
|
||||
@@ -1301,7 +1380,7 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
||||
/* 1st phase: greedy search to insert level */
|
||||
for (int lc = entryLevel; lc >= level + 1; lc--)
|
||||
{
|
||||
w = HnswSearchLayer(base, &q, ep, 1, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL);
|
||||
w = HnswSearchLayer(base, &q, ep, 1, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL, maintenance);
|
||||
ep = w;
|
||||
}
|
||||
|
||||
@@ -1320,7 +1399,7 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
||||
List *lw = NIL;
|
||||
ListCell *lc2;
|
||||
|
||||
w = HnswSearchLayer(base, &q, ep, efConstruction, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL);
|
||||
w = HnswSearchLayer(base, &q, ep, efConstruction, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL, maintenance);
|
||||
|
||||
/* Convert search candidates to candidates */
|
||||
foreach(lc2, w)
|
||||
|
||||
@@ -212,7 +212,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
||||
element->heaptidsLength = 0;
|
||||
|
||||
/* Find neighbors for element, skipping itself */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, true);
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, true, true);
|
||||
|
||||
/* Zero memory for each element */
|
||||
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
||||
|
||||
Reference in New Issue
Block a user