Compare commits

..

14 Commits

Author SHA1 Message Date
Andrew Kane
191bef7e35 Started ARM support [skip ci] 2024-03-30 09:43:18 -07:00
Andrew Kane
582e6ad821 Keep -march=native for Mac x86-64 [skip ci] 2024-03-28 01:30:56 -07:00
Andrew Kane
d0028ae769 Restored comment [skip ci] 2024-03-27 20:40:39 -07:00
Andrew Kane
81dc35b62b Check x86-64 [skip ci] 2024-03-27 19:16:49 -07:00
Andrew Kane
c82d15acad Test runtime dispatching [skip ci] 2024-03-27 18:30:39 -07:00
Andrew Kane
ba18942fcf Removed normvec from IVFFlat for simplicity (no difference in performance) 2024-03-27 16:41:17 -07:00
Andrew Kane
8e59455c3c Removed normvec for simplicity (no difference in performance) 2024-03-27 16:33:11 -07:00
Andrew Kane
bd50e3067d Updated readme [skip ci] 2024-03-27 14:14:49 -07:00
Andrew Kane
af9d4ad659 Updated readme [skip ci] 2024-03-27 14:12:08 -07:00
Andrew Kane
08abb63cbe Added notes about NULL vectors [skip ci] 2024-03-27 11:50:37 -07:00
Andrew Kane
06b8556a49 Revert "Updated readme [skip ci]"
This reverts commit 3f674c9994.
2024-03-25 23:33:46 -07:00
Andrew Kane
3f674c9994 Updated readme [skip ci] 2024-03-25 23:33:17 -07:00
Andrew Kane
31e41b3ba9 Added FAQ about binary vectors [skip ci] 2024-03-24 11:07:34 -07:00
Andrew Kane
903a925662 Improved type modifier tests 2024-03-21 17:31:08 -07:00
15 changed files with 93 additions and 59 deletions

View File

@@ -10,21 +10,15 @@ TESTS = $(wildcard test/sql/*.sql)
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION) REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
OPTFLAGS = -march=native OPTFLAGS =
# Mac ARM doesn't support -march=native # Since runtime dispatch not supported
ifeq ($(shell uname -s), Darwin) ifeq ($(shell uname -s), Darwin)
ifeq ($(shell uname -p), arm) ifeq ($(shell uname -m), x86_64)
# no difference with -march=armv8.5-a OPTFLAGS = -march=native
OPTFLAGS =
endif endif
endif endif
# PowerPC doesn't support -march=native
ifneq ($(filter ppc64%, $(shell uname -m)), )
OPTFLAGS =
endif
# For auto-vectorization: # For auto-vectorization:
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html # - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html # - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html

View File

@@ -604,6 +604,18 @@ and query with:
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5; SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
``` ```
#### Are binary vectors supported?
You can store binary vectors and perform exact nearest neighbor search by Hamming distance in Postgres without an extension ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py)).
```tsql
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
INSERT INTO items (embedding) VALUES (B'000'), (B'111');
SELECT * FROM items ORDER BY bit_count(embedding # B'101') LIMIT 5;
```
Indexing is not currently supported.
#### Do indexes need to fit into memory? #### Do indexes need to fit into memory?
No, but like other index types, youll likely see better performance if they do. You can get the size of an index with: No, but like other index types, youll likely see better performance if they do. You can get the size of an index with:
@@ -659,6 +671,8 @@ ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`). There may be even less results due to dead tuples or filtering conditions in the query. We recommend setting `hnsw.ef_search` to at least twice the `LIMIT` of the query. If you need more than 500 results, use an IVFFlat index instead. Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`). There may be even less results due to dead tuples or filtering conditions in the query. We recommend setting `hnsw.ef_search` to at least twice the `LIMIT` of the query. If you need more than 500 results, use an IVFFlat index instead.
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
#### Why are there less results for a query after adding an IVFFlat index? #### Why are there less results for a query after adding an IVFFlat index?
The index was likely created with too little data for the number of lists. Drop the index until the table has more data. The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
@@ -669,11 +683,13 @@ DROP INDEX index_name;
Results can also be limited by the number of probes (`ivfflat.probes`). Results can also be limited by the number of probes (`ivfflat.probes`).
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
## Reference ## Reference
### Vector Type ### Vector Type
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions. Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single-precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
### Vector Operators ### Vector Operators
@@ -697,7 +713,7 @@ l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
vector_dims(vector) → integer | number of dimensions | vector_dims(vector) → integer | number of dimensions |
vector_norm(vector) → double precision | Euclidean norm | vector_norm(vector) → double precision | Euclidean norm |
### Aggregate Functions ### Vector Aggregate Functions
Function | Description | Added Function | Description | Added
--- | --- | --- --- | --- | ---

View File

@@ -262,7 +262,6 @@ typedef struct HnswBuildState
HnswGraph *graph; HnswGraph *graph;
double ml; double ml;
int maxLevel; int maxLevel;
Vector *normvec;
/* Memory */ /* Memory */
MemoryContext graphCtx; MemoryContext graphCtx;
@@ -367,7 +366,7 @@ typedef struct HnswVacuumState
int HnswGetM(Relation index); int HnswGetM(Relation index);
int HnswGetEfConstruction(Relation index); int HnswGetEfConstruction(Relation index);
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum); FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum); Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
void HnswInitPage(Buffer buf, Page page); void HnswInitPage(Buffer buf, Page page);
void HnswInit(void); void HnswInit(void);

View File

@@ -489,7 +489,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
/* Normalize if needed */ /* Normalize if needed */
if (buildstate->normprocinfo != NULL) if (buildstate->normprocinfo != NULL)
{ {
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec)) if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value))
return false; return false;
} }
@@ -703,9 +703,6 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
buildstate->ml = HnswGetMl(buildstate->m); buildstate->ml = HnswGetMl(buildstate->m);
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m); buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext, buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext,
"Hnsw build graph context", "Hnsw build graph context",
#if PG_VERSION_NUM >= 150000 #if PG_VERSION_NUM >= 150000
@@ -729,7 +726,6 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
static void static void
FreeBuildState(HnswBuildState * buildstate) FreeBuildState(HnswBuildState * buildstate)
{ {
pfree(buildstate->normvec);
MemoryContextDelete(buildstate->graphCtx); MemoryContextDelete(buildstate->graphCtx);
MemoryContextDelete(buildstate->tmpCtx); MemoryContextDelete(buildstate->tmpCtx);
} }

View File

@@ -622,7 +622,7 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
if (normprocinfo != NULL) if (normprocinfo != NULL)
{ {
if (!HnswNormValue(normprocinfo, collation, &value, NULL)) if (!HnswNormValue(normprocinfo, collation, &value))
return; return;
} }

View File

@@ -84,7 +84,7 @@ GetScanValue(IndexScanDesc scan)
/* Fine if normalization fails */ /* Fine if normalization fails */
if (so->normprocinfo != NULL) if (so->normprocinfo != NULL)
HnswNormValue(so->normprocinfo, so->collation, &value, NULL); HnswNormValue(so->normprocinfo, so->collation, &value);
} }
return value; return value;

View File

@@ -158,16 +158,14 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
* if it's different than the original value * if it's different than the original value
*/ */
bool bool
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
{ {
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0) if (norm > 0)
{ {
Vector *v = DatumGetVector(*value); Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
if (result == NULL)
result = InitVector(v->dim);
for (int i = 0; i < v->dim; i++) for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm; result->x[i] = v->x[i] / norm;

View File

@@ -57,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
*/ */
if (buildstate->kmeansnormprocinfo != NULL) if (buildstate->kmeansnormprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec)) if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value))
return; return;
} }
@@ -153,7 +153,7 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
/* Normalize if needed */ /* Normalize if needed */
if (buildstate->normprocinfo != NULL) if (buildstate->normprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec)) if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value))
return; return;
} }
@@ -356,9 +356,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions); buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists); buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
"Ivfflat build temporary context", "Ivfflat build temporary context",
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
@@ -380,7 +377,6 @@ FreeBuildState(IvfflatBuildState * buildstate)
{ {
VectorArrayFree(buildstate->centers); VectorArrayFree(buildstate->centers);
pfree(buildstate->listInfo); pfree(buildstate->listInfo);
pfree(buildstate->normvec);
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
pfree(buildstate->listSums); pfree(buildstate->listSums);

View File

@@ -172,7 +172,6 @@ typedef struct IvfflatBuildState
VectorArray samples; VectorArray samples;
VectorArray centers; VectorArray centers;
ListInfo *listInfo; ListInfo *listInfo;
Vector *normvec;
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
double inertia; double inertia;
@@ -267,7 +266,7 @@ void VectorArrayFree(VectorArray arr);
void PrintVectorArray(char *msg, VectorArray arr); void PrintVectorArray(char *msg, VectorArray arr);
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum); FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
int IvfflatGetLists(Relation index); int IvfflatGetLists(Relation index);
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions); void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum); void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);

View File

@@ -85,7 +85,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, R
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
if (normprocinfo != NULL) if (normprocinfo != NULL)
{ {
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL)) if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value))
return; return;
} }

View File

@@ -293,7 +293,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
/* Fine if normalization fails */ /* Fine if normalization fails */
if (so->normprocinfo != NULL) if (so->normprocinfo != NULL)
IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL); IvfflatNormValue(so->normprocinfo, so->collation, &value);
} }
IvfflatBench("GetScanLists", GetScanLists(scan, value)); IvfflatBench("GetScanLists", GetScanLists(scan, value));

View File

@@ -75,16 +75,14 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
* if it's different than the original value * if it's different than the original value
*/ */
bool bool
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
{ {
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0) if (norm > 0)
{ {
Vector *v = DatumGetVector(*value); Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
if (result == NULL)
result = InitVector(v->dim);
for (int i = 0; i < v->dim; i++) for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm; result->x[i] = v->x[i] / norm;

View File

@@ -29,6 +29,15 @@
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1) #define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1)) #define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
#if defined(__x86_64__) && defined(__gnu_linux__) && defined(__has_attribute) && __has_attribute(target_clones)
#define RUNTIME_DISPATCH __attribute__((target_clones("default", "avx", "fma", "avx512f")))
#elif defined(__aarch64__) && defined(__gnu_linux__) && defined(__has_attribute) && __has_attribute(target_clones)
/* TODO Fix error: target does not support function version dispatcher */
#define RUNTIME_DISPATCH __attribute__((target_clones("default", "arch=armv8.5-a")))
#else
#define RUNTIME_DISPATCH
#endif
PG_MODULE_MAGIC; PG_MODULE_MAGIC;
/* /*
@@ -532,6 +541,23 @@ vector_to_float4(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
RUNTIME_DISPATCH
static float
l2_squared_distance_impl(int16 dim, float *ax, float *bx)
{
float distance = 0.0;
/* Auto-vectorized */
for (int16 i = 0; i < dim; i++)
{
float diff = ax[i] - bx[i];
distance += diff * diff;
}
return distance;
}
/* /*
* Get the L2 distance between vectors * Get the L2 distance between vectors
*/ */
@@ -541,19 +567,11 @@ l2_distance(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x; float distance;
float *bx = b->x;
float distance = 0.0;
float diff;
CheckDims(a, b); CheckDims(a, b);
/* Auto-vectorized */ distance = l2_squared_distance_impl(a->dim, a->x, b->x);
for (int i = 0; i < a->dim; i++)
{
diff = ax[i] - bx[i];
distance += diff * diff;
}
PG_RETURN_FLOAT8(sqrt((double) distance)); PG_RETURN_FLOAT8(sqrt((double) distance));
} }
@@ -568,19 +586,11 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x; float distance;
float *bx = b->x;
float distance = 0.0;
float diff;
CheckDims(a, b); CheckDims(a, b);
/* Auto-vectorized */ distance = l2_squared_distance_impl(a->dim, a->x, b->x);
for (int i = 0; i < a->dim; i++)
{
diff = ax[i] - bx[i];
distance += diff * diff;
}
PG_RETURN_FLOAT8((double) distance); PG_RETURN_FLOAT8((double) distance);
} }

View File

@@ -116,8 +116,30 @@ SELECT '[1, ,3]'::vector;
ERROR: invalid input syntax for type vector: "[1, ,3]" ERROR: invalid input syntax for type vector: "[1, ,3]"
LINE 1: SELECT '[1, ,3]'::vector; LINE 1: SELECT '[1, ,3]'::vector;
^ ^
SELECT '[1,2,3]'::vector(3);
vector
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::vector(2); SELECT '[1,2,3]'::vector(2);
ERROR: expected 2 dimensions, not 3 ERROR: expected 2 dimensions, not 3
SELECT '[1,2,3]'::vector(3, 2);
ERROR: invalid type modifier
LINE 1: SELECT '[1,2,3]'::vector(3, 2);
^
SELECT '[1,2,3]'::vector('a');
ERROR: invalid input syntax for type integer: "a"
LINE 1: SELECT '[1,2,3]'::vector('a');
^
SELECT '[1,2,3]'::vector(0);
ERROR: dimensions for type vector must be at least 1
LINE 1: SELECT '[1,2,3]'::vector(0);
^
SELECT '[1,2,3]'::vector(16001);
ERROR: dimensions for type vector cannot exceed 16000
LINE 1: SELECT '[1,2,3]'::vector(16001);
^
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]); SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
unnest unnest
--------- ---------

View File

@@ -22,7 +22,13 @@ SELECT '[1,]'::vector;
SELECT '[1a]'::vector; SELECT '[1a]'::vector;
SELECT '[1,,3]'::vector; SELECT '[1,,3]'::vector;
SELECT '[1, ,3]'::vector; SELECT '[1, ,3]'::vector;
SELECT '[1,2,3]'::vector(3);
SELECT '[1,2,3]'::vector(2); SELECT '[1,2,3]'::vector(2);
SELECT '[1,2,3]'::vector(3, 2);
SELECT '[1,2,3]'::vector('a');
SELECT '[1,2,3]'::vector(0);
SELECT '[1,2,3]'::vector(16001);
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]); SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
SELECT '{"[1,2,3]"}'::vector(2)[]; SELECT '{"[1,2,3]"}'::vector(2)[];