"""Analyze trace patterns to assess PD separation benefit. Computes metrics relevant to deciding PD-combined vs PD-separated: - Input/output token ratio (high ratio = prefill-heavy → PD sep benefits) - Prefix sharing density (high sharing → benefits from shared KV cache) - Session length distribution (multi-turn = more prefix reuse) - Arrival burstiness (bursty prefill → PD sep can absorb spikes) - Compute-intensity ratio: prefill FLOP share vs decode FLOP share Usage: python scripts/analyze_trace.py --input traces/sampled_1000req_seed42.jsonl """ from __future__ import annotations import argparse import collections import json import statistics from pathlib import Path def main(): p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument("--input", type=Path, required=True) args = p.parse_args() rows = [] with args.input.open() as fh: for line in fh: rows.append(json.loads(line)) # Session structure sessions: dict[str, list[dict]] = collections.OrderedDict() chat_to_session: dict[int, str] = {} for r in rows: cid = int(r["chat_id"]) pid = int(r["parent_chat_id"]) sid = r.get("session_id") if sid is None: sid = str(cid) if pid < 0 else chat_to_session.get(pid, str(pid)) chat_to_session[cid] = str(sid) sessions.setdefault(str(sid), []).append(r) n_sessions = len(sessions) turns_per_session = [len(v) for v in sessions.values()] multi_turn = sum(1 for t in turns_per_session if t > 1) input_lens = [r["input_length"] for r in rows] output_lens = [r["output_length"] for r in rows] total_input = sum(input_lens) total_output = sum(output_lens) print("=" * 60) print("Trace Pattern Analysis for PD Separation Decision") print("=" * 60) # 1. Input/Output ratio io_ratio = total_input / max(total_output, 1) print(f"\n1. Input/Output Token Ratio") print(f" Total input tokens: {total_input:>12,}") print(f" Total output tokens: {total_output:>12,}") print(f" I/O ratio: {io_ratio:>12.1f}x") print(f" → {'STRONGLY' if io_ratio > 50 else 'Moderately' if io_ratio > 10 else 'Weakly'} prefill-heavy") # 2. Prefill compute share # Approximate: prefill FLOP ∝ input_length, decode FLOP ∝ output_length * input_length # More precisely: prefill dominates when input >> output prefill_share = total_input / (total_input + total_output) print(f"\n2. Compute Split (token count proxy)") print(f" Prefill share: {prefill_share*100:.1f}%") print(f" Decode share: {(1-prefill_share)*100:.1f}%") # 3. Session structure print(f"\n3. Session Structure") print(f" Sessions: {n_sessions}") print(f" Requests: {len(rows)}") print(f" Multi-turn: {multi_turn} ({multi_turn/n_sessions*100:.1f}%)") print(f" Turns/sess: min={min(turns_per_session)} max={max(turns_per_session)} " f"avg={statistics.fmean(turns_per_session):.1f}") # 4. Prefix sharing all_hash_ids = set() per_request_hashes = [] for r in rows: hids = set(r.get("hash_ids", [])) per_request_hashes.append(hids) all_hash_ids.update(hids) hash_refcount = collections.Counter() for hids in per_request_hashes: for h in hids: hash_refcount[h] += 1 shared_blocks = sum(1 for h, c in hash_refcount.items() if c > 1) total_blocks = len(all_hash_ids) block_reuse = shared_blocks / max(total_blocks, 1) avg_refcount = statistics.fmean(hash_refcount.values()) if hash_refcount else 0 print(f"\n4. Prefix Block Sharing") print(f" Unique blocks: {total_blocks:>10,}") print(f" Shared (ref>1): {shared_blocks:>10,} ({block_reuse*100:.1f}%)") print(f" Avg refcount: {avg_refcount:>10.2f}") print(f" → {'High' if block_reuse > 0.3 else 'Moderate' if block_reuse > 0.1 else 'Low'} prefix reuse potential") # 5. Input length distribution input_sorted = sorted(input_lens) pct = lambda q: input_sorted[min(int(q * len(input_sorted)), len(input_sorted) - 1)] print(f"\n5. Input Length Distribution") print(f" p10={pct(0.1):>8,} p50={pct(0.5):>8,} p90={pct(0.9):>8,} max={max(input_lens):>8,}") long_context = sum(1 for l in input_lens if l > 32000) print(f" Requests >32k tokens: {long_context} ({long_context/len(rows)*100:.1f}%)") # 6. Arrival pattern timestamps = sorted(float(r["timestamp"]) for r in rows) span = timestamps[-1] - timestamps[0] avg_rate = len(rows) / max(span, 0.001) # Burstiness: coefficient of variation of inter-arrival times inter_arrivals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps) - 1)] inter_arrivals = [t for t in inter_arrivals if t > 0] if inter_arrivals: cv = statistics.stdev(inter_arrivals) / statistics.fmean(inter_arrivals) else: cv = 0 print(f"\n6. Arrival Pattern") print(f" Span: {span:.1f}s ({span/60:.1f} min)") print(f" Avg rate: {avg_rate:.2f} req/s") print(f" Burstiness (CoV): {cv:.2f}") print(f" → {'Bursty' if cv > 1.5 else 'Moderate' if cv > 0.8 else 'Steady'} arrival pattern") # Summary print(f"\n{'=' * 60}") print("Summary: PD Separation Recommendation") print(f"{'=' * 60}") factors = [] if io_ratio > 50: factors.append("Very high I/O ratio (prefill-dominated)") elif io_ratio > 10: factors.append("High I/O ratio") if block_reuse > 0.1: factors.append(f"Significant prefix reuse ({block_reuse*100:.0f}% shared blocks)") if long_context / len(rows) > 0.3: factors.append(f"Many long-context requests ({long_context/len(rows)*100:.0f}%)") if cv > 1.0: factors.append("Bursty arrivals (PD sep absorbs prefill spikes)") if len(factors) >= 2: print("→ RECOMMEND PD separation:") elif len(factors) == 1: print("→ PD separation MAY help:") else: print("→ PD separation likely NOT beneficial:") for f in factors: print(f" • {f}") if not factors: print(" • No strong indicators for PD separation benefit") if __name__ == "__main__": main()