🤖 AI/ML

증류

Knowledge Distillation

큰 모델의 지식을 작은 모델로 전이하는 기법. 모델 경량화에 활용.

📖 상세 설명

증류(Knowledge Distillation)는 대규모 모델(Teacher)의 지식을 작고 효율적인 모델(Student)로 전달하는 모델 압축 기법입니다. Teacher 모델의 출력 확률 분포(soft labels)를 Student 모델이 학습함으로써, 단순히 정답만 학습하는 것보다 더 풍부한 정보를 전달받습니다.

증류 개념은 2015년 Geoffrey Hinton의 논문 "Distilling the Knowledge in a Neural Network"에서 처음 제안되었습니다. 이후 BERT를 6배 작게 압축한 DistilBERT(2019), GPT를 경량화한 다양한 시도들이 이어졌습니다. 2024-2025년에는 DeepSeek-R1, Llama 3.2, Phi-4 등이 대형 모델에서 증류한 소형 모델로 주목받으며, 증류가 LLM 시대의 핵심 기술로 자리잡았습니다.

증류의 핵심 원리는 "soft labels"에 있습니다. 예를 들어 고양이 이미지를 분류할 때, hard label은 "고양이=1, 개=0"이지만, Teacher의 soft label은 "고양이=0.9, 개=0.08, 호랑이=0.02"처럼 클래스 간 유사성 정보를 포함합니다. Temperature 파라미터로 확률 분포를 부드럽게 만들어 이 정보를 더 효과적으로 전달합니다. 최근에는 중간 레이어의 표현(intermediate representations)까지 증류하는 기법이 발전했습니다.

실무에서 증류는 모델 배포 비용 절감의 핵심입니다. 70B 파라미터 모델을 7B로 증류하면 GPU 비용을 90% 이상 절감하면서 원본 성능의 85-95%를 유지할 수 있습니다. 특히 엣지 디바이스, 모바일 앱, 실시간 서비스에서 지연시간과 비용을 줄이는 데 필수적입니다. 2025년 현재 대부분의 상용 LLM 서비스는 증류된 모델을 함께 제공합니다.

💻 코드 예제

PyTorch를 활용한 기본 지식 증류 구현:

# 지식 증류 (Knowledge Distillation) 기본 구현
import torch
import torch.nn as nn
import torch.nn.functional as F

class DistillationLoss(nn.Module):
    """
    Knowledge Distillation Loss
    - alpha: soft/hard loss 비율
    - temperature: 확률 분포 smoothing
    """
    def __init__(self, alpha=0.5, temperature=4.0):
        super().__init__()
        self.alpha = alpha
        self.temperature = temperature
        self.ce_loss = nn.CrossEntropyLoss()
        self.kl_loss = nn.KLDivLoss(reduction='batchmean')

    def forward(self, student_logits, teacher_logits, labels):
        # Hard Loss: Student vs 실제 정답
        hard_loss = self.ce_loss(student_logits, labels)

        # Soft Loss: Student vs Teacher (temperature scaling)
        T = self.temperature
        soft_student = F.log_softmax(student_logits / T, dim=1)
        soft_teacher = F.softmax(teacher_logits / T, dim=1)
        soft_loss = self.kl_loss(soft_student, soft_teacher) * (T * T)

        # 총 손실: alpha로 가중 평균
        total_loss = self.alpha * soft_loss + (1 - self.alpha) * hard_loss
        return total_loss

# 사용 예시
def train_with_distillation(student, teacher, dataloader, optimizer):
    teacher.eval()  # Teacher는 학습하지 않음
    student.train()
    criterion = DistillationLoss(alpha=0.7, temperature=4.0)

    for inputs, labels in dataloader:
        optimizer.zero_grad()

        # Teacher 추론 (gradient 계산 안 함)
        with torch.no_grad():
            teacher_logits = teacher(inputs)

        # Student 학습
        student_logits = student(inputs)
        loss = criterion(student_logits, teacher_logits, labels)

        loss.backward()
        optimizer.step()

    print(f"Distillation 학습 완료!")

Hugging Face를 활용한 LLM 증류 예제:

# Hugging Face 기반 LLM 증류 (DistilBERT 스타일)
from transformers import (
    AutoModelForSequenceClassification,
    AutoTokenizer,
    Trainer,
    TrainingArguments
)
from datasets import load_dataset

# Teacher: 대형 모델 (예: BERT-large)
teacher_name = "klue/roberta-large"
teacher = AutoModelForSequenceClassification.from_pretrained(
    teacher_name, num_labels=2
)

# Student: 소형 모델 (예: DistilBERT)
student_name = "monologg/distilkobert"
student = AutoModelForSequenceClassification.from_pretrained(
    student_name, num_labels=2
)
tokenizer = AutoTokenizer.from_pretrained(student_name)

# 커스텀 Trainer for Distillation
class DistillationTrainer(Trainer):
    def __init__(self, teacher_model, alpha=0.5, temperature=4.0, **kwargs):
        super().__init__(**kwargs)
        self.teacher = teacher_model
        self.teacher.eval()
        self.alpha = alpha
        self.temperature = temperature

    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.pop("labels")

        # Student forward
        student_outputs = model(**inputs)
        student_logits = student_outputs.logits

        # Teacher forward
        with torch.no_grad():
            teacher_outputs = self.teacher(**inputs)
            teacher_logits = teacher_outputs.logits

        # Distillation loss 계산
        loss = distillation_loss(
            student_logits, teacher_logits, labels,
            self.alpha, self.temperature
        )

        return (loss, student_outputs) if return_outputs else loss

print("Student 모델 파라미터:", sum(p.numel() for p in student.parameters()))
print("Teacher 모델 파라미터:", sum(p.numel() for p in teacher.parameters()))
# 출력: Student가 Teacher 대비 약 40-60% 크기

📊 성능 & 비용

주요 증류 모델 성능 비교 (2025년 기준):

모델 파라미터 원본 대비 크기 성능 유지율 추론 속도
BERT-base 110M 100% (원본) 100% 1x
DistilBERT 66M 60% 97% 1.6x
Llama 3.1 70B 70B 100% (원본) 100% 1x
Llama 3.2 8B (증류) 8B 11% ~90% 8x+
DeepSeek-R1-Distill-Qwen-7B 7B 1% (671B 대비) ~85% 100x+

* 성능 유지율은 벤치마크 평균 기준, 추론 속도는 동일 하드웨어 기준 상대값

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"GPT-4 수준의 응답이 필요한데 비용이 문제라면, 먼저 GPT-4로 합성 데이터를 생성하고 이를 기반으로 Llama 8B를 파인튜닝하는 방식의 증류를 고려해보세요. 품질의 80-90%를 유지하면서 비용을 1/50로 줄일 수 있습니다."
💬 면접에서
"증류에서 temperature는 핵심 하이퍼파라미터입니다. T=1이면 원래 확률 분포, T가 높아지면 분포가 평탄해져서 클래스 간 관계 정보가 더 많이 전달됩니다. 보통 T=3~6 사이에서 최적 성능이 나옵니다."
💬 기술 토론에서
"DeepSeek-R1이 671B를 7B로 증류해서 o1 수준의 추론 능력을 보여준 건 큰 돌파구예요. 핵심은 CoT(Chain of Thought) 출력까지 함께 증류한 거죠. 단순히 최종 답만 증류하는 것보다 추론 과정을 함께 학습시키는 게 중요합니다."

⚠️ 흔한 실수 & 주의사항

Student 모델이 너무 작음

Teacher 대비 Student가 너무 작으면 지식을 담을 용량이 부족합니다. 일반적으로 Teacher의 20-40% 크기가 적당하며, 10% 이하로 줄이면 성능 저하가 급격해집니다.

Temperature 설정 무시

T=1로 두면 hard label 학습과 비슷해져서 증류 효과가 감소합니다. 대부분의 경우 T=3~6이 최적이며, 작업에 따라 튜닝이 필요합니다.

올바른 접근법

중간 레이어 증류(Intermediate Layer Distillation)를 함께 사용하세요. 최종 출력만 증류하는 것보다 hidden states까지 증류하면 성능이 크게 향상됩니다. TinyBERT, MiniLM이 이 방식으로 성공했습니다.

🔗 관련 용어

📚 더 배우기