Compare commits

..

12 Commits

Author SHA1 Message Date
Andrew Kane
46eda44530 Removed comments [skip ci] 2023-12-18 14:38:10 -05:00
Andrew Kane
02f55399eb Fixed comments [skip ci] 2023-12-18 14:08:48 -05:00
Andrew Kane
a679b584e9 Improved check [skip ci] 2023-12-04 13:59:48 -08:00
Andrew Kane
006e5c7aca Fixed identifier check 2023-12-04 13:53:07 -08:00
Andrew Kane
ba3730d2a5 Fixed identifier check 2023-12-04 13:38:00 -08:00
Andrew Kane
7aea658b26 Added HalfIsInf [skip ci] 2023-12-04 13:33:39 -08:00
Andrew Kane
7620089901 Improved Float8ToHalf [skip ci] 2023-12-04 13:03:59 -08:00
Andrew Kane
544e303d74 Added todo [skip ci] 2023-12-04 13:01:25 -08:00
Andrew Kane
d09aa9f873 Added casts between half to double precision [skip ci] 2023-12-04 12:49:28 -08:00
Andrew Kane
95eff595f0 Added cast from half to numeric [skip ci] 2023-12-04 12:38:34 -08:00
Andrew Kane
4b630d4f27 Added cast from half to real [skip ci] 2023-12-04 12:27:13 -08:00
Andrew Kane
3b2198d52c Removed comment [skip ci] 2023-12-04 10:27:03 -08:00
8 changed files with 208 additions and 120 deletions

View File

@@ -46,21 +46,45 @@ 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 = '<->'
@@ -75,8 +99,3 @@ 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[]);

View File

@@ -344,23 +344,47 @@ 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 <-> (
@@ -377,10 +401,3 @@ 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[]);

View File

@@ -103,7 +103,7 @@ DatumGetHalf(Datum X)
}
/*
* Append a half to a StringInfo buffer
* Get a half from a message buffer
*/
static half
pq_getmsghalf(StringInfo msg)
@@ -114,13 +114,12 @@ pq_getmsghalf(StringInfo msg)
uint16 i;
} swap;
/* TODO Likely use float4 for clients */
swap.i = pq_getmsgint(msg, 2);
return swap.h;
}
/*
* Get a half from a message buffer
* Append a half to a StringInfo buffer
*/
static void
pq_sendhalf(StringInfo buf, half h)
@@ -131,7 +130,6 @@ pq_sendhalf(StringInfo buf, half h)
uint16 i;
} swap;
/* TODO Likely use float4 for clients */
swap.h = h;
pq_sendint16(buf, swap.i);
}
@@ -281,6 +279,32 @@ 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
*/
@@ -289,10 +313,26 @@ Float4ToHalf(float num)
{
half result = Float4ToHalfUnchecked(num);
/* TODO Perform checks without HalfToFloat4 */
if (unlikely(isinf(HalfToFloat4(result))) && !isinf(num))
if (unlikely(HalfIsInf(result)) && !isinf(num))
float_overflow_error();
if (unlikely(HalfToFloat4(result) == 0.0f) && num != 0.0)
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)
float_underflow_error();
return result;
@@ -414,6 +454,8 @@ 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
@@ -426,6 +468,20 @@ 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
*/
@@ -439,6 +495,45 @@ 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
*/

View File

@@ -5,12 +5,7 @@
#include <float.h>
/* _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)
#ifdef __FLT16_MAX__
#define FLT16_SUPPORT
#endif

View File

@@ -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);

View File

@@ -58,6 +58,12 @@ 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
-------
@@ -76,6 +82,44 @@ 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
-------------

View File

@@ -12,10 +12,21 @@ 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[]);

View File

@@ -1,93 +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 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();