diff --git a/reference/README.md b/reference/README.md new file mode 100644 index 0000000..494b350 --- /dev/null +++ b/reference/README.md @@ -0,0 +1,7 @@ +# Reference Implementations + +This directory contains reference implementations of experiments run in the FanOutQA paper. These implementations are +provided "as-is", with no guarantees on usability or support provided. Note the code quality of these implementations +may be lower than the library code, as these are earlier explorations run a) on a deadline and b) to develop the +interfaces that were eventually exposed in this library. + diff --git a/reference/paper_benchmarks/.gitignore b/reference/paper_benchmarks/.gitignore new file mode 100644 index 0000000..e9a9a46 --- /dev/null +++ b/reference/paper_benchmarks/.gitignore @@ -0,0 +1,161 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +**.DS_Store +results/extra-* +results/prompts-* +results-old/ +.wikicache/ +tmp*/ +BLEURT-20/ +.llmcache/ diff --git a/reference/paper_benchmarks/README.md b/reference/paper_benchmarks/README.md new file mode 100644 index 0000000..95eeb8c --- /dev/null +++ b/reference/paper_benchmarks/README.md @@ -0,0 +1,28 @@ +# benchmarks + +make sure to do `python -m spacy download en_core_web_sm` + +closedbook: just the q and 0 shot prompt to output short text + +openbook: like closedbook but you can search wikipedia wowza + +wiki provided: chunk the known evidence 1024 characters at a time (preferring splitting at paragraph and sentence +boundaries), retrieve as many passages fit into context window as possible, use bm25 + +run: + +```shell +WIKI_CACHE_DIR=/nlp/data/andrz/fanoutqa-bench/.wikicache python run_openbook.py -m mistral-chat -n 3 +for i in slurm/*; do sbatch $i; done +``` + +# eval + +install: + +```shell +wget https://storage.googleapis.com/bleurt-oss-21/BLEURT-20.zip +unzip BLEURT-20.zip +rm BLEURT-20.zip +python -m spacy download en_core_web_sm +``` \ No newline at end of file diff --git a/reference/paper_benchmarks/bench/__init__.py b/reference/paper_benchmarks/bench/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reference/paper_benchmarks/bench/engines.py b/reference/paper_benchmarks/bench/engines.py new file mode 100644 index 0000000..64ae8af --- /dev/null +++ b/reference/paper_benchmarks/bench/engines.py @@ -0,0 +1,111 @@ +import os + +import aiolimiter +import torch +from kani import AIFunction, ChatMessage +from kani.engines.anthropic import AnthropicEngine +from kani.engines.base import BaseEngine +from kani.engines.huggingface.llama2 import LlamaEngine +from kani.engines.openai import OpenAIEngine +from kani.engines.openai.models import ChatCompletion + +# openai +ft_org_id = "org-dxkPAvuWroRmYw7z0VhqMC5S" +lab_org_id = "org-bgAXfs8WdU5942SLngg0OGpd" + +# hf +common_hf_model_args = dict( + device_map="auto", + torch_dtype=torch.float16, +) + +if mcd := os.getenv("MODEL_CACHE_DIR"): + common_hf_model_args["cache_dir"] = mcd + + +def can_parallel(engine): + return isinstance(engine, OpenAIEngine) + + +def get_engine(name: str, ctx_size=None) -> BaseEngine: + if name == "gpt-4": + return RatelimitedOpenAIEngine( + model="gpt-4-0613", + organization=lab_org_id, + max_context_size=ctx_size or 8192, + tpm_limiter=aiolimiter.AsyncLimiter(300000), + temperature=0, + ) + elif name == "gpt-4-turbo": + return RatelimitedOpenAIEngine( + model="gpt-4-0125-preview", + organization=ft_org_id, + max_context_size=ctx_size or 128000, + tpm_limiter=aiolimiter.AsyncLimiter(400000), + temperature=0, + ) + elif name == "gpt-3.5-turbo": + return RatelimitedOpenAIEngine( + model="gpt-3.5-turbo-1106", + organization=ft_org_id, + max_context_size=ctx_size or 16000, + tpm_limiter=aiolimiter.AsyncLimiter(2000000), + temperature=0, + ) + elif name == "llama-chat": + return LlamaEngine( + "meta-llama/Llama-2-70b-chat-hf", + max_context_size=ctx_size or 4096, + model_load_kwargs=common_hf_model_args, + use_auth_token=True, + do_sample=False, + ) + elif name == "mistral-chat": + return LlamaEngine( + "mistralai/Mistral-7B-Instruct-v0.2", + max_context_size=ctx_size or 32768, + model_load_kwargs=common_hf_model_args, + do_sample=False, + ) + elif name == "mixtral": + return LlamaEngine( + "mistralai/Mixtral-8x7B-Instruct-v0.1", + max_context_size=ctx_size or 32768, + model_load_kwargs=common_hf_model_args, + do_sample=False, + ) + elif name == "longllama": + return LlamaEngine( + "syzymon/long_llama_3b_instruct", + max_context_size=ctx_size or 512000, + tokenizer_kwargs={"trust_remote_code": True}, + model_load_kwargs={"trust_remote_code": True, **common_hf_model_args}, + do_sample=False, + ) + elif name == "claude": + return AnthropicEngine( + model="claude-2.1", + max_context_size=ctx_size or 200000, + temperature=0, + ) + else: + raise ValueError("Invalid model name") + + +class RatelimitedOpenAIEngine(OpenAIEngine): + def __init__( + self, *args, rpm_limiter: aiolimiter.AsyncLimiter = None, tpm_limiter: aiolimiter.AsyncLimiter = None, **kwargs + ): + super().__init__(*args, **kwargs) + self.rpm_limiter = rpm_limiter + self.tpm_limiter = tpm_limiter + + async def predict( + self, messages: list[ChatMessage], functions: list[AIFunction] | None = None, **hyperparams + ) -> ChatCompletion: + if self.rpm_limiter: + await self.rpm_limiter.acquire() + if self.tpm_limiter: + n_toks = self.function_token_reserve(functions) + sum(self.message_len(m) for m in messages) + await self.tpm_limiter.acquire(n_toks) + return await super().predict(messages, functions, **hyperparams) diff --git a/reference/paper_benchmarks/bench/runner.py b/reference/paper_benchmarks/bench/runner.py new file mode 100644 index 0000000..294dcc4 --- /dev/null +++ b/reference/paper_benchmarks/bench/runner.py @@ -0,0 +1,198 @@ +import argparse +import asyncio +import json +import logging +from collections import namedtuple + +import tqdm +from kani import Kani + +from bench.engines import can_parallel, get_engine +from bench.utils import REPO_ROOT, load_questions +from fanout.utils import batched + +parser = argparse.ArgumentParser() +parser.add_argument( + "-m", + "--model", + choices=[ + "gpt-4", + "gpt-4-turbo", + "gpt-3.5-turbo", + "llama-chat", + "mistral-chat", + "mixtral", + "longllama", + "claude", + ], + required=True, +) +parser.add_argument("-n", "--count", type=int, default=None) +parser.add_argument("--ignore-config", type=bool, default=False) +parser.add_argument("--no-cache-prompt", type=bool, default=False) +parser.add_argument("--ctx-size", type=int, default=None) + + +def argstokwargs(args): + return dict( + model_name=args.model, + n_questions=args.count, + ignore_config=args.ignore_config, + no_cache_prompt=args.no_cache_prompt, + ctx_size=args.ctx_size, + ) + + +log = logging.getLogger(__name__) + +TOKEN_RESERVE = 520 # 512 for gen + 8 for formatting etc +BATCH_SIZE = 20 + +GenerationResult = namedtuple("GenerationResult", "success content extra") + + +class Runner: + def __init__( + self, + bench_setting: str, + model_name: str = None, + n_questions: int = None, + ignore_config=False, + no_cache_prompt=False, + ctx_size: int = None, + ): + if ctx_size is not None: + model_key = model_name + model_name = f"{model_name}-ctx-{ctx_size}" + else: + model_key = model_name + self.bench_setting = bench_setting + self.model_name = model_name + self.n_questions = n_questions + self.ignore_config = ignore_config + self.no_cache_prompt = no_cache_prompt + self.prompts = [] + self.engine = get_engine(model_key, ctx_size=ctx_size) + self.questions = [] + self.write_lock = asyncio.Lock() + self.results_file = open(REPO_ROOT / f"results/results-{self.bench_setting}-{self.model_name}.jsonl", "a") + self.extras_file = open(REPO_ROOT / f"results/extra-{self.bench_setting}-{self.model_name}.jsonl", "a") + + def get_questions(self): + qs = load_questions() + + # filter to only ids in questions + if not self.ignore_config: + cfg_fp = REPO_ROOT / f"config/{self.bench_setting}-{self.model_name}.txt" + if cfg_fp.exists(): + with open(cfg_fp) as f: + ids = set(l.strip() for l in f if l.strip()) + qs = [q for q in qs if q["id"] in ids] + + if self.n_questions: + return qs[: self.n_questions] + return qs + + def load(self, *args, **kwargs): + self.questions = self.get_questions() + self.prompts = self.get_prompts(self.questions) + + def get_prompt(self, question, *args, **kwargs) -> str: + raise NotImplementedError + + def get_prompts(self, questions, *args, **kwargs) -> list[str]: + # load existing prompts + prompt_fp = REPO_ROOT / f"results/prompts-{self.bench_setting}-{self.model_name}.json" + existing_prompts = {} # id to prompt + if prompt_fp.exists() and not self.no_cache_prompt: + with open(prompt_fp) as f: + prompts = json.load(f) + existing_prompts = {p["id"]: p["prompt"] for p in prompts} + + # gen prompts that are missing + out = [] + for q in tqdm.tqdm(questions): + if q["id"] in existing_prompts: + prompt = existing_prompts[q["id"]] + else: + prompt = self.get_prompt(q, *args, **kwargs) + existing_prompts[q["id"]] = prompt + out.append(prompt) + + # save prompts, merge with existing + with open(prompt_fp, "w") as f: + data = [{"id": p_id, "prompt": p} for p_id, p in existing_prompts.items()] + json.dump(data, f) + + return out + + # def save(self, results: list[GenerationResult]): + # output = [] + # for idx, gen in enumerate(results): + # q = self.questions[idx] + # data = {"id": q["id"], "answer": gen.content, "question": q, "prompt": self.prompts[idx]} + # if gen.extra: + # data["extra"] = gen.extra + # output.append(data) + # + # with open(REPO_ROOT / f"results/{self.bench_setting}-{self.model_name}.json", "w") as f: + # json.dump(output, f, indent=2) + + def save_one(self, result: GenerationResult, idx: int): + q = self.questions[idx] + data = {"id": q["id"], "answer": result.content, "question": q} + self.results_file.write(json.dumps(data)) + self.results_file.write("\n") + # extras + data = { + "id": q["id"], + "answer": result.content, + "question": q, + "prompt": self.prompts[idx], + "extra": result.extra, + } + self.extras_file.write(json.dumps(data)) + self.extras_file.write("\n") + + # runners + async def run_one(self, prompt, question, kani_cls=Kani, **kwargs) -> GenerationResult: + ai = kani_cls(self.engine, **kwargs) + try: + resp = await ai.chat_round_str(prompt) + return GenerationResult(success=True, content=resp, extra=None) + except Exception: + log.exception("Error getting response") + return GenerationResult(success=False, content=None, extra=None) + + async def _run_one(self, *args, idx: int, **kwargs): + result = await self.run_one(*args, **kwargs) + async with self.write_lock: + self.save_one(result, idx) + return result + + async def _run_series(self, *args, **kwargs): + results = [] + for idx, prompt in tqdm.tqdm(enumerate(self.prompts), total=len(self.prompts)): + question = self.questions[idx] + result = await self._run_one(prompt, question, *args, idx=idx, **kwargs) + results.append(result) + return results + + async def _run_parallel(self, *args, **kwargs): + results = [] + for batch in tqdm.tqdm(batched(enumerate(self.prompts), BATCH_SIZE), total=len(self.prompts) // BATCH_SIZE + 1): + r = await asyncio.gather( + *(self._run_one(prompt, self.questions[idx], *args, idx=idx, **kwargs) for idx, prompt in batch) + ) + results.extend(r) + return results + + async def run(self, kani_cls=Kani, **kwargs) -> list[GenerationResult]: + if can_parallel(self.engine): + return await self._run_parallel(kani_cls=kani_cls, **kwargs) + return await self._run_series(kani_cls=kani_cls, **kwargs) + + async def close(self): + await self.engine.close() + self.results_file.close() + self.extras_file.close() diff --git a/reference/paper_benchmarks/bench/score.py b/reference/paper_benchmarks/bench/score.py new file mode 100644 index 0000000..6b72e31 --- /dev/null +++ b/reference/paper_benchmarks/bench/score.py @@ -0,0 +1,50 @@ +import itertools +import re +from collections import namedtuple + +from fanout.norm import normalize + +AccuracyResult = namedtuple("AccuracyResult", "found score missing") + + +def answer_in_text(reference, candidate: str) -> AccuracyResult: + """Is the answer present in the text?""" + if isinstance(reference, list): + missing = [] + for a in reference: + result = answer_in_text(a, candidate) + missing.extend(result.missing) + n_found = len(reference) - len(missing) + return AccuracyResult(found=n_found == len(reference), score=n_found / len(reference), missing=missing) + elif isinstance(reference, dict): + missing = [] + vals = itertools.chain(reference.keys(), reference.values()) + for a in vals: + result = answer_in_text(a, candidate) + missing.extend(result.missing) + n_ref = len(reference) * 2 + n_found = n_ref - len(missing) # kvs + return AccuracyResult(found=n_found == n_ref, score=n_found / n_ref, missing=missing) + else: + if isinstance(reference, bool): + reference = "yes" if reference else "no" + # primitive + norm_ans = normalize(reference) + norm_cand = normalize(candidate) + # ensure the answer is surrounded by word boundaries + if not re.search(rf"\b{re.escape(norm_ans)}\b", norm_cand): + return AccuracyResult(found=False, score=0, missing=[norm_ans]) + return AccuracyResult(found=True, score=1, missing=[]) + + +def str_answer(ans) -> str: + """Ensure the answer is a string for string-based metrics like ROUGE. Don't normalize it otherwise.""" + if isinstance(ans, list): + return "\n".join(map(str_answer, ans)) + elif isinstance(ans, dict): + return "\n".join(f"{k} - {str_answer(v)}" for k, v in ans.items()) + elif isinstance(ans, bool): + return "yes" if ans else "no" + elif ans is None: + return "" + return str(ans) diff --git a/reference/paper_benchmarks/bench/utils.py b/reference/paper_benchmarks/bench/utils.py new file mode 100644 index 0000000..5d10f32 --- /dev/null +++ b/reference/paper_benchmarks/bench/utils.py @@ -0,0 +1,20 @@ +import json +from pathlib import Path + +REPO_ROOT = Path(__file__).parents[1] + + +def load_questions(fp: Path = None): + if fp is None: + fp = REPO_ROOT / "fanout-final-test.json" + with open(fp) as f: + return json.load(f) + + +def load_results(fp: Path): + """Load results from a jsonl file""" + results = [] + with open(fp) as f: + for line in f: + results.append(json.loads(line)) + return results diff --git a/reference/paper_benchmarks/fanout/__init__.py b/reference/paper_benchmarks/fanout/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reference/paper_benchmarks/fanout/config.py b/reference/paper_benchmarks/fanout/config.py new file mode 100644 index 0000000..342195f --- /dev/null +++ b/reference/paper_benchmarks/fanout/config.py @@ -0,0 +1,8 @@ +import os +from pathlib import Path + +if cache_dir := os.getenv("WIKI_CACHE_DIR"): + CACHE_DIR = Path(cache_dir) +else: + CACHE_DIR = Path(__file__).parent / "../../ccbqa-processing/.wikicache" +CACHE_DIR.mkdir(exist_ok=True) diff --git a/reference/paper_benchmarks/fanout/norm.py b/reference/paper_benchmarks/fanout/norm.py new file mode 100644 index 0000000..98798a3 --- /dev/null +++ b/reference/paper_benchmarks/fanout/norm.py @@ -0,0 +1,79 @@ +import logging +import re +from decimal import Decimal + +import ftfy +import spacy + +log = logging.getLogger(__name__) +nlp = spacy.load("en_core_web_sm") + + +def normalize(text, remove_stopwords=False): + """ + - ftfy + - normalize numbers + - lemmatize + - remove stopwords (optional) + - remove punctuation + - remove redundant whitespace + """ + text = str(text).lower() + text = ftfy.fix_text(text) + text = normalize_numbers(text) + text = lemmatize(text, remove_stopwords=remove_stopwords) + text = remove_punct(text) + text = normalize_whitespace(text) + return text + + +def normalize_numbers(text: str, do_text_sub=False): + """Use regex to normalize numbers like 5.2 billion, and numbers with commas""" + # numbers with commas + comma_sub_text = re.sub(r"(\d+,)+\d+(\.\d+)?", lambda m: m[0].replace(",", ""), text) + + if not do_text_sub: + return comma_sub_text + + # numbers with text + def number_text_sub(match: re.Match): + n = Decimal(match[1]) # for precision + muls = match[2].strip() + for mul in muls.split(): + match mul.lower(): + case "thousand": + n *= 1_000 + case "million": + n *= 1_000_000 + case "billion": + n *= 1_000_000_000 + case "trillion": + n *= 1_000_000_000_000 + return str(n.normalize()) + + textual_number_sub_text = re.sub( + r"(\d+(?:\.\d+)?)((?:\s*(?:thousand|million|billion|trillion))+)", + number_text_sub, + comma_sub_text, + flags=re.IGNORECASE, + ) + + return textual_number_sub_text + + +def lemmatize(text: str, remove_stopwords=False): + """Return a normalized string with each word replaced by its lemmatized version.""" + doc = nlp(text) + if remove_stopwords: + return " ".join(tok.lemma_ for tok in doc if not tok.is_stop) + return " ".join(tok.lemma_ for tok in doc) + + +def remove_punct(text: str): + """Remove all punctuation from the string.""" + return re.sub(r"[,.?!:;]", "", text) + + +def normalize_whitespace(text: str): + """Replace all whitespace with a single space.""" + return re.sub(r"\s+", " ", text) diff --git a/reference/paper_benchmarks/fanout/retrieval.py b/reference/paper_benchmarks/fanout/retrieval.py new file mode 100644 index 0000000..c7462df --- /dev/null +++ b/reference/paper_benchmarks/fanout/retrieval.py @@ -0,0 +1,75 @@ +from collections import namedtuple +from typing import Iterable + +import numpy as np +from rank_bm25 import BM25Plus + +from fanout.norm import normalize +from fanout.wiki import get_page_markdown + +RetrievalResult = namedtuple("RetrievalResult", "title content") + + +class Corpus: + """A corpus of wiki docs. Indexes the docs on creation, normalizing the text beforehand with lemmatization.""" + + def __init__(self, documents: list[dict], doc_len: int): + """ + :param documents: The list of evidences to index + :param doc_len: The maximum length, in characters, of each chunk + """ + self.documents = [] + normalized_corpus = [] + for doc in documents: + title = doc["title"] + content = get_page_markdown(doc["pageid"]) + for chunk in chunk_text(content, max_chunk_size=doc_len): + self.documents.append(RetrievalResult(title, chunk)) + normalized_corpus.append(self.tokenize(chunk)) + + self.index = BM25Plus(normalized_corpus) + + def tokenize(self, text: str): + """Split the text into words, lemmatize, remove stopwords.""" + return normalize(text).split(" ") + + def best(self, q) -> Iterable[RetrievalResult]: + """Yield the best matching fragments to the given query.""" + tok_q = self.tokenize(q) + scores = self.index.get_scores(tok_q) + idxs = np.argsort(scores)[::-1] + for idx in idxs: + yield self.documents[idx] + + +def chunk_text(text, max_chunk_size=1024, chunk_on=("\n\n", "\n", ". ", ", ", " "), chunker_i=0): + """ + Recursively chunks *text* into a list of str, with each element no longer than *max_chunk_size*. + Prefers splitting on the elements of *chunk_on*, in order. + """ + + if len(text) <= max_chunk_size: # the chunk is small enough + return [text] + if chunker_i >= len(chunk_on): # we have no more preferred chunk_on characters + # optimization: instead of merging a thousand characters, just use list slicing + return [text[:max_chunk_size], *chunk_text(text[max_chunk_size:], max_chunk_size, chunk_on, chunker_i + 1)] + + # split on the current character + chunks = [] + split_char = chunk_on[chunker_i] + for chunk in text.split(split_char): + chunk = f"{chunk}{split_char}" + if len(chunk) > max_chunk_size: # this chunk needs to be split more, recurse + chunks.extend(chunk_text(chunk, max_chunk_size, chunk_on, chunker_i + 1)) + elif chunks and len(chunk) + len(chunks[-1]) <= max_chunk_size: # this chunk can be merged + chunks[-1] += chunk + else: + chunks.append(chunk) + + # if the last chunk is just the split_char, yeet it + if chunks[-1] == split_char: + chunks.pop() + + # remove extra split_char from last chunk + chunks[-1] = chunks[-1][: -len(split_char)] + return chunks diff --git a/reference/paper_benchmarks/fanout/utils.py b/reference/paper_benchmarks/fanout/utils.py new file mode 100644 index 0000000..231b84d --- /dev/null +++ b/reference/paper_benchmarks/fanout/utils.py @@ -0,0 +1,47 @@ +from itertools import islice + +from markdownify import MarkdownConverter + + +def batched(iterable, n): + # batched('ABCDEFG', 3) --> ABC DEF G + if n < 1: + raise ValueError("n must be at least one") + it = iter(iterable) + while batch := tuple(islice(it, n)): + yield batch + + +# markdownification +def yeet(*_): + return "" + + +def is_valid_url(x): + if not x: + return False + return not x.startswith("data:") + + +class MDConverter(MarkdownConverter): + def convert_img(self, el, text, convert_as_inline): + alt = el.attrs.get("alt", None) or "" + return f"![{alt}](image)" + + def convert_a(self, el, text, convert_as_inline): + return text + + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def convert_div(self, el, text, convert_as_inline): + content = text.strip() + if not content: + return "" + return f"{content}\n" + + # sometimes these appear inline and are just annoying + convert_script = yeet + convert_style = yeet + + +def markdownify(html: str): + return MDConverter(heading_style="atx").convert(html) diff --git a/reference/paper_benchmarks/fanout/wiki.py b/reference/paper_benchmarks/fanout/wiki.py new file mode 100644 index 0000000..a5ac1de --- /dev/null +++ b/reference/paper_benchmarks/fanout/wiki.py @@ -0,0 +1,134 @@ +"""Utils for working with Wikipedia""" + +import datetime +import json +import logging +import re + +import mediawiki +from bs4 import BeautifulSoup +from mediawiki import MediaWiki, MediaWikiPage + +from fanout.config import CACHE_DIR +from fanout.norm import normalize +from fanout.utils import markdownify + +USER_AGENT = "fanoutqa/bench (andrz@seas.upenn.edu) pymediawiki/0.7.4" +DATASET_EPOCH = datetime.datetime(year=2023, month=11, day=20, tzinfo=datetime.timezone.utc) + +log = logging.getLogger(__name__) +wikipedia = MediaWiki(user_agent=USER_AGENT) + + +# ==== classes ==== +class DatedPage(MediaWikiPage): + """To query contents as of the right date, we override some of the request params""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._dated_html = False + + def get_dated_html(self): + """Get the page content as of the dataset epoch.""" + cache_filename = CACHE_DIR / f"{self.pageid}-dated.html" + if self._dated_html is False: + if cache_filename.exists(): + self._dated_html = cache_filename.read_text() + else: + self._dated_html = "" + query_params = { + "prop": "revisions", + "rvprop": "content", + "rvlimit": 1, + "rvparse": "", + "titles": self.title, + # added here + "rvstart": DATASET_EPOCH.isoformat(), + } + request = self.mediawiki.wiki_request(query_params) + page = request["query"]["pages"][self.pageid] + try: + self._dated_html = page["revisions"][0]["*"] + except KeyError: + log.warning(f"Could not find dated revision of {self.title} - maybe the page did not exist yet?") + pass + cache_filename.write_text(self._dated_html) + return self._dated_html + + def get_dated_revid(self): + query_params = { + "prop": "revisions", + "rvprop": "ids", + "rvlimit": 1, + "rvparse": "", + "titles": self.title, + # added here + "rvstart": DATASET_EPOCH.isoformat(), + } + request = self.mediawiki.wiki_request(query_params) + page = request["query"]["pages"][self.pageid] + return page["revisions"][0]["revid"] + + def get_backlinks(self): + """Cached version of page.backlinks""" + cache_filename = CACHE_DIR / f"{self.pageid}-backlinks.json" + if cache_filename.exists(): + with open(cache_filename) as f: + return json.load(f) + backlinks = self.backlinks + with open(cache_filename, "w") as f: + json.dump(backlinks, f) + return backlinks + + +class WikiError(Exception): + pass + + +# ==== main ==== +def get_wikipedia_page_by_id(pageid): + try: + return DatedPage(wikipedia, pageid=pageid, redirect=True, preload=False) + except (mediawiki.PageError, mediawiki.DisambiguationError) as e: + log.exception("Got PageError:") + raise WikiError(repr(e)) from e + + +def get_page_all_text(pageid: int, norm=True, remove_citations_and_notes=False) -> str: + """ + Render the page HTML **as of the dataset epoch** and retrieve all visible text (including tables and infoboxes), + without markup. + """ + if norm: + cache_filename = CACHE_DIR / f"{pageid}-dated-norm.txt" + else: + cache_filename = CACHE_DIR / f"{pageid}-dated.txt" + + if cache_filename.exists(): + text = cache_filename.read_text() + else: + page = get_wikipedia_page_by_id(pageid) + html = page.get_dated_html() + soup = BeautifulSoup(html, "html.parser") + text = soup.get_text(separator=" ", strip=True) + if norm: + text = normalize(text) + cache_filename.write_text(text) + + if remove_citations_and_notes: + text = re.sub(r"\s\[ (((note\s+)?\d+)|edit) ]\s", " ", text) + return text + + +def get_page_markdown(pageid: int): + """Get the page content in markdown, including tables and infoboxes, appropriate for displaying to an LLM""" + cache_filename = CACHE_DIR / f"{pageid}-dated.md" + + if cache_filename.exists(): + return cache_filename.read_text() + + page = get_wikipedia_page_by_id(pageid) + html = page.get_dated_html() + text = markdownify(html) + cache_filename.write_text(text) + return text diff --git a/reference/paper_benchmarks/gen_slurm.py b/reference/paper_benchmarks/gen_slurm.py new file mode 100644 index 0000000..7ec042d --- /dev/null +++ b/reference/paper_benchmarks/gen_slurm.py @@ -0,0 +1,76 @@ +SETTINGS = ( + "openbook", + "closedbook", + "wiki_provided", +) +MODELS = { + "gpt-4": "small", + "gpt-4-turbo": "small", + "gpt-3.5-turbo": "small", + "llama-chat": "large", + "mistral-chat": "med", + "mixtral": "large", + # "longllama": "large", + "claude": "small", +} +TEMPLATE = """\ +#!/bin/bash +# +#SBATCH --partition=p_nlp +#SBATCH --job-name=foqa-{model}{fn_extra}-{setting} +#SBATCH --output=/nlp/data/andrz/logs/%j.%x.log +#SBATCH --error=/nlp/data/andrz/logs/%j.%x.log +#SBATCH --time=7-0 +#SBATCH -c {cpus} +#SBATCH --mem={mem} +#SBATCH --gpus={gpus} +{gpuconstraint} +export WIKI_CACHE_DIR=/nlp/data/andrz/fanoutqa-bench/.wikicache +srun python run_{setting}.py -m {model}{extra} +""" + + +def write_slurm_file(setting, model, extra="", fn_extra=""): + size = MODELS[model] + if size == "small": + cpus = 4 + mem = "32G" + gpus = 0 + gpuconstraint = "" + elif size == "med": + cpus = 4 + mem = "64G" + gpus = 1 + gpuconstraint = "#SBATCH --constraint=48GBgpu" + else: + cpus = 16 + mem = "128G" + gpus = 3 + gpuconstraint = "#SBATCH --constraint=48GBgpu" + + content = TEMPLATE.format( + setting=setting, + model=model, + cpus=cpus, + mem=mem, + gpus=gpus, + gpuconstraint=gpuconstraint, + extra=extra, + fn_extra=fn_extra, + ) + with open(f"slurm/{model}{fn_extra}-{setting}.sh", "w") as f: + f.write(content) + + +def main(): + for s in SETTINGS: + for m in MODELS: + write_slurm_file(s, m) + + for m in MODELS: + write_slurm_file("openbook", m, extra=" --ctx-size 4096", fn_extra="-short") + write_slurm_file("wiki_provided", m, extra=" --ctx-size 4096", fn_extra="-short") + + +if __name__ == "__main__": + main() diff --git a/reference/paper_benchmarks/get_10_questions.py b/reference/paper_benchmarks/get_10_questions.py new file mode 100644 index 0000000..080b163 --- /dev/null +++ b/reference/paper_benchmarks/get_10_questions.py @@ -0,0 +1,15 @@ +"""Output 10 questions for the human eval.""" + +import random + +from bench.utils import REPO_ROOT, load_questions + + +def main(): + qs = load_questions(REPO_ROOT / "fanout-final-dev.json") + for i, q in enumerate(random.sample(qs, 10)): + print(f"**Question {i + 1} ({q['id']})**: {q['question']}") + + +if __name__ == "__main__": + main() diff --git a/reference/paper_benchmarks/requirements.txt b/reference/paper_benchmarks/requirements.txt new file mode 100644 index 0000000..fef2470 --- /dev/null +++ b/reference/paper_benchmarks/requirements.txt @@ -0,0 +1,21 @@ +accelerate~=0.26.1 +aiolimiter~=1.1.0 +anthropic~=0.14.0 +beautifulsoup4~=4.12.3 +ftfy~=6.1.3 +kani~=0.7.2 +markdownify~=0.11.6 +openai~=1.10.0 +protobuf~=4.25.2 +pymediawiki~=0.7.4 +rank-bm25~=0.2.2 +sentencepiece~=0.1.99 +spacy~=3.7.2 +tiktoken~=0.5.2 +torch~=2.2.0 +tqdm~=4.66.1 +transformers~=4.37.2 + +# scoring +rouge-score~=0.1.2 +git+https://github.com/google-research/bleurt.git@master diff --git a/reference/paper_benchmarks/run_closedbook.py b/reference/paper_benchmarks/run_closedbook.py new file mode 100644 index 0000000..094a428 --- /dev/null +++ b/reference/paper_benchmarks/run_closedbook.py @@ -0,0 +1,27 @@ +import asyncio +import logging + +from bench.runner import Runner, argstokwargs, parser + + +class ClosedBookRunner(Runner): + def get_prompt(self, question, *args, **kwargs) -> str: + question = question["question"] + prompt = ( + "Answer the following question, and output only your answer. If the answer is a list, output one on" + f" each line. Current date: 11-20-2023.\n\n[Question]: {question}" + ) + return prompt + + +async def main(): + args = parser.parse_args() + runner = ClosedBookRunner(bench_setting="closedbook", **argstokwargs(args)) + runner.load() + await runner.run() + await runner.close() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + asyncio.run(main()) diff --git a/reference/paper_benchmarks/run_openbook.py b/reference/paper_benchmarks/run_openbook.py new file mode 100644 index 0000000..bee32d4 --- /dev/null +++ b/reference/paper_benchmarks/run_openbook.py @@ -0,0 +1,153 @@ +import asyncio +import logging +import re + +import mediawiki +from kani import ChatMessage, ChatRole, Kani, ToolCall, ai_function +from kani.engines.base import BaseEngine, Completion + +from bench.runner import GenerationResult, Runner, argstokwargs, parser +from fanout.retrieval import Corpus +from fanout.wiki import DatedPage, wikipedia + +log = logging.getLogger("openbook") + + +# ===== runner ===== +class OpenBookRunner(Runner): + def get_prompt(self, question, *args, **kwargs) -> str: + question = question["question"] + prompt = ( + "You have the ability to search Wikipedia for information. To do so, output a message in the format" + " {YOUR_SEARCH_QUERY} (e.g. `List of states and territories of the United" + " States`).\nAnswer the following question, and output only your answer or a search, but not" + " both. If the answer is a list, output one on each line. Current date: 11-20-2023.\n\n[Question]:" + f" {question}" + ) + return prompt + + async def run_one(self, prompt, question, kani_cls=Kani, **kwargs) -> GenerationResult: + try: + return await asyncio.wait_for(self.run_impl(prompt, kani_cls, question=question, **kwargs), timeout=600) + except Exception: + log.exception("Error getting response") + return GenerationResult(success=False, content=None, extra=None) + + async def run_impl(self, prompt, kani_cls=Kani, **kwargs): + ai = kani_cls(self.engine, **kwargs) + content = None + msgs = [] + async for msg in ai.full_round(prompt): + msgs.append(repr(msg)) + content = msg.content or content + if msg.role == ChatRole.ASSISTANT: + print(msg.content) + return GenerationResult(success=True, content=content, extra=msgs) + + +class OpenAIOpenBookRunner(OpenBookRunner): + def get_prompt(self, question, *args, **kwargs) -> str: + question = question["question"] + if self.model_name.startswith("gpt-3.5-turbo"): + prompt = ( + "Answer the following question, and output only your answer. You may search before outputting your" + " answer. If the answer is a list, output one on each line. Current date: 11-20-2023.\n\n[Question]:" + f" {question}" + ) + else: + prompt = ( + "Answer the following question, and output only a function call or your answer. If the answer is a" + f" list, output one on each line. Current date: 11-20-2023.\n\n[Question]: {question}" + ) + return prompt + + +# ==== kani ==== +class SearchEngine(BaseEngine): + def __init__(self, engine: BaseEngine, strict=False): + self.engine = engine + self.strict = strict + self.max_context_size = engine.max_context_size + self.token_reserve = engine.token_reserve + + def message_len(self, message): + if message.role == ChatRole.FUNCTION: + message = message.copy_with(role=ChatRole.USER) + return self.engine.message_len(message) + + async def predict(self, messages, functions=None, **kwargs): + translated_messages = [] + for m in messages: + if m.role == ChatRole.FUNCTION: + translated_messages.append(m.copy_with(role=ChatRole.USER)) + else: + translated_messages.append(m) + resp = await self.engine.predict(translated_messages, functions, **kwargs) + # search for a pair + content = resp.message.text + tool_calls = None + if self.strict: + match = re.match(r"(.+?)", content) + else: + match = re.search(r"(.+?)", content) + if match: + content = content[: match.end()] + tool_calls = [ToolCall.from_function("search", query=match[1])] + return Completion(message=ChatMessage.assistant(content, tool_calls=tool_calls)) + + def function_token_reserve(self, *args, **kwargs): + return 0 # wahoo we hardcode the prompt in the user message + + async def close(self): + return await self.engine.close() + + +class WikipediaKani(Kani): + def __init__(self, *args, question: str, **kwargs): + super().__init__(*args, **kwargs) + self.question = question + self.max_search_tokens = self.engine.max_context_size // 2 + + @ai_function() + def search(self, query: str): + """Search Wikipedia for an article with the given title, and get its content. If no such article is found, return similar article names.""" + try: + page = DatedPage(wikipedia, title=query, redirect=True, preload=False) + except mediawiki.PageError: + similar = wikipedia.search(query) + similar_searches = "\n".join(f"{title}" for title in similar) + return f"No page with that title exists. Try one of the similar searches:\n{similar_searches}" + except mediawiki.DisambiguationError as e: + similar_searches = "\n".join(f"{title}" for title in e.options) + return f"That may refer to multiple pages. Select one of these pages:\n{similar_searches}" + else: + corpus = Corpus([{"title": page.title, "pageid": page.pageid}], doc_len=1024) + # retrieve as many fragments as fit in the context window + retrieved_docs = [] + prompt = f"\n{page.title}\n{{}}" + for doc in corpus.best(self.question): + formatted = f"\n{doc.content}\n\n" + content = prompt.format("".join(retrieved_docs) + formatted) + doc_len = self.engine.message_len(ChatMessage.user(content)) + if doc_len > self.max_search_tokens: + break + retrieved_docs.append(formatted) + return prompt.format("".join(retrieved_docs)) + + +# ==== main ==== +async def main(): + args = parser.parse_args() + if args.model.startswith("gpt"): + runner = OpenAIOpenBookRunner(bench_setting="openbook", **argstokwargs(args)) + else: + runner = OpenBookRunner(bench_setting="openbook", **argstokwargs(args)) + runner.engine = SearchEngine(runner.engine, strict=True) + runner.load() + await runner.run(kani_cls=WikipediaKani) + await runner.close() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + asyncio.run(main()) diff --git a/reference/paper_benchmarks/run_wiki_provided.py b/reference/paper_benchmarks/run_wiki_provided.py new file mode 100644 index 0000000..5cf0113 --- /dev/null +++ b/reference/paper_benchmarks/run_wiki_provided.py @@ -0,0 +1,56 @@ +import asyncio +import logging + +from kani import ChatMessage + +from bench.runner import Runner, TOKEN_RESERVE, argstokwargs, parser +from fanout.retrieval import Corpus + + +class WikiProvidedRunner(Runner): + def load(self, max_prompt_tokens): + self.questions = self.get_questions() + self.prompts = self.get_prompts(self.questions, max_prompt_tokens) + + # noinspection PyMethodOverriding + def get_prompt(self, question, max_prompt_tokens) -> str: + question_text = question["question"] + + # index all of the documents, splitting by paragraph/sentence to a max of 1024 characters + corpus = Corpus(question["necessary_evidence"], doc_len=1024) + + # build the initial prompt + prompt = ( + "*** BEGIN DATA ***\n\n{}\n*** END DATA ***\n\nAnswer the following question based on the documents" + " above, and output only your answer. If the answer is a list, output one on each line. Current date:" + f" 11-20-2023.\n\n[Question]: {question_text}" + ) + + # retrieve as many fragments as fit in the context window + # format: \n{title}\n{content}\n + retrieved_docs = [] + for doc in corpus.best(question_text): + formatted = f"\n{doc.title}\n{doc.content}\n\n" + content = prompt.format("".join(retrieved_docs) + formatted) + doc_len = self.engine.message_len(ChatMessage.user(content)) + if doc_len > max_prompt_tokens: + break + retrieved_docs.append(formatted) + prompt = prompt.format("".join(retrieved_docs)) + return prompt + + +async def main(): + args = parser.parse_args() + runner = WikiProvidedRunner(bench_setting="wiki-provided", **argstokwargs(args)) + max_prompt_tokens = runner.engine.max_context_size - TOKEN_RESERVE + print(f"Building prompts for input length {max_prompt_tokens}...") + runner.load(max_prompt_tokens) + print("Generating...") + await runner.run() + await runner.close() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + asyncio.run(main()) diff --git a/reference/paper_benchmarks/score.py b/reference/paper_benchmarks/score.py new file mode 100644 index 0000000..264548c --- /dev/null +++ b/reference/paper_benchmarks/score.py @@ -0,0 +1,245 @@ +import asyncio +import hashlib +import json +import re +from pathlib import Path + +import rouge_score.scoring +import tqdm +from bleurt.score import BleurtScorer +from kani import Kani +from kani.engines.openai import OpenAIEngine +from rouge_score.rouge_scorer import RougeScorer + +from bench.score import answer_in_text, str_answer +from bench.utils import REPO_ROOT, load_questions, load_results +from fanout.utils import batched + +rouge_types = ["rouge1", "rouge2", "rougeL", "rougeLsum"] +# ===== llm ===== +LLM_CACHE_DIR = REPO_ROOT / ".llmcache" +LLM_CACHE_DIR.mkdir(exist_ok=True) +engine = OpenAIEngine(model="gpt-4-0613", temperature=0, seed=31415) +factuality_system = "You are comparing a submitted answer to an expert answer on a given question." + + +def factuality_prompt_old(question, reference, answer): + return ( + f"[BEGIN DATA]\n************\n[Question]: {question}\n************\n[Expert]:" + f" {reference}\n************\n[Submission]: {answer}\n************\n[END DATA]\n\nCompare the factual content" + " of the submitted answer with the expert answer. Ignore any differences in style, grammar, or" + " punctuation.\nThe submitted answer may either be a subset or superset of the expert answer, or it may" + " conflict with it. Determine which case applies. First, write out in a step by step manner your reasoning" + " about the factual content to be sure that your conclusion is correct. Avoid simply stating the correct" + ' answers at the outset. Then print only the single character "A", "B", "C", "D", or "E" (without quotes' + " or punctuation) on its own line corresponding to the correct answer. At the end, repeat just the letter" + " again by itself on a new line.\n(A) The submitted answer is a subset of the expert answer and is fully" + " consistent with it.\n(B) The submitted answer is a superset of the expert answer and is fully consistent" + " with it.\n(C) The submitted answer contains all the same details as the expert answer.\n(D) There is a" + " disagreement between the submitted answer and the expert answer.\n(E) The answers differ, but these" + " differences don't matter from the perspective of factuality." + ) + + +def factuality_prompt(question, reference, answer): + return ( + f"[BEGIN DATA]\n************\n[Question]: {question}\n************\n[Expert]:" + f" {reference}\n************\n[Submission]: {answer}\n************\n[END DATA]\n\nCompare the factual content" + " of the submitted answer with the expert answer. Ignore any differences in style, grammar, or" + " punctuation.\nThe submitted answer may either be a subset or superset of the expert answer, or it may" + " conflict with it. Determine which case applies. First, write out in a step by step manner your reasoning" + " about the factual content to be sure that your conclusion is correct. Avoid simply stating the correct" + ' answers at the outset. Then print only the single character "A", "B", "C", "D", "E", or "F" (without quotes' + " or punctuation) on its own line corresponding to the correct answer. At the end, repeat just the letter" + " again by itself on a new line.\n(A) The submitted answer is a subset of the expert answer and is fully" + " consistent with it.\n(B) The submitted answer is a superset of the expert answer and is fully consistent" + " with it.\n(C) The submitted answer contains all the same details as the expert answer.\n(D) There is a" + " disagreement between the submitted answer and the expert answer.\n(E) The answers differ, but these" + " differences don't matter from the perspective of factuality.\n(F) The submitted answer does not answer the" + " question or is otherwise invalid." + ) + + +class Scorer: + def __init__(self, results, key, questions=None, only_score_answered=False): + self.key = key + self.only_score_answered = only_score_answered + self.questions = questions or load_questions() + self.questions_by_id = {q["id"]: q for q in self.questions} + self.results = results + self.results_by_id = {r["id"]: r for r in self.results} + self.rouge = RougeScorer(rouge_types, use_stemmer=True) + self.bleurt = BleurtScorer("BLEURT-20") + # number of trials to eval + if self.only_score_answered: + self.eval_len = len(self.results) + else: + self.eval_len = len(self.questions) + + @classmethod + def from_fp(cls, fp: Path, questions): + results = load_results(fp) + return cls(results=results, key=fp.stem.removeprefix("results-"), questions=questions) + + async def score(self): + acc = self.score_accuracy() + rouge = self.score_rouge() + bleurt_ = self.score_bleurt() + gptscore = await self.score_gpt() + data = {"acc": acc, "rouge": rouge, "bleurt": bleurt_, "gpt": gptscore} + with open(REPO_ROOT / f"results/score-{self.key}.json", "w") as f: + json.dump(data, f, indent=2) + + def get_qa_pairs(self): + if self.only_score_answered: + for a in tqdm.tqdm(self.results): + q = self.questions_by_id.get(a["id"]) + yield q, a + else: + for q in tqdm.tqdm(self.questions): + a = self.results_by_id.get(q["id"]) + if a is None: + yield None, None + yield q, a + + def score_accuracy(self): + accs = [] + n_perfect = 0 + for q, a in self.get_qa_pairs(): + if a is None: + accs.append(0) + continue + result = answer_in_text(q["answer"], a["answer"]) + accs.append(result.score) + if result.found: + n_perfect += 1 + + assert len(accs) == self.eval_len + avg_acc = sum(accs) / self.eval_len + pct_perfect = n_perfect / self.eval_len + print(f"AVG ACC: {avg_acc}") + print(f"PCT PFT: {pct_perfect}") + return {"acc": avg_acc, "perfect": pct_perfect} + + def score_rouge(self): + scores = {t: [] for t in rouge_types} + for q, a in self.get_qa_pairs(): + if a is None: + for score in scores.values(): + score.append(rouge_score.scoring.Score(0, 0, 0)) + continue + results = self.rouge.score(str_answer(q["answer"]), str_answer(a["answer"])) + for k, v in results.items(): + scores[k].append(v) + + assert all(len(v) == self.eval_len for v in scores.values()) + print("=== ROUGE ===") + out = {} + for k, v in scores.items(): + print(f"--- {k} ---") + avg_precision = sum(s.precision for s in v) / self.eval_len + avg_recall = sum(s.recall for s in v) / self.eval_len + avg_fscore = sum(s.fmeasure for s in v) / self.eval_len + print(f"precision: {avg_precision}") + print(f"recall: {avg_recall}") + print(f"fscore: {avg_fscore}") + out[k] = {"precision": avg_precision, "recall": avg_recall, "fscore": avg_fscore} + print() + return out + + def score_bleurt(self): + references = [] + candidates = [] + for q, a in self.get_qa_pairs(): + if a is None: + candidates.append("") + else: + candidates.append(str_answer(a["answer"])) + references.append(str_answer(q["answer"])) + + scores = self.bleurt.score(references=references, candidates=candidates) + assert len(scores) == self.eval_len + avg_score = sum(scores) / self.eval_len + print(f"BLEURT: {avg_score}") + return avg_score + + async def score_gpt(self): + accs = [] + + for pairs in batched(self.get_qa_pairs(), 20): + # eval 20 qs at a time + coros = [] + for q, a in pairs: + if a is None: + accs.append(0) + continue + # sometimes we have fun neural text degeneration, just cut it off + ans = a["answer"][:4000] + coro = self.get_llm_factuality(q, ans) + coros.append(coro) + + # and score their answers + # B, C, E = full score, anything else = 0 + answers = await asyncio.gather(*coros) + for result in answers: + mc = result.strip()[-1].lower() + if mc in "bce": + accs.append(1) + else: + accs.append(0) + + assert len(accs) == self.eval_len + avg_acc = sum(accs) / self.eval_len + print(f"GPT ACC: {avg_acc}") + return avg_acc + + async def get_llm_factuality(self, question, answer): + # cache + ans_hash = hashlib.sha256(answer.encode()).hexdigest()[:8] + cache_filename = LLM_CACHE_DIR / f"factual-{self.key}-{question['id']}-{ans_hash}.txt" + if cache_filename.exists(): + return cache_filename.read_text() + + # ask the LLM if it is subjective + prompt = factuality_prompt(question["question"], str_answer(question["answer"]), answer) + ai = Kani(engine, system_prompt=factuality_system) + resp = await ai.chat_round_str(prompt) + cache_filename.write_text(resp) + return resp + + +async def score_human(): + questions = load_questions(REPO_ROOT / "fanout-final-test-answers.json") + + # read the human responses and {id, answer} them + results = [] + with open(REPO_ROOT / "human_responses.txt") as f: + data = f.read() + for segment in data.split("###SEP "): + segment = segment.strip() + if not segment: + continue + # fix some weird tokenization stuff in the human responses + segment = segment.replace("https://en.wikipedia.org/wiki/", "").replace("_", " ") + + id_, content = segment.split("\n", 1) + results.append({"id": id_, "answer": content}) + + scorer = Scorer(results, key="human", questions=questions, only_score_answered=True) + await scorer.score() + + +async def main(fps=None): + if fps is None: + fps = (REPO_ROOT / "results").glob("results-*.jsonl") + questions = load_questions(REPO_ROOT / "fanout-final-test-answers.json") + for result_path in fps: + print(result_path.stem) + scorer = Scorer.from_fp(result_path, questions) + await scorer.score() + + +if __name__ == "__main__": + # fps = [Path(a) for a in sys.argv[1:]] + # asyncio.run(main(fps)) + asyncio.run(score_human()) diff --git a/reference/paper_benchmarks/scores_to_csv.py b/reference/paper_benchmarks/scores_to_csv.py new file mode 100644 index 0000000..7a9d414 --- /dev/null +++ b/reference/paper_benchmarks/scores_to_csv.py @@ -0,0 +1,35 @@ +"""Read all the scores and output them as CSV so I don't have to type them all.""" + +import json + +from bench.utils import REPO_ROOT + +settings = ["closedbook", "openbook", "wiki-provided"] +models = ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo", "llama-chat", "mistral-chat", "mixtral", "claude"] + + +def print_one(fp): + with open(fp) as f: + scores = json.load(f) + acc = scores["acc"]["acc"] + perf = scores["acc"]["perfect"] + r1p = scores["rouge"]["rouge1"]["precision"] + r1r = scores["rouge"]["rouge1"]["recall"] + r1f = scores["rouge"]["rouge1"]["fscore"] + r2p = scores["rouge"]["rouge2"]["precision"] + r2r = scores["rouge"]["rouge2"]["recall"] + r2f = scores["rouge"]["rouge2"]["fscore"] + rLp = scores["rouge"]["rougeL"]["precision"] + rLr = scores["rouge"]["rougeL"]["recall"] + rLf = scores["rouge"]["rougeL"]["fscore"] + bleurt = scores["bleurt"] + gptscore = scores["gpt"] + print(",".join(map(str, (acc, perf, r1p, r1r, r1f, r2p, r2r, r2f, rLp, rLr, rLf, bleurt, gptscore)))) + + +for setting in settings: + print(f"==== {setting} ====") + for model in models: + result_path = REPO_ROOT / f"results/score-{setting}-{model}.json" + print(f"{model},", end="") + print_one(result_path) diff --git a/reference/paper_benchmarks/scores_to_web_fmt.py b/reference/paper_benchmarks/scores_to_web_fmt.py new file mode 100644 index 0000000..1cc8149 --- /dev/null +++ b/reference/paper_benchmarks/scores_to_web_fmt.py @@ -0,0 +1,80 @@ +"""Read all the scores and output them as CSV so I don't have to type them all.""" + +import json + +from bench.utils import REPO_ROOT + +settings = ["closedbook", "openbook", "wiki-provided"] +model_info = { + "gpt-4": { + "name": "GPT-4", + "authors": "OpenAI", + "url": "https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo", + "citation": "OpenAI, 2023", + "type": "FOUNDATION", + "context": 8192, + }, + "gpt-4-turbo": { + "name": "GPT-4-turbo", + "authors": "OpenAI", + "url": "https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo", + "citation": "OpenAI, 2023", + "type": "FOUNDATION", + "context": 128000, + }, + "gpt-3.5-turbo": { + "name": "GPT-3.5-turbo", + "authors": "OpenAI", + "url": "https://platform.openai.com/docs/models/gpt-3-5-turbo", + "citation": "OpenAI, 2023", + "type": "FOUNDATION", + "context": 16384, + }, + "llama-chat": { + "name": "LLaMA 2 70B", + "authors": "Meta", + "url": "https://ai.meta.com/research/publications/llama-2-open-foundation-and-fine-tuned-chat-models/", + "citation": "Touvron et al., 2023", + "type": "FOUNDATION", + "context": 4096, + }, + "mistral-chat": { + "name": "Mistral-7B", + "authors": "Mistral AI", + "url": "https://mistral.ai/news/announcing-mistral-7b/", + "citation": "Jiang et al., 2023", + "type": "FOUNDATION", + "context": 32000, + }, + "mixtral": { + "name": "Mixtral-8x7B", + "authors": "Mistral AI", + "url": "https://mistral.ai/news/mixtral-of-experts/", + "citation": "Jiang et al., 2024", + "type": "FOUNDATION", + "context": 32000, + }, + "claude": { + "name": "Claude 2.1", + "authors": "Anthropic", + "url": "https://www.anthropic.com/news/claude-2-1", + "citation": "Anthropic, 2023", + "type": "FOUNDATION", + "context": 200000, + }, +} + +for setting in settings: + results = [] + for model, info in model_info.items(): + result_path = REPO_ROOT / f"results/score-{setting}-{model}.json" + with open(result_path) as f: + scores = json.load(f) + scores["rouge"].pop("rougeLsum", None) + scores["acc"]["loose"] = scores["acc"].pop("acc") + scores["acc"]["strict"] = scores["acc"].pop("perfect") + scores.update(info) + results.append(scores) + + with open(REPO_ROOT / f"results/web-{setting}.json", "w") as f: + json.dump(results, f) diff --git a/reference/paper_benchmarks/validate_results.py b/reference/paper_benchmarks/validate_results.py new file mode 100644 index 0000000..c05d128 --- /dev/null +++ b/reference/paper_benchmarks/validate_results.py @@ -0,0 +1,73 @@ +""" +Ensure that each result has all of the answers for the test set. +Write the IDs of the missing configurations to config/{model}-{setting}.txt, one per line +""" + +import json +from pathlib import Path + +from bench.utils import REPO_ROOT, load_questions, load_results + + +def fix_one(fp: Path): + fn = fp.stem + prompt_file = open(REPO_ROOT / f"results/prompts-{fn}.json", "w") + results_file = open(REPO_ROOT / f"results/results-{fn}.jsonl", "w") + extras_file = open(REPO_ROOT / f"results/extra-{fn}.jsonl", "w") + + with open(fp) as f: + data = json.load(f) + + prompts = [] + for result in data: + smol = {"id": result["id"], "answer": result["answer"], "question": result["question"]} + results_file.write(json.dumps(smol)) + results_file.write("\n") + # extras + extras_file.write(json.dumps(result)) + extras_file.write("\n") + # prompts + prompts.append({"id": result["id"], "prompt": result["prompt"]}) + + json.dump(prompts, prompt_file) + + prompt_file.close() + results_file.close() + extras_file.close() + + +def validate(questions, results) -> list[str]: + """Given the questions and results, output a list of missing IDs""" + question_ids_list = [q["id"] for q in questions] + question_ids = set(q["id"] for q in questions) + result_ids = set(r["id"] for r in results if r["answer"]) + return sorted(question_ids.difference(result_ids), key=lambda i: question_ids_list.index(i)) + + +def validate_one(questions, fp): + fn = fp.stem.removeprefix("results-") + results = load_results(fp) + missing = validate(questions, results) + with open(REPO_ROOT / f"config/{fn}.txt", "w") as f: + f.write("\n".join(missing)) + print(f"{fp}: {len(questions) - len(missing)} / {len(questions)}") + # remove the ones that errored + with open(fp, "w") as f: + for r in results: + if not r["answer"]: + continue + f.write(json.dumps(r)) + f.write("\n") + + +def main(): + questions = load_questions() + # for result_path in (REPO_ROOT / "results-old").glob("*.json"): + # fix_one(result_path) + + for result_path in (REPO_ROOT / "results").glob("results-*.jsonl"): + validate_one(questions, result_path) + + +if __name__ == "__main__": + main() diff --git a/reference/paper_benchmarks/viz-scores.ipynb b/reference/paper_benchmarks/viz-scores.ipynb new file mode 100644 index 0000000..43e250a --- /dev/null +++ b/reference/paper_benchmarks/viz-scores.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "code", + "outputs": [], + "source": [ + "# load the data\n", + "import json\n", + "\n", + "from bench.utils import REPO_ROOT\n", + "\n", + "settings = [\"closedbook\", \"openbook\", \"wiki-provided\"]\n", + "setting_names = [\"Closed Book\", \"Open Book\", \"Evidence Provided\"]\n", + "models = [\"llama-chat\", \"gpt-4\", \"gpt-3.5-turbo\", \"mistral-chat\", \"mixtral\", \"gpt-4-turbo\", \"claude\"]\n", + "model_names = [\"LLaMA 2\", \"GPT-4\", \"GPT-3.5-turbo\", \"Mistral-7B\", \"Mixtral-8x7B\", \"GPT-4-turbo\", \"Claude 2.1\"]\n", + "\n", + "results_loose = {k: [] for k in setting_names}\n", + "results_model = {k: [] for k in setting_names}\n", + "\n", + "# def print_one(fp):\n", + "# with open(fp) as f:\n", + "# scores = json.load(f)\n", + "# acc = scores[\"acc\"][\"acc\"]\n", + "# perf = scores[\"acc\"][\"perfect\"]\n", + "# r1p = scores[\"rouge\"][\"rouge1\"][\"precision\"]\n", + "# r1r = scores[\"rouge\"][\"rouge1\"][\"recall\"]\n", + "# r1f = scores[\"rouge\"][\"rouge1\"][\"fscore\"]\n", + "# r2p = scores[\"rouge\"][\"rouge2\"][\"precision\"]\n", + "# r2r = scores[\"rouge\"][\"rouge2\"][\"recall\"]\n", + "# r2f = scores[\"rouge\"][\"rouge2\"][\"fscore\"]\n", + "# rLp = scores[\"rouge\"][\"rougeL\"][\"precision\"]\n", + "# rLr = scores[\"rouge\"][\"rougeL\"][\"recall\"]\n", + "# rLf = scores[\"rouge\"][\"rougeL\"][\"fscore\"]\n", + "# bleurt = scores[\"bleurt\"]\n", + "# gptscore = scores[\"gpt\"]\n", + "# print(\",\".join(map(str, (acc, perf, r1p, r1r, r1f, r2p, r2r, r2f, rLp, rLr, rLf, bleurt, gptscore))))\n", + "\n", + "\n", + "for setting, setting_name in zip(settings, setting_names):\n", + " for model, model_name in zip(models, model_names):\n", + " result_path = REPO_ROOT / f\"results/score-{setting}-{model}.json\"\n", + " with open(result_path) as f:\n", + " scores = json.load(f)\n", + " loose = scores[\"acc\"][\"acc\"]\n", + " results_loose[setting_name].append(loose)\n", + " results_model[setting_name].append(scores[\"gpt\"])\n" + ], + "metadata": { + "collapsed": false + }, + "id": "53c8b7b5d53696f" + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ], + "metadata": { + "collapsed": false + }, + "id": "bc8afd1110f9c495" + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "# liam code\n", + "def show_values(axs, orient=\"v\", space=.01, label_thresh=0):\n", + " def _single(ax):\n", + " if orient == \"v\":\n", + " for p in ax.patches:\n", + " _x = p.get_x() + p.get_width() / 2\n", + " _y = p.get_y() + p.get_height() + (p.get_height()*0.01)\n", + " value = '{:.1f}'.format(p.get_height()*100)\n", + " if p.get_height() >= label_thresh:\n", + " ax.text(_x, _y, value, ha=\"center\", fontsize=11) #, rotation=40)\n", + "\n", + " if isinstance(axs, np.ndarray):\n", + " for idx, ax in np.ndenumerate(axs):\n", + " _single(ax)\n", + " else:\n", + " _single(axs)\n", + "\n", + "# # Put the intended figsize here\n", + "# fig, ax = plt.subplots(figsize=(5,2.8))\n", + "# \n", + "# # Put your dataframe here \n", + "# sns.barplot(ax=ax, data=df, y='accuracy', x='model', hue='chat')\n", + "# \n", + "# # Can customize legend here\n", + "# ax.legend(loc='upper right', ncol=2, fontsize=12, columnspacing=0.5, labelspacing=0.3, handlelength=1.5, handletextpad=0.4, fancybox=False)\n", + "\n", + "\n", + "# # Set size of text and other things\n", + "# ax.xaxis.set_tick_params(labelsize=14)\n", + "# \n", + "# # Set no printing of axis label and set y limits\n", + "# ax.set(xlabel=None)\n", + "# ax.set(ylim=(0.0, 0.6))" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-02-15T17:11:49.221278Z", + "start_time": "2024-02-15T17:11:49.207232Z" + } + }, + "id": "78b8da145cbb5620", + "execution_count": 36 + }, + { + "cell_type": "code", + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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yJBbVqFCBURHR8stEwQBbdq0gbu7e471ffz4Ea1btwYAhIWFISUlBRMmTECfPn2wb98+7QZfyGmUgImMjET//v3x888/4/Xr1wCAihUrwt3dHS1atIC7uzuWL1+OlStXajVYIiIiIiIiIioYt2/fxvLlyxEcHAxfX1/ZcrFYjKZNm8qVPXHiBJKSktCnT58c64yMjMSNGzdw69YtVK9eHQBgbm6O1q1b48KFC2jcuLH2D6SQ0tFkoxcvXsj+kUqUKAEAeP/+vWx9t27dsGvXLi2ER0RERERERESfw6hRo+Dr6ytLlOQkLCwMpqam6NixY47lDhw4gLp168rV2bJlS5QuXRpRUVH5jrko0SgBY2lpKWv5YmRkBHNzc9y5c0e2PikpKdc+YERERERERERUOERGRuLatWvw8/PLtezHjx+xc+dOdOnSBYaGhjmWvX37NhwdHeWWiUQiODo64vbt2/mKuajRqAtSkyZNcPr0afz8888AgI4dOyIoKAgVKlSARCLB4sWLFZonEREREREREVHhk5KSgnHjxiEwMFCtgXEPHDiAN2/e5Nr9CAASEhJQqlQpheXm5uZ48+aNJuEWWRq1gBk9ejSqVKmCtLQ0AFmD9JQqVQo//vgjvL29YWZmhl9++UWrgRIRERERERGR9s2ZMweWlpbo37+/WuVDQ0NhaWkJDw+PAo6seNGoBcx3332H7777Tvba2toat27dwrVr16CrqwtHR0fo6WlUNRERERERERF9JvHx8QgODsbu3buRmJgIIGsqaul/3717h5IlS8rKv3v3Dnv37sXgwYOhq6uba/3m5uayerNLSEiAtbW1lo6iaMhzC5iUlBR07doVoaGh8hXp6MDJyQm1a9dm8oWIiIiIiIioCIiLi0N6ejrat28Pc3NzmJubywbWdXNzQ4sWLeTK7969Gx8+fFCr+xEApWO9CIKAO3fuKIwNU9zlOVNiZGSEI0eOoG3btgURDxERERERERF9JvXq1cPx48flll25cgVjx47FqlWr0KhRI7l1YWFhsLe3R5MmTdSqv23btti6dStiYmLg4OAAADh69Chev36Ndu3aaecgigiNxoD57rvvEB0dre1YiIiIiIiIiOgzKlWqFFxdXeX+6tWrBwBo2LAhGjRoICv78uVLHDlyBF5eXkrrio+Ph56eHmbNmiVb1r17d9SqVQvdunXD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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib as mpl\n", + "\n", + "x = np.arange(len(models)) # the label locations\n", + "width = 0.25 # the width of the bars\n", + "multiplier = 0\n", + "colors = mpl.colormaps[\"Set2\"].colors\n", + "\n", + "fig, axs = plt.subplots(2, 1, figsize=(11, 3.8))\n", + "\n", + "ax = axs[0]\n", + "for idx, (attribute, measurement) in enumerate(results_loose.items()):\n", + " offset = width * multiplier\n", + " rects = ax.bar(x + offset, measurement, width, label=attribute, color=colors[idx])\n", + " # ax.bar_label(rects, padding=3)\n", + " multiplier += 1\n", + "\n", + "# Add some text for labels, title and custom x-axis tick labels, etc.\n", + "ax.set_ylabel('Loose Accuracy', fontsize=12)\n", + "# ax.set_title('Accuracy by Model and Task')\n", + "ax.set_xticks(x + width, model_names, fontsize=12)\n", + "\n", + "# human perfomance\n", + "ax.axhline(y=0.6853, label=\"Human (Open Book)\", color=colors[1], linestyle=\"--\")\n", + "ax.set_ylim(0.1, 0.8)\n", + "\n", + "# ax.legend(loc='center right', bbox_to_anchor=(1.27, 0.6), fontsize=11)\n", + "ax.legend(loc='upper left', ncols=4, fontsize=11)\n", + "\n", + "# Call cursed function\n", + "show_values(ax, label_thresh=0.1)\n", + "\n", + "# ===== do it again =====\n", + "width = 0.25 # the width of the bars\n", + "multiplier = 0\n", + "ax = axs[1]\n", + "for idx, (attribute, measurement) in enumerate(results_model.items()):\n", + " offset = width * multiplier\n", + " rects = ax.bar(x + offset, measurement, width, label=attribute, color=colors[idx])\n", + " # ax.bar_label(rects, padding=3)\n", + " multiplier += 1\n", + "\n", + "# Add some text for labels, title and custom x-axis tick labels, etc.\n", + "ax.set_ylabel('Model Judge Accuracy', fontsize=12)\n", + "ax.set_xticks(x + width, model_names, fontsize=12)\n", + "ax.set_ylim(0, 0.55)\n", + "\n", + "# human perfomance\n", + "ax.axhline(y=0.4519, label=\"Human (Open Book)\", color=colors[1], linestyle=\"--\")\n", + "\n", + "# Call cursed function\n", + "show_values(ax, label_thresh=0.015)\n", + "\n", + "plt.subplots_adjust(hspace=0.15)\n", + "\n", + "plt.tight_layout(pad=0)\n", + "plt.savefig(\"viz.pdf\")\n", + "plt.show()\n" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-02-15T17:41:25.092401Z", + "start_time": "2024-02-15T17:41:24.401128Z" + } + }, + "id": "c68dbc694aed2aca", + "execution_count": 48 + }, + { + "cell_type": "code", + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "cc24fd609a4b7ec8" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}