関連記事: GB10にクロック上限をかける方法 → (韓国語) / LLM推論で最も熱いのはGPUではなかった → (英語) / DGX Spark 2台でDeepSeek V4 Flashを動かす → (韓国語) / DGX Spark 4台をスイッチで接続する → (韓国語)
ASUS Ascent GX10(NVIDIA GB10、DGX Spark製品群)を3台目として導入するにあたり、工場出荷状態のマシンを推論サーバーに仕立てる手順を最初から踏み直した。この記事の対象はASUS Ascent GX10だ。NVIDIA DGX Sparkや他メーカーのGB10機でも似ている可能性は高いが、ファームウェア、標準イメージ、パッケージ構成は製品によって異なりうるので、そのまま従う前に各自の機器で確認してほしい。先の2台で数週間かけて一つずつ突き止めたことを今回は一度に適用し、その過程で遭遇した問題も一緒に書く。この記事はその手順を四つのかたまりに分ける。必ずやるべきこと、やるとよいこと、遭遇した問題、そしてセットアップの終わった1台ですぐ試せるレシピだ。自宅ネットワーク固有の設定(VLAN、アドレス、スイッチ)は省いた。
注意。 この記事には、ファームウェアのアップグレード、ドライバの入れ替え、デスクトップパッケージの削除、ファイアウォール設定のように、元に戻しにくい、あるいはリモート接続を切断しかねないシステム変更が含まれる。コマンドを実行する前に、各自の機器とバージョンで何が変わるかを自分で確認し、自己責任で進めてほしい。数値は私たちの機器での実測であり、保証ではない。
初回起動時に出る設定画面(アカウント、キーボード、ネットワーク)は案内どおりに済ませればよい。特に選ぶものはない。そこから先はモニターとキーボードを片付けてSSHで作業するほうが楽だ。この記事の残りの手順もすべてSSHで行った。コーディングエージェント(例: Claude Code)にSSH接続権限を与えて一緒に進めるやり方を勧める。以下の手順はほとんどが点検と照合の繰り返しなので、エージェントに向いている。
# DGX Spark側 (初回のみ、ローカル端末で)
ip -br a # 有線IPの確認
systemctl status ssh # SSHサーバーの動作確認 (なければ sudo apt install openssh-server)
# 作業PC側
ssh-copy-id user@<spark-ip> # 公開鍵をコピー。以後はパスワードなしで接続
ssh user@<spark-ip>
工場出荷状態はこうだった。ドライバとファームウェアのバージョンはマシンごとに異なりうるので、まず確認する。4台目のマシンはタイムゾーンが最初からAsia/Seoulで、OTAメタパッケージも新しい版だったなど、表と少しずつ違っていた。
| 項目 | 工場出荷状態 (2026-08入荷分) | この記事の目標 |
|---|---|---|
| OS / カーネル | Ubuntu 24.04.4 / 6.17.0-1031-nvidia | そのまま |
| デフォルトターゲット | graphical (GNOME、gdm3、snap 13個、設定ウィザードアプリ) | multi-user |
| GPUドライバ | 580.173.02 | 595.84 |
| USB-C PDファームウェア | 0x1 | 0x516 |
| クロック | GPU最大3003MHz、CPU性能コア3.9GHz (制限なし) | (推奨) GPU 2000MHz、性能コア2.8GHz |
| SSH / ファイアウォール | パスワードログイン許可 / ファイアウォール無効 | 鍵のみ / デフォルト拒否 |
| セキュリティ更新 | 自動化なし、15件待機 | 自動適用、自動再起動なし |
セットアップを始める前に、そして終わった後にもう一度、OSから見えるデバイス一覧を期待値と照合する。1行ずつ実行してもよいし、まとめてスクリプトで回してもよい。
lspci | grep -E 'ConnectX|NVIDIA|Non-Volatile|Realtek|MEDIATEK' # PCIデバイス
ip -br link # ネットワークインターフェース
ethtool enP7s7 | grep -E 'Speed|Link detected' # 10G RJ45
for d in /sys/class/net/en*np*; do echo $(basename $d) $(cat $d/operstate) $(cat $d/speed); done # 200G
ls /sys/bus/usb/devices | grep -c '^usb'; lsusb # USBルートハブ数とデバイス
lsblk -d -o NAME,SIZE,MODEL | grep nvme # NVMe
nproc; free -g | grep Mem; nvidia-smi -L # CPU、メモリ、GPU
nvidia-smi --query-gpu=driver_version,clocks.sm --format=csv # ドライバ、クロック
for z in /sys/class/thermal/thermal_zone*; do printf '%s=%s°C ' $(cat $z/device/path | sed 's/.*\.//') $(( $(cat $z/temp)/1000 )); done; echo # 温度ゾーン7個
fwupdmgr get-devices | grep 'Current version' # EC、SoC FW、PD、NVMeファームウェア
sudo dmesg --level=err,crit | grep -v -i tpm | tail # カーネルエラー
systemctl list-units --state=failed # 失敗したユニット
| 項目 | GX10で期待する値 (4台とも同一だった) |
|---|---|
| PCIデバイス | NVIDIA VGA(GB10) 1、NVMe(Phison) 1、Realtek 8127 10G 1、MediaTek 7925 Wi-Fi 1、ConnectX-7 4個(ケーブルが挿さっているときのみ) |
| ネットワークインターフェース | enP7s7(10G RJ45) UP 10000Mb/s、wlP9s9(Wi-Fi)、200Gは enp1s0f0np0、enp1s0f1np1、enP2p1s0f0np0、enP2p1s0f1np1 の4個(物理ポートは2個、各ポートがPCIeファンクション2個として見える)。ケーブルの挿さったポートは up 200000 |
| USB | ルートハブ12個(USB 2.0 6 + 3.0 6)、内蔵デバイスとしてWi-Fi/BTコンボ1個 |
| ストレージ | NVMe 1個(出荷構成により1TBまたは4TB) |
| CPU / メモリ / GPU | 20コア / MemTotal 121GiB / GPU 0: NVIDIA GB10 |
| 温度ゾーン | 7個: TSOC、TS0E、TS0P、TS1E、TS1P、TGPU、TUNC。アイドル42~50°C |
| ファームウェア | EC、SoC FW(UEFI)、USB-C PD、NVMeのバージョンが列挙される。PDが0x1なら1.3へ |
| カーネルエラー / 失敗ユニット | nvidia-fs: warning: error retrieving numa node は無害。それ以外のエラー0、失敗ユニット0 |
物理的にも確認しよう。 OSにデバイスが見えることと、ポートが実際に動作することは別だ。特に200G QSFPの2ポートはケーブルがないとPCIデバイス自体が見えないため、DACケーブルを実際に挿して両方のケージでリンクが上がるか(operstate up、speed 200000)を見る必要がある。USB-Cポートはそれぞれデバイスを挿して lsusb に出るか、RJ45はリンクLEDと ethtool の10000Mb/s、HDMIはモニターで一度ずつ。マシンを設置場所に固定する前、交換が可能な期間内にやっておくのがよい。私たちは3台目を200Gポートにケーブルを挿さないまま受け入れてしまい、「PCIに見えない」のを故障だとしばらく疑った(3章)。
工場イメージはGNOMEデスクトップだ。推論サーバーとして使うなら取り除いたほうがよい。GB10はCPUとGPUが128GiBのメモリプール一つを分け合うが、デスクトップが動いているとそのプールから数GiBを占有する。単一ノードレシピのREADMEはデスクトップの載ったホストのレシピ起動時の利用可能メモリを約114.5GiBと書いているが、サーバーモードに変えたこのノードは同じレシピの起動時に117.4GiB、アイドル状態では119.0GiBだった。同じマシンで前後を測ったわけではないので厳密な比較ではないが、3~4.5GiBの差は大きなモデルを載せるときそのままKVキャッシュの余裕になる。
図1. 利用可能メモリ(MemTotal 121.6GiB基準)。デスクトップホスト(レシピREADME、起動時) ~114.5GiB、このノードのサーバーモードは起動時117.4GiB、アイドル119.0GiB。
128GiBのマシンでMemTotalが121GiBに見える理由とその内訳は付録で扱う。
問題はデスクトップを消すコマンド自体ではなく、その後の apt autoremove --purge だ。2台目をセットアップしたとき、このコマンドがNVIDIAプラットフォームパッケージ14個を丸ごと持っていったことがある。これらのパッケージは dgx-spark-ota-update-meta というメタパッケージの依存関係としてしか参照されていないので、メタパッケージがsnapdと一緒に抜けた瞬間に「誰も必要としないパッケージ」になる。ブートパラメータを入れてくれるスニペット2個(nvidia-spark-initcall-bl、nvidia-spark-grub-pci)が一緒に消え、再インストールと update-grub、再起動で元に戻した。
今回は三段階で防いだ。消す前にプラットフォームパッケージを手動インストールとしてマークし、消すコマンドはまずシミュレーションで回して削除リストにドライバ、CUDA、プラットフォームパッケージが混ざれば止まるようにし、終わった後にブートパラメータを照合する。
# 以下、1章と2章のコマンドはrootシェル(sudo -i)基準
# 1) 保護: メタパッケージの依存パッケージ(snap関連を除く) + インストール済みのNVIDIA/DGX/docker/CUDAパッケージを手動インストールとしてマーク
apt-cache depends dgx-spark-ota-update-meta | awk '/Depends:/{print $2}' | grep -v snaps | xargs apt-mark manual
apt-mark manual $(dpkg -l | awk '/^ii/{print $2}' | grep -E '^(nvidia-|dgx-|docker|containerd|cuda|lldpd|mlnx)')
# (基準マシンがあれば、そちらの `apt-mark showmanual` リストと照合して足りないものを追加する)
# 2) 切り替え + シミュレーションゲート (保護対象が削除リストに混ざれば止まる)
systemctl set-default multi-user.target
CAND="ubuntu-desktop ubuntu-desktop-minimal gdm3 gnome-shell xorg xwayland gnome-control-center \
libreoffice-core network-manager-gnome snapd nvidia-desktop-default-snaps nvidia-system-station \
dgxstation-desktop dgx-oobe-desktop dgx-spark-ota-update-meta ubuntu-server-minimal"
PURGE=""; for p in $CAND; do dpkg -s "$p" >/dev/null 2>&1 && PURGE="$PURGE $p"; done # インストール済みのものだけ
apt-get -s purge $PURGE | awk '/^Remv/{print $2}' | grep -E '^(nvidia-driver|linux-modules-nvidia|cuda|docker|nvidia-spark|network-manager$|netplan|openssh-server$|ufw$)' \
&& { echo "保護対象を含む、中断"; exit 1; }
apt-get purge -y $PURGE
apt-get -s autoremove --purge | awk '/^Remv/{print $2}' | grep -E '^(nvidia-|dgx-|lldpd|docker|cuda|mlnx)' \
&& { echo "保護対象を含む、中断"; exit 1; }
apt-get autoremove --purge -y
rm -rf /snap /var/snap /var/lib/snapd /home/*/snap
# 3) 照合 (再起動後にももう一度)
update-grub
grep -o 'initcall_blacklist=[^ ]*\|pci=pcie_bus_safe\|iommu.passthrough=[0-9]' /boot/grub/grub.cfg | sort -u
ゲートは Remv 行全体ではなく、パッケージ名のフィールドだけを取り出して判定しなければならない。行にはバージョン文字列が付くため(Remv network-manager [1.46.0-1])、名前の末尾を固定したパターンは静かにマッチせず、アンカーを外すと今度は正当な削除対象である network-manager-gnome まで誤検出する。4台目のマシンで、行全体を見るゲートが実際には効いていなかったことを発見し、上のように直した。
OTAメタパッケージは復活させない。復活させると nvidia-desktop-default-snaps を通じてsnapdが戻ってきてサーバーモードが壊れる。メタパッケージの仕事は「次のリリースでNVIDIAが追加するパッケージを引き込むこと」なので、ときどき Depends リストとインストール状態を照合し、足りないものだけ個別にインストールすればよい。個別のプラットフォームパッケージは apt upgrade で正常に更新される。
もう一つ。サーバーモードに切り替えても dgx-dashboard と dgx-dashboard-admin は残る。デスクトップ向けのWeb管理UI(ポート11000)とその背後のroot D-Busバックエンドで、SSHだけで管理するサーバーでは使い道がなく、adminプロセスの常駐メモリが時間とともに数百MBまで育つ(うちのノードの実測で270~360MB)。systemctl disable --now dgx-dashboard dgx-dashboard-admin で止めておく。
変える理由は速度ではなく不具合だ。580.173.02ではKVキャッシュをディスクにオフロードする経路でGPUエラー(Xid 13)が再現し、595.84に上げると同じコードで消えた。新しいマシンも最初から595に合わせる。
apt-get -s install nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04 | grep -E '^(Inst|Remv)'
# 削除(Remv)行がすべて580系であることを確認する。そうでなければ止める
apt-get -s install nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04 \
| grep ^Remv | grep -v 580 && { echo "580以外の削除項目あり、中断"; exit 1; }
apt-get install -y nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04
linux-modules-nvidia-595-open-nvidia-hwe-24.04 は、いま実行中のカーネルに合うビルド済みモジュールパッケージを引き込む(ここでは …-6.17.0-1031-nvidia)。DKMSビルドが不要で、マシンごとにカーネルが違ってもそれぞれ合うものを受け取る。インストール直後は再起動するまで nvidia-smi が「Driver/library version mismatch」を出すが、これは正常だ。戻すには apt-cache policy nvidia-driver-580-open でバージョンを確認したうえで apt-get install nvidia-driver-580-open=<バージョン> linux-modules-nvidia-580-open-nvidia-hwe-24.04。
GB10製品群の全般で報告されている現象がある(事例: GX10ユーザーの報告、PGXユーザーの報告、まとめは別記事の付録(韓国語))。最大負荷でもクロックが400~950MHzにとどまって性能が1/3に落ちるのに、スロットルフラグはすべてNot Activeだ。USB-C PDコントローラのファームウェアが電源ネゴシエーションを誤り、SoCが自らクロックを下げてロックするケースとして知られており、再起動では解けず、電源ケーブルを抜いて放電しないと解けない。予防はPDファームウェアを最新にすることだ。このマシンのPDファームウェアは0x1で、LVFSに0x516(緊急度High)が上がっていた(出典: fwupdmgr get-updatesの出力、LVFSファームウェアID com.asus.gx10dgx.usbpd.firmware、LVFSページ)。
fwupdmgr refresh --force
fwupdmgr get-updates # "GX10 USB-C PD FW Controller Update" 0x516
fwupdmgr update -y --no-reboot-check # カプセルが /boot/efi/EFI/ubuntu/fw/ に準備される
# 再起動 (カプセル1個なら約2分) → fwupdmgr get-devices で 0x00000516 を確認
ドライバの入れ替え、サーバーモードへの切り替え、ファームウェアの適用を1回の再起動で処理した。同じ経路でUEFI(「GX10 SoC FW」)とECファームウェアも配信される。
カプセルが複数待機しているときは二つ注意する。4台目のマシンではEC、SoC FW、PDの3カプセルが1回の再起動にまとまり、SSH復帰まで約9分かかったうえ、PDだけが失敗した(expected 0x516 and got 0x1)。fwupdmgr refresh --force のあと再度 update すると失敗したカプセルが再ステージングされ、PDだけを適用する再起動では成功した。結果の判定は fwupdmgr get-devices --json が正確だ。テキスト出力はデバイス名がすべて「UEFI Device Firmware」で同じため区別しづらく、JSONの UpdateState(2=成功、3=失敗、4=再起動待ち)を見ればよい。
| 項目 | 設定 | メモ |
|---|---|---|
| sshd | PasswordAuthentication no、KbdInteractiveAuthentication no、PermitRootLogin prohibit-password | ~/.ssh/authorized_keys に鍵が入っていることを確認したうえで sshd -t && systemctl reload ssh |
| ufw | incoming deny / outgoing allow / routed deny。SSHは管理ネットワークからのみ、サービスポートは必要なときに1行ずつ | SSHで作業中なら ufw allow from <管理ネットワーク/24> to any port 22 proto tcp を先に入れてから ufw enable し、既存セッションは開いたまま新しい端末で接続を確認する。推論APIポート(8000、8888など)をLANに開くときも明示的に追加する |
| unattended-upgrades | パッケージリストの更新とアップグレードを自動化、Automatic-Reboot "false"、Remove-Unused-Kernel-Packages "true" | 推論中に再起動されては困るので自動再起動は切る |
| タイムゾーン / Wi-Fi | timedatectl set-timezone / nmcli radio wifi off | 工場出荷状態はUTC(マシンによって異なる)。Wi-Fiはデスクトップのpurgeと再起動、有線の検証が終わるまで二つ目の経路として残しておき、最後に切る |
| persistenced / journald | enabled / persistent | 工場デフォルトのまま |
ここからはやらなくても動く。しかし外付けの冷却ファンなしでGB10を長時間回すなら、電力と発熱を一度は考えておくのがよく、以下の数値がその判断材料だ。
GB10にはワット単位の電力上限(nvidia-smi -pl)がない。唯一の調節手段はクロック上限(-lgc)だ。1400MHzから無制限まで200MHz刻みでスイープした結果が以下の二つのグラフだ。デコードはメモリ帯域がボトルネックなのでクロックを下げてもトークン速度はほとんど落ちない一方、電力と温度はクロックとともに急上昇する。
図2. LLMデコード(DeepSeek V4 Flash Q2、llama.cpp、256トークン3回の中央値)、上限別のtok/s(実線)と負荷時電力(点線)。2000MHzは16.7 tok/sで22W、無制限は17.5 tok/sで45W。2600以上はSMの実測クロックが~2495MHzにとどまり結果が同じ。
図3. 動画生成(MiniMax-H3、20 step)、上限別の総時間(実線)と負荷時電力(点線)。2000MHzは103s、50W、66°C。2200は97s、65W、73°C。2400以上は約96s、85W、80°C以上。GPU演算が大半の作業では時間の利得が2200~2400で頭打ちになる。
| 上限 | 用途 | LLMデコード | 動画生成 |
|---|---|---|---|
| 2200 | 速度優先 | 17.1 tok/s、28W、57°C | 97s、65W、73°C |
| 2000 | バランス (推奨値) | 16.7 tok/s、22W、55°C | 103s、50W、66°C |
| 1800 | 真夏、換気の悪い日 | 16.2 tok/s、19W、53°C | 107s、41W、61°C |
| 1400~1600 | 長時間、低発熱 | 15.0~15.7 tok/s、15~17W、49~51°C | 113~125s、28~33W |
# /etc/systemd/system/gpu-clock-cap.service
# 作成後: systemctl daemon-reload && systemctl enable --now gpu-clock-cap (止めるには systemctl stop)
[Unit]
Description=GPU clock cap 300-2000MHz
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/nvidia-smi -lgc 300,2000
ExecStop=/usr/bin/nvidia-smi -rgc
[Install]
WantedBy=multi-user.target
2000を指定すると実際には内部ステップに合わせて約1989MHzになる。一つだけ覚えておけばよい。この上限はベンチマーク条件だ。性能数値を書くときはクロック条件を併記し、負荷中に nvidia-smi --query-gpu=clocks.sm で実際のクロックを確認する。以下のレシピの数字はすべてこの上限がかかった状態の値だ。
CPU上限はGPUとは理由が異なる。出発点は「LLM推論中なのになぜCPUが熱いのか」だった。GB10を2台つないでvLLM(TP=2)でデコードを回したところ、GPUは70°C前後なのに性能コア3~4個が100%に張り付いたままSoC温度を96°Cまで押し上げた。原因はvLLMのプロセス間メッセージキューが新しいメッセージを待つ間スピンするデフォルト値だった(下の2.3)。その修正はソフトウェア側の対処にすぎず、エンジンやイメージが変われば問題はまた再発しうる。だからホスト側の安全網として性能コアのクロック上限も一緒にかける。1台でTP=1で回す構成には2.3のスピン自体がないので、この上限は別のエンジンや構成がCPUを過剰に回したときのための保険であり、任意だ。
GB10の20コアは性能コア10個(最大3.9GHz)と効率コア10個(2.8GHz)だ。性能コアを効率コアと同じ2.8GHzに制限したときの温度変化をゾーン別に見ると、性能コアから遠ざかるほど低下幅が小さくなり、熱源が性能コアであることがわかる。
図4. 性能コア 3.9 → 2.8GHz の上限適用時、負荷中の温度変化(°C)。性能コア −14、アンコア/メモリ −3.7、GPU −1.7、NVMe 0。SoC総合は −9。単一ストリームの速度は変化なし、同時8ストリームで −5%。
# /etc/systemd/system/cpu-clock-cap.service: 性能コア(最大3.9GHz)だけ2.8GHzに
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/bash -c 'for p in /sys/devices/system/cpu/cpufreq/policy*; do \
m=$(cat $p/cpuinfo_max_freq); \
if [ "$m" -gt 2808000 ]; then echo 2808000 > $p/scaling_max_freq; fi; done'
ExecStop=/usr/bin/bash -c 'for p in /sys/devices/system/cpu/cpufreq/policy*; do \
cat $p/cpuinfo_max_freq > $p/scaling_max_freq; done'
[Install]
WantedBy=multi-user.target
/sys/devices/system/cpu/cpufreq/boost はこのプラットフォームではデフォルト0なので、別途触る必要はない。まとめると、GPU上限は電力と発熱の上限であり、CPU上限はソフトウェアがCPUを過剰に回し始めたときの安全網だ。どちらも数字だけでなくかける理由まで把握しておけば、後で適切なタイミングで外せる。
mp executor)でのみ起きる。1台でTP=1で回す場合は該当しない(ワーカーが同じプロセス内にあり、キュー自体がない)。2台以上をつなぐか、--distributed-executor-backend=mp を明示したときの話だ。vLLMの shm_broadcast.py には、メッセージキューを待つとき1秒間(busy_loop_s=1)スピンしてからようやくスリープするデフォルト値がある。デコードのステップ間隔は1秒よりはるかに短いので、事実上つねにスピンしていることになる。コアが数十個ある一般的なサーバーでは3~4コアの浪費は目立たないが、CPUとGPUが一つのパッケージに入っているGB10ではその熱がそのままSoC温度になる。py-spyでスタックを取ってデフォルト値を2msに変えたところ、vLLMプロセスのCPU合計が333%から89%に減り、SoC温度は11°C下がった。調査の経緯は元記事 (英語)に、条件を揃えた再現実験は後続実験 (英語)にある。
図5. スピンのデフォルト(1秒)と2msパッチ。左: vLLMプロセスのCPU使用量合計(%)。右: 負荷中のSoC温度(°C、外部ファンのないノード)。再現実験ではデフォルトが負荷開始90秒で85°C、2分18秒で90°C、ピークは95°Cに達し、パッチはピーク85°Cだった。
# コンテナ/仮想環境の中、vLLM起動前 (パスはインストール場所に合わせる)
sed -i 's/busy_loop_s: float = [0-9.]*/busy_loop_s: float = 0.002/' \
$(python -c 'import vllm,os;print(os.path.dirname(vllm.__file__))')/distributed/device_communicators/shm_broadcast.py
grep -n 'busy_loop_s' $(python -c 'import vllm,os;print(os.path.dirname(vllm.__file__))')/distributed/device_communicators/shm_broadcast.py
# 変わった行が見えるはず。パターンがなければsedは黙って何もしない。
# ビルドによって環境変数(VLLM_MQ_SPIN_SECONDSなど)で公開されている場合はそちらを使う。
一つ正直に断っておきたい。後日の再検証では、単一ストリームのデコードがデフォルト40.9に対しパッチ38.7 tok/sと約2~6%遅かった(反復のばらつきの範囲内だが傾向は一貫していた。同時4ストリームでは差なし、CPU減少はそのまま再現)。そこで私たちはこのパッチを「発熱が問題のときに入れるスイッチ」として位置づけている。外部冷却がなく夏なら入れ、単一ストリームの速度が優先なら切る、という具合だ。2.2のCPU上限はその選択とは無関係にかけておく。
推論サーバーは一度セットアップすればその前に座って作業することは少なく、ほとんどは別の場所からSSHで入る。ルーターのポートフォワーディングの代わりにTailscaleを入れておけば、どこからでも同じ名前でつながる。この記事の後半の作業も家の外からtailnetで行った。
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up # 表示されたURLでログイン (管理コンソールでauth keyを作って --authkey で渡してもよい)
tailscale ip -4 # このマシンのtailnetアドレス
ufw allow in on tailscale0 # ファイアウォールを有効にしたなら、tailnetインターフェースは丸ごと許可
MagicDNSを有効にしておけば ssh <ホスト名> だけでつながる。推論APIも同じ経路で開けるので、LANの外で使うサービスはufwにLANルールを追加する代わりにtailnetだけに開けておくほうが単純だ。
| 症状 | 原因 / 対処 |
|---|---|
再起動後に docker.service が失敗: error creating buildkit instance: invalid database | 工場イメージに入っていたbuildkitのローカルDB(2025-09作成)が破損していた。最近導入した2台でもまったく同じに再現した、イメージ共通の欠陥だ。ビルドキャッシュに過ぎないので systemctl stop docker.socket docker; mv /var/lib/docker/buildkit /var/lib/docker/buildkit.bak; systemctl start docker。イメージやコンテナとは無関係だ。続けて nvidia-ctk runtime configure --runtime=docker でnvidiaランタイムを登録しておく。 |
ドライバインストール直後の nvidia-smi: Driver/library version mismatch | カーネルモジュールは旧バージョンがロードされたままでライブラリだけ新しい。再起動すれば解消。再起動前にGPUを使う作業を回さないこと。 |
595で nvidia-smi --query-gpu=clocks.applications.graphics、display_mode が文字列を返す | 「Requested functionality has been deprecated」。点検スクリプトが数値比較をすると壊れる。clocks.sm を見るか、文字列を例外処理する。 |
| 負荷中にGPUクロックが400~950MHzにとどまり電力~10W、スロットルフラグはすべてNot Active | PDファームウェアによる低クロック固着(1.3、事例1、2)。再起動や nvidia-smi -rgc では解けず、シャットダウン → 電源ケーブルを抜く → 電源ボタンで放電 → 1分後に再接続。意図的にかけた上限(約1989MHz)と区別すること。 |
apt autoremove の後にブートパラメータが消えた | メタパッケージの依存パッケージだったプラットフォームパッケージが一緒に削除された(1.1)。大きなpurgeの後は /proc/cmdline と /etc/default/grub.d/ を基準マシンと照合する。 |
netplan generate が Invalid YAML: aliases are not supported で失敗し、どのnetplan設定も効かない | 工場イメージに、中身が全部ヌルバイトの壊れたnetplanファイルが残っていることがあった(4台目のマシン、715バイトすべて0x00)。ファイル1個が壊れるとnetplanは全体を放棄するため、症状が原因から離れた場所に出る。/etc/netplan/* を file で走査して壊れたファイルを退避し、netplan generate をやり直す。 |
| 静的IPを入れても再起動するとDHCPに戻る | このイメージではnetplanがネットワークの正本だ。NMプロファイルが /run にしか生成されない(揮発性の)マシンがあり、nmcli con mod で入れた設定が再起動で消えることがある。永続設定は /etc/netplan/ に書く。/etc/NetworkManager/system-connections/ が空なのは故障ではなく、netplanレンダラーの正常な動作だ。 |
200G ConnectX-7が lspci にない | ケーブルが挿さっていないとNICが現れない(ホットプラグパッケージが管理)。ドライバの問題ではない。 |
再起動したら /tmp に置いたファイルが消えた | Ubuntuは起動時に /tmp を空にする。再起動前にステージングファイルはホームへ。 |
リモートスクリプトの終了を pgrep -f スクリプト名 で待ったが永遠に「実行中」 | 待つ側のsshコマンドラインに同じ文字列があり、自分自身を捕まえる。pgrep -f "[d]ownload.sh" のように角括弧でくくるパターンを使う。pkill -f も同じ理由で自分自身を殺す。 |
| モデルサーバーがメモリを90%超占有すると、ユーザースペースのOOMデーモンが先に殺すことがある | earlyoomのようなデーモンが有効か確認する(systemctl is-active earlyoom)。大きなモデルをメモリぎりぎりまで意図的に載せるレシピの中には、earlyoomを切るよう案内しているものもある。 |
単一ノードレシピの周辺では「デスクトップ/ディスプレイがUMA 7GiBを占めるので、UEFIのDisplay項目を切れ」という助言が出回っている(7GiBの出典: MiaAIレシピのREADME)。確認したところ、GX10ファームウェア(GX10DGX.0105)にDisplay、iGPU、iGPU Memory Carveoutといった項目はあるものの、すべて非表示(SuppressIf)扱いで設定画面には出ず、NVIDIA公式のUEFIガイドにもない。ファームウェアが実際に取り分けるのは付録のとおり約3.9GiB(carveout 3.0GiBを含む)で固定されており、問題の7GiBはGNOME/XorgのようなユーザースペースによるGPU割り当てなので、サーバーモードに変えれば戻ってくる。ファームウェア側で手を入れることはなく、非表示の変数を直接いじるのは検証されていないので勧めない。
2台以上を束ねるときに使う200Gポートは、三つの段階で人を惑わせた。各段階の症状と解決を順に書く。すべて私たちのマシンで実際に遭遇したものだ。
lspci にConnectX-7が1行も出ない。ドライバ(mlx5_core)はロードされている。ホットプラグパッケージが管理する正常な動作で、ケーブルを挿せば現れる。enp1s0f0np0 と enP2p1s0f0np0 のように名前の違う二つのインターフェースが同じ物理ポートだ(PCIeルート2個にそれぞれ見えるsocket-direct構成。NVIDIAの文書も「QSFPポート1つがLinuxインターフェース2つとして見える」と記している: ConnectX-7 Networking、構造の解説はServeTheHome)。cat /sys/class/net/*/phys_switch_id で見ると四つのインターフェースが同じ値で、phys_port_name だけp0/p1の二つだ。ボンディングせず、二つのインターフェースにそれぞれ別サブネットのIPを与える(NVIDIAのConnect Two Sparksプレイブックも同じ方式)。そうすることでNCCLが二つの経路を使い分け、帯域が合算される。リンクとRoCEの状態はACTIVEなのにIPがないだけで「上がってこない」ように見えるのがこの段階だ。
nmcli con add type ethernet con-name ic200g ifname enp1s0f0np0 ipv4.method manual ipv4.addresses <rail-A>/24 ipv4.never-default yes 802-3-ethernet.mtu 9000
nmcli con add type ethernet con-name ic200g-b ifname enP2p1s0f0np0 ipv4.method manual ipv4.addresses <rail-B>/24 ipv4.never-default yes 802-3-ethernet.mtu 9000
# 相手のマシンも同じ二つのサブネットで。ファイアウォールはポート名4個をすべて許可しておく(ケーブルを別のケージに移しても壊れないように)
for i in enp1s0f0np0 enp1s0f1np1 enP2p1s0f0np0 enP2p1s0f1np1; do ufw allow in on $i; doneib_write_bw も iperf3 も13Gb/s付近で止まる(正常は経路あたり98~110、合算約196Gb/s。経路あたりの上限はケーブルではなくPCIe Gen5 x4側で、のちのスイッチ構成では ib_send_bw で経路あたり109Gb/sが均一に出た。NVIDIAプレイブックのベンチマークガイドはib_write_bwで約197Gb/sを期待値とし、同じ13Gb/sの頭打ちはNVIDIAフォーラムにも報告されている)。同じデバイスでプロセス2個を回して6.9+6.9に分け合うならデバイス側の上限に当たっているということで、1台の中で二つのインターフェース間で流しても13ならケーブルと相手マシンの容疑は晴れる。再起動では解けない。2台をシャットダウンして電源ケーブルを抜き、電源ボタンを押して放電し、1分後に再接続すると解けた(13.3 → 108Gb/s、2経路合計196Gb/s)。以後、通常の再起動を何度しても維持された。PDファームウェアの低クロック固着と同系統の電源側の現象と見られる。permanent MAC address doesn't match で有効化が失敗する。nmcli con mod ic200g 802-3-ethernet.mac-address "" connection.interface-name <新しいインターフェース> でMACの固定を解除し、インターフェース名で紐付け直す。ファイアウォールルールもポート名基準なので、上のように4個すべて開けておく。切り分けの際は二つ覚えておきたい。ufwはICMPをデフォルト許可するので、pingは通るのにTCPが通らない状態が生じる(ib_write_bw が「Couldn't connect」なのにpingは通る)。そしてGB10では ib_write_bw が低く出てもNCCLは正常という事例が報告されているので(NVIDIAフォーラム)、最終判定はNCCL(nccl-tests)で行う。NCCLログの GPU_DIRECT_RDMA_SUPPORTED=False はGB10では正常だ。
ここまでは2台をケーブルで直結する話だ。ノードが3台以上になるとポートが2個しかないためフルメッシュの直結が成り立たず、スイッチが必要になる。その構成はDGX Spark 4台をスイッチで接続する(韓国語)に別途書いた。
セットアップの終わった1台でそのまま再現できるレシピを二つ挙げる。どちらも上のGPU上限(1989MHz)がかかった状態で測った。
最速の動作確認。公式インストールスクリプトでollamaを入れ、Hugging Faceの ggml-org/Qwen3.8-27B-GGUF から Qwen3.8-27B-Q4_K_M.gguf(19GB)を取得してModelfileで登録する。一つ落とし穴がある。この環境ではollamaのデフォルトコンテキストが262144になっており(ollama ps で確認)、小さなモデルでもKVキャッシュで数十GBを占めうる。Modelfileに num_ctx を明示する。
curl -fsSL https://ollama.com/install.sh | sh
pip install -U huggingface_hub # hf CLI
hf download ggml-org/Qwen3.8-27B-GGUF Qwen3.8-27B-Q4_K_M.gguf --local-dir ~/models/qwen3.8-27b
cat > Modelfile <<EOF
FROM /home/$USER/models/qwen3.8-27b/Qwen3.8-27B-Q4_K_M.gguf
PARAMETER num_ctx 32768
EOF
ollama create qwen3.8-27b-q4km -f Modelfile # 約1分 (blobコピー)
ollama run qwen3.8-27b-q4km --verbose "一文で自己紹介して"
| 測定 | 値 | メモ |
|---|---|---|
| ロード | 100% GPU、20GB | ollama ps。初回ロード43秒(コールドページキャッシュ)、以後は常駐 |
| デコード | 11.5~11.8 tok/s | 27B dense Q4 19GBはGB10のメモリ帯域(273GB/s)基準で上限が約14 tok/sなので正常範囲 |
| プロンプト評価 | 290 tok/s | 最初のリクエストはウォームアップで36秒かかり、2回目から0.3秒 |
| 生成中のGPU | 95%、1989MHz、25W、57°C | GPU上限適用状態 |
オリジナルのFP8(166.9GB)は1台に入らない。MiaAI-Lab/DeepSeek-v4-Flash-One-DGX-Spark は 0xSero/deepseek-v4-flash-0731-spark をSparkInferカーネルとDSpark投機的デコードでサービングする。このチェックポイントはエキスパート256個のうち216個を残し(REAP)、EXL3 3.0bpwで量子化した106.9GBのビルドだ。したがって「オリジナルのDeepSeek V4 Flash」ではなくプルーニング+3bpw版であることは知ったうえで使う。
# 前提: docker + compose v2、nvidiaランタイム登録(nvidia-ctk runtime configure --runtime=docker)、ユーザーがdockerグループ
git clone https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-One-DGX-Spark mia-one && cd mia-one
./download.sh # イメージpull → HF 107GB → TP4→TP1再結合 → チェックサム。ディスク~200GB、回線次第で30分~数時間
systemctl stop earlyoom ollama # レシピの要求(サーバーがメモリの94%を意図的に占有する) + 4.1のollamaも落とす。free -h で利用可能 ≥ 114.3GiB を確認
./start.sh --no-wait # デフォルト: MAX_MODEL_LEN 384000、MAX_NUM_SEQS 1、KV_RECORD=stock432、DSpark投機的デコード
curl -s -o /dev/null -w '%{http_code}\n' localhost:8888/health # 200なら準備完了。このマシンでは552秒かかった
サーバーモードの恩恵をここで受けた。起動時の利用可能メモリが117.4GiB(レシピREADMEのホストは114.5)なので、KVプールが510,778トークンで確保された。README最高値の439,622より16%大きい。
図6. プロンプト長別のプリフィル速度(tok/s、プレフィックスキャッシュなし)。32k~100kで~1,000 tok/s、330kで843 tok/s。同じ長さのニードルテスト(隠しておいた合言葉を探し出す)は64k、200k、330kすべて正確に当て、プリエンプション(preemption)は0だった。
| 測定 | 値 | READMEの主張 |
|---|---|---|
| 起動 (/health 200) | 552s | - |
| デコード 単一ストリーム (thinking off) | 36~37 (512 tok ×3)、38~44 (受け入れテストのコード生成) | 44~47 (330kコンテキスト、structured) |
| プリフィル 32k / 100k | 1,091 / 1,024 tok/s | ~1,024 |
| ニードル 64k / 200k / 330k | すべて正確 | 320k/370k 正確 |
レシピ自体の受け入れテスト(acceptance_c1.py) | すべてpass | ゲート ≥35 tok/s |
デコードだけREADMEの80~95%水準だったが、私たちはGPU上限(1989MHz)をかけた状態であり、README側の条件(330kコンテキスト、structured出力)とも異なっていた。EXL3トレリスデコードは演算の比重が大きいのでクロックの影響を受けるだろうという推測はあるが、上限を外したA/Bはしていない。最初の構造化出力(JSONスキーマ)リクエストは23トークンに28秒かかるが、文法のコンパイルが最初の一度だけかかるコストなので2回目からは正常だ。2台以上あるなら、オリジナルFP8をTP=2で載せる別記事 (韓国語)がある。
ip -br a、systemctl status ssh → 作業PCから ssh-copy-idtimedatectl set-timezone、apt-get updateapt-mark manual (メタのDepends + インストール済みの nvidia-spark-* など)/proc/cmdline、PDファームウェア0x516、failedユニット0、docker正常この記事のメモリの数字はすべて二進単位のGiB(230バイト)だ。free -g と /proc/meminfo が使う単位であり、製品表記の「128GB」も実際には128GiB(十進137.4GB)だ。DMIが報告するメモリアドレス範囲0x80000000~0x207FFFFFFFがちょうど237バイト = 128GiBであり、そこから以下の順に差し引かれる(すべてこのマシンで読んだ値)。
| 段階 | サイズ | 内容 |
|---|---|---|
| 物理LPDDR5X | 128.00GiB | CPUとGPUが共用する単一プール (LPDDR5 8533MT/s、dmidecode) |
| メモリマップにない区間 | −0.37GiB | UEFIがカーネルに渡すメモリマップ(memblock 127.63GiB)にそもそも含まれないDRAM区間。ファームウェア側が使っていると見られるが、用途は公開されていない |
| マップにあるがreserved | −3.55GiB | ディスプレイ/iGPUの予約がここに入る。NVIDIAのリリースノート(2026年7月)は「Display Reserved Memory」をBIOSで2GB(既定)か4GBに選べると記している(リリースノート、フォーラムの告知)。私たちのASUSファームウェアでは大きい2領域が2616MiB + 460MiB = 3076MiBで、起動時フレームバッファ(BOOTFB)がその中にあり、3章で見た非表示項目「iGPU Memory Carveout」の値24(×128MiB = 3072MiB)と一致する。ただしその項目の単位は文書化されておらず、最小値も24なのでこれ以上は減らせない。残りの約0.55GiBはUEFIランタイム、ACPI、TPMなど。差し引き後のSystem RAMは124.08GiB |
| カーネル自体の固定割り当て | −2.45GiB | 4KiBページ32,526,926個のpage構造体(64Bずつ = 1.94GiB) + カーネルイメージ、initramfs、percpuなど0.5GiB。ここまで引くとMemTotal 121.63GiB、free -g の121。CMA 128MiBはこの中に含まれる |
| アイドル時のシステム使用 | −2.6GiB | サービス、スラブ(GPUドライバのUVMを含む)、ページキャッシュは除く。サーバーモードのアイドルでMemAvailable 119.0GiB。デスクトップが動いているとここからさらに数GiBが引かれる |
つまり「128GBのうち121GBしか見えない」のは故障ではなく正常で、モデルを載せるときの基準にすべきなのはMemTotalではなくMemAvailableだ。100MiBが惜しいサイズのモデルを載せるなら、手を入れられるのは実質ひとつ、page構造体1.94GiBを減らす64KiBページカーネルだ(NVIDIAがlinux-image-nvidia-64k-hwe-24.04として提供、構造体が約1/16になり1.8GiBほど回収できる。Grace性能チューニングガイドも大容量メモリのワークロードに64Kを勧めている)。ただしSparkに最初から入っているDGX OSのカーネルは64Kでない-nvidiaカーネルであり、595ドライバには4GiB以上を一度に固定マッピングする経路(ディスクKVオフロードなど)で既知の不具合(open-gpu-kernel-modules #1269)があるため、使うワークロードで先に検証し、4Kの起動エントリを残しておくこと。ディスプレイ予約はBIOSに項目があっても既定値(2GB、あるいは私たちのファームウェアの24)が最小値なので減らせず、CMA(128MiB)はムーバブルページとしてそのまま使われるので減らしても得はなく(cma=とcrashkernel=の説明はカーネルパラメータ文書)、crashkernelはすでに0だ。
記録: 2026-08-21~22。機器: ASUS Ascent GX10 (NVIDIA GB10、128GB)。他のGB10製品はファームウェアとイメージが異なる可能性がある。ファームウェアGX10DGX.0105、Ubuntu 24.04.4、カーネル6.17.0-1031-nvidia、ドライバ595.84。クロック上限スイープとスピン実験の数値は同機種の既存2台で測った(各リンク参照)。ネットワーク構成、アドレス、ホスト名など環境依存の項目は省いた。