Piper TTSで英単語3,859語の音声を無料生成 ― GPUより効いたバッチモード11倍速と自動発音モード

開発eurekapu-nuxt4

Piper TTSで英単語3,859語の音声を無料生成 ― GPUより効いたバッチモード11倍速と自動発音モード

朝は昨日止めた画像バッチの再開から始めた。英単語レッスンサイトの3,859語に例文イラストを付ける最終ラン。残り約900枚を failed ゼロで走らせつつ、待ち時間でミラーカラム型ビューアの作り直しを進めさせた。この時点では、夕方までの主役が画像だと思っていた。実際に山になったのは、午後にひょいと頼んだ音声のほうだった。

セクション区分、これ意味あるのか

ビューアを眺めていて、前から引っかかっていたことを口に出した。セクションが「スタンダード1〜7」と「アドバンス」に分かれ、その中にさらに難度ラベルが重なっている。単語の難易度で切っているらしいのだが、学習者としてこの区分を頼りに何かを選べる気がしない。

思いつきで壊す前に、手元のデータで裏を取ることにした。3,859語の語源情報の内訳を全数集計してもらうと、語源ファミリーで再編できるだけの材料が揃っていることがわかった。再編の提案までもらったが、着手の判断は今日はしていない。画像と音声のバッチが走っている横で構造をいじるのは分が悪い。

PC画面の余白がでかすぎる

ビューアをPCで開くと、コンテンツが中央の細い帯に収まり、左右に白が広がっていた。構成は「見出し語→語義→メモ→例文→和訳→画像」で固定なのだから、幅を目一杯使って文字も画像も大きくしていい。ただし英語の例文が折り返すと読みづらくなる。その匙加減だけ伝えて、調整させた。

結果はビューア専用のスタイル上書き(デスクトップ901px以上のみ)に収まった。一覧モードとモバイルには触れていない。折り返しの心配は、全3,859例文の最長が88字だと実測してもらって消えた。この幅なら全語が1行に収まる。例文の長さを生成時のフォーマットゲートで揃えておいたことが、思わぬところで効いた。

単語と例文に音声を付ける ― GPUの出番はなかった

「音声も確か無料でできたはずですよね」と聞いてみると、以前に別コンテンツの141語でやった Piper TTS(OSS・完全ローカル実行)の仕組みがそのまま使えるという。単語と例文で約7,700ファイル。そのまま生成を回し始めた。

ところが遅い。12分で924ファイル、毎分77ファイル。当初見積もりの6倍速には遠く、完了見込みが1.5時間先に伸びた。6並列にしたのに1.7倍しか速くなっていない、という数字を眺めながら、この機械にはRTX 3070が載っていることを思い出した。CPUじゃなくてGPUでできないんですかね、と聞いた。GPUに投げれば片付く話だろうと思っていた。

違った。調べさせると、ボトルネックは演算ではなかった。1ファイル生成するたびに piper.exe を起動し、63MBのモデルを読み直す。この起動オーバーヘッドが支配的だから、並列を増やしても効かなかったのだ。piper には --json-input というバッチ入力モードがあり、モデルを1回読んだまま全文を連続合成できる。切り替えたら11倍速が出た。GPU化には CPU専用ビルドの差し替えと CUDA セットアップが要るが、もう必要なくなった。

R2へのアップロードでも同じ形の問題を踏んだ。毎分57ファイルで2時間超の見込み。こちらは wrangler の起動オーバーヘッドが支配的だったので、並列を10から30に上げて45〜50分に縮めた。「遅いときは演算より起動回数を疑う」を、1日に2回体で覚えた。

ページ遷移したら発音するモード

音声が載る目処が立ったので、使い方も足した。矢印キーでページを送りながら単語を眺めるとき、遷移のたびに自動で発音してほしい。ヘッダーのバーに「単語」「例文」のチェックを2つ置き、単語だけ・例文だけ・両方の3パターンを選べるようにして、設定は localStorage に保存する ― と伝えて実装させた。ユニットテストと、設定が残ることを確かめるE2Eも足してある。

検証で一度だけ肝を冷やした。チェックを入れたのに localStorage が null に見えたのだ。バグではなく、Vue の watcher が非同期フラッシュのため、クリックと同じティックで読んでいただけだった。読み直したら保存されていた。

スロットリンクって何ですか

画像バッチは午後にかけてペースが落ちていった。序盤は13分で110枚だったのが、後半は30分で110枚。「あと1時間ですか」と完走時刻を聞き返す羽目になった。報告に繰り返し出てくる言葉を私は「スロットリンク」と聞き取っていて、そもそも何なのかを聞いた。スロットリング。エラーで止まるのではなく、混雑時にサーバー側が処理量を意図的に絞る流量制限で、「今は混んでいるので後にして」と断られる分だけ遅くなる。止まっているのではなく絞られている、とわかれば待ち方も決まる。

画像は10時半すぎに完走した。done 4,212・failed 0。今日1日で939枚、途中でDB接続エラー起因の孤児が1件出たが回収済み。これで3,859語すべてに例文イラストが付いた。

締めの泥仕事

締めは泥仕事が続いた。バックグラウンドのアップロードが「stopped」通知を返すのに実際はプロセスが生きている、という罠を2回踏み、最後は出力をファイル直書きにしてデタッチ起動へ切り替えさせた。E2Eは wrangler 30並列でマシンが高負荷のため、普段17秒のスイートが25分経っても終わらない。一度止めて、アップロード完了後にフォアグラウンドで chromium だけ回して確定させた。

学びメモ

  • 生成が遅いとき、まず疑うのは演算力ではなく起動回数。プロセス起動+モデル読み直しのオーバーヘッドは並列化では消えない
  • GPU追加のような足し算より、モデル読み込み1回のバッチモードという引き算が先
  • コンテンツ生成時にフォーマット(例文長)を揃えておくと、後段のレイアウト判断が「最長88字なら1行」の一言で済む
  • localStorage の検証はクリックと同じティックで読まない(Vue の watcher は非同期フラッシュ)
  • 高負荷マシンでのE2Eは粘らない。静かになってから回すほうが早い

明日以降

  • セクション区分の語源ファミリー再編に着手するか決める。実測データと提案は手元に揃っている
  • 自動発音モードをモバイル実機で触って、遷移のたびの再生が煩わしくないか確かめる
#TTS#Piper#英単語学習 #R2 #Nuxt