TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

自作の埋め込みサーバーをDynamoの埋めで込みワーカーに置き換えることはできるか

ステータス:ドラフト(2026-11-15)、実測。 本稿の数値は2026-11-15に筆者が直接測定した値です。Kubernetes v1.34、Dynamo v1.6.2の vllm-runtime:1.6.2 イメージ(vLLM 0.31.0)、モデルQwen3-Embedding-0.6B、8GB級ノートPC用GPU1基です。比較対象は汎用製品ではなく筆者が自作したサーバーの1つであるため、数値をそのまま他のサーバーに移行することはできません。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」、確認できなかったものは「未確認」と表示します。

埋め込みサーバーをDynamoに移行してみる

目次

1. なぜ埋め込みなのか

第3部と 第4部ではテスト用チャットモデルで測定しました。今回は実際に使用しているサービスにDynamoを適用してみました。しかし、使用しているサービスを詳しく見るとチャットモデルではありませんでした。検索(RAG)に使用する埋め込み、再ランク付け、そして音声認識を1つのプロセスが1枚のGPUで処理する、自作のFastAPIサーバーでした。

このサーバーではDynamoの代表機能が役に立ちません。埋め込みはトーク数を生成しないため、プレフィックスキャッシュも、KVルーティングも、分離サービングも該当しません。Dynamoのコードも同じことを示しています。埋め込みワーカーのヘルプには、KVイベントとKVルーターの登録をスキップすると記載されています(v1.6.2、 components/src/dynamo/vllm/backend_args.py).

そのため、問いが変わります。 自作サーバーを撤去してDynamo埋め込みワーカーに置き換えることができるか、そしてそうした場合何が変わるのかということです。

2. 構成

DynamoのvLLMバックエンドは --embedding-worker 引数により埋め込み専用ワーカーになります。vLLM側には --runner pooling を併せて渡します。

command: ["python3", "-m", "dynamo.vllm"]
args:
  - --model
  - Qwen/Qwen3-Embedding-0.6B
  - --embedding-worker
  - --runner
  - pooling
  - --max-model-len
  - "1024"
  - --gpu-memory-utilization
  - "0.4"

グラフはフロントエンド1つとワーカー1つです。モデルは既存サーバーが使用しているものと同じリビジョンをあらかじめ取得しておき、ワーカーは HF_HUB_OFFLINE=1 で起動しました。 --max-model-len 1024 は既存サーバーの入力制限に合わせた値です。

比較の手順は以下の通りです。

  1. 既存サーバーにリクエストを送信し、ベクトルとレイテンシを収集します(3回)。
  2. 既存サーバーを停止し、同じGPUにDynamoグラフをデプロイします。
  3. 同じリクエストをDynamoフロントエンドに送信します(3回)。
  4. グラフを停止し、既存サーバーを復旧させます。

リクエストは文書40件(韓国語と英語、1文書あたり平均84トークン)を8件ずつまとめたものです。既存サーバーのクライアントが実際に8件ずつまとめて送信するためです。レイテンシはシーケンシャル30回と並行度4で40回測定し、3回繰り返しのの中央値を使用します。

3. ベクトルは同一だった

項目 既存サーバー Dynamo埋め込みワーカー
次元 1024 1024
最初のベクトルの長さ(L2) 1.0 1.0
文書40件のトークン数合計 3,375 3,375
文書ごとのコサイン類似度 最小0.9996 · 中央0.9998 · 最大0.9999

トークン数が正確に一致し、ベクトルは小数点第4位で分かれます。既存サーバーはtransformersで直接推論し、Dynamo側はvLLMで推論するため、演算順序と精度処理が異なります。検索順位を変えるほどの差であるかは別途測定していません(未実測)。インデックスを作り直さずに混用してよいかは、この数値だけでは断定できません。

4. そのままでは置き換えられなかった

両方のサーバーとも /v1/embeddings を開きます。名前が同じであるためアドレスを変更するだけで済みそうに見えますが、同じリクエストに対する応答が異なっていたものが4つありました。

リクエスト 既存サーバー Dynamo埋め込みワーカー
dimensions: 256 200、256次元 500
input_type: "query" (クエリ用インストラクション) 200、インストラクションを付与して28トークン 200, 無視して9トークン
制限を超える長い入力 200、1,024トークンに切り詰め 500
encoding_format: "base64" 無視して配列 base64文字列

dimensions. ワーカーのログに原因が残りました。vLLMがこのモデルをMatryoshka埋め込みをサポートしていないモデルと見なし、 dimensions が存在するとリクエストを拒否します。既存のクライアントはすべてのリクエストに dimensions を入れて送信します。値がモデルの本来の次元と同じであっても「存在してはならない」という検証にひっかかるため、クライアントを修正しない限りリクエストはすべて失敗します。vLLMにはモデルをMatryoshkaとして扱うオーバーライドオプションがあると認識していますが、本稿では検証していません(未実測)。

クエリ用インストラクション。 こちらの方がより危険です。エラーが発生しないためです。Qwen3-Embeddingはクエリの前にインストラクションを付与するように作られたモデルであり、既存サーバーは input_type や instruction フィールドを受け取ってその処理を代行します。Dynamo側は未知のフィールドを破棄し、文字列そのままを埋め込みます。同じクエリで両側が出力したベクトルのコサイン類似度は 0.82 でした。インストラクションを除いて比較すると0.9995です。アドレスを変更するだけで全てのリクエストが200で返ってくるものの、検索品質は静かに低下します。インストラクションはクライアントが文字列として付与して送信する必要があります。

長い入力。 既存サーバーは制限で切り捨て、vLLMは制限を超えると拒否します。どちらが正しいとは言い難いですが、もしクライアントが切り捨て処理をサーバーに任せていた場合、その処理を引き継ぐ必要があります。

エラーコード。 dimensions と長い入力はどちらもクライアントのリクエストが不正な場合ですが、フロントエンドは Internal server error とともに500を返しました。ワーカーのログにはvLLMの検証エラーが原因とともに残っていました。500はリトライ対象として扱うクライアントが多いため、このままだと修正不可能なリクエストを継続して再送し続けることになります。v1.6.2で見られた動作であり、最新版で変更されたかは未確認です。

5. レイテンシとスループット

同一GPU(8GB級)で測定した値です。バッチサイズ8のリクエスト1件あたりのレイテンシであり、3回繰り返しの1の中央値です。エラーは両者とも0件でした。

シーケンシャル p50 シーケンシャル p99 並行度4 p50 並行度4 p99 並行度4 スループット
既存サーバー 54.3ms 76.0ms 225.9ms 245.9ms 141.0文書/s
Dynamo埋め込みワーカー 36.9ms 40.5ms 77.4ms 108.9ms 395.8文書/s

シーケンシャルレイテンシは0.68倍、並行度4のスループットは2.8倍です。繰り返しごとのスループットは既存サーバーが139.4・141.0・141.2、Dynamoが409.1・395.8・351.9文書/sでした。

並行度4でこのような結果になる原因は、既存サーバーの構造にあると考えられます。既存サーバーは推論をグローバルロック1つでラップし、一度に1つのリクエストのみを処理します。並行度4のレイテンシ(226ms)がシーケンシャル(54ms)の約4倍であることがその名残です。vLLMは同時に流入したリクエストを1つのバッチにまとめます。ただし、ロックを解除したりバッチ処理を無効にして別途測定したわけではないため、この説明は確認されたものではなく数値に合致した推測です。

この表を読む際に注意すべき点が3つあります。

6. 移行できないもの

既存サーバーが行っている処理は3つで、本稿で移行を試みたのはそのうちの1つです。

機能 Dynamo v1.6.2のvLLMバックエンド 本稿における扱い
埋め込み --embedding-worker 実測
再ランク付け 再ランク付けハンドラーはSGLangバックエンド側のコードでのみ確認しました 未実測
音声認識 リアルタイム文字起こしハンドラーがコードに存在します 未実測

埋め込みだけを移行すると、サーバーが1つから2つに増えます。既存サーバーは3つのモデルを1つのプロセスに載せて8GBカード1枚を共有していますが、埋め込みを切り離すとそのカード上でプロセスが2つになり、フロントエンドポッドももう1つ生成されます。ワーカーが1台だけの場合、Dynamoフロントエンドが行う処理はリクエストをその1台に転送することだけです。

7. まとめ

シリーズのこれまでの記事と合わせて見ると以下のようになります。KVルーティングはプレフィックスが重複するチャットリクエストにおいて効果を発揮します(第3部)。分離サービングには高速なネットワークと大規模なモデルが必要です(第4部)。埋め込みのように生成を伴わない処理にはどちらも該当せず、残る疑問はAPIコントラクトが一致しているかという点になります(本稿)。

参考

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます