mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
8 Commits
halfvec-ci
...
halfvec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c09df83f6 | ||
|
|
17e0058927 | ||
|
|
3fc734fb36 | ||
|
|
caca8990fc | ||
|
|
b640d84381 | ||
|
|
3efa99fe77 | ||
|
|
7087a45ea3 | ||
|
|
5a229ba984 |
54
.github/workflows/build.yml
vendored
54
.github/workflows/build.yml
vendored
@@ -8,8 +8,16 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- postgres: 17
|
||||||
|
os: ubuntu-22.04
|
||||||
- postgres: 16
|
- postgres: 16
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- postgres: 15
|
||||||
|
os: ubuntu-22.04
|
||||||
|
- postgres: 14
|
||||||
|
os: ubuntu-22.04
|
||||||
|
- postgres: 13
|
||||||
|
os: ubuntu-20.04
|
||||||
- postgres: 12
|
- postgres: 12
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
@@ -24,6 +32,37 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
export PG_CONFIG=`which pg_config`
|
export PG_CONFIG=`which pg_config`
|
||||||
sudo --preserve-env=PG_CONFIG make install
|
sudo --preserve-env=PG_CONFIG make install
|
||||||
|
- run: make installcheck
|
||||||
|
- if: ${{ failure() }}
|
||||||
|
run: cat regression.diffs
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libipc-run-perl
|
||||||
|
- run: make prove_installcheck
|
||||||
|
mac:
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ankane/setup-postgres@v1
|
||||||
|
with:
|
||||||
|
postgres-version: 14
|
||||||
|
- run: make
|
||||||
|
env:
|
||||||
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter
|
||||||
|
- run: make install
|
||||||
|
- run: make installcheck
|
||||||
|
- if: ${{ failure() }}
|
||||||
|
run: cat regression.diffs
|
||||||
|
- run: |
|
||||||
|
brew install cpanm
|
||||||
|
cpanm --notest IPC::Run
|
||||||
|
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_10.tar.gz
|
||||||
|
tar xf REL_14_10.tar.gz
|
||||||
|
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_10/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||||
|
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make
|
||||||
|
env:
|
||||||
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
@@ -56,5 +95,20 @@ jobs:
|
|||||||
git fetch origin ${{ github.ref }}
|
git fetch origin ${{ github.ref }}
|
||||||
git reset --hard FETCH_HEAD
|
git reset --hard FETCH_HEAD
|
||||||
make
|
make
|
||||||
|
make install
|
||||||
|
chown -R postgres .
|
||||||
|
sudo -u postgres make installcheck
|
||||||
|
sudo -u postgres make prove_installcheck
|
||||||
env:
|
env:
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||||
|
valgrind:
|
||||||
|
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ankane/setup-postgres-valgrind@v1
|
||||||
|
with:
|
||||||
|
postgres-version: 16
|
||||||
|
- run: make
|
||||||
|
- run: sudo --preserve-env=PG_CONFIG make install
|
||||||
|
- run: make installcheck
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -400,6 +400,29 @@ Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html
|
|||||||
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
- `vector`
|
||||||
|
- `halfvec`
|
||||||
|
|
||||||
|
## Half-Precision
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE items (id bigserial PRIMARY KEY, embedding halfvec(3));
|
||||||
|
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||||
|
CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT id FROM items ORDER BY embedding::halfvec(3) <-> '[1,2,3]' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT id FROM (
|
||||||
|
SELECT * FROM items ORDER BY embedding::halfvec(3) <-> '[1,2,3]' LIMIT 20
|
||||||
|
) ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
## Hybrid Search
|
## Hybrid Search
|
||||||
|
|
||||||
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search.
|
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search.
|
||||||
|
|||||||
@@ -104,13 +104,30 @@ HalfToFloat4(half num)
|
|||||||
/* TODO Improve performance */
|
/* TODO Improve performance */
|
||||||
|
|
||||||
/* Assumes same endianness for floats and integers */
|
/* Assumes same endianness for floats and integers */
|
||||||
/* TODO Use union to swap */
|
union
|
||||||
uint16 bin = *((uint16 *) &num);
|
{
|
||||||
uint32 exponent = (bin & 0x7C00) >> 10;
|
float f;
|
||||||
uint32 mantissa = bin & 0x03FF;
|
uint32 i;
|
||||||
|
} swapfloat;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
half h;
|
||||||
|
uint16 i;
|
||||||
|
} swaphalf;
|
||||||
|
|
||||||
|
uint16 bin;
|
||||||
|
uint32 exponent;
|
||||||
|
uint32 mantissa;
|
||||||
|
uint32 result;
|
||||||
|
|
||||||
|
swaphalf.h = num;
|
||||||
|
bin = swaphalf.i;
|
||||||
|
exponent = (bin & 0x7C00) >> 10;
|
||||||
|
mantissa = bin & 0x03FF;
|
||||||
|
|
||||||
/* Sign */
|
/* Sign */
|
||||||
uint32 result = (bin & 0x8000) << 16;
|
result = (bin & 0x8000) << 16;
|
||||||
|
|
||||||
if (exponent == 31)
|
if (exponent == 31)
|
||||||
{
|
{
|
||||||
@@ -156,8 +173,8 @@ HalfToFloat4(half num)
|
|||||||
result |= mantissa << 13;
|
result |= mantissa << 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Use union to swap */
|
swapfloat.i = result;
|
||||||
return *((float *) &result);
|
return swapfloat.f;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +190,30 @@ Float4ToHalfUnchecked(float num)
|
|||||||
/* TODO Improve performance */
|
/* TODO Improve performance */
|
||||||
|
|
||||||
/* Assumes same endianness for floats and integers */
|
/* Assumes same endianness for floats and integers */
|
||||||
/* TODO Use union to swap */
|
union
|
||||||
uint32 bin = *((uint32 *) &num);
|
{
|
||||||
int exponent = (bin & 0x7F800000) >> 23;
|
float f;
|
||||||
int mantissa = bin & 0x007FFFFF;
|
uint32 i;
|
||||||
|
} swapfloat;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
half h;
|
||||||
|
uint16 i;
|
||||||
|
} swaphalf;
|
||||||
|
|
||||||
|
uint32 bin;
|
||||||
|
int exponent;
|
||||||
|
int mantissa;
|
||||||
|
uint16 result;
|
||||||
|
|
||||||
|
swapfloat.f = num;
|
||||||
|
bin = swapfloat.i;
|
||||||
|
exponent = (bin & 0x7F800000) >> 23;
|
||||||
|
mantissa = bin & 0x007FFFFF;
|
||||||
|
|
||||||
/* Sign */
|
/* Sign */
|
||||||
uint16 result = (bin & 0x80000000) >> 16;
|
result = (bin & 0x80000000) >> 16;
|
||||||
|
|
||||||
if (isinf(num))
|
if (isinf(num))
|
||||||
{
|
{
|
||||||
@@ -238,8 +272,8 @@ Float4ToHalfUnchecked(float num)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Use union to swap */
|
swaphalf.i = result;
|
||||||
return *((half *) & result);
|
return swaphalf.h;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,6 +512,9 @@ halfvec_in(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define AppendChar(ptr, c) (*(ptr)++ = (c))
|
||||||
|
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert internal representation to textual representation
|
* Convert internal representation to textual representation
|
||||||
*/
|
*/
|
||||||
@@ -489,7 +526,6 @@ halfvec_out(PG_FUNCTION_ARGS)
|
|||||||
int dim = vector->dim;
|
int dim = vector->dim;
|
||||||
char *buf;
|
char *buf;
|
||||||
char *ptr;
|
char *ptr;
|
||||||
int n;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Need:
|
* Need:
|
||||||
@@ -504,21 +540,17 @@ halfvec_out(PG_FUNCTION_ARGS)
|
|||||||
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
|
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
|
||||||
ptr = buf;
|
ptr = buf;
|
||||||
|
|
||||||
*ptr = '[';
|
AppendChar(ptr, '[');
|
||||||
ptr++;
|
|
||||||
for (int i = 0; i < dim; i++)
|
for (int i = 0; i < dim; i++)
|
||||||
{
|
{
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
AppendChar(ptr, ',');
|
||||||
*ptr = ',';
|
|
||||||
ptr++;
|
AppendFloat(ptr, HalfToFloat4(vector->x[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
n = float_to_shortest_decimal_bufn(HalfToFloat4(vector->x[i]), ptr);
|
AppendChar(ptr, ']');
|
||||||
ptr += n;
|
|
||||||
}
|
|
||||||
*ptr = ']';
|
|
||||||
ptr++;
|
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
PG_FREE_IF_COPY(vector, 0);
|
PG_FREE_IF_COPY(vector, 0);
|
||||||
@@ -731,15 +763,18 @@ Datum
|
|||||||
vector_to_halfvec(PG_FUNCTION_ARGS)
|
vector_to_halfvec(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
HalfVector *result;
|
HalfVector *result;
|
||||||
|
|
||||||
CheckDim(vec->dim);
|
CheckDim(vec->dim);
|
||||||
|
CheckExpectedDim(typmod, vec->dim);
|
||||||
|
|
||||||
result = InitHalfVector(vec->dim);
|
result = InitHalfVector(vec->dim);
|
||||||
|
|
||||||
for (int i = 0; i < vec->dim; i++)
|
for (int i = 0; i < vec->dim; i++)
|
||||||
{
|
{
|
||||||
result->x[i] = Float4ToHalfUnchecked(vec->x[i]);
|
result->x[i] = Float4ToHalfUnchecked(vec->x[i]);
|
||||||
|
/* TODO Better error for overflow */
|
||||||
CheckElement(result->x[i]);
|
CheckElement(result->x[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#define HALF_MAX FLT16_MAX
|
#define HALF_MAX FLT16_MAX
|
||||||
#else
|
#else
|
||||||
/* TODO #pragma message("")? */
|
/* TODO #pragma message("")? */
|
||||||
#define half __fp16
|
#define half uint16
|
||||||
#define HALF_MAX 65504
|
#define HALF_MAX 65504
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -541,9 +541,11 @@ Datum
|
|||||||
halfvec_to_vector(PG_FUNCTION_ARGS)
|
halfvec_to_vector(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
HalfVector *vec = PG_GETARG_HALFVEC_P(0);
|
HalfVector *vec = PG_GETARG_HALFVEC_P(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
Vector *result;
|
Vector *result;
|
||||||
|
|
||||||
CheckDim(vec->dim);
|
CheckDim(vec->dim);
|
||||||
|
CheckExpectedDim(typmod, vec->dim);
|
||||||
|
|
||||||
result = InitVector(vec->dim);
|
result = InitVector(vec->dim);
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ SELECT '[1,2,3]'::halfvec::vector;
|
|||||||
[1,2,3]
|
[1,2,3]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '[1,2,3]'::vector::halfvec(2);
|
||||||
|
ERROR: expected 2 dimensions, not 3
|
||||||
|
SELECT '[1,2,3]'::halfvec::vector(2);
|
||||||
|
ERROR: expected 2 dimensions, not 3
|
||||||
|
SELECT '[65520]'::vector::halfvec;
|
||||||
|
ERROR: infinite value not allowed in halfvec
|
||||||
SELECT '[1e-8]'::vector::halfvec;
|
SELECT '[1e-8]'::vector::halfvec;
|
||||||
halfvec
|
halfvec
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ SELECT '{{1}}'::real[]::vector;
|
|||||||
SELECT '[1,2,3]'::vector::real[];
|
SELECT '[1,2,3]'::vector::real[];
|
||||||
SELECT '[1,2,3]'::vector::halfvec;
|
SELECT '[1,2,3]'::vector::halfvec;
|
||||||
SELECT '[1,2,3]'::halfvec::vector;
|
SELECT '[1,2,3]'::halfvec::vector;
|
||||||
|
SELECT '[1,2,3]'::vector::halfvec(2);
|
||||||
|
SELECT '[1,2,3]'::halfvec::vector(2);
|
||||||
|
SELECT '[65520]'::vector::halfvec;
|
||||||
SELECT '[1e-8]'::vector::halfvec;
|
SELECT '[1e-8]'::vector::halfvec;
|
||||||
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
||||||
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
|
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ my $node;
|
|||||||
my @queries = ();
|
my @queries = ();
|
||||||
my @expected;
|
my @expected;
|
||||||
my $limit = 20;
|
my $limit = 20;
|
||||||
|
my $dim = 10;
|
||||||
|
my $array_sql = join(",", ('random()') x $dim);
|
||||||
|
|
||||||
sub test_recall
|
sub test_recall
|
||||||
{
|
{
|
||||||
@@ -52,18 +54,20 @@ $node->start;
|
|||||||
|
|
||||||
# Create table
|
# Create table
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v halfvec(3));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v halfvec($dim));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 10000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Generate queries
|
# Generate queries
|
||||||
for (1 .. 20)
|
for (1 .. 20)
|
||||||
{
|
{
|
||||||
my $r1 = rand();
|
my @r = ();
|
||||||
my $r2 = rand();
|
for (1 .. $dim)
|
||||||
my $r3 = rand();
|
{
|
||||||
push(@queries, "[$r1,$r2,$r3]");
|
push(@r, rand());
|
||||||
|
}
|
||||||
|
push(@queries, "[" . join(",", @r) . "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check each index type
|
# Check each index type
|
||||||
@@ -90,7 +94,7 @@ for my $i (0 .. $#operators)
|
|||||||
));
|
));
|
||||||
|
|
||||||
# Test approximate results
|
# Test approximate results
|
||||||
my $min = $operator eq "<#>" ? 0.80 : ($operator eq "<=>" ? 0.80 : 0.99);
|
my $min = $operator eq "<#>" ? 0.95 : 0.99;
|
||||||
test_recall($min, $operator);
|
test_recall($min, $operator);
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|||||||
Reference in New Issue
Block a user