# LatentSync 完全ガイド：ByteDanceの拡散リップシンクモデルを公式準拠で本番運用する

> ByteDanceの音声条件付き潜在拡散リップシンクモデル LatentSync を、公式ドキュメント（GitHub・論文・HuggingFace）に忠実に解説。最新1.6の仕組み、Replicate APIとセルフホストの両手順、inference_steps/guidance_scaleのチューニング、顔検出失敗・OOM・音ズレへの回復性設計まで、本番運用に必要な実装を具体コードで示します。

- 公開日: 2026-06-24
- 著者: 友田 陽大
- タグ: LatentSync, リップシンク, AI動画, 拡散モデル, Python, GPU, 生成AI, MLOps
- URL: https://tomodahinata.com/blog/latentsync-lip-sync-diffusion-model-production-guide

## 要点

- LatentSyncはByteDanceの『音声条件付き潜在拡散モデル』。SyncNet監督とTREPAで“口だけ動く違和感”を消し、論文ではHDTFのSyncNet精度を91%→94%へ改善している
- 最新の1.6は512×512学習で歯・口元のボケを解消。推論VRAMは約18GB（1.5は約8GB）で、モデル構造は1.5から不変・チェックポイント差し替えのみ
- 試すだけならReplicate API（L40S・1回約$0.11・約108秒）、作り込むならセルフホスト（conda + PyTorch 2.5.1・Apache-2.0）の二択で考える
- 品質はinference_steps[20-50]とguidance_scale[1.0-3.0]で制御し、enable_deepcacheで高速化。用途別プリセットをそのまま使える形で提示する
- 本番では顔検出失敗・長尺OOM・fps/音声フォーマット不一致が必ず起きる。セグメント分割・冪等キャッシュ・SyncNet信頼度ゲートで回復性を担保する

---

## この記事のゴール

LatentSync は ByteDance が公開した、**音声から口の動きを生成する（リップシンク）拡散モデル**です。GitHub のリポジトリ名がそのまま思想を表しています——「**Taming Stable Diffusion for Lip Sync!**（Stable Diffusion をリップシンク用に飼い慣らす）」。

本稿は、その**公式ドキュメント（[GitHub](https://github.com/bytedance/LatentSync) / [論文](https://arxiv.org/abs/2412.09262) / [HuggingFace](https://huggingface.co/ByteDance/LatentSync-1.6)）の内容に厳密に基づきつつ**、公式 README には書かれていない「**どの場面で・どう使い・どこで詰まるか**」までを、実際に動くコードで埋めるものです。読み終えたときに、次の3つができる状態を目指します。

1. LatentSync が**何をするモデルで、なぜ品質が高いのか**を、人に説明できる。
2. **Replicate API（自前GPU不要）** と **セルフホスト** のどちらを選ぶべきか判断し、**今日中に手を動かせる**。
3. デモではなく**本番**——顔検出失敗・長尺動画のOOM・音ズレ——に耐える**回復性のある実装**を組める。

> **筆者について（信頼性の開示）**：私は、動画をアップロードするだけで「音声分離 → 文字起こし → 翻訳 → 多言語吹き替え → 口元同期」まで全自動化する**AI動画ローカライズ基盤を単独で設計・実装し、本番運用**しています。その第5段（リップシンク）の実装主役は Wav2Lip 系 → MuseTalk を経て **LatentSync** に移りました。本稿の「落とし穴」と「回復性設計」は、デモ用の知識ではなく、その実運用で踏み抜いた地雷の記録です。パイプライン全体の設計は[別記事](/blog/production-ai-video-localization-lipsync-gpu-pipeline)に、案件の概要は本稿末尾の[実績リンク](/case-studies/ai-video-localization-lipsync)にまとめています。

---

## 30秒のまとめ（結論を先に）

| 観点 | 結論 |
| --- | --- |
| **何のモデルか** | 1枚の話す動画 + 任意の音声 →「**その音声を喋っているように口元を作り替えた動画**」を生成する拡散モデル |
| **何がすごいか** | SyncNet監督 + TREPA で「**音と口が合っているのに自然**」を両立。論文ではHDTF/VoxCeleb2で当時のSOTAを上回ると報告 |
| **最新版** | **LatentSync 1.6**（2025/06/11公開）。512×512学習で歯・口元のボケを改善。推論 **VRAM 約18GB** |
| **軽量版** | **LatentSync 1.5**。推論 **VRAM 約8GB** で動く。コスト最優先ならこちら |
| **試すだけ** | **Replicate API**：自前GPU不要・**1回約$0.11**・L40S・約108秒。プロトタイプ最速 |
| **作り込む** | **セルフホスト**：conda + PyTorch 2.5.1。**Apache-2.0** なので商用も可。バッチ大量処理ならこちら |
| **品質の2大ノブ** | `inference_steps`（20→50で高品質・低速）／`guidance_scale`（1.0→3.0で同期重視・歪みリスク） |
| **向く用途** | 動画ローカライズ吹替・AIアナウンサー/アバター・eラーニング・パーソナライズ動画 |
| **向かない用途** | 横顔/激しい動き/複数話者が同時に映る素材、ゼロレイテンシのライブ配信 |

「**まず自分の素材で品質を確かめたい**」なら、この後すぐの [Replicate API 章](#使い方a試すだけなら-replicate-api自前gpu不要) に飛んでください。3分で結果が出ます。

---

## LatentSync は何をするモデルなのか

入力は2つ、**話している人物の動画**と**喋らせたい音声**です。出力は1つ、**口元だけがその音声に同期するよう作り替えられた動画**です。顔の向き・表情・背景・カメラワークは元動画のまま、口の形だけが新しい音声に合わせて生成されます。

これが効くのは、たとえば次のような場面です。

- **動画ローカライズ（吹き替え）**：日本語で撮った解説動画を、英語ナレーションに差し替えても**口が英語を喋っているように見える**。字幕より没入感が高く、海外展開のCVを上げる。
- **AIアナウンサー / バーチャルヒューマン**：一度撮影したアンカー映像に、毎日変わる原稿の音声を当てて、**撮り直しゼロで日替わりニュース**を量産する。
- **eラーニング・社内研修**：講師を1回だけ撮影し、章ごとの音声を差し替えて**教材を高速更新**する。台本修正のたびに再撮影しない。
- **マーケティング動画のパーソナライズ**：1本のテンプレ動画に、宛名や商品名を含む音声を流し込み、**「○○様、こんにちは」を一人ひとり別の動画**にする。

逆に、**横顔が長く続く**・**口元が手やマイクで隠れる**・**1フレームに複数の顔が同時に映る**・**カット切り替えが激しい**素材は、内部の顔検出・顔位置合わせが破綻しやすく、品質が落ちます。後述の[落とし穴](#本番で必ず詰まる5つの落とし穴と回復性設計)で具体的な回避策を示します。

---

## 仕組み：なぜ「拡散 × SyncNet × TREPA」なのか（論文準拠でやさしく）

ここは公式論文の核心を、**正確さを保ったまま噛み砕く**章です。実装だけ知りたい人は[次章](#バージョンの選び方15-vs-16)へ飛んで構いません。ただし「なぜ落とし穴がああいう形で出るのか」はここを理解していると腑に落ちます。

### 出発点：拡散モデルをそのまま使うと「口が合わない」

LatentSync の土台は **Stable Diffusion 系の潜在拡散モデル（Latent Diffusion Model, LDM）** です。画像を直接いじるのではなく、VAE で圧縮した**潜在空間（latent）でノイズ除去**を行うため、高解像度でも計算が軽い、というSDの利点をそのまま引き継ぎます。

公式の構造はこうです（README より）。

- 音声は **Whisper** でメルスペクトログラムから**音声埋め込み**に変換される。
- その音声埋め込みは **U-Net の cross-attention 層**を通じて生成過程に注入される。
- **参照フレームとマスク済みフレーム**を、ノイズを加えた潜在変数と**チャンネル方向に連結**して U-Net に入力する。

ところが論文（[arXiv:2412.09262](https://arxiv.org/abs/2412.09262)）が指摘するのは、**「音声条件付き LDM を素朴に適用すると、リップシンク精度が不十分になる」**という問題です。原因は **shortcut learning（近道学習）**——モデルは楽をして、**音声と口の関係**ではなく、**前後フレーム同士の見た目の相関**ばかり学んでしまう。結果、「映像としては綺麗だが、音と口がずれている」動画が出来上がります。

### 解決策その1：SyncNet 監督で「音と口の一致」を強制する

そこで LatentSync は **SyncNet**（音声と口の同期度を判定するネットワーク）を**学習の監督信号として組み込み**ます。生成された口元が音声と合っているかを SyncNet が採点し、その損失を逆伝播することで、**「音と口を合わせる」ことをモデルに明示的に強制**します。さらに収束を安定させるため **StableSyncNet** というアーキテクチャを導入しています。

論文の定量結果として、**HDTF テストセットの SyncNet 精度が 91% → 94% に改善**したと報告されています。これは「口がどれだけ正確に音についていくか」の直接的な指標です。

### 解決策その2：TREPA で「時間的にガタつかない」ようにする

フレームを1枚ずつ綺麗に作れても、**連続再生するとチラつく・歯がブレる**——これが拡散ベースのリップシンクの典型的な弱点です。LatentSync は **TREPA（Temporal REPresentation Alignment）** で、生成フレーム列の**時間方向の表現を整列**させ、時間的一貫性を高めます。学習時には **TREPA・LPIPS・SyncNet の損失をピクセル空間で**適用しています。

### この3点が「落とし穴の形」を決めている

仕組みを押さえると、後述のトラブルが**必然**だと分かります。

- 顔位置合わせ（face alignment）が前提なので、**横顔・遮蔽・複数顔**は弱い。
- 拡散の反復回数（`inference_steps`）が品質と速度を直接トレードオフする。
- 音声条件の効かせ具合（`guidance_scale`）を上げすぎると、SyncNet 寄りに振れて**歪み・ジッタ**が出る。

つまりパラメータは「気分」で回すものではなく、**この設計の延長線上で意味を持つノブ**なのです。

---

## バージョンの選び方：1.5 vs 1.6

公式が現在配布しているのは **1.5** と **1.6** で、**コードは共通**です。バージョン切り替えは「**対応するチェックポイントを読み込み、U-Net config の解像度パラメータを変えるだけ**」（README より）。1.6 は **1.5 から学習データを 512×512 に上げただけで、モデル構造・学習戦略は不変**です。

| 項目 | LatentSync 1.5 | LatentSync 1.6（最新） |
| --- | --- | --- |
| 公開 | — | **2025/06/11** |
| 学習解像度 | 256×256 中心 | **512×512** |
| 主目的 | 軽量・低VRAM | **歯・口元のボケ解消**（高精細） |
| 推論 VRAM | **約8GB** | **約18GB** |
| 時間的一貫性 | temporal層で強化済み | 1.5を継承 |
| 中国語動画 | 1.5で性能向上 | 継承 |
| config（推論） | `configs/unet/stage2.yaml` 系 | `configs/unet/stage2_512.yaml` |

**選び方はシンプルです。**

- **顔が大きく映る・品質最優先**（吹替の主役カット、アバターのアップ）→ **1.6**。歯や唇のディテールが効く。
- **VRAM 8GB級のGPUしかない・大量バッチでコストを刻みたい** → **1.5**。十分実用的。
- 迷うなら **1.6 から試す**。L40S/A100/RTX 4090(24GB) など 18GB 以上積めるなら 1.6 一択でよい。

> ⚠️ **混同しやすい点**：HuggingFace のリポジトリ ID は `ByteDance/LatentSync-1.6` ですが、その中に **1.5/1.6 両方のチェックポイント**が含まれます。`setup_env.sh` は 1.6 の `latentsync_unet.pt` を取得します。1.5 を使いたいときは対応する重みと config を選びます。

---

## 使い方A：試すだけなら Replicate API（自前GPU不要）

「品質が自分の素材で通用するか、まず確かめたい」段階では、**GPUを用意せず API で叩く**のが最短です。Replicate の [`bytedance/latentsync`](https://replicate.com/bytedance/latentsync) は、リポジトリの推論スクリプトをラップしたホスティング版です。

- **ハードウェア**：Nvidia **L40S**
- **コスト**：**1回 約 $0.11**（≒ $1 で約9回）
- **所要時間**：典型 **約108秒**（入力により大きく変動）
- **入力**：動画 `mp4`、音声 `mp3 / aac / wav / m4a`

入力フィールドは、リポジトリの推論引数（後述）をそのまま反映しており、実質的に `video` / `audio` / `guidance_scale` / `inference_steps` / `seed` です。

> 📌 **正確性のための注記**：各フィールドの**最新のデフォルト値や範囲は、必ず Replicate の "API" タブで確認**してください。ホスティング版のラッパーは公式リポジトリの更新に追従するため、本稿執筆時点の値と差異が出ることがあります。本稿のコードは**構造**を示すもので、値は確認のうえ調整してください。

### TypeScript（プロトタイプ：同期実行）

まず最小構成。Node の公式クライアント `replicate` を使います。

```ts
// scripts/lipsync-quickstart.ts
import Replicate from "replicate";

const replicate = new Replicate({ auth: process.env.REPLICATE_API_TOKEN });

const output = await replicate.run("bytedance/latentsync", {
  input: {
    video: "https://example.com/source.mp4",
    audio: "https://example.com/dub_en.wav",
    guidance_scale: 1.5, // 1.0〜3.0：上げるほど同期重視（歪みに注意）
    inference_steps: 20, // 20〜50：上げるほど高品質・低速
    seed: 1247,          // 再現性のため固定。-1で毎回ランダム
  },
});

console.log("generated video url:", String(output));
```

これは `run()` が**完了までブロックする**ため、CLIや検証には便利ですが、Webアプリのリクエストハンドラで使うと**100秒超のリクエスト**が立ち、タイムアウトと二重実行の温床になります。**本番は次のWebhook方式**にします。

### TypeScript（本番：非同期 + 冪等性 + Webhook）

本番品質にするための要件は3つ——**①ブロックしない（非同期）**、**②同じ入力で二重課金しない（冪等性）**、**③外部入力を検証する（型安全・セキュリティ）**。Next.js の Route Handler で実装します。

```ts
// app/api/lipsync/route.ts
import { NextResponse } from "next/server";
import { createHash } from "node:crypto";
import Replicate from "replicate";
import { z } from "zod";

// ① 外部入力は境界で必ず検証する（CLAUDE.md準拠：信頼境界はサーバー側）
const RequestSchema = z.object({
  videoUrl: z.string().url(),
  audioUrl: z.string().url(),
  guidanceScale: z.number().min(1).max(3).default(1.5),
  inferenceSteps: z.number().int().min(20).max(50).default(20),
  seed: z.number().int().default(1247),
});
type LipSyncInput = z.infer<typeof RequestSchema>;

const replicate = new Replicate({ auth: process.env.REPLICATE_API_TOKEN });

// ② 入力内容から決定的なキーを作る。同じ素材＋同じパラメータなら同じジョブ。
function jobKey(input: LipSyncInput): string {
  return createHash("sha256").update(JSON.stringify(input)).digest("hex");
}

export async function POST(req: Request) {
  const parsed = RequestSchema.safeParse(await req.json());
  if (!parsed.success) {
    // 422: 何が不正かを返す（PIIは含めない）
    return NextResponse.json({ error: parsed.error.flatten() }, { status: 422 });
  }
  const input = parsed.data;
  const key = jobKey(input);

  // 冪等性：既存ジョブがあれば作り直さず返す（二重課金・二重生成の防止）
  const existing = await jobStore.get(key);
  if (existing) {
    return NextResponse.json({ jobId: existing.predictionId, status: existing.status, cached: true });
  }

  // ③ 非同期で起動。完了はWebhookで受ける（リクエストはすぐ返る）
  const prediction = await replicate.predictions.create({
    model: "bytedance/latentsync",
    input: {
      video: input.videoUrl,
      audio: input.audioUrl,
      guidance_scale: input.guidanceScale,
      inference_steps: input.inferenceSteps,
      seed: input.seed,
    },
    webhook: `${process.env.PUBLIC_BASE_URL}/api/lipsync/webhook`,
    webhook_events_filter: ["completed"],
  });

  await jobStore.put(key, { predictionId: prediction.id, status: prediction.status });
  return NextResponse.json({ jobId: prediction.id, status: prediction.status, cached: false });
}
```

```ts
// app/api/lipsync/webhook/route.ts
import { NextResponse } from "next/server";
import { verifyReplicateSignature } from "@/lib/replicate-webhook"; // 署名検証（後述の方針）

export async function POST(req: Request) {
  const raw = await req.text();
  // セキュリティ：Webhookは必ず署名検証する。検証なしの本文を信頼しない。
  if (!(await verifyReplicateSignature(req.headers, raw))) {
    return NextResponse.json({ error: "invalid signature" }, { status: 401 });
  }
  const event = JSON.parse(raw) as { id: string; status: string; output?: string };

  if (event.status === "succeeded" && event.output) {
    await jobStore.markDone(event.id, event.output); // 生成URLを保存し、後段に通知
  } else if (event.status === "failed" || event.status === "canceled") {
    await jobStore.markFailed(event.id); // 失敗を記録し、UIへ反映
  }
  return NextResponse.json({ ok: true });
}
```

ポイントは、`jobStore`（Vercel KV / Upstash Redis などのKVで十分）に **`sha256(入力)` をキーに**ジョブを記録していること。これだけで「ユーザーが送信ボタンを連打しても1回しか走らない」「同じ動画×同じ音声×同じパラメータを再依頼してもキャッシュが返る」という**冪等性とコスト効率**が同時に手に入ります。Webhook は**署名検証必須**（Replicate は署名ヘッダを送ります）——検証なしのエンドポイントは、誰でも「成功した」と偽れる穴になります。

### Python / curl

サーバーがPythonなら公式クライアントで同形に書けます。

```python
# pip install replicate
import replicate

output = replicate.run(
    "bytedance/latentsync",
    input={
        "video": "https://example.com/source.mp4",
        "audio": "https://example.com/dub_en.wav",
        "guidance_scale": 1.5,
        "inference_steps": 20,
        "seed": 1247,
    },
)
print(output)  # 生成された動画のURL
```

```bash
# 素のHTTP。CIやシェルからの単発実行に。
curl -s -X POST https://api.replicate.com/v1/predictions \
  -H "Authorization: Bearer $REPLICATE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bytedance/latentsync",
    "input": {
      "video": "https://example.com/source.mp4",
      "audio": "https://example.com/dub_en.wav",
      "guidance_scale": 1.5,
      "inference_steps": 20,
      "seed": 1247
    }
  }'
```

---

## 使い方B：セルフホスト（公式手順に忠実）

大量バッチ・データプライバシー・1本あたり単価の最小化が要件なら、**自前GPUでのセルフホスト**です。ここは公式 `README` / `setup_env.sh` / `requirements.txt` に**そのまま従う**のが正解で、勝手なバージョン変更は依存破壊の元になります。

### 1. 環境構築（公式 `setup_env.sh` の中身）

公式は **conda 環境 + Python 3.10.13** を前提にしています。

```bash
# システム依存（Ubuntu系）
sudo apt -y install libgl1

# conda環境を作成（Pythonは3.10.13で固定）
conda create -y -n latentsync python=3.10.13
conda activate latentsync

# ffmpegはconda-forgeから
conda install -y -c conda-forge ffmpeg

# Python依存を一括インストール
pip install -r requirements.txt
```

`requirements.txt` の主要ピン（**勝手に上げない**こと）：

```text
torch==2.5.1            # CUDA 12.1 ビルド（--extra-index-url cu121）
torchvision==0.20.1
diffusers==0.32.2
transformers==4.48.0
mediapipe==0.10.11      # 顔ランドマーク
insightface==0.7.3      # 顔検出・整列
onnxruntime-gpu==1.21.0
DeepCache==0.1.1        # enable_deepcache の実体
gradio==5.24.0
numpy==1.26.4
```

> 🔧 **ハマりどころ**：`torch==2.5.1` は **CUDA 12.1** ビルドを `--extra-index-url https://download.pytorch.org/whl/cu121` から取得します。ドライバ/CUDAが噛み合わないと `onnxruntime-gpu` が CPU にフォールバックして「**動くが激遅**」になります。`nvidia-smi` でドライバ、`python -c "import torch; print(torch.cuda.is_available())"` で True を必ず確認してください。

### 2. チェックポイント取得

`setup_env.sh` は HuggingFace から重みを取得します。手動なら次の2コマンド。

```bash
huggingface-cli download ByteDance/LatentSync-1.6 whisper/tiny.pt --local-dir checkpoints
huggingface-cli download ByteDance/LatentSync-1.6 latentsync_unet.pt --local-dir checkpoints
```

最終的なディレクトリ構成はこうなります。

```text
./checkpoints/
├── latentsync_unet.pt      # 本体（U-Net）
└── whisper/
    └── tiny.pt             # 音声埋め込み用Whisper
```

### 3. 推論（公式 `inference.sh` の中身）

GUIで試すなら `python gradio_app.py`。バッチ・自動化は CLI（`./inference.sh`）です。公式の `inference.sh` は **1.6（512×512）を既定**にしており、中身は次の通りです。

```bash
#!/bin/bash
python -m scripts.inference \
    --unet_config_path "configs/unet/stage2_512.yaml" \
    --inference_ckpt_path "checkpoints/latentsync_unet.pt" \
    --inference_steps 20 \
    --guidance_scale 1.5 \
    --enable_deepcache \
    --video_path "assets/demo1_video.mp4" \
    --audio_path "assets/demo1_audio.wav" \
    --video_out_path "video_out.mp4"
```

`scripts.inference` が受け取る**全引数**（argparse 定義どおり）：

| 引数 | 型 | 既定 | 役割 |
| --- | --- | --- | --- |
| `--unet_config_path` | str | `configs/unet.yaml` | U-Net設定。1.6は `configs/unet/stage2_512.yaml` |
| `--inference_ckpt_path` | str | **必須** | U-Netチェックポイント |
| `--video_path` | str | **必須** | 入力動画 |
| `--audio_path` | str | **必須** | 当てる音声 |
| `--video_out_path` | str | **必須** | 出力先 |
| `--inference_steps` | int | `20` | 拡散の反復回数。20→50で高品質・低速 |
| `--guidance_scale` | float | `1.0` | 音声条件の強さ。1.0→3.0で同期重視 |
| `--seed` | int | `1247` | 乱数シード。`-1` で毎回ランダム |
| `--temp_dir` | str | `temp` | 中間ファイル置き場 |
| `--enable_deepcache` | flag | off | DeepCacheで推論を高速化 |

> 💡 `inference.sh` が `guidance_scale 1.5` を渡しているのに argparse の既定は `1.0` です。**実運用の推奨は 1.5 付近**——公式デモがそう設定しているのが根拠です。素の `python -m scripts.inference` を直叩きするときは、`1.0` のままだと同期が弱く感じることがあるので明示しましょう。

内部では、GPUの **compute capability が 8.0 以上（Ampere/Ada 世代、例：A100・L40S・RTX 30/40）なら `float16`**、それ未満（V100・T4 等）なら `float32` で動きます。古いGPUで「思ったより遅い・VRAMを食う」のはこの分岐が理由です。

---

## パラメータ実践チューニング：用途別プリセット

公式は範囲だけを示します（`inference_steps` [20-50] / `guidance_scale` [1.0-3.0]）。ここに**実務での当たり値**を足します。下表はそのまま使えるプリセットです。

| 用途 | `inference_steps` | `guidance_scale` | `enable_deepcache` | 版 | 狙い |
| --- | --- | --- | --- | --- | --- |
| **ドラフト確認**（社内レビュー） | 20 | 1.5 | ✅ | 1.5 | とにかく速く・安く形を見る |
| **標準の吹替**（YouTube/広報） | 25–30 | 1.5–2.0 | ✅ | 1.6 | 品質と速度の最良点 |
| **アップの主役カット**（顔が大きい） | 35–50 | 1.5 | ❌ | 1.6 | 歯・唇のディテール最優先 |
| **同期が甘い素材**（早口/歌） | 30 | 2.0–2.5 | ✅ | 1.6 | 口の追従を強める |
| **歪み・ジッタが出た** | +10 steps | **下げる**（→1.5） | ✅ | 1.6 | guidanceの上げ過ぎを戻す |

ノブの意味を実務語で言い換えるとこうです。

- **`inference_steps`（拡散の反復）**：上げるほど綺麗だが**線形に遅くなる**。20→40で品質は伸びるが時間も倍。**まず20で全体を確認 → 採用カットだけ高steps**が費用対効果が高い。
- **`guidance_scale`（音声条件の強さ）**：上げると**口が音についてくる**が、上げすぎると**顔が歪む・ガタつく**。公式も「**精度は上がるが歪み・ジッタの可能性**」と明言。**1.5を基準に、同期不足なら+0.5刻み**。歪んだら戻す。
- **`seed`**：**固定すると結果が再現**できる。A/B比較や「やり直し」のたびに絵が変わると検証にならないので、**本番は必ず固定**（既定 `1247` でよい）。新しいバリエーションが欲しいときだけ変える。
- **`enable_deepcache`**：DeepCache による**高速化フラグ**。ドラフトでは ON、最終アップでは僅かな品質差を嫌って OFF、という使い分けが実務的。

> **コスト直結の原則**：品質は `inference_steps` に**ほぼ比例して時間=お金**がかかります。全フレームを高 steps で回すのは無駄が大きい。「**ドラフトは20でレビュー → 通ったカットだけ高 steps で本生成**」の二段構えが、本番の単価を最も効かせます。

---

## 本番で必ず詰まる5つの落とし穴と回復性設計

デモ（10秒・正面・無音なし）では起きず、**現実の素材（数十分・横顔・無音・複数話者）で一斉に噴出**する問題です。私が実運用で踏んだ順に、原因と対策を示します。仕組み章で見たとおり、これらは**LatentSyncの設計の必然**として現れます。

### ① 顔検出・顔整列の失敗（最頻出）

LatentSync は内部で顔を検出・整列（mediapipe / insightface）してから口元を作ります。**横顔・うつむき・手やマイクでの遮蔽・極端なサイズ**で検出が外れると、口元が崩壊するか処理が落ちます。

**対策**：

- 投入前に**顔の有無と正面性を自前で前検査**し、検出できないカットは**リップシンクを通さず素通し（パススルー）**にする。全カットを無理に同期させない判断が品質を守る。
- 1フレームに**複数の顔**が映るなら、話者を特定して**クロップしてから**投入する。

```python
# 投入前ガード：正面に近い単一の顔があるカットだけをLatentSyncへ回す
import mediapipe as mp

_detector = mp.solutions.face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.6)

def is_lipsyncable(frame_rgb) -> bool:
    """顔が1つ・十分な信頼度で検出できるカットのみTrue。Falseなら原画パススルー。"""
    result = _detector.process(frame_rgb)
    faces = result.detections or []
    return len(faces) == 1  # 複数顔・無顔は同期対象外にして破綻を防ぐ
```

### ② 長尺動画でのVRAM枯渇（OOM）

拡散モデルは**フレーム数に応じてVRAMを食い**ます。数十分の動画を丸ごと1回で渡すと、18GBでも **CUDA out of memory** で落ちます。

**対策**：**動画をセグメント分割して逐次処理し、結果を連結**する。さらに、無音区間は口を動かす必要がないため、**発話区間検出（VAD）で無音をLatentSyncに通さない**と、品質（無音時の口パク幻覚）とコストを同時に改善できます。私の基盤では、このVADによる素通しで**リップシンクのGPU処理コストを約40%削減**しました（[パイプライン設計記事](/blog/production-ai-video-localization-lipsync-gpu-pipeline)で詳述）。

```python
# OOMを正常系として扱う：失敗したら窓を狭めて再試行（回復性）
def lipsync_segment(seg, *, max_frames: int = 750):
    try:
        return run_latentsync(seg)
    except torch.cuda.OutOfMemoryError:
        torch.cuda.empty_cache()
        if seg.frame_count <= 1:
            raise  # これ以上割れない＝本物の異常。握りつぶさない
        a, b = seg.split_in_half()      # 二分割してフォールバックを局所化
        return concat(lipsync_segment(a), lipsync_segment(b))
```

### ③ fps / 解像度 / 音声フォーマットの不一致

入力の **fps がバラバラ**だったり、音声が想定外コーデックだと、口と音が**全体的にズレる**か処理が失敗します。

**対策**：投入前に **ffmpeg で正規化**を1段挟む。fpsを固定し、音声を 16kHz/wav に揃えるだけで、再現性と安定性が跳ね上がります。

```bash
# 投入前の正規化：fps固定・音声をwav 16kHzに統一
ffmpeg -y -i input.mp4 -r 25 -c:v libx264 -pix_fmt yuv420p norm_video.mp4
ffmpeg -y -i dub.m4a -ar 16000 -ac 1 dub.wav
```

### ④ 歯・口元のボケ

**1.5 で歯がボケる**——これは公式も認識していた弱点で、**1.6 が 512×512 学習で解決**しています。

**対策**：顔が大きく映る素材は**まず 1.6**。それでも気になるなら `inference_steps` を上げる。アップが多いコンテンツで 1.5 を使い続けるのは、解決済みの問題を抱え込むのと同じです。

### ⑤ 品質を「目視」でしか確認していない

本番で最も危険なのは、**品質ゲートが人間の目視だけ**であること。大量バッチでは破綻したカットが必ずすり抜けます。

**対策**：LatentSync リポジトリ同梱の**評価スクリプトで同期品質を機械採点**し、しきい値を下回ったカットだけ人手レビュー or 再生成に回す。SyncNet信頼度を**定量ゲート**にすることで、レビュー工数を激減させられます。

```bash
# 生成物の音と口の同期度を機械採点（CIや後処理に組み込む）
./eval/eval_sync_conf.sh     # SyncNet confidence
./eval/eval_syncnet_acc.sh   # SyncNet accuracy
```

---

## 本番運用の設計原則（可観測性・冪等性・回復性・コスト）

落とし穴の対策を、運用品質の言葉でまとめ直します。これが「動く」と「本番で落ちない」の差です。

- **冪等性**：`sha256(動画 + 音声 + steps + guidance + seed)` を**ジョブキー**にし、結果をキャッシュ。再送・連打・リトライで二重生成しない。Replicate でも自前GPUでも同じ設計。
- **回復性**：OOM・顔検出失敗・GPUプリエンプションを**例外ではなく正常系**として扱う。「窓を狭めて再試行」「同期不能カットは原画パススルー」で、1カットの失敗が全体を巻き込まないようにする。
- **可観測性**：カットごとに **どの版で・何 steps・guidanceいくつ・SyncNet信頼度いくつ**を構造化ログに残す。「なぜこのカットだけ口が崩れたか」を後から追える状態にする。PII（顔・音声内容）はログに出さない。
- **コスト効率**：①VADで無音を素通し ②ドラフト20steps→採用カットのみ高steps ③冪等キャッシュで再生成ゼロ、の三段で単価を刻む。Replicate なら 1回 $0.11 が**そのまま積算**されるので、無駄打ちの削減が直接効く。
- **セルフホストの単価感**：18GB級のスポット/オンデマンドGPUを**バッチで埋めて回す**と、1本あたりは Replicate より安くなりやすい。ただし環境構築・運用・GPU調達のコストを足すと、**少量ならAPI、定常大量ならセルフホスト**が損益分岐の目安。

---

## 他のリップシンクモデルとの比較

「結局どれを使う？」に答えるための整理です。**用途で選ぶ**のが正解で、万能の1つはありません。

| モデル | 方式 | 強み | 弱み | ライセンス | 向く場面 |
| --- | --- | --- | --- | --- | --- |
| **LatentSync** | 拡散（音声条件付きLDM）+ SyncNet/TREPA | **自然さ・時間的一貫性・同期精度の総合力** | 相応のVRAM、横顔に弱い | **Apache-2.0** | 高品質な吹替・アバター・教材 |
| **Wav2Lip** | GAN系（軽量） | 軽い・速い・低スペックで動く | 解像度・口元の精細さが低い | 研究用途中心（要確認） | プロト・低負荷の大量処理 |
| **SadTalker** | 3DMM + 顔生成 | **静止画1枚**から喋らせられる | 動画ベースの自然さでは劣りがち | 確認要 | 写真からのトーキングヘッド |
| **MuseTalk** | リアルタイム志向 | **高速・準リアルタイム** | 同期の安定性で拡散系に一歩譲る場面 | 確認要 | ライブ寄り・低遅延要件 |

実務の意思決定はおおむねこうです。

- **品質が最重要で、Apache-2.0で商用も通したい** → **LatentSync**。
- **静止画1枚しかない** → SadTalker。
- **低遅延・準リアルタイムが要件** → MuseTalk。
- **とにかく軽く大量に** → Wav2Lip でドラフト、採用分を LatentSync で本生成、という**二段構え**も有効。

> 各モデルのライセンスは更新されるため、**商用利用は必ず一次情報で確認**してください。LatentSync は本稿執筆時点で **Apache-2.0**（公式リポジトリ）です。

---

## よくある質問（FAQ）

**Q. 商用利用できますか？**
A. LatentSync は公式リポジトリで **Apache-2.0** ライセンスです。商用利用に対応しますが、**生成物に映る人物の肖像権・音声の権利**は別問題です。本人の同意なく実在人物を喋らせるのは法的・倫理的リスクが大きいので、**同意のある素材**で使ってください。

**Q. 日本語の音声でも使えますか？**
A. 使えます。音声は Whisper の埋め込みを介して条件付けされ、**言語に依存せず口の動きを生成**します。1.5で**中国語動画の性能が改善**された経緯もあり、非英語でも実用的です。

**Q. 必要なGPU / VRAMは？**
A. 推論で **1.6 が約18GB**、**1.5 が約8GB**。1.6 は L40S・A100・RTX 4090(24GB) 級、1.5 は 8〜16GB級でも動きます。学習は別物で、512×512のStage 2は **55GB** 必要です（通常は学習不要、推論だけで足ります）。

**Q. リアルタイム配信に使えますか？**
A. 向きません。拡散ベースで1本に**秒〜分**かかります（Replicateで典型108秒）。**ライブ用途は MuseTalk** 等の低遅延モデルを検討してください。

**Q. 横顔でも口を合わせられますか？**
A. 苦手です。内部の顔整列が前提で、**正面に近いほど安定**します。横顔が続くカットは**同期せず原画パススルー**にするのが、品質を守る現実解です。

**Q. 最低・最大の動画長は？**
A. 明示的な上限はありませんが、**長尺は VRAM 制約でセグメント分割が必須**です。実務では「セグメント分割 + 無音スキップ + 冪等キャッシュ」を前提に組みます。

**Q. 自前GPUとAPI、どちらが安い？**
A. **少量ならAPI**（環境構築ゼロ、1回 $0.11）。**定常的に大量**なら、スポットGPUをバッチで埋めるセルフホストが1本単価で有利になりやすい。まずAPIで品質と需要を確かめ、量が増えたらセルフホストへ移すのが王道です。

---

## まとめ：LatentSync を「動く」から「本番で稼ぐ」へ

LatentSync の本質は、**「拡散の自然さ」と「音と口の正確な同期」を、SyncNet監督とTREPAで両立させた点**にあります。だからこそ品質が高く、だからこそ顔整列・反復回数・条件強度という**設計に根ざしたノブ**を理解して扱う必要があります。

実装の道筋はシンプルです。

1. **Replicate API** で自分の素材の品質をまず確かめる（自前GPU不要・1回 $0.11）。
2. 手応えがあれば **1.6 をセルフホスト**し、`inference_steps` / `guidance_scale` を用途別プリセットで詰める。
3. **本番は冪等性・回復性・可観測性・コスト**を設計に織り込む——顔検出ガード、セグメント分割、VADで無音スキップ、SyncNet信頼度ゲート。

ここまでやって初めて、デモではなく「**顧客の素材で落ちない**」プロダクトになります。そして、ここが一番伝えたい点ですが——**この一連の設計判断こそが、外注で差がつくところ**です。「モデルを繋ぐだけ」のデモは誰でも作れますが、**長尺・横顔・無音・複数話者の現実素材で破綻しない基盤**は、踏んだ地雷の数がそのまま品質になります。

> 私は、本稿の落とし穴と回復性設計を**実際に本番運用しているAI動画ローカライズ基盤**で実装しました。リップシンクを含む動画AIパイプラインの構築・改善をお考えなら、[実績](/case-studies/ai-video-localization-lipsync)をご覧のうえ、お気軽にご相談ください。**一人 × 生成AI**で、PoCから本番運用まで一気通貫で、速く・安く・安全に作ります。

---

## 出典・公式リソース

- **公式リポジトリ（GitHub）**：[bytedance/LatentSync](https://github.com/bytedance/LatentSync) — README・`inference.sh`・`setup_env.sh`・`requirements.txt`・学習/評価スクリプト
- **論文（arXiv）**：[LatentSync: Audio Conditioned Latent Diffusion Models for Lip Sync (2412.09262)](https://arxiv.org/abs/2412.09262) — SyncNet監督・TREPA・StableSyncNet・定量結果
- **モデル配布（HuggingFace）**：[ByteDance/LatentSync-1.6](https://huggingface.co/ByteDance/LatentSync-1.6) — 1.5/1.6 チェックポイント
- **ホスティングAPI（Replicate）**：[bytedance/latentsync](https://replicate.com/bytedance/latentsync) — 入力スキーマ・料金・実行環境

※ バージョン・パラメータ・料金・ライセンスは更新されます。**実装前に必ず一次情報を確認**してください。本稿の数値（SyncNet 91%→94%、推論VRAM 8GB/18GB、512×512、約$0.11/108秒・L40S など）は本稿執筆時点の公式情報に基づきます。
