CoolFace
Datasetpublic

echodict/llama.cpp

version https://git-lfs.github.com/spec/v1 oid sha256:cfc44b7ba25614df70e6b65e3341cae0310163bd32fd31a6b928a542df433faf size 30786

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes773downloads
bench.py323 linesDownload Raw Back to bench
1from __future__ import annotations2 3import argparse4import json5import os6import re7import signal8import socket9import subprocess10import sys11import threading12import time13import traceback14from contextlib import closing15from datetime import datetime16 17import matplotlib18import matplotlib.dates19import matplotlib.pyplot as plt20import requests21from statistics import mean22 23 24def main(args_in: list[str] | None = None) -> None:25    parser = argparse.ArgumentParser(description="Start server benchmark scenario")26    parser.add_argument("--name", type=str, help="Bench name", required=True)27    parser.add_argument("--runner-label", type=str, help="Runner label", required=True)28    parser.add_argument("--branch", type=str, help="Branch name", default="detached")29    parser.add_argument("--commit", type=str, help="Commit name", default="dirty")30    parser.add_argument("--host", type=str, help="Server listen host", default="0.0.0.0")31    parser.add_argument("--port", type=int, help="Server listen host", default="8080")32    parser.add_argument("--model-path-prefix", type=str, help="Prefix where to store the model files", default="models")33    parser.add_argument("--n-prompts", type=int,34                        help="SERVER_BENCH_N_PROMPTS: total prompts to randomly select in the benchmark", required=True)35    parser.add_argument("--max-prompt-tokens", type=int,36                        help="SERVER_BENCH_MAX_PROMPT_TOKENS: maximum prompt tokens to filter out in the dataset",37                        required=True)38    parser.add_argument("--max-tokens", type=int,39                        help="SERVER_BENCH_MAX_CONTEXT: maximum context size of the completions request to filter out in the dataset: prompt + predicted tokens",40                        required=True)41    parser.add_argument("--hf-repo", type=str, help="Hugging Face model repository", required=True)42    parser.add_argument("--hf-file", type=str, help="Hugging Face model file", required=True)43    parser.add_argument("-ngl", "--n-gpu-layers", type=int, help="layers to the GPU for computation", required=True)44    parser.add_argument("--ctx-size", type=int, help="Set the size of the prompt context", required=True)45    parser.add_argument("--parallel", type=int, help="Set the number of slots for process requests", required=True)46    parser.add_argument("--batch-size", type=int, help="Set the batch size for prompt processing", required=True)47    parser.add_argument("--ubatch-size", type=int, help="physical maximum batch size", required=True)48    parser.add_argument("--scenario", type=str, help="Scenario to run", required=True)49    parser.add_argument("--duration", type=str, help="Bench scenario", required=True)50 51    args = parser.parse_args(args_in)52 53    start_time = time.time()54 55    # Start the server and performance scenario56    try:57        server_process = start_server(args)58    except Exception:59        print("bench: server start error :")60        traceback.print_exc(file=sys.stdout)61        sys.exit(1)62 63    # start the benchmark64    iterations = 065    data = {}66    try:67        start_benchmark(args)68 69        with open("results.github.env", 'w') as github_env:70            # parse output71            with open('k6-results.json', 'r') as bench_results:72                # Load JSON data from file73                data = json.load(bench_results)74                for metric_name in data['metrics']:75                    for metric_metric in data['metrics'][metric_name]:76                        value = data['metrics'][metric_name][metric_metric]77                        if isinstance(value, float) or isinstance(value, int):78                            value = round(value, 2)79                            data['metrics'][metric_name][metric_metric]=value80                            github_env.write(81                                f"{escape_metric_name(metric_name)}_{escape_metric_name(metric_metric)}={value}\n")82                iterations = data['root_group']['checks']['success completion']['passes']83 84    except Exception:85        print("bench: error :")86        traceback.print_exc(file=sys.stdout)87 88    # Stop the server89    if server_process:90        try:91            print(f"bench: shutting down server pid={server_process.pid} ...")92            if os.name == 'nt':93                interrupt = signal.CTRL_C_EVENT94            else:95                interrupt = signal.SIGINT96            server_process.send_signal(interrupt)97            server_process.wait(0.5)98 99        except subprocess.TimeoutExpired:100            print(f"server still alive after 500ms, force-killing pid={server_process.pid} ...")101            server_process.kill()  # SIGKILL102            server_process.wait()103 104        while is_server_listening(args.host, args.port):105            time.sleep(0.1)106 107    title = (f"llama.cpp {args.name} on {args.runner_label}\n "108             f"duration={args.duration} {iterations} iterations")109    xlabel = (f"{args.hf_repo}/{args.hf_file}\n"110              f"parallel={args.parallel} ctx-size={args.ctx_size} ngl={args.n_gpu_layers} batch-size={args.batch_size} ubatch-size={args.ubatch_size} pp={args.max_prompt_tokens} pp+tg={args.max_tokens}\n"111              f"branch={args.branch} commit={args.commit}")112 113    # Prometheus114    end_time = time.time()115    prometheus_metrics = {}116    if is_server_listening("0.0.0.0", 9090):117        metrics = ['prompt_tokens_seconds', 'predicted_tokens_seconds',118                   'kv_cache_usage_ratio', 'requests_processing', 'requests_deferred']119 120        for metric in metrics:121            resp = requests.get(f"http://localhost:9090/api/v1/query_range",122                                params={'query': 'llamacpp:' + metric, 'start': start_time, 'end': end_time, 'step': 2})123 124            with open(f"{metric}.json", 'w') as metric_json:125                metric_json.write(resp.text)126 127            if resp.status_code != 200:128                print(f"bench: unable to extract prometheus metric {metric}: {resp.text}")129            else:130                metric_data = resp.json()131                values = metric_data['data']['result'][0]['values']132                timestamps, metric_values = zip(*values)133                metric_values = [float(value) for value in metric_values]134                prometheus_metrics[metric] = metric_values135                timestamps_dt = [str(datetime.fromtimestamp(int(ts))) for ts in timestamps]136                plt.figure(figsize=(16, 10), dpi=80)137                plt.plot(timestamps_dt, metric_values, label=metric)138                plt.xticks(rotation=0, fontsize=14, horizontalalignment='center', alpha=.7)139                plt.yticks(fontsize=12, alpha=.7)140 141                ylabel = f"llamacpp:{metric}"142                plt.title(title,143                          fontsize=14, wrap=True)144                plt.grid(axis='both', alpha=.3)145                plt.ylabel(ylabel, fontsize=22)146                plt.xlabel(xlabel, fontsize=14, wrap=True)147                plt.gca().xaxis.set_major_locator(matplotlib.dates.MinuteLocator())148                plt.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%Y-%m-%d %H:%M:%S"))149                plt.gcf().autofmt_xdate()150 151                # Remove borders152                plt.gca().spines["top"].set_alpha(0.0)153                plt.gca().spines["bottom"].set_alpha(0.3)154                plt.gca().spines["right"].set_alpha(0.0)155                plt.gca().spines["left"].set_alpha(0.3)156 157                # Save the plot as a jpg image158                plt.savefig(f'{metric}.jpg', dpi=60)159                plt.close()160 161                # Mermaid format in case images upload failed162                with open(f"{metric}.mermaid", 'w') as mermaid_f:163                    mermaid = (164                    f"""---165config:166    xyChart:167        titleFontSize: 12168        width: 900169        height: 600170    themeVariables:171        xyChart:172            titleColor: "#000000"173---174xychart-beta175    title "{title}"176    y-axis "llamacpp:{metric}"177    x-axis "llamacpp:{metric}" {int(min(timestamps))} --> {int(max(timestamps))}178    line [{', '.join([str(round(float(value), 2)) for value in metric_values])}]179                    """)180                    mermaid_f.write(mermaid)181 182    # 140 chars max for commit status description183    bench_results = {184        "i": iterations,185        "req": {186            "p95": round(data['metrics']["http_req_duration"]["p(95)"], 2),187            "avg": round(data['metrics']["http_req_duration"]["avg"], 2),188        },189        "pp": {190            "p95": round(data['metrics']["llamacpp_prompt_processing_second"]["p(95)"], 2),191            "avg": round(data['metrics']["llamacpp_prompt_processing_second"]["avg"], 2),192            "0": round(mean(prometheus_metrics['prompt_tokens_seconds']), 2) if 'prompt_tokens_seconds' in prometheus_metrics else 0,193        },194        "tg": {195            "p95": round(data['metrics']["llamacpp_tokens_second"]["p(95)"], 2),196            "avg": round(data['metrics']["llamacpp_tokens_second"]["avg"], 2),197            "0": round(mean(prometheus_metrics['predicted_tokens_seconds']), 2) if 'predicted_tokens_seconds' in prometheus_metrics else 0,198        },199    }200    with open("results.github.env", 'a') as github_env:201        github_env.write(f"BENCH_RESULTS={json.dumps(bench_results, indent=None, separators=(',', ':') )}\n")202        github_env.write(f"BENCH_ITERATIONS={iterations}\n")203 204        title = title.replace('\n', ' ')205        xlabel = xlabel.replace('\n', ' ')206        github_env.write(f"BENCH_GRAPH_TITLE={title}\n")207        github_env.write(f"BENCH_GRAPH_XLABEL={xlabel}\n")208 209 210def start_benchmark(args):211    k6_path = './k6'212    if 'BENCH_K6_BIN_PATH' in os.environ:213        k6_path = os.environ['BENCH_K6_BIN_PATH']214    k6_args = [215        'run', args.scenario,216        '--no-color',217        '--no-connection-reuse',218        '--no-vu-connection-reuse',219    ]220    k6_args.extend(['--duration', args.duration])221    k6_args.extend(['--iterations', args.n_prompts])222    k6_args.extend(['--vus', args.parallel])223    k6_args.extend(['--summary-export', 'k6-results.json'])224    k6_args.extend(['--out', 'csv=k6-results.csv'])225    args = f"SERVER_BENCH_N_PROMPTS={args.n_prompts} SERVER_BENCH_MAX_PROMPT_TOKENS={args.max_prompt_tokens} SERVER_BENCH_MAX_CONTEXT={args.max_tokens} "226    args = args + ' '.join([str(arg) for arg in [k6_path, *k6_args]])227    print(f"bench: starting k6 with: {args}")228    k6_completed = subprocess.run(args, shell=True, stdout=sys.stdout, stderr=sys.stderr)229    if k6_completed.returncode != 0:230        raise Exception("bench: unable to run k6")231 232 233def start_server(args):234    server_process = start_server_background(args)235 236    attempts = 0237    max_attempts = 600238    if 'GITHUB_ACTIONS' in os.environ:239        max_attempts *= 2240 241    while not is_server_listening(args.host, args.port):242        attempts += 1243        if attempts > max_attempts:244            assert False, "server not started"245        print(f"bench:     waiting for server to start ...")246        time.sleep(0.5)247 248    attempts = 0249    while not is_server_ready(args.host, args.port):250        attempts += 1251        if attempts > max_attempts:252            assert False, "server not ready"253        print(f"bench:     waiting for server to be ready ...")254        time.sleep(0.5)255 256    print("bench: server started and ready.")257    return server_process258 259 260def start_server_background(args):261    # Start the server262    server_path = '../../../build/bin/llama-server'263    if 'LLAMA_SERVER_BIN_PATH' in os.environ:264        server_path = os.environ['LLAMA_SERVER_BIN_PATH']265    server_args = [266        '--host', args.host,267        '--port', args.port,268    ]269    server_args.extend(['--hf-repo', args.hf_repo])270    server_args.extend(['--hf-file', args.hf_file])271    server_args.extend(['--n-gpu-layers', args.n_gpu_layers])272    server_args.extend(['--ctx-size', args.ctx_size])273    server_args.extend(['--parallel', args.parallel])274    server_args.extend(['--batch-size', args.batch_size])275    server_args.extend(['--ubatch-size', args.ubatch_size])276    server_args.extend(['--n-predict', args.max_tokens * 2])277    server_args.append('--cont-batching')278    server_args.append('--metrics')279    server_args.append('--flash-attn')280    args = [str(arg) for arg in [server_path, *server_args]]281    print(f"bench: starting server with: {' '.join(args)}")282    pkwargs = {283        'stdout': subprocess.PIPE,284        'stderr': subprocess.PIPE285    }286    server_process = subprocess.Popen(287        args,288        **pkwargs)  # pyright: ignore[reportArgumentType, reportCallIssue] # ty: ignore[no-matching-overload]289 290    def server_log(in_stream, out_stream):291        for line in iter(in_stream.readline, b''):292            print(line.decode('utf-8'), end='', file=out_stream)293 294    thread_stdout = threading.Thread(target=server_log, args=(server_process.stdout, sys.stdout))295    thread_stdout.start()296    thread_stderr = threading.Thread(target=server_log, args=(server_process.stderr, sys.stderr))297    thread_stderr.start()298 299    return server_process300 301 302def is_server_listening(server_fqdn, server_port):303    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:304        result = sock.connect_ex((server_fqdn, server_port))305        _is_server_listening = result == 0306        if _is_server_listening:307            print(f"server is listening on {server_fqdn}:{server_port}...")308        return _is_server_listening309 310 311def is_server_ready(server_fqdn, server_port):312    url = f"http://{server_fqdn}:{server_port}/health"313    response = requests.get(url)314    return response.status_code == 200315 316 317def escape_metric_name(metric_name):318    return re.sub('[^A-Z0-9]', '_', metric_name.upper())319 320 321if __name__ == '__main__':322    main()323