LLM 평가(Evaluation) 체계 가이드 - 벤치마크부터 자체 평가 구축까지
LLM 평가의 핵심 개념, 주요 벤치마크(MMLU, HumanEval, MT-Bench), 자동 평가(LLM-as-Judge), RAG 평가(RAGAS), 자체 평가 체계 구축, A/B 테스트 전략을 체계적으로 정리합니다.
선생님이 없는 시험을 생각해보세요. 학생이 얼마나 잘하는지 알려면 시험지를 채점해야 하는데, 채점 기준도 만들어야 하고 심지어 채점관이 또 다른 AI일 수도 있습니다. LLM 평가가 딱 이렇습니다.
어떤 모델이 더 나은지, 파인튜닝이 효과가 있는지, 프로덕션에서 품질이 떨어지고 있는지 — 이 모든 것을 숫자로 측정하는 것이 평가입니다. 이 글에서는 벤치마크의 의미부터 자체 평가 체계 구축까지 차근차근 살펴봅니다.
이 글에서 배우는 것
- MMLU·HumanEval·MT-Bench 등 주요 벤치마크의 의미와 한계
- LLM-as-Judge: 강력한 LLM을 평가자로 써서 자동 채점하는 방법
- RAG 품질을 측정하는 RAGAS 프레임워크와 각 지표 해석
- 여러분 도메인에 맞는 자체 평가 파이프라인 구축 방법
- A/B 테스트로 프로덕션에서 모델 성능을 지속적으로 모니터링하는 전략
1. LLM 평가가 중요한 이유
평가가 필요한 상황
언제 평가가 필요할까요? 사실 LLM을 쓰는 모든 순간에 필요합니다. 모델을 고를 때도, 파인튜닝 후에도, 프로덕션 배포 후에도요.
| 상황 | 평가 목적 | 평가 방법 |
|---|---|---|
| 모델 선택 | GPT-4 vs Claude vs LLaMA 비교 | 벤치마크, 도메인 태스크 평가 |
| 파인튜닝 | 학습 전후 성능 비교 | 태스크별 정확도, 형식 준수율 |
| RAG 파이프라인 | 검색+생성 품질 측정 | RAGAS, Recall@K |
| 프롬프트 최적화 | 프롬프트 변형 간 비교 | A/B 테스트, LLM-as-Judge |
| 프로덕션 모니터링 | 서비스 품질 지속 관리 | 사용자 피드백, 자동 평가 |
평가의 어려움
그런데 LLM 평가는 유독 까다롭습니다. 왜 그럴까요?
- 비결정론적 특성: 같은 입력에 다른 출력 (Temperature > 0)
- 주관성: "좋은 답변"의 기준이 모호
- 다차원: 정확도, 유창성, 유용성, 안전성 등 다양한 축
- 도메인 의존: 범용 벤치마크와 실제 성능 괴리
이런 어려움 때문에 "단 하나의 점수"로 LLM을 평가하기는 어렵고, 목적에 맞는 여러 지표를 조합해야 합니다.
2. 주요 벤치마크
범용 벤치마크
"이 모델 벤치마크 점수가 높던데요" — 그 벤치마크가 무엇을 재는지 알아야 의미 있는 비교가 됩니다. 주요 벤치마크와 각자 측정하는 것을 정리했습니다.
| 벤치마크 | 평가 대상 | 형식 | 문제 수 | 특징 |
|---|---|---|---|---|
| MMLU | 범용 지식 | 4지선다 | 15,908 | 57개 주제, 가장 널리 사용 |
| MMLU-Pro | 범용 지식 (고난도) | 10지선다 | 12,032 | MMLU 업그레이드, CoT 필요 |
| HellaSwag | 상식 추론 | 4지선다 | 10,042 | 상황 후속 문장 선택 |
| ARC-Challenge | 과학 추론 | 4지선다 | 1,172 | 초등~중등 과학 문제 |
| Winogrande | 상식 | 2지선다 | 1,267 | 대명사 지시 대상 판별 |
| TruthfulQA | 사실성 | 생성형 | 817 | 할루시네이션 평가 |
| GSM8K | 수학 | 생성형 | 1,319 | 초등 수학 문제 |
| MATH | 수학 (고난도) | 생성형 | 5,000 | 경시대회 수준 수학 |
코드 벤치마크
| 벤치마크 | 평가 대상 | 언어 | 문제 수 |
|---|---|---|---|
| HumanEval | 함수 생성 | Python | 164 |
| HumanEval+ | 강화된 테스트 | Python | 164 |
| MBPP | 기본 프로그래밍 | Python | 500 |
| SWE-bench | 실제 이슈 해결 | 다중 | 2,294 |
| LiveCodeBench | 실시간 코딩 | 다중 | 지속 갱신 |
대화/지시 벤치마크
| 벤치마크 | 평가 대상 | 평가 방식 |
|---|---|---|
| MT-Bench | 멀티턴 대화 | GPT-4가 1~10점 채점 |
| AlpacaEval | 지시 따르기 | GPT-4 승률 비교 |
| Chatbot Arena | 실사용자 선호도 | Elo 레이팅 (블라인드) |
| IFEval | 지시 따르기 정확도 | 형식 준수 정확도 |
MTEB (Massive Text Embedding Benchmark)
임베딩 모델의 종합 평가:
| 태스크 | 설명 | 평가 지표 |
|---|---|---|
| Retrieval | 검색 관련도 | nDCG@10 |
| Classification | 텍스트 분류 | Accuracy |
| Clustering | 텍스트 클러스터링 | V-measure |
| STS (Semantic Similarity) | 의미 유사도 | Spearman 상관 |
| Reranking | 리랭킹 | MAP |
3. 자동 평가: LLM-as-Judge
개념
사람이 모든 응답을 하나하나 채점하면 비용과 시간이 너무 많이 듭니다. GPT-4나 Claude처럼 강력한 LLM을 평가자(Judge)로 활용해 다른 모델의 응답 품질을 자동으로 채점하는 방식이 LLM-as-Judge입니다.
한 문장으로: LLM-as-Judge는 채점관도 AI인 방식으로, 대규모 자동 평가를 가능하게 하되 평가자 LLM의 편향을 주의해야 합니다.
import anthropic
client = anthropic.Anthropic()
def llm_judge(question: str, response: str, criteria: list) -> dict:
"""LLM을 평가자로 활용"""
eval_prompt = f"""다음 응답을 평가하세요.
## 질문
{question}
## 응답
{response}
## 평가 기준 (각 1~5점)
{chr(10).join(f"- {c}" for c in criteria)}
## 채점 규칙
- 1점: 매우 부족
- 2점: 부족
- 3점: 보통
- 4점: 우수
- 5점: 매우 우수
JSON 형식으로 반환:
{{"scores": {{"기준명": 점수}}, "average": 평균, "reasoning": "평가 근거", "improvements": "개선 사항"}}
"""
result = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
temperature=0.0,
messages=[{"role": "user", "content": eval_prompt}]
)
return json.loads(result.content[0].text)
# 사용
scores = llm_judge(
question="Kafka에서 컨슈머 랙이 증가하는 원인은?",
response="...(평가 대상 응답)...",
criteria=["정확성", "완전성", "실용성", "코드 예시 품질"]
)Pairwise 비교
점수를 매기는 것보다 "A와 B 중 어느 게 낫나?"를 묻는 게 더 정확할 때가 많습니다. Chatbot Arena 방식처럼 두 응답을 나란히 보여주는 Pairwise 비교입니다.
def pairwise_compare(question: str, response_a: str, response_b: str) -> str:
"""두 응답 중 더 나은 것을 선택"""
prompt = f"""두 응답을 비교하여 더 나은 것을 선택하세요.
질문: {question}
응답 A: {response_a}
응답 B: {response_b}
더 나은 응답을 선택하고 이유를 설명하세요.
반환 형식: {{"winner": "A" 또는 "B" 또는 "tie", "reason": "이유"}}
"""
result = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
temperature=0.0,
messages=[{"role": "user", "content": prompt}]
)
return json.loads(result.content[0].text)LLM-as-Judge의 한계와 보완
| 한계 | 설명 | 보완 방법 |
|---|---|---|
| 위치 편향 | 첫 번째 응답을 선호하는 경향 | 순서를 바꿔 2회 평가 |
| 장문 편향 | 긴 응답을 높게 평가하는 경향 | "간결함" 기준 추가 |
| 자기 편향 | 같은 모델의 응답을 선호 | 다른 모델로 평가 |
| 지시 편향 | 평가 기준 순서에 민감 | 기준 순서 랜덤화 |
4. RAG 평가: RAGAS
RAGAS 프레임워크
RAG를 만들었는데 "잘 동작하는지" 어떻게 알 수 있을까요? RAGAS는 검색 품질과 생성 품질을 각각 측정하는 지표 모음입니다. 네 가지 숫자를 보면 RAG의 어느 부분이 약한지 바로 알 수 있습니다.
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
context_utilization
)
from datasets import Dataset
# 평가 데이터 구성
eval_data = {
"question": [
"Spark에서 셔플 파티션을 조정하는 방법은?",
"Kafka 컨슈머 그룹의 리밸런싱이란?"
],
"answer": [
"spark.sql.shuffle.partitions 설정을 변경합니다...",
"컨슈머 그룹 내에서 파티션 할당을 재조정하는 과정입니다..."
],
"contexts": [
["Spark 셔플 파티션은 spark.sql.shuffle.partitions로 설정..."],
["Kafka 리밸런싱은 컨슈머 그룹의 멤버 변경 시 발생..."]
],
"ground_truth": [
"spark.sql.shuffle.partitions 설정으로 셔플 파티션 수를 조정합니다.",
"리밸런싱은 컨슈머 그룹 내 파티션 재할당 과정입니다."
]
}
dataset = Dataset.from_dict(eval_data)
results = evaluate(
dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
# 'context_precision': 0.85, 'context_recall': 0.90}RAGAS 지표 해석
| 지표 | 의미 | 낮으면 | 개선 방향 |
|---|---|---|---|
| Faithfulness | 응답이 컨텍스트에 근거하는가 | 할루시네이션 | 프롬프트에 "컨텍스트만 사용" 강조 |
| Answer Relevancy | 응답이 질문에 적절한가 | 엉뚱한 답변 | 프롬프트 개선, 검색 품질 향상 |
| Context Precision | 검색 결과가 관련성 있는가 | 노이즈 문서 | 리랭킹 추가, 메타데이터 필터 |
| Context Recall | 필요한 정보를 다 찾았는가 | 정보 누락 | K값 증가, 하이브리드 검색 |
5. 자체 평가 체계 구축
평가 데이터셋 설계
범용 벤치마크는 여러분 도메인과 맞지 않을 수 있습니다. 실제로 중요한 것은 "우리 서비스의 실제 질문에 얼마나 잘 답하는가"이기 때문에 자체 평가 데이터셋이 필요합니다.
# 평가 데이터셋 구조
eval_dataset = [
{
"id": "eval-001",
"category": "troubleshooting",
"difficulty": "medium",
"question": "Spark executor OOM 에러가 발생합니다. 원인과 해결 방법은?",
"reference_answer": "executor.memory를 늘리거나, 파티션 수를 증가시키거나...",
"required_elements": ["executor.memory", "파티션", "데이터 스큐"],
"evaluation_criteria": {
"accuracy": "기술적으로 정확한 원인 분석",
"completeness": "주요 원인 3가지 이상 포함",
"actionability": "구체적 설정값 또는 코드 포함",
"format": "구조화된 형식 (번호 목록 등)"
}
}
]자동 평가 파이프라인
class EvaluationPipeline:
def __init__(self, judge_model: str = "claude-sonnet-4-6"):
self.judge = anthropic.Anthropic()
self.judge_model = judge_model
self.results = []
def evaluate_batch(self, model_fn, eval_dataset: list) -> dict:
"""배치 평가 실행"""
for item in eval_dataset:
# 1. 모델 응답 생성
response = model_fn(item["question"])
# 2. 자동 지표 평가
auto_scores = self.auto_evaluate(item, response)
# 3. LLM-as-Judge 평가
judge_scores = self.judge_evaluate(item, response)
self.results.append({
"id": item["id"],
"category": item["category"],
"auto_scores": auto_scores,
"judge_scores": judge_scores,
"response": response
})
return self.aggregate_results()
def auto_evaluate(self, item: dict, response: str) -> dict:
"""규칙 기반 자동 평가"""
scores = {}
# 필수 요소 포함 여부
required = item.get("required_elements", [])
found = sum(1 for e in required if e.lower() in response.lower())
scores["element_coverage"] = found / max(len(required), 1)
# 응답 길이 적정성
word_count = len(response.split())
scores["length_appropriate"] = 1.0 if 50 < word_count < 500 else 0.5
return scores
def aggregate_results(self) -> dict:
"""결과 집계"""
categories = {}
for r in self.results:
cat = r["category"]
if cat not in categories:
categories[cat] = []
categories[cat].append(r["judge_scores"]["average"])
return {
"overall_average": sum(r["judge_scores"]["average"] for r in self.results) / len(self.results),
"by_category": {k: sum(v)/len(v) for k, v in categories.items()},
"total_evaluated": len(self.results)
}6. A/B 테스트와 프로덕션 평가
온라인 A/B 테스트
오프라인 평가로 "좋다"고 나온 모델이 실제 사용자에게도 좋으리라는 보장은 없습니다. 프로덕션에서 절반의 트래픽으로 새 모델을 조용히 테스트하는 A/B 실험이 최종 검증입니다.
import random
from datetime import datetime
class ABTestManager:
def __init__(self):
self.experiments = {}
def create_experiment(self, name: str, variants: dict, traffic_split: dict):
"""A/B 테스트 실험 생성"""
self.experiments[name] = {
"variants": variants, # {"A": model_a, "B": model_b}
"traffic_split": traffic_split, # {"A": 0.5, "B": 0.5}
"results": {"A": [], "B": []},
"created_at": datetime.now()
}
def get_variant(self, experiment_name: str, user_id: str) -> str:
"""사용자별 일관된 변형 할당"""
# 사용자 ID 기반 결정적 할당 (같은 사용자는 항상 같은 변형)
hash_val = hash(f"{experiment_name}:{user_id}") % 100
exp = self.experiments[experiment_name]
cumulative = 0
for variant, ratio in exp["traffic_split"].items():
cumulative += ratio * 100
if hash_val < cumulative:
return variant
return list(exp["variants"].keys())[-1]
def record_feedback(self, experiment_name: str, variant: str, score: float):
"""사용자 피드백 기록"""
self.experiments[experiment_name]["results"][variant].append(score)
def get_statistics(self, experiment_name: str) -> dict:
"""통계적 유의성 분석"""
exp = self.experiments[experiment_name]
stats = {}
for variant, scores in exp["results"].items():
if scores:
stats[variant] = {
"mean": sum(scores) / len(scores),
"count": len(scores),
"positive_rate": sum(1 for s in scores if s > 0) / len(scores)
}
return stats프로덕션 모니터링 지표
| 지표 | 설명 | 수집 방법 | 목표 |
|---|---|---|---|
| 사용자 만족도 | 좋아요/싫어요 비율 | 피드백 버튼 | > 80% 긍정 |
| 태스크 완료율 | 사용자가 원하는 결과를 얻었는지 | 후속 질문 분석 | > 70% |
| 응답 지연시간 | TTFT, 전체 응답 시간 | 서버 메트릭 | P95 < 3초 |
| 할루시네이션율 | 사실과 다른 응답 비율 | 자동 검증 + 사람 평가 | < 5% |
| 거부율 | 안전성 가드레일에 의한 차단 | 로그 분석 | 적정 수준 유지 |
| 비용 | 토큰당 비용, 일일 비용 | API 과금 | 예산 내 |
참고: LLM 평가는 "한 번에 완벽하게"가 아닌 "지속적으로 개선"하는 프로세스입니다. 프로덕션 데이터에서 실패 사례를 수집하고 평가 데이터셋에 추가하는 피드백 루프를 구축하세요.
마치며 — 핵심 요약
- LLM 평가는 비결정론적 특성·주관성·다차원성 때문에 어렵지만, 그래서 더 체계적인 접근이 필요합니다.
- 벤치마크(MMLU, HumanEval, SWE-bench)는 모델 간 상대 비교에 유용하지만, 여러분 도메인의 실제 성능과 다를 수 있습니다. 항상 직접 테스트를 병행하세요.
- LLM-as-Judge로 대규모 자동 평가가 가능합니다. 단, 위치 편향·자기 편향을 방지하기 위해 순서를 바꿔 2회 평가하고, 다른 모델을 평가자로 쓰세요.
- RAGAS 네 지표(Faithfulness·Answer Relevancy·Context Precision·Context Recall)를 보면 RAG의 어느 부분이 약한지 정확히 진단할 수 있습니다.
- 자체 평가 데이터셋은 처음부터 50~100건이라도 만들어두세요. 프로덕션 실패 케이스를 수집해 계속 추가하는 피드백 루프가 핵심입니다.
- 평가는 "한 번에 완벽하게"가 아니라 "계속 개선하는 과정"입니다. 오늘 당장 RAGAS로 여러분의 RAG를 측정해보세요 — 숫자가 개선 방향을 알려줄 겁니다.
References
- Hendrycks, D. et al. (2021). "Measuring Massive Multitask Language Understanding (MMLU)." ICLR
- Chen, M. et al. (2021). "Evaluating Large Language Models Trained on Code (HumanEval)." arXiv
- Zheng, L. et al. (2023). "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena." NeurIPS
- Es, S. et al. (2024). "RAGAS: Automated Evaluation of Retrieval Augmented Generation." EACL
- Muennighoff, N. et al. (2023). "MTEB: Massive Text Embedding Benchmark." EACL
- LMSYS Chatbot Arena — https://chat.lmsys.org/
— Data Dynamics 엔지니어링 팀