0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

26/05/12日記 WindowsでWSL2 使って faster-whisper を構築し、PowerShell から一発起動できるようにする

0
Last updated at Posted at 2026-05-12

はじめに

会議や授業の録音とかをスムーズに文字起こしできるよう、faster-whisper を WSL2 上に構築して PowerShell からエイリアスのように起動できる環境を作りました。

faster-whisper は OpenAI の Whisper を CTranslate2 で再実装したもので、同じ精度のまま最大 4 倍の高速化とメモリ使用量の削減を実現しています。授業 1 コマ (90 分) を 10〜15 分で処理できるのは、この高速化があってこそです。

グラボはRTX5060Tiつかってます。

最新の RTX 50 シリーズ (Blackwell, sm_120) は ML 系ライブラリのサポートがまだ過渡期で、地味につまずきポイントが多かったので、寄り道も含めて全部記録しておきます。

ゴール

PowerShell で

f-whisper .\授業01.m4a

と打つだけで、WSL 内の faster-whisper (large-v3 モデル)が GPU で書き起こしを実行し、同じフォルダに .txt / .timestamp.txt / .srt の 3 形式で結果を出力する。

環境

  • Windows 11
  • NVIDIA RTX 5060 Ti 16GB (Blackwell, sm_120)
  • WSL2 + Ubuntu 26.04
  • Python 3.12 (uv で管理)
  • faster-whisper 最新版

寄り道 1: WSL2 ネイティブか Windows ネイティブか

最初に悩んだのがここでした。最終的には、WSL2使うことにしました。
理由:

  • faster-whisper は GPU 推論に cuDNN を要求するが、Windows ネイティブだと cudnn_ops_infer64_8.dll の PATH 問題でハマる人が多い
  • WSL2 なら Windows 側のドライバを共有して、WSL 内部には CUDA Toolkit を入れなくていい
  • pip 経由で nvidia-cublas-cu12 nvidia-cudnn-cu12 を入れる方式が公式推奨で、Linux の方が情報が多い
  • 50 シリーズ (Blackwell) 対応の修正は Linux 側に先に入る傾向がある

Windows ネイティブが向くのは「リアルタイムでマイク入力を扱う」「GUI アプリと統合する」みたいなケース。バッチ的にファイルを食わせるだけなら WSL2 で確実に幸せになれます。


ステップ 1: WSL2 + Ubuntu 26.04 セットアップ

WSL を別ドライブに置きたい場合(C ドライブを圧迫したくない場合)、--locationでインストール時に保存場所を指定できます。

wsl --install -d Ubuntu-26.04 --location W:\WSL\Ubuntu-26.04

確認:

wsl -l -v

Ubuntu-26.04 Stopped 2 のように VERSION が 2 になっていれば OK。VERSION 1 だと GPU が使えません。


ステップ 2: WSL から GPU が見えるか確認

WSL に入って:

nvidia-smi

グラボが認識されていれば成功です。ここでは CUDA Version: 13.x のような表示が出ることがありますが、これは「ドライバが対応する最大の CUDA バージョン」を表示しているだけで、実際に使うランタイム(CUDA 12)とは別物なので気にしなくて大丈夫です。

WSL 側に CUDA Toolkit を別途インストールする必要はありません。


つまずきポイント 1: Python 3.14 は使えない

Ubuntu 26.04 の標準 Python はバージョン 3.14。これがそのまま使えると思ったら罠でした。

$ python3 --version
Python 3.14.4

faster-whisper の中核である ctranslate2 (および PyAV) は C 拡張を含むため、Python のマイナーバージョンごとに wheel が必要ですが、3.14 用の wheel はまだ存在しません

公式リポジトリの Issue でも「3.14 で失敗、3.13 で成功」という報告があり、関連エコシステム (whisply, WhisperX) もパッケージ要件として Python <3.14, >=3.10 を指定しています。
3.12が安定しているので、そちらをいれます。


ステップ 3: uv のインストール

Python 3.12 をどう入れるか。Ubuntu 26.04 の apt には 3.12 のパッケージが無いので、pyenvuvconda か、選択肢があります。

今回は uv を選択。Rust 製で速く、Python のバージョン管理 + パッケージ管理 + venv 管理を一つでこなせる。(物知りげに言っていますが初めて使いました)

curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version

ステップ 4: 作業ディレクトリと venv の作成

cd ~
mkdir -p tools/fasterWhisper
cd tools/fasterWhisper
uv venv --python 3.12
source .venv/bin/activate
python --version   # Python 3.12.x

uv が Python 3.12 を自動でダウンロードして .venv/ を作ってくれます。

寄り道: 作業場所のおすすめ

私はWSL ディストリ本体 (ext4.vhdx) を W ドライブに置いたので、/home/... 配下に何を作ってもすべて W ドライブの中に書かれます。/mnt/c/... (Windows の C を直接見るマウントポイント) を作業場所にすると I/O が極端に遅くなるので、必ず Ubuntu ホーム配下で作業します。


ステップ 5: faster-whisper + CUDA ライブラリのインストール

uv pip install faster-whisper
uv pip install nvidia-cublas-cu12 "nvidia-cudnn-cu12==9.*"

nvidia-cudnn-cu12==9.* がポイント。最新の ctranslate2CUDA 12 + cuDNN 9 のみサポート。cuDNN 8 を入れてしまうと ctranslate2==4.4.0 までダウングレードが必要になります。

Blackwell (5060 Ti) は CUDA 12.8+ / cuDNN 9 が必須なので、最新のままで OK。

ちなみに ffmpeg は 不要。faster-whisper は内部で PyAV (ffmpeg バンドル版) を使うので、m4a / mp4 / mp3 などをそのまま渡せます。


ステップ 6: 動作確認

つまずきポイント 2: LD_LIBRARY_PATH の組み立て

公式 README にあるコマンドをそのまま実行したらエラーになりました。

export LD_LIBRARY_PATH=$(python -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))')
TypeError: expected str, bytes or os.PathLike object, not NoneType

これは Python 3.12+ と最新の nvidia 系 wheel の組み合わせで起きる namespace package 問題で、__file__None を返すケースがあるためです。

__path__ を使う書き方に修正:

export LD_LIBRARY_PATH=$(python -c "import nvidia.cublas, nvidia.cudnn; import os; print(os.path.join(list(nvidia.cublas.__path__)[0], 'lib') + ':' + os.path.join(list(nvidia.cudnn.__path__)[0], 'lib'))")

GPU でモデルロード確認

python -c "
from faster_whisper import WhisperModel
m = WhisperModel('tiny', device='cuda', compute_type='float16')
print('GPU model loaded OK')
"

GPU model loaded OK が出れば成功。Blackwell 環境での最大の関門突破です。

Warning: You are sending unauthenticated requests to the HF Hub という警告が出ますが、これは Hugging Face のレート制限の話で、初回モデルダウンロード以降はアクセスしないので無視して OK です。


ステップ 7: テスト音源で書き起こし確認

定番テスト音源 (Whisper エコシステムでよく使われる JFK の演説) をダウンロード:

curl -LO https://github.com/openai/whisper/raw/main/tests/jfk.flac
python -c "
from faster_whisper import WhisperModel
m = WhisperModel('tiny', device='cuda', compute_type='float16')
segments, info = m.transcribe('jfk.flac')
for s in segments:
    print(f'[{s.start:.2f}-{s.end:.2f}] {s.text}')
print(f'lang={info.language} prob={info.language_probability:.2f}')
"

JFK の有名な一節が出力されれば成功。

そのまま 'tiny''large-v3' に変えて、本番モデルのダウンロード(約 3GB)と動作も確認しておきます。


ステップ 8: transcribe.py を作る

引数を受け取って文字起こし結果を出力するスクリプト。

~/tools/fasterWhisper/transcribe.py:

import argparse, os, sys
from faster_whisper import WhisperModel

p = argparse.ArgumentParser()
p.add_argument("audio", help="音声/動画ファイルのパス")
p.add_argument("--model", default="large-v3", help="モデル名 (tiny/base/small/medium/large-v3)")
p.add_argument("--language", default="auto", help="言語コード (ja/en/auto)")
p.add_argument("--device", default="cuda")
p.add_argument("--compute-type", default="float16", help="float16/float32/int8_float16")
p.add_argument("--no-vad", action="store_true", help="VADを無効化")
p.add_argument("--output-dir", default=None, help="出力先 (省略時は入力と同じ場所)")
p.add_argument("--format", default="txt,srt", help="出力形式 カンマ区切り (txt,srt,vtt)")
p.add_argument("--initial-prompt", default=None, help="専門用語などのヒント")
args = p.parse_args()

lang = None if args.language == "auto" else args.language

print(f"Loading model: {args.model} ({args.compute_type}) on {args.device}...", file=sys.stderr)
model = WhisperModel(args.model, device=args.device, compute_type=args.compute_type)

print(f"Transcribing: {args.audio}", file=sys.stderr)
segments, info = model.transcribe(
    args.audio,
    language=lang,
    vad_filter=not args.no_vad,
    initial_prompt=args.initial_prompt,
)
print(f"Detected language: {info.language} (prob={info.language_probability:.2f})", file=sys.stderr)

audio_dir, audio_name = os.path.split(os.path.abspath(args.audio))
base_name = os.path.splitext(audio_name)[0]
out_dir = args.output_dir or audio_dir
os.makedirs(out_dir, exist_ok=True)
base = os.path.join(out_dir, base_name)

def ts(t, sep=","):
    h, rem = divmod(int(t), 3600)
    m, s = divmod(rem, 60)
    ms = int((t - int(t)) * 1000)
    return f"{h:02}:{m:02}:{s:02}{sep}{ms:03}"

def ts_short(t):
    h, rem = divmod(int(t), 3600)
    m, s = divmod(rem, 60)
    return f"{h:02}:{m:02}:{s:02}"

formats = [f.strip() for f in args.format.split(",")]
files = {}
if "txt" in formats:
    files["txt"] = open(f"{base}.txt", "w", encoding="utf-8")
    files["timestamp_txt"] = open(f"{base}.timestamp.txt", "w", encoding="utf-8")
if "srt" in formats:
    files["srt"] = open(f"{base}.srt", "w", encoding="utf-8")
if "vtt" in formats:
    files["vtt"] = open(f"{base}.vtt", "w", encoding="utf-8")
    files["vtt"].write("WEBVTT\n\n")

for i, seg in enumerate(segments, 1):
    text = seg.text.strip()
    if "txt" in files:
        files["txt"].write(text + "\n")
        files["timestamp_txt"].write(f"[{ts_short(seg.start)} - {ts_short(seg.end)}] {text}\n")
    if "srt" in files:
        files["srt"].write(f"{i}\n{ts(seg.start)} --> {ts(seg.end)}\n{text}\n\n")
    if "vtt" in files:
        files["vtt"].write(f"{ts(seg.start,'.')} --> {ts(seg.end,'.')}\n{text}\n\n")
    print(f"[{seg.start:6.2f} - {seg.end:6.2f}] {text}", file=sys.stderr)

for f in files.values():
    f.close()

output_exts = []
if "txt" in formats:
    output_exts.extend(["txt", "timestamp.txt"])
if "srt" in formats:
    output_exts.append("srt")
if "vtt" in formats:
    output_exts.append("vtt")
print(f"Done. Output: {base}.[{','.join(output_exts)}]", file=sys.stderr)

出力例

jfk.txt (素のテキスト):

And so my fellow Americans,
ask not what your country can do for you,
ask what you can do for your country.

jfk.timestamp.txt (タイムスタンプ付き):

[00:00:00 - 00:00:03] And so my fellow Americans,
[00:00:03 - 00:00:07] ask not what your country can do for you,
[00:00:07 - 00:00:11] ask what you can do for your country.

jfk.srt (字幕用):

1
00:00:00,000 --> 00:00:03,000
And so my fellow Americans,
...

ステップ 9: run.sh (シェルラッパー) を作る

PowerShell から呼ばれる入り口。venv 有効化と LD_LIBRARY_PATH 設定をここで吸収します。

~/tools/fasterWhisper/run.sh:

#!/usr/bin/env bash
set -e

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$DIR/.venv/bin/activate"

export LD_LIBRARY_PATH=$(python -c "import nvidia.cublas, nvidia.cudnn; import os; print(os.path.join(list(nvidia.cublas.__path__)[0], 'lib') + ':' + os.path.join(list(nvidia.cudnn.__path__)[0], 'lib'))")

exec python "$DIR/transcribe.py" "$@"

実行権限を付与:

chmod +x run.sh

なぜ LD_LIBRARY_PATH を毎回設定する必要があるのか

pip でインストールした NVIDIA 系ライブラリの .so ファイルは .venv/lib/.../nvidia/cublas/lib/ などの奥まった場所に置かれます。OS のローダーは標準ではここを見ないので、Python 起動前に明示的に教えてあげる必要があります。

システムレベルで CUDA を入れた場合(apt install)は標準パスに置かれるので不要ですが、その方式はバージョン管理が面倒なので、現代では pip + venv + LD_LIBRARY_PATH が公式推奨です。


ステップ 10: PowerShell プロファイルに f-whisper を登録

ここからは Windows 側の作業。

つまずきポイント 3: 実行ポリシー

最初にプロファイルを読み込もうとすると:

このシステムではスクリプトの実行が無効になっているため...

というエラーが出ます。Windows のデフォルトでは PowerShell スクリプトの実行が制限されているためです。

解決:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
  • CurrentUser スコープなので、自分のユーザーアカウントだけに適用 (管理者権限不要)
  • RemoteSigned はローカルスクリプトは許可、ネットからダウンロードしたものは署名必要

つまずきポイント 4: プロファイルファイルがそもそも無い

$PROFILE 変数はパスを返してくれますが、実体のファイルは自分で作る必要があります

Test-Path $PROFILE

False が返ったら作成:

New-Item -Path $PROFILE -Type File -Force

-Force で親フォルダごと作ってくれます。

プロファイルを編集

notepad $PROFILE

以下を貼り付けて保存:

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8

function f-whisper {
    if ($args.Count -eq 0) {
        Write-Host "Usage: f-whisper <audio-file> [--model large-v3] [--language ja] [--format txt,srt] ..."
        Write-Host ""
        Write-Host "Examples:"
        Write-Host "  f-whisper .\lecture01.m4a"
        Write-Host "  f-whisper .\lecture01.mp4 --model medium"
        Write-Host "  f-whisper .\lecture01.m4a --initial-prompt '情報科学。固有名詞: 田中先生'"
        return
    }

    $file = $args[0]
    if (Test-Path $file) {
        $resolved = (Resolve-Path $file).Path
        $drive = $resolved.Substring(0, 1).ToLower()
        $rest_path = $resolved.Substring(2).Replace('\', '/')
        $file = "/mnt/$drive$rest_path"
    } else {
        Write-Host "Warning: file not found locally: $file" -ForegroundColor Yellow
    }

    $rest = @()
    if ($args.Count -gt 1) {
        $rest = $args[1..($args.Count - 1)]
    }

    wsl -d Ubuntu-26.04 【ここにrun.shまでのpath $file @rest
}

ポイント

  • @rest: PowerShell の splatting。配列を個別の引数として展開する書き方。これで --model medium などのオプションが argparse にそのまま流れる
  • wsl -d Ubuntu-26.04: 明示的にディストリ指定。複数 WSL を入れていても確実に Ubuntu-26.04 が呼ばれる

プロファイル反映

. $PROFILE

確認

Get-Command f-whisper

Function f-whisper と出れば登録成功です。


動作確認

デスクトップに test.m4a という音声ファイルがあったとします。

cd C:\Users\xxx\Desktop
f-whisper .\test.m4a

実行後、デスクトップに以下が生成されます:

  • test.txt (素のテキスト)
  • test.timestamp.txt (範囲付きタイムスタンプ)
  • test.srt (字幕)

使い方バリエーション

# 軽量モデルで高速処理
f-whisper .\test.m4a --model medium

# 言語を明示
f-whisper .\test.mp4 --language ja

# 専門用語を伝えて精度向上
f-whisper .\統計学01.m4a --initial-prompt "統計学。固有名詞: 田中先生、ベイズ統計、最尤推定"

# vtt も含めて全形式出力
f-whisper .\test.m4a --format txt,srt,vtt

# 出力先を変える
f-whisper .\test.m4a --output-dir C:\Users\xxx\Documents\transcripts

# 量子化で速度を稼ぐ
f-whisper .\test.m4a --compute-type int8_float16

速度の目安 (RTX 5060 Ti, large-v3, float16)

90 分授業を 約 10〜15 分 で処理。コマンド打ってコーヒー入れて戻ってきたら終わっている感覚です。


まとめ

レシピが固まれば、あとは PowerShell からシュッと呼べる定型作業になります。

私はやったことすぐ忘れるので備忘録てきな記事なのですが、まあ同じ構成で困っている人の参考になれば幸いです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?