"""Reverse-ablation trace surgeon for the PD-disagg crossover study. Takes the REAL agentic trace and neutralizes ONE agentic property at a time, so we can see which removal restores PD-disagg to parity with colocation. This is the subtractive complement to the additive synthetic sweep (D1-D5). Neutralizations (compose freely; each defaults to off): --set-output N set every output_length to N (kill short-output -> test decode-benefit starvation) --max-input N clamp input_length to N, truncating hash_ids to ceil(N/512) (kill huge-prefill -> test prefill-bound bottleneck) --uniform-arrival respace requests evenly over the original span, order preserved (kill burstiness -> test arrival variance) --unique-hash replace all hash_ids with globally-unique ids (kill intra-session reuse -> test cache/affinity) --max-turns N keep only the first N turns of each session (flatten session skew / heavy tail) The schema is preserved so the replayer consumes the output unchanged. """ from __future__ import annotations import argparse import json import math from collections import defaultdict from pathlib import Path BLOCK_SIZE = 512 UNIQUE_HASH_BASE = 2_000_000_000 def load_rows(path: Path) -> list[dict]: rows = [] with path.open() as fh: for line in fh: line = line.strip() if line: rows.append(json.loads(line)) return rows def resolve_session(row: dict, chat_to_session: dict) -> str: if "session_id" in row: return str(row["session_id"]) cid, pid = int(row["chat_id"]), int(row["parent_chat_id"]) sid = str(cid) if pid < 0 else chat_to_session.get(pid, str(pid)) chat_to_session[cid] = sid return sid def main() -> None: p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument("--input", type=Path, required=True) p.add_argument("--output", type=Path, required=True) p.add_argument("--set-output", type=int, default=None) p.add_argument("--max-input", type=int, default=None) p.add_argument("--uniform-arrival", action="store_true") p.add_argument("--unique-hash", action="store_true") p.add_argument("--max-turns", type=int, default=None) args = p.parse_args() rows = load_rows(args.input) # ensure session_id present c2s: dict = {} for r in rows: r["session_id"] = resolve_session(r, c2s) applied = [] # --- flatten session skew: keep first N turns per session --- if args.max_turns is not None: kept_count: dict = defaultdict(int) rows.sort(key=lambda r: (r["session_id"], r.get("turn", 0))) out = [] for r in rows: if kept_count[r["session_id"]] < args.max_turns: kept_count[r["session_id"]] += 1 out.append(r) rows = out rows.sort(key=lambda r: r.get("timestamp", 0.0)) applied.append(f"max_turns={args.max_turns}") # --- clamp input + truncate hash_ids --- if args.max_input is not None: for r in rows: if r["input_length"] > args.max_input: r["input_length"] = args.max_input keep_blocks = max(1, math.ceil(r["input_length"] / BLOCK_SIZE)) r["hash_ids"] = list(r.get("hash_ids", []))[:keep_blocks] applied.append(f"max_input={args.max_input}") # --- set fixed output length --- if args.set_output is not None: for r in rows: r["output_length"] = args.set_output applied.append(f"set_output={args.set_output}") # --- kill reuse: globally-unique hashes --- if args.unique_hash: nxt = UNIQUE_HASH_BASE for r in rows: n = max(1, len(r.get("hash_ids", [])) or math.ceil(r["input_length"] / BLOCK_SIZE)) r["hash_ids"] = list(range(nxt, nxt + n)) nxt += n applied.append("unique_hash") # --- de-burst: uniform arrival over original span (order preserved) --- if args.uniform_arrival: rows.sort(key=lambda r: r.get("timestamp", 0.0)) ts = [r.get("timestamp", 0.0) for r in rows] span = (ts[-1] - ts[0]) if len(ts) > 1 else 0.0 n = len(rows) for i, r in enumerate(rows): r["timestamp"] = round(ts[0] + (span * i / max(n - 1, 1)), 6) applied.append("uniform_arrival") rows.sort(key=lambda r: r.get("timestamp", 0.0)) args.output.parent.mkdir(parents=True, exist_ok=True) with args.output.open("w") as fh: for r in rows: fh.write(json.dumps(r) + "\n") inputs = sorted(r["input_length"] for r in rows) outs = sorted(r["output_length"] for r in rows) q = lambda v, p: v[min(int(p * len(v)), len(v) - 1)] if v else 0 print(f"wrote {len(rows)} rows -> {args.output}") print(f" neutralized: {applied or ['none (passthrough)']}") print(f" input p50={q(inputs,.5)} p90={q(inputs,.9)} p99={q(inputs,.99)} " f"output p50={q(outs,.5)} p90={q(outs,.9)}") if __name__ == "__main__": main()