narcolepticchicken commited on
Commit
1856652
·
verified ·
1 Parent(s): 47ac3cc

Upload ablation_study.py

Browse files
Files changed (1) hide show
  1. ablation_study.py +47 -95
ablation_study.py CHANGED
@@ -14,18 +14,16 @@ ACO Ablation Study + Cost-Quality Frontier Report.
14
  10. no telemetry feedback
15
 
16
  Each ablation removes one module from the full ACO and re-runs the benchmark.
17
- Reports which modules actually save money and which are noise.
18
  """
19
  import json, sys, random, os
20
  from dataclasses import dataclass, field, asdict
21
  from collections import defaultdict
22
  from typing import List, Dict
23
 
24
- # Import the benchmark suite
25
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) or ".")
26
  import importlib.util
27
 
28
- # Try local import, fall back to Hub download
29
  try:
30
  from benchmark_suite import (
31
  generate_tasks, simulate_task, Config, CONFIGS, MODELS,
@@ -47,12 +45,7 @@ except ImportError:
47
 
48
  random.seed(42)
49
 
50
- # ═══════════════════════════════════════════════════════════════════
51
- # Ablation configs: full ACO minus one module
52
- # ═══════════════════════════════════════════════════════════════════
53
-
54
  def make_ablation_configs() -> List[Config]:
55
- """Create 10 ablation configs, each removing one module from full ACO."""
56
  base = dict(
57
  use_model_routing=True, use_learned_router=True,
58
  use_context_budget=True, use_cache_layout=True,
@@ -60,7 +53,7 @@ def make_ablation_configs() -> List[Config]:
60
  use_retry_optimizer=True, use_meta_tools=True,
61
  use_early_termination=True, use_telemetry=True,
62
  )
63
- ablations = [
64
  Config("abl1", "no model router", use_model_routing=False, use_learned_router=False,
65
  use_context_budget=True, use_cache_layout=True, use_tool_gate=True,
66
  use_verifier_budget=True, use_retry_optimizer=True, use_meta_tools=True,
@@ -79,47 +72,27 @@ def make_ablation_configs() -> List[Config]:
79
  use_early_termination=True, use_telemetry=True),
80
  Config("abl10", "no telemetry feedback", **{**base, "use_telemetry": False}),
81
  ]
82
- return ablations
83
-
84
-
85
- # ═══════════════════════════════════════════════════════════════════
86
- # Override select_model for ablation 9 (no specialist models)
87
- # ═══════════════════════════════════════════════════════════════════
88
-
89
- def select_model_abl9(task) -> str:
90
- """No specialist models: always use frontier-tier (tier 4) for routed tasks."""
91
- if task.risk_level == "high" or task.difficulty > 0.5:
92
- return "gpt-5.2" # tier 4
93
- if task.difficulty > 0.2:
94
- return "gpt-5.2"
95
- return "gpt-5-mini" # tier 2 minimum, no tier-1 specialist
96
-
97
 
98
  def run_ablation(n_per_domain: int = 20) -> Dict:
99
- """Run all 10 ablations + full ACO baseline."""
100
  tasks = generate_tasks(n_per_domain)
101
  full_aco = Config("I", "full ACO", use_model_routing=True, use_learned_router=True,
102
  use_context_budget=True, use_cache_layout=True, use_tool_gate=True,
103
  use_verifier_budget=True, use_retry_optimizer=True, use_meta_tools=True,
104
  use_early_termination=True, use_telemetry=True)
105
- all_configs = [full_aco] + make_ablation_configs()
106
 
107
  print(f"Generated {len(tasks)} tasks across 5 domains")
108
- print(f"Running {len(all_configs)} configs (full ACO + 10 ablations) x {len(tasks)} tasks\n")
109
 
110
  all_results = []
111
  for config in all_configs:
112
  print(f" {config.name}: {config.label}...", end=" ", flush=True)
113
  for task in tasks:
114
- # Special handling for ablation 9
115
  if config.name == "abl9":
116
- # Override model selection to avoid specialist (tier 1) models
117
  result = simulate_task(config, task)
118
- # Force model to non-specialist
119
  if result["tier"] == 1:
120
- result["model"] = "gpt-5-mini"
121
- result["tier"] = 2
122
- # Recalculate cost
123
  mi = MODELS["gpt-5-mini"]
124
  result["cost"] = round(
125
  (result["input_tokens"] / 1_000_000) * mi["cost_in"] +
@@ -133,7 +106,6 @@ def run_ablation(n_per_domain: int = 20) -> Dict:
133
 
134
  return {"tasks": [asdict(t) for t in tasks], "results": all_results}
135
 
136
-
137
  def print_ablation_report(metrics: Dict, config_labels: Dict):
138
  print(f"\n{'='*100}")
139
  print(f" ACO ABLATION REPORT - Which Modules Actually Save Money?")
@@ -146,26 +118,21 @@ def print_ablation_report(metrics: Dict, config_labels: Dict):
146
  print("-" * 100)
147
 
148
  verdicts = []
149
- for abl_name in ["abl1", "abl2", "abl3", "abl4", "abl5", "abl6", "abl7", "abl8", "abl9", "abl10"]:
 
 
150
  m = metrics["by_config"][abl_name]
151
  label = config_labels.get(abl_name, abl_name)
152
  cost_delta = m["total_cost"] - full["total_cost"]
153
  cost_pct = (cost_delta / full["total_cost"]) * 100 if full["total_cost"] > 0 else 0
154
  quality_delta = (m["success_rate"] - full["success_rate"]) * 100
155
 
156
- # Verdict: does removing this module hurt?
157
- if quality_delta < -3:
158
- verdict = "CRITICAL"
159
- elif cost_delta > 0 and quality_delta < -1:
160
- verdict = "HURTS QUALITY"
161
- elif cost_delta > 0.02:
162
- verdict = "SAVES MONEY"
163
- elif abs(cost_pct) < 2 and abs(quality_delta) < 1:
164
- verdict = "NOISE"
165
- elif cost_delta < 0 and quality_delta >= 0:
166
- verdict = "COST INCREASE"
167
- else:
168
- verdict = "MARGINAL"
169
 
170
  verdicts.append((abl_name, label, verdict, cost_pct, quality_delta))
171
  print(f" {label:<38} {m['success_rate']*100:>6.1f}% ${m['total_cost']:>8.4f} "
@@ -181,98 +148,83 @@ def print_ablation_report(metrics: Dict, config_labels: Dict):
181
  hurts = [v for v in verdicts if v[2] == "HURTS QUALITY"]
182
  marginal = [v for v in verdicts if v[2] == "MARGINAL"]
183
 
184
- print(f"\n CRITICAL modules (removing causes >3pp quality loss):")
185
- for _, label, _, _, qd in critical:
186
- print(f" - {label}")
187
- if not critical: print(f" (none)")
188
 
189
- print(f"\n SAVES MONEY modules (removing increases cost):")
190
- for _, label, _, cp, _ in saves:
191
- print(f" - {label} (+{cp:.1f}% cost without it)")
192
- if not saves: print(f" (none)")
193
 
194
- print(f"\n NOISE modules (removing has <2% cost and <1pp quality impact):")
195
- for _, label, _, _, _ in noise:
196
- print(f" - {label}")
197
- if not noise: print(f" (none)")
198
 
199
- print(f"\n HURTS QUALITY modules (removing reduces quality but saves cost):")
200
- for _, label, _, _, qd in hurts:
201
- print(f" - {label} ({qd:+.1f}pp quality loss)")
202
- if not hurts: print(f" (none)")
203
 
 
 
 
204
 
205
  def print_frontier_report(metrics: Dict, config_labels: Dict):
206
- """Cost-quality frontier: plot all configs on cost vs quality axis."""
207
  print(f"\n{'='*100}")
208
  print(f" COST-QUALITY FRONTIER")
209
  print(f"{'='*100}")
210
 
 
 
211
  configs = []
212
- for cn in ["A", "B", "C", "D", "E", "F", "G", "H", "I"]:
213
  m = metrics["by_config"][cn]
214
  configs.append((cn, config_labels.get(cn, cn), m["success_rate"], m["total_cost"]))
215
 
216
- # Sort by cost
217
  configs.sort(key=lambda x: x[3])
218
 
219
- print(f"\n {'Config':<40} {'Quality':>8} {'Cost':>10} {'Position':>30}")
220
- print("-" * 90)
221
 
 
222
  for cn, label, sr, cost in configs:
223
- # Visual bar
224
  bar_len = int(sr * 30)
225
- cost_bar = int(cost / max(c[3] for c in configs) * 20)
226
  bar = "█" * bar_len + "░" * (30 - bar_len)
227
  print(f" {cn}. {label:<36} {sr*100:>6.1f}% ${cost:>8.4f} {bar}")
228
 
229
- # Find Pareto-optimal configs
230
- print(f"\n Pareto-optimal configs (no other config is both cheaper AND better):")
231
  pareto = []
232
  for i, (cn, label, sr, cost) in enumerate(configs):
233
  dominated = False
234
  for j, (cn2, label2, sr2, cost2) in enumerate(configs):
235
  if i != j and sr2 >= sr and cost2 <= cost and (sr2 > sr or cost2 < cost):
236
- dominated = True
237
- break
238
- if not dominated:
239
- pareto.append((cn, label, sr, cost))
240
 
 
241
  for cn, label, sr, cost in pareto:
242
- print(f" {cn}. {label}: {sr*100:.1f}% quality at ${cost:.4f}")
243
 
244
- # Iso-quality analysis
245
- print(f"\n Iso-quality analysis (configs within ±2pp of frontier quality):")
246
- frontier_q = max(c[2] for c in configs)
247
  for cn, label, sr, cost in configs:
248
  if sr >= frontier_q - 0.02:
249
- savings = (1 - cost / max(c[3] for c in configs)) * 100
250
  print(f" {cn}. {label}: {sr*100:.1f}% at ${cost:.4f} ({savings:+.1f}% vs most expensive)")
251
 
252
-
253
  def main():
254
  n = int(sys.argv[1]) if len(sys.argv) > 1 else 20
255
  data = run_ablation(n)
256
  metrics = compute_metrics(data["results"])
257
 
258
  config_labels = {c.name: c.label for c in CONFIGS}
259
- # Add ablation labels
260
- for c in make_ablation_configs():
261
- config_labels[c.name] = c.label
262
 
263
  print_ablation_report(metrics, config_labels)
264
  print_frontier_report(metrics, config_labels)
265
 
266
- output = {
267
- "n_tasks_per_domain": n,
268
- "metrics": metrics,
269
- "config_labels": config_labels,
270
- "raw_results": data["results"],
271
- }
272
  with open("/tmp/aco_ablation_results.json", "w") as f:
273
  json.dump(output, f, indent=2)
274
  print(f"\nResults saved to /tmp/aco_ablation_results.json")
275
 
276
-
277
- if __name__ == "__main__":
278
- main()
 
14
  10. no telemetry feedback
15
 
16
  Each ablation removes one module from the full ACO and re-runs the benchmark.
17
+ Also runs all 9 baseline configs (A-I) for the frontier report.
18
  """
19
  import json, sys, random, os
20
  from dataclasses import dataclass, field, asdict
21
  from collections import defaultdict
22
  from typing import List, Dict
23
 
 
24
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) or ".")
25
  import importlib.util
26
 
 
27
  try:
28
  from benchmark_suite import (
29
  generate_tasks, simulate_task, Config, CONFIGS, MODELS,
 
45
 
46
  random.seed(42)
47
 
 
 
 
 
48
  def make_ablation_configs() -> List[Config]:
 
49
  base = dict(
50
  use_model_routing=True, use_learned_router=True,
51
  use_context_budget=True, use_cache_layout=True,
 
53
  use_retry_optimizer=True, use_meta_tools=True,
54
  use_early_termination=True, use_telemetry=True,
55
  )
56
+ return [
57
  Config("abl1", "no model router", use_model_routing=False, use_learned_router=False,
58
  use_context_budget=True, use_cache_layout=True, use_tool_gate=True,
59
  use_verifier_budget=True, use_retry_optimizer=True, use_meta_tools=True,
 
72
  use_early_termination=True, use_telemetry=True),
73
  Config("abl10", "no telemetry feedback", **{**base, "use_telemetry": False}),
74
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  def run_ablation(n_per_domain: int = 20) -> Dict:
77
+ """Run all baselines + 10 ablations."""
78
  tasks = generate_tasks(n_per_domain)
79
  full_aco = Config("I", "full ACO", use_model_routing=True, use_learned_router=True,
80
  use_context_budget=True, use_cache_layout=True, use_tool_gate=True,
81
  use_verifier_budget=True, use_retry_optimizer=True, use_meta_tools=True,
82
  use_early_termination=True, use_telemetry=True)
83
+ all_configs = list(CONFIGS) + make_ablation_configs()
84
 
85
  print(f"Generated {len(tasks)} tasks across 5 domains")
86
+ print(f"Running {len(all_configs)} configs (9 baselines + 10 ablations) x {len(tasks)} tasks\n")
87
 
88
  all_results = []
89
  for config in all_configs:
90
  print(f" {config.name}: {config.label}...", end=" ", flush=True)
91
  for task in tasks:
 
92
  if config.name == "abl9":
 
93
  result = simulate_task(config, task)
 
94
  if result["tier"] == 1:
95
+ result["model"] = "gpt-5-mini"; result["tier"] = 2
 
 
96
  mi = MODELS["gpt-5-mini"]
97
  result["cost"] = round(
98
  (result["input_tokens"] / 1_000_000) * mi["cost_in"] +
 
106
 
107
  return {"tasks": [asdict(t) for t in tasks], "results": all_results}
108
 
 
109
  def print_ablation_report(metrics: Dict, config_labels: Dict):
110
  print(f"\n{'='*100}")
111
  print(f" ACO ABLATION REPORT - Which Modules Actually Save Money?")
 
118
  print("-" * 100)
119
 
120
  verdicts = []
121
+ for abl_name in [f"abl{i}" for i in range(1, 11)]:
122
+ if abl_name not in metrics["by_config"]:
123
+ continue
124
  m = metrics["by_config"][abl_name]
125
  label = config_labels.get(abl_name, abl_name)
126
  cost_delta = m["total_cost"] - full["total_cost"]
127
  cost_pct = (cost_delta / full["total_cost"]) * 100 if full["total_cost"] > 0 else 0
128
  quality_delta = (m["success_rate"] - full["success_rate"]) * 100
129
 
130
+ if quality_delta < -3: verdict = "CRITICAL"
131
+ elif cost_delta > 0 and quality_delta < -1: verdict = "HURTS QUALITY"
132
+ elif cost_delta > 0.02: verdict = "SAVES MONEY"
133
+ elif abs(cost_pct) < 2 and abs(quality_delta) < 1: verdict = "NOISE"
134
+ elif cost_delta < 0 and quality_delta >= 0: verdict = "COST INCREASE"
135
+ else: verdict = "MARGINAL"
 
 
 
 
 
 
 
136
 
137
  verdicts.append((abl_name, label, verdict, cost_pct, quality_delta))
138
  print(f" {label:<38} {m['success_rate']*100:>6.1f}% ${m['total_cost']:>8.4f} "
 
148
  hurts = [v for v in verdicts if v[2] == "HURTS QUALITY"]
149
  marginal = [v for v in verdicts if v[2] == "MARGINAL"]
150
 
151
+ print(f"\n CRITICAL (removing causes >3pp quality loss):")
152
+ for _, label, _, _, qd in critical: print(f" - {label} ({qd:+.1f}pp)")
153
+ if not critical: print(" (none)")
 
154
 
155
+ print(f"\n SAVES MONEY (removing increases cost):")
156
+ for _, label, _, cp, _ in saves: print(f" - {label} (+{cp:.1f}% cost without it)")
157
+ if not saves: print(" (none)")
 
158
 
159
+ print(f"\n NOISE (removing has <2% cost, <1pp quality):")
160
+ for _, label, _, _, _ in noise: print(f" - {label}")
161
+ if not noise: print(" (none)")
 
162
 
163
+ print(f"\n HURTS QUALITY (removing reduces quality but saves cost):")
164
+ for _, label, _, _, qd in hurts: print(f" - {label} ({qd:+.1f}pp)")
165
+ if not hurts: print(" (none)")
 
166
 
167
+ print(f"\n MARGINAL (small effect either way):")
168
+ for _, label, _, _, _ in marginal: print(f" - {label}")
169
+ if not marginal: print(" (none)")
170
 
171
  def print_frontier_report(metrics: Dict, config_labels: Dict):
 
172
  print(f"\n{'='*100}")
173
  print(f" COST-QUALITY FRONTIER")
174
  print(f"{'='*100}")
175
 
176
+ # Use all configs that exist in metrics
177
+ available = [k for k in ["A","B","C","D","E","F","G","H","I"] if k in metrics["by_config"]]
178
  configs = []
179
+ for cn in available:
180
  m = metrics["by_config"][cn]
181
  configs.append((cn, config_labels.get(cn, cn), m["success_rate"], m["total_cost"]))
182
 
 
183
  configs.sort(key=lambda x: x[3])
184
 
185
+ print(f"\n {'Config':<40} {'Quality':>8} {'Cost':>10} {'Visual':>35}")
186
+ print("-" * 95)
187
 
188
+ max_cost = max(c[3] for c in configs) if configs else 1
189
  for cn, label, sr, cost in configs:
 
190
  bar_len = int(sr * 30)
 
191
  bar = "█" * bar_len + "░" * (30 - bar_len)
192
  print(f" {cn}. {label:<36} {sr*100:>6.1f}% ${cost:>8.4f} {bar}")
193
 
 
 
194
  pareto = []
195
  for i, (cn, label, sr, cost) in enumerate(configs):
196
  dominated = False
197
  for j, (cn2, label2, sr2, cost2) in enumerate(configs):
198
  if i != j and sr2 >= sr and cost2 <= cost and (sr2 > sr or cost2 < cost):
199
+ dominated = True; break
200
+ if not dominated: pareto.append((cn, label, sr, cost))
 
 
201
 
202
+ print(f"\n Pareto-optimal (no other config is both cheaper AND better):")
203
  for cn, label, sr, cost in pareto:
204
+ print(f" {cn}. {label}: {sr*100:.1f}% at ${cost:.4f}")
205
 
206
+ frontier_q = max(c[2] for c in configs) if configs else 0
207
+ print(f"\n Iso-quality configs (within ±2pp of best quality {frontier_q*100:.1f}%):")
 
208
  for cn, label, sr, cost in configs:
209
  if sr >= frontier_q - 0.02:
210
+ savings = (1 - cost / max_cost) * 100
211
  print(f" {cn}. {label}: {sr*100:.1f}% at ${cost:.4f} ({savings:+.1f}% vs most expensive)")
212
 
 
213
  def main():
214
  n = int(sys.argv[1]) if len(sys.argv) > 1 else 20
215
  data = run_ablation(n)
216
  metrics = compute_metrics(data["results"])
217
 
218
  config_labels = {c.name: c.label for c in CONFIGS}
219
+ for c in make_ablation_configs(): config_labels[c.name] = c.label
 
 
220
 
221
  print_ablation_report(metrics, config_labels)
222
  print_frontier_report(metrics, config_labels)
223
 
224
+ output = {"n_tasks_per_domain": n, "metrics": metrics,
225
+ "config_labels": config_labels, "raw_results": data["results"]}
 
 
 
 
226
  with open("/tmp/aco_ablation_results.json", "w") as f:
227
  json.dump(output, f, indent=2)
228
  print(f"\nResults saved to /tmp/aco_ablation_results.json")
229
 
230
+ if __name__ == "__main__": main()