目次

起動コマンドを実行して、コーヒーを淹れて戻ってくると、DGX Sparkのターミナルではまだモデルの準備が続いている。初回の起動は約7分。ところが、同じマシンで2回目も同じくらいで済むとは限らない。PLEのファイルをそのまま使うと、起動が53分に延びることがある。

Qwen3.8-Flash-Nextは、総パラメータ125B-A6Bに、別途51BのPLEを持つモデルだ。DGX Sparkの実使用メモリは約121.63GiB。それでも、PLEをSSDに置く設計のおかげで動かせる。この記事では、当ラボで確認した構成と、起動で詰まりやすい点をまとめる。

DGX Sparkで動く理由#

Flash-NextはQwen3.8シリーズの名前を持つが、Qwen4アーキテクチャの先行公開版として紹介されている。公式の表記は「125B-A6B」で、125Bの総パラメータのうち、各トークンで使うのは約6B。ほかに51BのPLE(n-gram埋め込みテーブル)と、投機デコード用の4BのMTPヘッドがある。ここで紹介する構成ではMTPを読み込まない。

PLEが大きくても、各トークンで参照するのはテーブル全体ではなく、16個の表から1行ずつだ。NVFP4なら1トークンあたりの読み出し量は約2.5KB。そこでPLEをSSD上のファイルに置き、必要な行だけを読む。本体の重みは約81GBがメモリに載り、PLEのバッキングファイルは約47.7GiBを使う。

エキスパートの重みをSSDへ移すのとは事情が違う。エキスパートはトークン生成のたびに使うので、SSD待ちが速度に響く。PLEは巨大でも参照量が小さいため、この方式が成り立つ。

もうひとつ助かるのがKVキャッシュだ。48層のうち36層はGated DeltaNetで、KVキャッシュを持つのは残り12層のQwen Sparse Attention。実測では128Kトークン時のKVキャッシュは3.04GBだった。長い資料をまとめて読ませる用途でも、キャッシュの圧迫を抑えやすい。

使ったモデルと環境#

当ラボで使ったチェックポイントはsenfu/Qwen3.8-Flash-Next-NVFP4のrevision 5d37b3b3711d8406174b96ff950c0aa16324b266。ファイル合計は128.9GBで、421ファイルだった。ディスクにはモデル本体に加えてPLEのバッキングファイルを置くため、空き容量は200GB程度を見ておきたい。

実行環境はDGX Spark(GB10、128GB unified memory、メモリ帯域273GB/s)。ドライバはnvidia-driver-580-open系のopenカーネルモジュールを使う。proprietaryドライバでは、PLEの処理中にillegal memory accessが起きた。modinfo nvidia | grep -i licenseの結果がDual MIT/GPLならopen、NVIDIAならproprietaryだ。

SGLangは本家のソースからビルドした。使ったcommitは4fb9b5b5bad41ac7de5586ec812c421fe3aa2b8b。Flash-Next向けの修正が本家に入ったのは2026年9月5日で、当時の公開Dockerイメージはその1日前のビルドだったため、記事の構成ではソースビルドにしている。環境はtorch==2.13.0、flashinfer_python==0.6.18。Rust拡張のビルドでcargoが必要になった場合は、今回の用途では不要なので次の指定で回避できる。

snippet.txt
SGLANG_BUILD_RUST_EXTS=none

起動前に設定する環境変数#

3つの環境変数を設定してからサーバを起動する。

snippet.txt
export SGLANG_QWEN4_PLE_FILE_SKIP_DEVICE_CHECK=1
export MAX_JOBS=2
export SGLANG_WARMUP_TIMEOUT=3600

最初の変数は、SGLangがPLEをSSDに置けるか判定する際のチェックを飛ばすために使う。GB10では、openドライバでも判定に使われる属性がfalseを返す。一方でGB10はATSを使って必要なアクセスを実現できるため、チェックを飛ばした構成で動作した。

MAX_JOBS=2はJITコンパイルの並列数を抑える。既定値のままだとホストメモリを使い切り、サーバが終了することがあった。SGLANG_WARMUP_TIMEOUT=3600は起動時のウォームアップが600秒でタイムアウトする問題への対処だ。コンパイルと初期化に時間がかかるため、タイムアウトを1時間に延ばしている。

c=1で起動するコマンド#

以下は、対話利用向けに同時リクエスト数を1にした例だ。まずモデルのパスを自分の保存先に合わせる。

snippet.txt
python3 -m sglang.launch_server \
  --model-path ~/models/qwen38-flash-next-senfu \
  --host 0.0.0.0 --port 30000 \
  --quantization modelopt_fp4 \
  --ple-offload-embedding --ple-offload-backend file \
  --mem-fraction-static 0.85 \
  --context-length 32768 --max-total-tokens 32768 \
  --chunked-prefill-size 1024 \
  --max-running-requests 1 --max-mamba-cache-size 1 \
  --disable-radix-cache \
  --watchdog-timeout 3600 \
  --prefill-attention-backend triton --decode-attention-backend trtllm_mha \
  --reasoning-parser qwen3 --tool-call-parser qwen3_coder

当ラボで確認した性能は、c=1で27 tok/s。128Kコンテクストの測定ではKVキャッシュが3.04GBだった。上の起動例はメモリを見ながら調整しやすいよう、コンテクストを32Kにしている。128Kを使う場合は--context-lengthと--max-total-tokensを変更し、実際の利用状況に合わせてメモリを確認する。

量子化の指定は--quantization modelopt_fp4。モデルカードではmodelopt_mixedが案内されているが、ここではこの指定で起動した構成を使っている。

2回目の起動が53分になる落とし穴#

PLEのバッキングファイルは初回に作られ、再起動後も残る。ファイルがすでにある状態で再び書き込むと、起動が約53分かかる場合があった。当ラボではファイルの内容を4KB窓で32か所サンプル確認し、正しい内容なら書き込みを省くパッチを適用。これで2回目以降も約7分で起動できた。

パッチを使わない場合は、起動前に古いバッキングファイルを削除する運用でも同じ問題を避けられる。削除対象の場所は使っているSGLangの設定で確認してから作業しよう。モデルの重みと混同して消さないように注意したい。

当初はPLEの行を取り出す処理をCPU側へ回すパッチも試したが、これはproprietaryドライバでの問題を避けるためのものだった。openドライバに切り替えた構成では不要だった。

まず確認する順番#

起動で止まったら、最初にドライバの種類を確認する。次に3つの環境変数が設定されているか、チェックポイントのrevisionとSGLangのcommitが合っているかを見る。最後に、PLEのバッキングファイルを再利用して遅くなっていないか確認する。

この構成で、DGX Spark上にFlash-Nextを載せ、c=1で27 tok/sを確認できた。まずは32Kの設定で起動し、使いたいプロンプト長や空きメモリに合わせてコンテクストを広げてみてほしい。

目次