メインコンテンツまでスキップ

概要

生成AI アプリケーションにおいて、大規模モデルの内部パラメータの「記憶」のみに依存することには明らかな限界があります:(1)モデルの知識は古くなり、最新の情報をカバーできない;(2)モデルに直接答えを「生成」させることで、ハルシネーションのリスクが増大する。これによりRAG(Retrieval-Augmented Generation)が生まれました。RAGの主要なタスクは、モデルに何もないところから答えを作り出させることではなく、外部の知識ベースから最も関連性の高いTop-K情報チャンクを検索し、それらを根拠となるコンテキストとしてモデルに提供することです。

これを実現するために、ユーザークエリと知識ベース内の文書間の意味的関連性を測定するメカニズムが必要です。ベクトル表現は標準的なツールです:クエリと文書の両方を意味ベクトルにエンコードすることで、ベクトル類似度を使用して関連性を測定できます。事前学習済み言語モデルの進歩により、高品質な埋め込みの生成が主流となりました。したがって、RAGの検索段階は典型的なベクトル類似度検索問題となります:大規模なベクトルコレクションから、クエリに最も類似するKベクトル(すなわち、候補となる知識片)を見つけることです。

RAGにおけるベクトル検索はテキストに限定されません;マルチモーダルシナリオにも自然に拡張されます。マルチモーダルRAGシステムでは、画像、音声、ビデオ、その他のデータタイプもベクトルにエンコードして検索し、生成モデルにコンテキストとして提供できます。例えば、ユーザーが画像をアップロードした場合、システムはまず関連する説明や知識スニペットを検索し、次に説明コンテンツを生成できます。医療QAでは、RAGは患者記録や文献を検索して、より正確な診断提案をサポートできます。

近似最近傍探索

バージョン4.0から、Apache DorisはANN検索を正式にサポートしています。追加のデータタイプは導入されていません:ベクトルは固定長配列として保存されます。距離ベースのインデックス化のために、Faissに基づいた新しいインデックスタイプANNが実装されています。

一般的なSIFTデータセットを例として、次のようなテーブルを作成できます:

CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="flat"
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);
  • index_type: hnsw (Hierarchical Navigable Small Worldの場合)、ivf (inverted fileの場合)、または ivf_on_disk (転置リストをディスクに保存しキャッシュ経由で提供するIVFの場合)
  • metric_type: l2_distance は距離関数としてL2距離を使用することを意味します
  • dim: 128 はベクトル次元が128であることを意味します
  • quantizer: flat は各ベクトル次元が元のfloat32として保存されることを意味します
パラメータ必須サポート/オプションデフォルト説明
index_typeはいhnsw, ivf, ivf_on_disk(なし)ANNインデックスアルゴリズム。HNSW、インメモリIVF、およびIVF On-Diskをサポートします。
metric_typeはいl2_distance, inner_product(なし)ベクトル類似度/距離メトリック。L2 = ユークリッド距離; inner_productはベクトルが正規化されている場合にコサイン類似度を近似できます。
dimはい正の整数 (> 0)(なし)ベクトル次元。インポートされるすべてのベクトルが一致する必要があり、そうでなければエラーが発生します。
nlistいいえ正の整数1024IVF転置リスト数。index_type=ivf または index_type=ivf_on_disk の場合に有効; 大きな値はリコール/速度のトレードオフを改善する可能性がありますが、構築オーバーヘッドが増加します。
max_degreeいいえ正の整数32HNSW M (ノードあたりの最大近傍数)。インデックスメモリと検索性能に影響します。
ef_constructionいいえ正の整数40HNSW efConstruction (構築時の候補キューサイズ)。大きくすると品質は向上しますが構築速度は低下します。
quantizerいいえflat, sq8, sq4, pqflatベクトル符号化/量子化: flat = 生データ; sq8/sq4 = スカラー量子化 (8/4ビット)、pq = メモリを削減するproduct quantization。
pq_m'quantizer=pq'の場合必須正の整数(なし)使用するサブベクトル数を指定します (ベクトル次元dimはpq_mで割り切れる必要があります)。
pq_nbits'quantizer=pq'の場合必須正の整数(なし)各サブベクトルを表現するために使用されるビット数。faissではpq_nbitsは一般的に24以下である必要があります。

コサイン類似度が必要な場合

DorisのANNインデックス metric_typel2_distanceinner_product をサポートしていますが、cosine を直接サポートしていません。

ビジネスメトリックがコサイン類似度の場合、推奨されるアプローチは:

  1. データを書き込む前にベクトルを単位長にL2正規化する。
  2. metric_type="inner_product" でANNインデックスを構築する。
  3. inner_product_approximate(...)ORDER BY ... DESC でクエリする。

DDLの例:

CREATE INDEX idx_emb_cosine ON your_table (embedding) USING ANN PROPERTIES (
"index_type"="hnsw",
"metric_type"="inner_product",
"dim"="768"
);

なぜこれが機能するか:

  • コサイン類似度:cos(x, y) = (x · y) / (||x|| ||y||)
  • L2正規化後(||x|| = ||y|| = 1):cos(x, y) = x · y

したがって、コサイン類似度を最大化することは、正規化されたベクトルにおける内積を最大化することと等価です。 ベクトルが正規化されていない場合、内積はもはやコサインと等価ではありません。

S3 TVF経由でインポート:

INSERT INTO sift_1M
SELECT *
FROM S3(
"uri" = "https://selectdb-customers-tools-bj.oss-cn-beijing.aliyuncs.com/sift_database.tsv",
"format" = "csv");

SELECT count(*) FROM sift_1M

+----------+
| count(*) |
+----------+
| 1000000 |
+----------+

l2_distance_approximate / inner_product_approximateを使用すると、ANNインデックスパスがトリガーされます。関数はインデックスのmetric_typeと正確に一致する必要があります(例:metric_type=l2_distancel2_distance_approximateを使用;metric_type=inner_productinner_product_approximateを使用)。順序について:L2は距離の昇順を使用します(小さいほど近い);inner productはスコアの降順を使用します(大きいほど近い)。

SELECT id,
l2_distance_approximate(
embedding,
[0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2]
) AS distance
FROM sift_1M
ORDER BY distance
LIMIT 10;
--------------

+--------+----------+
| id | distance |
+--------+----------+
| 178811 | 210.1595 |
| 177646 | 217.0161 |
| 181997 | 218.5406 |
| 181605 | 219.2989 |
| 821938 | 221.7228 |
| 807785 | 226.7135 |
| 716433 | 227.3148 |
| 358802 | 230.7314 |
| 803100 | 230.9112 |
| 866737 | 231.6441 |
+--------+----------+
10 rows in set (0.02 sec)

正確なground truthと比較するには、l2_distanceまたはinner_product_approximateサフィックスなし)を使用してください。この例では、正確な検索には約290ミリ秒かかります:

10 rows in set (0.29 sec)

ANNインデックスを使用すると、この例ではクエリレイテンシが約290msから約20msに短縮されます。

ANNインデックスはセグメント単位で構築されます。分散テーブルでは、各セグメントがローカルのTopNを返し、その後TopN演算子がタブレットとセグメント全体の結果をマージしてグローバルなTopNを生成します。

順序に関する注意:

  • metric_type = l2_distanceの場合、距離が小さいほど近いベクトル → ORDER BY dist ASCを使用します。
  • metric_type = inner_productの場合、値が大きいほど近いベクトル → インデックス経由でTopNを取得するにはORDER BY dist DESCを使用します。

近似範囲検索

一般的なTopN最近傍検索(最も近いN件のレコードを返す)に加えて、もう一つの典型的なパターンは閾値ベースの範囲検索です。固定数の結果を返す代わりに、ターゲットベクトルまでの距離が述語(>, >=, <, <=)を満たすすべての点を返します。例えば、距離が閾値より大きいまたは小さいベクトルが必要な場合があります。これは「十分に類似した」または「十分に異なった」候補が必要な場合に有用です。推薦システムでは、多様性を向上させるために近いが同一でないアイテムを取得することがあります。異常検知では、正常分布から離れた点を探します。

SQLの例:

SELECT count(*)
FROM sift_1M
WHERE l2_distance_approximate(
embedding,
[0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2])
> 300
--------------

+----------+
| count(*) |
+----------+
| 999271 |
+----------+
1 row in set (0.19 sec)

これらの範囲ベースのベクトル検索もANNインデックスによって高速化されます。インデックスがまず候補を絞り込み、次に近似距離が計算されることで、コストが削減され、レイテンシが改善されます。サポートされている述語:>>=<<=

Compound Searchは、同一のSQL文内でANN TopN検索と範囲述語を組み合わせ、距離制約も満たすTopN結果を返します。

SELECT id,
l2_distance_approximate(
embedding, [0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2]) as dist
FROM sift_1M
WHERE l2_distance_approximate(
embedding, [0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2])
> 300
ORDER BY dist limit 10
--------------

+--------+----------+
| id | dist |
+--------+----------+
| 243590 | 300.005 |
| 549298 | 300.0317 |
| 429685 | 300.0533 |
| 690172 | 300.0916 |
| 123410 | 300.1333 |
| 232540 | 300.1649 |
| 547696 | 300.2066 |
| 855437 | 300.2782 |
| 589017 | 300.3048 |
| 930696 | 300.3381 |
+--------+----------+
10 rows in set (0.12 sec)

重要な問題は、述語フィルタリングがTopNの前に実行されるか後に実行されるかです。述語が最初にフィルタリングされ、削減されたセットにTopNが適用される場合は事前フィルタリングで、そうでない場合は事後フィルタリングです。事後フィルタリングの方が高速になる可能性がありますが、再現率を大幅に低下させる場合があります。Dorisは再現率を保持するために事前フィルタリングを使用します。

Dorisはインデックスを使用して両フェーズを高速化できます。しかし、最初のフェーズ(範囲フィルタ)の選択性が高すぎる場合、両フェーズにインデックスを使用すると再現率が低下する可能性があります。Dorisは述語の選択性とインデックスタイプに基づいて、インデックスを2回使用するかどうかを適応的に決定します。

追加フィルタを使用したANN検索

これは、ANN TopNの前に他の述語を適用し、それらの制約の下でTopNを返すことを指します。

小さな8次元ベクトルとテキストフィルタを使用した例:

CREATE TABLE ann_with_fulltext (
id int NOT NULL,
embedding array<float> NOT NULL,
comment String NOT NULL,
value int NULL,
INDEX idx_comment(`comment`) USING INVERTED PROPERTIES("parser" = "english") COMMENT 'inverted index for comment',
INDEX ann_embedding(`embedding`) USING ANN PROPERTIES("index_type"="hnsw","metric_type"="l2_distance","dim"="8")
) DUPLICATE KEY (`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 1
PROPERTIES("replication_num"="1");

サンプルデータを挿入し、commentに"music"が含まれる行のみを検索する:

INSERT INTO ann_with_fulltext VALUES
(1, [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8], 'this is about music', 10),
(2, [0.2,0.1,0.5,0.3,0.9,0.4,0.7,0.1], 'sports news today', 20),
(3, [0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.2], 'latest music trend', 30),
(4, [0.05,0.06,0.07,0.08,0.09,0.1,0.2,0.3], 'politics update',40);

SELECT id, comment,
l2_distance_approximate(embedding, [0.1,0.1,0.2,0.2,0.3,0.3,0.4,0.4]) AS dist
FROM ann_with_fulltext
WHERE comment MATCH_ANY 'music' -- Filter using inverted index
ORDER BY dist ASC -- Ann topn calculation after predicates evaluate.
LIMIT 2;

+------+---------------------+----------+
| id | comment | dist |
+------+---------------------+----------+
| 1 | this is about music | 0.663325 |
| 3 | latest music trend | 1.280625 |
+------+---------------------+----------+
2 rows in set (0.04 sec)

TopNがベクトルインデックスによって高速化されることを保証するために、すべての述語列には適切なセカンダリインデックス(例:転置インデックス)が必要です。

ANN検索に関連するセッション変数

HNSWのビルド時パラメータに加えて、セッション変数を介して検索時パラメータを渡すことができます:

  • hnsw_ef_search: EF検索パラメータ。候補キューの最大長を制御します。大きいほど精度が高く、レイテンシも高くなります。デフォルトは32です。
  • hnsw_check_relative_distance: 精度を向上させるために相対距離チェックを有効にするかどうか。デフォルトはtrueです。
  • hnsw_bounded_queue: パフォーマンスを最適化するために境界付き優先キューを使用するかどうか。デフォルトはtrueです。

ベクトル量子化

FLATエンコーディングでは、HNSWインデックス(生ベクトルとグラフ構造)が大量のメモリを消費する可能性があります。HNSWが機能するためには完全にメモリに常駐している必要があるため、大規模では メモリがボトルネックになる可能性があります。

スカラー量子化(SQ)はfloat32ストレージを圧縮してメモリを削減します。積量子化(PQ)は、高次元ベクトルをより小さなサブベクトルに圧縮し、各サブベクトルを独立して量子化することでメモリオーバーヘッドを削減します。スカラー量子化について、DorisはINT8とINT4(SQ8 / SQ4)の2つのスカラー量子化スキームを現在サポートしています。SQ8を使用した例:

CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="sq8"
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);

768次元のCohere-MEDIUM-1MおよびCohere-LARGE-10Mデータセットにおいて、SQ8はFLATと比較してインデックスサイズを約3分の1に削減します。

DatasetDimStorage/Index SchemeTotal DiskData PartIndex PartNotes
Cohere-MEDIUM-1M768DDoris (FLAT)5.647 GB (2.533 + 3.114)2.533 GB3.114 GB1M vectors
Cohere-MEDIUM-1M768DDoris SQ INT83.501 GB (2.533 + 0.992)2.533 GB0.992 GBINT8 symmetric quantization
Cohere-MEDIUM-1M768DDoris PQ(pq_m=384,pq_nbits=8)3.149 GB (2.535 + 0.614)2.535 GB0.614 GBproduct quantization
Cohere-LARGE-10M768DDoris (FLAT)56.472 GB (25.328 + 31.145)25.328 GB31.145 GB10M vectors
Cohere-LARGE-10M768DDoris SQ INT835.016 GB (25.329 + 9.687)25.329 GB9.687 GBINT8 quantization

量子化は、各距離計算で量子化された値をデコードする必要があるため、追加のビルド時間オーバーヘッドを発生させます。128次元ベクトルの場合、ビルド時間は行数と共に増加します。SQ対FLATでは、ビルドが最大約10倍遅くなる可能性があります。

同様に、DorisはProduct Quantizationもサポートしていますが、PQを使用する場合は追加のパラメータを提供する必要があることに注意してください:

  • pq_m:元の高次元ベクトルを分割するサブベクトルの数を示します(ベクトル次元dimはpq_mで割り切れる必要があります)。
  • pq_nbits:各サブベクトル量子化のビット数を示し、各部分空間コードブックのサイズを決定します。faissではpq_nbitsは一般的に24以下である必要があります。

PQ量子化は学習中に十分なデータを必要とし、学習ポイントの数はクラスタ数以上である必要があることに注意してください(n >= 2 ^ pq_nbits)。

CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="pq", -- Specify using PQ for quantization
"pq_m"="2", -- Required when using PQ, indicates splitting high-dimensional vector into pq_m low-dimensional sub-vectors
"pq_nbits"="2" -- Required when using PQ, indicates the number of bits for each subspace codebook
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);

ANN-SQ-BUILD_COSTS

パフォーマンスチューニング

ベクトル検索は典型的なセカンダリインデックスポイントルックアップのシナリオです。高QPSと低レイテンシのために、以下を考慮してください:

チューニングにより、ハードウェアFE 32C 64GB + BE 32C 64GBで、Dorisは3000+ QPS(データセット:Cohere-MEDIUM-1M)に到達することができます。

クエリパフォーマンス

ConcurrencySchemeQPSAvg Latency (s)P99 (s)CPU UsageRecall
240Doris3340.43990.0713681680.16339982540%91.00%
240Doris SQ INT83188.63590.0747288520.16037019540%88.26%
240Doris SQ INT42818.22910.0846638680.17482681543%80.38%
240Doris brute force3.678725.55487882629.363227973100%100.00%
480Doris4155.72200.1133872710.26108607560%91.00%
480Doris SQ INT83833.11300.1230402140.27691286750%88.26%
480Doris SQ INT43431.05380.1376369950.28163124957%80.38%
480Doris brute force3.678725.55487882629.363227973100%100.00%

Prepared Statementsの使用

最近のembeddingモデルは768次元以上のベクトルを出力することがよくあります。768次元のリテラルをSQLにインラインで組み込むと、パース時間が実行時間を上回る可能性があります。prepared statementsを使用してください。現在DorisはMySQLクライアントのprepareコマンドを直接サポートしていないため、JDBCを使用してください:

  1. JDBC URLでサーバーサイドprepared statementsを有効にする: jdbc:mysql://127.0.0.1:9030/demo?useServerPrepStmts=true
  2. プレースホルダー(?)でPreparedStatementを使用し、それを再利用する。

セグメント数の削減

ANNインデックスはセグメントごとに構築されます。セグメントが多すぎるとオーバーヘッドが発生します。理想的には、ANNインデックス付きテーブルの各タブレットは約5セグメント以下にする必要があります。be.confwrite_buffer_sizevertical_compaction_max_segment_sizeを調整してください(例:両方とも10737418240)。

Rowset数の削減

セグメントの削減と同じ動機:スケジューリングオーバーヘッドを最小化する。各ロードはrowsetを作成するため、バッチ取り込みにはstream loadまたはINSERT INTO SELECTを優先してください。

ANNインデックスをメモリに保持

現在のANNアルゴリズムはメモリベースです。セグメントのインデックスがメモリにない場合、ディスクI/Oが発生します。ANNインデックスを常駐させるために、be.confenable_segment_cache_prune=falseを設定してください。

parallel_pipeline_task_num = 1

ANN TopNクエリは各スキャナーから非常に少ない行を返すため、高いpipelineタスクの並列性は不要です。parallel_pipeline_task_num = 1を設定してください。

enable_profile = false

超レイテンシ敏感なクエリの場合、クエリプロファイリングを無効にしてください。

Python SDK

AIの時代において、Pythonはデータ処理とインテリジェントアプリケーション開発の主流言語となっています。開発者がPython環境でDorisのベクトル検索機能をより簡単に使用できるようにするため、一部のコミュニティ貢献者がDoris用のPython SDKを開発しています。

使用制限

  1. ANNインデックス列は NOT NULL Array<Float>である必要があり、インポートされる全てのベクトルは宣言されたdimと一致する必要があります。そうでない場合、エラーが発生します。

  2. ANNインデックスはDuplicateKeyテーブルモデルでのみサポートされています。

  3. Dorisはpre-filterセマンティクス(ANN TopNの前に述語を適用)を使用します。述語に行を正確に特定できるセカンダリインデックスのない列(例:inverted indexなし)が含まれている場合、Dorisは正確性を保持するためbrute forceにフォールバックします。 例:

    SELECT id, l2_distance_approximate(embedding, [xxx]) AS distance
    FROM sift_1M
    WHERE round(id) > 100
    ORDER BY distance LIMIT 10;

idはキーですが、セカンダリインデックス(転置インデックスなど)がなければ、その述語はインデックス解析後に適用されるため、Dorisはpre-filterセマンティクスを満たすためにブルートフォースにフォールバックします。

  1. SQLの距離関数がインデックスDDLで定義されたmetric typeと一致しない場合、l2_distance_approximate / inner_product_approximateを呼び出してもDorisはTopNにANNインデックスを使用できません。
  2. metric type inner_productの場合、ORDER BY inner_product_approximate(...) DESC LIMIT N(DESCが必要)のみがANNインデックスによって高速化されます。
  3. xxx_approximate()の最初のパラメータはColumnArrayでなければならず、2番目はCASTまたはArrayLiteralでなければなりません。これらを逆にするとブルートフォース検索が実行されます。