このトピックでは、半精度浮動小数点形式 (FLOAT2) タイプの圧縮列と関連する操作について説明します。 従来のベクトル検索システムは、画像、オーディオファイル、およびテキストを、大量のストレージを消費する高次元浮動小数点配列に変換する。 ストレージ使用量を削減し、コストを削減するために、AnalyticDB for PostgreSQLはFLOAT2データ型を提供します。
FLOAT2データ型
FLOAT2は、2バイト (16ビット) を占有し、4バイト (32ビット) を占有するFLOAT4データを格納するバイナリ浮動小数点データ型である。 IEEE 754標準で定義されているbinary16形式は、次の形式レイアウトを持ちます。
符号ビット: 1ビット。
指数幅: 5ビット。
重要な精度: 11ビット (明示的に格納された10) 。
フォーマットを次の図に示します。
このフォーマットは、指数フィールドがすべてゼロで記憶されない限り、値1の暗黙のリードビットを有すると仮定される。 この場合、メモリフォーマットには10ビットの仮数部しか現れないが、合計精度は11ビットである。 IEEE 754規格では、10ビットの仮数部は、11ビットの仮数部精度 (log10(211) ≒ 3.311桁) を必要とする。
0 01111 0000000000 = 1
0 01111 0000000001 = 1 + 2-10 = 1.0009765625 (smallest number greater than 1)
1 10000 0000000000 = -2
0 11110 1111111111 = 65504 (max half precision)
0 00001 0000000000 = 2-14 ≈ 6.10352 × 10-5 (smallest positive normal number)
0 00000 1111111111 = 2-14 - 2-24 ≈ 6.09756 × 10-5 (greatest subnormal number)
0 00000 0000000001 = 2-24 ≈ 5.96046 × 10-8 (smallest positive subnormal number)
0 00000 0000000000 = 0
1 00000 0000000000 = -0
0 11111 0000000000 = infinity
1 11111 0000000000 = -infinity
0 01101 0101010101 = 0.333251953125 ≈ 1/3デフォルトでは、仮数部のビット数が奇数であるため、1/3は同様の方法で倍精度を切り捨てます。
データをFLOAT2型とFLOAT4型の間で変換するには、システムはビットをシフトし、基数を15〜127に変換します。 たとえば、データをFLOAT2からFLOAT4に変換するには、システムは次の操作を実行します。
符号ビットを16ビット左にシフトする。
112を加算して基数を15から127に変換し、13ビット左にシフトして指数を右に揃えます。
13ビットだけ左にシフトすることによって、仮数部を左に整列させる。
データをFLOAT4からFLOAT2に変換するために、逆の演算が実行される。
浮動小数点データ圧縮は、クエリおよび計算中に精度の損失を引き起こす。 ある程度の精度損失は許容されます。
FLOAT4データ型と比較して、FLOAT2データ型は4バイトのデータを2バイトに圧縮し、ディスク容量の半分を占めます。 ベクトル列の圧縮率は0.5です。
FLOAT2データとして保存される値は、-65519.99から65519.99の範囲でなければなりません。 値が65519より大きい場合、システムはInfinityを表示します。 値が-65519未満の場合、システムは-Infinityと表示します。 ベクトルは、ベクトル検索の前に0〜1の範囲に正規化する必要があります。 そうでなければ、計算されたベクトル距離は、FLOAT2値の範囲を超え、不正確になる。
FLOAT2とFLOAT4の間の変換は、パフォーマンスのオーバーヘッドを引き起こします。 2つの変換アルゴリズムを使用して、FLOAT2配列のデータ型を変換できます。
Cプログラムを使用して、配列内のFLOAT2要素を変換します。 一度に変換される要素は1つだけです。
Advanced Vector Extensions (AVX) およびStreaming SIMD Extensions 2 (SSE2) をサポートするハードウェアの場合は、ハードウェア固有のインターフェイスと関数を使用します。 4つのFLOAT2要素を同時に変換することができます。
クエリではインデックスなどのトラバースメソッドを使用するため、変換できるエントリは少数です。
FLOAT2データを格納するテーブルを作成する
FLOAT2は内部データ型です。 システムは、複数のデータ型および関連する演算子間の変換を管理する。 この場合、FLOAT2は、プリミティブデータ型として処理される。
構文:
CREATE TABLE [TABLE_NAME]
(
C1 INT,
C2 FLOAT2[],
C3 VARCHAR(20),
PRIMARY KEY(C1)
) DISTRIBUTED BY(C1);C2列は、FLOAT2ベクトルを格納する。
例:
FACE_TABLEという名前のテーブルを作成します。 FLOAT2ベクトルを格納する列としてC2列を指定します。
CREATE TABLE FACE_TABLE (
C1 INT PRIMARY KEY,
C2 FLOAT2[],
C3 VARCHAR(20)
) DISTRIBUTED BY (C1);データの挿入
次のいずれかの方法を使用して、FLOAT2配列をテーブルに挿入できます。 FLOAT2配列を明示的に定義し、sql1で説明するようにテーブルに挿入できます。 FLOAT4配列を定義することもでき、システムはそれをFLOAT2配列に変換し、sql2およびsql3で説明されているように配列をテーブルに挿入します。
例:
FACE_TABLEテーブルに3つのエントリを挿入します。
sql1 = INSERT INTO FACE_TABLE (C1, C2, C3)
VALUES (1, ARRAY[1.3, 2.4, 5.6]::FLOAT2[], 'name1');
sql2 = INSERT INTO FACE_TABLE (c1, c2, c3)
VALUES (2, ARRAY [3.4, 6.1, 7.6]::REAL[], 'name2');
sql3 = INSERT INTO FACE_TABLE (c1, c2, c3)
VALUES (3, ARRAY [9.5, 1.2, 0.6]::FLOAT4[],'name3');クエリデータ
FLOAT2データは、クエリ中にデータ精度の損失をもたらします。 たとえば、挿入された値1.3は1.2998としてクエリされ、挿入された値5.6は5.60156としてクエリされます。 このレベルの精度損失は、ベクトル検索では無視することができる。
例:
SELECT * FROM FACE_TABLE;
c1 | c2 | c3
----+---------------------------+-------
1 | {1.2998,2.40039,5.60156} | name1
2 | {3.40039,6.10156,7.60156} | name2
3 | {9.5,1.2002,0.600098} | name3FLOAT2テーブルのデータ圧縮
次のステートメントは、FLOAT4ベクトルを格納するテーブルと、FLOAT2ベクトルを格納するテーブルを作成します。 2つのテーブルのサイズを比較します。
--CREATE TABLE
CREATE TABLE TAB1(A FLOAT4[]);
CREATE TABLE TAB2(A FLOAT2[]);
--INSERT DATA
INSERT INTO TAB1
SELECT GEN_RAND_F2_ARR (1, 1024) FROM GENERATE_SERIES (1,10000);
INSERT INTO TAB2
SELECT GEN_RAND_F2_ARR (1, 1024) FROM GENERATE_SERIES (1,10000);
--QUERY SIZE
SELECT PG_SIZE_PRETTY (PG_RELATION_SIZE('tab1'));
PG_SIZE_PRETTY
----------------
45 MB(1 row)
SELECT PG_SIZE_PRETTY (PG_RELATION_SIZE('tab2'));
PG_SIZE_PRETTY
----------------
21 MB(1 row)FLOAT2テーブルは21 MBのストレージを占有します。これは、FLOAT4テーブルが占有する45 MBのストレージの半分にすぎません。
FLOAT2データの圧縮と解凍のパフォーマンステスト
システムは、データを変換する2つの機能を提供する。 array_f16_to_f32関数はFLOAT2ベクトルをFLOAT4ベクトルに変換し、array_f32_to_f16関数はFLOAT4ベクトルをFLOAT2ベクトルに変換します。 テスト用の各ベクトルの長さは1,024次元であり、テストはAVXとSSE2をサポートするマシンで実行されます。
例:
--CREATE TABLE
CREATE TABLE TAB1(A FLOAT4[]);
CREATE TABLE TAB2(A FLOAT2[]);
--INSERT TABLE
INSERT INTO TAB1 SELECT GEN_RAND_F2_ARR(1, 1024) FROM GENERATE_SERIES (1,10000);
INSERT INTO TAB2 SELECT GEN_RAND_F2_ARR(1, 1024) FROM GENERATE_SERIES (1,10000);
\TIMING
--query size
SELECT ARRAY_F32_TO_F16(a) FROM TAB1;
Time: 5998.832 ms (00:05.999)
SELECT ARRAY_F16_TO_F32(a) FROM TAB2;
Time: 5507.388 ms (00:05.507) 距離計算
このシステムは、FLOAT2データをFLOAT4データに変換して距離を計算するユークリッド距離計算方法を提供します。
例:
ユークリッド距離を計算します。
SELECT L2_DISTANCE(ARRAY[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]::FLOAT2[],
ARRAY[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]::FLOAT2[]);
SELECT L2_DISTANCE(ARRAY[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]:: FLOAT4[],
ARRAY [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]:: FLOAT2[]);
SELECT L2_DISTANCE (ARRAY[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]:: FLOAT2[],
ARRAY [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]::FLOAT2[]);ユースケース
セキュリティシステムは、捕捉された顔データを毎日スケジュールベースで顔テーブルに記憶し、顔画像に基づいて関連する監視データを取り出す。 次のセクションでは、ベクトル検索でFLOAT2データを使用する方法について説明します。
顔認識に関連するデータを格納するテーブルを作成します。
CREATE TABLE FACE_TABLE ( C1 INT PRIMARY KEY, C2 FLOAT2[], C3 VARCHAR(20) ) DISTRIBUTED BY(C1);説明C1: 顔番号を格納する列。
C2: 顔ベクトルを格納する列。
C3: 顔の名前を格納する列。
FACE_TABLEテーブルにベクトルインデックスを作成します。
CREATE INDEX FACE_TABLE_IDX ON FACE_TABLE USING ANN(C2) WITH(dim=10);モニタリングデータをFACE_TABLEテーブルにインポートします。
INSERT INTO FACE_TABLE (C1, C2, C3) VALUES (1, ARRAY[1.3, 2.4, 5.6]::FLOAT2[], 'name1'); INSERT INTO FACE_TABLE (c1, c2, c3) VALUES (2, ARRAY[3.4, 6.1, 7.6]::REAL[], 'name2'); INSERT INTO FACE_TABLE (c1, c2, c3) VALUES (3, ARRAY[9.5, 1.2, 0.6]::FLOAT4[],'name3');顔ベクトルに基づいてベクトル検索を実行します。
SELECT * FROM FACE_TABLE ORDER BY C1 <-> ARRAY[2.81574,9.84361,8.07218]:: FLOAT2[] LIMIT 10;説明ARRAY[2.81574,9.84361,8.07218]:: FLOAT2[]は、顔ベクトルを指定する。 システムは、基礎となるデータベースから関連情報を取り出す。