Spaces:
Running
Running
File size: 20,425 Bytes
5741b22 125a807 5741b22 ad552a7 5741b22 125a807 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 ad552a7 5741b22 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | #!/usr/bin/env python3
"""Post-hoc, task-weighted ProgramBench pilot curves. Never uses best-so-far scores."""
from __future__ import annotations
import argparse
from bisect import bisect_right
from contextlib import closing
import csv
from datetime import datetime, timezone
import json
import math
from pathlib import Path
import sqlite3
SCHEDULE = [0, 30, 60, 120, 300, 600, 1200, 2400, 4800, 7200]
POST_HOC_SCOPE = {
"kind": "post_hoc_subset",
"included_tasks": ["jqlang__jq.b33a763", "burntsushi__xsv.f430466",
"cmatsuoka__figlet.202a0a8", "sharkdp__hexyl.2e26437"],
"excluded_tasks": ["burntsushi__ripgrep.3b7fd44"],
"exclusion_reason": "slow_saved_artifact_grading",
}
CONDITIONS = ["single", "peers", "async"]
AXES = ["wall_seconds", "reference_seconds", "api_prefill_reference_seconds"]
CLOCK_EVENT_TYPES = {"episode_start", "agent_created", "message_sent", "message_delivered",
"tool_start", "tool_end", "snapshot", "model_request_end"}
CLOCK_EVENT_FIELDS = {"type", "elapsed_ms", "sequence", "utc", "agent_id", "parent_id",
"causal_event_id", "duration_ms", "path", "request_id", "id"}
def analysis_scope(source):
"""Validate the disclosed task-level exclusion; never change the original evidence."""
if source is None:
return None
if not isinstance(source, dict) or set(source) != set(POST_HOC_SCOPE):
raise ValueError("Unexpected analysis scope")
for key, expected in POST_HOC_SCOPE.items():
value = source[key]
if isinstance(expected, list):
if not isinstance(value, list) or len(value) != len(expected) or set(value) != set(expected):
raise ValueError("Unexpected analysis task subset")
elif value != expected:
raise ValueError("Unexpected analysis scope category")
return {key: list(value) if isinstance(value, list) else value for key, value in POST_HOC_SCOPE.items()}
def finite(value):
return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)
def read_json(path, default):
return json.loads(path.read_text()) if path.exists() else default
def read_clock_events(path):
"""Read actual JSONL records, retaining only causal-clock metadata.
str.splitlines() also splits Unicode separators inside valid JSON strings.
File iteration respects JSONL newlines and bounds transcript memory to a single
record instead of retaining every repeated prompt and tool response.
"""
events, has_origin = [], False
if not path.exists():
return events
with path.open(encoding="utf-8") as stream:
for line in stream:
if not line.strip():
continue
event = json.loads(line)
is_origin = bool(event.get("utc")) and not has_origin
if is_origin or event.get("type") in CLOCK_EVENT_TYPES:
events.append({key: value for key, value in event.items() if key in CLOCK_EVENT_FIELDS})
has_origin = has_origin or is_origin
return events
def epoch(value):
return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
def clock_origin(events):
return next((epoch(event["utc"]) - event.get("elapsed_ms", 0) / 1000
for event in events if event.get("utc")), None)
def clock_trace(events, requests, prefill_tps=10_000, decode_tps=265):
"""Two causal clocks from gateway operations and measured tool durations.
Primary prefill counts estimated new context input; sensitivity prefill counts
every billed prompt token. Gateway start/end times also capture compaction calls
that bypass Pi's regular generation hooks. Only completed operations add time.
"""
timeline, warnings = [], []
origin = clock_origin(events)
for index, event in enumerate(events):
if finite(event.get("elapsed_ms")):
timeline.append((event["elapsed_ms"] / 1000, index, event))
ledger_ids = {r.get("client_request_id") for r in requests if r.get("client_request_id")}
observed_ids = {e.get("request_id") for e in events if e.get("type") == "model_request_end"}
if observed_ids - ledger_ids:
warnings.append("model_events_missing_from_gateway_ledger")
for index, row in enumerate(requests):
if row.get("status") == "rejected":
continue
output, prompt, context = row.get("completion_tokens"), row.get("prompt_tokens"), row.get("context_charged")
if not all(finite(v) and v >= 0 for v in (output, prompt, context)) or not row.get("ended_at"):
warnings.append("request_has_unknown_usage_or_end_time")
continue
if origin is None:
warnings.append("missing_episode_clock_origin")
continue
start, end = epoch(row["started_at"]) - origin, epoch(row["ended_at"]) - origin
if end < start:
warnings.append("invalid_gateway_timestamps")
continue
ident = row["id"]
durations = (max(0, context - output) / prefill_tps + output / decode_tps,
prompt / prefill_tps + output / decode_tps)
timeline.extend([(start, -2, {"type": "clock_model_start", "agent_id": row["agent_id"], "id": ident}),
(end, -1, {"type": "clock_model_end", "agent_id": row["agent_id"], "id": ident,
"durations": durations})])
agents, sent, starts = {}, {}, {}
trace = [(0.0, 0.0, 0.0)]
snapshot_clocks = {}
for wall, _, event in sorted(timeline, key=lambda row: (row[0], row[1])):
kind, agent = event.get("type"), event.get("agent_id")
if agent:
agents.setdefault(agent, (0.0, 0.0))
if kind == "agent_created":
agents[agent] = agents.get(event.get("parent_id"), (0.0, 0.0))
elif kind == "message_sent":
sent[event.get("sequence")] = agents.get(agent, (0.0, 0.0))
elif kind == "message_delivered":
inherited = sent.get(event.get("causal_event_id"))
if inherited is None:
warnings.append("missing_message_send_event")
else:
agents[agent] = tuple(max(a, b) for a, b in zip(agents[agent], inherited))
elif kind in ("clock_model_start", "tool_start"):
starts[(kind, event.get("id", event.get("sequence")))] = agents.get(agent, (0.0, 0.0))
elif kind in ("clock_model_end", "tool_end"):
if kind == "clock_model_end":
key, durations = ("clock_model_start", event["id"]), event["durations"]
else:
duration = event.get("duration_ms")
if not finite(duration) or duration < 0:
warnings.append("invalid_tool_duration")
continue
key, durations = ("tool_start", event.get("causal_event_id")), (duration / 1000,) * 2
anchor = starts.get(key, agents.get(agent, (0.0, 0.0)))
agents[agent] = tuple(max(current, begin + duration)
for current, begin, duration in zip(agents[agent], anchor, durations))
current = tuple(max((clock[i] for clock in agents.values()), default=0.0) for i in (0, 1))
trace.append((max(0, wall), *current))
if kind == "snapshot" and event.get("path"):
snapshot_clocks[str(event["path"])] = current
if not events:
warnings.append("missing_runner_events")
return {"trace": trace, "snapshot_clocks": snapshot_clocks,
"valid": not warnings, "warnings": sorted(set(warnings))}
def clocks_at(clocks, wall, snapshot_path=None):
if snapshot_path in clocks["snapshot_clocks"]:
return clocks["snapshot_clocks"][snapshot_path]
rows = clocks["trace"]
index = bisect_right([row[0] for row in rows], wall) - 1
return rows[max(0, index)][1:]
def score_at(points, time_value, axis):
eligible = [point for point in points if point[axis] <= time_value]
if not eligible:
return 0.0, False
# Ordering includes original wall time for tied normalized clocks. Regressions stay.
latest = max(eligible, key=lambda point: (point[axis], point["wall_seconds"], point["order"]))
return latest["score"], True
def attempt_data(directory, run, requests, prefill_tps, decode_tps):
"""Score one saved attempt using only requests belonging to its solver clock."""
result = read_json(directory / "runner" / "result.json", {})
controller_status = read_json(directory / "status.json", {})
event_path = directory / "runner" / "events.jsonl"
events = read_clock_events(event_path)
elapsed = result.get("elapsed_seconds")
origin = clock_origin(events)
attempt_requests = requests
if origin is not None:
end = origin + elapsed if finite(elapsed) else math.inf
# Event UTC timestamps have millisecond precision. Include that rounding
# tolerance while excluding old attempts even if worker request IDs repeat.
attempt_requests = [row for row in requests
if origin - 0.001 <= epoch(row["started_at"]) <= end + 0.001]
clocks = clock_trace(events, attempt_requests, prefill_tps, decode_tps)
grades = read_json(directory / "grades.json", [])
if isinstance(grades, dict):
grades = grades.get("grades", [])
points, invalid_grades, discarded_grades, latest_grade = [], 0, 0, None
for order, grade in enumerate(grades):
score = grade.get("score")
valid = grade.get("valid", True)
if score is None and grade.get("sha256"):
summary = read_json(directory / "evaluations" / grade["sha256"] / run["task"] / "summary.json", {})
score = summary.get("analysis_score", summary.get("score"))
valid = valid and summary.get("valid", summary.get("complete", False))
wall = grade.get("elapsed_seconds")
valid = bool(valid and finite(score) and 0 <= score <= 1 and finite(wall) and wall >= 0)
if not valid:
invalid_grades += 1
# The original controller also repeated the final archive at scheduled
# cutoffs beyond early termination. These are not additional solver states.
if finite(elapsed) and finite(wall) and wall > elapsed + 1e-6:
discarded_grades += 1
continue
if finite(wall) and wall >= 0:
candidate = (wall, order, valid)
if latest_grade is None or candidate[:2] >= latest_grade[:2]:
latest_grade = candidate
if not valid:
continue
primary, sensitivity = clocks_at(clocks, wall, grade.get("snapshot_path"))
points.append({"order": order, "wall_seconds": wall, "reference_seconds": primary,
"api_prefill_reference_seconds": sensitivity, "score": score})
points.sort(key=lambda point: (point["wall_seconds"], point["order"]))
last_score = points[-1]["score"] if points else 0.0
stop_reason = controller_status.get("stop_reason", result.get("stop_reason", "missing_result"))
status = controller_status.get("status")
if status not in {"completed", "failed", "interrupted", "budget_exhausted", "running", "pending"}:
status = ("completed" if stop_reason in {"completed", "wall_time_limit", "wall_time_exhausted"}
else "budget_exhausted" if "budget" in stop_reason or stop_reason == "daily_quota_headroom"
else "interrupted" if stop_reason == "interrupted" else "failed")
terminal_valid = (latest_grade is not None and latest_grade[2] and finite(elapsed)
and abs(latest_grade[0] - elapsed) <= 1e-6)
return {"status": status, "stop_reason": stop_reason, "elapsed_seconds": elapsed,
"final_score": last_score, "has_valid_grade": bool(points), "invalid_grade_count": invalid_grades,
"has_valid_final_grade": bool(terminal_valid), "discarded_scheduled_grade_count": discarded_grades,
"clock_valid": clocks["valid"], "clock_warnings": clocks["warnings"], "points": points,
"clock_total": clocks_at(clocks, elapsed) if finite(elapsed) else clocks["trace"][-1][1:]}
def episode_data(root, run, requests, prefill_tps, decode_tps):
history = root / "recovery-attempts" / run["run_id"]
archives = sorted((path for path in history.glob("attempt-*") if path.is_dir()),
key=lambda path: int(path.name.removeprefix("attempt-")))
directories = archives + [root / "episodes" / run["run_id"]]
points, attempts, wall_offset, reference_offset, sensitivity_offset = [], [], 0.0, 0.0, 0.0
warnings = set()
for index, directory in enumerate(directories):
attempt = attempt_data(directory, run, requests, prefill_tps, decode_tps)
if index < len(archives) and not finite(attempt["elapsed_seconds"]):
raise ValueError(f"Archived attempt has no solver duration: {run['run_id']} attempt {index + 1}")
for point in attempt["points"]:
points.append({**point, "order": len(points),
"wall_seconds": wall_offset + point["wall_seconds"],
"reference_seconds": reference_offset + point["reference_seconds"],
"api_prefill_reference_seconds": sensitivity_offset + point["api_prefill_reference_seconds"]})
wall_offset += attempt["elapsed_seconds"] if finite(attempt["elapsed_seconds"]) else 0
primary, sensitivity = attempt["clock_total"]
reference_offset += primary
sensitivity_offset += sensitivity
warnings.update(attempt["clock_warnings"])
attempts.append({key: value for key, value in attempt.items() if key not in {"points", "clock_total"}})
attempts[-1]["attempt"] = index + 1
active = attempts[-1]
return {"run_id": run["run_id"], "task": run["task"], "condition": run["condition"],
"status": active["status"], "stop_reason": active["stop_reason"],
"elapsed_seconds": wall_offset if finite(active["elapsed_seconds"]) else None,
"final_score": points[-1]["score"] if points else 0.0, "has_valid_grade": bool(points),
"has_valid_final_grade": active["has_valid_final_grade"],
"invalid_grade_count": sum(attempt["invalid_grade_count"] for attempt in attempts),
"current_attempt_invalid_grade_count": active["invalid_grade_count"],
"attempt_count": len(attempts), "attempts": attempts,
"clock_valid": all(attempt["clock_valid"] for attempt in attempts),
"clock_warnings": sorted(warnings), "points": points,
"api_tokens_charged": sum(row.get("charged", 0) for row in requests)}
def aggregate_at(episodes, tasks, condition, time_value, axis):
selected = [row for row in episodes if row["condition"] == condition]
clock_valid = axis == "wall_seconds" or all(row["clock_valid"] for row in selected)
task_scores, observed = [], 0
for task in tasks:
repetitions = [row for row in selected if row["task"] == task]
values = [score_at(row["points"], time_value, axis) for row in repetitions]
task_scores.append(sum(value for value, _ in values) / len(values) if values else 0.0)
observed += int(any(valid for _, valid in values))
return {"axis": axis, "time_seconds": time_value, "condition": condition,
"mean_hidden_test_fraction": sum(task_scores) / len(tasks) if clock_valid else None,
"tasks_total": len(tasks), "tasks_with_valid_grade": observed,
"episodes_configured": len(selected), "clock_valid": clock_valid}
def analyze(experiment_dir, config, prefill_tps=10_000, decode_tps=265):
if prefill_tps <= 0 or decode_tps <= 0:
raise ValueError("Reference rates must be positive")
root = Path(experiment_dir)
tasks = [task["instance_id"] for task in config["tasks"]]
if not tasks or len(tasks) != len(set(tasks)):
raise ValueError("Selected task IDs must be nonempty and unique")
runs = [row for row in config["runs"] if row.get("category", "benchmark") == "benchmark"]
if len({run["run_id"] for run in runs}) != len(runs):
raise ValueError("Duplicate run ID")
if any(run["task"] not in tasks or run["condition"] not in CONDITIONS for run in runs):
raise ValueError("Unexpected task or condition")
requests = []
ledger = root / "budget.sqlite"
if ledger.exists():
with closing(sqlite3.connect(f"file:{ledger.resolve()}?mode=ro", uri=True)) as db:
db.row_factory = sqlite3.Row
requests = [dict(row) for row in db.execute("SELECT * FROM requests")]
episodes = [episode_data(root, run, [row for row in requests if row["run_id"] == run["run_id"]],
prefill_tps, decode_tps) for run in runs]
curves = []
for axis in AXES:
grid = set(SCHEDULE if axis == "wall_seconds" else [0.0])
grid.update(point[axis] for row in episodes for point in row["points"])
for instant in sorted(grid):
curves.extend(aggregate_at(episodes, tasks, condition, instant, axis) for condition in CONDITIONS)
final = {}
for condition in CONDITIONS:
scores = []
selected = [row for row in episodes if row["condition"] == condition]
for task in tasks:
values = [row["final_score"] for row in selected if row["task"] == task]
scores.append(sum(values) / len(values) if values else 0.0)
final[condition] = {"mean_hidden_test_fraction": sum(scores) / len(tasks),
"tasks_total": len(tasks),
"tasks_with_valid_grade": len({row["task"] for row in selected if row["has_valid_grade"]}),
"episodes_configured": len(selected),
"episodes_completed": sum(row["status"] == "completed" for row in selected)}
summary = {"generated_at_utc": datetime.now(timezone.utc).isoformat(), "tasks": tasks,
"aggregation": "mean hidden-test fraction within each task, then equal-weight mean over every selected task",
"failure_policy": "latest valid score carried forward; zero before any valid grade; no best-so-far filtering; retries accumulate prior solver and normalized time; scheduled grades after attempt end excluded",
"reference_clock": {"decode_tokens_per_second": decode_tps, "prefill_tokens_per_second": prefill_tps,
"primary_prefill": "estimated newly entering context tokens",
"sensitivity_prefill": "all API prompt tokens",
"label": "operational normalization; assumed reference rates; not Anthropic's unknown constants",
"includes": "completed model operations, actual tool durations, parent/message causal handoffs",
"limitation": "no partial in-flight decode/tool progress at snapshots"},
"uncertainty": "exploratory five-task convenience sample; one repetition; no confidence intervals",
"final": final, "episodes": episodes}
root.mkdir(parents=True, exist_ok=True)
(root / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
with (root / "curves.csv").open("w", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=list(curves[0]))
writer.writeheader()
writer.writerows(curves)
return summary, curves
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--experiment-dir", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--reference-prefill-tps", type=float, default=10_000)
parser.add_argument("--reference-decode-tps", type=float, default=265)
args = parser.parse_args()
summary, curves = analyze(args.experiment_dir, json.loads(args.config.read_text()),
args.reference_prefill_tps, args.reference_decode_tps)
print(json.dumps({"summary": str(args.experiment_dir / "summary.json"),
"curves": str(args.experiment_dir / "curves.csv"), "final": summary["final"]}))
if __name__ == "__main__":
main()
|