| import argparse |
| import json |
| import os |
| import statistics |
| import time |
|
|
| from model_config import SYSTEM_PROMPT, resolve_model |
| from model_runtime import resolve_generation_settings |
| from provenance import runtime_provenance |
| from run_questions import get_model_device, read_questions |
| from terminal_chat import generate, load_model |
|
|
|
|
| def mean(values): |
| return statistics.mean(values) if values else None |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Benchmark model load time and per-question inference latency." |
| ) |
| parser.add_argument( |
| "--questions", |
| default="examples/questions.txt", |
| help="Question file to benchmark.", |
| ) |
| parser.add_argument( |
| "--input-format", |
| choices=["lines", "blocks"], |
| default="lines", |
| ) |
| parser.add_argument( |
| "--model", |
| help="Model name from models.json. Defaults to MODEL_NAME or models.json default.", |
| ) |
| parser.add_argument("--base-model", help="Override the base Hugging Face model repo.") |
| parser.add_argument("--adapter", help="Override the LoRA adapter Hugging Face repo.") |
| parser.add_argument( |
| "--model-mode", |
| choices=["adapter", "base"], |
| default=os.getenv("MODEL_MODE", "adapter").lower(), |
| ) |
| parser.add_argument( |
| "--device", |
| choices=["auto", "cuda", "mps", "cpu"], |
| default=os.getenv("DEVICE", "auto").lower(), |
| ) |
| parser.add_argument("--allow-cpu", action="store_true") |
| parser.add_argument("--fast", action="store_true") |
| parser.add_argument("--max-new-tokens", type=int) |
| parser.add_argument("--temperature", type=float) |
| parser.add_argument("--top-p", type=float) |
| parser.add_argument( |
| "--limit", |
| type=int, |
| help="Only run the first N questions.", |
| ) |
| args = parser.parse_args() |
|
|
| questions = read_questions(args.questions, args.input_format) |
| if args.limit is not None: |
| questions = questions[: args.limit] |
| if not questions: |
| raise SystemExit("No questions to benchmark.") |
|
|
| selected_model = resolve_model(args.model, args.base_model, args.adapter) |
| generation_settings = resolve_generation_settings( |
| args.max_new_tokens, |
| args.temperature, |
| args.top_p, |
| args.fast, |
| ) |
|
|
| load_started = time.perf_counter() |
| tokenizer, model = load_model( |
| selected_model, |
| args.allow_cpu, |
| args.model_mode, |
| args.device, |
| ) |
| load_seconds = time.perf_counter() - load_started |
|
|
| results = [] |
| for index, question in enumerate(questions, start=1): |
| messages = [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": question}, |
| ] |
| started = time.perf_counter() |
| answer = generate( |
| tokenizer, |
| model, |
| messages, |
| generation_settings["max_new_tokens"], |
| generation_settings["temperature"], |
| generation_settings["top_p"], |
| ) |
| seconds = time.perf_counter() - started |
| output_tokens = len(tokenizer(answer, add_special_tokens=False)["input_ids"]) |
| results.append( |
| { |
| "index": index, |
| "question": question, |
| "answer": answer, |
| "seconds": round(seconds, 3), |
| "output_tokens": output_tokens, |
| "tokens_per_second": round(output_tokens / seconds, 3) |
| if seconds > 0 |
| else None, |
| } |
| ) |
|
|
| latencies = [item["seconds"] for item in results] |
| token_rates = [ |
| item["tokens_per_second"] |
| for item in results |
| if item["tokens_per_second"] is not None |
| ] |
| report = { |
| "device": get_model_device(model), |
| "load_seconds": round(load_seconds, 3), |
| "question_count": len(results), |
| "latency_seconds": { |
| "mean": round(mean(latencies), 3), |
| "min": round(min(latencies), 3), |
| "max": round(max(latencies), 3), |
| }, |
| "tokens_per_second": { |
| "mean": round(mean(token_rates), 3) if token_rates else None, |
| "min": round(min(token_rates), 3) if token_rates else None, |
| "max": round(max(token_rates), 3) if token_rates else None, |
| }, |
| "fast": args.fast, |
| "results": results, |
| **runtime_provenance( |
| selected_model, |
| args.model_mode, |
| args.device, |
| generation_settings, |
| ), |
| } |
| print(json.dumps(report, indent=2, ensure_ascii=False)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|