#!/usr/bin/env python3 """Run a small correctness and latency probe against an LLM server.""" from __future__ import annotations import argparse import json import math import statistics import time from pathlib import Path from typing import Any, Dict, List, Optional from urllib import request from profile_common import extract_openai_chat_text DEFAULT_PROMPTS = [ "Introduce Shanghai in one short sentence.", "What is 2+2? Answer briefly.", "Write one short haiku about GPUs.", ] def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "Send a few short requests to an LLM server and record latency plus " "sample outputs." ) ) parser.add_argument( "--framework", required=True, choices=("sglang", "vllm", "trtllm", "tokenspeed"), help="Serving framework.", ) parser.add_argument( "--url", required=True, help="Server base URL, for example http://127.0.0.1:30000.", ) parser.add_argument( "--model", default=None, help="OpenAI model id. Auto-discovered for vLLM, TensorRT-LLM, and TokenSpeed when omitted.", ) parser.add_argument( "--requests", type=int, default=6, help="How many probe requests to send.", ) parser.add_argument( "--max-tokens", type=int, default=48, help="Generation length for each request.", ) parser.add_argument( "--timeout", type=float, default=180.0, help="Per-request timeout in seconds.", ) parser.add_argument( "--prompt", action="append", default=[], help="Optional prompt override. Repeat to add more prompts.", ) parser.add_argument( "--output", default=None, help="Optional JSON output path.", ) return parser.parse_args() def post_json(url: str, payload: Dict[str, Any], timeout: float) -> Dict[str, Any]: req = request.Request( url=url, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) with request.urlopen(req, timeout=timeout) as resp: raw = resp.read() return json.loads(raw.decode("utf-8")) if raw else {} def get_json(url: str, timeout: float) -> Dict[str, Any]: req = request.Request(url=url, method="GET") with request.urlopen(req, timeout=timeout) as resp: raw = resp.read() return json.loads(raw.decode("utf-8")) if raw else {} def discover_openai_model(base_url: str, timeout: float) -> str: payload = get_json(base_url.rstrip("/") + "/v1/models", timeout=timeout) data = payload.get("data") if not isinstance(data, list) or not data: raise RuntimeError(f"No models returned by {base_url.rstrip('/')}/v1/models") first = data[0] if isinstance(first, dict) and first.get("id"): return str(first["id"]) raise RuntimeError(f"Malformed /v1/models payload from {base_url.rstrip('/')}") def p95(values: List[float]) -> Optional[float]: if not values: return None ordered = sorted(values) index = max(0, math.ceil(len(ordered) * 0.95) - 1) return ordered[index] def sglang_request(base_url: str, prompt: str, max_tokens: int, timeout: float) -> str: payload = { "text": prompt, "sampling_params": { "temperature": 0.0, "max_new_tokens": max_tokens, }, "stream": False, } body = post_json(base_url.rstrip("/") + "/generate", payload, timeout=timeout) return str(body.get("text", "")) def openai_request( base_url: str, model: str, prompt: str, max_tokens: int, timeout: float, ) -> Dict[str, str]: payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0.0, "max_tokens": max_tokens, "stream": False, } body = post_json( base_url.rstrip("/") + "/v1/chat/completions", payload, timeout=timeout, ) text, source = extract_openai_chat_text(body) return {"text": text, "source": source} def run_probe(args: argparse.Namespace) -> Dict[str, Any]: prompts = args.prompt or list(DEFAULT_PROMPTS) model = args.model if args.framework in {"vllm", "trtllm", "tokenspeed"} and not model: model = discover_openai_model(args.url, timeout=args.timeout) latencies: List[float] = [] samples: List[Dict[str, Any]] = [] errors: List[Dict[str, str]] = [] for request_idx in range(args.requests): prompt = prompts[request_idx % len(prompts)] start = time.time() try: if args.framework == "sglang": text = sglang_request( args.url, prompt, max_tokens=args.max_tokens, timeout=args.timeout, ) source = "generate.text" else: assert model is not None result = openai_request( args.url, model, prompt, max_tokens=args.max_tokens, timeout=args.timeout, ) text = result["text"] source = result["source"] elapsed = time.time() - start latencies.append(elapsed) samples.append( { "prompt": prompt, "latency_s": round(elapsed, 3), "content": text[:240], "source": source, "non_empty": bool(text.strip()), } ) except Exception as exc: # pragma: no cover - runtime probe path errors.append({"prompt": prompt, "error": repr(exc)}) return { "framework": args.framework, "url": args.url, "model": model, "requests": args.requests, "success": len(samples), "errors": len(errors), "all_non_empty": ( all(sample["non_empty"] for sample in samples) if samples else False ), "avg_latency_s": round(statistics.mean(latencies), 3) if latencies else None, "p95_latency_s": round(p95(latencies), 3) if latencies else None, "samples": samples[:3], "error_samples": errors[:3], } def main() -> int: args = parse_args() summary = run_probe(args) rendered = json.dumps(summary, ensure_ascii=False, indent=2) print(rendered) if args.output: output_path = Path(args.output).expanduser().resolve() output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(rendered + "\n", encoding="utf-8") return 0 if __name__ == "__main__": raise SystemExit(main())