Blog
llm-evaluationbenchmarkragasmmluaitesting

LLM 평가(Evaluation) 체계 가이드 - 벤치마크부터 자체 평가 구축까지

LLM 평가의 핵심 개념, 주요 벤치마크(MMLU, HumanEval, MT-Bench), 자동 평가(LLM-as-Judge), RAG 평가(RAGAS), 자체 평가 체계 구축, A/B 테스트 전략을 체계적으로 정리합니다.

Data Dynamics2026年4月16日13 min read
This post is not yet translated. The original Korean version is shown below.

LLM의 성능을 객관적으로 측정하고 비교하는 것은 모델 선택, Fine-Tuning 효과 검증, 프로덕션 품질 관리의 핵심입니다. 이 글에서는 LLM 평가의 기본 개념부터 자체 평가 체계 구축까지 체계적으로 다룹니다.


1. LLM 평가가 중요한 이유

평가가 필요한 상황

상황평가 목적평가 방법
모델 선택GPT-4 vs Claude vs LLaMA 비교벤치마크, 도메인 태스크 평가
Fine-Tuning학습 전후 성능 비교태스크별 정확도, 형식 준수율
RAG 파이프라인검색+생성 품질 측정RAGAS, Recall@K
프롬프트 최적화프롬프트 변형 간 비교A/B 테스트, LLM-as-Judge
프로덕션 모니터링서비스 품질 지속 관리사용자 피드백, 자동 평가

평가의 어려움

  • 비결정성: 같은 입력에 다른 출력 (Temperature > 0)
  • 주관성: "좋은 답변"의 기준이 모호
  • 다차원: 정확도, 유창성, 유용성, 안전성 등 다양한 축
  • 도메인 의존: 범용 벤치마크와 실제 성능 괴리

2. 주요 벤치마크

범용 벤치마크

벤치마크평가 대상형식문제 수특징
MMLU범용 지식4지선다15,90857개 주제, 가장 널리 사용
MMLU-Pro범용 지식 (고난도)10지선다12,032MMLU 업그레이드, CoT 필요
HellaSwag상식 추론4지선다10,042상황 후속 문장 선택
ARC-Challenge과학 추론4지선다1,172초등~중등 과학 문제
Winogrande상식2지선다1,267대명사 지시 대상 판별
TruthfulQA사실성생성형817할루시네이션 평가
GSM8K수학생성형1,319초등 수학 문제
MATH수학 (고난도)생성형5,000경시대회 수준 수학

코드 벤치마크

벤치마크평가 대상언어문제 수
HumanEval함수 생성Python164
HumanEval+강화된 테스트Python164
MBPP기본 프로그래밍Python500
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

개념

강력한 LLM(GPT-4, Claude)을 평가자(Judge)로 활용하여 다른 모델의 응답 품질을 자동으로 채점합니다.

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 비교

두 응답을 비교하여 어느 것이 더 나은지 판별합니다.

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 시스템의 검색 품질과 생성 품질을 자동으로 평가합니다.

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 테스트

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 평가는 "한 번에 완벽하게"가 아닌 "지속적으로 개선"하는 프로세스입니다. 프로덕션 데이터에서 실패 사례를 수집하고 평가 데이터셋에 추가하는 피드백 루프를 구축하세요.


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 엔지니어링 팀