mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
1 Commits
hnsw-datum
...
samples-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64223989cd |
@@ -33,12 +33,6 @@ HnswInit(void)
|
||||
HNSW_DEFAULT_EF_CONSTRUCTION, HNSW_MIN_EF_CONSTRUCTION, HNSW_MAX_EF_CONSTRUCTION
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
,AccessExclusiveLock
|
||||
#endif
|
||||
);
|
||||
add_int_reloption(hnsw_relopt_kind, "dimensions", "Number of dimensions",
|
||||
HNSW_DEFAULT_DIMENSIONS, HNSW_MIN_DIMENSIONS, HNSW_MAX_DIMENSIONS
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
,AccessExclusiveLock
|
||||
#endif
|
||||
);
|
||||
|
||||
@@ -131,7 +125,6 @@ hnswoptions(Datum reloptions, bool validate)
|
||||
static const relopt_parse_elt tab[] = {
|
||||
{"m", RELOPT_TYPE_INT, offsetof(HnswOptions, m)},
|
||||
{"ef_construction", RELOPT_TYPE_INT, offsetof(HnswOptions, efConstruction)},
|
||||
{"dimensions", RELOPT_TYPE_INT, offsetof(HnswOptions, dimensions)},
|
||||
};
|
||||
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
|
||||
11
src/hnsw.h
11
src/hnsw.h
@@ -42,9 +42,6 @@
|
||||
#define HNSW_DEFAULT_EF_SEARCH 40
|
||||
#define HNSW_MIN_EF_SEARCH 1
|
||||
#define HNSW_MAX_EF_SEARCH 1000
|
||||
#define HNSW_DEFAULT_DIMENSIONS -1
|
||||
#define HNSW_MIN_DIMENSIONS 1
|
||||
#define HNSW_MAX_DIMENSIONS HNSW_MAX_DIM
|
||||
|
||||
/* Tuple types */
|
||||
#define HNSW_ELEMENT_TUPLE_TYPE 1
|
||||
@@ -62,7 +59,7 @@
|
||||
|
||||
#define HNSW_MAX_SIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - sizeof(ItemIdData))
|
||||
|
||||
#define HNSW_ELEMENT_TUPLE_SIZE(_datum) MAXALIGN(offsetof(HnswElementTupleData, value) + VARSIZE_ANY(_datum))
|
||||
#define HNSW_ELEMENT_TUPLE_SIZE(_dim) MAXALIGN(offsetof(HnswElementTupleData, vec) + VECTOR_SIZE(_dim))
|
||||
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
|
||||
|
||||
#define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page))
|
||||
@@ -106,7 +103,7 @@ typedef struct HnswElementData
|
||||
OffsetNumber offno;
|
||||
OffsetNumber neighborOffno;
|
||||
BlockNumber neighborPage;
|
||||
Datum value;
|
||||
Vector *vec;
|
||||
} HnswElementData;
|
||||
|
||||
typedef HnswElementData * HnswElement;
|
||||
@@ -137,7 +134,6 @@ typedef struct HnswOptions
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
int m; /* number of connections */
|
||||
int efConstruction; /* size of dynamic candidate list */
|
||||
int dimensions;
|
||||
} HnswOptions;
|
||||
|
||||
typedef struct HnswBuildState
|
||||
@@ -208,7 +204,7 @@ typedef struct HnswElementTupleData
|
||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
||||
ItemPointerData neighbortid;
|
||||
uint16 unused2;
|
||||
char value[FLEXIBLE_ARRAY_MEMBER];
|
||||
Vector vec;
|
||||
} HnswElementTupleData;
|
||||
|
||||
typedef HnswElementTupleData * HnswElementTuple;
|
||||
@@ -266,7 +262,6 @@ typedef struct HnswVacuumState
|
||||
/* Methods */
|
||||
int HnswGetM(Relation index);
|
||||
int HnswGetEfConstruction(Relation index);
|
||||
int HnswGetDimensions(Relation index);
|
||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
void HnswCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "lib/pairingheap.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
@@ -106,6 +105,8 @@ CreateElementPages(HnswBuildState * buildstate)
|
||||
{
|
||||
Relation index = buildstate->index;
|
||||
ForkNumber forkNum = buildstate->forkNum;
|
||||
int dimensions = buildstate->dimensions;
|
||||
Size etupSize;
|
||||
Size maxSize;
|
||||
HnswElementTuple etup;
|
||||
HnswNeighborTuple ntup;
|
||||
@@ -117,9 +118,10 @@ CreateElementPages(HnswBuildState * buildstate)
|
||||
|
||||
/* Calculate sizes */
|
||||
maxSize = HNSW_MAX_SIZE;
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
||||
|
||||
/* Allocate once */
|
||||
etup = palloc0(BLCKSZ);
|
||||
etup = palloc0(etupSize);
|
||||
ntup = palloc0(BLCKSZ);
|
||||
|
||||
/* Prepare first page */
|
||||
@@ -131,14 +133,12 @@ CreateElementPages(HnswBuildState * buildstate)
|
||||
foreach(lc, buildstate->elements)
|
||||
{
|
||||
HnswElement element = lfirst(lc);
|
||||
Size etupSize;
|
||||
Size ntupSize;
|
||||
Size combinedSize;
|
||||
|
||||
HnswSetElementTuple(etup, element);
|
||||
|
||||
/* Calculate sizes */
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(element->value);
|
||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||
|
||||
@@ -273,15 +273,18 @@ InsertTuple(Relation index, Datum *values, HnswElement element, HnswBuildState *
|
||||
int m = buildstate->m;
|
||||
|
||||
/* Detoast once for all calls */
|
||||
element->value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
|
||||
/* Normalize if needed */
|
||||
if (buildstate->normprocinfo != NULL)
|
||||
{
|
||||
if (!HnswNormValue(buildstate->normprocinfo, collation, &element->value, buildstate->normvec))
|
||||
if (!HnswNormValue(buildstate->normprocinfo, collation, &value, buildstate->normvec))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy value to element so accessible outside of memory context */
|
||||
memcpy(element->vec, DatumGetVector(value), VECTOR_SIZE(buildstate->dimensions));
|
||||
|
||||
/* Insert element in graph */
|
||||
HnswInsertElement(element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||
|
||||
@@ -357,6 +360,7 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
|
||||
/* Allocate necessary memory outside of memory context */
|
||||
element = HnswInitElement(tid, buildstate->m, buildstate->ml, buildstate->maxLevel);
|
||||
element->vec = palloc(VECTOR_SIZE(buildstate->dimensions));
|
||||
|
||||
/* Use memory context since detoast can allocate */
|
||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||
@@ -364,8 +368,9 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
/* Insert tuple */
|
||||
inserted = InsertTuple(index, values, element, buildstate, &dup);
|
||||
|
||||
/* Switch memory context */
|
||||
/* Reset memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextReset(buildstate->tmpCtx);
|
||||
|
||||
/* Add outside memory context */
|
||||
if (dup != NULL)
|
||||
@@ -373,18 +378,9 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
|
||||
/* Add to buildstate or free */
|
||||
if (inserted)
|
||||
{
|
||||
element->value = datumCopy(element->value, false, -1);
|
||||
buildstate->elements = lappend(buildstate->elements, element);
|
||||
}
|
||||
else
|
||||
{
|
||||
element->value = PointerGetDatum(NULL);
|
||||
HnswFreeElement(element);
|
||||
}
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextReset(buildstate->tmpCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -399,7 +395,6 @@ HnswGetMaxInMemoryElements(int m, double ml, int dimensions)
|
||||
elementSize += sizeof(HnswNeighborArray) * (avgLevel + 1);
|
||||
elementSize += sizeof(HnswCandidate) * (m * (avgLevel + 2));
|
||||
elementSize += sizeof(ItemPointerData);
|
||||
/* TODO Handle non-vector types */
|
||||
elementSize += VECTOR_SIZE(dimensions);
|
||||
return (maintenance_work_mem * 1024L) / elementSize;
|
||||
}
|
||||
@@ -417,10 +412,7 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
||||
|
||||
buildstate->m = HnswGetM(index);
|
||||
buildstate->efConstruction = HnswGetEfConstruction(index);
|
||||
buildstate->dimensions = HnswGetDimensions(index);
|
||||
|
||||
if (buildstate->dimensions < 0)
|
||||
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
||||
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
||||
|
||||
/* Require column to have dimensions to be indexed */
|
||||
if (buildstate->dimensions < 0)
|
||||
|
||||
@@ -123,6 +123,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
||||
Size minCombinedSize;
|
||||
HnswElementTuple etup;
|
||||
BlockNumber currentPage = insertPage;
|
||||
int dimensions = e->vec->dim;
|
||||
HnswNeighborTuple ntup;
|
||||
Buffer nbuf;
|
||||
Page npage;
|
||||
@@ -131,7 +132,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||
|
||||
/* Calculate sizes */
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(e->value);
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||
maxSize = HNSW_MAX_SIZE;
|
||||
@@ -404,7 +405,7 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(dup->value);
|
||||
Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(dup->vec->dim);
|
||||
HnswElementTuple etup;
|
||||
int i;
|
||||
|
||||
@@ -514,7 +515,7 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
||||
|
||||
/* Create an element */
|
||||
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
||||
element->value = value;
|
||||
element->vec = DatumGetVector(value);
|
||||
|
||||
/* Prevent concurrent inserts when likely updating entry point */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "hnsw.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/datum.h"
|
||||
#include "vector.h"
|
||||
|
||||
/*
|
||||
@@ -35,20 +34,6 @@ HnswGetEfConstruction(Relation index)
|
||||
return HNSW_DEFAULT_EF_CONSTRUCTION;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the number of dimensions in the index
|
||||
*/
|
||||
int
|
||||
HnswGetDimensions(Relation index)
|
||||
{
|
||||
HnswOptions *opts = (HnswOptions *) index->rd_options;
|
||||
|
||||
if (opts)
|
||||
return opts->dimensions;
|
||||
|
||||
return HNSW_DEFAULT_DIMENSIONS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get proc
|
||||
*/
|
||||
@@ -202,8 +187,7 @@ HnswFreeElement(HnswElement element)
|
||||
{
|
||||
HnswFreeNeighbors(element);
|
||||
list_free_deep(element->heaptids);
|
||||
if (DatumGetPointer(element->value))
|
||||
pfree(DatumGetPointer(element->value));
|
||||
pfree(element->vec);
|
||||
pfree(element);
|
||||
}
|
||||
|
||||
@@ -230,7 +214,7 @@ HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
||||
element->blkno = blkno;
|
||||
element->offno = offno;
|
||||
element->neighbors = NULL;
|
||||
element->value = PointerGetDatum(NULL);
|
||||
element->vec = NULL;
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -340,7 +324,7 @@ HnswSetElementTuple(HnswElementTuple etup, HnswElement element)
|
||||
else
|
||||
ItemPointerSetInvalid(&etup->heaptids[i]);
|
||||
}
|
||||
memcpy(&etup->value, DatumGetPointer(element->value), VARSIZE_ANY(element->value));
|
||||
memcpy(&etup->vec, element->vec, VECTOR_SIZE(element->vec->dim));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -463,9 +447,8 @@ HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHe
|
||||
|
||||
if (loadVec)
|
||||
{
|
||||
Datum value = PointerGetDatum(&etup->value);
|
||||
|
||||
element->value = datumCopy(value, false, -1);
|
||||
element->vec = palloc(VECTOR_SIZE(etup->vec.dim));
|
||||
memcpy(element->vec, &etup->vec, VECTOR_SIZE(etup->vec.dim));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,7 +476,7 @@ HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index,
|
||||
|
||||
/* Calculate distance */
|
||||
if (distance != NULL)
|
||||
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->value)));
|
||||
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->vec)));
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
@@ -504,7 +487,7 @@ HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index,
|
||||
static float
|
||||
GetCandidateDistance(HnswCandidate * hc, Datum q, FmgrInfo *procinfo, Oid collation)
|
||||
{
|
||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, hc->element->value));
|
||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, PointerGetDatum(hc->element->vec)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -767,7 +750,7 @@ HnswGetDistance(HnswElement a, HnswElement b, int lc, FmgrInfo *procinfo, Oid co
|
||||
}
|
||||
}
|
||||
|
||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, a->value, b->value));
|
||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(a->vec), PointerGetDatum(b->vec)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -894,7 +877,7 @@ HnswFindDuplicate(HnswElement e)
|
||||
HnswCandidate *neighbor = &neighbors->items[i];
|
||||
|
||||
/* Exit early since ordered by distance */
|
||||
if (!datumIsEqual(e->value, neighbor->element->value, false, -1))
|
||||
if (vector_cmp_internal(e->vec, neighbor->element->vec) != 0)
|
||||
break;
|
||||
|
||||
/* Check for space */
|
||||
@@ -947,13 +930,13 @@ HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int
|
||||
/* Load elements on insert */
|
||||
if (index != NULL)
|
||||
{
|
||||
Datum q = hc->element->value;
|
||||
Datum q = PointerGetDatum(hc->element->vec);
|
||||
|
||||
for (int i = 0; i < currentNeighbors->length; i++)
|
||||
{
|
||||
HnswCandidate *hc3 = ¤tNeighbors->items[i];
|
||||
|
||||
if (!DatumGetPointer(hc3->element->value))
|
||||
if (hc3->element->vec == NULL)
|
||||
HnswLoadElement(hc3->element, &hc3->distance, &q, index, procinfo, collation, true);
|
||||
else
|
||||
hc3->distance = GetCandidateDistance(hc3, q, procinfo, collation);
|
||||
@@ -1034,7 +1017,7 @@ HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, F
|
||||
List *w;
|
||||
int level = element->level;
|
||||
int entryLevel;
|
||||
Datum q = element->value;
|
||||
Datum q = PointerGetDatum(element->vec);
|
||||
HnswElement skipElement = existing ? element : NULL;
|
||||
|
||||
/* No neighbors if no entry point */
|
||||
|
||||
@@ -93,7 +93,7 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
||||
|
||||
if (itemUpdated)
|
||||
{
|
||||
Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(PointerGetDatum(&etup->value));
|
||||
Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim);
|
||||
|
||||
/* Mark rest as invalid */
|
||||
for (int i = idx; i < HNSW_HEAPTIDS; i++)
|
||||
@@ -481,7 +481,6 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
||||
HnswNeighborTuple ntup;
|
||||
Size etupSize;
|
||||
Size ntupSize;
|
||||
Datum value;
|
||||
Buffer nbuf;
|
||||
Page npage;
|
||||
BlockNumber neighborPage;
|
||||
@@ -505,11 +504,8 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
||||
if (ItemPointerIsValid(&etup->heaptids[0]))
|
||||
continue;
|
||||
|
||||
/* Get datum */
|
||||
value = PointerGetDatum(&etup->value);
|
||||
|
||||
/* Calculate sizes */
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(value);
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim);
|
||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(etup->level, vacuumstate->m);
|
||||
|
||||
/* Get neighbor page */
|
||||
@@ -532,7 +528,7 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
||||
|
||||
/* Overwrite element */
|
||||
etup->deleted = 1;
|
||||
MemSet(&etup->value, 0, VARSIZE_ANY(value));
|
||||
MemSet(&etup->vec.x, 0, etup->vec.dim * sizeof(float));
|
||||
|
||||
/* Overwrite neighbors */
|
||||
for (int i = 0; i < ntup->count; i++)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "miscadmin.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
@@ -65,11 +66,18 @@
|
||||
static void
|
||||
AddSample(Datum *values, IvfflatBuildState * buildstate)
|
||||
{
|
||||
VectorArray samples = buildstate->samples;
|
||||
int targsamples = samples->maxlen;
|
||||
MemoryContext oldCtx;
|
||||
Datum value;
|
||||
int targsamples = buildstate->targsamples;
|
||||
|
||||
/* Use memory context since detoast can allocate */
|
||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||
|
||||
/* Detoast once for all calls */
|
||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
|
||||
/* Restore memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
|
||||
/*
|
||||
* Normalize with KMEANS_NORM_PROC since spherical distance function
|
||||
@@ -81,18 +89,23 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
||||
return;
|
||||
}
|
||||
|
||||
if (samples->length < targsamples)
|
||||
{
|
||||
VectorArraySet(samples, samples->length, DatumGetVector(value));
|
||||
samples->length++;
|
||||
}
|
||||
/* Copy datum */
|
||||
value = datumCopy(value, false, -1);
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextReset(buildstate->tmpCtx);
|
||||
|
||||
if (list_length(buildstate->samples) < targsamples)
|
||||
buildstate->samples = lappend(buildstate->samples, DatumGetVector(value));
|
||||
else
|
||||
{
|
||||
if (buildstate->rowstoskip < 0)
|
||||
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples);
|
||||
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, list_length(buildstate->samples), targsamples);
|
||||
|
||||
if (buildstate->rowstoskip <= 0)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate));
|
||||
#else
|
||||
@@ -100,7 +113,8 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
||||
#endif
|
||||
|
||||
Assert(k >= 0 && k < targsamples);
|
||||
VectorArraySet(samples, k, DatumGetVector(value));
|
||||
lc = list_nth_cell(buildstate->samples, k);
|
||||
lfirst(lc) = DatumGetVector(value);
|
||||
}
|
||||
|
||||
buildstate->rowstoskip -= 1;
|
||||
@@ -115,21 +129,13 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
bool *isnull, bool tupleIsAlive, void *state)
|
||||
{
|
||||
IvfflatBuildState *buildstate = (IvfflatBuildState *) state;
|
||||
MemoryContext oldCtx;
|
||||
|
||||
/* Skip nulls */
|
||||
if (isnull[0])
|
||||
return;
|
||||
|
||||
/* Use memory context since detoast can allocate */
|
||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||
|
||||
/* Add sample */
|
||||
AddSample(values, state);
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextReset(buildstate->tmpCtx);
|
||||
AddSample(values, buildstate);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -138,7 +144,7 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
static void
|
||||
SampleRows(IvfflatBuildState * buildstate)
|
||||
{
|
||||
int targsamples = buildstate->samples->maxlen;
|
||||
int targsamples = buildstate->targsamples;
|
||||
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
||||
|
||||
buildstate->rowstoskip = -1;
|
||||
@@ -449,12 +455,13 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
||||
|
||||
/* Sample rows */
|
||||
/* TODO Ensure within maintenance_work_mem */
|
||||
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||
buildstate->samples = NIL;
|
||||
buildstate->targsamples = numSamples;
|
||||
if (buildstate->heap != NULL)
|
||||
{
|
||||
SampleRows(buildstate);
|
||||
|
||||
if (buildstate->samples->length < buildstate->lists)
|
||||
if (list_length(buildstate->samples) < buildstate->lists)
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("ivfflat index created with little data"),
|
||||
@@ -467,7 +474,7 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
||||
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
|
||||
|
||||
/* Free samples before we allocate more memory */
|
||||
VectorArrayFree(buildstate->samples);
|
||||
list_free_deep(buildstate->samples);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -80,6 +80,10 @@
|
||||
#define RandomInt() random()
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 130000
|
||||
#define list_sort(list, cmp) list_qsort(list, cmp)
|
||||
#endif
|
||||
|
||||
/* Variables */
|
||||
extern int ivfflat_probes;
|
||||
|
||||
@@ -178,7 +182,8 @@ typedef struct IvfflatBuildState
|
||||
Oid collation;
|
||||
|
||||
/* Variables */
|
||||
VectorArray samples;
|
||||
List *samples;
|
||||
int targsamples;
|
||||
VectorArray centers;
|
||||
ListInfo *listInfo;
|
||||
Vector *normvec;
|
||||
@@ -274,7 +279,7 @@ typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
||||
VectorArray VectorArrayInit(int maxlen, int dimensions);
|
||||
void VectorArrayFree(VectorArray arr);
|
||||
void PrintVectorArray(char *msg, VectorArray arr);
|
||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||
void IvfflatKmeans(Relation index, List *samples, VectorArray centers);
|
||||
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
int IvfflatGetLists(Relation index);
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
||||
*/
|
||||
static void
|
||||
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
|
||||
InitCenters(Relation index, List *samples, VectorArray centers, float *lowerBound)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
Oid collation;
|
||||
int64 j;
|
||||
float *weight = palloc(samples->length * sizeof(float));
|
||||
float *weight = palloc(list_length(samples) * sizeof(float));
|
||||
int numCenters = centers->maxlen;
|
||||
int numSamples = samples->length;
|
||||
int numSamples = list_length(samples);
|
||||
|
||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||
collation = index->rd_indcollation[0];
|
||||
|
||||
/* Choose an initial center uniformly at random */
|
||||
VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length));
|
||||
VectorArraySet(centers, 0, list_nth(samples, RandomInt() % list_length(samples)));
|
||||
centers->length++;
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
@@ -42,7 +42,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
Vector *vec = VectorArrayGet(samples, j);
|
||||
Vector *vec = list_nth(samples, j);
|
||||
double distance;
|
||||
|
||||
/* Only need to compute distance for new center */
|
||||
@@ -74,7 +74,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
break;
|
||||
}
|
||||
|
||||
VectorArraySet(centers, i + 1, VectorArrayGet(samples, j));
|
||||
VectorArraySet(centers, i + 1, list_nth(samples, j));
|
||||
centers->length++;
|
||||
}
|
||||
|
||||
@@ -106,25 +106,41 @@ CompareVectors(const void *a, const void *b)
|
||||
return vector_cmp_internal((Vector *) a, (Vector *) b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare list vectors
|
||||
*/
|
||||
static int
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
CompareListVectors(const ListCell *a, const ListCell *b)
|
||||
#else
|
||||
CompareListVectors(const void *a, const void *b)
|
||||
#endif
|
||||
{
|
||||
Vector *va = lfirst((ListCell *) a);
|
||||
Vector *vb = lfirst((ListCell *) b);
|
||||
|
||||
return CompareVectors(va, vb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Quick approach if we have little data
|
||||
*/
|
||||
static void
|
||||
QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
QuickCenters(Relation index, List *samples, VectorArray centers)
|
||||
{
|
||||
int dimensions = centers->dim;
|
||||
Oid collation = index->rd_indcollation[0];
|
||||
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||
|
||||
/* Copy existing vectors while avoiding duplicates */
|
||||
if (samples->length > 0)
|
||||
if (list_length(samples) > 0)
|
||||
{
|
||||
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
||||
for (int i = 0; i < samples->length; i++)
|
||||
list_sort(samples, CompareListVectors);
|
||||
for (int i = 0; i < list_length(samples); i++)
|
||||
{
|
||||
Vector *vec = VectorArrayGet(samples, i);
|
||||
Vector *vec = list_nth(samples, i);
|
||||
|
||||
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
||||
if (i == 0 || CompareVectors(vec, list_nth(samples, i - 1)) != 0)
|
||||
{
|
||||
VectorArraySet(centers, centers->length, vec);
|
||||
centers->length++;
|
||||
@@ -160,7 +176,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
* https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf
|
||||
*/
|
||||
static void
|
||||
ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
ElkanKmeans(Relation index, List *samples, VectorArray centers)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
FmgrInfo *normprocinfo;
|
||||
@@ -171,7 +187,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
int64 k;
|
||||
int dimensions = centers->dim;
|
||||
int numCenters = centers->maxlen;
|
||||
int numSamples = samples->length;
|
||||
int numSamples = list_length(samples);
|
||||
VectorArray newCenters;
|
||||
int *centerCounts;
|
||||
int *closestCenters;
|
||||
@@ -182,7 +198,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
float *newcdist;
|
||||
|
||||
/* Calculate allocation sizes */
|
||||
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
||||
Size samplesSize = 0;
|
||||
Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim);
|
||||
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions);
|
||||
Size centerCountsSize = sizeof(int) * numCenters;
|
||||
@@ -326,7 +342,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
if (upperBound[j] <= halfcdist[closestCenters[j] * numCenters + k])
|
||||
continue;
|
||||
|
||||
vec = VectorArrayGet(samples, j);
|
||||
vec = list_nth(samples, j);
|
||||
|
||||
/* Step 3a */
|
||||
if (rj)
|
||||
@@ -377,7 +393,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
{
|
||||
int closestCenter;
|
||||
|
||||
vec = VectorArrayGet(samples, j);
|
||||
vec = list_nth(samples, j);
|
||||
closestCenter = closestCenters[j];
|
||||
|
||||
/* Increment sum and count of closest center */
|
||||
@@ -514,9 +530,9 @@ CheckCenters(Relation index, VectorArray centers)
|
||||
* We use spherical k-means for inner product and cosine
|
||||
*/
|
||||
void
|
||||
IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
IvfflatKmeans(Relation index, List *samples, VectorArray centers)
|
||||
{
|
||||
if (samples->length <= centers->maxlen)
|
||||
if (list_length(samples) <= centers->maxlen)
|
||||
QuickCenters(index, samples, centers);
|
||||
else
|
||||
ElkanKmeans(index, samples, centers);
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More;
|
||||
|
||||
my $node;
|
||||
my @queries = ();
|
||||
my @expected;
|
||||
my $limit = 20;
|
||||
|
||||
sub test_recall
|
||||
{
|
||||
my ($min, $operator) = @_;
|
||||
my $correct = 0;
|
||||
my $total = 0;
|
||||
|
||||
my $explain = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
||||
));
|
||||
like($explain, qr/Index Scan/);
|
||||
|
||||
for my $i (0 .. $#queries)
|
||||
{
|
||||
my $actual = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||
));
|
||||
my @actual_ids = split("\n", $actual);
|
||||
my %actual_set = map { $_ => 1 } @actual_ids;
|
||||
|
||||
my @expected_ids = split("\n", $expected[$i]);
|
||||
|
||||
foreach (@expected_ids)
|
||||
{
|
||||
if (exists($actual_set{$_}))
|
||||
{
|
||||
$correct++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
|
||||
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||
}
|
||||
|
||||
# Initialize node
|
||||
$node = get_new_node('node');
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Create table
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v float4[3]);");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 10000) i;"
|
||||
);
|
||||
|
||||
$node->safe_psql("postgres", qq(
|
||||
CREATE FUNCTION float4_l2_distance(float4[], float4[]) RETURNS float8
|
||||
AS 'BEGIN RETURN l2_distance(\$1::vector, \$2::vector); END;'
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION float4_l2_squared_distance(float4[], float4[]) RETURNS float8
|
||||
AS 'BEGIN RETURN vector_l2_squared_distance(\$1::vector, \$2::vector); END;'
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = float4[], RIGHTARG = float4[], PROCEDURE = float4_l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS float4_l2_ops
|
||||
FOR TYPE float4[] USING hnsw AS
|
||||
OPERATOR 1 <-> (float4[], float4[]) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 float4_l2_squared_distance(float4[], float4[]);
|
||||
));
|
||||
|
||||
# Generate queries
|
||||
for (1 .. 20)
|
||||
{
|
||||
my $r1 = rand();
|
||||
my $r2 = rand();
|
||||
my $r3 = rand();
|
||||
push(@queries, "{$r1,$r2,$r3}");
|
||||
}
|
||||
|
||||
# Check each index type
|
||||
my @operators = ("<->");
|
||||
my @opclasses = ("float4_l2_ops");
|
||||
|
||||
for my $i (0 .. $#operators)
|
||||
{
|
||||
my $operator = $operators[$i];
|
||||
my $opclass = $opclasses[$i];
|
||||
|
||||
# Get exact results
|
||||
@expected = ();
|
||||
foreach (@queries)
|
||||
{
|
||||
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
|
||||
push(@expected, $res);
|
||||
}
|
||||
|
||||
# Add index
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING hnsw (v $opclass) WITH (dimensions = 3);");
|
||||
|
||||
my $min = $operator eq "<#>" ? 0.80 : 0.99;
|
||||
test_recall($min, $operator);
|
||||
}
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user