이 글은 누구를 위한 것인가
- 대형 LLM 비용을 줄이기 위해 소형 모델로 증류하려는 팀
- 엣지 디바이스(모바일, IoT)에 AI를 배포하려는 개발자
- 도메인 특화 소형 모델을 만들려는 팀
들어가며
GPT-4 수준의 답변을 GPT-4 가격으로 모든 요청에 사용할 수는 없다. 지식 증류는 대형 모델(Teacher)의 출력으로 소형 모델(Student)을 학습시켜 성능을 최대한 유지하면서 속도/비용을 줄인다.
이 글은 bluefoxdev.kr의 지식 증류 모델 경량화 가이드 를 참고하여 작성했습니다.
1. 모델 경량화 전략
[경량화 방법 비교]
지식 증류 (Knowledge Distillation):
Teacher → Student 학습
성능 유지율: 85-95%
학습 필요: O
적합: 도메인 특화 모델
양자화 (Quantization):
FP32 → INT8/INT4 변환
크기: 50-75% 감소
학습 불필요: X
속도: 2-4배 향상
GPTQ, AWQ, GGUF (llama.cpp)
가지치기 (Pruning):
불필요한 가중치 제거
크기: 20-50% 감소
정확도 손실 있음
LoRA (Low-Rank Adaptation):
전체 파인튜닝 대신 저차원 행렬
학습 파라미터: 0.1-1%
메모리: 극적 감소
[증류 데이터 전략]
Teacher: GPT-4, Claude Opus
Student: Llama 3 8B, Mistral 7B
학습 데이터: Teacher의 소프트 레이블 + 실제 데이터
소프트 레이블: 확률 분포 (argmax 아님)
2. 지식 증류 구현
# Teacher-Student 증류 (Python/PyTorch)
import torch
import torch.nn.functional as F
from anthropic import Anthropic
client = Anthropic()
# Teacher: Claude로 소프트 레이블 생성
async def generate_soft_labels(questions: list[str]) -> list[dict]:
"""Teacher 모델이 각 질문에 대한 상세 답변 생성"""
soft_labels = []
for question in questions:
response = client.messages.create(
model="claude-opus-4-7", # Teacher: 대형 모델
max_tokens=512,
messages=[{"role": "user", "content": question}]
)
soft_labels.append({
"question": question,
"teacher_response": response.content[0].text,
"logprobs": None # 실제로는 log probabilities 추출
})
return soft_labels
# Student 파인튜닝 데이터셋 생성
def create_distillation_dataset(soft_labels: list[dict]) -> list[dict]:
"""증류 학습 데이터 포맷 변환"""
return [
{
"messages": [
{"role": "user", "content": item["question"]},
{"role": "assistant", "content": item["teacher_response"]}
]
}
for item in soft_labels
]
# 지식 증류 손실 함수
def distillation_loss(
student_logits: torch.Tensor,
teacher_logits: torch.Tensor,
labels: torch.Tensor,
temperature: float = 4.0,
alpha: float = 0.7
) -> torch.Tensor:
"""
KL 발산: Student가 Teacher 확률 분포를 모방
CrossEntropy: 실제 레이블로 정확도 유지
"""
# 소프트 레이블 손실 (온도 스케일링)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
kl_loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (temperature ** 2)
# 하드 레이블 손실 (실제 정답)
ce_loss = F.cross_entropy(student_logits, labels)
# 결합: alpha * KL + (1-alpha) * CE
return alpha * kl_loss + (1 - alpha) * ce_loss
// GGUF 양자화 모델 서빙 (llama.cpp + TypeScript)
// llama-node 또는 @llama-node/llama-cpp 패키지 사용
async function runQuantizedModel(prompt: string): Promise<string> {
// 실제로는 llama.cpp 바인딩 또는 Ollama API 사용
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'llama3:8b-instruct-q4_K_M', // 4비트 양자화
prompt,
stream: false,
}),
});
const data = await response.json();
return data.response;
}
// 모델 크기 비교
const modelComparison = [
{ name: 'Llama 3 70B FP16', size: '140GB', speed: '10 tok/s', quality: 95 },
{ name: 'Llama 3 70B INT8', size: '70GB', speed: '20 tok/s', quality: 93 },
{ name: 'Llama 3 8B FP16', size: '16GB', speed: '80 tok/s', quality: 80 },
{ name: 'Llama 3 8B Q4_K_M', size: '4.9GB', speed: '150 tok/s', quality: 77 },
];
// 라우팅: 복잡도에 따라 모델 선택
async function smartModelRouter(query: string): Promise<string> {
const isComplex = query.length > 200 || /분석|비교|설명해|이유|왜/.test(query);
if (isComplex) {
// 복잡한 질문: 대형 모델 (Claude API)
const { default: Anthropic } = await import('@anthropic-ai/sdk');
const client = new Anthropic();
const r = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: query }],
});
return r.content[0].type === 'text' ? r.content[0].text : '';
}
// 단순한 질문: 로컬 양자화 모델
return runQuantizedModel(query);
}
마무리
지식 증류의 핵심은 Teacher의 소프트 레이블(확률 분포)을 Student에게 전달하는 것이다. 하드 레이블(정답만)보다 소프트 레이블이 더 많은 정보를 담아 Student가 더 효율적으로 학습한다. 도메인 특화 데이터로 Student를 증류하면 해당 도메인에서 Teacher에 근접한 성능을 훨씬 작은 모델로 달성할 수 있다.