はじめに
AMD Ryzen AI MAX+ 395(Strix Halo / XDNA2 NPU)搭載マシンで、USB カメラ映像に対して
- YOLO11m の物体検出を NPU(VitisAI EP)でオフロード
- VLM(Nemotron Nano Omni)の日本語キャプションを GPU(ROCm)で生成
しながら Chrome に MJPEG 配信する「LLaVA-NPU」パイプラインを、Ubuntu を 26.04 へ・ROCm を 7.14 へアップグレードした後に動かし直した記録です。
記事は 2 部構成です。
- 前半: このパイプラインをどう動かすか(構成・バックエンド切替・一発起動・検証)
- 後半: アップグレード直後にどこでトラブルが起きて、どう回避したか(NPU が消える/serve が起動しない/NPU サイドカーの Python が壊れる)
検証環境: NucBox EVO X2 / Ryzen AI MAX+ 395 w/ Radeon 8060S (gfx1151) / Ubuntu 26.04 LTS / kernel
7.0.0-28-generic/ ROCm 7.14 / XRT 2.21.75 / NPU Firmware 1.1.2.65
最新のGitHub
第 1 部: どのように動かすか
1-1. 全体構成
パイプラインは 4 プロセス。tmux セッション llava の 4 ウィンドウとして起動します。
| ウィンドウ | 役割 | 実行環境 |
|---|---|---|
capture |
USB カメラ → 共有メモリ(SHM) に最新フレーム書込(1280×720 BGR, 30fps) | uv venv |
serve |
FastAPI + MJPEG 配信 + YOLO bbox / VLM caption を WebSocket 配信 | uv venv |
vlm |
llama-server(Nemotron Nano Omni, マルチモーダル) | ROCm |
npu-yolo |
NPU サイドカー(VitisAI EP で YOLO11m 推論、ローカル HTTP で bbox 配信) | Ryzen AI venv |
ポイントは NPU 推論を独立プロセス(サイドカー)に分離していること。NPU 実行には onnxruntime-vitisai + XRT の LD_LIBRARY_PATH が必要で、これは ~/ryzenai/ryzenai_venv にしか無いため、本体(torch-ROCm / ultralytics / fastapi)の venv とは混ぜません。VLM の llama-server と同じ「別プロセス + ローカル HTTP」パターンです。
[capture] [npu-yolo sidecar] [serve]
USB cam RAI venv + VitisAI EP FastAPI + MJPEG + WS
| | SHM(webcam_latest) を attach |
|--- SHM ----------->| 最新フレーム取得 |
| letterbox640 / RGB / ÷255 |
| onnx(a16w8) 推論 @ NPU |
| decode + class-aware NMS |
| 逆letterbox → 入力フレーム座標 |
| bbox JSON --GET /latest--------->| poll → /ws/bbox へ push
SHM を書く FrameSHM は純 Python(numpy + multiprocessing.shared_memory のみ、torch 非依存)なので、RAI venv からも import してフレームを読めるのが成立の鍵です。
1-2. バックエンド切替(GPU ⇄ NPU は 1 行)
config.yaml の yolo.backend を切り替えるだけ。NPU が不調なら gpu に戻せば従来の Ultralytics/ROCm 経路が serve プロセス内で動きます。
yolo:
backend: npu # npu = XDNA2 NPU(VitisAI, 別プロセス) / gpu = Ultralytics(ROCm, serve内)
なぜ A16W8 か: YOLO11m を NPU で動かすには量子化が必須ですが、XINT8(活性化 8bit)だと分類ヘッドが潰れて検出ゼロになります。A16W8(活性化 INT16 / 重み INT8)で FP32 相当の精度に回復します。
1-3. 一発起動と検証
cd ~/LLaVA-NPU
./start_all.sh # backend:npu なら npu-yolo ウィンドウも自動起動
# 停止:
./stop_all.sh
# ログ:
tmux attach -t llava # Ctrl-b 0/1/2/3 = capture / serve / vlm / npu-yolo
起動順は不問(serve 側はサイドカーが立つまで HTTP リトライ)。初回のみ VitisAI が量子化モデルをコンパイルするため、最初の 1 推論に約 20 秒かかります。サイドカーはウォームアップ完了後に /latest を出す設計なので、serve は準備できるまで自然に待ちます。
NPU で実際に走っているかは xrt-smi で確認します。
/opt/xilinx/xrt/bin/xrt-smi examine -d 0000:c6:00.1 -r all
# → HW Context=Active・Columns[0-7]・Submissions 増加 なら NPU 実行中
サイドカーの HTTP を直接叩けば bbox が見えます。
curl -s http://127.0.0.1:8082/latest
# {"frame_seq":1171,"frame_w":1280,"frame_h":720,"connected":true,
# "boxes":[{"label":"person","conf":0.894,"x1":485.8,"y1":411.7,"x2":828.7,"y2":711.3},
# {"label":"bed","conf":0.406,...}]}
あとはブラウザで http://localhost:8080/ を開き、人・物が bbox に正しく収まるか目視確認するだけです。判定基準は「xrt-smi が Active」+「ブラウザで正しい位置に bbox」です。
正常時の各ウィンドウの様子:
capture : 30.0 fps 1280x720 BGR → SHM
serve : http://localhost:8080/ HTTP 200 / VLM キャプション ~1.2s
vlm : llama-server 8081 image processed in 325 ms
npu-yolo : VitisAIExecutionProvider セッション確立 / warmup 40ms / 26.8 inf/s
第 2 部: どこでトラブルが起きて、どう回避したか
ここからが本題です。Ubuntu 26.04 / ROCm 7.14 へアップグレードした直後、上記パイプラインは動きませんでした。原因は独立した 4 つの問題で、順に切り分けました。
2-1. NPU が丸ごと消える(xrt-smi → 0 devices found)
再起動後、まず NPU デバイス自体が認識されません。
-
xrt-smi examine→ 0 devices found -
/dev/accel/が存在しない -
lsmod | grep xdna→ 空(amdxdna未ロード) - ただし PCI では見えている:
c6:00.1 ... Strix Halo Neural Processing Unit
症状 A: modprobe amdxdna が Exec format error
手動ロードを試すと、こうなります。
modprobe: ERROR: could not insert 'amdxdna': Exec format error
# dmesg: amdxdna: disagrees about version of symbol module_layout
原因は DKMS モジュールが古いヘッダ状態でビルドされていたこと。稼働カーネルは gcc-15 ビルドで module_layout の CRC が 0xe9196a28 なのに、DKMS 生成物は 0xd954c786 を要求していました。26.04 アップグレードの過程で「稼働カーネル本体」と「DKMS がビルドに使うヘッダ状態」がズレたのが正体です。
回避策は現行カーネルに対して DKMS を作り直すだけ。
sudo dkms remove xrt-amdxdna/2.21.260102.53.release --all
sudo dkms install xrt-amdxdna/2.21.260102.53.release
sudo depmod -a
# 事前確認: 要求 CRC が稼働カーネルと一致するか(.ko.zst であることに注意)
modprobe --dump-modversions /lib/modules/$(uname -r)/updates/dkms/amdxdna.ko.zst | grep module_layout
# → 0xe9196a28 module_layout (= カーネル本体と一致すれば成功見込み)
sudo modprobe amdxdna
ls -l /dev/accel/ # → accel0 が生成される
💡 カーネル同梱の in-tree
amdxdna(/lib/modules/$(uname -r)/kernel/drivers/accel/amdxdna/amdxdna.ko.zst)も存在し、その CRC は必ずカーネルと一致します。DKMS がどうしても直らなければ in-tree 版に切り替える手もありますが、XRT 2.21 ユーザ空間との ABI 互換はxrt-smi examineの成否で確認してください。今回は DKMS 版で成功しました。
症状 B: mmap ... failed (EAGAIN) — memlock 不足
ドライバはロードできても、今度は xrt-smi がこう落ちます。
xrt-smi examine: mmap(len=64MB, offset=4GB) failed (err=-11 EAGAIN)
原因は memlock リミットが **8MB(8192KB)**と低く、XRT/NPU が要求する 64MB のピン留めメモリを確保できなかったこと(root では memlock が緩く成功したことで確定しました)。
回避策は全ユーザに memlock unlimited を適用。
echo '* - memlock unlimited' | sudo tee /etc/security/limits.d/99-xrt-memlock.conf
⚠️ この設定は PAM ログイン経由のセッションにのみ効きます(反映は再ログイン後)。将来
start_all.shを systemd サービス化する場合は PAM を通らないため、unit にLimitMEMLOCK=infinityが別途必要です。
復旧確認
再起動後、一般ユーザで以下の 3 点が sudo なしで通れば OK。
ulimit -l # → unlimited ✅
ls -l /dev/accel/accel0 # crw-rw-rw-+ root render 261,0 ✅
source /opt/xilinx/xrt/setup.sh && xrt-smi examine # [0000:c6:00.1] NPU Strix Halo ✅
XRT
Version : 2.21.75
NPU Firmware Version : 1.1.2.65
Device(s) Present
[0000:c6:00.1] NPU Strix Halo ✅
2-2. start_all.sh を叩くと serve と npu-yolo だけ落ちる
NPU デバイスが復旧したので ./start_all.sh を実行すると、4 ウィンドウのうち capture / vlm は正常、serve と npu-yolo が別々の理由で起動失敗しました。どちらもアップグレードの副作用です。
症状 C: serve が ModuleNotFoundError: No module named 'fastapi'
File ".../src/server/app.py", line 22, in <module>
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
ModuleNotFoundError: No module named 'fastapi'
原因は、アップグレードで .venv が base 依存のみで再作成されていたこと。fastapi / aiortc / uvicorn / requests は pyproject.toml の optional group webrtc にあり、uv run serve(extra 指定なし)だと暗黙同期で env を base に揃えてしまい fastapi が消えるのです。
回避策は起動時に extra を明示すること。start_all.sh の serve 行を次のように修正しました。
-tmux send-keys -t "$SESSION:serve" "${ENV_PREFIX}uv run serve" C-m
+tmux send-keys -t "$SESSION:serve" "${ENV_PREFIX}uv run --extra webrtc serve" C-m
症状 D: npu-yolo サイドカーの Python が起動すらしない
これが一番厄介でした。サイドカーのウィンドウはこう出ます。
sys.executable = '/home/user/ryzenai/ryzenai_venv/venv/bin/python'
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
ModuleNotFoundError: No module named 'encodings'
インタプリタ自体が起動不能です。調べると根本原因はこうでした。
- ryzenai venv は旧
/usr/bin/python3.12(3.12.3)から--copiesで作成されていた。 - Ubuntu 26.04 で system Python が 3.14 になり、
/usr/bin/python3.12と/usr/lib/python3.12(stdlib)が消失。 - コピーされた python バイナリは残っているが、参照すべき stdlib が無くなり起動できない。
ここで安易に「新しい 3.14 で venv を作り直す」のは罠です。venv の site-packages に入っている RAI スタック(onnxruntime_vitisai 1.23.3 / voe 1.7.1 など 330 個)はすべて cp312(Python 3.12 ABI)のコンパイル済み wheel なので、3.14 では import できません。公式にも Ryzen AI は Python 3.12.x 専用(Linux Installation に Install Python 3.12.x と明記、3.13/3.14 は非対応)でした。
回避策: 壊れているのはインタプリタと stdlib だけなので、site-packages を温存したままインタプリタだけを 3.12 に差し替えます。apt に python3.12 が無い 26.04 では、uv 管理の standalone cpython-3.12.13(3.12 系なので cp312 wheel と ABI 互換)が使えます。
STD=$HOME/.local/share/uv/python/cpython-3.12.13-linux-x86_64-gnu/bin/python3.12
VENV=$HOME/ryzenai/ryzenai_venv/venv
# 旧 --copies バイナリを消してから venv を「in-place upgrade」する
# (--clear しないので site-packages 330 個は消えない)
rm -f "$VENV"/bin/python "$VENV"/bin/python3 "$VENV"/bin/python3.12
"$STD" -m venv --without-pip "$VENV" # bin/ を standalone への symlink で再生成
# 検証: VitisAIExecutionProvider が出れば成功
source $HOME/ryzenai/ryzenai_venv/setup_ryzenai_env.sh
python -c "import onnxruntime as ort, voe; print(ort.__version__, ort.get_available_providers())"
# → 1.23.3.dev... ['VitisAIExecutionProvider', 'CPUExecutionProvider']
python -m venv <既存dir>(--clear なし)は bin/ と pyvenv.cfg を作り直すだけで site-packages は保持します。旧 --copies バイナリが残っていると symlink 生成がスキップされるので、先に rm するのがコツです。
2-3. 復旧完了
以上 4 点を解消して ./start_all.sh を回すと、全 4 ウィンドウが一発で立ち上がりました。
capture : 30fps 1280x720 BGR → SHM
serve : http://localhost:8080/ HTTP 200
vlm : llama-server 8081 Nemotron キャプション ~1.2s
npu-yolo : VitisAIExecutionProvider セッション確立 / warmup 40ms
/latest → {"boxes":[{"label":"person","conf":0.73,...}]} ✅ NPU 推論
まとめ: 再発しやすいポイント
OS メジャーアップグレード後に NPU パイプラインが動かなくなったら、次の順で疑うと早いです。
-
NPU が消えたら、まず DKMS 再ビルド。 版数が同じでも稼働カーネルとヘッダがズレると
module_layoutCRC 不一致(Exec format error)が起きる。sudo dkms install xrt-amdxdna/<ver>で解消。 -
memlock unlimited は XRT/NPU の必須要件。 低いと
mmap ... EAGAINでデバイス認識に失敗する。 -
system Python 依存の venv は壊れる。
--copiesで作った venv は base の Python が消えると起動不能に。RAI は 3.12.x 専用なので、uv の standalone 3.12.13 でpython -m venv --without-pip(旧 bin を rm 後)し、site-packages 温存でインタプリタだけ差し替えるのが最短。cp312 wheel はそのまま動く。 -
uv runは extra を勝手に外す。 optional group に依存があるスクリプトはuv run --extra <group> ...で起動する。