-
Notifications
You must be signed in to change notification settings - Fork 654
[WIP] Add RTEB retrieval code #2529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ff8be03
Merging RTEB code
fzowl e0f343e
Merging RTEB code
fzowl d595263
Merging ModelMeta
fzowl 82fe6fe
Merging ModelMeta
fzowl 005df9b
Merge pull request #1 from fzliu/merge_model_meta
fzliu 3f9f078
Further simplification (#3)
fzowl 7d00cb0
Corrections due to the tests
fzowl 6719514
Adding a new test
fzowl 3755e54
A few file path corrections
fzowl f81c439
Correcting the embedding file names (non in-mem case)
fzowl ee6581f
Merge branch 'embeddings-benchmark:main' into main
fzowl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import logging | ||
| import os | ||
| from collections import defaultdict | ||
| from pathlib import Path | ||
|
|
||
| import pytorch_lightning as pl | ||
| from ebr.core import Encoder, Retriever | ||
| from ebr.datasets import DATASET_REGISTRY, DatasetMeta | ||
| from ebr.models import MODEL_REGISTRY, ModelMeta | ||
| from ebr.retrieve import run_retrieve_task | ||
| from pytorch_lightning.strategies.ddp import DDPStrategy | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
| os.environ["TOKENIZERS_PARALLELISM"] = "false" | ||
|
|
||
|
|
||
| def get_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser() | ||
|
|
||
| # Evaluation | ||
| parser.add_argument( | ||
| "--gpus", type=int, default=0, help="Number of gpus used for encoding." | ||
| ) | ||
| parser.add_argument( | ||
| "--cpus", | ||
| type=int, | ||
| default=1, | ||
| help="Number of cpus used for computation (this is only for models that are not using gpus).", | ||
| ) | ||
| parser.add_argument("--bf16", action="store_true", help="`Use bf16 precision.") | ||
| parser.add_argument( | ||
| "--batch_size", type=int, default=16, help="Batch size for encoding." | ||
| ) | ||
| parser.add_argument( | ||
| "--embd_batch_size", | ||
| type=int, | ||
| default=1024, | ||
| help="Batch size for computing similarity of embeddings.", | ||
| ) | ||
| parser.add_argument( | ||
| "--embd_in_memory_threshold", | ||
| type=int, | ||
| default=200000, | ||
| help="Embeddings will be stored in memory if the amount is below this threshold.", | ||
| ) | ||
|
|
||
| # Model | ||
| # parser.add_argument( | ||
| # "--model_name", type=str, default=None, help="Model name or path.") | ||
| # parser.add_argument( | ||
| # "--embd_dtype", type=str, default="float", help="Embedding type. Options: float32, int8, binary.") | ||
| # parser.add_argument( | ||
| # "--embd_dim", type=int, default=None, help="Embedding dimension.") | ||
| # parser.add_argument( | ||
| # "--max_length", type=int, default=None, help="Maximum length of model input.") | ||
|
|
||
| # Data | ||
| parser.add_argument( | ||
| "--data_path", | ||
| type=str, | ||
| default="data/", | ||
| help="Path of the dataset, must be specified for custom tasks.", | ||
| ) | ||
| parser.add_argument( | ||
| "--task_name", | ||
| type=str, | ||
| default=None, | ||
| help="Name of the task. Can be multiple tasks splitted by `,`.", | ||
| ) | ||
| parser.add_argument( | ||
| "--data_type", | ||
| default="eval", | ||
| choices=["eval", "train", "chunk", "merge"], | ||
| help="Dataset type.", | ||
| ) | ||
| parser.add_argument( | ||
| "--num_workers", type=int, default=4, help="Number of workers for dataloader." | ||
| ) | ||
|
|
||
| # Output | ||
| parser.add_argument( | ||
| "--save_path", type=str, default="output/", help="Path to save the output." | ||
| ) | ||
| parser.add_argument( | ||
| "--save_embds", action="store_true", help="Whether to save the embeddings." | ||
| ) | ||
| parser.add_argument( | ||
| "--load_embds", | ||
| action="store_true", | ||
| help="Whether to load the computed embeddings.", | ||
| ) | ||
| parser.add_argument( | ||
| "--save_prediction", | ||
| action="store_true", | ||
| help="Whether to save the predictions.", | ||
| ) | ||
| parser.add_argument( | ||
| "--topk", type=int, default=100, help="Number of top documents per query." | ||
| ) | ||
| parser.add_argument( | ||
| "--overwrite", action="store_true", help="Whether to overwrite the results." | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
| return args | ||
|
|
||
|
|
||
| def _dump_model_meta( | ||
| results_dir: str = "results", | ||
| model_registry: dict[str, ModelMeta] = MODEL_REGISTRY, | ||
| ): | ||
| models = [meta.model_dump() for meta in model_registry.values()] | ||
| with open(Path(results_dir) / "models.json", "w") as f: | ||
| f.write(json.dumps(models, indent=4)) | ||
|
|
||
|
|
||
| def _dump_dataset_info( | ||
| results_dir: str = "results", | ||
| dataset_registry: dict[str, DatasetMeta] = DATASET_REGISTRY, | ||
| ): | ||
| group_data = defaultdict(list) | ||
| for dataset_meta in dataset_registry.values(): | ||
| for group_name in dataset_meta.groups.keys(): | ||
| leaderboard = dataset_meta.loader.LEADERBOARD | ||
| group_data[(leaderboard, group_name)].append(dataset_meta.dataset_name) | ||
|
|
||
| groups = [] | ||
| for (leaderboard, group_name), datasets in group_data.items(): | ||
| groups.append( | ||
| {"name": group_name, "datasets": datasets, "leaderboard": leaderboard} | ||
| ) | ||
| with open(Path(results_dir) / "datasets.json", "w") as f: | ||
| f.write(json.dumps(groups, indent=4)) | ||
|
|
||
|
|
||
| def _compile_results(results_dir: str = "results", output_dir: str = "output"): | ||
| results = [] | ||
| for dataset_output_dir in Path(output_dir).iterdir(): | ||
| dataset_results = [] | ||
| for one_result in dataset_output_dir.iterdir(): | ||
| eval_file = one_result / "retrieve_eval.json" | ||
| if eval_file.exists(): | ||
| with open(eval_file) as f: | ||
| dataset_results.append(json.load(f)) | ||
|
|
||
| results.append( | ||
| { | ||
| **DATASET_REGISTRY[dataset_output_dir.name].model_dump(), | ||
| "results": dataset_results, | ||
| "is_closed": DATASET_REGISTRY[dataset_output_dir.name].tier != 3, | ||
| } | ||
| ) | ||
|
|
||
| with open(Path(results_dir) / "results.json", "w") as f: | ||
| f.write(json.dumps(results, indent=4)) | ||
|
|
||
|
|
||
| def main(args: argparse.Namespace): | ||
| _dump_model_meta() | ||
| _dump_dataset_info() | ||
|
|
||
| if args.gpus: | ||
| trainer = pl.Trainer( | ||
| strategy=DDPStrategy(find_unused_parameters=False), | ||
| accelerator="gpu", | ||
| devices=args.gpus, | ||
| precision="bf16" if args.bf16 else "32", | ||
| ) | ||
| else: | ||
| trainer = pl.Trainer( | ||
| strategy=DDPStrategy(), | ||
| accelerator="cpu", | ||
| devices=args.cpus, | ||
| ) | ||
|
|
||
| if not trainer.is_global_zero: | ||
| logging.basicConfig(level=logging.ERROR) | ||
|
|
||
| # Evaluate each model on the specified datasets | ||
| for model_meta in MODEL_REGISTRY.values(): | ||
| encoder = Encoder( | ||
| model_meta.load_model(), | ||
| save_embds=args.save_embds, | ||
| load_embds=args.load_embds, | ||
| ) | ||
| retriever = Retriever( | ||
| topk=args.topk, | ||
| similarity=model_meta.similarity, | ||
| save_prediction=args.save_prediction, | ||
| ) | ||
|
|
||
| eval_results = {} | ||
| for dataset_meta in DATASET_REGISTRY.values(): | ||
| # if trainer.is_global_zero: | ||
| # trainer.print(f"Evaluating {model_meta.model_name} on {dataset_meta.dataset_name}") | ||
|
|
||
| result = run_retrieve_task(dataset_meta, trainer, encoder, retriever, args) | ||
| eval_results[dataset_meta.dataset_name] = result | ||
|
|
||
| metric = "ndcg_at_10" | ||
|
|
||
| # Print the results | ||
| if trainer.is_global_zero: | ||
| trainer.print("=" * 40) | ||
| trainer.print(args.save_path) | ||
| trainer.print("=" * 40) | ||
| for task in eval_results.keys(): | ||
| if metric in eval_results[task]: | ||
| trainer.print(f"{task:<32}{eval_results[task][metric]:.4f}") | ||
|
|
||
| _compile_results() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| args = get_args() | ||
| main(args) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from __future__ import annotations |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from __future__ import annotations |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from abc import ABC | ||
| from functools import cache | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from torch.utils.data import Dataset | ||
|
|
||
| if TYPE_CHECKING: | ||
| from ebr.core.meta import DatasetMeta | ||
|
|
||
|
|
||
| def add_instruct(dataset: Dataset, instruct: str, input_type: str): | ||
| for item in dataset.data: | ||
| if instruct: | ||
| item["text"] = instruct + item["text"] | ||
| item["input_type"] = input_type | ||
|
|
||
| return dataset | ||
|
|
||
|
|
||
| class RetrievalDataset(ABC): | ||
| LEADERBOARD: str = None | ||
|
|
||
| def __init__( | ||
| self, | ||
| data_path: str, | ||
| dataset_meta: DatasetMeta, | ||
| query_instruct: str | None = None, | ||
| corpus_instruct: str | None = None, | ||
| **kwargs, | ||
| ): | ||
| assert type(self).LEADERBOARD, "leaderboard must be defined" | ||
| super().__init__() | ||
| self._dataset_meta = dataset_meta | ||
| self._query_instruct = query_instruct | ||
| self._corpus_instruct = corpus_instruct | ||
| self._task_path = (Path(data_path) / dataset_meta.dataset_name).resolve() | ||
|
|
||
| # def __getattr__(self, name: str) -> Any: | ||
| # try: | ||
| # return super().__getattr__(name) | ||
| # except AttributeError: | ||
| # return getattr(self._dataset_meta, name) | ||
|
|
||
| @property | ||
| @cache | ||
| def corpus(self) -> Dataset: | ||
| corpus = self._corpus() | ||
| corpus = add_instruct(corpus, self._corpus_instruct, "document") | ||
| return corpus | ||
|
|
||
| def _corpus(self) -> Dataset: | ||
| raise NotImplementedError | ||
|
|
||
| @property | ||
| @cache | ||
| def queries(self) -> Dataset: | ||
| queries = self._queries() | ||
| queries = add_instruct(queries, self._query_instruct, "query") | ||
| return queries | ||
|
|
||
| def _queries(self) -> Dataset: | ||
| raise NotImplementedError | ||
|
|
||
| @property | ||
| @cache | ||
| def relevance(self) -> dict: | ||
| # Dict of dict: relevance[query_id][corpus_id] = score | ||
| pass | ||
|
|
||
| def prepare_data(self): | ||
| _ = self.corpus | ||
| _ = self.queries | ||
| _ = self.relevance |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this?