| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """ |
| | Run benchmark using the `optimum-benchmark` library with some customization in `transformers`. |
| | |
| | Assume we are under `transformers` root directory: (make sure the commits are valid commits) |
| | ```bash |
| | python benchmark/benchmark.py --config-dir benchmark/config --config-name generation --commit=9b9c7f03da625b13643e99205c691fe046461724 --metrics=decode.latency.mean,per_token.latency.mean,per_token.throughput.value backend.model=google/gemma-2b benchmark.input_shapes.sequence_length=5,7 benchmark.input_shapes.batch_size=1,2 --multirun |
| | ``` |
| | """ |
| |
|
| | import argparse |
| | import glob |
| | import json |
| | import os.path |
| | import re |
| | import tempfile |
| | from contextlib import contextmanager |
| | from pathlib import Path |
| |
|
| | from git import Repo |
| |
|
| | from huggingface_hub import HfApi |
| |
|
| | from optimum_benchmark import Benchmark |
| | from optimum_benchmark_wrapper import main |
| |
|
| |
|
| | PATH_TO_REPO = Path(__file__).parent.parent.resolve() |
| |
|
| |
|
| | @contextmanager |
| | def checkout_commit(repo: Repo, commit_id: str): |
| | """ |
| | Context manager that checks out a given commit when entered, but gets back to the reference it was at on exit. |
| | Args: |
| | repo (`git.Repo`): A git repository (for instance the Transformers repo). |
| | commit_id (`str`): The commit reference to checkout inside the context manager. |
| | """ |
| | current_head = repo.head.commit if repo.head.is_detached else repo.head.ref |
| |
|
| | try: |
| | repo.git.checkout(commit_id) |
| | yield |
| |
|
| | finally: |
| | repo.git.checkout(current_head) |
| |
|
| |
|
| | def summarize(run_dir, metrics, expand_metrics=False): |
| | """Produce a summary for each optimum-benchmark launched job's output directory found in `run_dir`. |
| | |
| | Each summary's format is as follows (for `expand_metrics=False`): |
| | ``` |
| | { |
| | "model": "google/gemma-2b", |
| | "commit": "3cd6ed22e4d49219f300f5055e71e3929aba20d7", |
| | "config": "benchmark.input_shapes.batch_size=1,benchmark.input_shapes.sequence_length=5", |
| | "metrics": { |
| | "decode.latency.mean": 1.624666809082031, |
| | "per_token.latency.mean": 0.012843788806628804, |
| | "per_token.throughput.value": 77.85864553330948 |
| | } |
| | } |
| | ``` |
| | """ |
| | reports = glob.glob(os.path.join(run_dir, "**/benchmark_report.json"), recursive=True) |
| | report_dirs = [str(Path(report).parent) for report in reports] |
| |
|
| | summaries = [] |
| | for report_dir in report_dirs: |
| | commit = re.search(r"/commit=([^/]+)", report_dir).groups()[0] |
| |
|
| | if not os.path.isfile(os.path.join(report_dir, "benchmark.json")): |
| | continue |
| | benchmark = Benchmark.from_json(os.path.join(report_dir, "benchmark.json")) |
| | report = benchmark.report |
| |
|
| | model = benchmark.config.backend["model"] |
| |
|
| | |
| | |
| | benchmark_name = re.sub(f"backend.model={model},*", "", report_dir) |
| | benchmark_name = str(Path(benchmark_name).parts[-1]) |
| | if benchmark_name.startswith("commit="): |
| | benchmark_name = benchmark.config.name |
| |
|
| | metrics_values = {} |
| | |
| | for metric in metrics: |
| | keys = metric.split(".") |
| | value = report.to_dict() |
| | current = metrics_values |
| | for key in keys: |
| | |
| | |
| | if key not in value: |
| | continue |
| | value = value[key] |
| |
|
| | if expand_metrics: |
| | if isinstance(value, dict): |
| | if key not in current: |
| | current[key] = {} |
| | current = current[key] |
| | else: |
| | current[key] = value |
| |
|
| | if not expand_metrics: |
| | metrics_values[metric] = value |
| |
|
| | |
| | print(f"model: {model}") |
| | print(f"commit: {commit}") |
| | print(f"config: {benchmark_name}") |
| | if len(metrics_values) > 0: |
| | print("metrics:") |
| | if expand_metrics: |
| | print(metrics_values) |
| | else: |
| | for metric, value in metrics_values.items(): |
| | print(f" - {metric}: {value}") |
| | print("-" * 80) |
| |
|
| | summary = { |
| | "model": model, |
| | "commit": commit, |
| | "config": benchmark_name, |
| | "metrics": metrics_values, |
| | } |
| | summaries.append(summary) |
| |
|
| | with open(os.path.join(report_dir, "summary.json"), "w") as fp: |
| | json.dump(summary, fp, indent=4) |
| |
|
| | return summaries |
| |
|
| |
|
| | def combine_summaries(summaries): |
| | """Combine a list of summary obtained from the function `summarize`. |
| | |
| | The combined summary's format is as follows: |
| | ``` |
| | "google/gemma-2b": { |
| | "benchmark.input_shapes.batch_size=1,benchmark.input_shapes.sequence_length=5": { |
| | "3cd6ed22e4d49219f300f5055e71e3929aba20d7": { |
| | "metrics": {"decode.latency.mean": 1.624666809082031} |
| | }, |
| | "c97ee28b117c0abe8e08891f402065e4df6d72aa": { |
| | "metrics": {"decode.latency.mean": 1.6278163452148438} |
| | } |
| | }, |
| | "benchmark.input_shapes.batch_size=2,benchmark.input_shapes.sequence_length=5": { |
| | "3cd6ed22e4d49219f300f5055e71e3929aba20d7": { |
| | "metrics": {"decode.latency.mean": 1.6947791748046876} |
| | }, |
| | "c97ee28b117c0abe8e08891f402065e4df6d72aa": { |
| | "metrics": { |
| | "decode.latency.mean": 1.6980519409179688} |
| | } |
| | } |
| | } |
| | ``` |
| | """ |
| | combined = {} |
| | for summary in summaries: |
| | model = summary["model"] |
| | config = summary["config"] |
| | commit = summary["commit"] |
| |
|
| | if model not in combined: |
| | combined[model] = {} |
| |
|
| | if config not in combined[model]: |
| | combined[model][config] = {} |
| |
|
| | if commit not in combined[model][config]: |
| | combined[model][config][commit] = {"metrics": summary["metrics"]} |
| |
|
| | with open(os.path.join(exp_run_dir, "summary.json"), "w") as fp: |
| | json.dump(combined, fp, indent=4) |
| |
|
| | print(json.dumps(combined, indent=4)) |
| |
|
| | return combined |
| |
|
| |
|
| | if __name__ == "__main__": |
| |
|
| | def list_str(values): |
| | return values.split(",") |
| |
|
| | parser = argparse.ArgumentParser() |
| |
|
| | parser.add_argument("--config-dir", type=str, required=True, help="The path to the config directory.") |
| | parser.add_argument("--config-name", type=str, required=True, help="The config name.") |
| |
|
| | |
| | parser.add_argument("--ensure_empty", type=bool, default=True, help="If to create a temporary directory.") |
| | parser.add_argument( |
| | "--commit", |
| | type=list_str, |
| | default="", |
| | help="Comma-separated list of branch names and/or commit sha values on which the benchmark will run. If `diff` is specified, it will run on both the current head and the `main` branch.", |
| | ) |
| | parser.add_argument("--metrics", type=str, help="The metrics to be included in the summary.") |
| |
|
| | parser.add_argument("--repo_id", type=str, default=None, help="The repository to which the file will be uploaded.") |
| | parser.add_argument("--path_in_repo", type=str, default=None, help="Relative filepath in the repo.") |
| | parser.add_argument("--token", type=str, default=None, help="A valid user access token (string).") |
| |
|
| | args, optimum_benchmark_args = parser.parse_known_args() |
| |
|
| | repo = Repo(PATH_TO_REPO) |
| |
|
| | metrics = [ |
| | "prefill.latency.mean", |
| | "prefill.throughput.value", |
| | "decode.latency.mean", |
| | "decode.throughput.value", |
| | "per_token.latency.mean", |
| | "per_token.throughput.value", |
| | ] |
| | if args.metrics is not None: |
| | metrics = args.metrics.split(",") |
| |
|
| | |
| | models = [""] |
| | for idx, arg in enumerate(optimum_benchmark_args): |
| | if arg.startswith("backend.model="): |
| | models = arg[len("backend.model=") :] |
| | models = models.split(",") |
| | break |
| | optimum_benchmark_args = [arg for arg in optimum_benchmark_args if not arg.startswith("backend.model=")] |
| |
|
| | |
| | current_head = str(repo.head.commit) if repo.head.is_detached else str(repo.head.ref) |
| | commits = [x for x in args.commit if x != ""] |
| | if len(commits) == 0: |
| | commits = [current_head] |
| | elif len(commits) == 1 and commits[0] == "diff": |
| | |
| | commits = ["main", current_head] |
| |
|
| | |
| | run_dir_arg_idx, run_dir = -1, None |
| | sweep_dir_arg_idx, sweep_dir = -1, None |
| | for idx, arg in enumerate(optimum_benchmark_args): |
| | if arg.startswith("hydra.run.dir="): |
| | run_dir = arg[len("hydra.run.dir=") :] |
| | run_dir_arg_idx = idx |
| | elif arg.startswith("hydra.sweep.dir="): |
| | sweep_dir = arg[len("hydra.sweep.dir=") :] |
| | sweep_dir_arg_idx = idx |
| | exp_run_dir, arg_dix, arg_name = ( |
| | (sweep_dir, sweep_dir_arg_idx, "hydra.sweep.dir") |
| | if "--multirun" in optimum_benchmark_args |
| | else (run_dir, run_dir_arg_idx, "hydra.run.dir") |
| | ) |
| |
|
| | |
| | if exp_run_dir is None and args.ensure_empty: |
| | exp_run_dir = "_benchmark" |
| |
|
| | if args.ensure_empty: |
| | os.makedirs(exp_run_dir, exist_ok=True) |
| | exp_run_dir = tempfile.mkdtemp(dir=exp_run_dir) |
| |
|
| | run_summaries = [] |
| | for commit in commits: |
| | with checkout_commit(repo, commit): |
| | commit = str(repo.head.commit) |
| |
|
| | commit_run_dir = exp_run_dir |
| | if exp_run_dir is not None: |
| | commit_run_dir = os.path.join(exp_run_dir, rf"commit\={commit}") |
| |
|
| | print(f"Run benchmark on commit: {commit}") |
| |
|
| | for model in models: |
| | model_arg = [f"backend.model={model}"] if model != "" else [] |
| | dir_args = [] |
| | if commit_run_dir is not None: |
| | if arg_dix > -1: |
| | optimum_benchmark_args[arg_dix] = f"{arg_name}={commit_run_dir}" |
| | else: |
| | dir_args = [ |
| | f"hydra.sweep.dir={commit_run_dir}", |
| | f"hydra.run.dir={commit_run_dir}/" + "${hydra.job.override_dirname}", |
| | ] |
| | main(args.config_dir, args.config_name, model_arg + dir_args + optimum_benchmark_args) |
| |
|
| | if commit_run_dir is not None: |
| | |
| | summaries = summarize(commit_run_dir.replace("\\", ""), metrics) |
| | run_summaries.extend(summaries) |
| |
|
| | |
| | if exp_run_dir is not None: |
| | with open(os.path.join(exp_run_dir, "summaries.json"), "w") as fp: |
| | json.dump(run_summaries, fp, indent=4) |
| |
|
| | combined_summary = combine_summaries(run_summaries) |
| |
|
| | if args.repo_id is not None and args.path_in_repo is not None: |
| | |
| | api = HfApi() |
| | api.upload_folder( |
| | folder_path=exp_run_dir, |
| | path_in_repo=args.path_in_repo, |
| | repo_id=args.repo_id, |
| | repo_type="dataset", |
| | token=args.token, |
| | ) |
| |
|