LLM 평가(Evaluation) 체계 가이드 - 벤치마크부터 자체 평가 구축까지
LLM 평가의 핵심 개념, 주요 벤치마크(MMLU, HumanEval, MT-Bench), 자동 평가(LLM-as-Judge), RAG 평가(RAGAS), 자체 평가 체계 구축, A/B 테스트 전략을 체계적으로 정리합니다.
선생님이 없는 시험을 생각해보세요. 학생이 얼마나 잘하는지 알려면 시험지를 채점해야 하는데, 채점 기준도 만들어야 하고 심지어 채점관이 또 다른 AI일 수도 있습니다. LLM 평가가 딱 이렇습니다.
어떤 모델이 더 나은지, Fine-Tuning이 효과가 있는지, 프로덕션에서 품질이 떨어지고 있는지 — 이 모든 것을 숫자로 측정하는 것이 평가입니다. 이 글에서는 벤치마크의 의미부터 자체 평가 체계 구축까지 차근차근 살펴봅니다.
이 글에서 배우는 것
- MMLU·HumanEval·MT-Bench 등 주요 벤치마크의 의미와 한계
- LLM-as-Judge: 강력한 LLM을 평가자로 써서 자동 채점하는 방법
- RAG 품질을 측정하는 RAGAS 프레임워크와 각 지표 해석
- 여러분 도메인에 맞는 자체 평가 파이프라인 구축 방법
- A/B 테스트로 프로덕션에서 모델 성능을 지속적으로 모니터링하는 전략
1. LLM 평가가 중요한 이유
평가가 필요한 상황
언제 평가가 필요할까요? 사실 LLM을 쓰는 모든 순간에 필요합니다. 모델을 고를 때도, Fine-Tuning 후에도, 프로덕션 배포 후에도요.
| 상황 | 평가 목적 | 평가 방법 |
|---|---|---|
| 모델 선택 | GPT-4 vs Claude vs LLaMA 비교 | 벤치마크, 도메인 태스크 평가 |
| Fine-Tuning | 학습 전후 성능 비교 | 태스크별 정확도, 형식 준수율 |
| 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 엔지니어링 팀