1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ROCm 7.14 → 10.0.0 を gfx1151 で移行したら「移植」がほとんど要らなかった話 — whisperX / CTranslate2 / NeMo / 音声対話AI を全部通して確認する

1
Posted at

Summary
Ryzen AI MAX+ 395 (gfx1151) の ROCm を 7.14.0 → 10.0.0 に上げた記録。
手順書は「旧版を purge してから新版を入れる」と書いていたが、実際に調べると
ROCm 10 は 7.14 と共存インストールできる/opt/rocm が update-alternatives
方式だからで、順序を逆にすると「ROCm 無しの時間帯」が消えて切り戻しも効く。
依存プロジェクト側は身構えていたが、CTranslate2 も whisperX も NeMo も再ビルド不要だった。
torch の wheel が ROCm ランタイムを丸ごと同梱しているため、システム ROCm を
入れ替えても影響を受けない。これを憶測でなく /proc/self/maps で確定させた。
llama.cpp すら 7.14 でビルドしたバイナリが ROCm 10 でそのまま動く(soname が同じ)。
一方で hipconfig --version7.15 を返す、apt autoremove が NPU の予備カーネルを
消しにくる、ビルドディレクトリをリネームすると RUNPATH で壊れる、といった罠は踏んだ。
最後に音声対話AI(VOICEVOX / llama-server / NeMo STT / three-vrm)を丸ごと起動して、
発話 → 応答まで通ることを確認するところまで。

はじめに

ROCm 10.0.0 が出たので、音声対話AIを動かしている手元の mini PC を 7.14 から上げた。

この手の移行で怖いのは本体よりも周辺で、うちの場合は

  • whisperX(faster-whisper 経由の CTranslate2 が ROCm にリンクしている)
  • NeMo(nvidia の ASR。whisperX と同一プロセスに同居させている)
  • llama.cpp の HIP ビルド
  • 上記を束ねた音声対話AI一式

がぶら下がっている。事前に書いた手順書には「CTranslate2 は即死する。最優先で再ビルド」
と書いていた。これは結果的に間違いだった、というのがこの記事の一番の収穫になる。

憶測で「たぶん壊れる」と書いた箇所を一つずつ実測で潰していったら、
やることが半分以下に減った。その過程を、踏んだ罠込みで残しておく。

検証環境

機種 GMKtec NucBox EVO X2
CPU/GPU AMD Ryzen AI MAX+ 395 w/ Radeon 8060S (gfx1151, RDNA 3.5)
メモリ 96GB unified(BIOS で 48GB を VRAM に割当)
OS Ubuntu 26.04 (resolute)
kernel 7.0.0-30-generic(GA カーネル)
移行前 ROCm 7.14.0-3 (repo.amd.com/rocm/packages-multi-arch/ubuntu2604)
移行後 ROCm 10.0.0-4 (stable.repo.amd.com/rocm/core/packages/ubuntu2604)
PyTorch 2.8.0+rocm7.12.0(gfx1151 専用 wheel)
NPU XDNA2 / XRT 2.25.37(今回は無関係だが後述)

ROCm 10.0.0 の compatibility matrix では Ubuntu 26.04 は GA kernel 7.0
gfx1151 は正式サポート対象として載っている。手元の 7.0.0-30-generic
そのまま要件を満たしていたので、HWE カーネルの hold といった小細工は不要だった。

$ uname -r
7.0.0-30-generic
$ apt-mark showhold
(空)

なお Ubuntu 26.04 は公式アーカイブにも ROCm を同梱していて apt install rocm が通るが、
中身は 7.1.0 相当で古い。最新を使いたいなら AMD のリポジトリを足す必要がある。


第1部 — ROCm 7.14 → 10.0.0

つまづき1: 「消してから入れる」は危険だった

AMD の公式ドキュメントには「ROCm 7.2.4 以前が入っているならアンインストールしてから進めろ」
と書いてある。自分の手順書もそれに倣って「7.14 を purge → ROCm 10 を install」の順にしていた。

だがこの順序には穴がある。先に消した後で ROCm 10 が入らなかったら、ROCm 無しの機械が残る。
移行作業でいちばん避けたい状態がこれだ。

そこで消す前に、共存できないかを調べた。結論として共存できる。根拠は3つ:

1. パッケージ側に競合宣言が無い

$ curl -s https://stable.repo.amd.com/rocm/core/packages/ubuntu2604/dists/stable/main/binary-amd64/Packages \
  | awk '/^Package: amdrocm-base10\.0$/{f=1} f&&/^(Conflicts|Replaces|Breaks|Depends):/{print} f&&/^$/{exit}'
Depends: libgcc-s1, libstdc++6, python3, libc6, amdrocm-runtime10.0

ConflictsReplacesBreaks も無い。

2. apt のシミュレーションで削除が0件

$ apt-get install -s amdrocm-core-sdk10.0-gfx1151 | grep -c '^Inst'
52
$ apt-get install -s amdrocm-core-sdk10.0-gfx1151 | grep -c '^Remv'
0

3. /opt/rocm が update-alternatives 方式

これが決定的だった。TheRock 系のパッケージング(7.13 以降)では、/opt/rocm の中身は
実体ディレクトリではなく alternatives へのシンボリックリンクになっている。

$ ls -l /opt/rocm
lrwxrwxrwx  amdgcn  -> /etc/alternatives/rocm-amdgcn
lrwxrwxrwx  bin     -> /etc/alternatives/rocm-bin
lrwxrwxrwx  core    -> /etc/alternatives/core
lrwxrwxrwx  core-7  -> /etc/alternatives/core-7
drwxr-xr-x  core-7.14          ← 実体はここ
lrwxrwxrwx  include -> /etc/alternatives/rocm-include
lrwxrwxrwx  lib     -> /etc/alternatives/rocm-lib

つまり core-7.14/core-10.0/別ディレクトリとして並べられる設計になっている。
ファイル衝突は起きない。

というわけで順序を逆にした。先に ROCm 10 を入れて検証し、それから 7.14 を消す。
これなら ROCm 無しの時間帯が発生しないし、問題があれば alternatives を戻すだけで済む。

つまづき2: GPG 鍵を上書きすると切り戻せなくなる

公式手順は鍵をこう置けと言う。

wget https://stable.repo.amd.com/rocm/gpg/packages.gpg -O - | \
    gpg --dearmor | sudo tee /etc/apt/keyrings/amdrocm.gpg > /dev/null

ところが手元では同じパス /etc/apt/keyrings/amdrocm.gpg を旧リポジトリが既に使っていた
このまま上書きすると、鍵が違った場合に旧リポジトリの検証が壊れる。
つまり「ROCm 10 がダメだったら 7.14 に戻す」という退路が消える。

比べてみたら、バイト単位で同一だった。

$ gpg --show-keys --with-fingerprint new-amdrocm.gpg
pub   rsa4096 2026-01-26 [SCEAR] [有効期限: 2029-01-25]
      D0F0 04A0 025A 1145 C780  7FCD 0701 EAC4 D5E0 2107
$ gpg --show-keys --with-fingerprint /etc/apt/keyrings/amdrocm.gpg
pub   rsa4096 2026-01-26 [SCEAR] [有効期限: 2029-01-25]
      D0F0 04A0 025A 1145 C780  7FCD 0701 EAC4 D5E0 2107
$ cmp new-amdrocm.gpg /etc/apt/keyrings/amdrocm.gpg && echo IDENTICAL
IDENTICAL

同一なので取得ごと省略した。クリーンな機械なら公式どおりでいい。
**要点は「既存の鍵と同じパスに書く前に中身を比べろ」**ということ。

リポジトリ登録

sudo tee /etc/apt/sources.list.d/amdrocm-stable.sources << 'EOF'
X-Repo-Id: amdrocm-stable
Types: deb
URIs: https://stable.repo.amd.com/rocm/core/packages/ubuntu2604/
Suites: stable
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/amdrocm.gpg
Enabled: yes
EOF

sudo apt update

旧リポジトリの rocm.list はこの時点では消さない。 切り戻し用に残しておく。

入手可能性をここで確認する。ここで候補が出なければ、まだ何も壊していないので引き返せる。

$ apt-cache policy amdrocm-core-sdk10.0-gfx1151
  インストールされているバージョン: (なし)
  候補:               10.0.0-4

ROCm 10 を 7.14 と共存インストール

sudo apt install amdrocm-core-sdk10.0-gfx1151

実測でダウンロード 0.9GB、展開後 8.0GB。7.14(8.0GB)と一時的に共存するので
16GB 使う。新規52パッケージ、7.14 の削除は0件

alternatives は auto モードなので、優先度が高い方に自動で切り替わる

$ update-alternatives --display core
  リンクは現在 /opt/rocm/core-10.0 を指しています
/opt/rocm/core-10.0 - 優先度 1375134277
/opt/rocm/core-7.14 - 優先度 983081250

数字が大きい ROCm 10 が勝つ。/opt/rocm/bin/opt/rocm/lib も、
これで ROCm 10 側を指すようになる。

この時点で再起動は不要。 amdgpu ドライバはカーネル in-tree で、今回は一切触っていない。

つまづき3: hipconfig --version は 10.0.0 を返さない

検証しようとして最初に面食らったのがこれ。

$ /opt/rocm/bin/hipconfig --version
7.15.26333-0000000

ROCm 10 を入れたのに 7.15。バージョンが戻ったように見えるが、これは正しい。
ROCm 10.0.0 が同梱する HIP のバージョンが 7.15 なだけで、製品バージョンとは別物。
実際 compatibility matrix でも HIP のバージョンは製品番号と一致していない。

製品バージョンを見たいならこっち。

$ cat /opt/rocm/core-10.0/.info/version
10.0.0

ROCm 10 の動作検証(7.14 を消す前に)

デバイス認識。

$ rocminfo | grep -i gfx1151
  Name:                    gfx1151
      Name:                    amdgcn-amd-amdhsa--gfx1151
$ rocm-smi
0  1  0x1586, 50234  34.0°C  12.029W  ...  auto  1%  2%

コンパイラも見ておく。後で llama.cpp を再ビルドするのに使うので、ここで壊れていたら困る。

// t.hip
#include <hip/hip_runtime.h>
#include <cstdio>
__global__ void k(float*a){a[threadIdx.x]=threadIdx.x*2.0f;}
int main(){float*d;hipMalloc(&d,64*4);hipLaunchKernelGGL(k,dim3(1),dim3(64),0,0,d);
float h[64];hipMemcpy(h,d,64*4,hipMemcpyDeviceToHost);
hipDeviceProp_t p;hipGetDeviceProperties(&p,0);
printf("h[63]=%.1f arch=%s\n",h[63],p.gcnArchName);return 0;}
$ /opt/rocm/bin/hipcc --offload-arch=gfx1151 t.hip -o t
$ LD_LIBRARY_PATH=/opt/rocm/lib:/opt/rocm/lib/llvm/lib ./t
h[63]=126.0 arch=gfx1151

コンパイルも実行も通った。ここまで来て初めて 7.14 を消しにかかる。

もしダメだったら sudo update-alternatives --set core /opt/rocm/core-7.14 で即座に戻せる。
7.14 はまだ入ったままなので、これが本当に効く。

つまづき4: グロブ 'amdrocm7.14*' はほとんど当たらない

7.14 を消す段になって気づいたのだが、パッケージ名がこうなっている。

amdrocm-base7.14
amdrocm-blas7.14-gfx1151
amdrocm-core-sdk7.14-gfx1151
amdrocm-llvm7.14
...

バージョン番号が名前の途中に埋まっている。だから 'amdrocm7.14*' という
グロブは amdrocm-blas7.14 にも amdrocm-base7.14 にも当たらない
これで消したつもりになると、大半が残る。

dpkg の実リストから抜くのが確実。

# まずシミュレートして、ROCm 10 が巻き添えにならないことを確認する
apt-get -s remove --purge $(dpkg -l | awk '$1=="ii" && $2 ~ /^amdrocm/ && $2 ~ /7\.14/ {print $2}') \
  | grep -E '^(Remv|Purg)' | grep -c '10\.0'
# => 0

sudo apt purge $(dpkg -l | awk '$1=="ii" && $2 ~ /^amdrocm/ && $2 ~ /7\.14/ {print $2}')

結果は52パッケージちょうど削除、ROCm 10 への波及ゼロ、残留設定(rc)もゼロ。
/opt/rocm からは core-7.14/ が消え、alternatives は core-10.0 を指したままになった。

シミュレーションで削除件数を数えるとき、apt-get -s remove --purge の出力は
Remv ではなく Purg で始まる行になる。grep '^Remv' だけ見ていると
「削除0件」と誤読するので注意(実際に一度やらかした)。

つまづき5: apt autoremove が NPU の予備カーネルを消しにくる

purge の後、反射的に apt autoremove を打ちたくなるが、シミュレートしたらこうなった。

$ apt-get -s autoremove | grep -E '^(Remv|Purg)' | awk '{print $2}'
linux-headers-7.0.0-28-generic
linux-headers-7.0.0-28
linux-main-modules-zfs-7.0.0-28-generic
linux-image-7.0.0-28-generic
linux-modules-7.0.0-28-generic
linux-tools-7.0.0-28-generic
linux-tools-7.0.0-28

ROCm と全く関係ない旧カーネル 7.0.0-28 一式が対象になっている。
この機械は NPU (XDNA2) を使っていて、xrt-amdxdnaDKMS で入っている。

$ dkms status
xrt-amdxdna/2.25.260102.56.release, 7.0.0-28-generic, x86_64: installed
xrt-amdxdna/2.25.260102.56.release, 7.0.0-29-generic, x86_64: installed
xrt-amdxdna/2.25.260102.56.release, 7.0.0-30-generic, x86_64: installed

3カーネル分ビルドされている。予備を消すと NPU 側の退避先が減るだけで、
ROCm のアップグレードには何の関係もない。実行しなかった。

ROCm 10 の検証が全部済んでから、旧リポジトリ定義だけ消して締める。

sudo rm -f /etc/apt/sources.list.d/rocm.list
sudo apt update

第2部 — whisperX / CTranslate2 は再ビルドが要らなかった

ここが今回いちばん予想と違ったところ。

「即死する」と書いていた

CTranslate2 は ROCm 対応フォークをソースからビルドして /usr/local/lib に入れてある。
ビルド時のリンク先はシステムの /opt/rocm なので、7.14 を消したら動かなくなる ——
と考えて、手順書には「即死。最優先で再ビルド」と書いていた。

実際、ldd はそう見える。

$ ldd /usr/local/lib/libctranslate2.so | grep -E 'hip|roc'
	libhipblas.so.3 => /opt/rocm/lib/libhipblas.so.3
	libamdhip64.so.7 => /opt/rocm/lib/libamdhip64.so.7
	librocblas.so.5 => /opt/rocm/lib/librocblas.so.5
	librocsolver.so.0 => /opt/rocm/lib/librocsolver.so.0
	...

ところが動く

念のため、システム ROCm を見せない状態で動かしてみた。

$ env -u LD_LIBRARY_PATH python -c "
import torch, ctranslate2
x=torch.randn(512,512,device='cuda'); print('torch gpu:', float((x@x).sum())!=0)
print('ct2 devices:', ctranslate2.get_cuda_device_count())"
torch gpu: True
ct2 devices: 1

動く。/etc/ld.so.conf.d/ に ROCm のエントリは無いので、ld キャッシュ経由でもない。

/proc/self/maps で確定させる

「たぶん torch が持っているライブラリを掴んでいる」という推測で終わらせず、
プロセスが実際にロードしている .so を数えた。

import torch, ctranslate2, re
ctranslate2.get_cuda_device_count()
libs = {m.group(1) for l in open('/proc/self/maps')
        if (m := re.search(r'(/\S+\.so[.0-9]*)$', l.strip()))
        and re.search(r'hip|roc|amd', m.group(1), re.I)}
print('/opt/rocm:', sum(l.startswith('/opt/rocm') for l in libs))
print('venv     :', sum('site-packages' in l for l in libs))
/opt/rocm: 3
venv     : 70

70個が venv 内、/opt/rocm からは3個だけ。 しかもその3個は
libhsa-amd-aqlprofile64.so と OpenMP (libomp.so / libarcher.so) で、
HIP や BLAS の本体ではない。

なぜこうなるか

gfx1151 用の PyTorch wheel は ROCm ランタイムを丸ごと同梱している

.venv/lib/python3.12/site-packages/
  _rocm_sdk_core/lib/              libamdhip64.so.7, libamd_comgr.so.3, libhsa-runtime64.so.1 ...
  _rocm_sdk_libraries_gfx1151/lib/ libhipblas.so.3, librocblas.so.5, libhiprand.so.1 ...

そして import torch はこれらを先に読み込む。後から import された CTranslate2 は、
既にプロセスに載っている torch 同梱の 7.12 に解決される。だから
システム側が 7.14 だろうが 10.0 だろうが影響を受けない。

以前から「import torchimport ctranslate2 より先に」というルールで
運用していたのだが、その理由がここで腑に落ちた。これは単なる相性の問題ではなく、
CTranslate2 がどのランタイムに解決されるかを決めている
当然 ROCm 10 でもこの順序は必要なまま。

なお librocsolver.so.0librocm_kpack.so.0 は venv 側に存在しない。
これらは /opt/rocmlibrocblas.so.5 が引く依存で、venv 版の librocblas は
引かない。ldd の出力だけ見ていると「venv に足りないライブラリがある」と
誤解するポイント。実際にロードされるものを見ないと分からない。

PyTorch は上げなかった

ROCm 10 世代の wheel も出ている。

python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ \
  "torch[device-gfx1151]==2.13.0+rocm10.0.0" \
  "torchvision[device-gfx1151]==0.28.0+rocm10.0.0" \
  "torchaudio==2.11.0+rocm10.0.0"

が、上げなかった。torchaudio 2.9 で torchaudio.infoAudioMetaData が削除されていて、
whisperX の VAD/話者分離に使っている pyannote-audio がこれに依存している
ROCm 10 世代の torchaudio は 2.11 なので、上げると音声側が壊れる。

システム ROCm と torch のバージョンは独立に選べる(それが第2部の結論そのもの)ので、
torch 2.8.0+rocm7.12.0 のまま ROCm 10 に載せている。実際それで動いている。

rocm[libraries,device-gfx1151] という wheel もあるが、これは ROCm SDK を入れるだけで
PyTorch は入らない。torch が欲しいなら torch[device-gfx1151] を指定する。


第3部 — llama.cpp

soname が同じなので、旧バイナリがそのまま動く

7.14 でビルドした llama-cli の依存を ROCm 10 環境で見たら、全部解決していた

$ ldd build/bin/llama-cli | grep -c 'not found'
0
$ ldd build/bin/llama-cli | grep -E 'hip|roc' | head -3
	libhipblas.so.3 => /opt/rocm/lib/libhipblas.so.3
	libamdhip64.so.7 => /opt/rocm/lib/libamdhip64.so.7
	librocblas.so.5 => /opt/rocm/lib/librocblas.so.5

ROCm 10 も soname は libamdhip64.so.7 のまま。メジャーバージョンが 7→10 に
飛んだのはあくまで製品名で、ABI の識別子は据え置きということらしい
(hipconfig が 7.15 を返すのと平仄が合っている)。

リンクが通るだけでカーネルが動くとは限らないので、実際に推論させた。

$ build/bin/llama-bench -m Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf -p 128 -n 32 -ngl 99 -r 3
| qwen35moe 35B.A3B Q4_K | 20.81 GiB | 34.66 B | ROCm | 99 | pp128 | 617.08 ± 18.97 |
| qwen35moe 35B.A3B Q4_K | 20.81 GiB | 34.66 B | ROCm | 99 |  tg32 |  50.20 ±  0.62 |

7.14 でビルドしたバイナリが ROCm 10 上で普通に動く。 再ビルドは必須ではなかった。

とはいえ 7.14 のヘッダ由来の潜在的なズレを残したくないので、再ビルドはした。
旧ビルドは残してある。

つまづき6: ビルドディレクトリをリネームすると壊れる

ここで一番派手に転んだ。

動いている build/ を潰したくなかったので、build-rocm10/ に別途ビルドして、
検証後にリネームして入れ替えようとした。

cmake -B build-rocm10 -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_HIP_COMPILER=/opt/rocm/lib/llvm/bin/clang++
cmake --build build-rocm10 -j$(nproc)
# ここまでは動作確認 OK

mv build build-rocm714-backup && mv build-rocm10 build

これで起動しなくなった。

$ build/bin/llama-cli --list-devices
build/bin/llama-cli: error while loading shared libraries:
libllama-cli-impl.so: cannot open shared object file: No such file or directory

libllama-cli-impl.sobuild/bin/ちゃんと存在する。原因は RUNPATH。

$ objdump -x build/bin/llama-cli | grep RUNPATH
  RUNPATH  /home/user/llama.cpp/build-rocm10/bin:/opt/rocm/core-10.0/lib:/opt/rocm/lib:

ビルドディレクトリの絶対パスが焼き込まれている。 リネームした瞬間に無効になる。

最近の llama.cpp は共有ライブラリ構成になっていて(以前のビルドは bin/.so
0個だったが、今回は libllama-cli-impl.so などが並ぶ)、この差が効いた。

解決

最終的に置く場所で configure し直せば済む。オブジェクトファイルは再利用されるので、
再コンパイルは走らず再リンクだけで終わる(実測で1分未満)。

cd ~/llama.cpp
cmake -B build -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
$ objdump -x build/bin/llama-cli | grep RUNPATH
  RUNPATH  /home/user/llama.cpp/build/bin:/opt/rocm/core-10.0/lib:/opt/rocm/lib:

このとき -DCMAKE_HIP_COMPILER=... を付けたまま再 configure すると
Configuring incomplete, errors occurred! で落ちた。既存キャッシュにコンパイラが
記録済みの状態で指定し直したのが原因らしい。フラグを外して実行したら通り
CMAKE_CACHEFILE_DIR も新しいパスに更新された。

教訓: 最初から最終的な場所でビルドする。 退避したいなら古い方を rename する

ベンチ比較

Qwen3.6-35B-A3B-UD-Q4_K_XL(20.81 GiB)、-ngl 99 -r 3

ビルド pp128 (t/s) tg32 (t/s)
7.14 ビルド(ROCm 10 上で実行) 617.08 ± 18.97 50.20 ± 0.62
ROCm 10 ビルド 592.63 ± 2.54 50.10 ± 0.58

生成速度(tg32)は誤差の範囲で同じ。pp は数%低い値が出ているが、
旧ビルドでも測定ごとに 604〜617 の幅があり、有意差とは言えない。

ROCm 10 にしても速くも遅くもならなかった、というのが正直な結果。


第4部 — NeMo と音声対話AI

最後に、これらを束ねている音声対話AI一式を丸ごと起動して確認する。
start_all.sh で4サービスが立つ構成になっている。

サービス ポート 中身
VOICEVOX 50021 音声合成(docker / CPU)
llama-server 9931 Qwen3.6-35B-A3B
ttllm 8001 STT (NeMo / whisperX) + オーケストレーション
three-vrm 8000 ブラウザ側の VRM 表示

ROCm 依存は2箇所

移行の影響を受けうるのは次の2つだった。

1. llama.cpp/build/bin/llama-server

start_all.sh が絶対パスで直接叩いている。

LLAMA_BIN="/home/$USER/llama.cpp/build/bin/llama-server"
[[ -x "$LLAMA_BIN" ]] || die "llama-server が見つかりません: $LLAMA_BIN"

第3部で再ビルドしたものを同じ build/ に置いたので無変更で通った。
build-rocm10/ のままにしていたら、ここで落ちていた。
(第3部のリネーム事故を踏んだおかげで、結果的に正しい場所に落ち着いた)

2. ttllm/.venv — whisperX とは別の venv

これが見落としやすい。whisperX 用の venv とは別に、NeMo と whisperX を
同居させた共用 venv がある。

~/whisperx/whisperX-rocm/.venv    ← whisperX 単体用
~/AIassistant/ttllm/.venv         ← NeMo + whisperX 共用(Python 3.12)

こちらも torch 2.8.0+rocm7.12.0 を同梱しているので、第2部の結論がそのまま効く。
確認だけして無変更。

$ ttllm/.venv/bin/python -c "
import torch
print(torch.__version__, torch.cuda.get_device_name(0))
import ctranslate2
print(ctranslate2.__version__, ctranslate2.get_cuda_device_count())"
2.8.0+rocm7.12.0 Radeon 8060S Graphics
4.6.2 1

プロジェクト自身のゲートを使う

このプロジェクトには verify_coexist.py という検証スクリプトが置いてあった。
「NeMo と whisperX が1プロセスに同居できること」を確かめるために以前書いたもので、
今回の移行検証にそのまま使える。

$ ttllm/.venv/bin/python ttllm/verify_coexist.py
load: 25.3s  (EncDecRNNTBPEModelWithPrompt)
--- whisperX ---
load: 1.9s

=== 転写結果の比較 ===
音源                     NeMo                whisperX
human_greeting         こんにちは            こんにちは。
human_mountain         日本で二番目に高い山は   日本で2番目に高い山は、
human_river            日本で一番長い川は      日本で一番長い川は、
zundamon_greeting      こんにちは            こんにちは
zundamon_mountain      日本で二番目に高い山は   日本で2番目に高い山は
zundamon_river         日本で一番長い川は      日本で一番長い川は

=== 判定 ===
OK: 同一プロセスで両バックエンドがロード・転写でき、NeMo は既存結果と一致

転写結果が移行前のリファレンスと一致している。ここまで出れば NeMo 側は問題ない。

移行のたびに手で確認するのは面倒なので、プロジェクト側にこういうゲートを
用意しておくと移行作業がだいぶ楽になる
、というのは今回の副次的な収穫だった。

全体を起動して通す

$ ./start_all.sh
[launch]   VOICEVOX is up
[launch]   llama-server is up
[launch]   ttllm is up
[launch] STT を warmup (モデルロード + 初回推論を先に済ませる) ...
[launch]   warmup 完了
[launch]   three-vrm is up

ttllm の /health で STT の状態を見る。

$ curl -s http://localhost:8001/health
{"ok":true,
 "stt":{"backend":"nemo","requested":"auto","fallback":"whisperx","fallback_active":false,
        "model":"nvidia/nemotron-3.5-asr-streaming-0.6b","device":"cuda","loaded":true},
 "llama":{"url":"http://localhost:9931","reachable":true}}

backend: nemo / device: cuda / fallback_active: false
NeMo が GPU で動いていて、whisperX へのフォールバックは発動していない。

実際に音声を投げる。

$ curl -s -X POST http://localhost:8001/transcribe -F "audio=@human_mountain.wav"
{"transcript":"日本で二番目に高い山は"}

音声 → STT → LLM の通し。

$ time curl -s -X POST http://localhost:8001/voice_chat -F "audio=@human_mountain.wav"
real	0m1.409s

transcript: 日本で二番目に高い山は
reply: それ、富士山だよ!
       日本一高い山は富士山で、二番目は白山(はくさん)だね!...

1.4秒で応答。移行前と体感は変わらない。
(内容の正しさは別問題。二番目は北岳である。これはモデルの知識の話で ROCm とは関係ない)

音声合成も確認。

$ curl -s -X POST -G "http://localhost:50021/audio_query" \
    --data-urlencode "text=こんにちは、テストです" --data-urlencode "speaker=3" -o q.json
$ curl -s -X POST "http://localhost:50021/synthesis?speaker=3" \
    -H 'Content-Type: application/json' --data-binary @q.json -o out.wav
$ file out.wav
out.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 24000 Hz

つまづき7: VOICEVOX に日本語を URL エンコード無しで投げると死ぬ

最初こう書いて失敗した。

$ curl -s -X POST "http://localhost:50021/audio_query?text=テストです&speaker=3" -o q.json
$ cat q.json
Invalid HTTP request received.

生の日本語を URL に入れたのが原因。-G --data-urlencode を使えばいい。
ROCm とは無関係な curl の使い方の問題だが、移行検証中にこれを踏むと
「VOICEVOX が壊れた」と勘違いしかねないので書いておく。

つまづき8: reasoning モデルは content が空で返る

llama-server の疎通確認でこうなった。

$ curl -s http://localhost:9931/v1/chat/completions -d '{"messages":[...],"max_tokens":64}'
{"choices":[{"message":{"content":""}}],"usage":{"completion_tokens":64,...}}

content が空。壊れたかと思ったが、Qwen3.6 は reasoning モデルで、
思考トークンだけで max_tokens: 64 を使い切っていただけだった。
reasoning_content を見ると思考過程がびっしり入っている。

max_tokens を 200 にしたらちゃんと返ってきた。

content: 日本で一番高い山は富士山です。

疎通確認のつもりで小さい max_tokens を指定すると、正常なのに壊れて見える。


移行後の状態

/opt/rocm/core-10.0/.info/version   10.0.0
rocminfo                            gfx1151 / AMD Radeon 8060S Graphics
amdrocm*10.0 パッケージ             52
amdrocm*7.14 パッケージ             0(残留設定も0)
whisperX (torch 2.8.0 + ct2 4.6.2)  無変更で動作
NeMo (nemotron-3.5-asr 0.6b)        無変更で動作、転写結果は移行前と一致
llama.cpp                           tg32 50.10 t/s(移行前 50.20 と同等)
音声対話AI 一式                     start_all.sh で全サービス起動、通しで応答 1.4s
NPU (XDNA2 / XRT)                   無影響

HSA_OVERRIDE_GFX_VERSION はこの環境では元々設定していない
gfx1151 ネイティブビルドで揃えているので、設定するとむしろ壊れる。ROCm 10 でも同じ。

まとめ — 踏んだ罠一覧

# 対処
1 手順書どおり「purge → install」だと ROCm 無しの時間帯ができる 共存できるので順序を逆に。alternatives で切り戻し可
2 GPG 鍵を既存と同じパスに上書きすると旧リポジトリの検証が壊れる 上書き前に cmp で比較。今回は同一だったので何もしない
3 hipconfig --version7.15 を返す 製品版は /opt/rocm/core-10.0/.info/version
4 グロブ 'amdrocm7.14*' が大半のパッケージに当たらない dpkg -l + awk で実リストから抽出
5 apt autoremove が NPU の DKMS 用予備カーネルを消しにくる 実行しない
6 ビルドディレクトリをリネームすると RUNPATH が壊れる 最終的な場所で configure し直す(再リンクのみで済む)
7 VOICEVOX に日本語を URL エンコード無しで投げると Invalid HTTP request curl -G --data-urlencode
8 reasoning モデルで content が空に見える max_tokens を増やすか reasoning_content を見る

そして一番の収穫は、「たぶん壊れる」を実測で潰すと作業が激減するということ。

CTranslate2 は再ビルドすると30分近くかかる。「即死する」と思い込んだまま進めていたら、
不要なビルドを2回(CTranslate2 と Python バインディング)やった上で、
PyTorch まで巻き添えで上げようとして pyannote を壊していたはずだ。

ldd は「リンク時に何を要求しているか」しか教えてくれない。
実際に何がロードされるかは /proc/self/maps を見る
この一手間で、移行計画の半分が消えた。

移行そのものは、共存インストールという逃げ道を先に確保してしまえば、
拍子抜けするほど素直だった。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?