FaceFusionのWebUIは開くのに、動画をアップロードしてもプレビューがいつまでも表示されない――この症状をWindows 11とRTX 3080 Ti 12GBのローカル環境で切り分けました。最初はコンテンツフィルターの影響を疑いましたが、結論から言えば、主因はそこではありませんでした。ONNX Runtimeが必要とするcuDNN 9のDLLはPC内に存在していたものの、FaceFusionを起動したPythonプロセスから正しく探索・事前ロードできず、CUDA推論がCPUへフォールバックしていたことが本当の原因です。
WebUIは実際に5桁ポートでHTTP 200まで起動し、動画からのプレビュー画像生成にも成功しました。CUDA実セッション、実プレビュー、1496フレームの動画変換まで確認しているため、「画面だけ開いた」という判定ではありません。
症状:アップロードは通るのにプレビューが出ない
対象はFaceFusion 3.6.1のローカルWebUIです。ソース画像と約50秒の720p動画は選択でき、WebUI自体も起動します。しかし、動画プレビューの更新が極端に遅く、利用者から見ると「何も生成されない」状態でした。
この症状では、次の4つを混同しないことが重要です。
- Webサーバーが開くこと:GradioがHTTP応答できるか。
- 動画が読めること:OpenCVやFFmpegが指定時刻のフレームを復号できるか。
- プレビューUIが値を返すこと:FaceFusionの
update_preview_image()が実画像を生成できるか。 - GPUで推論できること:ONNX Runtimeの実セッションがCUDAで動いているか。
HTTP 200だけでは後ろ3項目を証明できません。また、onnxruntime.get_available_providers()にCUDAExecutionProviderが並んでいても、それはインストールされたONNX RuntimeがCUDA対応ビルドであることを示すだけです。必要DLLを読み込んだ実セッションがGPUで推論できるかは別に確認する必要があります。
最初の仮説:コンテンツフィルターのせいか
FaceFusionには入力フレームを解析する処理があります。そのため「動画を選べるのにプレビューが出ない」という見え方から、コンテンツ解析が止めている可能性を最初に調べました。
今回のローカルラッパーでは、利用ポリシーを管理するための解析関数が明示的に差し替えられており、ストリーム解析と単一フレーム解析はいずれもFalseを返す状態でした。さらに、プレビュー側が保持しているインポート参照にも差し替えが届いていることを確認しました。動画の指定フレームも(720, 1280, 3)の配列として復号できています。
つまり、コンテンツ解析や動画デコードは「疑う価値のある仮説」でしたが、今回の停止点ではありませんでした。ここで調査を終えず、推論バックエンドの標準エラーを追ったことが解決につながりました。
本当の原因:cudnn64_9.dllを実行時に見つけられない
単フレームの実推論を行うと、ONNX Runtimeがcudnn64_9.dllを読み込めず、CUDA Execution Providerの初期化に失敗していました。その後はCPU Execution Providerへフォールバックするため、完全なクラッシュにはなりません。エラー画面が出ず、ただプレビューが非常に遅くなるのが厄介な点です。
環境にはonnxruntime-gpu 1.24.4が入り、ビルド対象はCUDA 12.8でした。必要なCUDA・cuDNN DLLは共有Conda環境のLibrary\binなどに既に存在していました。したがって、重いCUDAやcuDNNを新たに再インストールする必要はありません。問題はファイルの不在ではなく、ローカル仮想環境から共有ランタイムへのDLL探索経路でした。
| 確認 | 結果 | 意味 |
|---|---|---|
| 動画フレーム復号 | (720, 1280, 3) | 入力動画やFFmpegが主因ではない |
| コンテンツ解析 | False | 今回の停止点ではない |
| Provider一覧 | CUDAあり | CUDA対応ビルドの存在だけを示す |
| 修正前の実推論 | cudnn64_9.dll読込失敗 | CPUフォールバックの直接証拠 |
| 修正後の実セッション | CUDA → CPUの順 | CUDAが実際に初期化された |
修正1:PowerShell側で共有DLLディレクトリを渡す
ランチャーでは、既存の共有Conda環境からTensorRT、cuDNN、CUDA Runtime、Library\binの候補を列挙し、存在するディレクトリだけをPATHと専用環境変数へ渡します。
$dllDirectories = @()
if ($condaPrefix) {
$sitePackages = Join-Path $condaPrefix 'Lib\site-packages'
$dllDirectories = @(
(Join-Path $sitePackages 'tensorrt_libs'),
(Join-Path $sitePackages 'nvidia\tensorrt\bin'),
(Join-Path $sitePackages 'nvidia\cudnn\bin'),
(Join-Path $sitePackages 'nvidia\cuda_runtime\bin'),
(Join-Path $condaPrefix 'Library\bin')
) | Where-Object { Test-Path -LiteralPath $_ }
}
$pathEntries = @($dllDirectories) + @($env:PATH -split ';')
$env:PATH = ($pathEntries | Where-Object { $_ } | Select-Object -Unique) -join ';'
$env:FACEFUSION_DLL_DIRECTORIES =
($dllDirectories | Select-Object -Unique) -join ';'
PATHだけでも動く構成はありますが、Python 3.8以降のWindows DLL探索や、ライブラリ側の読み込み順によっては不安定です。そのため次のPython側の登録と組み合わせました。
修正2:FaceFusionをimportする前にDLLを登録・事前ロードする
重要なのは処理順です。FaceFusionや推論モジュールを読み込んだ後では遅いため、エントリーポイントの先頭でDLLディレクトリを登録し、cudnn64_9.dllがある完全なランタイムをONNX Runtimeへ事前ロードします。
_DLL_DIRECTORY_HANDLES: list[Any] = []
def _configure_windows_gpu_runtime() -> None:
if os.name != 'nt':
return
dll_directories = os.environ.get(
'FACEFUSION_DLL_DIRECTORIES', ''
).split(os.pathsep)
for dll_directory in dll_directories:
if dll_directory and os.path.isdir(dll_directory):
_DLL_DIRECTORY_HANDLES.append(
os.add_dll_directory(dll_directory)
)
for dll_directory in dll_directories:
if os.path.isfile(os.path.join(dll_directory, 'cudnn64_9.dll')):
import onnxruntime
onnxruntime.preload_dlls(directory=dll_directory)
break
_configure_windows_gpu_runtime()
from facefusion import content_analyser, core
os.add_dll_directory()の戻り値をグローバル配列へ保持している点も大切です。戻り値のハンドルが破棄されると、登録した探索ディレクトリが早期に閉じられる可能性があります。「関数を呼んだのに後段でまた見失う」状態を避けるため、プロセス寿命まで保持します。
修正3:通常のWebUIではTensorRTを先頭にしない
TensorRTは定常処理を高速化できる一方、初回にはエンジンの構築やキャッシュ作成が入り、プレビュー更新が止まったように見えることがあります。そこで通常利用のbalancedプロファイルはcuda, cpuとし、TensorRTは明示的なthroughputプロファイルへ分離しました。
balanced = @{
Threads = 4
Providers = @('cuda', 'cpu')
PreferRamDisk = $true
TempFrameFormat = 'jpeg'
VideoPreset = 'medium'
VideoQuality = 88
}
quality = @{
Threads = 2
Providers = @('cuda', 'cpu')
PreferRamDisk = $true
MinimumFreeGiB = 24
TempFrameFormat = 'png'
VideoPreset = 'veryslow'
VideoQuality = 100
PixelBoost = '1024x1024'
}
日常のプレビューはbalancedで待ち時間を抑え、最終変換はqualityでPNG中間フレーム、品質100、veryslow、1024×1024ピクセルブーストを使います。なお品質100はFaceFusion/エンコーダーの最高設定であり、数学的な可逆圧縮を意味するわけではありません。
RAMディスクは「速くする」前に容量と退避を設計する
動画処理は大量の中間フレームを作ります。今回の高画質変換ではRAMディスクに37.79GiBの空きを確認してから開始し、PNGフレームを配置しました。ランチャーは必要空き容量を下回る場合やディレクトリ作成に失敗した場合、プロジェクト内の通常ディスクへ自動フォールバックします。
RAMディスクは高速ですが、再起動や電源断で消えます。そのため入力素材・最終出力・検証ログはRAMディスクへ置かず、中間フレームだけに限定します。処理後は残留ファイル0件まで確認しました。
WebUIは本当に直ったのか:3段階で確認
1. BATからWebUIを起動
利用者が実際にダブルクリックする入口と同じnewFaceFusion.batを使い、5桁ポート17868で起動しました。結果はHTTP 200で、起動ログにもProviders: cuda, cpu、RAMディスクの空き容量、プレビュー用設定が表示されました。
FaceFusion local profile: balanced
URL: http://127.0.0.1:17868
Providers: cuda, cpu
Threads: 4
Temp: R:\Temp2\NewFaceFusion\temp
Frame format: jpeg
Pixel boost: 512x512
HTTP status: 200
2. 実動画からプレビュー画像を生成
HTTP確認とは別に、WebUIが呼ぶupdate_preview_image()へ実際のソース画像と動画を渡しました。返り値は空ではなく、Gradio管理下にimage.webpが作られ、ファイルの存在と描画内容まで確認できました。これにより、アップロード後のプレビュー生成ロジックが最後まで通ることを確認しています。
3. 動画全体を最高画質プロファイルで変換
約49.8秒、1496フレームの720p動画をqualityプロファイルで最後まで処理しました。処理速度は定常値で3.70fps、FaceFusion処理は約6分43秒、起動から出力検証までの総時間は約7分3秒でした。
| 修正前の単フレーム | 約8.17秒、cuDNN読込失敗後にCPUフォールバック |
|---|---|
| 修正後の単フレーム | 約2.27秒、CUDAエラーなし |
| 動画全体 | 1496フレーム、3.70fps、約6分43秒 |
| 出力 | HEVC Main、1280×720、1496フレーム、AAC 48kHz stereo |
| 整合性 | 映像・音声デコードOK、入力と出力のフレーム数一致 |
1秒、15秒、30秒、45秒の4地点からQAフレームも抽出し、顔置換の継続性を目視確認しました。最終動画のSHA-256も記録しており、後から同一ファイルか再確認できます。
再発時の切り分けチェックリスト
- WebUIのHTTP 200だけで完了にしない:実動画を渡してプレビュー画像ファイルの生成まで確認します。
- 動画フレームを直接読む:指定時刻の配列形状が取得できるか確認します。
- コンテンツ解析の戻り値と参照先を確認する:関数本体だけでなく、プレビュー側がimport済みの参照も対象です。
- 標準エラーでDLL名を探す:
cudnn64_9.dllなど、最初のCUDA Provider初期化エラーを見落とさないようにします。 - Provider一覧だけを信用しない:モデルから実際に
InferenceSessionを作り、実推論を1回通します。 - 既存ランタイムを再利用する:DLLが既にあるなら、CUDA一式を重複インストールする前に探索パスと事前ロードを直します。
- TensorRTの初回構築を分離する:通常プレビューはCUDA優先、長尺バッチだけTensorRTを選べる構成にします。
- RAMディスクに最低空き容量とフォールバックを設ける:高速化より先に、容量不足と揮発性への対策を入れます。
まとめ:見えていた症状と、止まっていた層は違った
今回の「動画を上げてもプレビューが生成されない」という症状は、コンテンツフィルターではなく、CUDA推論の初期化失敗とCPUフォールバックによる待ち時間が主因でした。エラーで止まらず遅くなるため、UIロジックの不具合に見えたのです。
解決の要点は、既存の共有CUDA・cuDNNランタイムをランチャーで列挙し、Python側でFaceFusionのimport前にos.add_dll_directory()とonnxruntime.preload_dlls()を実行したことです。加えて、通常のWebUIからTensorRT初回構築を外し、高画質変換だけRAMディスクとPNG中間フレームを使う構成に分離しました。
FaceFusionに限らず、Windows上のGPUアプリで「CUDAは選べるのに妙に遅い」「画面は開くが結果が出ない」という場合、機能フラグやUIだけでなく、DLL探索、実セッション、実推論の3層を順に確認すると原因へ早く到達できます。