mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
1 Commits
half
...
half-index
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47852e8d15 |
@@ -46,45 +46,21 @@ CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
||||
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE CAST (real AS half)
|
||||
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS real)
|
||||
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (float8 AS half)
|
||||
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS float8)
|
||||
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (integer AS half)
|
||||
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (numeric AS half)
|
||||
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS numeric)
|
||||
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
@@ -99,3 +75,8 @@ CREATE OPERATOR <=> (
|
||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS half_l2_ops
|
||||
FOR TYPE half[] USING hnsw AS
|
||||
OPERATOR 1 <-> (half[], half[]) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 half_l2_squared_distance(half[], half[]);
|
||||
|
||||
@@ -344,47 +344,23 @@ CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
||||
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- half casts
|
||||
|
||||
CREATE CAST (real AS half)
|
||||
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS real)
|
||||
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (float8 AS half)
|
||||
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS float8)
|
||||
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (integer AS half)
|
||||
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (numeric AS half)
|
||||
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (half AS numeric)
|
||||
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
||||
|
||||
-- half operators
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
@@ -401,3 +377,10 @@ CREATE OPERATOR <=> (
|
||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
-- half opclasses
|
||||
|
||||
CREATE OPERATOR CLASS half_l2_ops
|
||||
FOR TYPE half[] USING hnsw AS
|
||||
OPERATOR 1 <-> (half[], half[]) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 half_l2_squared_distance(half[], half[]);
|
||||
|
||||
109
src/half.c
109
src/half.c
@@ -103,7 +103,7 @@ DatumGetHalf(Datum X)
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a half from a message buffer
|
||||
* Append a half to a StringInfo buffer
|
||||
*/
|
||||
static half
|
||||
pq_getmsghalf(StringInfo msg)
|
||||
@@ -114,12 +114,13 @@ pq_getmsghalf(StringInfo msg)
|
||||
uint16 i;
|
||||
} swap;
|
||||
|
||||
/* TODO Likely use float4 for clients */
|
||||
swap.i = pq_getmsgint(msg, 2);
|
||||
return swap.h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Append a half to a StringInfo buffer
|
||||
* Get a half from a message buffer
|
||||
*/
|
||||
static void
|
||||
pq_sendhalf(StringInfo buf, half h)
|
||||
@@ -130,6 +131,7 @@ pq_sendhalf(StringInfo buf, half h)
|
||||
uint16 i;
|
||||
} swap;
|
||||
|
||||
/* TODO Likely use float4 for clients */
|
||||
swap.h = h;
|
||||
pq_sendint16(buf, swap.i);
|
||||
}
|
||||
@@ -279,32 +281,6 @@ Float4ToHalfUnchecked(float num)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if half is infinite
|
||||
*/
|
||||
static inline bool
|
||||
HalfIsInf(half num)
|
||||
{
|
||||
#ifdef FLT16_SUPPORT
|
||||
return isinf(num);
|
||||
#else
|
||||
return (num << 1) == 0xF800;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if half is zero
|
||||
*/
|
||||
static inline bool
|
||||
HalfIsZero(half num)
|
||||
{
|
||||
#ifdef FLT16_SUPPORT
|
||||
return num == 0;
|
||||
#else
|
||||
return (num << 1) == 0x0000;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a float4 to a half
|
||||
*/
|
||||
@@ -313,26 +289,10 @@ Float4ToHalf(float num)
|
||||
{
|
||||
half result = Float4ToHalfUnchecked(num);
|
||||
|
||||
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
||||
/* TODO Perform checks without HalfToFloat4 */
|
||||
if (unlikely(isinf(HalfToFloat4(result))) && !isinf(num))
|
||||
float_overflow_error();
|
||||
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
||||
float_underflow_error();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a float8 to a half
|
||||
*/
|
||||
static half
|
||||
Float8ToHalf(double num)
|
||||
{
|
||||
/* TODO Convert directly for greater accuracy */
|
||||
half result = Float4ToHalfUnchecked((float) num);
|
||||
|
||||
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
||||
float_overflow_error();
|
||||
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
||||
if (unlikely(HalfToFloat4(result) == 0.0f) && num != 0.0)
|
||||
float_underflow_error();
|
||||
|
||||
return result;
|
||||
@@ -454,8 +414,6 @@ integer_to_half(PG_FUNCTION_ARGS)
|
||||
|
||||
/*
|
||||
* Convert numeric to half
|
||||
*
|
||||
* TODO Improve error message if out of range
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(numeric_to_half);
|
||||
Datum
|
||||
@@ -468,20 +426,6 @@ numeric_to_half(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_HALF(h);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert half to numeric
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_numeric);
|
||||
Datum
|
||||
half_to_numeric(PG_FUNCTION_ARGS)
|
||||
{
|
||||
half h = PG_GETARG_HALF(0);
|
||||
float f = HalfToFloat4(h);
|
||||
Numeric num = DatumGetNumeric(DirectFunctionCall1(float4_numeric, Float4GetDatum(f)));
|
||||
|
||||
PG_RETURN_NUMERIC(num);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert float4 to half
|
||||
*/
|
||||
@@ -495,45 +439,6 @@ float4_to_half(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_HALF(h);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert half to float4
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float4);
|
||||
Datum
|
||||
half_to_float4(PG_FUNCTION_ARGS)
|
||||
{
|
||||
half h = PG_GETARG_HALF(0);
|
||||
float f = HalfToFloat4(h);
|
||||
|
||||
PG_RETURN_FLOAT4(f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert float8 to half
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(float8_to_half);
|
||||
Datum
|
||||
float8_to_half(PG_FUNCTION_ARGS)
|
||||
{
|
||||
float8 d = PG_GETARG_FLOAT8(0);
|
||||
half h = Float8ToHalf(d);
|
||||
|
||||
PG_RETURN_HALF(h);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert half to float8
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float8);
|
||||
Datum
|
||||
half_to_float8(PG_FUNCTION_ARGS)
|
||||
{
|
||||
half h = PG_GETARG_HALF(0);
|
||||
float f = HalfToFloat4(h);
|
||||
|
||||
PG_RETURN_FLOAT8((double) f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 distance between half arrays
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#ifdef __FLT16_MAX__
|
||||
/* _Float16 and __fp16 are not supported on x86_64 with GCC 11 */
|
||||
#if defined(__is_identifier)
|
||||
#if __is_identifier(_Float16)
|
||||
#define FLT16_SUPPORT
|
||||
#endif
|
||||
#elif defined(FLT16_MAX)
|
||||
#define FLT16_SUPPORT
|
||||
#endif
|
||||
|
||||
|
||||
@@ -462,8 +462,8 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
||||
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
||||
|
||||
/* Require column to have dimensions to be indexed */
|
||||
if (buildstate->dimensions < 0)
|
||||
elog(ERROR, "column does not have dimensions");
|
||||
// if (buildstate->dimensions < 0)
|
||||
// elog(ERROR, "column does not have dimensions");
|
||||
|
||||
if (buildstate->dimensions > HNSW_MAX_DIM)
|
||||
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", HNSW_MAX_DIM);
|
||||
|
||||
@@ -58,12 +58,6 @@ SELECT '{1,2,3}'::half[];
|
||||
{1,2,3}
|
||||
(1 row)
|
||||
|
||||
SELECT '{1,2,3}'::half[]::real[];
|
||||
float4
|
||||
---------
|
||||
{1,2,3}
|
||||
(1 row)
|
||||
|
||||
SELECT '65505'::integer::half;
|
||||
half
|
||||
-------
|
||||
@@ -82,44 +76,6 @@ SELECT 'Infinity'::real::half;
|
||||
Infinity
|
||||
(1 row)
|
||||
|
||||
SELECT '1e-38'::real::half;
|
||||
ERROR: value out of range: underflow
|
||||
SELECT '1.5'::half::real;
|
||||
float4
|
||||
--------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT '1.5'::real::half;
|
||||
half
|
||||
------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT '1.5'::half::double precision;
|
||||
float8
|
||||
--------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT '1.5'::double precision::half;
|
||||
half
|
||||
------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT '1.5'::half::numeric;
|
||||
numeric
|
||||
---------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT '1.5'::numeric::half;
|
||||
half
|
||||
------
|
||||
1.5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
||||
l2_distance
|
||||
-------------
|
||||
|
||||
@@ -12,21 +12,10 @@ SELECT '1.5 '::half;
|
||||
SELECT '1.5a'::half;
|
||||
|
||||
SELECT '{1,2,3}'::half[];
|
||||
SELECT '{1,2,3}'::half[]::real[];
|
||||
|
||||
SELECT '65505'::integer::half;
|
||||
SELECT 'NaN'::real::half;
|
||||
SELECT 'Infinity'::real::half;
|
||||
SELECT '1e-38'::real::half;
|
||||
|
||||
SELECT '1.5'::half::real;
|
||||
SELECT '1.5'::real::half;
|
||||
|
||||
SELECT '1.5'::half::double precision;
|
||||
SELECT '1.5'::double precision::half;
|
||||
|
||||
SELECT '1.5'::half::numeric;
|
||||
SELECT '1.5'::numeric::half;
|
||||
|
||||
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
||||
SELECT l2_distance('{0,0}'::half[], '{0,1}'::half[]);
|
||||
|
||||
93
test/t/019_hnsw_half.pl
Normal file
93
test/t/019_hnsw_half.pl
Normal file
@@ -0,0 +1,93 @@
|
||||
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 half[3]);");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()]::numeric[]::half[] FROM generate_series(1, 10000) i;"
|
||||
);
|
||||
|
||||
# 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 = ("half_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);");
|
||||
|
||||
my $min = $operator eq "<#>" ? 0.80 : 0.99;
|
||||
test_recall($min, $operator);
|
||||
}
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user