HumanEval: Code Generation LLM 的 Benchmark
Introduction
在 CodeLLM 的 evaluation 當中,我們最常看到的就是 HumanEval 這組 Benchmark。這篇文章我們會簡介 HumanEval 這組 Benchmark 的內容。包含了:
HumanEval 的 Dataset 結構
HumanEval evaluation 的邏輯
目前在 HumanEval 上面表現不錯的 CodeLLM
HumanEval 是在 Evaluating Large Language Models Trained on Code 裡面由 OpenAI 提出來的 Benchmark。一開始是為了評斷 Codex Model 的 performance 設計,隨後成為了眾多 CodeLLM 拿來做 Benchmark 的標準。
HumanEval 的 Dataset 結構
HumanEval 當中的測試資料是由人產出來的 Dataset,總共有 164 組資料,一組 HumanEval 的資料如下:
{
"task_id":"HumanEval/1",
"prompt":"from typing import List\\n\\n\\ndef separate_paren_groups(paren_string: str) -> List[str]:\\n \\"\\"\\" Input to this function is a string containing multiple groups of nested parentheses. Your goal is to\\n separate those group into separate strings and return the list of those.\\n Separate groups are balanced (each open brace is properly closed) and not nested within each other\\n Ignore any spaces in the input string.\\n >>> separate_paren_groups('( ) (( )) (( )( ))')\\n ['()', '(())', '(()())']\\n \\"\\"\\"\\n",
"entry_point":"separate_paren_groups",
"canonical_solution":" result = []\\n current_string = []\\n current_depth = 0\\n\\n for c in paren_string:\\n if c == '(':\\n current_depth += 1\\n current_string.append(c)\\n elif c == ')':\\n current_depth -= 1\\n current_string.append(c)\\n\\n if current_depth == 0:\\n result.append(''.join(current_string))\\n current_string.clear()\\n\\n return result\\n",
"test":"\\n\\nMETADATA = {\\n 'author': 'jt',\\n 'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n assert candidate('(()()) ((())) () ((())()())') == [\\n '(()())', '((()))', '()', '((())()())'\\n ]\\n assert candidate('() (()) ((())) (((())))') == [\\n '()', '(())', '((()))', '(((())))'\\n ]\\n assert candidate('(()(())((())))') == [\\n '(()(())((())))'\\n ]\\n assert candidate('( ) (( )) (( )( ))') == ['()', '(())', '(()())']\\n"
}
以下是每個欄位的說明:
task_id
: Task IDprompt
: 用來餵給 CodeLLM 的 promptentry_point
: 代表拿來被測試 function 的 entry point。舉個例子來說,在上面的例子當中,CodeLLM 要產生separate_paren_groups
這個 function。在 evaluation 的時候會執行separate_paren_groups
這個 function 來測試結果。canonical_solution
: 參考答案test
: 對應的測試
HumanEval 的 evaluation 邏輯
整個 HumanEval evaluation 的流程如下:
根據每個 data 的產生相對應的 code
執行單元測試
計算 pass@k
以下分別對每個步驟做些說明
根據每個 data 的產生相對應的 code
首先,我們要根據每個 data 產生相對應的 code。下面是 https://github.com/openai/human-eval 當中的 code
from human_eval.data import write_jsonl, read_problems
problems = read_problems()
num_samples_per_task = 200
samples = [
dict(task_id=task_id, completion=generate_one_completion(problems[task_id]["prompt"]))
for task_id in problems
for _ in range(num_samples_per_task)
]
write_jsonl("samples.jsonl", samples)
當中我們可以看到幾個重點:
generate_one_completion
就是我們做 evaluation 的時候要提供的 function。裡面接收了prompt
並且回傳產生的程式碼由於 LLM 本身並不是每次產出的結果都是一模一樣的,這邊有個
num_samples_per_task = 200
的參數,代表每個 task 會產生兩百個樣本。總共會有 164*200=32800 組結果
執行單元測試
接著我們針對產出的程式碼進行 unittest,可以參考 https://github.com/openai/human-eval/blob/312c5e5532f0e0470bf47f77a6243e02a61da530/human_eval/execution.py#L13-L87 這邊的 code
當中有一段
# ref: <https://github.com/openai/human-eval/blob/312c5e5532f0e0470bf47f77a6243e02a61da530/human_eval/execution.py#L37>
# Construct the check program and run it.
check_program = (
problem["prompt"] + completion + "\\n" +
problem["test"] + "\\n" +
f"check({problem['entry_point']})"
)
就是把 prompt、completion 以及 test 組合起來,接著直接用 Python 的 exec 執行。如果測試通過就標記為 passed。
計算 pass@k
pass@k 這個數值代表在 top-k generated code sample 當中,至少有一個 sample 可以通過 unit test 的機率。計算方式如下:
假設在 n 個 generated sample 當中,有 c 個正確的 sample,那麼總共能產生 k 個 samples 的組合數是 C(n, k)。相同道理,總共能產生 n-c 組錯誤 sample 的組合數是 C(n-c, k)
因此,生成 k 個 sample 都不能通過測試的機率是 C(n-c,k)/C(n,k)。在 k 個 sample 當中至少有一個 sample 可以通過測試的機率為 1-C(n-c,k)/C(n,k)
# ref: <https://github.com/openai/human-eval/blob/312c5e5532f0e0470bf47f77a6243e02a61da530/human_eval/evaluation.py#L22-L28>
def estimator(n: int, c: int, k: int) -> float:
"""
Calculates 1 - comb(n - c, k) / comb(n, k).
"""
if n - c < k:
return 1.0
return 1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))
目前有哪些表現不錯的 CodeLLM
目前可以從 EvalPlus Leaderboard 上面看到 HumanEval 的結果。這邊列出 Top 20 的 CodeLLM 作為參考
這邊可以看到 GPT-4 佔據了前兩名,不過 DeepSeek-Coder 系列的 Model 表現得相當不錯,甚至在第二十名的 DeepSeek-Coder-1.3B-Instruct 這個 Model 的參數量只有 1.3B,遠小於綁上其他的 Model。
結論
隨著 LLM 的能力越來越進步,我們也需要更好的方式來評斷 LLM 生成的結果是不是符合人類的需求。對於 CodeLLM 的表現目前還在繼續的探索當中,HumanEval dataset 還有 pass@k 這個 metric 只是一種初步評估的方式,實務上,針對各自的 usecase 發展出適合自己工作類型的 benchmark 才是最好的方式。