🎙️
Cloud Run の GPU で faster-whisper を動かす
Cloud Run の GPU で faster-whisper を動かしてみたら思ったより大変だったので残しておきます。ちなみに、GPU なしでは CPU数を8、メモリを16GiBにしても1時間の音声ファイルを文字起こしするのに1時間半〜2時間ほどかかりました。
Cloud Run の設定
Cloud Run での設定方法は公式ドキュメントに詳しく書かれているので割愛します。
Dockerfile
執筆時点では CUDA 12.2 がプリインストールされているようなのですが、この状態から faster-whisper が動くようにビルドするのに苦労しました。試行錯誤の過程は省略しますが、cuBLAS
と cuDNN
を追加でインストールする必要がありました。また、途中でダウンロードしている Debian のパッケージファイルが大きいので最後に削除してイメージサイズを減らします(削除したら 13.8GB -> 2.1GB になりました!)。
なお、大きい音声ファイルでも大丈夫なように Hypercorn を使って HTTP/2 に対応しました(HTTP/1 ではリクエスト・レスポンスともに 32MiB が上限)。
FROM python:3.12-slim-bookworm
# uvをインストール
COPY /uv /bin/uv
# パッケージをインストール
COPY app /app
WORKDIR /app
COPY requirements.txt .
RUN uv venv --python=python3.12 && \
uv pip install --no-cache-dir -r requirements.txt
# CUDA まわりで足りないものをインストール
RUN apt update && \
apt install -y --no-install-recommends wget && \
## cuBLAS
apt update && \
apt install -y --no-install-recommends wget && \
wget -q https://developer.download.nvidia.com/compute/cuda/12.9.0/local_installers/cuda-repo-debian12-12-9-local_12.9.0-575.51.03-1_amd64.deb && \
dpkg -i cuda-repo-debian12-12-9-local_12.9.0-575.51.03-1_amd64.deb && \
cp /var/cuda-repo-debian12-12-9-local/cuda-*-keyring.gpg /usr/share/keyrings/ && \
apt update && \
apt install -y --no-install-recommends libcublas-12-9 && \
## cuDNN
wget -q https://developer.download.nvidia.com/compute/cudnn/9.10.1/local_installers/cudnn-local-repo-debian12-9.10.1_1.0-1_amd64.deb && \
dpkg -i cudnn-local-repo-debian12-9.10.1_1.0-1_amd64.deb && \
cp /var/cudnn-local-repo-debian12-9.10.1/cudnn-*-keyring.gpg /usr/share/keyrings/ && \
apt update && \
apt install -y --no-install-recommends cudnn-cuda-12 && \
## 不要なファイルを削除
rm -f /usr/share/keyrings/*.gpg && \
rm -f /var/cuda-repo-debian12-12-9-local/cuda-*-keyring.gpg && \
rm -f /var/cudnn-local-repo-debian12-9.10.1/cudnn-*-keyring.gpg && \
rm -f cuda-repo-debian12-12-9-local_12.9.0-575.51.03-1_amd64.deb && \
rm -f cudnn-local-repo-debian12-9.10.1_1.0-1_amd64.deb && \
apt purge -y --auto-remove cuda-repo-debian12-12-9-local cudnn-local-repo-debian12-9.10.1 && \
apt purge -y --auto-remove wget && \
apt clean
EXPOSE 8080
# 起動
CMD ["uv", "run", "hypercorn", "main:app", "--bind", "0.0.0.0:8080", "--access-logfile", "-", "--error-logfile","-"]
なお、 CUDA 12.2 以外の環境では NVIDIA の公式サイトから対応するバージョンのパッケージファイルを探せばよさそうです。
main.py
API 本体はお好きなように実装してください。
app/main.py
from dataclasses import asdict
from fastapi import FastAPI, UploadFile, HTTPException
from faster_whisper import WhisperModel
import json
import logging
import os
from shutil import copyfileobj
from tempfile import NamedTemporaryFile
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
faster_whisper_logger = logging.getLogger("faster_whisper").setLevel(logging.DEBUG)
app = FastAPI()
# モデルを初期化
model_name = "large-v3-turbo"
device = "cuda"
logger.info(f"モデルをロード中: {model_name} ({device})")
model = WhisperModel(model_name, device=device)
logger.info("ロード完了")
@app.post("/transcribe")
def transcribe_audio(upload_file: UploadFile):
temp_file_path = None
try:
logger.info("一時ファイルに保存")
with NamedTemporaryFile(delete=False, suffix=f"-{upload_file.filename}") as temp_file:
temp_file_path = temp_file.name
copyfileobj(upload_file.file, temp_file)
logger.info(f"文字起こし開始: {temp_file_path}")
segments_generator, info_obj = model.transcribe(temp_file_path)
segments = list(segments_generator)
logger.info(f"文字起こし完了")
return {
"info": asdict(info_obj),
"segments": segments,
}
logger.info("処理完了")
except Exception as e:
logger.error(f"エラー発生: {e}")
raise HTTPException(status_code=500, detail=str(e))
finally:
if temp_file_path and os.path.exists(temp_file_path):
os.remove(temp_file_path)
logger.info(f"一時ファイルを削除完了: {temp_file_path}")
Build & Run
ローカルでのコマンドですが、Cloud Run でもちゃんとビルドできました。
docker build -t transcription_api .
docker run -p 8080:8080 --rm transcription_api
サンプルリクエスト
こちらから拝借したサンプル音声を投げてみます。
api_endpoint="http://localhost:8080/transcribe"
curl -X POST \
--form "upload_file=@data/sample_audio.mp3" \
$api_endpoint | jq > result.json
ちゃんと動いてそうです。35 秒の音声ファイルを 1 秒くらいで文字起こししてくれました。90 分のファイルでも 3 分ほどでできました。
result.json(一部省略)
{
"info": {
"language": "ja",
"language_probability": 0.9990234375,
"duration": 35.172,
"duration_after_vad": 35.172,
"all_language_probs": [...],
"transcription_options": {
"beam_size": 5,
...
"hotwords": null
},
"vad_options": null
},
"segments": [
{
"id": 1,
"seek": 0,
"start": 0.3,
"end": 6.04,
"text": "パラ言語情報ということなんですが、簡単に最初に復習をしておきたいと思います。",
"tokens": [...],
"avg_logprob": -0.056525735622819734,
"compression_ratio": 1.7802197802197801,
"no_speech_prob": 0.055755615234375,
"words": null,
"temperature": 0.0
},
{
"id": 2,
"seek": 0,
"start": 6.92,
"end": 13.88,
"text": "こうやって話しておりますと、それはもちろん言語的情報を伝えるということが一つの重要な目的なんでありますが、",
"tokens": [...],
"avg_logprob": -0.056525735622819734,
"compression_ratio": 1.7802197802197801,
"no_speech_prob": 0.055755615234375,
"words": null,
"temperature": 0.0
},
{
"id": 3,
"seek": 0,
"start": 14.040000000000001,
"end": 18.12,
"text": "同時にパラ言語情報、そして非言語情報が伝わっております。",
"tokens": [...],
"avg_logprob": -0.056525735622819734,
"compression_ratio": 1.7802197802197801,
"no_speech_prob": 0.055755615234375,
"words": null,
"temperature": 0.0
},
{
"id": 4,
"seek": 0,
"start": 18.240000000000002,
"end": 25.46,
"text": "この三文法は藤崎先生によるものでして、パラ言語情報というのは、要は意図的に制御できる。",
"tokens": [...],
"avg_logprob": -0.056525735622819734,
"compression_ratio": 1.7802197802197801,
"no_speech_prob": 0.055755615234375,
"words": null,
"temperature": 0.0
},
{
"id": 5,
"seek": 2546,
"start": 25.46,
"end": 30.92,
"text": "和社がちゃんとコントロールして出しているんだけども、言語情報と違って連続的に変化する。",
"tokens": [...],
"avg_logprob": -0.09018049581811347,
"compression_ratio": 1.3518518518518519,
"no_speech_prob": 0.000060617923736572266,
"words": null,
"temperature": 0.0
},
{
"id": 6,
"seek": 2546,
"start": 31.3,
"end": 34.64,
"text": "カテゴライズすることがやや難しい、そういった情報であります。",
"tokens": [...],
"avg_logprob": -0.09018049581811347,
"compression_ratio": 1.3518518518518519,
"no_speech_prob": 0.000060617923736572266,
"words": null,
"temperature": 0.0
}
]
}
Discussion