DGX Sparkクラスのボックス2台、モデルは1つ:各マシンにフルコピーを置き、分割を解消
オペレーション
7週間、私たちのローカル言語モデルは、テンソル並列化により2台のNVIDIA GB10マシンに分割して稼働していました。10月22日、これを各マシンにモデルのフルコピーを置き、手前に最小接続数プロキシを配置する構成に置き換えました。スループットは毎秒約180トークンから210トークンに向上し、どちらのマシンを再起動してもモデルを停止させずに済むようになりました。
なぜ分割していたのか
各マシンには、CPUとGPUが共有するメモリが128 GB搭載されています。9月時点ではモデルは1台のマシンに収まりましたが、キャッシュはギリギリでした。使用していたvLLMビルドでは、このモデルのKVキャッシュをFP8で保持できなかったため、キャッシュはBF16となり、1台のマシンには499,110トークン分しか空きがありませんでした。これは、モデルのフルコンテキストである262,144トークンに対して、リクエスト2件分にも満たない容量です。両マシン間の直接ネットワークリンク経由でテンソル並列化により分割した結果、キャッシュは300万トークン以上まで拡大しました。モデルを分割したのは速度のためではなく、容量を確保するためでした。
変更点
同じモデルの新しい4ビットビルド(量子化対応蒸留で作成されたNVFP4)は約71 GiBのメモリを必要とし、GB10向けのコミュニティ版vLLMビルド(eugr/spark-vllm-b12x)ではキャッシュをFP8で実行します。現在、1台でモデル全体と0.76〜1.08百万トークンのキャッシュを保持でき、マシンが割り当てるメモリ量に応じて、フルコンテキストで2.9〜4.1リクエスト分の容量があります。両方のコピーは、モデル独自の推測的デコーディング(MTP、ドラフトトークン4つ)を使用しています。切り替え前に、新しいビルドでツール呼び出しテストを実行したところ、スコアは86で、置き換えた旧ビルドは75でした。
測定結果
| 2台に分割 | 各マシンにコピー | |
|---|---|---|
| KVキャッシュ | 300万トークン以上 | 76万および108万トークン |
| フルコンテキストでのリクエスト数 | 約12 | 2.9および4.1 |
| 負荷時のスループット | 約180トークン/秒 | 210トークン/秒(111 + 99) |
| 起動時間 | 約20分 | コピーあたり5〜13分 |
| 1台の再起動時 | モデルが停止 | もう1つのコピーが対応 |
1コピー単体での同時リクエスト数ごとの結果です。短いプロンプトでは、同時1リクエストで毎秒36トークン、4で82、8で109、16で150でした。メールエージェントの長いプロンプトでは、4で81、8で113、16では87にとどまりました。同時8リクエストを超えると、かえって遅くなります。
午後の時間帯、1つのコピーは最大で同時12リクエスト、もう1つは8リクエストを処理しました。前者は毎秒91〜93トークンを生成し、毎分26〜27リクエストを完了しました。後者は毎秒81〜83トークンで、毎分22リクエストです。現在は両方とも同時12リクエストに設定しています。
諦めたもの
容量。分割したモデルは300万トークン以上のキャッシュを1つ持っていましたが、コピーではそれぞれ小さいキャッシュを2つ持つことになり、リクエストは自身を処理するコピーのキャッシュしか利用できません。当社のエージェントは短〜中程度のリクエストを多数送信するため、スループットの方が重要でした。もし非常に長いコンテキストをいくつか同時に処理する場合は、分割の方が依然として適切な選択かもしれません。当社は分割構成を維持し、元に戻すのはスクリプト1本で済みます。
切り替え中に起きた問題
- モデルなしで11分、その後502エラーが30秒間続発。同じマシン上でコピーをロードできるようにするには、分割したモデルを先に停止する必要があったため、当社のエージェントは約11分間待機しました。最初のコピーが起動した際も、プロキシはさらに約30秒間「502 no live upstreams」を返し続けました。バックエンドがダウンしている間に失敗としてマークされており(
max_fails=3、fail_timeout=30s)、その時間が経過するまで再試行されなかったためです。 - プロキシをバイパスしたクライアント。バッチジョブの1つが設定に1台目のマシンのアドレスを持っていたため、その負荷はすべて1つのコピーに集中し、もう1つには余裕がありました。現在、すべてのクライアントはプロキシ経由で接続し、マシン自身のアドレスはヘルスチェック専用です。
- 急激に変動したメトリクス。スループット監視ツールは、プロキシ経由でvLLMの
/metricsを読み取っていました。バックエンドが2つある状態では、連続した読み取りが異なるコピーから行われ、カウンタが前後に飛び、監視ツールはモデルが枯渇していると報告しました。現在は各コピーから直接読み取り、その値を合算しています。 - 新しいコピーを停止しかねなかった古いウォッチドッグ。分割構成では、モデルが応答しなくなった際にサービスを再起動するウォッチドッグがありました。新しいサービスは古いものに対して
Conflicts=を宣言しているため、この再起動は新しいコピーを停止させることになっていました。有効だが稼働していないユニットを再起動する運用レイヤーがあるため、単に停止するだけでなく、systemctl disable --nowで無効化する必要がありました。 - 他の作業も行うマシン上のメモリ。1台のマシンでデータベースとサイトビルドも実行しています。vLLMの
gpu-memory-utilizationを0.78に設定していたところ、他の処理用に確保できたメモリはわずか5 GiBだったため、0.74に下げ、負荷を分担していたもう一方のコピーを生かしたまま、該当のコピーのみを再起動しました。これらのマシンでは、vLLMに割り当てるメモリは、他のすべての処理と同じメモリプールから確保されます。
これらのマシンを2台お持ちの場合
- モデルと必要なキャッシュが1台のマシンに収まるなら、最少接続プロキシの背後に各マシンでコピーを実行してください。必要なキャッシュを1台で保持できない場合のみ、モデルを分割してください。
- バッチジョブや古い設定も含め、すべてのクライアントをプロキシに向けます。
- 各コピーから直接メトリクスを読み取ります。
- 他に何が実行されているかに応じて、各マシンに独自のメモリ割り当てを与えます。
- 実際のトラフィックに基づき、1つのコピーが同時に処理するリクエスト数を設定します。私たちの環境では、8より12が優れていました。
- 切り替えには、コピーの起動に必要な時間だけモデルが利用できなくなることを想定し、計画に組み込んでください。
Factory Floorには、1台または2台のマシン用のプロキシと起動設定、モデル切り替え前に実行するヘルスチェックとツール使用テスト、そしてマシン全体の残りの部分用のウォッチドッグとメモリガードが含まれています。