NaturalSpeech 3の中核コンポーネント「FACodec(Factorized Audio Codec)」を使って参照ボイスからVoice Conversion を行う

初めに

FACodec(Factorized Audio Codec)は、NaturalSpeech 3の中核コンポーネントです。

オーディオ仕様は以下になっています

  • : 16kHz、ホップサイズ200サンプル

また以下の制限があります。

  • 音声は最大5秒に制限(長い音声はテンソルサイズ不一致エラーの原因)
  • FACodecは16kHzを期待(librosaが自動変換)

開発環境

環境構築

リポジトリのクローンします

git clone https://github.com/lifeiteng/naturalspeech3_facodec.git
cd naturalspeech3_facodec

uvプロジェクトの初期化します

uv init --no-readme

Pythonバージョンの設定

pyproject.tomlrequires-python>=3.11に変更し、Python 3.11を使用するように設定します。

uv python pin 3.11

次に依存関係のインストールしていきます。

# PyTorch(torch 2.1.2が推奨)
uv add torch==2.1.2 torchaudio==2.1.2

# その他の依存関係
uv add pyworld soundfile "librosa==0.10.1" einops huggingface_hub

# NumPyとsetuptoolsの互換性対応
uv add "numpy<2" setuptools

ns3_codecパッケージのインストールしていきます。

setup.pysetup.py.bakにリネームし、pyproject.tomlでパッケージを管理します。uv add -e .だとうまくいかなったので、uv pipを仕方なく使います。

mv setup.py setup.py.bak
uv pip install -e .

pyproject.tomlの設定します

[project]
name = "ns3-codec"
version = "0.2.2"
description = "FACodec: Speech Codec with Attribute Factorization for NaturalSpeech 3"
requires-python = ">=3.11"
dependencies = [
    "einops>=0.8.1",
    "huggingface-hub>=1.2.3",
    "librosa==0.10.1",
    "numpy<2",
    "pyworld>=0.3.5",
    "setuptools>=80.9.0",
    "soundfile>=0.13.1",
    "torch==2.1.2",
    "torchaudio==2.1.2",
]

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["."]
include = ["ns3_codec*"]

実行

サンプルのテストは以下のように実行できます

uv run python test.py

多報酬強化学習による制御可能で感情表現豊かなゼロショットTTS「GLM-TTS」をWindows + Dockerで動かす

初めに

最近出てきた以下のTTSを触ってみます

github.com

アーキテクチャは以下のようになっています

  1. LLM (Llama): テキスト → 音声トークン列を生成
  2. Flow Matching: 音声トークン → メルスペクトログラム → ボコーダーで波形生成

依存ライブラリにWeTextProcessingがあるため、WindowsMacでは動かすのが大変なのでDockerを使うことが推奨されます

Windowsでインストールしようとすると以下のエラーが出ました。

error: Unable to find a compatible Visual Studio installation

この記事では Docker版に対応したリポジトリを公開しており、こちらを元に進めていきます

github.com

開発環境

  • Windows 11
  • RTX 4070 ti super
  • Docker

環境構築

リポジトリのクローンします

git clone https://github.com/ayutaz/GLM-TTS.git
cd GLM-TTS

Dockerビルド前にモデルをダウンロードしておきます(約10GB):

# ディレクトリ作成
mkdir ckpt

# HuggingFace CLIでダウンロード
pip install huggingface-hub
huggingface-cli download zai-org/GLM-TTS --local-dir ckpt

Dockerイメージのビルドします

docker compose build

推論

CLI推論で確認:

# 中国語サンプル
docker compose --profile cli run --rm glm-tts-cli

# 英語サンプル
docker compose --profile cli run --rm glm-tts-cli python glmtts_inference.py --data=example_en --exp_name=_test_en --use_cache

Web UI(Gradio)で確認:

docker compose up glm-tts

Aho-Corasickアルゴリズムを使用した高速でメモリ効率の良い複数パターン文字列検索ライブラリ「pyahocorasick」を動かして速度比較を行う

初めに

値の関連付けが必要で高速に文字列を検索したい時に使えるらしいpyahocorasickを触ってみます

github.com

開発環境

項目 バージョン
OS macOS 26.0.1
Python 3.12.12
uv 0.9.5
pyahocorasick 2.3.0

環境構築

まずはプロジェクトの初期化を行います

# プロジェクトディレクトリで実行
uv init --no-readme

# Pythonバージョンを固定
uv python pin 3.12

開発用依存関係をインストールします

uv add --dev pytest twine setuptools wheel

本ライブラリをUnicode版でビルドします

AHOCORASICK_UNICODE=yes uv pip install -e .

実行

実際に以下のようなでもスクリプトを作成して実行してみます

import ahocorasick

# 47都道府県
PREFECTURES = [
    "北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
    "茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県",
    "新潟県", "富山県", "石川県", "福井県", "山梨県", "長野県", "岐阜県",
    "静岡県", "愛知県", "三重県", "滋賀県", "京都府", "大阪府", "兵庫県",
    "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県", "山口県",
    "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県",
    "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県",
]


def main():
    print("=" * 60)
    print("pyahocorasick 基本デモ")
    print("=" * 60)

    # 1. Automatonを作成
    print("\n【1. Automatonの作成】")
    automaton = ahocorasick.Automaton()
    print(f"  Automaton created: {automaton}")

    # 2. キーワードを登録(値も関連付け可能)
    print("\n【2. キーワードの登録】")
    for i, pref in enumerate(PREFECTURES):
        # キーワードに辞書を関連付ける例
        automaton.add_word(pref, {"name": pref, "index": i})
    print(f"  登録キーワード数: {len(automaton)}")

    # 3. オートマトンを構築
    print("\n【3. オートマトンの構築】")
    automaton.make_automaton()
    print(f"  状態: {automaton}")

    # 4. テキストを検索
    print("\n【4. テキスト検索】")
    text = "東京都から京都府へ行き、大阪府と兵庫県を訪問した後、北海道と沖縄県にも足を延ばした"
    print(f"  検索テキスト: 「{text}」")
    print(f"  テキスト長: {len(text)}文字")

    print("\n【5. 検索結果】")
    matches = []
    for end_index, value in automaton.iter(text):
        start_index = end_index - len(value["name"]) + 1
        matches.append({
            "start": start_index,
            "end": end_index,
            "keyword": value["name"],
            "index": value["index"],
        })

    for match in matches:
        print(f"  位置 {match['start']:2d}-{match['end']:2d}: {match['keyword']} (index={match['index']})")

    print(f"\n  マッチ数: {len(matches)}件")

    # 6. 追加機能のデモ
    print("\n" + "=" * 60)
    print("追加機能")
    print("=" * 60)

    # キーワードの存在確認
    print("\n【キーワードの存在確認】")
    print(f"  '東京都' in automaton: {'東京都' in automaton}")
    print(f"  '東京' in automaton: {'東京' in automaton}")

    # 値の取得
    print("\n【値の取得】")
    print(f"  automaton.get('大阪府'): {automaton.get('大阪府')}")
    print(f"  automaton.get('存在しない', 'default'): {automaton.get('存在しない', 'default')}")

    # 統計情報
    print("\n【統計情報】")
    print(f"  キーワード数: {len(automaton)}")
    print(f"  Unicode mode: {ahocorasick.unicode}")


if __name__ == "__main__":
    main()

結果は以下のとおりです

============================================================
pyahocorasick 基本デモ
============================================================1. Automatonの作成】
  Automaton created: <ahocorasick.Automaton object at 0x10095d8b0>2. キーワードの登録】
  登録キーワード数: 473. オートマトンの構築】
  状態: <ahocorasick.Automaton object at 0x10095d8b0>4. テキスト検索】
  検索テキスト: 「東京都から京都府へ行き、大阪府と兵庫県を訪問した後、北海道と沖縄県にも足を延ばした」
  テキスト長: 41文字

【5. 検索結果】
  位置  0- 2: 東京都 (index=12)
  位置  5- 7: 京都府 (index=25)
  位置 12-14: 大阪府 (index=26)
  位置 16-18: 兵庫県 (index=27)
  位置 26-28: 北海道 (index=0)
  位置 30-32: 沖縄県 (index=46)

  マッチ数: 6============================================================
追加機能
============================================================

【キーワードの存在確認】
  '東京都' in automaton: True
  '東京' in automaton: False

【値の取得】
  automaton.get('大阪府'): {'name': '大阪府', 'index': 26}
  automaton.get('存在しない', 'default'): default

【統計情報】
  キーワード数: 47
  Unicode mode: 1

他の文字検索ライブラリとの比較

調査をすると以下のようなものが見つかりました

ライブラリ 実装言語 値関連付け 部分一致 Unicode Pickle メンテナンス
pyahocorasick C 活発
ahocorasick_rs Rust 活発
flashtext Python 低調
ahocorapy Python 低調
regex C - - 活発

また機能や性能を調べると以下のようになります

相対速度(pyahocorasick = 1.0として)

以下はWeb上の各種ベンチマーク結果に基づく概算値です。実際の性能は環境・データにより異なります。

ライブラリ 検索速度 構築速度 メモリ効率
ahocorasick_rs 1.5〜7× 高速 同等〜やや遅い 同等
pyahocorasick 1.0(基準) 1.0 1.0
flashtext 0.3〜1.0× 遅い 低い
ahocorapy 0.1〜0.3× 遅い 低い
regex (1000+ keywords) 0.01〜0.1× N/A N/A

キーワード数と性能の関係

キーワード数 regex pyahocorasick ahocorasick_rs
10 良好 良好 良好
100 良好 良好 良好
500 低下開始 良好 良好
1,000+ 急激に低下 安定 安定
10,000+ 使用不可 安定 安定

技術選定について

これらがある中で以下のようになります

                    キーワード数
                    ↓
    ┌───────────────┴───────────────┐
    │                               │
  〜100個                        100個以上
    │                               │
    ▼                               ▼
  regex/re              値の関連付けが必要?
                        ↓
            ┌───────────┴───────────┐
            │                       │
           必要                   不要
            │                       │
            ▼                       ▼
    pyahocorasick            ahocorasick_rs
    (バランス良好)            (最速)

ここで キーワードに値を関連付けたい : 値の関連付けがキーワードになってきます。これは以下のような用途・例の場合に適当することができそうです

以下のようにキーワードに任意のデータを紐付けできるため用途は以下の場合を想定できます

  • 固有表現抽出(NER)
  • 辞書ベース変換(読み仮名・翻訳)
  • コンテンツフィルタリング
  • ログ解析・アラート
automaton.add_word("東京", {"reading": "とうきょう", "en": "Tokyo"})  

拡散ベースの動画生成を100〜200倍高速化するフレームワーク「TurboDiffusion」をWindowsで動かす

初めに

動画生成モデルで高速に生成できるものが出てきたので触ってみます

主要技術:

  • SageAttention: 8bit量子化アテンション
  • SLA (Sparse-Linear Attention): Top-kスパースアテンション
  • rCM (Rectified Consistency Models): タイムステップ蒸留

開発環境

  • Windows 11
  • cuda (13.0) : RTX 4070 ti super
  • uv (0.9.x)

環境構築

まずはリポジトリをcloneします

git clone https://github.com/thu-ml/TurboDiffusion.git
cd TurboDiffusion
git submodule update --init --recursive

プロジェクトを初期化します

uv init --python 3.12

次にpyproject.tomlを以下のように設定します

[project]
name = "turbodiffusion"
description = "TurboDiffusion: video generation acceleration framework that could accelerate end-to-end video generation by 100-205x with negligible video quality loss."
version = "1.0.0"
authors = [
  { name = "Jintao Zhang" },
  { name = "Kaiwen Zheng" },
  { name = "Kai Jiang" },
  { name = "Haoxu Wang" },
]
readme = "README.md"
requires-python = ">=3.9"
license = { file = "LICENSE" }

classifiers = [
  "Programming Language :: Python :: 3",
  "License :: OSI Approved :: Apache Software License",
]

dependencies = [
  "torch==2.8.0",
  "torchvision",
  # triton and flash-attn are installed manually on Windows
  # "triton>=3.3.0",
  # "flash-attn",
  "einops",
  "numpy",
  "pillow",
  "loguru",
  "imageio[ffmpeg]",
  "pandas",
  "PyYAML",
  "omegaconf",
  "attrs",
  "fvcore",
  "ftfy",
  "regex",
  "transformers",
  "nvidia-ml-py",
  "triton-windows<3.5",
  "flash-attn",
]

[build-system]
requires = [
  "setuptools>=62",
  "wheel>=0.38",
  "packaging>=21",
  "torch>=2.7.0",
  "ninja",
]
build-backend = "setuptools.build_meta"

[project.urls]
Homepage = "https://github.com/thu-ml/TurboDiffusion"
Repository = "https://github.com/thu-ml/TurboDiffusion"

[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"
explicit = true

[tool.uv.sources]
torch = { index = "pytorch-cu128" }
torchvision = { index = "pytorch-cu128" }
flash-attn = { path = "flash_attn-2.8.3+cu128torch2.8.0cxx11abiTRUE-cp312-cp312-win_amd64.whl" }

WindowsFlash-attentionを使いたいので、Windows向けのwheelをダウンロードします

curl -L -o flash_attn-2.8.3+cu128torch2.8.0cxx11abiTRUE-cp312-cp312-win_amd64.whl "https://github.com/bdashore3/flash-attention/releases/download/v2.8.3/flash_attn-2.8.3+cu128torch2.8.0cxx11abiTRUE-cp312-cp312-win_amd64.whl"

依存パッケージをインストールします

次にTurboDiffusionのインストールします

uv add -e . --no-build-isolation
uv sync

チェックポイントのダウンロード

以下で今回使用するモデルをダウンロードします

# VAE
curl -L -o Wan2.1_VAE.pth "https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B/resolve/main/Wan2.1_VAE.pth"

# Text Encoder (約10GB)
curl -L -o models_t5_umt5-xxl-enc-bf16.pth "https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B/resolve/main/models_t5_umt5-xxl-enc-bf16.pth"

# DiTモデル (1.3B非量子化版、約2.7GB)
curl -L -o TurboWan2.1-T2V-1.3B-480P.pth "https://huggingface.co/TurboDiffusion/TurboWan2.1-T2V-1.3B-480P/resolve/main/TurboWan2.1-T2V-1.3B-480P.pth"

ダウンロードしたモデルは checkpoints に保存します

実行

実行するときは以下で実行可能です

uv turbodiffusion\inference\wan2.1_t2v_infer.py ^
    --model Wan2.1-1.3B ^
    --dit_path checkpoints/TurboWan2.1-T2V-1.3B-480P.pth ^
    --resolution 480p ^
    --prompt "A cat walking on the street" ^
    --num_steps 4 ^
    --attention_type sla ^
    --save_path output/test.mp4

デモ動画

  • アニメ風 (demo_anime.mp4 / demo_anime.gif)
Anime style, a cute girl with pink hair and big eyes walking through a cherry blossom garden, sakura petals falling, soft lighting, Studio Ghibli style

  • ダンス (demo_dance.mp4 / demo_dance.gif)
A professional dancer performing hip hop dance moves in a dance studio with mirrors, dynamic movements, energetic, studio lighting

  • 風景 (demo_landscape.mp4 / demo_landscape.gif)
A beautiful mountain landscape with a flowing river, autumn trees with orange and red leaves, mist rising from the valley, cinematic drone shot, golden hour lighting

軽量でボイスクローニング可能なTTS「VyvoTTS」をWindows + uvで動かす

初めに

Orpheus TTSをベースに開発がされたLLMベースのTTSになっています。

github.com

Orpheus TTSから以下のような変更点があります

  1. モデルサイズの大幅な縮小 : Llama-3.2-3b → LFM2-350M
  2. 推論エンジンの多様化 : 以下に対応
    • Transformers: 標準、Flash Attention対応
    • vLLM: 最速(Linux専用)
    • Unsloth: 4bit/8bit量子化、省メモリ
    • HQQ: 高品質量子化(1-8bit)
  3. GradualRatioDataset : プリトレーニング時に、テキストQAデータと音声データの比率を段階的に変更

開発環境

項目 バージョン
OS Windows 11
GPU NVIDIA GeForce RTX 4070 Ti SUPER (16GB VRAM)
CUDA Toolkit 12.1
Python 3.12
uv 0.9.2

環境構築

uvの環境を作成します

uv init

project.tomlを以下のように書き換えます

[project]
name = "vyvotts"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "accelerate>=1.12.0",
    "flash-attn>=2.7.4",
    "kernels>=0.11.5",
    "pyyaml>=6.0.3",
    "snac>=1.2.1",
    "soundfile>=0.13.1",
    "torch>=2.5.1",
    "torchaudio>=2.5.1",
    "torchvision>=0.20.1",
    "transformers>=4.57.3",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true

[tool.uv.sources]
torch = { index = "pytorch-cu124" }
torchvision = { index = "pytorch-cu124" }
torchaudio = { index = "pytorch-cu124" }
flash-attn = { url = "https://huggingface.co/lldacing/flash-attention-windows-wheel/resolve/main/flash_attn-2.7.4%2Bcu124torch2.6.0cxx11abiFALSE-cp312-cp312-win_amd64.whl" }

pythonのversionを固定します

uv python pin 3.12

依存パッケージをインストールします

uv sync

推論の実行

推論スクリプトの作成します

import torch
from snac import SNAC
from transformers import AutoModelForCausalLM, AutoTokenizer
import yaml
import time
import soundfile as sf


def load_config(config_path: str):
    with open(config_path, 'r') as file:
        return yaml.safe_load(file)


def main():
    print("Loading configuration...")
    config = load_config("vyvotts/configs/inference/lfm2.yaml")

    # Token constants from config
    START_OF_HUMAN = config['START_OF_HUMAN']
    END_OF_TEXT = config['END_OF_TEXT']
    END_OF_HUMAN = config['END_OF_HUMAN']
    START_OF_SPEECH = config['START_OF_SPEECH']
    END_OF_SPEECH = config['END_OF_SPEECH']
    PAD_TOKEN = config['PAD_TOKEN']
    AUDIO_TOKENS_START = config['AUDIO_TOKENS_START']

    device = "cuda"
    model_name = "Vyvo/VyvoTTS-LFM2-Neuvillette"

    print("Loading SNAC model...")
    snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz")
    snac_model = snac_model.to(device)

    print("Loading LLM model with Flash Attention 2...")
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        torch_dtype=torch.bfloat16,
        attn_implementation="flash_attention_2",
        device_map="auto",
    )
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    # Input text
    text = "Hello, this is a test of the VyvoTTS speech synthesis system."
    print(f"Generating speech for: {text}")

    # Preprocess
    input_ids = tokenizer(text, return_tensors="pt").input_ids
    start_token = torch.tensor([[START_OF_HUMAN]], dtype=torch.int64)
    end_tokens = torch.tensor([[END_OF_TEXT, END_OF_HUMAN]], dtype=torch.int64)
    modified_input_ids = torch.cat([start_token, input_ids, end_tokens], dim=1).to(device)
    attention_mask = torch.ones_like(modified_input_ids)

    # Generate
    torch.cuda.synchronize()
    start_time = time.time()

    with torch.no_grad():
        generated_ids = model.generate(
            input_ids=modified_input_ids,
            attention_mask=attention_mask,
            max_new_tokens=1200,
            do_sample=True,
            temperature=0.6,
            top_p=0.95,
            repetition_penalty=1.1,
            eos_token_id=END_OF_SPEECH,
        )

    torch.cuda.synchronize()
    generation_time = time.time() - start_time

    # Parse audio tokens
    token_indices = (generated_ids == START_OF_SPEECH).nonzero(as_tuple=True)
    if len(token_indices[1]) > 0:
        last_idx = token_indices[1][-1].item()
        cropped = generated_ids[:, last_idx+1:]
    else:
        cropped = generated_ids

    row = cropped[0]
    row = row[row != END_OF_SPEECH]
    row_length = row.size(0)
    new_length = (row_length // 7) * 7
    trimmed = row[:new_length]
    code_list = [t.item() - AUDIO_TOKENS_START for t in trimmed]

    # Redistribute codes to SNAC layers
    layer_1, layer_2, layer_3 = [], [], []
    for i in range((len(code_list)+1)//7):
        layer_1.append(code_list[7*i])
        layer_2.append(code_list[7*i+1]-4096)
        layer_3.append(code_list[7*i+2]-(2*4096))
        layer_3.append(code_list[7*i+3]-(3*4096))
        layer_2.append(code_list[7*i+4]-(4*4096))
        layer_3.append(code_list[7*i+5]-(5*4096))
        layer_3.append(code_list[7*i+6]-(6*4096))

    codes = [
        torch.tensor(layer_1).unsqueeze(0).to(device),
        torch.tensor(layer_2).unsqueeze(0).to(device),
        torch.tensor(layer_3).unsqueeze(0).to(device)
    ]

    # Decode audio
    audio = snac_model.decode(codes)
    audio_numpy = audio.detach().squeeze().cpu().numpy()

    # Save
    output_path = "test_output.wav"
    sf.write(output_path, audio_numpy, 24000)

    print(f"Audio shape: {audio.shape}")
    print(f"Generation time: {generation_time:.2f}s")
    print(f"Saved to: {output_path}")


if __name__ == "__main__":
    main()

以下で実行します

uv run python scripts/inference_test.py

テキスト・視覚・時間範囲のプロンプトを使用して音声から特定の音を分離する「sam-audio」をuv + Windowsで動かす

初めに

SAM-Audio(Segment Anything Model for Audio)は、Meta(Facebook Research)が開発した音声分離のためのファウンデーションモデルです。テキスト、視覚、または時間範囲のプロンプトを使用して、複雑な音声ミックスから特定の音を分離できます。

github.com

以下に uvで環境構築をしたリポジトリを公開しています

github.com

開発環境

環境構築

以下に対応するためにpyproject.toml の修正を行います

  • PyTorchのCUDA版を取得
  • Git依存関係のuv形式指定
  • perception-models が Python 3.11以上を要求
  • torchcodecのWindows対応
[project]
name = "sam_audio"
version = "0.1.0"
description = "Segment Anything Audio"
authors = [
    { name="Andros Tjandra", email="androstj@meta.com" },
    { name="Ann Lee", email="annl@meta.com" },
    { name="Bowen Shi", email="bshi@meta.com" },
    { name="Julius Richter", email="jrichter@meta.com" },
    { name="Matt Le", email="mattle@meta.com" },
    { name="Yi-Chiao Wu", email="yichiaowu@meta.com" },
]

readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.11"
dependencies = [
    "dacvae",
    "audiobox_aesthetics",
    "einops",
    "imagebind",
    "laion-clap",
    "numpy",
    "perception-models",
    "pydub",
    "torch>=2.5.0",
    "torchaudio>=2.5.0",
    "torchcodec",
    "torchdiffeq",
    "torchvision>=0.20.0",
    "transformers>=4.54.0",
]

[tool.setuptools.packages.find]
include = ["sam_audio*"]

[tool.ruff]

target-version = "py311"

lint.select=[
    "B",
    "C",
    "E",
    "W",
    "F",
    "I",
]
lint.ignore = [
    "E501",
    "E731",
    "C901",
    "B006",
]

[project.urls]
Homepage = "https://github.com/facebookresearch/sam-audio"
Repository = "https://github.com/facebookresearch/sam-audio"

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

# ========== uv設定(CUDA 12.6対応) ==========

[[tool.uv.index]]
name = "pytorch-cu126"
url = "https://download.pytorch.org/whl/cu126"
explicit = true

[tool.uv.sources]
# Git依存関係
dacvae = { git = "https://github.com/facebookresearch/dacvae.git" }
imagebind = { git = "https://github.com/facebookresearch/ImageBind.git" }
laion-clap = { git = "https://github.com/LAION-AI/CLAP.git" }
perception-models = { git = "https://github.com/facebookresearch/perception_models.git", branch = "unpin-deps" }

# PyTorchパッケージ(CUDA 12.6インデックスから取得)
torch = { index = "pytorch-cu126" }
torchvision = { index = "pytorch-cu126" }
torchaudio = { index = "pytorch-cu126" }
# torchcodecはWindows用CUDA wheelがないためPyPIから取得

修正後のライブラリをインストールするために以下を実行します

uv sync

torchcodecが動作するためにFFmpeg 6のDLL(共有ライブラリ)が必要です。そのため FFmpeg Buildsから「shared」ビルドをダウンロードし、DLLをPATHの通ったディレクトリに配置。

実行

推論をするための以下のようなサンプルスクリプトを作成します

"""SAM-Audio サンプル実行スクリプト"""

import os
import torch
import torchaudio
import gc
from sam_audio import SAMAudio, SAMAudioProcessor

# メモリ最適化設定
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"

def main():
    print("=== SAM-Audio Sample ===")

    # デバイス設定
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    print(f"Device: {device}")

    # GPUメモリをクリア
    if device.type == "cuda":
        torch.cuda.empty_cache()
        gc.collect()
        print(f"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")

    # モデルとプロセッサをロード(baseモデルを使用してメモリ節約)
    model_name = "facebook/sam-audio-base"
    print(f"Loading model: {model_name}")
    model = SAMAudio.from_pretrained(model_name).to(device).eval()
    processor = SAMAudioProcessor.from_pretrained(model_name)
    print("Model loaded!")

    # 入力ファイル
    video_file = "examples/assets/office.mp4"
    description = "A man speaking"

    print(f"Input: {video_file}")
    print(f"Description: {description}")

    # 処理
    print("Processing...")
    inputs = processor(audios=[video_file], descriptions=[description]).to(device)

    with torch.inference_mode():
        result = model.separate(inputs)

    # 保存
    sample_rate = processor.audio_sampling_rate
    torchaudio.save("target.wav", result.target[0].cpu(), sample_rate)
    torchaudio.save("residual.wav", result.residual[0].cpu(), sample_rate)

    print("=== Done! ===")
    print(f"Saved: target.wav (分離された音声)")
    print(f"Saved: residual.wav (残りの音声)")

if __name__ == "__main__":
    main()

このときに VRAM 16GBだとlargeモデルは動かなかったので、baseモデルで推論を行っています。入力時に以下のようなプロンプトを指定することで、指定した男性のみの音声を取得することができます

    description = "A man speaking"

時間指定する場合は、Inputの部分に anchors=[[["+", 6.3, 7.0]]] を追加して実行します

FastSpeech2をuv + Windowsで動かす

初めに

前回 数年前のTTSの tacotron2を動かしました。今回はtactron2の課題の解決した FastSpeech2を実際に動かしていきます

ayousanz.hatenadiary.jp

FastSpeech2で解決したものとして Variance Adaptorを使うことによって明示的な制御をするようになりました。また非自己回帰になったことで推論速度が上がっています

以下でuvで対応したリポジトリは公開しています

github.com

アーキテクチャと課題

非自己回帰 + Variance Adaptorのアーキテクチャになっています。このため推論速度の改善と音単位での時間が合うようになっています

Adaptorは以下でそれぞれ制御しています

  • Duration Predictor : 長さ予測
  • Pitch Predictor : ピッチ予測
  • Energy Predictor : 音量予測

課題として以下のような問題がありました

  • 前処理の「依存地獄」
  • 音質の「平均化」: Loss_{Total} = Loss_{Mel} + Loss_{Duration} + Loss_{Pitch} + Loss_{Energy} の計算をすると平均値が正になる
  • ボコーダーとの「ミスマッチ」:

開発環境

環境構築

環境を構築します

uv init --no-readme
uv python pin 3.12

uvの環境用に以下の project.toml を作成します

     [project]
     name = "fastspeech2"
     version = "1.0.0"
     description = "FastSpeech 2 - PyTorch Implementation for Text-to-Speech"
     requires-python = ">=3.10,<3.13"
     dependencies = [
         "numpy>=1.24.0,<2.0",
         "scipy>=1.11.0",
         "librosa>=0.10.0",
         "matplotlib>=3.7.0",
         "pyyaml>=6.0",
         "g2p-en>=2.1.0",
         "pypinyin>=0.49.0",
         "inflect>=7.0.0",
         "unidecode>=1.3.0",
         "soundfile>=0.12.0",
         "tgt>=1.4.4",
         "tqdm>=4.65.0",
     ]

     [tool.hatch.build.targets.wheel]
     packages = ["."]

     [[tool.uv.index]]
     name = "pytorch-cu121"
     url = "https://download.pytorch.org/whl/cu121"
     explicit = true

     [tool.uv.sources]
     torch = { index = "pytorch-cu121" }
     torchvision = { index = "pytorch-cu121" }
     torchaudio = { index = "pytorch-cu121" }

依存関係をインストールします

     uv sync --no-install-project
     uv add torch torchvision torchaudio

librosaのAPIの変更に対応するために以下の変更をします

audio/stft.py

     42行目:
     # 変更前
     fft_window = pad_center(fft_window, filter_length)
     # 変更後
     fft_window = pad_center(fft_window, size=filter_length)

     145-147行目:
     # 変更前
     mel_basis = librosa_mel_fn(
         sampling_rate, filter_length, n_mel_channels, mel_fmin, mel_fmax
     )
     # 変更後
     mel_basis = librosa_mel_fn(
         sr=sampling_rate, n_fft=filter_length, n_mels=n_mel_channels, fmin=mel_fmin, fmax=mel_fmax
     )

audio/audio_processing.py

     57行目:
     # 変更前
     win_sq = librosa_util.pad_center(win_sq, n_fft)
     # 変更後
     win_sq = librosa_util.pad_center(win_sq, size=n_fft)

     5.3 utils/model.py(torch.load警告対応)

     20行目、63行目、65行目:
     # 変更前
     ckpt = torch.load(ckpt_path)
     # 変更後
     ckpt = torch.load(ckpt_path, weights_only=False)

モデルのダウンロード

GoogleDriveから任意のモデルをダウンロードします。今回はLJSpeechを使いました。

drive.google.com

HiFi-GANボコーダーもダウンロードをして配置をします

     python -c "import zipfile; zipfile.ZipFile('hifigan/generator_LJSpeech.pth.tar.zip').extractall('hifigan/')"
     python -c "import zipfile; zipfile.ZipFile('hifigan/generator_universal.pth.tar.zip').extractall('hifigan/')"

実行

以下で推論を実行します

uv run python synthesize.py --text "Hello, this is a test." --restore_step 900000 --mode single -p config/LJSpeech/preprocess.yaml -m config/LJSpeech/model.yaml -t config/LJSpeech/train.yaml