""" Script to benchmark latency for PyTorch, ONNX, and ONNX INT8 models on CPU. """ import time from pathlib import Path import numpy as np import torch from transformers import AutoModelForSequenceClassification, AutoTokenizer try: from optimum.onnxruntime import ORTModelForSequenceClassification OPTIMUM_AVAILABLE = True except ImportError: OPTIMUM_AVAILABLE = False import mlflow def benchmark_model(model, tokenizer, texts, model_type="pytorch"): latencies = [] # Warmup inputs = tokenizer(texts[:5], return_tensors="pt", padding=True, truncation=True) if model_type == "pytorch": with torch.no_grad(): model(**inputs) else: model(**inputs) print(f"Benchmarking {model_type}...") for text in texts: inputs = tokenizer([text], return_tensors="pt", padding=True, truncation=True, max_length=128) start_time = time.perf_counter() if model_type == "pytorch": with torch.no_grad(): model(**inputs) else: model(**inputs) end_time = time.perf_counter() latencies.append((end_time - start_time) * 1000) # ms mean_latency = np.mean(latencies) p95_latency = np.percentile(latencies, 95) throughput = len(texts) / (sum(latencies) / 1000) # samples / sec return mean_latency, p95_latency, throughput def main(): model_name = "xlm-roberta-base" pytorch_dir = Path("models/sentiment/multilingual/best") onnx_dir = Path("models/onnx/sentiment") int8_dir = Path("models/onnx/sentiment_int8") if not pytorch_dir.exists(): print(f"Directory {pytorch_dir} not found. Skipping benchmark.") return tokenizer = AutoTokenizer.from_pretrained(model_name) texts = ["This is a test sentence."] * 100 results = {} # 1. PyTorch CPU print("Loading PyTorch model...") pt_model = AutoModelForSequenceClassification.from_pretrained(str(pytorch_dir)) pt_model.eval() mean_pt, p95_pt, tput_pt = benchmark_model(pt_model, tokenizer, texts, "pytorch") results["PyTorch (CPU)"] = { "mean_ms": mean_pt, "p95_ms": p95_pt, "throughput": tput_pt, } if OPTIMUM_AVAILABLE: # 2. ONNX CPU if onnx_dir.exists(): print("Loading ONNX model...") onnx_model = ORTModelForSequenceClassification.from_pretrained(str(onnx_dir)) mean_onnx, p95_onnx, tput_onnx = benchmark_model(onnx_model, tokenizer, texts, "onnx") results["ONNX (CPU)"] = { "mean_ms": mean_onnx, "p95_ms": p95_onnx, "throughput": tput_onnx, } # 3. ONNX INT8 CPU if int8_dir.exists(): print("Loading ONNX INT8 model...") int8_model = ORTModelForSequenceClassification.from_pretrained(str(int8_dir)) mean_int8, p95_int8, tput_int8 = benchmark_model(int8_model, tokenizer, texts, "onnx_int8") results["ONNX INT8 (CPU)"] = { "mean_ms": mean_int8, "p95_ms": p95_int8, "throughput": tput_int8, } print("\n--- Latency Benchmark Results ---") print(f"{'Model':<20} | {'Mean (ms)':<10} | {'P95 (ms)':<10} | {'Throughput (samples/s)':<25}") print("-" * 75) for name, metrics in results.items(): print(f"{name:<20} | {metrics['mean_ms']:<10.2f} | {metrics['p95_ms']:<10.2f} | {metrics['throughput']:<25.2f}") # Target check if "ONNX INT8 (CPU)" in results: int8_p95 = results["ONNX INT8 (CPU)"]["p95_ms"] if int8_p95 < 300: print(f"\nSUCCESS: ONNX INT8 P95 latency is {int8_p95:.2f}ms, which is < 300ms target.") else: print(f"\nWARNING: ONNX INT8 P95 latency is {int8_p95:.2f}ms, which is > 300ms target.") mlflow.set_tracking_uri("sqlite:///mlflow.db") mlflow.set_experiment("latency-benchmark") with mlflow.start_run(): for name, metrics in results.items(): prefix = name.lower().replace(" ", "_").replace("(", "").replace(")", "") mlflow.log_metric(f"{prefix}_mean_latency", metrics["mean_ms"]) mlflow.log_metric(f"{prefix}_p95_latency", metrics["p95_ms"]) mlflow.log_metric(f"{prefix}_throughput", metrics["throughput"]) if __name__ == "__main__": main()