| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| 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, |
| ) |
|
|