Files
15a7d9d2a4 Upload metadata along with model weights (#320)
* uploading metadata

* lint and bugfix

* Update eval/utils.py

Co-authored-by: Costa Huang <[email protected]>

---------

Co-authored-by: Hamish Ivison <[email protected]>
Co-authored-by: Costa Huang <[email protected]>
2024-09-04 09:16:08 -07:00

379 lines
13 KiB
Python

import argparse
import os
import torch
import numpy as np
import pandas as pd
import json
from tqdm import tqdm
from eval.mmlu.categories import subcategories, categories
from eval.utils import get_next_word_predictions, load_hf_tokenizer, load_hf_lm, query_openai_chat_model, dynamic_import_function, upload_results_to_hf, check_and_upload_model_metadata
choices = ["A", "B", "C", "D"]
def format_subject(subject):
l = subject.split("_")
s = ""
for entry in l:
s += " " + entry
return s
def format_example(df, idx, include_answer=True):
prompt = df.iloc[idx, 0]
k = df.shape[1] - 2
for j in range(k):
prompt += "\n{}. {}".format(choices[j], df.iloc[idx, j + 1])
prompt += "\nAnswer:"
if include_answer:
prompt += " {}\n\n".format(df.iloc[idx, k + 1])
return prompt
def gen_prompt(train_df, subject, k=-1):
prompt = "The following are multiple choice questions (with answers) about {}.\n\n".format(
format_subject(subject)
)
if k == -1:
k = train_df.shape[0]
for i in range(k):
prompt += format_example(train_df, i)
return prompt
@torch.no_grad()
def eval_hf_model(args, subject, model, tokenizer, dev_df, test_df, batch_size=1):
prompts = []
chat_formatting_function = dynamic_import_function(args.chat_formatting_function) if args.use_chat_format else None
for i in range(0, test_df.shape[0]):
k = args.ntrain
prompt_end = format_example(test_df, i, include_answer=False)
train_prompt = gen_prompt(dev_df, subject, k)
prompt = train_prompt + prompt_end
if args.use_chat_format:
messages = [{"role": "user", "content": prompt}]
prompt = chat_formatting_function(messages, tokenizer, add_bos=False)
if prompt[-1] in ["\n", " "]:
prompt += "The answer is:"
else:
prompt += " The answer is:"
tokenized_prompt = tokenizer(prompt, truncation=False, add_special_tokens=False).input_ids
# make sure every prompt is less than 2048 tokens
while len(tokenized_prompt) > 2048:
k -= 1
train_prompt = gen_prompt(dev_df, subject, k)
prompt = train_prompt + prompt_end
if args.use_chat_format:
messages = [{"role": "user", "content": prompt}]
prompt = chat_formatting_function(messages, tokenizer, add_bos=False)
if prompt[-1] in ["\n", " "]:
prompt += "The answer is:"
else:
prompt += " The answer is:"
tokenized_prompt = tokenizer(prompt, truncation=False, add_special_tokens=False).input_ids
prompts.append(prompt)
# get the answer for all examples
# adding a prefix space here, as that's expected from the prompt
# TODO: should raise a warning if this returns more than one token
answer_choice_ids = [tokenizer.encode(" " + answer_choice, add_special_tokens=False)[-1] for answer_choice in choices]
pred_indices, all_probs = get_next_word_predictions(
model, tokenizer, prompts, candidate_token_ids=answer_choice_ids, return_token_predictions=False, batch_size=batch_size
)
# get the metrics
cors = []
groud_truths = test_df.iloc[:, -1].values
for i in range(len(pred_indices)):
prediction = choices[pred_indices[i]]
ground_truth = groud_truths[i]
cors.append(prediction == ground_truth)
acc = np.mean(cors)
cors = np.array(cors)
all_probs = np.array(all_probs)
print("Average accuracy {:.3f} - {}".format(acc, subject))
return cors, acc, all_probs
def eval_openai_chat_engine(args, subject, engine, dev_df, test_df, batch_size=1):
import tiktoken
gpt_tokenizer = tiktoken.get_encoding("cl100k_base")
answer_choice_ids = [gpt_tokenizer.encode(" " + x)[0] for x in choices] # be careful, the tokenizer will tokenize " A" and "A" differently.
prompts = []
for i in range(0, test_df.shape[0]):
k = args.ntrain
prompt_end = format_example(test_df, i, include_answer=False)
train_prompt = gen_prompt(dev_df, subject, k)
prompt = train_prompt + prompt_end
prompts.append(prompt)
instances = [{"id": prompt, "prompt": prompt} for _, prompt in enumerate(prompts)]
results = query_openai_chat_model(
engine=args.openai_engine,
instances=instances,
batch_size=args.eval_batch_size if args.eval_batch_size else 10,
output_path=os.path.join(args.save_dir, f"{subject}_openai_results.jsonl"),
logit_bias={token_id: 100 for token_id in answer_choice_ids},
max_tokens=1,
)
# get the metrics
cors = []
groud_truths = test_df.iloc[:, -1].values
for i in range(len(test_df)):
prediction = results[i]["output"].strip()
ground_truth = groud_truths[i]
cors.append(prediction == ground_truth)
acc = np.mean(cors)
cors = np.array(cors)
all_probs = np.array([[0.25, 0.25, 0.25, 0.25] for _ in range(len(test_df))]) # dummy probs, just don't want to dig into the openai probs
print("Average accuracy {:.3f} - {}".format(acc, subject))
return cors, acc, all_probs
def main(args):
if args.model_name_or_path:
print("Loading model and tokenizer...")
tokenizer = load_hf_tokenizer(
model_name_or_path=args.model_name_or_path,
revision=args.hf_revision,
tokenizer_name_or_path=args.tokenizer_name_or_path,
use_fast_tokenizer=not args.use_slow_tokenizer,
)
model = load_hf_lm(
model_name_or_path=args.model_name_or_path,
revision=args.hf_revision,
load_in_8bit=args.load_in_8bit,
device_map="balanced_low_0" if torch.cuda.device_count() > 1 else "auto",
gptq_model=args.gptq,
)
from transformers import GPTNeoXForCausalLM, OPTForCausalLM
if isinstance(model, GPTNeoXForCausalLM) or isinstance(model, OPTForCausalLM):
tokenizer.model_max_length = model.config.max_position_embeddings
print("Set tokenizer.model_max_length to model.config.max_position_embeddings: {}".format(model.config.max_position_embeddings))
subjects = sorted(
[
f.split("_test.csv")[0]
for f in os.listdir(os.path.join(args.data_dir, "test"))
if "_test.csv" in f
]
)
if args.subjects:
assert all(subj in subjects for subj in args.subjects), f"Some of the subjects you specified are not valid: {args.subjects}"
subjects = args.subjects
if not os.path.exists(args.save_dir):
os.makedirs(args.save_dir)
all_cors = []
subcat_cors = {
subcat: [] for subcat_lists in subcategories.values() for subcat in subcat_lists
}
cat_cors = {cat: [] for cat in categories}
for subject in tqdm(subjects, desc=f"Evaluating subjects: "):
dev_df = pd.read_csv(
os.path.join(args.data_dir, "dev", subject + "_dev.csv"), header=None
)[: args.ntrain]
test_df = pd.read_csv(
os.path.join(args.data_dir, "test", subject + "_test.csv"), header=None
)
if args.n_instances and args.n_instances < test_df.shape[0]:
test_df = test_df.sample(args.n_instances, random_state=42)
if args.model_name_or_path:
cors, acc, probs = eval_hf_model(args, subject, model, tokenizer, dev_df, test_df, args.eval_batch_size)
else:
cors, acc, probs = eval_openai_chat_engine(args, subject, args.openai_engine, dev_df, test_df, args.eval_batch_size)
subcats = subcategories[subject]
for subcat in subcats:
subcat_cors[subcat].append(cors)
for key in categories.keys():
if subcat in categories[key]:
cat_cors[key].append(cors)
all_cors.append(cors)
test_df["correct"] = cors
for j in range(probs.shape[1]):
choice = choices[j]
test_df["choice{}_probs".format(choice)] = probs[:, j]
test_df.to_csv(
os.path.join(
args.save_dir, "{}.csv".format(subject)
),
index=None,
)
for subcat in subcat_cors:
subcat_acc = np.mean(np.concatenate(subcat_cors[subcat]))
print("Average accuracy {:.3f} - {}".format(subcat_acc, subcat))
for cat in cat_cors:
cat_acc = np.mean(np.concatenate(cat_cors[cat]))
print("Average accuracy {:.3f} - {}".format(cat_acc, cat))
weighted_acc = np.mean(np.concatenate(all_cors))
print("Average accuracy: {:.3f}".format(weighted_acc))
# save results
with open(os.path.join(args.save_dir, "metrics.json"), "w") as f:
json.dump(
{
"average_acc": weighted_acc,
"subcat_acc": {
subcat: np.mean(np.concatenate(subcat_cors[subcat]))
for subcat in subcat_cors
},
"cat_acc": {
cat: np.mean(np.concatenate(cat_cors[cat]))
for cat in cat_cors
},
},
f,
)
if args.upload_to_hf is not None:
# upload metrics to HF. Main metric is the accuracy
results = {
"average_acc": weighted_acc,
"subcat_acc": {
subcat: np.mean(np.concatenate(subcat_cors[subcat]))
for subcat in subcat_cors
},
"cat_acc": {
cat: np.mean(np.concatenate(cat_cors[cat]))
for cat in cat_cors
},
}
task_name = f"oi_mmlu_{args.ntrain}shots"
primary_score = results["average_acc"]
upload_results_to_hf(
results,
args.upload_to_hf,
args.hf_upload_name,
task_name=task_name,
primary_score=primary_score,
prepend_timestamp=True,
)
check_and_upload_model_metadata(
args.model_name_or_path, args.upload_to_hf, args.hf_upload_name, hf_revision=args.hf_revision
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--ntrain",
type=int,
default=5
)
parser.add_argument(
"--data_dir",
type=str,
default="data/mmlu"
)
parser.add_argument(
"--save_dir",
type=str,
default="results/mmlu/llama-7B/"
)
parser.add_argument(
"--model_name_or_path",
type=str,
default=None,
help="if specified, we will load the model to generate the predictions."
)
parser.add_argument(
"--hf_revision",
type=str,
default=None,
help="if specified, we will load the model from a revision of the model in the hub"
)
parser.add_argument(
"--tokenizer_name_or_path",
type=str,
default=None,
help="if specified, we will load the tokenizer from here."
)
parser.add_argument(
"--use_slow_tokenizer",
action="store_true",
help="If given, we will use the slow tokenizer."
)
parser.add_argument(
"--openai_engine",
type=str,
default=None,
help="if specified, we will use the OpenAI API to generate the predictions."
)
parser.add_argument(
"--subjects",
nargs="*",
help="which subjects to evaluate. If not specified, all the 57 subjects will be evaluated."
)
parser.add_argument(
"--n_instances",
type=int,
help="if specified, a maximum of n_instances per subject will be used for the evaluation."
)
parser.add_argument(
"--eval_batch_size",
type=int,
default=1,
help="batch size for evaluation."
)
parser.add_argument(
"--load_in_8bit",
action="store_true",
help="load model in 8bit mode, which will reduce memory and speed up inference."
)
parser.add_argument(
"--gptq",
action="store_true",
help="If given, we're evaluating a 4-bit quantized GPTQ model."
)
parser.add_argument(
"--use_chat_format",
action="store_true",
help="If given, we will use the chat format for the prompts."
)
parser.add_argument(
"--chat_formatting_function",
type=str,
default="eval.templates.create_prompt_with_tulu_chat_format",
help="The function to use to create the chat format. This function will be dynamically imported. Please see examples in `eval/templates.py`."
)
parser.add_argument(
"--upload_to_hf",
type=str,
default=None,
help="If specified, we will upload the results to Hugging Face Datasets. "
"This should be the name of the dataset to upload to."
)
parser.add_argument(
"--hf_upload_name",
type=str,
default=None,
help="If uploading to hf, this is the model name"
)
args = parser.parse_args()
# model_name_or_path and openai_engine cannot be both None or both not None.
assert (args.model_name_or_path is None) != (args.openai_engine is None), "Either model_name_or_path or openai_engine should be specified."
main(args)