#!/usr/bin/env python3 """Criterion-A time_scale calibration. Binary-search the smallest replay_time_scale whose A-family L-C-A similarity to the real (scale=1.0) arrival process stays >= tau. Uniform time scaling distorts only the A axis (rate + fano; interarrival CV is scale-invariant), so this bounds the arrival-axis distortion introduced by compression using the same similarity metric Stop-A uses. Pure trace metadata -> deterministic, no GPU needed. Usage: PYTHONPATH=src python3 scripts/calibrate_time_scale.py \ --trace trace_windows/traces/chat_w20260311_1000.jsonl \ --gpu-count 8 --min-input 0 --max-input 8192 --tau 0.9 """ from __future__ import annotations import argparse import json import math from pathlib import Path from aituner.lca import _family_similarity, build_workload_profile from aituner.trace import TraceRequest, WindowRecord def load_rows(path: Path, lo: int, hi: int) -> list[dict]: with path.open(encoding="utf-8") as fh: rows = [json.loads(l) for l in fh if l.strip()] return [r for r in rows if lo <= int(r["input_length"]) <= hi] def build_requests(rows: list[dict]) -> tuple[list[TraceRequest], float, float]: reqs = [] for i, r in enumerate(rows): reqs.append( TraceRequest( row_id=str(r.get("chat_id", i)), arrival_s=float(r["timestamp"]), sampling_u=float(r.get("sampling_u", 0.0)), body={}, prompt_tokens_hint=int(r["input_length"]), completion_tokens_hint=int(r["output_length"]), metadata={"hash_ids": r.get("hash_ids") if isinstance(r.get("hash_ids"), list) else None}, ) ) amin = min(x.arrival_s for x in reqs) amax = max(x.arrival_s for x in reqs) return reqs, amin, amax def profile_at(reqs, amin, amax, gpu_count, scale): rs = [ TraceRequest( x.row_id, (x.arrival_s - amin) * scale, x.sampling_u, x.body, x.prompt_tokens_hint, x.completion_tokens_hint, x.metadata, ) for x in reqs ] span = (amax - amin) * scale w = WindowRecord( window_id="w", trace_path="", trace_type="chat", window_start=0.0, window_end=span, source_payload={"block_size": 64}, ) return build_workload_profile(rs, w, gpu_count=gpu_count, length_mode="total") def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--trace", type=Path, required=True) ap.add_argument("--gpu-count", type=int, default=8) ap.add_argument("--min-input", type=int, default=0) ap.add_argument("--max-input", type=int, default=8192) ap.add_argument("--tau", type=float, default=0.9) args = ap.parse_args() rows = load_rows(args.trace, args.min_input, args.max_input) reqs, amin, amax = build_requests(rows) print(f"n={len(reqs)} raw arrival span={amax - amin:.1f}s") base = profile_at(reqs, amin, amax, args.gpu_count, 1.0) print(f"{'scale':>6} {'simA':>7} {'rate/gpu':>9} {'fano':>8} {'span_s':>8}") for s in (1.0, 0.95, 0.9, 0.85, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2): p = profile_at(reqs, amin, amax, args.gpu_count, s) a = _family_similarity(base.vector, p.vector)["A"] print(f"{s:6.2f} {a:7.3f} {math.expm1(p.vector[7]):9.3f} {math.expm1(p.vector[9]):8.2f} {(amax-amin)*s:8.1f}") lo, hi = 0.05, 1.0 for _ in range(40): mid = (lo + hi) / 2 a = _family_similarity(base.vector, profile_at(reqs, amin, amax, args.gpu_count, mid).vector)["A"] if a >= args.tau: hi = mid else: lo = mid print(f"\nsmallest scale with simA>={args.tau}: {hi:.4f} (arrival span {(amax-amin)*hi:.0f}s)") return 0 if __name__ == "__main__": raise SystemExit(main())