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

2台のDGX SparkでQwen3.8 Flash Next 176Bを提供する

このチュートリアルでは、リポジトリの単一ファイルのレシピを使って、2台のNVIDIA DGX SparkノードにQwen3.8-Flash-Next-NVFP4をダウンロード、パッチ適用、提供する方法を説明します。GB10のユニファイドメモリ制限と、モデルに必要なSM121スパースアテンション要件にも対応します。

2台のDGX SparkでQwen3.8 Flash Next 176Bを提供する

概要

このプロジェクトは、RadixArk/Qwen3.8-Flash-Next-NVFP4をSGLangで提供するための、本番運用を想定したデプロイ手順です。このモデルは、約1760億パラメータ、135 GB、NVFP4量子化済みのMixture-of-Expertsモデルです。

付属のstart.shスクリプトは、TP=2のテンソル並列処理を使い、モデルを2台のNVIDIA DGX Sparkシステムに分散します。ノード間は、直接接続されたConnectX-7 200 Gb RoCEv2リンクで通信します。起動すると、ヘッドノードは0.0.0.0:8888でOpenAI互換APIを公開します。

この手順は単なるランチャーではありません。重みのダウンロードと検証、ワーカーへの同期、両ノードでのパッチ適用済みコンテナイメージのビルド、クラスタの起動、APIの準備完了待機までを行います。冪等性を備えているため、再実行すると中断したダウンロードを再開し、キャッシュを再利用し、古いコンテナを置き換えられます。

この手順で提供される機能

  • 2ノードのテンソル並列サービング: 1台のDGX Sparkがランク0とSGLangルーターを実行し、2台目がランク1を実行します。
  • OpenAI互換エンドポイント: APIは、チャット、補完、推論出力、ツール呼び出しの解析、テキストと画像の入力に対応します。
  • 長いコンテキスト: デフォルトのコンテキスト長は900000トークンで、モデル本来の262,144トークンのコンテキストにYaRNスケーリングを適用します。
  • NEXTN投機的デコーディング: デフォルトの投機的設定は3/1/4で、両ノードでCUDAグラフデコードを使用します。
  • SM121互換性: DGX Spark上でコンパイルに失敗するQwen Sparse Attentionパスに対して、Tritonフォールバックを適用します。
  • 運用コマンド: スクリプトには、事前チェック、ダウンロード、起動、停止、ステータス確認、ログ表示、スモークテストが含まれます。

READMEによると、テスト済みの2ノードクラスタでは、単一のデコードストリームで毎秒64.4トークン、2ストリームで合計毎秒116.8トークンを達成しています。4ストリーム時の合計スループットは毎秒114.1トークンでした。

SM121カーネルパッチが必要な理由

Qwen4Expアーキテクチャは、Qwen Sparse Attentionバックエンドを介してアテンションを振り分けます。そのFlash Attentionリゾルバーは従来のFA2を優先し、それ以外の場合はflash-attn-4 CuTe DSLインターフェースを使用します。DGX SparkのSM121アーキテクチャでは、このフォールバックがコンパイル時にMLIRのレイアウト整合性エラーで失敗します。

このリポジトリは、SM121互換のTritonアテンションフォールバックを含む派生SGLangイメージをビルドすることで、この起動ブロッカーを解決します。

生成される.patch/ビルドコンテキストには、モデルのQSA呼び出し契約に合わせたFlashDecoding形式の可変長カーネルであるqsa_fa_fallback.pyが含まれます。Grouped-Query Attention、最大256のヘッド次元、オンラインSoftmaxに対応します。また、cu_seqlensをデバイス上で読み取るため、バックエンドがシーケンステーブルを書き換える場合でもCUDAグラフのリプレイを有効に保てます。

Dockerビルド中に、この手順はqwen_sparse_attn_backend.pyへパッチを適用し、is_sm100_supported()がfalseの場合にTritonフォールバックが選択されるようにします。そのため、B100およびB200の対応データセンターGPUでは標準パスが引き続き使用されます。

必要なハードウェアとソフトウェア

クラスタハードウェア

  • GB10 GPU、SM121アーキテクチャ、各128 GBの統合メモリを備えたNVIDIA DGX Sparkシステム2台。
  • スイッチを介さず、2台のマシン間で直接接続されたConnectX-7ポート。
  • 両ノードで有効化されたRoCEインターフェース。たとえば、一方のノードではrocep1s0f1/enp1s0f1np1、もう一方ではrocep1s0f0/enp1s0f0np0です。
  • 約135 GBのチェックポイント、コンテナイメージ、キャッシュ、一時ビルドデータを格納するための十分なストレージ。

ヘッドノードのソフトウェア

  • Docker。
  • Hugging Faceのhf CLI。
  • チェックポイントへのアクセスに認証が必要な場合は、シェル環境にHugging Faceトークン。
  • ヘッドノードからワーカーへのパスワードなしSSHアクセス。

ヘッドノードに次のコマンドでHugging Face CLIをインストールします。

pip install hf

デフォルトでは、ワーカーにはspark2というSSHエイリアスで接続します。ワーカーへのログインユーザーは、ヘッドノードでstart.shを実行するユーザーと同じになります。これらの前提は.envで変更できます。

2ノードクラスタを設定する

ヘッドノードでリポジトリをクローンし、そのディレクトリに移動して、ローカル設定ファイルを作成します。

git clone <this-repo>
cd <this-repo>
cp .env.example .env

.envを編集し、少なくともヘッドのRoCE IP、ワーカーのRoCE IP、SSH接続先を確認します。デフォルトでは、次のプライベートファブリックアドレスを想定しています。

HEAD_CX7_IP=10.0.22.1
WORKER_CX7_IP=10.0.22.2
WORKER_HOST=spark2

ワーカーで別のアカウントを使用する場合は、WORKER_USERを設定します。SSH接続先を完全に指定するには、WORKER_SSHにuser@hostのような値を設定します。シェル環境変数は.envの値より優先されます。

想定トポロジ

クライアントはポート8888でヘッドノードのAPIに接続します。ヘッドノードはランク0、SGLangサーバー、ルーターを実行します。ランク1はワーカー上で実行されます。NCCLトラフィックは、2つのシステム間の直接接続されたConnectX-7 RoCEv2接続に固定されます。

この手順ではNCCL_NET=IBを使用し、InfiniBand互換のRoCEトランスポートを有効化して、この構成ではNVLSとCUMEMを無効にします。また、ステージング済みのホストNCCL 2.30.7ビルドを事前ロードすることもできます。この動作は、USE_HOST_NCCL=1によってデフォルトで有効になっています。

環境を検証する

モデルをダウンロードする前に、組み込みの事前チェックを実行します:

./start.sh doctor

doctorコマンドは、ファブリック、GPU、RAM、ディスク容量、SSH接続、ポート、デプロイの制約を確認します。大規模なダウンロードとイメージのビルドを開始する前に、報告されたネットワーク、認証、ストレージ、リソースの問題をすべて解決してください。

モデルのダウンロードと同期

ヘッドノードにチェックポイントをダウンロードして検証し、ワーカーに同期します:

./start.sh download

デフォルトのDOWNLOAD_MODE=rsyncでは、ヘッドがファイルをダウンロードした後、ワーカーにコピーします。この操作は再開可能です。各マシンからHugging Faceへ個別にアクセスして取得する場合は、次のように設定します:

DOWNLOAD_MODE=direct

GB10では、SGLangの--load-format dummyオプションを使ってこのモデルをテストしないでください。リポジトリのクラッシュ分析によると、ダミー初期化処理は一時的にfp8パラメータをfp16に変換します。その結果、モデルの51.2 GBのfp8 PLE n-gramテーブルによって、一時的なメモリ需要が150 GBを超え、使用可能な統合DRAMが約121.7 GBしかないシステムが完全にフリーズする可能性があります。

パッチ済みイメージのビルドとサービスの起動

次のコマンドで2ノードのデプロイを起動します:

./start.sh serve

このコマンドは、事前チェックを実行し、重みとイメージが利用可能であることを確認し、両方のノードでSM121対応パッチ済みイメージをビルドし、TP2クラスターを起動して、APIの準備が完了するまで待機します。生成されるローカルイメージの名前はqwen38-flashnext-dspark:localです。

初回起動には、135 GBの重みの読み込み、JITコンパイル、CUDAグラフのキャプチャが含まれるため、約10分かかります。スクリプトのデフォルトの待機時間上限はWAIT_TIMEOUT_MIN=90です。

初回実行を完全に自動化するには、次を使用します:

./start.sh

サブコマンドを指定しない場合、スクリプトはdoctor、download、serveを順番に実行します。

基本的なチャットリクエストの送信

サーバーの準備が完了したら、OpenAI互換のチャットエンドポイントを呼び出します:

curl http://127.0.0.1:8888/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "Qwen3.8-Flash-Next-NVFP4",
    "messages": [
      {
        "role": "user",
        "content": "Explain RoCEv2 in one paragraph."
      }
    ]
  }'

デフォルトでは推論が有効になっており、推論内容は別にストリーミングされます。1回のリクエストで思考を無効にするには、JSON本文にchat_template_kwargsを追加します:

{
  "model": "Qwen3.8-Flash-Next-NVFP4",
  "messages": [
    {"role": "user", "content": "Summarize tensor parallelism."}
  ],
  "chat_template_kwargs": {
    "enable_thinking": false
  }
}

画像入力は、標準のOpenAI互換image_urlコンテンツパートでサポートされています。READMEではテキストと画像を組み合わせた入力が動作すると確認されていますが、完全な画像リクエストの例は記載されていません。

OpenAI互換クライアントへの接続

カスタムOpenAIエンドポイントをサポートするアプリケーションでは、次のベースURLを使用できます:

http://127.0.0.1:8888/v1

モデル識別子は次のとおりです:

Qwen3.8-Flash-Next-NVFP4

ローカルエンドポイントがリクエストを認証しない場合でも、クライアントがAPIキー項目を必要とする場合は、リポジトリの例で使用されているdummyを指定し、認証ヘッダーを無効にします。関連するモデルの機能と互換性設定は次のとおりです:

  • 推論が有効。
  • テキストと画像の入力。
  • 900000トークンのコンテキストウィンドウ。
  • サンプルクライアント設定で最大32768出力トークン。
  • 出力トークン用のフィールドはmax_tokens。
  • サンプルの互換性設定ではdeveloperロールをサポートしない。
  • reasoning-effortパラメータをサポートしない。

サーバーの運用

単一のランチャーで、主な管理コマンドを実行できます:

  • ./start.sh serveは事前チェックを実行し、イメージと重みが存在することを確認し、TP2を起動して、準備が完了するまで待機します。
  • ./start.sh downloadはサーバーを起動せずに、重みをダウンロード、検証、同期します。
  • ./start.sh stopは両方のコンテナを削除し、ログフォロワーを終了します。
  • ./start.sh statusは両方のノードのコンテナ状態を報告します。
  • ./start.sh logs [N]はヘッドとワーカーのログから最後のN行を表示します。
  • ./start.sh smokeは実行中のAPIに簡単なgreedy補完を送信します。
  • ./start.sh doctorは環境とレシピの完全な事前チェックを再実行します。

起動時の問題を診断する場合は、.serve.log、.sglang.log、.sglang-worker.logを確認してください。最初のファイルにはランチャーの出力が記録され、後の2つにはヘッドとワーカーのコンテナログが含まれます。

GB10のメモリ予算を理解する

各DGX Sparkは、CUDA割り当て、ページ固定ホストメモリ、Docker、オペレーティングシステムで共有される統合物理メモリプールを備えています。そのため、このレシピでは、ホストメモリとGPUメモリを独立したリソースとして扱うのではなく、保守的な固定メモリ比率を使用します。

報告されているノードごとの構成には、NVFP4のエキスパート、密結合、MTP、ビジョンの重みに約62.5 GB、fp8 PLEテーブル用のページ固定ホストメモリに約11 GB、約956,800トークンを保持する9 GBのbf16 KVキャッシュ、Mamba/GDN状態用に約3.6 GB、CUDAグラフとNCCLまたはcuBLASのワークスペース用に約8 GBが含まれます。

その結果、CUDAから認識される割り当ては約95.6 GBとなり、追加の処理に利用できる容量は約17.5 GBです。固定されたPLEテーブルは、nvidia-smiではプロセスごとのデバイス割り当てとして表示されませんが、物理DRAMは同じように消費します。実際の余裕を評価する際は、SGLangのavailable_gpu_memログ値、またはfree -hのavailable列を確認してください。

PLEオフロードを有効に保つ

GB10では--no-ple-offload-embeddingを追加しないでください。SGLangの自動ルールにより、PLE埋め込みテーブルはCPUにオフロードされます。これを上書きすると、ランクごとに約26 GBの固定ホストメモリがGPU側に配置され、マシンのメモリを過剰消費する可能性があります。

一時的なプリフィル用の余裕を確保する

QSAインデクサーは、サイズがchunk × historyに比例するfp32ロジットワークスペースを作成します。チャンクサイズが4096、履歴が300,000トークンの場合、関連するgatherおよびtop-kのコピーを考慮すると、この一時領域には約8~10 GBが必要になることがあります。

同梱されている値は、意図的に保守的に設定されています。

MEM_FRACTION_STATIC=0.82
CHUNKED_PREFILL_SIZE=1024
MAMBA_FULL_MEMORY_RATIO=0.3

900,000トークンのコンテキストでは、ピーク時の一時メモリを利用可能なユニファイドメモリ予算と照合してプロファイリングせずに、CHUNKED_PREFILL_SIZEを1024より大きくしないでください。リポジトリの報告によると、チャンクサイズ4096とMEM_FRACTION_STATIC=0.85の組み合わせだけでマシンがハングしました。

アロケータの実験を避ける

この手順では、PYTORCH_CUDA_ALLOC_CONF=expandable_segments:Trueを設定しないよう明示的に推奨しています。この設定はデプロイ環境で実証されておらず、著者が使用した既知の正常動作するDGX Sparkレシピにも含まれていませんでした。

重要な調整項目を設定する

調整項目は.envに設定するか、シェルでエクスポートしてください。シェルの値が優先されます。調整が必要になる可能性が高い設定は次のとおりです。

  • PORT:デフォルトは8888で、APIをすべてのインターフェースにバインドします。
  • CONTEXT_LENGTH:デフォルトは900000です。
  • MAX_RUNNING_REQUESTS:デフォルトは16ですが、デフォルトの比率ではMambaの設定により、実用上の同時実行数は14スロットに制限されます。
  • SPEC_STEPS、SPEC_TOPK、SPEC_DRAFT:NEXTN投機的デコーディングでは、それぞれ3、1、4がデフォルトです。
  • KERNEL_PATCH:デフォルトは1で、SM121フォールバックイメージがビルドされ、使用されます。
  • IMAGE:指定されたイメージを直接使用し、パッチ適用をスキップします。
  • CPUSET:GB10のより大きなCPUコア向けに、デフォルトは5-9,15-19です。空の値にするとピン留めを無効にできます。
  • EXTRA_ARGS:追加のSGLang引数を最後に追加するため、後に指定した値で上書きできます。

思考とツール呼び出しのループに対処する

デフォルトの思考機能、OpenAIツール、qwen3_coderツール呼び出しパーサーを組み合わせたセッションでは、既知のSGLangの問題が発生することがあります。サーバーがトークンID 0を繰り返し出力し、このトークナイザーでは!として表示されるため、max_tokensに達するまで止まらない場合があります。

推奨される回避策は、ツールを送信するリクエストに限って思考機能を無効にすることです。

"chat_template_kwargs": {"enable_thinking": false}

ツールパーサーを使用しながらサーバー全体で思考機能を無効にするには、デプロイを停止し、上書きオプションを付けて再起動します。

./start.sh stop
EXTRA_ARGS='--tool-call-parser qwen3_coder --default-chat-template-kwargs {"enable_thinking":false}' ./start.sh serve

EXTRA_ARGSがすでに.envで設定されている場合は、既存の設定にオプションを統合してください。ツールを提供する同じリクエストで、思考機能を再度有効にしないでください。パーサーがない場合でも思考機能は動作しますが、ツール呼び出しは構造化されたmessage.tool_callsではなく、通常のコンテンツ内の<tool_call> XMLとして表示されます。READMEでは、この回避策を使用する場合、エージェントのtemperatureを0.7以下に保つことも推奨しています。

高度な信頼性とパフォーマンスのヒント

スループット向上のために投機的デコーディングを使用する

NEXTN投機的デコーディングは、測定環境におけるスループットの主な向上要因です。デフォルトの3/1/4チェーンでは、各デコードステップで1回のフォワードパス中に4つのドラフトトークンを検証します。テストしたクラスターでは、2ストリームで合計毎秒約117トークンに達し、その後はより多くのストリームが残りのメモリを奪い合うにつれて横ばいになりました。

必要に応じて決定性を選択する

デフォルト設定では、greedyデコーディングはビット単位で再現可能ではありません。SM121上のNVFP4 MoE GEMMにおける浮動小数点削減のばらつきが原因と考えられますが、同率に近いトークンが実行ごとに変わることがあります。速度は遅くなりますが決定的な動作にするには、次のように再起動します。

EXTRA_ARGS="--moe-runner-backend triton" ./start.sh serve

TileLangの警告を正しく解釈する

JITコンパイル中、TileLangがLogits(bx, position)を複数のスレッドが書き込んでいると報告することがあります。リポジトリでは、計算された位置は重複していないため、これは誤検知だと説明しています。ただし、groupが実行時の値である場合、チェッカーはその性質を証明できません。必要に応じてPassKey.TL_DISABLE_DATA_RACE_CHECKで警告を抑制できます。

外部アクセスを保護する

APIは0.0.0.0にバインドされるため、許可された任意のインターフェースから到達可能です。READMEではLANまたはTailscaleクライアントからのアクセスについて説明していますが、認証レイヤーは追加されていません。エンドポイントを一般公開すべきでない場合は、適切なホストファイアウォール、プライベートネットワーク、またはプロキシによる制御を適用してください。

まとめ

このリポジトリは、難しい2ノード構成のデプロイを、再現可能なワークフローに変換します。主な特徴は、SM121互換のQwen Sparse Attentionフォールバック、慎重に制約したGB10統合メモリ設定、直接的なRoCEv2テンソル並列処理、そして単一のべき等な運用スクリプトを組み合わせている点です。

付属のデフォルト設定から始め、デプロイ前に./start.sh doctorを実行してください。また、ダミーウェイト、無効化したPLEオフロード、過大なプリフィルチャンク、実験的なアロケーター設定は避けてください。クラスターの準備が整えば、アプリケーションは使い慣れたOpenAI互換のチャットおよび補完インターフェースを通じて、ローカルサーバーを利用できます。