2026年9月12日土曜日

DGX Spark をデジタルフォトフレーム化する

DGX Spark(GX10)にデジタルフォトフレーム機能を追加する作業を記録をまとめた。

結果だけ見たい方は mikoto2000/dgx-spark-photoframe: GUI 無しの DGX Spark をデジタルフォトフレーム化するモノ を参照。

目標

まず目標の確認から。

  1. 15 秒ごとに photo ディレクトリ以下の画像をディスプレイに表示したい
  2. 表示はフルスクリーンで
  3. X, Wayland は使いたくない
  4. 電源 ON にしたら自動でフォトフレーム機能を実行してほしい

現状確認

次は現状確認。

現状はこのブログの手順を実行した直後という感じ。

  • https://mikoto2000.blogspot.com/2026/09/dgx-sparkasus-ascent-gx10.html
  • https://mikoto2000.blogspot.com/2026/09/dgx-sparkasus-ascent-gx102-deepseek-v4.html

USB-C モニタが認識されない問題に遭遇

USB-C のモニタを接続したが、認識してくれない。原因を調査していく。

この不具合に当たっている。

対応策は sudo apt purge nvidia-drm-options-modeset0 らしい。試してみる。

モニタの回路から完全に電源を抜かなければならない。

sudo apt purge nvidia-drm-options-modeset0 してから poweroff して、GX10 の電源ケーブルを抜いて 1 分程度待ってから電源をつける。

無事モニタが認識され、ログインプロンプトが表示された。

フォトフレーム機能の実装

なんかファイルを作ってばかりで進捗をツイートしにくい。

デジタルフォトフレーム機能のための Dockerfile と compose.yaml を作っている。

Dockerfile

まず Dockerfile はこれ。シンプルに mpv だけインストールした Ubuntu 環境。

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       mpv \
    && rm -rf /var/lib/apt/lists/*

COPY entrypoint.sh /entrypoint.sh

RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

次は entrypoint.sh。指定された写真フォルダ内の画像を再帰的に探して mpv に渡す。

#!/bin/bash

# コマンド実行時にエラーが発生したら即終了する。
# 未定義変数の参照や、パイプ途中のコマンド失敗もエラーとして扱う。
set -euo pipefail

# /photos 配下から表示対象の画像ファイルを検索し、
# mpv 用のプレイリストを生成する。
#
# 対象形式:
# - JPEG
# - PNG
# - WebP
#
# ファイル名に空白などが含まれていても扱えるよう、
# find では NULL 区切りで取得している。
find /photos \
  -type f \
  \( \
    -iname '*.jpg' -o \
    -iname '*.jpeg' -o \
    -iname '*.png' -o \
    -iname '*.webp' \
  \) \
  -print0 \
  | sort -z \
  | tr '\0' '\n' \
  > /tmp/photos.m3u

# 表示対象の画像が1枚も存在しない場合は、
# mpv を起動せずエラー終了する。
if [ ! -s /tmp/photos.m3u ]; then
  echo "表示可能な画像が /photos に存在しません。" >&2
  exit 1
fi

# mpv を起動し、DRM/KMS を利用して
# デスクトップ環境を介さずモニタへ直接描画する。
#
# DRM_CONNECTOR:
#   使用する DRM connector。
#   例: DP-1
#   未指定の場合は DP-1。
#
# PHOTO_DURATION:
#   1枚の写真を表示する秒数。
#   未指定の場合は15秒。
#
# PHOTO_ROTATE:
#   写真を回転して表示する角度。
#   0 / 90 / 180 / 270 を想定。
#   未指定の場合は0度。
#
# --loop-playlist=inf:
#   最後の写真まで表示したら先頭へ戻り、
#   スライドショーを無限に繰り返す。
#
# exec を使用することで mpv 自体をコンテナの PID 1 とし、
# docker stop などのシグナルを直接受信できるようにする。
exec mpv \
  --vo=drm \
  --drm-device="${DRM_DEVICE:-/dev/dri/card1}" \
  --drm-connector="${DRM_CONNECTOR:-DP-1}" \
  --drm-mode=preferred \
  --video-rotate="${PHOTO_ROTATE:-0}" \
  --profile=sw-fast \
  --fullscreen \
  --no-audio \
  --image-display-duration="${PHOTO_DURATION:-15}" \
  --loop-playlist=inf \
  --playlist=/tmp/photos.m3u

compose.yaml

次は compose.yaml。.env を読み込んでさっきの Dockerfile のイメージを起動する。

services:
  photoframe:
    build:
      context: .

    container_name: dgx-spark-digital-photoframe

    restart: unless-stopped

    devices:
      - "${DRM_DEVICE:-/dev/dri/card1}:${DRM_DEVICE:-/dev/dri/card1}"

    volumes:
      - "${PHOTO_DIR:-./photos}:/photos:ro"

    environment:
      DRM_DEVICE: "${DRM_DEVICE:-/dev/dri/card1}"
      DRM_CONNECTOR: "${DRM_CONNECTOR:-DP-1}"
      PHOTO_DURATION: "${PHOTO_DURATION:-15}"
      PHOTO_ROTATE: "${PHOTO_ROTATE:-0}"

.env(.env.example)

最後に .env(.env.example)。

写真ディレクトリ、出力デバイス、表示秒数、回転角度を指定できる。

# ホスト側の写真ディレクトリ。
# コンテナ内では /photos として読み取り専用でマウントされる。
PHOTO_DIR=~/photos

# 使用する DRM デバイス。
#
# 利用可能な DRM デバイスは、ホスト側で次のコマンドを実行して確認できる。
#
#   ls -l /dev/dri/
#
# 例:
#   card1
#   renderD128
#
# この場合は /dev/dri/card1 を指定する。
DRM_DEVICE=/dev/dri/card1

# 使用する DRM connector。
#
# 接続中の connector は、ホスト側で次のコマンドを実行して確認できる。
#
#   grep -H connected /sys/class/drm/card*-*/status
#
# 例:
#   /sys/class/drm/card0-DP-1/status: connected
#   /sys/class/drm/card0-DP-2/status: disconnected
#
# この場合は DP-1 を指定する。
DRM_CONNECTOR=Unknown-4

# 1枚の写真を表示する時間(秒)。
PHOTO_DURATION=15

# 写真の回転角度。
# 0 / 90 / 180 / 270 を指定する。
# モニタを縦置きする場合は 90 または 270 を指定する。
PHOTO_ROTATE=0

起動

これで .env を書いて docker compose up -d すればモニタに画像が映る。

参考資料

2026年9月9日水曜日

DGX Spark(ASUS ASCENT GX10)2 台連結と DeepSeek V4 Flash Vision Exp セットアップ記録

前回 は 1 台のセットアップを記録した。

今回は 2 台を QSFP ケーブルで連結し、DeepSeek V4 Flash Vision Exp を動かすまでの手順を記録した。

1. QSFP ケーブルでの 2 台接続

まず QSFP ケーブルで 2 台の GX10 を接続する。

QSFP ケーブルを差すときは、有線 LAN ポートに近い方のポートを使う。 そうすると、後で行うインターフェースが、この記事で書かれているものと同じになるはず。

2. インターフェースの特定と固定 IP の割り当て

接続後、ibdev2netdev コマンドでインターフェースを確認する。

ibdev2netdev

以下のような出力になる。

roceP2p1s0f0 port 1 ==> enP2p1s0f0np0 (Up)
roceP2p1s0f1 port 1 ==> enP2p1s0f1np1 (Down)
rocep1s0f0 port 1 ==> enp1s0f0np0 (Up)
rocep1s0f1 port 1 ==> enp1s0f1np1 (Down)

Up となっているインターフェースに、それぞれ固定 IP を割り当てる。

GX10-1 では /etc/netplan/40-cx7.yaml を編集する。

network:
  version: 2
  ethernets:
    enp1s0f1np1:
      addresses:
        - 192.168.100.10/24
      dhcp4: no
    enP2p1s0f1np1:
      addresses:
        - 192.168.101.10/24
      dhcp4: no

GX10-2 では同じファイルに以下の内容を書く。

network:
  version: 2
  ethernets:
    enp1s0f1np1:
      addresses:
        - 192.168.100.11/24
      dhcp4: no
    enP2p1s0f1np1:
      addresses:
        - 192.168.101.11/24
      dhcp4: no

両方で設定を適用する。

sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply

ip addr show で、設定が正しく適用されていることを確認する。

3. パスワードレス SSH の設定

ノード間でパスワードレス SSH を行うための設定をする。

両方の GX10 でキーペアを作成する。

ssh-keygen -t ed25519

お互いに公開鍵を送信し合う。

GX10-1 から GX10-2 へ公開鍵を送信する。

ssh-copy-id 192.168.100.11

GX10-2 から GX10-1 へ公開鍵を送信する。

ssh-copy-id 192.168.100.10

ノード間通信を確実に QSFP ケーブルで行うため、hosts ファイルにエントリを追加する。

192.168.100.10 gx10-1
192.168.100.11 gx10-2

ここまでで連携の土台が完成する。

4. DeepSeek V4 Flash レシピの適用

次に、DeepSeek-v4-Flash-DSpark-2x-DGX-Spark のレシピを適用する。

GX10-1 で作業を開始する。

mkdir -p ~/project
cd project
git clone https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark.git

env ファイルを用意する。

cd DeepSeek-v4-Flash-DSpark-2x-DGX-Spark
cp .env.dspark.example .env.dspark
vim .env.dspark

以下を指定する。

WORKER_HOST=192.168.100.11
MASTER_ADDR=192.168.100.10

VLLM_HOST_IP=192.168.100.10
WORKER_VLLM_HOST_IP=192.168.100.11

GX10-2 のユーザーを docker グループに入れ忘れていたのが発覚したのでここで入れる。

sudo usermod -aG docker <ユーザー名>

両方の GX10 で Docker イメージを pull する。

docker pull ghcr.io/anemll/dspark-vllm-gx10:0.1.1

次にモデルをダウンロードする。GX10-1 で以下を実行する。

./prepare-dspark-model-cache.sh --official

しばらく待つと、GX10-1 と GX10-2 の両方にモデルがダウンロードされる。

5. サーバー起動と動作確認

これで準備が整ったので、サーバーを起動する。

./start-deepseek-v4-flash-dspark.sh

boot-shape-warmup: 47/47 requests ok in 77s のような文字列が表示され、コマンド実行が終われば起動完了である。

docker ps でコンテナが動いていることを確認する。

最後に、curl コマンドでチャットのエンドポイントにリクエストを送り、動作確認する。

ポート 8888 で待ち受けているので、以下のコマンドでリクエストを投げる。

curl http://localhost:8888/v1/chat/completions \
    -H 'Content-Type: application/json' \
    -d '{"model":"deepseek-v4-flash-vision-exp","messages":[{"role":"user","content":"Design a small rate"}]}'

応答の JSON が返却されれば OK.

あとは OpenAI 互換の好きなクライアントで接続すれば良い。

以上。

参考資料

2026年9月8日火曜日

DGX Spark(ASUS ASCENT GX10)開封とセットアップ作業記録

DGX Spark は、日本では ASUS ASCENT GX10 として入手できる。 今回はその開封からセットアップまでを、トラブルも含めて記録した。

1. 開封と初回セットアップ

まずはコンセントにつないで電源を入れる。

GX10 はヘッドレスセットアップ(モニタやキーボードを接続せずに設定する方式)に対応しているので、モニタやキーボードは不要。 タブレットから接続して進める。

  1. タブレットで GX10-xxxx というネットワークに接続
    • 正確な SSID とパスワードは説明書に記載されている
  2. 同じくタブレットで http://gx10-xxxx.local に接続
    • この URL も説明書に記載されている
  3. ブラウザで GX10 のウェルカムページが開くので Get Started を押す
  4. 言語とタイムゾーンを設定して Continue
  5. 利用規約を読んで 承認
  6. ユーザー名とパスワードを設定して 続行
  7. 情報収集に協力するかどうかの選択
  8. GX10 が使用する Wi-Fi ネットワークを設定
  9. Wi-Fi につながると更新が始まる。ファームウェア更新も含まれるため、完了まで時間がかかる
  10. 更新が終わったらセットアップ完了。開始する を押す

これでセットアップは完了。あとは GX10 の IP アドレスを確認して SSH 接続するだけ。 IP アドレスは、GX10 に接続できる端末から ip addr を実行するか、ルーターの管理画面で確認できる。

2. カーネルパニック発生

セットアップが終わったと思ったら、カーネルパニック(OS が致命的なエラーで停止する状態)が発生した。

SSH が繋がらないため、電源長押しで強制終了し、再起動したのが原因だと思われる。 多分まだ裏でアップデート処理が走っていた…。

復旧するため、NVIDIA 公式の System Recovery ガイド を見ながら進める。リカバリイメージは NVIDIA のダウンロードページ から入手できる。

  1. リカバリイメージをダウンロードして展開
  2. リカバリ USB 作成スクリプト(CreateUSBKey.cmd)を実行し、リカバリ USB として使う USB デバイスを選択
    • 選択を誤るとデータが失われるため注意が必要だ
  3. リカバリ USB を DGX Spark に接続し、起動直後から DELINS を連打して UEFI 画面を開く
    • UEFI 画面を開くのは DEL キーだったようだ
  4. UEFI で Boot > DGX OS Recovery > Boot to this USB drive と選択
  5. Quit without saving? と聞かれるので Yes を選択
  6. しばらく待つとリカバリが終わるので再起動

リカバリ後、ホスト名が spark-xxx に変わっていた。

3. 再セットアップとサーバー化

再起動したら、スレッドの最初からもう一度セットアップをやり直す。

リカバリ後の OS では、ホスト名で SSH 接続できる。

SSH 接続後、中身は Ubuntu ベースなのでいつものアップグレードを実行。

sudo apt update
sudo apt upgrade

サーバーとして使うので、固定 IP を設定する。

sudo vim /etc/netplan/90-xxx.yaml

GUI はいらないので multi-user モードに変更。

sudo systemctl set-default multi-user.target

パスワード無しで SSH ログインできるように公開鍵を配置。

mkdir .ssh
chmod 700 .ssh
cd .ssh
vim authorized_keys
chmod 600 authorized_keys

GPU が熱暴走しないように、動作周波数を少し下げる。

sudo nvidia-smi -lgc 0,2200

ここまでやったら一度リブート。

sudo reboot

4. GPU を認識しなくなった

リブートしたら、GPU を認識しなくなった。

原因は、upgrade ではなく full-upgrade を実行しなかったことだ。 full-upgrade は依存関係の変更も反映するため、upgrade ではカーネル更新が GPU ドライバと整合しなかった。

sudo apt update
sudo apt full-upgrade

5. 工場出荷状態に戻して再インストール

ここで「GX10 用の初期化ユーティリティがある」ことを知った。

npaka さんの note Asus Ascent GX10 を工場出荷状態に戻す を参考に、 工場出荷状態に戻してやり直すことにした。

なお、セクション2のリカバリ(リカバリイメージとリカバリ USB 作成スクリプトを使う方法)とは別に、 ここでは ASUS の初期化ユーティリティで工場出荷状態に戻す。

  1. ASUS のサポートページ からリカバリイメージをダウンロード
  2. ダウンロードしたリカバリイメージは iso なので、Rufus で USB デバイスに書き込む
  3. USB を GX10 につないで USB から起動
  4. インストーラーの指示に従ってインストール
  5. インストールの進捗は表示されないため、完了まで待つ
  6. インストール完了、再起動後はまた最初と同じ初期セットアップ

6. 最終セットアップ

工場出荷状態に戻したため、初期セットアップからやり直す必要がある。 初期セットアップが終わったら、セクション3と同じ手順(SSH 接続、アップグレード、固定 IP、multi-user モード、公開鍵配置、動作周波数の調整、リブート)を再実行する。

7. 初期設定の完了と次の手順

これで初期設定は完了。

今回はここまで、次は GX10 同士を接続し、以下のレシピを適用する。

参考資料