knowledge-distillation について
knowledge-distillationは、大規模な教師モデルの振る舞いを小さな学生モデルへ移すためのスキルです。蒸留損失、合成応答データ、複数教師による学習、評価を扱い、モデルの圧縮や特化に役立ちます。
主な機能
- 01温度スケーリングとソフトターゲット
- 02順方向・逆方向KL蒸留
- 03ロジット蒸留と応答蒸留
- 04二段階・複数教師学習
- 05蒸留モデルの評価
活用シーン
- 01推論コストを抑えるモデル圧縮
- 02独自モデルの能力を小型モデルへ移行
- 03ドメインデータによる特化モデルの作成
- 04教師モデルの生成データで小型モデルを改善
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ kalarislabs/ research-agent-skills --skill knowledge-distillationインストール後、Claude Code で/knowledge-distillationを入力して読み込まれたか確認します。
同梱ファイルreferences/minillm.md
詳細
ナレッジ蒸留:LLM の圧縮
このスキルを使用する場面
次のような場合に、ナレッジ蒸留を使用します。
- モデルを圧縮する:性能の 90%以上を維持しながら、70B から 7B へ圧縮します
- 能力を転移する:プロプライエタリモデル(GPT-4)の能力をオープンソースモデル(LLaMA、Mistral)に転移します
- 推論コストを削減する:より小さな生徒モデルをデプロイします
- 特化型モデルを作成する:ドメイン固有の知識を蒸留します
- 小規模モデルを改善する:大規模な教師モデルから生成した合成データを使用します
主な手法:温度スケーリング、ソフトターゲット、逆 KLD(MiniLLM)、ロジット蒸留、応答蒸留
論文:Hinton et al. 2015(arXiv 1503.02531)、MiniLLM(arXiv 2306.08543)、KD Survey(arXiv 2402.13116)
インストール
# Standard transformers
pip install transformers datasets accelerate
# For training
pip install torch deepspeed wandb
# Optional: MiniLLM implementation
git clone https://github.com/microsoft/LMOps
cd LMOps/minillm
pip install -e .
クイックスタート
基本的なナレッジ蒸留
import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
# 1. Load teacher (large) and student (small) models
teacher = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-70b-hf", # Large teacher
torch_dtype=torch.float16,
device_map="auto"
)
student = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf", # Small student
torch_dtype=torch.float16,
device_map="cuda:0"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-70b-hf")
# 2. Define distillation loss
def distillation_loss(student_logits, teacher_logits, labels, temperature=2.0, alpha=0.5):
"""
Combine hard loss (cross-entropy) with soft loss (KL divergence).
Args:
temperature: Softens probability distributions (higher = softer)
alpha: Weight for distillation loss (1-alpha for hard loss)
"""
# Hard loss: Standard cross-entropy with true labels
hard_loss = F.cross_entropy(student_logits.view(-1, student_logits.size(-1)), labels.view(-1))
# Soft loss: KL divergence between student and teacher
soft_targets = F.softmax(teacher_logits / temperature, dim=-1)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_loss = F.kl_div(soft_student, soft_targets, reduction='batchmean') * (temperature ** 2)
# Combined loss
return alpha * soft_loss + (1 - alpha) * hard_loss
# 3. Training loop
for batch in dataloader:
# Teacher forward (no grad)
with torch.no_grad():
teacher_outputs = teacher(**batch)
teacher_logits = teacher_outputs.logits
# Student forward
student_outputs = student(**batch)
student_logits = student_outputs.logits
# Compute distillation loss
loss = distillation_loss(
student_logits,
teacher_logits,
batch['labels'],
temperature=2.0,
alpha=0.7 # 70% soft, 30% hard
)
# Backward and optimize
loss.backward()
optimizer.step()
optimizer.zero_grad()
MiniLLM(逆 KLD)
出典:arXiv 2306.08543(2024)
革新点:順方向 KLD の代わりに逆方向 KLD を使用し、生成モデルの蒸留を改善します。
def reverse_kl_loss(student_logits, teacher_logits, temperature=1.0):
"""
Reverse KL divergence: KL(Teacher || Student)
Better for generative models than forward KL.
"""
# Teacher distribution (target)
p_teacher = F.softmax(teacher_logits / temperature, dim=-1)
# Student distribution (model)
log_p_student = F.log_softmax(student_logits / temperature, dim=-1)
# Reverse KL: Sum over teacher, student learns to cover teacher's modes
reverse_kl = -(p_teacher * log_p_student).sum(dim=-1).mean()
return reverse_kl * (temperature ** 2)
# Training with MiniLLM
for batch in dataloader:
with torch.no_grad():
teacher_logits = teacher(**batch).logits
student_logits = student(**batch).logits
# Reverse KLD (better for generation)
loss = reverse_kl_loss(student_logits, teacher_logits, temperature=1.0)
loss.backward()
optimizer.step()
逆 KL を使用する理由
- 順方向 KL(標準):生徒モデルは教師モデルの 平均 に合わせることを学習します
- 逆方向 KL(MiniLLM):生徒モデルは教師モデルのすべてのモードを 網羅する ことを学習します
- 多様なテキスト生成に適しています
応答蒸留
# Generate synthetic data from teacher, train student to imitate
# 1. Generate synthetic responses from teacher
prompts = ["Explain AI:", "What is ML?", "Define NLP:"]
teacher_responses = []
for prompt in prompts:
inputs = tokenizer(prompt, return_tensors='pt').to(teacher.device)
outputs = teacher.generate(**inputs, max_new_tokens=256, do_sample=True, temperature=0.7)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
teacher_responses.append(response)
# 2. Train student on teacher's responses (standard fine-tuning)
train_dataset = [
{"text": f"{prompt}\n{response}"}
for prompt, response in zip(prompts, teacher_responses)
]
# 3. Fine-tune student
trainer = Trainer(
model=student,
args=TrainingArguments(output_dir="./student", num_train_epochs=3, learning_rate=2e-5),
train_dataset=train_dataset,
)
trainer.train()
基本概念
1. 温度スケーリング
目的:確率分布を滑らかにし、教師モデルの不確実性を明らかにします。
# Low temperature (T=1): Sharp distribution
logits = [3.0, 2.0, 1.0]
probs_T1 = softmax(logits / 1.0) # [0.67, 0.24, 0.09]
# High temperature (T=4): Soft distribution
probs_T4 = softmax(logits / 4.0) # [0.42, 0.34, 0.24]
# Higher T reveals more information about relative rankings
ルール:蒸留には T=2-5 を使用します(一般的なデフォルト値は 2 です)。
2. 損失関数の構成要素
# Total loss = alpha * soft_loss + (1 - alpha) * hard_loss
# Soft loss: Learn from teacher's knowledge
soft_loss = KL(student || teacher)
# Hard loss: Learn from ground truth labels
hard_loss = CrossEntropy(student_output, true_labels)
# Typical values:
alpha = 0.5 # Balanced
alpha = 0.7 # More emphasis on teacher
alpha = 0.3 # More emphasis on labels
3. 順方向 KLD と逆方向 KLD
# Forward KL: KL(Student || Teacher)
# - Student matches teacher's average behavior
# - Mode-seeking: Student focuses on teacher's highest probability modes
# - Good for classification
# Reverse KL: KL(Teacher || Student)
# - Student covers all of teacher's behaviors
# - Mode-covering: Student learns diverse behaviors
# - Good for generation (MiniLLM)
学習戦略
戦略 1:ロジット蒸留
# Train student to match teacher's logits directly
def logit_distillation_trainer(student, teacher, dataloader, temperature=2.0):
optimizer = torch.optim.AdamW(student.parameters(), lr=2e-5)
for epoch in range(3):
for batch in dataloader:
# Get logits
with torch.no_grad():
teacher_logits = teacher(**batch).logits
student_logits = student(**batch).logits
# MSE on logits (alternative to KLD)
loss = F.mse_loss(student_logits, teacher_logits)
# Or use KLD
# loss = F.kl_div(
# F.log_softmax(student_logits/temperature, dim=-1),
# F.softmax(teacher_logits/temperature, dim=-1),
# reduction='batchmean'
# ) * (temperature ** 2)
loss.backward()
optimizer.step()
optimizer.zero_grad()
return student
戦略 2:2 段階蒸留
# Stage 1: Distill from teacher
student = distill(teacher, student, epochs=5)
# Stage 2: Fine-tune on task-specific data
student = fine_tune(student, task_data, epochs=3)
# Results in better task performance than single-stage
戦略 3:複数教師蒸留
# Learn from multiple expert teachers
def multi_teacher_distillation(student, teachers, batch):
"""Distill from ensemble of teachers."""
teacher_logits_list = []
# Get logits from all teachers
with torch.no_grad():
for teacher in teachers:
logits = teacher(**batch).logits
teacher_logits_list.append(logits)
# Average teacher predictions
avg_teacher_logits = torch.stack(teacher_logits_list).mean(dim=0)
# Student learns from ensemble
student_logits = student(**batch).logits
loss = F.kl_div(
F.log_softmax(student_logits, dim=-1),
F.softmax(avg_teacher_logits, dim=-1),
reduction='batchmean'
)
return loss
本番環境へのデプロイ
完全な学習スクリプト
from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
def train_distilled_model(
teacher_name="meta-llama/Llama-2-70b-hf",
student_name="meta-llama/Llama-2-7b-hf",
output_dir="./distilled-llama-7b",
temperature=2.0,
alpha=0.7,
):
# Load models
teacher = AutoModelForCausalLM.from_pretrained(teacher_name, torch_dtype=torch.float16, device_map="auto")
student = AutoModelForCausalLM.from_pretrained(student_name, torch_dtype=torch.float16)
tokenizer = AutoTokenizer.from_pretrained(teacher_name)
# Custom trainer with distillation
class DistillationTrainer(Trainer):
def compute_loss(self, model, inputs, return_outputs=False):
# Student forward
outputs_student = model(**inputs)
student_logits = outputs_student.logits
# Teacher forward (no grad)
with torch.no_grad():
outputs_teacher = teacher(**inputs)
teacher_logits = outputs_teacher.logits
# Distillation loss
soft_targets = F.softmax(teacher_logits / temperature, dim=-1)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_loss = F.kl_div(soft_student, soft_targets, reduction='batchmean') * (temperature ** 2)
# Hard loss
hard_loss = outputs_student.loss
# Combined
loss = alpha * soft_loss + (1 - alpha) * hard_loss
return (loss, outputs_student) if return_outputs else loss
# Training arguments
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
learning_rate=2e-5,
warmup_steps=500,
logging_steps=100,
save_steps=1000,
bf16=True,
gradient_checkpointing=True,
)
# Train
trainer = DistillationTrainer(
model=student,
args=training_args,
train_dataset=train_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)
trainer.train()
student.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)
# Usage
train_distilled_model(
teacher_name="meta-llama/Llama-2-70b-hf",
student_name="meta-llama/Llama-2-7b-hf",
temperature=2.0,
alpha=0.7
)
ベストプラクティス
1. ハイパーパラメーターの選定
# Temperature
T = 1.0 # Sharp (less knowledge transfer)
T = 2.0 # Standard (good balance)
T = 5.0 # Soft (more knowledge transfer)
# Alpha (weight)
alpha = 0.5 # Balanced
alpha = 0.7 # Emphasize teacher knowledge
alpha = 0.9 # Strong distillation
# Rule: Higher T + higher alpha = stronger distillation
2. モデルサイズの比率
# Good ratios (teacher/student)
70B / 7B = 10× # Excellent
13B / 1B = 13× # Good
7B / 1B = 7× # Acceptable
# Avoid too large gap
70B / 1B = 70× # Too large, ineffective
3. データ品質
# Best: Use teacher-generated data + real data
train_data = {
"teacher_generated": 70%, # Diverse, high-quality
"real_data": 30% # Ground truth
}
# Avoid: Only real data (doesn't utilize teacher fully)
評価
from transformers import pipeline
# Compare student vs teacher
teacher_pipe = pipeline("text-generation", model=teacher)
student_pipe = pipeline("text-generation", model=student)
prompts = ["Explain quantum computing:", "What is AI?"]
for prompt in prompts:
teacher_out = teacher_pipe(prompt, max_new_tokens=100)
student_out = student_pipe(prompt, max_new_tokens=100)
print(f"Prompt: {prompt}")
print(f"Teacher: {teacher_out[0]['generated_text']}")
print(f"Student: {student_out[0]['generated_text']}")
print(f"Match quality: {calculate_similarity(teacher_out, student_out):.2f}")
リソース
- Hinton et al. 2015(基礎論文):https://arxiv.org/abs/1503.02531
- MiniLLM(逆 KLD):https://arxiv.org/abs/2306.08543
- LLM の KD サーベイ(2024):https://arxiv.org/abs/2402.13116
- MiniLLM GitHub:https://github.com/microsoft/LMOps/tree/main/minillm
エージェントの作業手順
- 環境を確認します。 GPU メモリ、モデルのチェックポイントとライセンス、フレームワークのバージョン、入力形式を確認します。
- 入力を特定します。 データまたはユーザーから、形式、識別子、パラメーターを確認します。結果に影響する値は推測せず、質問してください。
- まず小規模に実行します。 最初に、低解像度または短い長さで単一サンプルの推論を実行します。
- 上記の手順と参考資料に従って、タスク全体を実行します。
- 結果を検証します。 出力を定性的に確認し、タスクの指標で評価します。生成コンテンツのライセンス上の制限も確認します。
- 報告します。 実行した内容(バージョン、コマンド、パラメーター)、確認した内容、未確定の事項を伝えます。
| この問題が発生した場合 | 対応 |
|---|---|
| 大きな入力でメモリ不足になる | 解像度またはシーケンス長を下げる、半精度を使用する、または入力を分割します。 |
| インストール済みバージョンに、この手順に記載されている関数、フラグ、エンドポイントがない | インストール済みバージョンの公式ドキュメント(help()、--help、公式ドキュメント)を確認して適宜対応し、ユーザーに伝えます。API を捏造してはいけません。 |
| 必須の入力、識別子、パラメーターが曖昧 | ユーザーに質問するか、実行前に仮定を明示します。 |
誠実性に関するルール
- 結果、パラメーター、識別子、引用、統計情報を決して捏造しないでください。実行または検証できないことがあれば、率直に伝えてください。
- 商用利用または公開前に、モデルとデータセットのライセンスを確認してください。
- ここにあるバージョン固有の情報は古くなっている可能性があるため、インストール済みバージョンの公式ドキュメントで確認してください。
- 費用が発生する操作、共有 GPU やクラウドクォータを消費する操作、個人データや患者データに触れる操作、取り消せない操作を行う前に、ユーザーに確認してください。
関連スキル
speculative-decoding:投機的デコーディング、Medusa の複数ヘッド、ルックアヘッドデコーディング技術を使用して、LLM の推論を高速化します。model-merging:mergekit を使用して複数のファインチューニング済みモデルをマージし、再トレーニングせずに機能を組み合わせます。
コメント
Markdown 対応