mcpField
M
model-pruning

model-pruning

github.com/kalarislabsその他7342 回インストール3 日前に更新
リポジトリ

model-pruning について

Model Pruningは、Wanda、SparseGPT、N:MプルーニングなどでLLMの重みを削減するスキルガイドです。制約のあるハードウェアや推論向けにモデルを圧縮する実践者を対象に、手法の選択と検証方法を解説します。

主な機能

  1. 01WandaとSparseGPTの手法
  2. 02構造化およびN:Mスパース化
  3. 03再学習なしのワンショットプルーニング
  4. 04レイヤー単位および反復型の戦略
  5. 05キャリブレーションと結果検証の指針

活用シーン

  1. 01制約のあるハードウェア向けのモデルサイズ削減
  2. 02推論用スパースモデルの準備
  3. 03対象ハードウェアに応じたプルーニングパターンの選択
  4. 04検証によるプルーニング手法の比較

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kalarislabs/research-agent-skills --skill model-pruning

インストール後、Claude Code で/model-pruningを入力して読み込まれたか確認します。

同梱ファイルreferences/production-deployment.mdreferences/wanda.md

詳細

モデルプルーニング:LLM の圧縮

このスキルを使用する場面

次のような場合にモデルプルーニングを使用します。

  • 精度の低下を 1% 未満に抑えながら、モデルサイズを 40~60% 削減する
  • ハードウェアに適したスパース性を活用して、推論を高速化する(2~4 倍の高速化)
  • リソースが限られたハードウェアにデプロイする(モバイル、エッジデバイス)
  • ワンショット方式を使って、再トレーニングせずに圧縮する
  • メモリ使用量を削減して、効率的なサービングを実現する

主な手法:Wanda(重み × 活性化)、SparseGPT(二次情報)、構造化プルーニング、N:M スパース性

論文:Wanda ICLR 2024(arXiv 2306.11695)、SparseGPT(arXiv 2301.00774)

インストール

# Wanda implementation
git clone https://github.com/locuslab/wanda
cd wanda
pip install -r requirements.txt

# Optional: SparseGPT
git clone https://github.com/IST-DASLab/sparsegpt
cd sparsegpt
pip install -e .

# Dependencies
pip install torch transformers accelerate

クイックスタート

Wanda プルーニング(ワンショット、再トレーニング不要)

出典:ICLR 2024(arXiv 2306.11695)

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# Load model
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    torch_dtype=torch.float16,
    device_map="cuda"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

# Calibration data (small dataset for activation statistics)
calib_data = [
    "The quick brown fox jumps over the lazy dog.",
    "Machine learning is transforming the world.",
    "Artificial intelligence powers modern applications.",
]

# Wanda pruning function
def wanda_prune(model, calib_data, sparsity=0.5):
    """
    Wanda: Prune by weight magnitude × input activation.

    Args:
        sparsity: Fraction of weights to prune (0.5 = 50%)
    """
    # 1. Collect activation statistics
    activations = {}

    def hook_fn(name):
        def hook(module, input, output):
            # Store input activation norms
            activations[name] = input[0].detach().abs().mean(dim=0)
        return hook

    # Register hooks for all linear layers
    hooks = []
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear):
            hooks.append(module.register_forward_hook(hook_fn(name)))

    # Run calibration data
    model.eval()
    with torch.no_grad():
        for text in calib_data:
            inputs = tokenizer(text, return_tensors="pt").to(model.device)
            model(**inputs)

    # Remove hooks
    for hook in hooks:
        hook.remove()

    # 2. Prune weights based on |weight| × activation
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear) and name in activations:
            W = module.weight.data
            act = activations[name]

            # Compute importance: |weight| × activation
            importance = W.abs() * act.unsqueeze(0)

            # Flatten and find threshold
            threshold = torch.quantile(importance.flatten(), sparsity)

            # Create mask
            mask = importance >= threshold

            # Apply mask (prune)
            W *= mask.float()

    return model

# Apply Wanda pruning (50% sparsity, one-shot, no retraining)
pruned_model = wanda_prune(model, calib_data, sparsity=0.5)

# Save
pruned_model.save_pretrained("./llama-2-7b-wanda-50")
SparseGPT(二次情報によるプルーニング)

出典:arXiv 2301.00774

from sparsegpt import SparseGPT

# Load model
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")

# Initialize SparseGPT
pruner = SparseGPT(model)

# Calibration data
calib_data = load_calibration_data()  # ~128 samples

# Prune (one-shot, layer-wise reconstruction)
pruned_model = pruner.prune(
    calib_data=calib_data,
    sparsity=0.5,           # 50% sparsity
    prunen=0,               # Unstructured (0) or N:M structured
    prunem=0,
    percdamp=0.01,          # Damping for Hessian inverse
)

# Results: Near-lossless pruning at 50% sparsity
N:M 構造化プルーニング(ハードウェアアクセラレーター)
def nm_prune(weight, n=2, m=4):
    """
    N:M pruning: Keep N weights per M consecutive weights.
    Example: 2:4 = keep 2 out of every 4 weights.

    Compatible with NVIDIA sparse tensor cores (2:4, 4:8).
    """
    # Reshape weight into groups of M
    shape = weight.shape
    weight_flat = weight.flatten()

    # Pad to multiple of M
    pad_size = (m - weight_flat.numel() % m) % m
    weight_padded = F.pad(weight_flat, (0, pad_size))

    # Reshape into (num_groups, m)
    weight_grouped = weight_padded.reshape(-1, m)

    # Find top-N in each group
    _, indices = torch.topk(weight_grouped.abs(), n, dim=-1)

    # Create mask
    mask = torch.zeros_like(weight_grouped)
    mask.scatter_(1, indices, 1.0)

    # Apply mask
    weight_pruned = weight_grouped * mask

    # Reshape back
    weight_pruned = weight_pruned.flatten()[:weight_flat.numel()]
    return weight_pruned.reshape(shape)

# Apply 2:4 sparsity (NVIDIA hardware)
for name, module in model.named_modules():
    if isinstance(module, torch.nn.Linear):
        module.weight.data = nm_prune(module.weight.data, n=2, m=4)

# 50% sparsity, 2× speedup on A100 with sparse tensor cores

基本概念

1. プルーニングの基準

マグニチュードプルーニング(ベースライン):

# Prune weights with smallest absolute values
importance = weight.abs()
threshold = torch.quantile(importance, sparsity)
mask = importance >= threshold

Wanda(重み × 活性化):

# Importance = |weight| × input_activation
importance = weight.abs() * activation
# Better than magnitude alone (considers usage)

SparseGPT(二次情報):

# Uses Hessian (second derivative) for importance
# More accurate but computationally expensive
importance = weight^2 / diag(Hessian)
2. 構造化と非構造化

非構造化(細粒度):

  • 個々の重みをプルーニングする
  • 品質が高い(精度がより高い)
  • ハードウェアによる高速化は得られない(不規則なスパース性)

構造化(粗粒度):

  • ニューロン、ヘッド、またはレイヤー全体をプルーニングする
  • 品質が低い(精度の低下が大きい)
  • ハードウェアによる高速化が得られる(規則的なスパース性)

半構造化(N:M):

  • 両方の長所を兼ね備えています
  • スパース性 50%(2:4)→ NVIDIA GPU で 2 倍の高速化
  • 精度の低下は最小限です
3. スパース性のパターン
# Unstructured (random)
# [1, 0, 1, 0, 1, 1, 0, 0]
# Pros: Flexible, high quality
# Cons: No speedup

# Structured (block)
# [1, 1, 0, 0, 1, 1, 0, 0]
# Pros: Hardware friendly
# Cons: More accuracy loss

# N:M (semi-structured)
# [1, 0, 1, 0] [1, 1, 0, 0]  (2:4 pattern)
# Pros: Hardware speedup + good quality
# Cons: Requires specific hardware (NVIDIA)

プルーニング戦略

戦略 1:段階的なマグニチュードプルーニング
def gradual_prune(model, initial_sparsity=0.0, final_sparsity=0.5, num_steps=100):
    """Gradually increase sparsity during training."""
    for step in range(num_steps):
        # Current sparsity
        current_sparsity = initial_sparsity + (final_sparsity - initial_sparsity) * (step / num_steps)

        # Prune at current sparsity
        for module in model.modules():
            if isinstance(module, torch.nn.Linear):
                weight = module.weight.data
                threshold = torch.quantile(weight.abs().flatten(), current_sparsity)
                mask = weight.abs() >= threshold
                weight *= mask.float()

        # Train one step
        train_step(model)

    return model
戦略 2:レイヤー単位のプルーニング
def layer_wise_prune(model, sparsity_per_layer):
    """Different sparsity for different layers."""
    # Early layers: Less pruning (more important)
    # Late layers: More pruning (less critical)

    sparsity_schedule = {
        "layer.0": 0.3,   # 30% sparsity
        "layer.1": 0.4,
        "layer.2": 0.5,
        "layer.3": 0.6,   # 60% sparsity
    }

    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear):
            # Find layer index
            for layer_name, sparsity in sparsity_schedule.items():
                if layer_name in name:
                    # Prune at layer-specific sparsity
                    prune_layer(module, sparsity)
                    break

    return model
戦略 3:反復プルーニングとファインチューニング
def iterative_prune_finetune(model, target_sparsity=0.5, iterations=5):
    """Prune gradually with fine-tuning between iterations."""
    current_sparsity = 0.0
    sparsity_increment = target_sparsity / iterations

    for i in range(iterations):
        # Increase sparsity
        current_sparsity += sparsity_increment

        # Prune
        prune_model(model, sparsity=current_sparsity)

        # Fine-tune (recover accuracy)
        fine_tune(model, epochs=2, lr=1e-5)

    return model

# Results: Better accuracy than one-shot at high sparsity

本番環境へのデプロイ

詳細、コード例、パラメーター表については、references/production-deployment.md を参照してください。このステップが該当する場合にお読みください。

ベストプラクティス

1. スパース性の選択
# Conservative (safe)
sparsity = 0.3  # 30%, <0.5% loss

# Balanced (recommended)
sparsity = 0.5  # 50%, ~1% loss

# Aggressive (risky)
sparsity = 0.7  # 70%, 2-5% loss

# Extreme (model-dependent)
sparsity = 0.9  # 90%, significant degradation
2. 手法の選択
# One-shot, no retraining → Wanda or SparseGPT
if no_retraining_budget:
    use_method = "wanda"  # Faster

# Best quality → SparseGPT
if need_best_quality:
    use_method = "sparsegpt"  # More accurate

# Hardware speedup → N:M structured
if need_speedup:
    use_method = "nm_prune"  # 2:4 or 4:8
3. よくある落とし穴を避ける
# ❌ Bad: Pruning without calibration data
prune_random(model)  # No activation statistics

# ✅ Good: Use calibration data
prune_wanda(model, calib_data)

# ❌ Bad: Too high sparsity in one shot
prune(model, sparsity=0.9)  # Massive accuracy loss

# ✅ Good: Gradual or iterative
iterative_prune(model, target=0.9, steps=10)

パフォーマンス比較

スパース性 50% におけるプルーニング手法(LLaMA-7B):

手法精度の低下速度メモリ再トレーニングが必要
Magnitude-2.5%1.0×-50%いいえ
Wanda-0.8%1.0×-50%いいえ
SparseGPT-0.4%1.0×-50%いいえ
N:M (2:4)-1.0%2.0×-50%いいえ
Structured-3.0%2.0×-50%いいえ

出典:Wanda 論文(ICLR 2024)、SparseGPT 論文

リソース

エージェントの運用手順

  1. 環境を確認します。 GPU メモリ、モデルのチェックポイントとライセンス、フレームワークのバージョン、入力形式を確認します。
  2. 入力を明確にします。 データまたはユーザーに確認し、形式、識別子、パラメーターを特定します。結果に影響する値は推測せず、質問します。
  3. まず小規模に実行します。 最初に、単一サンプルを低解像度または短い長さで推論します。
  4. 上記の手順と参考情報を使用して、タスク全体を実行します。
  5. 結果を検証します。 出力を定性的に確認し、タスク指標でも評価します。生成コンテンツのライセンス上の制限も確認します。
  6. 報告します。 実行した内容(バージョン、コマンド、パラメーター)、確認した内容、未確認の事項を伝えます。
このような場合対応方法
大きな入力でメモリ不足が発生する解像度やシーケンス長を下げる、半精度を使用する、または入力を分割します。
これらの手順に記載されている関数、フラグ、エンドポイントがインストール済みのバージョンに存在しないインストール済みバージョンのドキュメント(help()、--help、公式ドキュメント)を確認し、適宜対応してユーザーに伝えます。API を捏造してはいけません。
必須の入力、識別子、パラメーターが曖昧であるユーザーに質問するか、実行前に仮定を明示します。

誠実性に関するルール

  • 結果、パラメーター、識別子、引用、統計情報を捏造してはいけません。実行または検証できないことは、はっきりと伝えます。
  • 商用利用または公開前に、モデルとデータセットのライセンスを確認します。
  • ここに記載されているバージョン固有の情報は古くなっている可能性があるため、インストール済みバージョンの公式ドキュメントで確認します。
  • 費用が発生する操作、共有 GPU やクラウドのクォータを消費する操作、個人データや患者データを扱う操作、または取り消せない操作を行う前に、許可を求めます。

関連スキル

  • awq-quantization:わずかな精度低下で 3 倍の高速化を実現する、LLM を 4 ビットに圧縮するためのアクティベーション対応重み量子化です。
  • quantizing-models-bitsandbytes:わずかな精度低下でメモリ使用量を 50~75%削減できるよう、LLM を 8 ビットまたは 4 ビットに量子化します。
  • speculative-decoding:スペキュレーティブデコーディング、Medusa の複数ヘッド、ルックアヘッドデコーディング技術を使用して LLM 推論を高速化します。

コメント