2026年9月14日月曜日

michaelfeil/infinity を DGX Spark 向け ARM64 CUDA イメージとしてビルドする

michaelfeil/infinity の ARM64 CUDA イメージのビルド、動作確認を行ったのでその作業を記録する。

なお、この作業記録は Codex が自身の作業を記録したものである。

なお成果物は mikoto2000/infinity at for_dgx_spark に置いた。

目的

DGX Spark 上で、 Infinity を使いたかったので、 ARM 用の Docker イメージを作成したい。

実行環境

  • 作業日: 2026-09-14
  • リポジトリ: Windows F:\project\infinity / WSL /mnt/f/project/infinity
  • WSL2: Ubuntu-24.04、ユーザー mikoto、ホスト linux/amd64
  • ホスト Docker Engine: 29.6.1
  • DinD 内 Docker Engine: 29.8.0
  • 専用 DinD コンテナ: infinity-spark-dind
  • Docker データボリューム: infinity-spark-dind-data
  • ソースは DinD の /workspace に読み取り専用でマウントする。
  • PowerShell のサンドボックスから通常権限では WSL の列挙が Wsl/EnumerateDistros/Service/E_ACCESSDENIED になったが、昇格実行で利用可能と確認した。 前の回答の「Docker がない」は Windows の PATH の確認に限られていた。

参照した仕様

DinD の準備 (WSL2 のシェルで実行)

cd /mnt/f/project/infinity
docker run -d --name infinity-spark-dind --privileged \
  --mount type=volume,source=infinity-spark-dind-data,target=/var/lib/docker \
  --mount type=bind,source=/mnt/f/project/infinity,target=/workspace,readonly \
  --workdir /workspace \
  docker:29-dind dockerd --host=unix:///var/run/docker.sock
docker exec infinity-spark-dind docker info
docker exec infinity-spark-dind docker run --privileged --rm tonistiigi/binfmt --install arm64
docker exec infinity-spark-dind docker buildx create \
  --name infinity-spark-builder --driver docker-container --use --bootstrap
docker exec infinity-spark-dind docker pull --platform linux/arm64 nvcr.io/nvidia/pytorch:25.11-py3

進行状況

  • DinD の作成と ARM64 エミュレーション確認: 成功。
  • default ビルダー (docker driver / BuildKit v0.33.0) も linux/arm64 に対応していた。 以降は取得済みイメージを共有できる --builder default を使用する。
  • Poetry エクスポートと infinity_emb-0.0.77-py3-none-any.whl 作成: 成功。
  • CPU 用の既存テスト4件、Spark 依存関係変換2件、環境分離2件: WSL2 上で合計8件成功。
  • ARM64 依存インストール: 成功 (309.9 秒)。
  • Infinity 本体 0.0.77 のインストール: 成功。
  • 環境分離後の python -m pip check: No broken requirements found. (終了コード0)。
  • ARM64 スモークテスト: 成功 (初回の読み込みを含め 824.1 秒)。 torch の CPU 演算、torchvision の NMS、小規模 BERT forward、Infinity サーバー構築を確認。
  • infinity_emb v2 --help: 成功 (終了コード0)。
  • ARM64 production イメージ: ビルド・DinD 内への保存に成功 (終了コード0)

追加した構成

  • libs/infinity_emb/Dockerfile.spark: Spark 専用。既存の Jinja 生成対象とは別に直接編集する。
  • libs/infinity_emb/docker_spark_prepare.py: GPU 関連パッケージを NVIDIA ベースのバージョンで制約し、その他は既存 Poetry ロックを使用する。
  • libs/infinity_emb/docker_spark_isolate.py: インストール後、アプリ依存と NVIDIA の torch/torchvision/Triton のみを仮想環境から参照できるようにする。
  • libs/infinity_emb/tests/docker/smoke_spark.py: GPU 不要の import、CPU テンソル演算、小規模 BERT forward、サーバー構築確認。
  • libs/infinity_emb/tests/docker/test_spark_prepare.py: NVIDIA パッケージの保持と不適切なベースイメージの拒否を検証。

依存エクスポート、ARM64 wheel のダウンロード、純 Python wheel 作成は BUILDPLATFORM (amd64) で実行し、production の依存インストールと 動作確認は ARM64/QEMU で実行する。 ダウンロードは Python 3.12 / CPython / ARM64 の manylinux タグを明示する。 GPUtil のみソース配布なので、ネイティブ側で py3-none-any wheel を作る。 wheelhouse はビルド時だけマウントし、最終イメージには含めない。 Python 仮想環境 /opt/infinity はインストール時のみ NVIDIA ベースの system site-packages を参照する。 venv --without-pip --system-site-packagespython -m pip を組み合わせ、 ベース付属の pip 25.3 を利用する。通常の venv は QEMU 上の ensurepip に 3分以上かかったため、初回ビルドを中断してこの方式に変更した。 変更後の仮想環境作成は 3.4 秒で完了した。初回ログは spark-build-initial.log。 次の試行では QEMU 内のオンライン pip 解決・インストールに時間がかかり、 1,328 秒時点でもインストール途中だったため中断した (spark-build-online-install.log)。 ARM64 wheel をネイティブ側で事前取得する処理は 23.2 秒で成功した。 ARM64 側では --no-index --find-links /wheelhouse --no-compile を使用する。 バイトコードの事前生成を省くだけで、ARM64 のネイティブ拡張インストールと実行確認は省略しない。 インストール後、必要なベース由来の Python パッケージと dist-info を仮想環境へ シンボリックリンクし、include-system-site-packages = false にする。 これにより NVIDIA の CUDA バイナリを再利用しつつ、NGC の学習・開発用ツールが アプリの依存解決に混ざることを防ぐ。ベースの実ファイルは削除・変更しない。 追加の x86_64 用 Flash Attention wheel はインストールしない。 既定エンジンは torch、BetterTransformer は無効とする。

ビルドコマンド (WSL2)

set -o pipefail
docker exec infinity-spark-dind docker buildx build \
  --builder default --platform linux/arm64 --target production \
  --progress plain -f libs/infinity_emb/Dockerfile.spark \
  -t infinity:spark-arm64 --load libs/infinity_emb \
  2>&1 | tee /mnt/f/project/infinity/spark-build.log

ログは spark-build.logspark-requirements-build.log に保存する (*.log は Git 管理対象外)。 イメージは DinD 内 にロードされる。ホストの docker images には現れない。

取得した NVIDIA ベースのマニフェスト:

  • マルチアーキテクチャ: sha256:417cbf33f87b5378849df37983552cd1f8bc8b62fe1ceabe004de816a55dff21
  • ARM64: sha256:4a85d8cf6fb3a943280960b8948cf4e9b6eca77b4414c68c9b2c7bb863f79b70

厳密に同じベースを使用する場合は、ビルドに以下を追加する。

--build-arg SPARK_BASE_IMAGE=nvcr.io/nvidia/pytorch:25.11-py3@sha256:4a85d8cf6fb3a943280960b8948cf4e9b6eca77b4414c68c9b2c7bb863f79b70

ビルド後の確認コマンド (WSL2)

docker exec infinity-spark-dind docker image inspect infinity:spark-arm64 \
  --format '{{.Os}}/{{.Architecture}} {{.Id}}'
docker exec infinity-spark-dind docker run --rm --platform linux/arm64 \
  --entrypoint python infinity:spark-arm64 /app/smoke_spark.py
docker exec infinity-spark-dind docker run --rm --platform linux/arm64 \
  infinity:spark-arm64 v2 --help

DGX Spark への転送・GPU 確認の手順

WSL2 側でイメージをエクスポートし、gzip 圧縮した tar を Spark に転送する。 大きいイメージなので、出力先の空き容量を確保する。

set -o pipefail
docker exec infinity-spark-dind docker save infinity:spark-arm64 | gzip -1 > infinity.tar.gz

Spark 上で実行する。

docker load -i infinity.tar.gz
docker run --rm --gpus all --entrypoint python infinity:spark-arm64 \
  -c 'import torch; print(torch.__version__, torch.version.cuda); print(torch.cuda.get_device_name(0)); x = torch.ones(4, device="cuda"); print(x + 1)'
docker run --rm --gpus all -p 7997:7997 \
  -v infinity-spark-models:/app/.cache/huggingface \
  infinity:spark-arm64 v2 --model-id BAAI/bge-small-en-v1.5 --engine torch --device cuda --port 7997

最後のコマンドはモデルをダウンロードして API を起動する。 GPU 演算・実モデル推論・性能・Flash Attention・TensorRT・CTranslate2 の GPU 実行は今回の確認範囲に含まれない。

ベースイメージの依存チェック

docker exec infinity-spark-dind docker run --rm --platform linux/arm64 \
  --entrypoint python nvcr.io/nvidia/pytorch:25.11-py3 -m pip check

Infinity 追加前から以下の1件が存在した (終了コード1)。

nvidia-resiliency-ext 0.4.1+cuda13 requires pynvml, which is not installed.

ログ: spark-base-pip-check.log

さらに pip inspect のメタデータ (spark-base-inspect.log) とアプリのロックを比較し、 以下の衝突を確認した。これらの開発用パッケージをアプリ環境から分離する理由となった。

ベース内のパッケージ 要求 アプリのロック
tyro typing-extensions >= 4.13 4.12.2
typeguard typing-extensions >= 4.14 4.12.2
torchtitan datasets >= 2.21 2.14.4
lightning-thunder networkx >= 3.3 3.2.1
lightning-thunder dill >= 0.3.8 0.3.7

実インストール時には、NGC の httpcore 1.0.9 とアプリ側の h11 0.14.0 の 衝突も検出された。httpcore はアプリの実行依存ではないため、同じ分離処理で解消した。 一時的に system site-packages を共有しているインストール途中のログにはこれらの 依存警告が出るが、最終環境では pip check により不整合がないことを確認した。

GPU スタックを新しい PyPI torch で置き換えたり、アプリのロック全体を更新する処理は行わない。

ARM64 スモークテストの実測結果

architecture: aarch64
torch: 2.10.0a0+b558c986e8.nv25.11
torchvision: 0.25.0a0+7a13ad0f
cuda_build: 13.0
sentence_transformers: 3.3.1
onnxruntime: 1.19.2
gpu_execution_tested: False

torch.backends.cuda.is_built() も真であることを確認した。 GPU を必要とする演算は実行していない。

初回スモークテストは QEMU 上で多数のモジュールを読み込むため約14分かかった。 途中で生成される .pyc とロード済み ARM64 共有ライブラリを確認し、停止ではなく 初期化が進行していることを確認した。 既存コードの torch.backends.*.allow_tf32 に PyTorch 2.10 の非推奨警告が出たが、 演算とスモークテストは成功した。TF32 設定 API の更新は今回の変更に含めない。 スモークテストと別プロセスでの CLI ヘルプ確認を合わせたステージは 1,145.3 秒だった。 この実測は amd64 上の QEMU であり、DGX Spark ネイティブ実行の性能を表すものではない。

完成したイメージ

  • タグ: infinity:spark-arm64
  • 保存先: WSL2 の infinity-spark-dind コンテナ内の Docker
  • OS / Architecture: linux/arm64 (docker image inspect で確認)
  • Docker が報告したイメージ ID: sha256:578dd3cebf7b75a06cf017428ec55ecfdc1534b66dc2d153e66c57819c3cde1c
  • ARM64 イメージマニフェスト: sha256:94b2018bb094a357afe043e0ab415ba7f78b303ebb13d3c2232185af66ea0199
  • docker image inspect の Size: 9725644356 bytes
  • 作成時刻: 2026-09-14T08:42:26.723536977Z (日本時間 17:42)
  • ENTRYPOINT: ["infinity_emb"]
  • インストール一覧: イメージ内 /app/installed-packages.txt。 完成イメージから取り出した一覧を spark-installed-packages.log に保存した。 pip freeze 出力なので、NVIDIA パッケージにはビルド元のローカル wheel URL が含まれる。 再インストール用 requirements としてではなく構成記録として使用する。

ビルド中にスモークテストと CLI の成功を確認したため、完成後に同じ重いテストは 繰り返さず、イメージ属性と保存済みパッケージ一覧を確認した。

DinD コンテナと専用データボリュームは再利用できるよう残している。 WSL2 再起動などで停止した場合は、以下で再開できる。

docker start infinity-spark-dind
docker exec infinity-spark-dind docker image inspect infinity:spark-arm64

Docker Hub / NGC などへのイメージ push は行っていない。Spark への転送結果は末尾に記載。

gzip 圧縮したイメージの保存 (2026-09-14 実施済み)

ユーザーの指定により infinity:spark-arm64docker save で書き出した。 WSL2 の Bash 内で以下を実行し、Windows PowerShell のバイナリパイプを経由せずに保存した。

set -o pipefail
set -o noclobber
docker exec infinity-spark-dind docker save infinity:spark-arm64 \
  | gzip -1 > /mnt/f/project/infinity/infinity.tar.gz.partial
gzip -t /mnt/f/project/infinity/infinity.tar.gz.partial

両コマンドの終了コードが 0 であることを確認後、一時ファイルを同じディレクトリの infinity.tar.gz にリネームした。

  • 保存先: F:\project\infinity\infinity.tar.gz
  • ファイルサイズ: 9679762352 bytes (約 9.68 GB / 9.02 GiB)
  • 検証: gzip -t による圧縮ファイル全体の整合性チェック成功
  • docker save 単体の出力は .tar。今回は gzip 圧縮したため、指定の .tar.gz が正しい。
  • Spark での読み込み: docker load -i infinity.tar.gz

DGX Spark への SCP 転送 (2026-09-14 実施済み)

ユーザーの指定により Windows OpenSSH の scp で転送した。

scp -o BatchMode=yes -o ConnectTimeout=15 F:/project/infinity/infinity.tar.gz mikoto@gx10-707e.local:~/infinity.tar.gz
ssh -o BatchMode=yes -o ConnectTimeout=15 mikoto@gx10-707e.local 'stat -c "%s %n" ~/infinity.tar.gz'

scp は終了コード 0。転送先 /home/mikoto/infinity.tar.gz のサイズが 9679762352 bytes であり、転送元と一致することを確認した。

(筆者追記)DGX Spark での GPU 実行確認

Codex にイメージファイルの転送までしてもらったので、それ以降の動作確認は自分で行った。

DGX Spark 上で、gzip 圧縮したイメージをロードし、GPU を使用して Infinity サーバーを起動した。

docker load -i ./infinity.tar.gz
docker run -it --rm \
    --gpus all \
    -p 8081:8081 \
    -v models:/app/.cache 
    infinity:spark-arm64 v2 \
        --model-id BAAI/bge-m3 \
        --model-id BAAI/bge-reranker-v2-m3 \
        --engine torch \
        --device cuda \
        --port 8081

curl でモデル一覧取得 API を呼び出し、サーバーが正しく起動していることを確認した。

curl http://localhost:8081/models

以上。

参考資料

2026年9月12日土曜日

自宅ネットワークを構築する

スイッチングハブが届いたので、自宅ネットワークの構築を始めた。

目標の構成

目標の構成は次のとおり。

BUFFALO WSR-1500AX2S-BK の動作確認

押し入れから引っ張り出してきた BUFFALO WSR-1500AX2S-BK の動作確認から始める。

モード切替スイッチをルーターモードにして電源を接続し、リセットボタンを押す。

ルーター本体に書いてある SSID とパスワードで、ルーターの LAN に接続する。

ルーターの初期設定

http://192.168.11.1/login.html にアクセスし、本体に記載のユーザー名とパスワードでログインする。

設定画面 から 詳細設定 を開く。

Internet の設定

まず Internet の設定をする。

今回は宅内 LAN の増設なので、DHCP を有効にするだけでよい。

LAN の設定

次に LAN の設定をする。

デフォルトからの変更点は次のとおりだ。

  • LAN側IPアドレス: 192.168.2.1

ルーターの IP アドレスと LAN のネットワークアドレスが変わったので、一度 PC の Wi-Fi を OFF にして接続し直す。

http://192.168.2.1/login.html にアクセスし、もう一度詳細設定画面を開く。

無線の設定

バンドステアリング Lite を OFF にする。

2.4 GHz 帯を OFF にする。

5 GHz 帯の設定をする。

各マシンのネットワーク設定

母艦 PC はもともと DHCP なので、物理的につなげるだけでよい。

gx10-707e.local の設定

有線の定義を /etc/netplan/50-ether.yaml に書く。

network:
  version: 2
  ethernets:
    enP7s7:
      dhcp4: false
      addresses:
        - 192.168.2.254/24
      routes:
        - to: default
          via: 192.168.2.1
      nameservers:

無線を無効化する。

sudo nmtui を実行し、接続の編集 から対象の SSID を選び、自動的に接続する のチェックを外す。

gx10-6acc.local の設定

有線の定義を /etc/netplan/50-ether.yaml に書く。

network:
  version: 2
  ethernets:
    enP7s7:
      dhcp4: false
      addresses:
        - 192.168.2.253/24
      routes:
        - to: default
          via: 192.168.2.1
      nameservers:

無線を無効化する。

sudo nmtui を実行し、接続の編集 から対象の SSID を選び、自動的に接続する のチェックを外す。

申し送り事項

ネットワーク構成の更新は完了した。

申し送り事項は次のとおりだ。

  1. 母艦 PC のケーブルが 2.5G
    • 調達した 10G ケーブルの長さが足りませんでした…
  2. 無線ガジェットはまだ以前の SSID に接続中
    • 設定が面倒なので必要に応じて徐々に移行
  3. 何か使っているルーターの調子が悪い
    • もしかしたら買い替え必要かも?

「3.」以外は明日以降、順次解消していく。

参考資料

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 同士を接続し、以下のレシピを適用する。

参考資料

2026年8月22日土曜日

Markdown と図を 1 つのファイルにまとめるエディタ「MaCa Editor」

MaCa Editor は、Markdown と図を Markdown Package Specification(mdpkg 仕様)に則った .mdpkg というひとつのファイルにまとめ、を閲覧・編集するためのデスクトップエディタです。 Markdown 文書の編集と SVG ベースの作図を 1 つのアプリケーションで行い、文章・画像・図をまとめて管理できます。

このツールが解決する問題

通常、文章と図は別々のツールで作ることが多いです。文章は Markdown エディタ、図は draw.io や Excalidraw などで作成し、最後に画像として貼り付けます。

例えば、技術資料を書いているときに、図を直したくなることがあります。 そのたびに、図ツールを開いて編集し、画像として書き出し、Markdown の images/ フォルダに置き直して、本文のリンク先が古い画像のままになっていないかを確認する、という手順を繰り返していました。 文章の修正が 1 行なのに、図の更新に数分かかることもあります。 しかも、書き出した画像と Markdown のリンクが別々に管理されているため、どちらかを更新し忘れると、資料全体の整合性が崩れます。

MaCa Editor は、この「文章と図を別々に管理する」手間を減らすことを目的としています。.mdpkg というパッケージ形式の中に、Markdown 文書・画像・SVG 図をまとめて保持し、1 つのアプリケーションで編集できるようにしています。

なぜ 1 つのアプリケーションにまとめるのか

文章と図を同じパッケージに保持すると、以下の利点があります。

  • 参照の整合性が保たれる: 図や画像のリンクがパッケージ内の相対パスで管理され、ファイルの移動や欠落を防ぎやすい
  • 編集の往復が減る: プレビュー上の図をクリックすると Drawing Editor が開き、その場で再編集できる
  • 配布が単純になる: .mdpkg 1 ファイルで文章・画像・図をまとめて持ち運べる

冒頭で挙げた「図を直すたびに書き出しと貼り直しを繰り返す」体験は、この設計でどのように改善されるのでしょうか。

まず、図の書き出しと貼り直しという手順自体がなくなります。 図は SVG としてパッケージ内に保持され、Markdown からはパッケージ内の相対パスで参照されます。 図を編集するときは、プレビュー上の図をクリックして Drawing Editor を開き、その場で書き出しなしに更新できます。 文章の修正が 1 行でも図の更新に数分かかる、という手間は、書き出しと貼り直しの往復が消えることで解消されます。

また、画像とリンクが別々に管理されていたために更新し忘れて整合性が崩れる問題も、構造上起こりにくくなります。 図とそれを参照する Markdown が同じパッケージにあり、リンクは相対パスで解決されるため、図を更新したときにリンク先を探し直す必要がありません。 ファイルの移動や欠落も、パッケージ内で管理されることで防ぎやすくなります。

つまり、この設計は「図の更新にかかる手間」と「図と文章の整合性を保つ手間」の両方を、1 つの場所で編集できるようにすることで削減しています。

主な機能

主な機能は以下の通りです。

  • Markdown の編集とプレビュー
  • ファイルと画像の管理
  • Drawing Editor
  • PlantUML・Mermaid

名称の由来

名称の MaCa は、Markdown と Canvas に由来します。Markdown による文章編集と、Canvas(描画領域)による SVG 作図を組み合わせた設計であることが、名称に表れています。

デモ動画

まとめ

MaCa Editor は、Markdown 文書と SVG 図を 1 つの .mdpkg パッケージにまとめて管理できるデスクトップエディタです。 文章・画像・図を同じ場所で編集・管理したい場合に適しており、図を多用する人にとってうれしいアプリケーションとなっています。

2026年8月13日木曜日

WSL2 上で llama.cpp をビルドする

前回 は、 DGX Spark 向けにビルドしましたが、今回は WSL2 上でビルドする手順。

今回は初めからデーモン化する前提で進めていく。

前提

  • Windows: Windows 11 Pro 25H2 ビルド 26200.8524
  • グラフィックボード: NVIDIA GeForce RTX 4070 Ti SUPER (16 GB)
  • WSL2: Ubuntu 24.04 LTS

前提パッケージのインストール

llama.cpp 版をビルドするために必要なパッケージをインストールする。

sudo apt update
sudo apt install -y git build-essential cmake libssl-dev nvidia-cuda-toolkit

llama.cpp 実行ユーザーの追加

llama というシステムユーザーを追加する。

sudo useradd \
  --system \
  --home-dir /opt/llama \
  --shell /usr/sbin/nologin \
  llama

llama.cpp 用のディレクトリ作成

sudo mkdir /opt/llama
sudo chown llama:llama /opt/llama

llama.cpp ソースコードのクローン

いつものように git clone する。

cd /opt/llama
sudo -u llama git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp

llama.cpp ビルド

NVIDIA のグラボなので、それ用の make コマンドを叩く。

sudo -u llama cmake -B build-cuda \
      -DGGML_CUDA=ON \
      -DCMAKE_BUILD_TYPE=Release \
      -DLLAMA_CURL=ON \
      -DLLAMA_OPENSSL=ON
sudo -u llama cmake --build build-cuda -j"$(nproc)" --target llama-server

動作確認

とりあえず普通に動くかを確認。

sudo -u llama /opt/llama/llama.cpp/build-cuda/bin/llama-server -hf unsloth/DeepSeek-V4-Flash-0731-GGUF:Q8_0

OK.

systemd 用のファイル作成

cat << 'EOF' | sudo tee /etc/systemd/system/llama-server.service
[Unit]
Description=llama.cpp OpenAI-compatible inference server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=llama
Group=llama
WorkingDirectory=/opt/llama/llama.cpp/build-cuda/bin

ExecStart=/opt/llama/llama.cpp/build-cuda/bin/llama-server \
  -hf unsloth/Qwen3.6-35B-A3B-GGUF:UD-IQ3_XXS \
  --gpu-layers all \
  --no-mmap \
  --host 0.0.0.0 \
  --port 11434

Restart=on-failure
RestartSec=5

TimeoutStartSec=0
TimeoutStopSec=120
KillSignal=SIGINT

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

設定反映

sudo systemctl daemon-reload
sudo systemctl enable llama-server
sudo systemctl start llama-server

以上。

2026年8月6日木曜日

DGX Spark 1 台で DeepSeek V4 Flash 0731 を動かす(llama.cpp 版)

前回 は DwarfStar で DeepSeek V4 Flash 0731 を動かしたが、今回は llama.cpp 版で動かしてみる。

サーバーの安定性が DwarfStar よりも高いらしいので、llama.cpp 。

前提

  • マシン: ASUS Ascent GX10 GX10-GG0006BN 90MS0371-M00060 (1TB)
  • Ollama, DwarfStar のデーモンを止めた後から

前提パッケージのインストール

llama.cpp 版をビルドするために必要なパッケージをインストールする。

sudo apt update
sudo apt install -y git build-essential cmake libssl-dev

llama.cpp ソースコードのクローン

いつものように git clone する。

cd ~/project
git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp

llama.cpp ビルド

DGX Spark なので、それ用の make コマンドを叩く。

cmake -B build-cuda -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=121 \
      -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=ON -DLLAMA_OPENSSL=ON
cmake --build build-cuda -j"$(nproc)" --target llama-server llama-cli llama-bench

ほんとうは llama-server だけで良いのだけど、参考にしたサイト通りに全部ビルドしちゃった。

モデルのダウンロード

前回 のモデルを使うのでダウンロードは省略。

サーバー起動

OpenAI API 互換サーバーを起動する。

./build-cuda/bin/llama-server --host 0.0.0.0 --port 11434 --no-mmap -m ~/project/ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-0731.gguf

結構長い時間待つ感じ。成句すると以下のような出力がされる。

0.00.300.436 I srv    load_model: loading model '/home/mikoto/project/ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-0731.gguf'
8.00.852.448 I srv    load_model: initializing, n_slots = 4, n_ctx_slot = 1048576, kv_unified = 'true'
8.00.861.742 I srv          init: chat template supports preserving reasoning, consider enabling it via --reasoning-preserve
8.00.861.881 I srv  llama_server: model loaded
8.00.861.884 I srv  llama_server: listening on http://0.0.0.0:11434

クライアント起動

OpenAI API 互換クライアントで接続確認。 OK.

> C:\Java\jdk-25\bin\java.exe -jar ..\project\rei\target\rei-0.0.1-SNAPSHOT.jar
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.sqlite.SQLiteJDBCLoader in an unnamed module (jar:nested:/F:/project/rei/target/rei-0.0.1-SNAPSHOT.jar/!BOOT-INF/lib/sqlite-jdbc-3.51.3.0.jar!/)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

AI Shell
通常入力は chat として扱います。/exit で終了します。
複数行入力: 複数行ペースト対応。行末に \\ を付けるか、Ctrl+J でも改行できます。
/paste で確実な複数行入力モード(終了は単独行の . )
22:15 deepseek-v4-flash> こんにちは!!!!!

 User
こんにちは!!!!!


=== thinking ===
The user greeted me with "こんにちは!!!" (Hello!!!!!). This is a simple greeting. I should respond naturally in Japanese. Looking at the memory, there was a similar greeting earlier and the assistant responded with "こんにちは!? 何かお手伝いできることはありますか?"

I'll respond similarly. No tools needed.
=== answer(23.7 s) ===
こんにちは!? 今日もお手伝いできることがあれば、何でも言ってくださいね!

models には対応していなさそう。

llama-server の daemon 化

実行ユーザーの追加

llama というシステムユーザーを追加する。

sudo useradd \
  --system \
  --home-dir /opt/llama \
  --shell /usr/sbin/nologin \
  llama

llama-server の配置

llama 用のディレクトリを作成し、ビルドした llama-server を配置する。

sudo cp -a ~/project/llama.cpp/build-cuda /opt/llama
sudo chown -R llama:llama /opt/llama

systemd ユニットを作成

作成していく。

cat << 'EOF' | sudo tee /etc/systemd/system/llama-server.service
[Unit]
Description=llama.cpp OpenAI-compatible inference server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=llama
Group=llama
WorkingDirectory=/opt/llama/bin

ExecStart=/opt/llama/bin/llama-server \
  -m /opt/ds4/ds4flash.gguf \
  --gpu-layers all \
  --no-mmap \
  --host 0.0.0.0 \
  --port 11434

Restart=on-failure
RestartSec=5

TimeoutStartSec=0
TimeoutStopSec=120
KillSignal=SIGINT

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

設定反映

sudo systemctl daemon-reload
sudo systemctl enable llama-server
sudo systemctl start llama-server

以上。

参考資料

変更履歴

日付 変更内容
2026/8/5 新規作成
2026/8/6 HTTPS 用フラグを追加