科学と神々株式会社アドベントカレンダー 2025
LLM量子化 Day 6: データモデルの設計
データモデルとは何か
量子化ツールを作る際、最初に考えるべきことは「どんなデータを扱うか」です。
データモデルとは、システムが扱う情報の構造を定義したものです。適切なデータモデルがあれば、コードは自然と整理され、バグも減ります。
量子化の3つの登場人物
llm-quantizeでは、量子化プロセスを3つの主要なデータモデルで表現しています:
SourceModel QuantizationConfig QuantizedModel
(入力) + (設定) → (出力)
映画制作に例えると
- SourceModel(原作): 映画化される元の小説。どんな作品で、何ページあるのか
- QuantizationConfig(脚本・演出方針): どう映画化するか。上映時間、どの場面を残すか
- QuantizedModel(完成映画): 出来上がった作品。何分の映画で、どれくらいの圧縮率か
SourceModel:入力モデルの表現
SourceModelは「量子化される前のモデル」を表現します。
どんな情報を持つか
SourceModel
├── model_path モデルのパス(HuggingFace IDまたはローカルパス)
├── model_type ソースの種類(HF_HUB / LOCAL_DIR)
├── architecture アーキテクチャ(LlamaForCausalLM等)
├── parameter_count パラメータ数(70億など)
├── dtype 元の精度(float16, bfloat16等)
├── hidden_size 隠れ層のサイズ
├── num_layers レイヤー数
└── ...
設計上の工夫
1. 二つの入力ソースへの対応
モデルは「HuggingFace Hub」からも「ローカルディレクトリ」からも読み込めます。この違いを明示的に区別することで、処理の分岐が明確になります。
# パスから自動判別
source = SourceModel.from_path("meta-llama/Llama-2-7b-hf") # → HF_HUB
source = SourceModel.from_path("./my-local-model") # → LOCAL_DIR
ユーザーは「パスを渡すだけ」で、内部では適切に処理されます。
2. 便利なプロパティ
よく使う条件分岐をプロパティ化しておくと、コードが読みやすくなります:
if source.is_local:
# ローカルモデル固有の処理
elif source.is_hub:
# HuggingFace Hub固有の処理
3. バリデーションの組み込み
「作成したら必ず正しい」とは限りません。バリデーションメソッドを用意しておくと、問題を早期に発見できます:
errors = source.validate()
if errors:
print(f"問題があります: {errors}")
QuantizationConfig:設定の表現
QuantizationConfigは「どのように量子化するか」を定義します。
どんな情報を持つか
QuantizationConfig
├── target_format 出力形式(GGUF / AWQ / GPTQ)
├── quantization_level 量子化レベル(Q4_K_M, 4bit等)
├── output_dir 出力ディレクトリ
├── calibration_samples キャリブレーションサンプル数(デフォルト256)
├── group_size グループサイズ(デフォルト128)
├── enable_checkpoints チェックポイント有効化
└── ...
設計上の工夫
1. Enumによる型安全性
文字列ではなくEnumを使うことで、タイプミスによるバグを防ぎます:
# 悪い例:文字列を使う
config = QuantizationConfig(target_format="gguf") # "gguF" と間違えるかも
# 良い例:Enumを使う
config = QuantizationConfig(target_format=OutputFormat.GGUF) # コンパイル時にチェック
2. デフォルト値の明示
多くのオプションには「賢明なデフォルト値」があります。これを明示することで、ユーザーは最小限の設定で始められます:
# 最小限の設定
config = QuantizationConfig(
target_format=OutputFormat.GGUF,
quantization_level="Q4_K_M"
)
# → output_dir=".", calibration_samples=256, group_size=128 が自動設定
3. 形式ごとのバリデーション
GGUFとAWQでは、有効な量子化レベルが異なります。これをバリデーションで検証します:
# GGUFでは "Q4_K_M" は有効
# AWQでは "4bit" のみ有効
errors = config.validate()
# target_format=AWQ, quantization_level="Q4_K_M" の場合
# → ["Invalid AWQ quantization level: Q4_K_M. Valid options: 4bit"]
4. 出力パスの自動生成
ユーザーが出力ファイル名を指定しなくても、適切な名前を自動生成します:
入力: meta-llama/Llama-2-7b-hf, GGUF, Q4_K_M
出力: Llama-2-7b-hf-Q4_K_M.gguf
入力: my-model, AWQ, 4bit
出力: my-model-awq-4bit/
QuantizedModel:出力の表現
QuantizedModelは「量子化の結果」を表現します。
どんな情報を持つか
QuantizedModel
├── output_path 出力ファイルパス
├── format 形式
├── file_size ファイルサイズ(バイト)
├── compression_ratio 圧縮率(0.0〜1.0)
├── validation_status 検証状態(VALID/INVALID/UNCHECKED)
├── duration_seconds 処理時間
├── peak_memory_bytes ピークメモリ使用量
└── ...
設計上の工夫
1. メタデータの保持
後から「この量子化ファイルは何から作られたか」を追跡できるよう、元モデルの情報を保持します:
result.source_model_path # → "meta-llama/Llama-2-7b-hf"
result.original_dtype # → "float16"
result.original_size # → 14000000000 (約14GB)
2. 圧縮率の計算
「どれくらい小さくなったか」は最も重要な指標の一つです:
compression_ratio = file_size / original_size
# 例:4GB / 14GB = 0.28 (72%の削減)
3. 検証状態の追跡
量子化が「成功した」と言えるためには、出力ファイルが正しく読み込めることを確認すべきです:
class ValidationStatus(Enum):
VALID = "valid" # 検証成功
INVALID = "invalid" # 検証失敗
UNCHECKED = "unchecked" # 未検証
4. JSON出力のサポート
自動化やログ記録のために、結果をJSON形式で出力できます:
{
"status": "success",
"output_path": "/output/llama-2-7b-Q4_K_M.gguf",
"format": "gguf",
"file_size": 4000000000,
"compression_ratio": 0.28,
"duration_seconds": 1800.5,
"validation_status": "valid"
}
データフロー全体像
3つのモデルがどのように連携するか、全体の流れを見てみましょう:
1. ユーザー入力
└── CLIコマンド: llm-quantize quantize meta-llama/... gguf -q Q4_K_M
2. SourceModel作成
└── SourceModel.from_path() でモデル情報を取得
└── validate() でエラーチェック
3. QuantizationConfig作成
└── CLIオプションから設定を構築
└── validate() でエラーチェック
4. 量子化実行
└── Quantizer(source, config).quantize()
5. QuantizedModel作成
└── 結果を構造化された形で返す
└── validate() で出力を検証
6. 結果表示
└── Human/JSON形式で結果を表示
Tips: データモデル設計のポイント
1. dataclassを活用する
Pythonの@dataclassは、データモデルの実装に最適です:
- 自動的に
__init__、__repr__、__eq__が生成される - 型ヒントが強制される
- デフォルト値を簡単に設定できる
@dataclass
class QuantizationConfig:
target_format: OutputFormat # 必須
quantization_level: str # 必須
output_dir: str = "." # オプション(デフォルトあり)
2. バリデーションは作成時に近い場所で
問題は早期に発見するほど、デバッグが容易です。データモデルにバリデーションを組み込むことで、問題のある設定が後段まで流れることを防ぎます。
3. 不変性を意識する
設定が途中で変わると、バグの原因になります。可能であれば、作成後は変更できないようにしましょう:
@dataclass(frozen=True) # frozen=Trueで不変に
class ImmutableConfig:
target_format: OutputFormat
4. 関連情報はまとめる
「このデータは常にセットで使われる」なら、それを一つの構造にまとめます。これにより、データの受け渡しが簡潔になり、関連性も明確になります。
次回予告
Day 7では「抽象化の力」について解説します。BaseQuantizerという共通インターフェースを通じて、GGUF、AWQ、GPTQという異なる量子化器を統一的に扱う方法を学びます。
データモデルは「設計図」です。しっかりとした設計図があれば、建物(コード)は自然と美しく建ちます。逆に、設計図がいい加減だと、どんなに腕の良い職人(プログラマー)でも良い建物は建てられません。