How to Evaluate an LLM's Performance on the GSM8K Benchmark: A Complete Implementation Guide
You evaluate an LLM on GSM8K by loading a checkpoint into a Transformer model, running greedy decoding on the dataset's math questions, extracting numeric answers from the "#### N" format, and calculating accuracy through tolerant numeric comparison against gold labels.
The GSM8K benchmark is the industry standard for measuring arithmetic reasoning capabilities in large language models. This guide explains the exact implementation used in the FareedKhan-dev/train-llm-from-scratch repository to evaluate an LLM's performance on the GSM8K benchmark, covering checkpoint reconstruction, greedy inference, and answer verification.
Loading the Model Checkpoint
The evaluation pipeline begins by reconstructing the model architecture from a saved checkpoint. In scripts/eval_post_training.py, the model_from_ckpt function (lines 28-42) loads the .pt file and preserves only the backbone language model weights.
This design allows the same evaluation code to work for both pure language model checkpoints and reward model checkpoints, as the function automatically strips any reward-specific heads while keeping the transformer backbone intact.
from src.post_training.inference import load_model_from_ckpt
# Load checkpoint - works for both LM and reward model checkpoints
model = load_model_from_ckpt("/ephemeral/ckpts/sft.pt", device="cpu")
Preparing the GSM8K Dataset
The load_gsm8k_eval function in src/post_training/evaluation.py (lines 107-116) pulls the official OpenAI GSM8K dataset from the Hugging Face datasets hub. It returns a list of question-answer pairs and supports an optional limit parameter for rapid testing on subsets of the data.
from src.post_training.evaluation import load_gsm8k_eval
# Load test split with optional limit for fast iteration
qa_pairs = load_gsm8k_eval(split="test", limit=200)
Generating Responses with Greedy Decoding
For each question, the pipeline constructs a prompt using the chat template via encode_prompt, then generates responses using batched_generate in src/post_training/evaluation.py (lines 23-44). Setting greedy=True forces argmax decoding (temperature 0) to ensure deterministic, reproducible results.
The batching mechanism respects the model's context length while processing multiple prompts in parallel, maximizing GPU utilization during evaluation.
from src.post_training.evaluation import batched_generate
# Generate with greedy decoding for deterministic evaluation
responses = batched_generate(
model,
prompts,
greedy=True,
max_new_tokens=300,
device="cpu"
)
Scoring and Computing Accuracy
The scoring layer extracts numeric answers and performs tolerant comparison:
-
Gold Answer Extraction:
gsm8k_gold_answerinsrc/post_training/rewards/parsing.py(lines 71-92) parses the final "#### N" annotation from the GSM8K answer field to extract the reference number. -
Answer Verification:
is_correctinsrc/post_training/rewards/verifiers.py(lines 34-41) implements tolerant numeric comparison, checking for exact matches after rounding and supporting optional format bonuses. -
Accuracy Aggregation: The
gsm8k_accuracyfunction insrc/post_training/evaluation.py(lines 76-104) orchestrates the full pipeline, returning the accuracy percentage along with raw counts of correct answers.
from src.post_training.evaluation import gsm8k_accuracy
# Run full evaluation with sample inspection
result = gsm8k_accuracy(
model,
qa_pairs,
device="cpu",
max_new_tokens=300,
greedy=True,
return_samples=5 # Include 5 examples for manual inspection
)
print(f"Accuracy: {result['accuracy']*100:.1f}% ({result['correct']}/{result['n']})")
Running the Evaluation
Command-Line Interface
The recommended approach uses the evaluation script in scripts/eval_post_training.py:
PYTHONPATH=. python scripts/eval_post_training.py \
--ckpt /ephemeral/ckpts/sft.pt \
--label my_model \
--limit 200
Output format:
[my_model] GSM8K test accuracy: 71.0% (142/200)
Programmatic Python API
For custom evaluation workflows, import the evaluation utilities directly:
from src.post_training.evaluation import gsm8k_accuracy, load_gsm8k_eval
from src.post_training.inference import load_model_from_ckpt
model = load_model_from_ckpt("/ephemeral/ckpts/sft.pt", device="cpu")
qa_pairs = load_gsm8k_eval(split="test", limit=100)
result = gsm8k_accuracy(
model,
qa_pairs,
device="cpu",
max_new_tokens=300,
greedy=True,
return_samples=5,
)
print(f"GSM8K accuracy: {result['accuracy']*100:.1f}%")
for samp in result["samples"]:
print(f"Q: {samp['q']}")
print(f"Gold: {samp['gold']} | Correct: {samp['correct']}")
print(f"Model: {samp['response']}\n")
Interactive Streamlit UI
The repository includes a web interface at ui/pages/8_Evaluate.py (lines 25-33). Launch with streamlit run ui/app.py, navigate to the Evaluate page, and select your checkpoint from /ephemeral/ckpts/ to run the evaluation interactively. The UI exposes the same gsm8k_accuracy function while providing visual inspection of sample generations.
Summary
-
Checkpoint Loading: Use
model_from_ckptinscripts/eval_post_training.pyto load transformer weights while automatically handling both LM and reward model checkpoints. -
Dataset Preparation:
load_gsm8k_evalfetches the official GSM8K dataset with optional subset limiting for rapid iteration. -
Greedy Decoding: The
batched_generatefunction withgreedy=Trueensures deterministic, reproducible evaluation by using argmax sampling. -
Answer Parsing:
gsm8k_gold_answerextracts the "#### N" format from GSM8K answers, whileis_correctperforms tolerant numeric comparison. -
Evaluation Entry Points: Choose between CLI scripts (
scripts/eval_post_training.py), Python API (gsm8k_accuracy), or the Streamlit UI (ui/pages/8_Evaluate.py).
Frequently Asked Questions
What is greedy decoding and why is it used for GSM8K evaluation?
Greedy decoding generates text by always selecting the highest probability token (argmax) at each step, setting temperature to zero. This eliminates randomness in the evaluation process, ensuring that running the same checkpoint twice produces identical results. The batched_generate function in src/post_training/evaluation.py implements this when greedy=True, making scores comparable across different evaluation runs and hardware configurations.
How does the repository extract numeric answers from GSM8K's formatted output?
The gsm8k_gold_answer function in src/post_training/rewards/parsing.py (lines 71-92) parses the special "#### N" delimiter that appears at the end of every GSM8K answer. This extracts the gold numeric value for comparison. The generated text is similarly processed to extract numeric candidates, which is_correct then compares using tolerant rounding logic to handle formatting variations like decimal places or thousand separators.
Can I evaluate reward model checkpoints using the same evaluation code?
Yes, the model_from_ckpt function automatically strips reward-specific heads and keeps only the language model backbone. According to the implementation in scripts/eval_post_training.py (lines 28-42), this allows the same evaluation pipeline to work seamlessly with both pure language model checkpoints (.pt files) and reward model checkpoints, without requiring separate loading logic or architecture modifications.
What is the difference between the CLI and programmatic evaluation approaches?
The CLI in scripts/eval_post_training.py provides a quick, table-formatted summary suitable for standard benchmarking, while the programmatic API (gsm8k_accuracy) offers granular control over parameters like return_samples for debugging specific failure modes. The CLI handles PYTHONPATH setup and device management automatically, whereas the Python API requires manual import of load_model_from_ckpt and load_gsm8k_eval, making it ideal for integration into custom training loops or hyperparameter sweeps.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →