💾

[翻訳] ディスクベースのベクトル検索でコストを削減する

に公開

https://opensearch.org/blog/reduce-cost-with-disk-based-vector-search/

ベクトル検索は、自然言語埋め込みモデルの進歩により、情報検索の分野で大きな注目を集めています。これらのモデルは、ドメイン固有のデータをベクトル空間にマッピングし、類似したデータが互いに近くに配置されます。検索を実行すると、クエリがこの空間に埋め込まれ、最近傍探索によって距離計算に基づいて最も類似した結果が特定されます。

ベクトルは、数値 (float、byte、または bit) を格納する固定次元の配列です。ベクトルの形状とベクトルを比較するために使用される関数は、埋め込みモデルによって決定されます。例えば、テキストのチャンクを Cohere v3 多言語埋め込みモデル に渡すと、モデルはベクトル空間内のテキストを表す 1,024 次元の 32 ビット浮動小数点数のベクトルを返します。

ベクトルレイアウト

類似性を判定するために、ドット積やユークリッド距離などの関数を使用してベクトル間の「距離」が計算されます。距離関数の選択は、通常、埋め込みを生成するモデルによって決まります。ベクトル検索はデータの暗黙的な意味を捉えることに優れており、多くの場合、より正確な結果につながります。さらに、ベクトル検索と従来のテキストベースの検索を組み合わせることで、検索品質をさらに向上させることができます。

ベクトル検索のメモリフットプリント

ベクトルベースのインデックスは非常に大きくなる可能性があります。例えば、10 億個の 768 次元浮動小数点埋め込みを格納するには、約 1000^3 * 768 * 4 ≒ 2861 GB のストレージが必要です。レプリカを有効にし、追加のメタデータを追加すると、このコストはさらに増加します。従来、多くの効率的な最近傍アルゴリズムでは、検索にランダムアクセスパターンが含まれるため、高速検索を可能にするためにベクトルが完全にメモリに常駐している必要があります。これにより、ディスクからベクトルを効率的に取得することが困難になり、ベクトル数が増加するにつれてコストが高くなります。

ベクトル数とメモリフットプリントの関係

ベクトルインデックスのメモリフットプリントを削減するために、通常はベクトル量子化技術が採用されます。量子化は、情報損失を最小限に抑えながら、ベクトルをより小さな表現に圧縮します。例えば、FP16 量子化 は 32 ビット浮動小数点数を 16 ビット浮動小数点数に変換し、メモリ使用量を半分に削減します。多くのデータセットは完全な 32 ビット空間を必要としないため、このアプローチでは精度の低下はほとんど見られません。

FP16 に加えて、量子化を使用してベクトルをさらに圧縮でき、圧縮率は 4x8x16x32x 以上になります。メモリ使用量をさらに削減することで、大幅なコスト削減につながります。

ただし、量子化にはトレードオフがあります。圧縮を強くするほど、検索精度が低下する可能性が高くなります。幸いなことに、セカンダリストレージの改善により、低メモリ環境でも検索レイテンシをある程度犠牲にして正確な最近傍検索を行うことが可能になりました。

OpenSearch におけるディスクベースのベクトル検索

2.17 では、knn_vector フィールドに on_disk モードを導入し、OpenSearch でディスクベースのベクトル検索を可能にしました。これにより、低メモリ環境でも検索を実行できます。これは、以下の図に示す 2 フェーズクエリアプローチを使用して実現されています。

ハイレベルアーキテクチャ

まず、インジェスト時に、設定可能な量子化メカニズムを使用してインデックスを構築し、フル精度のベクトルをディスクに保存します。量子化されたインデックスは、圧縮により検索時に完全にメモリに常駐しますが、必要なメモリは大幅に少なくなります。

検索時には、まず量子化されたインデックスをクエリして k 個より多くの候補結果を取得します。次に、これらの候補のフル精度ベクトルを遅延ロードしてメモリに読み込み、クエリとの距離を再計算します。この再順序付けステップにより精度が確保され、上位 k 件の結果のみを返します。

経験的に、この 2 フェーズアプローチは、さまざまな量子化技術で高い再現率の結果を生成できることが実証されています。

オンラインバイナリ量子化の導入

この機能の一部として、量子化サポートも拡張しました。以前は、OpenSearch で 8 倍以上の圧縮を達成するには、プロダクト量子化を使用する必要がありました。プロダクト量子化は効果的ですが、インジェストを開始する前にトレーニングステップが必要であり、ユーザーがモデルを管理する必要があります。

2.17 では、8 倍、16 倍、32 倍の圧縮に対応する新しいオンライン量子化技術を導入しました。事前トレーニングは不要です。追加のモデル準備なしで、すぐにインジェストを開始できます。 これにより、プロセスが簡素化され、オーバーヘッドが削減されます。

次の 8 次元の 32 ビット浮動小数点数ベクトルを考えてみましょう。

v1 = [0.56, 0.85, 0.53, 0.25, 0.46, 0.01, 0.63, 0.73]
v2 = [-0.99, -0.79, 0.23, -0.62, 0.87, -0.06, -0.24, -0.75]
v3 = [-0.15, 0.17, 0.10, 0.46, -0.79, -0.31, 0.36, -1.00]

これらを量子化するには、以下の手順を実行します。

1. 次元ごとの平均を計算

各次元 j の平均は、以下の式を使用して計算されます。

平均の計算式

上記のベクトルセットから、以下の平均が計算されます。

Mean = [-0.19, 0.08, 0.29, 0.03, 0.18, -0.12, 0.25, -0.34]

2. 新しいベクトルの量子化

ベクトルの各次元 j の量子化ルールは、以下のロジックで与えられます。

量子化ルール

新しいベクトルを考えてみましょう。

v_new = [0.45, -0.30, 0.67, 0.12, 0.25, -0.50, 0.80, 0.55]

このベクトルを量子化するには、各次元を平均値と比較します。次元が平均を超える場合は 1 に変換し、そうでない場合は 0 に変換します。

量子化の例

ディスクベースのベクトル検索を試す

ディスクベースのベクトル検索を設定するには、インデックスマッピングで modeon_disk に設定するだけです。このパラメータは、再スコアリングが有効な低メモリ設定を自動的に構成します。

PUT my-vector-index
{
  "settings" : {
    "index.knn": true
  },
  "mappings": {
    "properties": {
      "my_vector_field": {
        "type": "knn_vector",
        "dimension": 8,
        "space_type": "innerproduct",
        "mode": "on_disk"
      }
    }
  }
}

量子化を調整するには、マッピングで compression_level を指定できます。on_disk モードのデフォルトは 32x です。

PUT my-vector-index
{
  "settings" : {
    "index.knn": true
  },
  "mappings": {
    "properties": {
      "my_vector_field": {
        "type": "knn_vector",
        "dimension": 8,
        "space_type": "innerproduct",
        "mode": "on_disk",
        "compression_level": "16x"
      }
    }
  }
}

検索時に modeon_disk に設定されている場合、OpenSearch は自動的に 2 フェーズ検索プロセスを適用します。

GET my-vector-index/_search
{
  "query": {
    "knn": {
      "my_vector_field": {
        "vector": [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5],
        "k": 5
      }
    }
  }
}

on_disk モードを使用すると、OpenSearch は量子化インデックス検索から k 個より多くの結果を返すためのオーバーサンプル係数を設定します。クエリ本文で oversample_factor を指定することで、このデフォルト設定をオーバーライドできます。

GET my-vector-index/_search
{
  "query": {
    "knn": {
      "my_vector_field": {
        "vector": [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5],
        "k": 5,
        "rescore": {
            "oversample_factor": 10.0
        }
      }
    }
  }
}

詳細については、ディスクベースのベクトル検索 を参照してください。

実験

ディスクベースのベクトル検索のパフォーマンスを評価するために、さまざまなデータセットサイズと構成を使用していくつかの実験を実施しました。

100 万ベクトルテスト

100 万から 1,000 万ベクトルを含むデータセットに対して、シングルノード OpenSearch クラスター でいくつかの異なるテストを実行しました。

名前 空間タイプ 正規化 次元 インデックスベクトル数 備考
sift l2 なし 128 1,000,000 古典的な SIFT (Scale-Invariant Feature Transform) 画像記述子データセット
minillm-msmarco l2 あり 384 1,000,000 minillm を使用して ms-marco のサンプルをエンコード
mpnet-msmarco l2 あり 768 1,000,000 mpnet を使用して ms-marco のサンプルをエンコード
mxbai-msmarco cosine あり 1,024 1,000,000 mxbai を使用して ms-marco のサンプルをエンコード
tasb-msmarco ip なし 768 1,000,000 tasb を使用して ms-marco のサンプルをエンコード
e5small-msmarco l2 あり 384 8,841,823 e5small を使用して ms-marco のより大きなサンプルをエンコード
sNowflake-msmarco l2 あり 768 8,841,823 snowflake-arctic-embed-m を使用して ms-marco のより大きなサンプルをエンコード
clip-flickr l2 なし 512 6,637,685 clip モデルを使用して Flickr からのランダムな画像サンプルをエンコード

1x2x4x8x16x32x の圧縮レベルで in_memory モードと on_disk モードを比較する実験を実施しました。主なテストパラメータは以下の通りです。

  • on_disk モード: 再スコアリングステージを含む
  • in_memory モード: 再スコアリングステージを含まない
  • ハードウェア構成:
    • in_memory テスト: Amazon EC2 r6g.4xlarge インスタンス
    • on_disk テスト: Amazon EBS ストレージではなく、SSD が接続された Amazon EC2 r6gd.4xlarge インスタンス
  • リソース制御: Docker を使用して OpenSearch のメモリと CPU 割り当てを管理

以下の結果が得られました (再現率と圧縮レベルの比較)。

100 万ベクトルテスト結果

結果は、量子化とフル精度の再スコアリングを組み合わせることで、低メモリ環境でも妥当なレイテンシを維持しながら再現率を大幅に向上できることを示しています。ただし、パフォーマンスはデータセットによって異なります。例えば、このアプローチは sift データセットのパフォーマンスを向上させますが、再現率は比較的低いままです。最適な構成を決定するために、特定のデータセットでテストすることをお勧めします。

大規模テスト

小規模テストに加えて、より大きなデータセット Cohere v3 モデルでエンコードされた MS MARCO 2.1 でもテストを実行しました。8x16x32x の圧縮レベルで、このディスクベースのベクトル検索が in_memory ベクトル検索とどのように比較されるかを示すために、いくつかの異なるテストを設定しました。

OpenSearch 2.18 で opensearch-cluster-cdk を使用して 4 つの異なるクラスター構成をテストしました。本番環境の推奨事項に従ってクラスターとインデックス構成を選択しました。例えば、レプリカシャードと専用クラスターマネージャーノードを構成しました。また、シャードあたり 2〜3 vCPU を提供するシャード数を目標としました。

名前 データノード数 データノードタイプ データノードディスクサイズ データノードディスクタイプ JVM サイズ プライマリシャード数 レプリカシャード 圧縮レベル
in_memory 8 r6g.8xlarge 300 EBS 32 40 1 1x
on_disk_8x 10 r6gd.2xlarge 474 Instance 32 15 1 8x
on_disk_16x 6 r6gd.2xlarge 474 Instance 32 9 1 16x
on_disk_32x 4 r6gd.2xlarge 474 Instance 32 6 1 32x

表に示すように、圧縮レベルが高いクラスターは、圧縮なしのクラスターよりも大幅に少ないリソースを使用します。

テストでパフォーマンスを最適化するために、以下の追加調整を行いました。

  • フェッチ時間を短縮するために doc_values から ID を取得
  • _source ストレージを無効化

すべてのテストは OpenSearch Benchmark のベクトル検索ワークロードを使用して、以下の手順で実行しました。

  1. データセットをインジェスト
  2. インデックスをシャードあたり 5 セグメントに強制マージ
  3. ウォームアップクエリを実行してインデックスをメモリにロード
  4. シングルクライアントレイテンシテストを実行
  5. マルチクライアントスループットテストを実行
  6. ディスクベースの再スコアリングを無効にして手順 4 と 5 を繰り返し、再スコアリングなしの圧縮インデックスでのパフォーマンスを測定

以下の大規模テスト結果が得られました。

メトリクス/構成 in-memory on_disk_8x in_memory_8x on_disk_16x in_memory_16x on_disk_32x in_memory_32x
recall@100 (比率) 0.95 0.98 0.98 0.97 0.96 0.94 0.95
1 クライアント p90 検索レイテンシ (ms) 24.02 96.31 28.90 108.05 29.79 104.421 47.2906
1 クライアント平均スループット (QPS) 40.64 11.03 42.33 10.06 43.95 10.65 31.83
4 クライアント p90 検索レイテンシ (ms) 25.82 97.19 20.40 220.19 18.09 244.52 46.13
4 クライアント平均スループット (QPS) 162.80 45.86 204.62 25.80 193.27 25.28 146.77
8 クライアント p90 検索レイテンシ (ms) 27.69 95.05 30.88 414.20 27.94 429.79 25.07
8 クライアント平均スループット (QPS) 306.70 95.22 305.86 26.34 343.95 25.75 376.60

興味深いことに、このデータセットでは、再スコアリングを使用した on_disk アプローチは、再スコアリングなしの in_memory アプローチと同様の再現率を生成しますが、in_memory アプローチの方が大幅に高速です。これは、Cohere v3 モデルがバイナリ量子化データで非常にうまく動作するように最適化されているためと考えられます (この記事 を参照)。

学び

テストの結果、2 フェーズ近似最近傍アプローチは低メモリ環境で効果的に機能しますが、結果はデータセットによって大きく異なることがわかりました。独自の実験を実行する際は、index.knn.disk.vector.shard_level_rescoring_disabled 設定を有効と無効の両方でテストして、ユースケースに対するパフォーマンス上の利点を測定することをお勧めします。また、ディスクベースの検索では、セカンダリストレージが高い読み取りトラフィックに最適化されていることを確認してください。SSD が一般的に最良の結果を提供することがわかりました。

今後の予定

OpenSearch のベクトル検索には、多くの新しいエキサイティングな機能が予定されています。将来のバージョンでは、すべてのデータセットに対する量子化パフォーマンスの向上に焦点を当て、微調整の必要性を排除します。パフォーマンスと機能の継続的な改善については、GitHub リポジトリ をフォローしてください。いつものように、皆様からの貢献と機能リクエストを歓迎します!

OpenSearch Project

Discussion