|
| 1 | +import os |
| 2 | +import re |
| 3 | +import argparse |
| 4 | +import torch |
| 5 | +from unsloth import FastLanguageModel |
| 6 | +from datasets import load_dataset |
| 7 | +from tqdm import tqdm |
| 8 | + |
| 9 | +def extract_gt(text): |
| 10 | + if "####" in text: |
| 11 | + return text.split("####")[-1].strip() |
| 12 | + return None |
| 13 | + |
| 14 | +def extract_answer(content): |
| 15 | + # Extract prediction inside <answer>...</answer> |
| 16 | + pred_match = re.search(r"<answer>(.*?)</answer>", content, re.DOTALL) |
| 17 | + if pred_match: |
| 18 | + return pred_match.group(1).strip() |
| 19 | + return None |
| 20 | + |
| 21 | +def verify_format(content): |
| 22 | + # Verify presence of <reasoning> and <answer> blocks |
| 23 | + pattern = r"<reasoning>.*?</reasoning>\s*<answer>.*?</answer>" |
| 24 | + return 1.0 if re.search(pattern, content, re.DOTALL) else 0.0 |
| 25 | + |
| 26 | +def run_benchmark(model_name, num_samples=30): |
| 27 | + print(f"Loading model: {model_name}...") |
| 28 | + max_seq_length = 1024 |
| 29 | + |
| 30 | + model, tokenizer = FastLanguageModel.from_pretrained( |
| 31 | + model_name = model_name, |
| 32 | + max_seq_length = max_seq_length, |
| 33 | + load_in_4bit = True, |
| 34 | + fast_inference = False |
| 35 | + ) |
| 36 | + model.eval() |
| 37 | + |
| 38 | + print("Loading GSM8K test dataset...") |
| 39 | + dataset = load_dataset("openai/gsm8k", "main", split=f"test[:{num_samples}]") |
| 40 | + |
| 41 | + SYSTEM_PROMPT = """ |
| 42 | +Respond in the following format: |
| 43 | +<reasoning> |
| 44 | +... |
| 45 | +</reasoning> |
| 46 | +<answer> |
| 47 | +... |
| 48 | +</answer> |
| 49 | +""" |
| 50 | + |
| 51 | + correct = 0 |
| 52 | + format_compliant = 0 |
| 53 | + total_thought_length = 0 |
| 54 | + |
| 55 | + print(f"\nEvaluating on {num_samples} samples...") |
| 56 | + for item in tqdm(dataset): |
| 57 | + question = item["question"] |
| 58 | + raw_ans = item["answer"] |
| 59 | + gt = extract_gt(raw_ans) |
| 60 | + |
| 61 | + messages = [ |
| 62 | + {"role": "system", "content": SYSTEM_PROMPT}, |
| 63 | + {"role": "user", "content": question} |
| 64 | + ] |
| 65 | + |
| 66 | + inputs = tokenizer.apply_chat_template( |
| 67 | + messages, |
| 68 | + tokenize=True, |
| 69 | + add_generation_prompt=True, |
| 70 | + return_tensors="pt" |
| 71 | + ).to("cuda") |
| 72 | + |
| 73 | + with torch.no_grad(): |
| 74 | + outputs = model.generate( |
| 75 | + input_ids=inputs, |
| 76 | + max_new_tokens=512, |
| 77 | + temperature=0.6, |
| 78 | + top_p=0.95, |
| 79 | + use_cache=True |
| 80 | + ) |
| 81 | + |
| 82 | + generated_tokens = outputs[0][len(inputs[0]):] |
| 83 | + response = tokenizer.decode(generated_tokens, skip_special_tokens=True) |
| 84 | + |
| 85 | + # Evaluate metrics |
| 86 | + pred = extract_answer(response) |
| 87 | + is_compliant = verify_format(response) |
| 88 | + |
| 89 | + # Calculate thought length (characters inside <reasoning> tags) |
| 90 | + reasoning_match = re.search(r"<reasoning>(.*?)</reasoning>", response, re.DOTALL) |
| 91 | + thought_len = len(reasoning_match.group(1)) if reasoning_match else 0 |
| 92 | + total_thought_length += thought_len |
| 93 | + |
| 94 | + if is_compliant: |
| 95 | + format_compliant += 1 |
| 96 | + if gt and pred and gt == pred: |
| 97 | + correct += 1 |
| 98 | + |
| 99 | + accuracy = (correct / num_samples) * 100.0 |
| 100 | + compliance = (format_compliant / num_samples) * 100.0 |
| 101 | + avg_thought_len = total_thought_length / num_samples |
| 102 | + |
| 103 | + print("\n--- Benchmark Results ---") |
| 104 | + print(f"Model: {model_name}") |
| 105 | + print(f"Accuracy: {accuracy:.2f}% ({correct}/{num_samples})") |
| 106 | + print(f"Format Compliance: {compliance:.2f}% ({format_compliant}/{num_samples})") |
| 107 | + print(f"Average Thought Length: {avg_thought_len:.2f} chars") |
| 108 | + |
| 109 | + # Return metrics for report generation |
| 110 | + return { |
| 111 | + "accuracy": accuracy, |
| 112 | + "compliance": compliance, |
| 113 | + "thought_len": avg_thought_len |
| 114 | + } |
| 115 | + |
| 116 | +if __name__ == "__main__": |
| 117 | + parser = argparse.ArgumentParser() |
| 118 | + parser.add_argument("--model", type=str, required=True, help="Path or repo name of the model to evaluate") |
| 119 | + parser.add_argument("--samples", type=int, default=30, help="Number of test samples") |
| 120 | + args = parser.parse_args() |
| 121 | + |
| 122 | + run_benchmark(args.model, args.samples) |
0 commit comments