Engineering

VOICEVOXをApple containerで動かしたらCPUが4コアしか使われていなかったので割り当てを増やした

  • VOICEVOX
  • Apple Container
  • macOS
  • Apple Silicon
  • TTS

技術ニュースを RSS → Gemini で要約して Podcast 化する個人パイプラインで、TTS を Gemini TTS から VOICEVOX に置き換えられないか検討していました。常駐エンジンとして VOICEVOX を Mac で動かすにあたって、Apple 純正の container CLI を使ったところ、明らかに遅く出てくる現象に当たりました。原因はコンテナ側のデフォルト CPU 割り当てで、container run のオプション 2 つを足すだけで合成速度がほぼ 4.5 倍になります。

GitHubGitHub - apple/container: A tool for creating and running Linux containers using lightweight virtual machines on a Mac. It is written in Swift, and optimized for Apple silicon.A tool for creating and running Linux containers using lightweight virtual machines on a Mac. It is written in Swift, and optimized for Apple silicon. - apple/container

本記事は Docker Desktop ではなく、GitHub で配布されている Apple 純正の container コマンド(macOS 26 が必要、執筆時点 v1.0.0)に固有の落とし穴の話です。Docker Desktop はデフォルトで host の全コアが割り当てられるので踏まないはずです。

まずは素直に立てる

VOICEVOX 公式の Docker イメージは voicevox/voicevox_engine:cpu-latest が arm64 にも対応しているので、Apple container でも container run で素直に動きます。

container run --rm \
  -p 127.0.0.1:50021:50021 \
  voicevox/voicevox_engine:cpu-latest

起動したら HTTP API で生存確認します。VOICEVOX engine は :50021 にいて、/version が pong 代わりに使えます。

import urllib.request
with urllib.request.urlopen("http://127.0.0.1:50021/version") as r:
    print(r.read().decode())
# => "0.25.2"

ここまでは何の問題もなく動きます。

ベンチを取ったら明らかに遅い

VOICEVOX のコミュニティに KuronekoServer/voicevox_bench という測定スクリプトがあります。/synthesis を 10/50/100 文字 × 10 回計測する素直なやつで、zenn の比較表 の元データもこれです。

同じ手法をそのまま M5 Max(18 コア、6 Super コア + 12 Performance コア)の Apple container 上で叩いてみたところ、次のような数字が返ってきました。

文字数mean
100.82 s
502.73 s
1005.19 s
Avg2.92 s

zenn の表で並んでいる NVIDIA / AMD / Intel GPU と比較すると、100 文字あたり RTX 4090(0.0964 s)の 約 54 倍遅い水準で、ノート向け Radeon 780M(0.8332 s)すら届いていません。一方で voicevox_core を Rust から直叩きしたときには、同じ M5 Max で 113 文字を 1.48 秒(RTF 0.069)で生成できていました。

HTTP オーバーヘッドだけでこの差は出ません。何かをコンテナに取られている前提で container inspect を見にいきました。

Apple container はデフォルトで 4 CPU しか割り当てない

container ls -q | head -1 | xargs container inspect を見ると、resources セクションが次のようになっていました。

"resources" : {
  "cpuOverhead" : 1,
  "cpus" : 4,
  "memoryInBytes" : 1073741824
}

ホストが 18 コアあろうが、Apple container はコンテナに 4 CPU・1 GB メモリを固定で割り当てるのがデフォルトです。公式ドキュメントにもこの値は明記されていますが、container run --help-c, --cpus / -m, --memory のリファレンスには出てこないので、素直に立てると見落としやすい設定です。

-c, --cpus <cpus>       Number of CPUs to allocate to the container
-m, --memory <memory>   Amount of memory (1MiByte granularity), with optional unit

VOICEVOX engine 側はこれと別に cpu_num_threads という設定を持っていて、物理コア数と論理コア数が一致する環境(Apple Silicon はこれに該当します)では、未指定だと論理コア数の半分で動きます(Issue #1092 で提案された実装です)。コンテナ側が 4 CPU、engine 側が「その半分」つまり2 スレッドしか使えていない状態だった、というのが今回の素直な状況でした。

まずは -c-m を渡す

最初にやるのは container run 側でリソースを明示することです。M5 Max なら全コア渡してしまって構いません。

container run --rm \
  -c 18 -m 8g \
  -p 127.0.0.1:50021:50021 \
  voicevox/voicevox_engine:cpu-latest

これだけで container inspect の resources が cpus: 18, memory: 8192 MiB になります。同じベンチを取り直すと、Avg ベースで 2.92 s → 0.87 s と3.4 倍速くなりました。コンテナに 18 CPU 見えていれば、engine デフォルトの「半分」も 9 スレッドまで使える計算で、ほぼそのまま線形に効きます。

設定100 charAvg
デフォルト(cpus: 4)5.19 s2.92 s
-c 18 -m 8g(threads default = 9)1.54 s0.87 s

さらに VV_CPU_NUM_THREADS で詰める

engine 側のデフォルト「半分」を踏み倒して、全 18 スレッドを使わせます。VOICEVOX engine は --cpu_num_threads または環境変数 VV_CPU_NUM_THREADS を見るので、後者を渡すのが Apple container では楽です。

container run --rm \
  -c 18 -m 8g \
  -p 127.0.0.1:50021:50021 \
  -e VV_CPU_NUM_THREADS=18 \
  voicevox/voicevox_engine:cpu-latest

container inspectinitProcess.environmentVV_CPU_NUM_THREADS=18 が乗ったら、engine 側は 18 スレッドで動きます。ベンチ再計測の結果がこれです。

設定10 char50 char100 charAvg
デフォルト(cpus: 4)0.822.735.192.92
-c 18 -m 8g(threads default)0.260.811.540.87
-c 18 -m 8g + threads=180.220.601.110.64

最終的にデフォルトの 4.5 倍速くなりました。

VOICEVOX 公式の「半分で頭打ち」前提は M シリーズに当てはまらない

Issue #291 では、スレッド数を増やしても性能が伸びない(むしろ落ちる)現象が報告されています。Issue #1092 は、その対策としてデフォルト値を論理コア数の半分にする実装を提案したもので、cpu_num_threads のデフォルトの根拠はこちらにあります。これは Intel/AMD のハイブリッドコア(P + E)構成や SMT 込みの環境では妥当な近似ですが、Apple Silicon は別物です。

参考までに M5 Max で cpu_num_threads を振ったときの実測(voicevox_core を Rust 直叩きで、113 文字テキスト、release ビルド、warm-up 後 5 回平均、HTTP オーバーヘッドなし)はこうなりました。

cpu_num_threadsmeanRTFvs default(9)
112.44 s0.5790.16×
6(Super のみ)2.80 s0.1300.72×
0(default ≈ 半分)2.01 s0.0931.0×
12(Performance のみ)1.70 s0.0791.18×
18(全コア)1.48 s0.06871.36×

「半分で頭打ち」どころか、全コア使ったほうが 36 % 速い結果です。M5 Max は Super コアと Performance コアが混在していますが、cpu_num_threads=6(Super コア数に相当)はむしろデフォルトより遅く、12(Performance コア数に相当)でデフォルト超え、18(全コア)が最速という素直なスケールが出ました。Apple Silicon の Super/Performance 構成は Intel ハイブリッドと違って、Super コア側も合成計算に十分使えるため、半分で頭打ちにする必要がありません。

Rust 直叩き(113 文字、RTF 0.069)を zenn 表の 100 文字実測(秒)と並べると、L4 Tensor(0.134 s)、RTX 4070(0.1102 s)、RX 9070 XT(0.1514 s)より小さい数値です。文字数と指標(RTF と秒)が異なる単純比較にはなりますが、デスクトップ GPU を CPU だけで叩いている水準にあります。

Device100 文字備考
RTX 4090 CUDA0.0964 sデスクトップ GPU 最高峰
M5 Max Rust 直叩き(18 threads, 113 文字, RTF)0.069CPU only
RTX 4070 CUDA0.1102 sデスクトップ
L4 Tensor CUDA0.134 sデータセンター
Radeon RX 9070 XT DML0.1514 sデスクトップ
Radeon 780M DML(ノート)0.8332 sノート GPU
Intel Arc B580 DML1.1474 sデスクトップ低位

Apple container 経由の HTTP API でも、-c 18 -m 8g + threads=18 で Avg 0.64 s まで縮むので、Intel Arc B580 や Radeon 780M クラスは超えます。10 分の Podcast 音声を 35〜45 秒で生成できる計算で、個人用途では CPU だけで十分な水準でした。

未リリースの container 次世代版に期待

Apple container は今年(2026)に v1.0.0 がリリースされたばかりで、WWDC 2026 で発表された Container Machine で systemd 込みの「ちゃんとした Linux 環境」も入ってきています。デフォルトリソース割り当ての挙動が今後のバージョンで Docker Desktop に近づくかどうかは今のところ分かりませんが、root セッション以外のセッションごとにリソースを分ける設計が入ってくる余地はあります。

それまでは、Apple container で何か CPU バウンドなコンテナを動かすときは、まず container inspectcpus が幾つになっているか見るのが定石になりそうです。VOICEVOX に限らず、ffmpeg や ML 系を Apple container で常駐させる人は同じ罠に踏むはずです。