メインコンテンツへ移動
AIチュートリアル

TensorFoldで2台のDGX SparkにGLM-5.3-Flashを分散実行

2台のNVIDIA DGX SparkでGLM-5.3-Flash EXL3をインストール、設定、起動、テスト、チューニングする方法を解説します。このチュートリアルでは、RoCE接続、自動セットアップ、OpenAI互換リクエスト、画像・動画入力、推論制御、共有KVキャッシュの動作、モニタリング、高度な設定について説明します。

TensorFoldで2台のDGX SparkにGLM-5.3-Flashを分散実行

このプロジェクトの概要

このプロジェクトは、TensorFold v0.5.0を使用し、2台のNVIDIA DGX SparkシステムにまたがってGLM-5.3-Flashを提供します。各Sparkは分散ランクを1つ実行し、1台目のSparkはOpenAI互換HTTP APIもホストします。

デフォルト構成では、4件の同時リクエスト、リクエストあたり1,048,576トークンの最大コンテキストウィンドウ、画像と動画の入力、ツール呼び出し、構造化出力、トークナイズ用エンドポイント、Prometheusメトリクスに対応します。2台のシステム間の通信には、直接接続したConnectX-7 RoCEリンクを使用します。

このデプロイでは、Mia-AiLab GLM-5.3-Flash EXL3チェックポイントを使用します。ルーティングされるエキスパートは重みあたり約4ビットで保存され、その他のチェックポイントの重みはBF16です。チェックポイント全体の容量は約176 GBです。

主な機能

  • OpenAI互換API: チャット補完、補完、レスポンス、モデル一覧取得の各エンドポイントを使用できます。
  • 長いコンテキスト: デフォルトのFP8 KV構成では、個々のリクエストで最大1,048,576個のプロンプトと返信のトークンを処理できます。
  • 同時推論: デフォルトのDFlash2ドラフターを使用して、最大4件のリクエストを同時にデコードできます。
  • 共有キャッシュプール: 同時実行中の会話は、利用可能なメモリに応じて約210万~270万トークンを格納する1つのFP8 KVプールを共有します。
  • マルチモーダルリクエスト: JPEG、PNG、WebP、MP4、WebM、MOV、MKVなど、FFmpegでデコード可能な動画入力を送信できます。
  • 推論制御: リクエストごとに低・高・最大の推論負荷を選択するか、思考を無効にできます。
  • エージェント機能: xgrammarを介して、ツール呼び出しとスキーマ制約付き構造化出力を利用できます。
  • 運用エンドポイント: ヘルス状態の確認、入力のトークナイズまたはデトークナイズ、Prometheusメトリクスの収集ができます。
  • 自動デプロイ: 1つのコマンドで両方のマシンを準備し、両方のランクを起動し、スモークテストを実行して、APIアドレスを表示します。

ハードウェアとソフトウェアの要件

DGX Sparkを2台、またはそれぞれ128 GBのユニファイドメモリを搭載したGB10システムを2台用意する必要があります。サーバー起動時には各GPUで約110 GiBの空き容量が必要なため、先に他の大規模なGPUワークロードを停止してください。

ネットワーク要件

ConnectX-7ポートをQSFPケーブルで接続し、各エンドポイントに同じプライベートサブネット内のIPv4アドレスを割り当てます。たとえば、ヘッドには192.0.2.1/24、ワーカーには192.0.2.2/24を使用できます。各アドレスにはpingで到達でき、RoCE v2 GIDが利用可能でなければなりません。

両方のCX7ポートが接続され、アドレスが設定されている場合、プロジェクトは両方のレールを使用できます。READMEの測定結果によると、2レール使用時はプロンプトチャンクのall-gatherが約1.8倍高速になります。

SSHとシステム要件

  • ヘッドSparkからワーカーへの鍵ベースSSHアクセス。
  • 両方のシステムにDockerとNVIDIAコンテナランタイム。
  • 両方のシステムで、使用するアカウントがdockerグループに所属していること。
  • 両方のシステムにrsyncがインストールされていること。
  • 各Sparkに約205 GBのディスク容量。
  • 任意で、ヘッドにHugging Faceのhf CLIとHugging Faceトークン。

必要に応じてSSHキーを作成し、ワーカーにコピーして、非対話型アクセスを確認します。

ssh-keygen -t ed25519
ssh-copy-id user@192.0.2.2
ssh -o BatchMode=yes user@192.0.2.2 true

DFlash2ドラフターはCC BY-NC-ND 4.0でライセンスされており、非商用利用に限られます。DFlash2のダウンロードまたは使用を避ける必要がある場合は、最初のセットアップ前にチェックポイントのMTPドラフターを選択してください。

プロジェクトのインストールと設定

1. ヘッドでリポジトリをクローンする

git clone https://github.com/MiaAI-Lab/GLM-5.3-Flash-EXL3-2x-DGX-Sparks-TensorFold.git
cd GLM-5.3-Flash-EXL3-2x-DGX-Sparks-TensorFold
cp scripts/local.sh.example scripts/local.sh

ワーカー側に独自のチェックアウトは必要ありません。セットアップスクリプトが、必要なコンテナイメージとモデルファイルをヘッドから転送します。

2. ワーカーのアドレスを設定する

scripts/local.shを編集し、WORKERにワーカーのSSH接続先を設定します。

WORKER=user@192.0.2.2

分散ランクは、ワーカーへの接続に使用されるルートを介して通信します。WORKERが別のネットワーク上のアドレスを参照する場合は、FABRIC_PEERにワーカーのプライベートCX7アドレスを設定します。

WORKER=user@worker-hostname
FABRIC_PEER=192.0.2.2

3. 必要に応じてドラフターを選択する

デフォルトはDRAFTER=dflash2です。最大4件の同時リクエストに対応し、測定上およそ5~10パーセントのデコード性能向上が得られます。代わりにチェックポイントのMTPヘッドを使用する場合は、最初の起動前に次のように設定します。

DRAFTER=mtp

MTPを使用するとDFlash2のライセンスとダウンロードを回避できますが、デフォルトでは一度に1件のリクエストのみを処理します。

4. 両方のランクを起動する

./start.sh

初回実行時、スクリプトは両方のシステムを確認し、約25 GBのコンテナイメージを取得またはビルドし、ヘッドにチェックポイントとドラフターをダウンロードしてワーカーにコピーし、CUDAカーネルを~/.cache/tensorfold-glm53にコンパイルします。

以降の起動では、通常、各Sparkに約80 GiBの重みを読み込むために2~6分かかります。ランチャーはランク0のログを追跡して読み込みの進捗を表示し、分散スモークテストを実行して、ポート8888で稼働中のエンドポイントを表示します。

APIを起動せずに両方のシステムを準備するには、次を実行します。

scripts/prepare.sh

イメージを完全に再ビルドするには、次を使用します。

scripts/prepare.sh --rebuild

最初のAPIリクエストを送信する

デフォルトのモデル識別子は GLM-5.3-Flash-EXL3、ベースURLは http://<head-address>:8888/v1 です。

利用可能なモデルを一覧表示する

curl -s http://<head-address>:8888/v1/models

チャット補完を送信する

curl -s http://<head-address>:8888/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "GLM-5.3-Flash-EXL3",
    "messages": [
      {"role": "user", "content": "Write a Python fibonacci function."}
    ],
    "max_tokens": 2000
  }'

デフォルトでは、モデルは回答前に思考します。推論は reasoning_content に返され、最終回答は content に現れます。両方の段階に十分な出力トークンを割り当ててください。content が空の場合、モデルが推論中に出力上限を使い切った可能性があります。

推論とサンプリングを制御する

reasoning_effort を low、high、または max に設定します。思考せず直接回答させるには none を使用します。

curl -s http://<head-address>:8888/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "GLM-5.3-Flash-EXL3",
    "messages": [{"role": "user", "content": "Summarize TCP in two sentences."}],
    "reasoning_effort": "low",
    "temperature": 0,
    "max_tokens": 1000
  }'

APIは temperature、top_p、top_k、min_p、seed も受け付けます。明示的なシードを指定しない場合、サンプラーはプロンプトからキーを導出するため、同一のリクエストには同じ回答が返されます。

画像と動画を送信する

デフォルトでは、GLMのビジョンタワーはランク0で実行されます。画像と動画は、ユーザーメッセージ内でOpenAI形式のコンテンツパーツとして表現します。

ローカル画像を解析する

IMG=$(base64 -w0 photo.jpg)
curl -s http://<head-address>:8888/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "GLM-5.3-Flash-EXL3",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,'"$IMG"'"}},
        {"type": "text", "text": "What is in this picture?"}
      ]
    }],
    "max_tokens": 2000
  }'

デフォルトでは、サーバーはデータURL経由でメディアを受け付けます。公開HTTPS URLも取得させる場合は VISION_URLS=1 を設定してください。

対応メディアの上限

  • 1リクエストあたり最大50枚の画像。1枚あたり10 MB、画像データ全体で64 MBまで。
  • 1リクエストあたり最大4本の動画。1本あたり64 MB、動画データ全体で96 MBまで。
  • 動画は1本あたり最大1時間の映像を含められます。
  • リクエスト本文は最大96 MiBで、base64エンコードされたメディアにはおよそ70 MBを割り当てられます。
  • 1枚の画像には最大2,048トークンが割り当てられ、1リクエスト内の全画像でデフォルトの16,384トークン予算を共有します。
  • 動画のサンプリングはデフォルトで毎秒2フレーム、クリップ全体で最大128フレームです。

動画の場合は、画像コンテンツパーツを data:video/mp4;base64,... のようなデータURLを含む video_url パーツに置き換えてください。

テキストだけが必要な場合は VISION=0 を設定します。これによりビジョンタワーの読み込みを回避し、ランク0でキャッシュに使えるメモリが約1.8 GiB増えます。

サーバーを運用・監視する

再起動と停止

./start.sh restart
./stop.sh

永続設定を変更した後は再起動が必要です。サービスを停止すると、両方のマシンでGPUメモリが解放されます。

ログを確認する

docker logs -f glm53-flash-tf
ssh <worker> docker logs -f glm53-flash-tf

1つ目のコマンドはヘッド上のランク0を追跡します。2つ目はランク1を追跡し、ワーカーが予期せず終了した場合に特に役立ちます。

ヘルスとメトリクスを確認する

curl -s http://<head-address>:8888/health
curl -s http://<head-address>:8888/metrics

ヘルスレスポンスには、ビジー状態、アクティブなストリーム、共有プール容量(pool_tokens と pool_free_tokens を含む)が報告されます。メトリクスエンドポイントは、Prometheus向けのリクエスト数、レイテンシ、初回トークン到達時間、ヘルス指標を公開します。

コンテキストと共有KVメモリを理解する

デフォルト設定ではFP8 KVキャッシュを使用し、各リクエストに最大1,048,576トークンのウィンドウを割り当てます。4つのリクエストは、ある記録済みの起動時点で2,684,928トークンのプールを共有しますが、正確な容量は空きメモリによって異なります。

1つのリクエストでウィンドウ全体を使用できますが、アクティブな会話と保持された会話はすべて共通プールに収まる必要があります。たとえば、2つの会話でそれぞれ100万トークン近くまで使用でき、短い同時実行の会話で残りの容量を共有できます。まだ配置できないリクエストは、他のリクエストが完了するまで待機します。

収まる最大ウィンドウを計算するには CONTEXT=0 を使用します。ただし、これにより他の会話のプロンプト状態を保持するメモリがなくなる点に注意してください。正確なBF16 KVストレージに切り替えると、利用可能なウィンドウは大幅に縮小します。

共有メモリの枯渇は、明確な割り当て失敗を返す代わりにSparkをフリーズさせる可能性があります。メモリのトレードオフを理解している場合を除き、デフォルトの予約容量を維持してください。他のワークロードがマシンを共有する場合は MEMORY_RESERVE_GIB を増やします。

高度な設定

設定は環境変数、scripts/local.sh、または start.sh と同じ場所にある .env ファイルから指定できます。優先順位は、環境変数、scripts/local.sh、.env、最後にリポジトリのデフォルト値です。

1回の実行で同時実行数を変更する

PARALLEL=2 ./start.sh restart

PARALLELは、DFlash2が有効な場合に1から4まで設定できます。MTPのデフォルトは、一度に1リクエストです。

コンテキストウィンドウを変更する

CONTEXT=524288 ./start.sh restart

完全な100万トークンのウィンドウが不要な場合は、コンテキストを小さくするとキャッシュへの負荷を軽減できます。

デフォルトの出力上限を増やす

./start.sh restart --max-tokens 16384

追加のコマンドライン引数は、設定済みのデフォルト値の後にtensorfold serveへ渡されるため、追加引数が優先されます。./start.sh --helpを実行すると、サポートされている引数を確認できます。

準備処理を強制またはスキップする

PREPARE=1 ./start.sh restart
PREPARE=0 ./start.sh restart

最初のコマンドはセットアップの確認と準備処理を強制します。2つ目のコマンドはそれらをスキップします。

プロンプトの再利用を効果的に活用する

共有プレフィックスの再利用は、SHARED_PREFIX=1によってデフォルトで有効になっています。繰り返し使用するシステムプロンプトや、同一の保持済みプロンプトは、再度プリフェッチする代わりにキャッシュ済みの状態から再開できます。記載された測定では、64Kトークンのプロンプトを繰り返し使用した場合、処理時間が34秒から0.07秒未満に短縮され、7.9Kトークンのシステムプロンプトを再利用した場合は、起動時間が4.24秒から0.13秒に短縮されました。

キャッシュと重みの精度を選択する

  • KV=fp8がデフォルトで、最大のコンテキストウィンドウを有効にします。
  • KV=bf16は正確なキャッシュストレージを使用しますが、利用可能なウィンドウが大幅に短くなります。
  • DENSE=q4は、密なチェックポイントコンポーネントのデフォルトです。
  • DENSE=fp8とDENSE=bf16も利用でき、メモリ使用量への影響がそれぞれ異なります。

追加のAPI機能

チャット補完に加えて、サーバーは/v1/completions、/v1/responses、/v1/models、/tokenize、/detokenize、/health、/metricsを公開します。トークン化用のルートは/v1/配下でも利用できます。サーバーはAnthropic互換の/v1/messagesエンドポイントを提供していません。

ツール呼び出しはtoolsとストリーミングtool_callsをサポートし、引数は指定されたスキーマに従って型付けされます。構造化生成では、json_object、json_schema、ガイド付きJSON、正規表現、選択肢、文法、その他の構造化出力フィールドをサポートしており、推論完了後にxgrammarを通じて適用されます。

プロンプトとmax_tokensの合計が設定済みのウィンドウを超える場合、APIはOpenAI形式のエラーコードcontext_length_exceededを含むHTTP 400を返します。

よくある起動問題のトラブルシューティング

  • メモリ不足: 起動時に利用可能なGiB数が限られていると報告された場合は、docker psを確認し、両方のSparksで他のGPUワークロードを停止してください。
  • RoCEデバイスまたはルートがない: ip -4 addrでCX7インターフェースにIPv4アドレスがあることを確認してください。SSHが別のネットワークを使用している場合は、ワーカーのCX7アドレスをFABRIC_PEERに設定してください。
  • ワーカーのキャッシュに書き込めない: 以前のコンテナによってroot所有になっている場合は、ワーカーのHugging Faceキャッシュの所有者を修正してください。
  • 一方のランクが終了する: 両方のマシンのログを確認してください。通常、片側で障害が発生すると、ランチャーは残りのランクも停止します。
  • セットアップを変更した: パッチを変更した場合、またはTensorFold、ベースイメージ、関連するビルド入力を変更した場合は、scripts/prepare.sh --rebuildを実行してください。

パフォーマンスの目安

READMEに記載されたデフォルトの2台のSparkによる測定では、通常の文章デコードの速度は、1リクエスト時の毎秒60.4トークンから、4件の同時リクエスト時の合計毎秒108.8トークンまでの範囲でした。構造化生成では、合計毎秒114.7から227.9トークンでした。プリフィルは、8,219トークンのプロンプトで毎秒約1,952トークン、針が見つかった981,841トークンのneedle-in-a-haystackプロンプトで毎秒1,015トークンでした。

これらの数値は、4ストリーム、FP8 KVキャッシュ、4ビットの密な重み、DFlash2とコピーによるドラフト、ビジョン有効、GPUクロック2,200 MHz上限という条件で測定されたものです。あらゆる環境で保証される値ではなく、この構成における参考結果として扱ってください。

まとめ

このデプロイメントは、複雑な2ノードTensorFold構成を、実用的な起動ワークフローとOpenAI互換APIの背後にまとめています。CX7リンク、SSHアクセス、Dockerランタイム、ストレージ、ワーカーアドレスの準備が整えば、./start.shが準備、分散起動、検証、サービス提供を処理します。その後は標準のAPIクライアントを使用しながら、ワークロードに合わせて同時実行数、コンテキスト、推論、マルチモーダルの制限、メモリ動作を調整できます。