코드 생성 대규모 언어모델 평가 (Evaluating Large Language Models Trained on Code)
Digest: 2021년 기준 LLM의 코드 생성 능력을 체계적으로 평가할 벤치마크가 부재했다. OpenAI는 Codex(GitHub 코드로 파인튜닝한 GPT 모델)를 개발하면서, 함수 docstring으로부터 올바른 Python 코드를 생성하는 능력을 측정하는 HumanEval 벤치마크(164개 수작업 프로그래밍 문제)를 함께 공개했다. 핵심 기여는 pass@k 메트릭(k개 샘플 중 하나라도 통과하면 성공)의 통계적으로 올바른 추정 방법을 제안한 것으로, 이는 이후 모든 코드 생성 벤치마크의 표준이 되었다. Codex는 HumanEval에서 28.8% (pass@1), 반복 샘플링으로 70.2% (pass@100)를 달성하여 (Table 1), GPT-3의 0%와 극적인 차이를 보였다.
메타데이터
| 항목 | 내용 |
|---|---|
| 제목 | Evaluating Large Language Models Trained on Code |
| 저자 | Mark Chen, Jerry Tworek, Heewoo Jun, Qiming Yuan, … Wojciech Zaremba |
| 소속 | OpenAI |
| 연도 | 2021 |
| 발표 | arXiv:2107.03374 |
| 링크 | arXiv, GitHub |
| 키워드 | HumanEval, Codex, pass@k, code generation, functional correctness |
데이터셋 구성
규모 및 분할
| 항목 | 내용 |
|---|---|
| 전체 크기 | 164개 프로그래밍 문제 |
| Train/Test | Test only (학습용 데이터 없음, 평가 전용) |
| 언어 | Python |
| 작성 방식 | OpenAI 연구원이 수작업으로 작성 |
| 평균 테스트 케이스 | 문제당 약 7.7개 unit test |
Feature/Column 구조
| 필드 | 설명 | 예시 |
|---|---|---|
task_id | 고유 문제 식별자 | HumanEval/0 |
prompt | 함수 시그니처 + docstring | def has_close_elements(numbers, threshold): … |
canonical_solution | 정답 코드 | 참조 구현 |
test | unit test 함수 | assert has_close_elements([1.0, 2.0], 0.5) == False |
entry_point | 함수 이름 | has_close_elements |
난이도 체계
HumanEval은 공식적인 난이도 분류를 제공하지 않지만, 문제 유형별로 다음과 같이 분포한다:
| 유형 | 비율 (약) | 설명 |
|---|---|---|
| 문자열 처리 | 25% | 문자열 조작, 파싱 |
| 수학/수치 | 20% | 수학 연산, 소수 판별 등 |
| 리스트/배열 | 30% | 정렬, 필터링, 변환 |
| 논리/조건 | 15% | 조건 분기, 불리언 연산 |
| 기타 (재귀 등) | 10% | 재귀, 트리 등 |
실제 데이터 예시
예시 1: 간단한 리스트 처리
# Prompt
def has_close_elements(numbers: List[float], threshold: float) -> bool:
"""Check if in given list of numbers, are any two numbers
closer to each other than given threshold.
>>> has_close_elements([1.0, 2.0, 3.0], 0.5)
False
>>> has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3)
True
"""
# Canonical Solution
for idx, elem in enumerate(numbers):
for idx2, elem2 in enumerate(numbers):
if idx != idx2:
distance = abs(elem - elem2)
if distance < threshold:
return True
return False예시 2: 문자열 처리
# Prompt
def remove_vowels(text: str) -> str:
"""remove_vowels is a function that takes string and returns
string without vowels.
>>> remove_vowels('')
''
>>> remove_vowels("abcdef\nghijklm")
'bcdf\nghjklm'
>>> remove_vowels('aeiou')
''
"""
# Canonical Solution
return "".join([s for s in text if s.lower() not in "aeiou"])예시 3: 수학적 문제
# Prompt
def is_prime(n: int) -> bool:
"""Return true if a given number is prime, and false otherwise.
>>> is_prime(6)
False
>>> is_prime(101)
True
>>> is_prime(2)
True
"""
# Canonical Solution
if n < 2:
return False
for k in range(2, n - 1):
if n % k == 0:
return False
return True왜 이 연구를 하는가?
핵심 질문
LLM이 docstring만으로 기능적으로 올바른 코드를 생성할 수 있는가? 그리고 이를 어떻게 신뢰성 있게 측정할 수 있는가?
기존 접근법의 한계
| 한계 | 설명 |
|---|---|
| BLEU 기반 평가의 부적절성 | 코드의 정확성은 텍스트 유사도로 측정할 수 없음. 의미적으로 동일한 코드가 구조적으로 다를 수 있음 |
| Match 기반 메트릭 한계 | 정확히 일치하는 코드만 정답으로 인정하면, 다양한 올바른 구현을 놓침 |
| 기존 벤치마크의 contamination | 학습 데이터에 포함된 문제로 평가하면 실제 능력 측정 불가 |
핵심 통찰
코드 생성 능력은 **기능적 정확성(functional correctness)**으로 평가해야 하며, 이는 unit test 통과 여부로 결정된다. 또한 단일 샘플이 아닌 다중 샘플링 전략이 실용적으로 매우 효과적이다.
방법 (Method)
프레임워크 개요
graph TB A["Docstring<br/>(함수 시그니처 + 설명)"] --> B["LLM 코드 생성<br/>(n개 샘플 생성)"] B --> C["Unit Test 실행<br/>(각 샘플에 대해)"] C --> D{"통과 여부"} D -->|"1개 이상 통과"| E["pass@k = 성공"] D -->|"모두 실패"| F["pass@k = 실패"] G["pass@k 추정<br/>1 - C(n-c, k) / C(n, k)"] --> H["최종 점수"]
pass@k 메트릭
pass@k의 통계적 추정: n개 샘플 중 c개가 통과했을 때, k개를 뽑아 최소 1개가 통과할 확률:
이 추정법은 나이브한 방법(독립 시행을 k번 반복하여 성공 확률 추정)보다 분산이 훨씬 낮다.
Tip
실제 eval-phase라고 생각해봤을 때, LLM에게 하나의 문제에 대해 n개의 answer를 만들라 시키고, eval을 개별적으로 했을 때, 총 c개의 유효한 answer가 확인되었다고 하자. 그 뒤, k를 1 혹은 3으로 마음대로 잡고 위에 공식에 넣어서 계산하여 보고. 즉, k는 약간 단위 같은 느낌임.
실무에서도 1문제에 대한 위의 pass@k 계산 뒤, 전체 문제에 대해 평균을 내어 계산하여 보고.
핵심 구성요소
- Codex 모델: GPT 모델을 GitHub의 159GB Python 코드로 파인튜닝
- Docstring 컨디셔닝: 함수 시그니처와 docstring을 프롬프트로 제공
- 샌드박스 실행: 생성된 코드를 안전한 환경에서 unit test 실행
- Nucleus Sampling: temperature와 top-p를 조절하여 다양한 샘플 생성
발견 (Findings)
주요 결과
| 모델 | pass@1 | pass@10 | pass@100 |
|---|---|---|---|
| GPT-3 (175B) | 0.0% | — | — |
| GPT-J (6B) | 11.4% | 15.7% | 27.7% |
| Codex (12B) | 28.8% | 46.8% | 72.3% |
| Codex-S (12B, supervised) | 37.7% | 53.0% | 74.9% |
(Table 1, Table 2)
핵심 발견
- 코드 파인튜닝의 효과: GPT-3 → Codex로 파인튜닝만으로 0% → 28.8% 달성 (Table 1)
- 반복 샘플링의 위력: pass@1 28.8%인 모델도 pass@100에서 72.3%에 도달, 다양한 시도가 핵심 (Table 1)
- 모델 크기와 성능: 300M → 12B 파라미터로 스케일링 시 pass@1이 약 3배 향상 (Figure 3)
- Docstring 길이의 영향: 긴 체인의 연산을 기술하는 docstring에서 성능이 급격히 저하 (Section 4)
- Temperature 최적화: pass@1에는 낮은 temperature(0.2), pass@100에는 높은 temperature(0.8)가 최적 (Figure 5)
Temperature
Temperature 최적화: pass@1에는 낮은 temperature(0.2), pass@100에는 높은 temperature(0.8)가 최적 (Figure 5)
이론적 의의
LLM 코드 평가의 표준 정립
HumanEval은 코드 생성 벤치마크의 사실상 표준(de facto standard)이 되었다. pass@k 메트릭은 이후 MBPP, SWE-bench, LiveCodeBench 등 거의 모든 코드 벤치마크에서 채택되었다. “기능적 정확성 = unit test 통과”라는 평가 철학은 BLEU 등 표면적 유사도 메트릭을 완전히 대체했다.
Scaling Law의 코드 도메인 확장
모델 크기에 따른 pass@k의 log-linear 증가는 코드 생성에서도 스케일링 법칙이 성립함을 보여주었다. 이는 이후 Codex → GPT-4 → Claude 등의 코드 능력 발전을 예측하는 근거가 되었다.
관련 연구
- MBPP_2021_PythonProgramming — 유사 시기 발표된 Python 코딩 벤치마크, 더 많은 문제(974개) 포함
- SWE-bench_2023_SoftwareEngineering — HumanEval을 넘어 실제 소프트웨어 엔지니어링 과제로 확장
- Training Verifiers to Solve Math Word Problem — 수학 도메인에서의 유사한 반복 샘플링 + 검증 접근법
핵심 용어 정리
| 용어 | 정의 |
|---|---|
| pass@k | n개 코드 샘플 중 k개를 선택했을 때 최소 1개가 unit test를 통과할 확률 |
| Functional Correctness | 코드가 명세된 모든 unit test를 통과하는지로 판단하는 정확성 기준 |
| Codex | GPT 모델을 GitHub 코드로 파인튜닝한 OpenAI의 코드 생성 모델 |
| Nucleus Sampling (top-p) | 누적 확률이 p 이하인 토큰들에서만 샘플링하는 디코딩 전략 |
| Docstring | 함수의 동작을 설명하는 문자열, 여기서는 모델의 입력(프롬프트)으로 사용 |
| Temperature | 소프트맥스 분포의 날카로움을 조절하는 파라미터. 낮으면 결정적, 높으면 다양한 출력 |
태그
paper #2021 benchmark code_generation HumanEval pass_at_k Codex OpenAI