【Irodori-TTS】DGX Spark (GB10) でOpenAI互換TTSサーバーを構築する
はじめに
この記事は、Irodori-TTS を OpenAI 互換の HTTP API として LAN に公開するサーバー(voiceGenServer)を、NVIDIA DGX Spark(GB10)上に Docker で構築する手順をまとめたものです。
DGX Spark に搭載されている GB10 は CUDA compute capability が SM_121 と新しく、PyPI で配布されている通常の PyTorch wheel では動作しない問題があります。この記事では、その制約をどのように回避したかも含めて紹介します。
環境と前提
| 項目 | 内容 |
|---|---|
| ハードウェア | NVIDIA DGX Spark (GB10 / SM_121) |
| ベースイメージ | nvcr.io/nvidia/pytorch:26.01-py3 |
| OS | Ubuntu (Linux aarch64) |
| 公開ポート | 8088 |
GB10 における PyTorch の制約
なぜ通常の wheel が使えないのか
GB10 は CUDA compute capability 12.1 (SM_121) のアーキテクチャです。PyPI で配布されている通常の PyTorch(cu128 対応版)を使って DACVAE codec を CUDA で実行しようとすると、以下のエラーが発生します。
RuntimeError: nvrtc: error: invalid value for --gpu-architecture (-arch)
この問題を回避するため、GB10 対応が進んでいる NVIDIA NGC の PyTorch コンテナを使用します。
nvcr.io/nvidia/pytorch:26.01-py3 torch: 2.10.0a0+a36e1d39eb.nv26.01.42222806 cuda: True GPU: NVIDIA GB10 (compute capability 12.1)
torch.cuda.get_arch_list()に
sm_121は出ませんが、
sm_120/
compute_120互換として CUDA 実行は通ります。
pip から torch 系を入れてはいけない理由
Irodori-TTS の
requirements.txtには以下が含まれています。
torch>=2.10.0 torchaudio>=2.10.0 torchcodec>=0.10.0
これらをそのまま pip でインストールすると、次の問題が起きます。
| パッケージ | 問題 |
|---|---|
torch |
NGC コンテナの GB10 対応 torch が pip の通常版に上書きされる |
torchaudio |
PyTorch cu128 の wheel が CUDA 12 系ライブラリを要求し、NGC 26.01(CUDA 13.1)環境と競合する |
torchcodec |
Linux aarch64 用 wheel が存在せずインストール不可 |
そのため Dockerfile では、
requirements.txtからこれら 3 つを除外してインストールします。
torchaudio の shim について
torchaudio は NGC コンテナに含まれていませんが、Irodori-TTS 本体が
torchaudio.load/
torchaudio.save/
torchaudio.functional.resample/
torchaudio.functional.lfilterを呼んでいます。
Irodori-TTS 本体を編集せずに解決するため、サーバー起動スクリプト(
docker/run_server.py)でサーバー起動前に最小限の shim を
sys.modules["torchaudio"]に差し込んでいます。
| API | 代替実装 |
|---|---|
torchaudio.load |
soundfile.read |
torchaudio.save |
soundfile.write |
torchaudio.functional.resample |
torch.nn.functional.interpolate |
torchaudio.functional.lfilter |
scipy.signal.lfilter |
lfilterは
audiotoolsがラウドネス正規化(LUFS計算のKウェイティングフィルタ)のために CPU 実行時に呼び出します。NGC コンテナには scipy が含まれているため、これで代替できます。
ディレクトリ構成
voiceGenServer/ ├── Dockerfile ├── compose.yaml ├── .env ├── docker/ │ └── run_server.py # torchaudio shim + サーバー起動スクリプト ├── Irodori-TTS/ # git clone ├── Irodori-TTS-Server/ # git clone ├── voices/ # 参照音声置き場(.wav 等) └── .cache/huggingface/ # モデルキャッシュ(自動生成)
セットアップ手順
1. リポジトリのクローン
作業ディレクトリを作成し、必要な 2 つのリポジトリをクローンします。
mkdir -p ~/Documents/voiceGenServer cd ~/Documents/voiceGenServer git clone https://github.com/Aratako/Irodori-TTS.git git clone https://github.com/Aratako/Irodori-TTS-Server.git
動作確認済みのコミットは以下の通りです。
- Irodori-TTS:
256528abc65aa89eba5ff1388f8701a5b157076e
- Irodori-TTS-Server:
89f503cda41c30c09bcabd97b9d52f4128f4c9f3
2. 必要ディレクトリの作成
mkdir -p voices docker .cache/huggingface
3. ファイルの配置
以下の 4 つのファイルを作成します。
Dockerfile
FROM nvcr.io/nvidia/pytorch:26.01-py3
ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1 \
PYTHONUNBUFFERED=1 \
HF_HOME=/workspace/.cache/huggingface \
TORCH_FLOAT32_MATMUL_PRECISION=high
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg libsndfile1 git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace/voiceGenServer
# torch / torchaudio / torchcodec / sentencepiece を除外してインストール
COPY Irodori-TTS/requirements.txt /tmp/requirements.txt
RUN grep -v -E '^(torch|torchaudio|torchcodec|sentencepiece)([<>= @]|$)' /tmp/requirements.txt > /tmp/requirements-no-torch.txt \
&& python -m pip install --upgrade pip \
&& python -m pip install sentencepiece==0.2.1 \
&& python -m pip install --no-deps -r /tmp/requirements-no-torch.txt \
&& python -m pip install --no-deps \
accelerate argbind descript-audiotools fire flatten-dict flask gradio-client \
julius peft pyaml pydub pyloudnorm pystoi randomname safehttpx semantic-version \
torch-stoi transformers blinker brotli ffmpy gitpython groovy hf-gradio \
importlib-resources itsdangerous markdown2 orjson sentry-sdk starlette \
termcolor tomlkit typer annotated_doc \
&& python -m pip install \
"fastapi>=0.115.0" \
"pydantic-settings>=2.6.0" \
"python-dotenv>=1.0.1" \
"python-multipart>=0.0.9" \
"uvicorn[standard]>=0.32.0"
COPY Irodori-TTS /workspace/voiceGenServer/Irodori-TTS
RUN python -m pip install -e /workspace/voiceGenServer/Irodori-TTS --no-deps
COPY Irodori-TTS-Server /workspace/voiceGenServer/Irodori-TTS-Server
RUN python -m pip install -e /workspace/voiceGenServer/Irodori-TTS-Server --no-deps
COPY docker /workspace/voiceGenServer/docker
WORKDIR /workspace/voiceGenServer/Irodori-TTS-Server
EXPOSE 8088
CMD ["python", "/workspace/voiceGenServer/docker/run_server.py"]
requirements.txtから
torch/
torchaudio/
torchcodecを除外しているのが重要なポイントです。NGC コンテナの GB10 対応 torch を上書きしないようにするためです。
また、
sentencepiece>=0.1.99,<0.2は Python 3.12 / Linux aarch64 で wheel がなく、ソースビルドも失敗するため
sentencepiece==0.2.1を明示指定しています。動作上の問題は確認されていません。
compose.yaml
services:
irodori-tts-server:
build:
context: .
dockerfile: Dockerfile
image: voicegen/irodori-tts-server:gb10
working_dir: /workspace/voiceGenServer/Irodori-TTS-Server
ipc: host
user: "1000:1000"
gpus: all
ports:
- "${IRODORI_PORT:-8088}:${IRODORI_PORT:-8088}"
env_file:
- .env
environment:
HF_HOME: /workspace/.cache/huggingface
TORCH_FLOAT32_MATMUL_PRECISION: high
IRODORI_MODEL_DEVICE: cuda
IRODORI_CODEC_DEVICE: cuda
volumes:
- ./Irodori-TTS:/workspace/voiceGenServer/Irodori-TTS
- ./Irodori-TTS-Server:/workspace/voiceGenServer/Irodori-TTS-Server
- ./.cache/huggingface:/workspace/.cache/huggingface
- ./voices:/workspace/voiceGenServer/voices
- ./docker:/workspace/voiceGenServer/docker
Irodori-TTS、
Irodori-TTS-Server、
docker/をボリュームマウントしているため、これらのファイルを変更した場合は
docker compose restartだけで反映されます(ビルド不要)。
.env
# Server IRODORI_HOST=0.0.0.0 IRODORI_PORT=8088 # Optional. When set, clients must send Authorization: Bearer <value>. # IRODORI_API_KEY=change-me # Model IRODORI_HF_CHECKPOINT=Aratako/Irodori-TTS-500M-v3 IRODORI_CODEC_REPO=Aratako/Semantic-DACVAE-Japanese-32dim IRODORI_MODEL_NAME=irodori-tts IRODORI_MODEL_DEVICE=auto IRODORI_CODEC_DEVICE=auto IRODORI_MODEL_PRECISION=fp32 IRODORI_CODEC_PRECISION=fp32 IRODORI_COMPILE_MODEL=false IRODORI_COMPILE_DYNAMIC=false IRODORI_PRELOAD=false IRODORI_MODEL_LOAD_TIMEOUT=300 IRODORI_MAX_CONCURRENT_SYNTHESIS=1 IRODORI_SYNTHESIS_WAIT_TIMEOUT=300 # Voices IRODORI_VOICES_DIR=/workspace/voiceGenServer/voices IRODORI_ALLOW_NO_REF_VOICE=true # Defaults for /v1/audio/speech IRODORI_DEFAULT_RESPONSE_FORMAT=wav IRODORI_DEFAULT_NUM_STEPS=40 IRODORI_DEFAULT_T_SCHEDULE_MODE=linear IRODORI_DEFAULT_SWAY_COEFF=-1.0 IRODORI_DEFAULT_DURATION_SCALE=1.0 IRODORI_DEFAULT_CFG_SCALE_TEXT=3.0 IRODORI_DEFAULT_CFG_SCALE_SPEAKER=5.0 IRODORI_DEFAULT_CFG_GUIDANCE_MODE=independent IRODORI_DEFAULT_CHUNKING_ENABLED=true IRODORI_DEFAULT_CHUNK_MIN_CHARS=80
IRODORI_VOICES_DIRはコンテナ内の絶対パスで指定してください。
docker/run_server.py
以下のスクリプトを
docker/run_server.pyとして保存します。torchaudio の shim を
sys.modulesに差し込んでから、サーバーを起動します。
import sys
import types
import importlib.machinery
import soundfile as sf
import torch
import torch.nn.functional as F
def _as_audio_array(audio):
audio_cpu = audio.detach().to(device="cpu", dtype=torch.float32)
return audio_cpu.squeeze(0).numpy() if audio_cpu.shape[0] == 1 else audio_cpu.T.numpy()
def _save(path, audio, sample_rate, *args, **kwargs):
sf.write(str(path), _as_audio_array(audio), int(sample_rate))
def _resample(waveform, orig_freq, new_freq, *args, **kwargs):
if int(orig_freq) == int(new_freq):
return waveform
original_shape = waveform.shape
flat = waveform.reshape(-1, 1, original_shape[-1])
new_length = max(1, round(original_shape[-1] * float(new_freq) / float(orig_freq)))
resampled = F.interpolate(flat, size=new_length, mode="linear", align_corners=False)
return resampled.reshape(*original_shape[:-1], new_length)
def _load(path, *args, **kwargs):
data, sample_rate = sf.read(str(path), dtype="float32")
wav = torch.from_numpy(data)
if wav.ndim == 1:
wav = wav.unsqueeze(0)
else:
wav = wav.T
return wav, int(sample_rate)
def _lfilter(waveform, a_coeffs, b_coeffs, clamp=True, batching=False):
import numpy as np
from scipy.signal import lfilter as _scipy_lfilter
device = waveform.device
dtype = waveform.dtype
w = waveform.detach().cpu().to(torch.float32).numpy()
a = a_coeffs.detach().cpu().to(torch.float32).numpy()
b = b_coeffs.detach().cpu().to(torch.float32).numpy()
if a.ndim == 1:
result = _scipy_lfilter(b, a, w, axis=-1)
else:
result = np.zeros_like(w)
for i in range(a.shape[0]):
result[i] = _scipy_lfilter(b[i], a[i], w[i], axis=-1)
out = torch.from_numpy(result.copy()).to(dtype=dtype, device=device)
if clamp:
out = torch.clamp(out, -1.0, 1.0)
return out
# torchaudio を使えないため soundfile + torch で shim を構築する
torchaudio = types.ModuleType("torchaudio")
torchaudio.__spec__ = importlib.machinery.ModuleSpec("torchaudio", loader=None)
torchaudio.__version__ = "0.0.soundfile-shim"
torchaudio.save = _save
torchaudio.load = _load
torchaudio.functional = types.SimpleNamespace(resample=_resample, lfilter=_lfilter)
sys.modules["torchaudio"] = torchaudio
from irodori_openai_tts.__main__ import main
main()
4. ファイアウォールの設定
LAN からアクセスできるよう、使用するポートを開放します。
sudo ufw allow 8088/tcp
5. Docker イメージのビルド
docker compose build
初回はベースイメージのプルも含むため、時間がかかります。
6. サーバーの起動
docker compose up irodori-tts-server
初回リクエスト時にモデルが HuggingFace からダウンロードされます(
.envの
IRODORI_PRELOAD=falseの場合)。
API の使い方
ホストの LAN IP を
<HOST_IP>に置き換えて使用してください。
ヘルスチェック
まず、以下のコマンドでサーバーが正常に起動しているか確認します。
curl http://<HOST_IP>:8088/health
音声生成(参照音声なし)
参照音声を用意しなくても音声生成できます。
.envの
IRODORI_ALLOW_NO_REF_VOICE=trueが前提です。
curl http://<HOST_IP>:8088/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "irodori-tts",
"input": "こんにちは。これはテストです。",
"voice": "none",
"response_format": "wav"
}' \
--output speech.wav
音声生成(参照音声あり)
参照音声を指定すると、その声質に近い音声を生成できます。
# 1. voices/ に参照音声を置く
cp myvoice.wav voices/myvoice.wav
# 2. ファイル名(拡張子なし)を voice に指定する
curl http://<HOST_IP>:8088/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "irodori-tts",
"input": "こんにちは。",
"voice": "myvoice",
"response_format": "wav"
}' \
--output speech.wav
対応フォーマットは
.wav、
.flac、
.mp3、
.m4a、
.ogg、
.opus、
.aac、
.webmです。参照音声はサーバー再起動なしで即座に反映されます。
登録済みボイスの確認
curl http://<HOST_IP>:8088/v1/audio/voices
依存パッケージの問題と対処
--no-depsでインストールしているパッケージが多いのは、ベースイメージの torch を壊さないようにするためです。構築中に発生した主な問題と対処をまとめておきます。
| 問題 | 対処 |
|---|---|
starletteが見つからない |
fastapiを --no-depsなしで通常インストール |
audiotoolsが見つからない |
descript-audiotoolsが抜けていたため追加 |
flatten_dictが見つからない |
flatten-dictを --no-depsリストに追加 |
torchaudio.functional.lfilterがない |
run_server.pyの shim に scipy.signal.lfilterで実装した _lfilterを追加 |
注意点
参照音声ありで合成した場合、冒頭に短い異音が混入することがあります。LLM が出力したテキストの冒頭に不可視の文字列が入っていたことが原因と思われます。リクエスト側のスクリプトを見直すことで改善しました。
まとめ
今回は、Irodori-TTS を DGX Spark(GB10)上で OpenAI 互換の TTS API サーバーとして動かすための手順をまとめました。GB10 の CUDA 制約による torchaudio の shim 実装など、少し手間はかかりますが、一度構築してしまえば LAN 内から OpenAI 互換のインターフェースで日本語 TTS が使えるようになります。是非試してみてください。
私のXではLLMに限らず、AIを活用した業務改善情報の発信をしておりますのでご興味のある方は是非フォローをお願いします。https://x.com/Linus_lab