bitsandbytesasrquantization

VibeVoice-ASRを量子化してDERはどう変わるか?4bit・8bit・originalを実測比較

要約

microsoft/VibeVoice-ASR を bitsandbytes で量子化し、4bit・8bit・オリジナル(fp16) の3条件で話者ダイアライゼーション精度(DER)を実測した。結果を一言でまとめると:

量子化によってDERは改善するケースがある。特に4bit量子化はオリジナルよりも精度が高い場面が複数あった。

ただしコンテンツの種類によって挙動が大きく異なるため、「どんな音声にも量子化が効く」とは言えない。本記事では3種類の音声(ドラマ系・討論系①・討論系②)での実測値を詳細に分析し、量子化を実運用に持ち込む際の判断基準を提示する。

条件モデルサイズVRAM使用量ドラマ DER討論①DER討論②DER
original (fp16)16.16 GiB~22.9GB35.73%6.68%15.52%
8bit量子化9.3 GiB~29.6GB30.63%8.03%14.80%
4bit量子化5.85 GiB~24.1GB27.23%8.91%14.98%

はじめに:なぜVibeVoiceを量子化したいのか

LLMベースの音声処理モデルはその能力の高さと引き換えに、非常に大きなモデルサイズを要求する。microsoft/VibeVoice-ASRも例外ではなく、safetensorsの合計は 16.16 GiB。RTX 4090(24GB)でギリギリ動作するレベルだ。

本番環境やコスト削減を意識したとき、真っ先に候補に挙がるのが 量子化(Quantization) だ。モデルの重みをより少ないビット数で表現することで、メモリ使用量と推論コストを削減できる。

今回はHuggingFaceエコシステムで広く使われている bitsandbytes ライブラリを使い、4bit・8bitの量子化を試みた。単なる速度・サイズ計測だけでなく、話者ダイアライゼーションのDER(Diarization Error Rate)にどう影響するかを実データで計測した点が本記事のオリジナルな貢献だ。


用語解説

本記事に登場する専門用語を簡単に説明する。

DER(Diarization Error Rate)
話者ダイアライゼーションの精度指標。「誰がいつ話したか」の予測がどれだけ間違っているかを示す。0%が完璧で、数値が小さいほど良い。内訳は以下の3つの誤りから構成される:

  • Confusion(混同): ある話者の発話を別の話者と誤認識
  • Missed Detection(検出漏れ): 本来話者がいるのに検出できなかった区間
  • False Alarm(誤検出): 話者がいないのに話者ありと判定した区間

量子化(Quantization)
ニューラルネットワークの重みを表す浮動小数点数(通常fp32やfp16)を、より少ないビット数(int8やint4)に変換する技術。精度をわずかに犠牲にしながら、メモリ使用量と演算コストを大幅に削減できる。

bitsandbytes
HuggingFace製の量子化ライブラリ。load_in_8bit=Trueload_in_4bit=True を数行追加するだけでモデルを量子化できる。NF4(Normal Float 4)形式の4bit量子化に対応しており、LLMの軽量化で広く実績がある。

collar_sec
DER計算時に話者切り替わりの境界付近に設ける「猶予時間」。今回は 0.0(猶予なし)で計測しており、厳密な評価条件となっている。


実験環境

text
データセット : 日本語音声 10分
temperature  : 0
beam_size    : 3
repetition_penalty : 1.5
compute_type : fp16
GPU: RTX5090
評価指標 : DER (collar_sec=0.0, skip_overlap=False)

モデルサイズと実ファイル計測値

text
元モデル (fp16)                 : 16.16 GiB (model-*.safetensors 合計)
8bit量子化後 (vibevoice-asr-8bit): 9.3 GiB
4bit量子化後 (vibevoice-asr-4bit): 5.85 GiB

理論上、8bit量子化でサイズは約1/2、4bit量子化で約1/3になる。実測でもこの理論にほぼ合致した(fp16 → 8bit: 57.5%、fp16 → 4bit: 36.2%)。


量子化の実装コード

VibeVoice-ASRは AutoModelForSpeechSeq2Seq ではなく独自の VibeVoiceASRForConditionalGenerationVibeVoiceASRProcessor を使う点が一般的なWhisperと異なる。また、Processorのロード時に language_model_pretrained_name として Qwen/Qwen2.5-1.5B の指定が必要だ。

量子化スクリプト(quantize_vibevoice.py)

python
#!/usr/bin/env python
"""
Quantize VibeVoice ASR model with bitsandbytes and save it.

Usage:
    python quantize_vibevoice.py \
        --model_path microsoft/VibeVoice-ASR \
        --tokenizer_path Qwen/Qwen2.5-1.5B \
        --output_dir ./vibevoice-asr-4bit \
        --quantization 4bit
"""

import argparse
import importlib.metadata
from pathlib import Path

import torch
from transformers import BitsAndBytesConfig

from vibevoice.modular.modeling_vibevoice_asr import (
    VibeVoiceASRForConditionalGeneration,
)
from vibevoice.processor.vibevoice_asr_processor import VibeVoiceASRProcessor


def _parse_dtype(dtype_name: str) -> torch.dtype:
    table = {
        "bf16": torch.bfloat16,
        "fp16": torch.float16,
        "fp32": torch.float32,
    }
    if dtype_name not in table:
        raise ValueError(f"Unsupported dtype: {dtype_name}")
    return table[dtype_name]


def _ensure_bitsandbytes_installed() -> None:
    try:
        version = importlib.metadata.version("bitsandbytes")
    except importlib.metadata.PackageNotFoundError as exc:
        raise RuntimeError(
            "bitsandbytes is not installed.\n"
            "Install it with: uv pip install bitsandbytes"
        ) from exc
    print(f"Detected bitsandbytes=={version}")


def build_bnb_config(
    quantization: str, compute_dtype: torch.dtype
) -> BitsAndBytesConfig:
    if quantization == "4bit":
        return BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_quant_type="nf4",
            bnb_4bit_use_double_quant=True,  # さらにメモリを削減
            bnb_4bit_compute_dtype=compute_dtype,
        )
    if quantization == "8bit":
        return BitsAndBytesConfig(load_in_8bit=True)
    raise ValueError(f"Unsupported quantization: {quantization}")


def quantize_and_save(
    model_path: str,
    tokenizer_path: str,
    output_dir: str,
    quantization: str,
    compute_dtype: torch.dtype,
    device_map: str,
) -> None:
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    # VibeVoice固有: language_model_pretrained_name の指定が必要
    print(f"Loading processor from: {model_path}")
    processor = VibeVoiceASRProcessor.from_pretrained(
        model_path,
        language_model_pretrained_name=tokenizer_path,
    )

    print(f"Loading model in {quantization}...")
    bnb_config = build_bnb_config(
        quantization=quantization, compute_dtype=compute_dtype
    )

    model = VibeVoiceASRForConditionalGeneration.from_pretrained(
        model_path,
        quantization_config=bnb_config,
        device_map=device_map,
        trust_remote_code=True,
    )
    model.eval()

    print(f"Saving quantized model to: {output_dir}")
    model.save_pretrained(output_dir)
    processor.save_pretrained(output_dir)
    print("Done.")

実行コマンド

bash
# インストール
pip install bitsandbytes transformers accelerate torch

# 4bit量子化(NF4 + double quantization、compute_dtype=bf16)
python quantize_vibevoice.py \
    --model_path microsoft/VibeVoice-ASR \
    --tokenizer_path Qwen/Qwen2.5-1.5B \
    --output_dir ./vibevoice-asr-4bit \
    --quantization 4bit \
    --compute_dtype bf16

# 8bit量子化
python quantize_vibevoice.py \
    --model_path microsoft/VibeVoice-ASR \
    --tokenizer_path Qwen/Qwen2.5-1.5B \
    --output_dir ./vibevoice-asr-8bit \
    --quantization 8bit

量子化モデルの再ロード

保存済みモデルを使う際も BitsAndBytesConfig の再指定が必要な点に注意。

python
from transformers import BitsAndBytesConfig
from vibevoice.modular.modeling_vibevoice_asr import VibeVoiceASRForConditionalGeneration
from vibevoice.processor.vibevoice_asr_processor import VibeVoiceASRProcessor

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
)

model = VibeVoiceASRForConditionalGeneration.from_pretrained(
    "./vibevoice-asr-4bit",
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)
processor = VibeVoiceASRProcessor.from_pretrained(
    "./vibevoice-asr-4bit",
    language_model_pretrained_name="Qwen/Qwen2.5-1.5B",
)

実測結果の詳細分析

コンテンツ別DER比較

ドラマ系(15話者、444.19秒)

複数話者が複雑に絡み合う最も困難なシナリオ。

条件DERConfusionMissedFalse Alarm検出話者数VRAM生成時間
original35.73%128.17s10.51s20.01s1022,909MiB99.47s
8bit30.63%97.94s17.46s20.67s1329,597MiB224.39s
4bit27.23%80.34s8.27s32.35s1424,077MiB242.48s

注目ポイント: ドラマ系では量子化によってDERが大幅に改善した。特に4bitはオリジナルより 8.5ポイント低い。Confusionエラー(話者混同)が128.17秒 → 80.34秒と大幅に減少しており、量子化によって細かい話者の識別能力が高まった可能性がある。

一方でFalse Alarmは20秒から32秒に増加しており、モデルが「より積極的に話者を検出する」方向に変化したと解釈できる。

討論系①(10話者、585.33秒)

比較的明確な発話交代が行われる討論形式の音声。

条件DERConfusionMissedFalse Alarm検出話者数VRAM生成時間
original6.68%34.54s2.84s1.74s623,057MiB106.41s
8bit8.03%41.69s3.95s1.37s629,585MiB256.03s
4bit8.91%50.32s1.33s0.53s524,065MiB215.94s

注目ポイント: 討論系①ではオリジナルが最良。既にDERが6.68%と非常に低く、これ以上の改善余地が少ない「天井」に近い状態では、量子化による精度低下が出やすい。3条件すべてで話者数の推定が実際の10人より少なく、この音声では話者数推定自体が課題となっている。

討論系②(6話者、596.34秒)

より話者数が少ない討論形式。

条件DERConfusionMissedFalse Alarm検出話者数VRAM生成時間
original15.52%75.34s16.21s0.98s722,905MiB99.98s
8bit14.80%71.24s15.95s1.05s729,583MiB210.72s
4bit14.98%71.50s16.80s1.04s724,065MiB201.22s

注目ポイント: 討論系②では3条件でDERに大きな差がない(15.52% vs 14.80% vs 14.98%)。8bitがわずかに最良。この音声では量子化の有無よりも、Confusionエラー(71〜75秒)をどう減らすかが課題だ。


深い洞察:なぜ量子化でDERが改善するのか?

直感に反して量子化でDERが改善した理由について考察する。

仮説1:正則化効果

量子化は重みに微小なノイズを加える操作と見なせる。これが一種の正則化として機能し、過学習的な挙動を抑制した可能性がある。ドラマ系のような「難しい」タスクで効果が出やすいのは、オリジナルモデルが過度に訓練データの特性に依存していた場合に整合する。

仮説2:話者検出の積極性の変化

ドラマ系の結果を見ると、量子化(特に4bit)ではFalse Alarmが増加しながらもConfusionが大幅減少している。これはモデルが「不確かな場合でも別話者と判定する」方向に変化したことを示唆する。多話者環境ではConfusionの削減効果がFalse Alarmの増加を上回り、結果的にDERが改善した。

仮説3:推論の確率分布の平坦化

fp16からint4への変換で重みの表現精度が下がると、モデルの出力分布が平坦化(確信度が下がる)される場合がある。これにより支配的な話者への偏りが減少し、少数話者の検出精度が上がった可能性がある。ドラマ系でhyp_speakersが10→14に増加したことはこの仮説を支持する。


VRAM使用量の逆転現象に注意

意外な事実として、8bit量子化はオリジナルよりもVRAMを多く使用した

text
original : 22,909 MiB (ドラマ系)
4bit     : 24,077 MiB (ドラマ系)
8bit     : 29,597 MiB (ドラマ系)

モデルの重みファイルサイズ(ディスク容量)は8bit < 4bitの順で削減されているが、推論時のVRAM使用量は異なる。これは:

  1. 計算時の一時展開: 量子化モデルは演算時に一部をfp16に展開するためのバッファが必要
  2. 活性化値のメモリ: 重みだけでなく中間層の活性化値がVRAMを消費する
  3. bitsandbytesのオーバーヘッド: 量子化ライブラリ自体のメモリ管理コスト

4bit量子化でVRAMがオリジナルより増加した(22.9GB → 24.1GB)のも同じ理由だ。「量子化 = VRAMが必ず減る」という思い込みは危険で、実際の推論プロファイルで確認することが重要だ。


推論速度のトレードオフ

text
生成時間の比較(ドラマ系):
original : 99.47s  (1.0x)
8bit     : 224.39s (2.25x 遅い)
4bit     : 242.48s (2.44x 遅い)

量子化によって推論は 2〜2.5倍遅くなった。これはbitsandbytesの量子化演算がGPUカーネルのスループットを最大化できていないためだ。量子化はモデルをロードしてから推論する場合に最適化されており、バッチサイズや入力長によっても大きく変わる。

速度を優先するなら、以下の代替手法も検討に値する:

  • GPTQ量子化: オフライン最適化によりbitsandbytesより高速な推論が可能
  • AWQ(Activation-aware Weight Quantization): 重要な重みを保護しながら量子化
  • CTranslate2: WhisperモデルをC++で高速推論するフレームワーク(VibeVoiceのASRコンポーネントと相性が良い可能性)

FAQ

Q1. 量子化によってASR(文字起こし)の精度も下がりますか?

本実験ではDERのみを計測しており、ASR精度(WERやCER)は別途評価が必要です。一般的に量子化によるASR精度の低下は1〜3%程度と軽微なケースが多いですが、日本語の場合は特にCJK文字の境界処理に影響が出る可能性があります。

Q2. 4bit量子化でVRAMがオリジナルより増えたのはバグですか?

バグではありません。bitsandbytesはモデルの重みをint4で保持しますが、行列演算はfp16で行います。そのため推論時には重みの展開バッファや中間テンソルのVRAMが必要です。モデルサイズ(ディスク容量)とVRAM使用量は別物です。

Q3. repetition_penalty=1.5はかなり高めですが、なぜこの値を使いましたか?

VibeVoice-ASRはRich Transcription形式のタグ(話者ラベルなど)を含む出力を生成します。これらのタグが繰り返し生成される「ループ」問題を抑制するために、やや高めのrepetition_penaltyを設定しています。値が高すぎると正当な語句の再出現も抑制されるため、1.5は実験的に見つけたバランス点です。


まとめ

VibeVoice-ASRの量子化実験から得られた主な知見:

  1. 量子化は必ずしも精度を下げない — ドラマ系では4bit量子化がオリジナルを8.5ポイント上回った
  2. コンテンツ特性が結果を左右する — 複雑な多話者音声ほど量子化の恩恵を受けやすい
  3. VRAM使用量は単純に削減されない — 推論時のバッファにより、量子化後にVRAMが増えるケースがある
  4. 速度は2〜2.5倍低下する — bitsandbytesの量子化推論はモデルの演算効率が下がる
  5. 4bit量子化(NF4)は実用的な選択肢 — ディスク容量を1/3に削減しながら、特定コンテンツでは精度も改善

量子化は「精度を犠牲にしてコストを下げる」という単純な話ではない。コンテンツの特性を理解した上で選択することが重要で、まずは手元のデータでA/Bテストを行うことを強く推奨する。

関連するブログ

この記事に近いテーマのブログをピックアップしています。