SaylorTwift HF Staff commited on
Commit
371ff91
·
verified ·
1 Parent(s): e7168a9

Eval cells now record turns/tool_calls/completion+prompt tokens; summary gets efficiency-vs-base ratios

Browse files
Files changed (2) hide show
  1. eval_adapter.py +15 -5
  2. eval_watch.py +39 -13
eval_adapter.py CHANGED
@@ -65,15 +65,25 @@ def run_one(task_index, model):
65
  kwargs.pop("eval_sampling")
66
  result = env.run_rollout(**kwargs)
67
  except Exception as exc:
68
- return task_index, model, None, True, f"{type(exc).__name__}: {exc}"
69
  finally:
70
  try:
71
  env.close()
72
  except Exception:
73
  pass
74
  if not result.ok or result.reward is None:
75
- return task_index, model, None, True, getattr(result, "error", None) or "not ok"
76
- return task_index, model, float(result.reward) == 1.0, False, None
 
 
 
 
 
 
 
 
 
 
77
 
78
 
79
  def main():
@@ -87,8 +97,8 @@ def main():
87
  futures = [pool.submit(run_one, i, m) for i, m in work]
88
  done = 0
89
  for fut in as_completed(futures):
90
- i, m, correct, failed, err = fut.result()
91
- cells[(i, m)] = {"correct": correct, "failed": failed, "error": err}
92
  done += 1
93
  if done % 20 == 0 or done == len(work):
94
  print(f"[eval] {done}/{len(work)} cells done", flush=True)
 
65
  kwargs.pop("eval_sampling")
66
  result = env.run_rollout(**kwargs)
67
  except Exception as exc:
68
+ return task_index, model, None, True, f"{type(exc).__name__}: {exc}", None
69
  finally:
70
  try:
71
  env.close()
72
  except Exception:
73
  pass
74
  if not result.ok or result.reward is None:
75
+ return task_index, model, None, True, getattr(result, "error", None) or "not ok", None
76
+ turns = getattr(result, "turns", []) or []
77
+ metrics = {
78
+ "turns": getattr(result, "n_turns", 0) or len(turns),
79
+ "tool_calls": sum(getattr(t, "n_tools", 0) for t in turns),
80
+ "completion_tokens": sum(len(getattr(t, "completion_token_ids", []) or []) for t in turns),
81
+ # Each turn resends the whole conversation, so this grows ~quadratically
82
+ # with turn count — the quantity the article's input-token savings cut.
83
+ "prompt_tokens_total": sum(len(getattr(t, "prompt_token_ids", []) or []) for t in turns),
84
+ "wall_s": getattr(result, "wall_s", 0.0),
85
+ }
86
+ return task_index, model, float(result.reward) == 1.0, False, None, metrics
87
 
88
 
89
  def main():
 
97
  futures = [pool.submit(run_one, i, m) for i, m in work]
98
  done = 0
99
  for fut in as_completed(futures):
100
+ i, m, correct, failed, err, metrics = fut.result()
101
+ cells[(i, m)] = {"correct": correct, "failed": failed, "error": err, "metrics": metrics}
102
  done += 1
103
  if done % 20 == 0 or done == len(work):
104
  print(f"[eval] {done}/{len(work)} cells done", flush=True)
eval_watch.py CHANGED
@@ -62,23 +62,42 @@ def lora_rpc(route, name, path=None):
62
  urllib.request.urlopen(req, timeout=900)
63
 
64
 
65
- def pass_at_1(cells_path):
 
66
  cells = json.loads(Path(cells_path).read_text())["cells"]
67
  scored = [c["correct"] for c in cells.values() if not c["failed"]]
68
- return (sum(scored) / len(scored)) if scored else None, len(scored)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
 
71
  def main():
72
  fs = repo_files()
73
  # 1. Base-model cells, once. Every checkpoint compares against these.
74
- if "eval/base_cells.json" not in fs:
 
 
 
 
 
 
 
 
75
  print("[watch] evaluating base model first", flush=True)
76
  run_eval(BASE_MODEL, "/tmp/base_cells.json")
77
  upload("/tmp/base_cells.json", "eval/base_cells.json")
78
- elif not Path("/tmp/base_cells.json").exists():
79
- # A previous worker already computed base cells: reuse them locally.
80
- local = api.hf_hub_download(REPO, "eval/base_cells.json")
81
- Path("/tmp/base_cells.json").write_text(Path(local).read_text())
82
 
83
  summary = {}
84
  if "eval/summary.json" in fs:
@@ -105,14 +124,21 @@ def main():
105
  print(f"[watch] {ckpt} eval failed ({exc}); will retry next poll", flush=True)
106
  continue
107
  upload(f"/tmp/{ckpt}_cells.json", f"eval/{ckpt}_results.json")
108
- p1, n = pass_at_1(f"/tmp/{ckpt}_cells.json")
109
- pb, nb = pass_at_1("/tmp/base_cells.json")
110
- summary[ckpt] = {"pass_at_1": p1, "scorable": n,
111
- "base_pass_at_1": pb,
112
- "delta": (p1 - pb) if p1 is not None and pb is not None else None}
 
 
 
 
 
113
  Path("/tmp/summary.json").write_text(json.dumps(summary, indent=2))
114
  upload("/tmp/summary.json", "eval/summary.json")
115
- print(f"[watch] {ckpt}: pass@1={p1} (base {pb})", flush=True)
 
 
116
  time.sleep(POLL_S)
117
 
118
 
 
62
  urllib.request.urlopen(req, timeout=900)
63
 
64
 
65
+ def agg(cells_path):
66
+ """pass@1 plus efficiency means over solved tasks (the article's second axis)."""
67
  cells = json.loads(Path(cells_path).read_text())["cells"]
68
  scored = [c["correct"] for c in cells.values() if not c["failed"]]
69
+ solved = [c["metrics"] for c in cells.values() if c["correct"] and c.get("metrics")]
70
+
71
+ def mean(key):
72
+ vals = [m[key] for m in solved if key in m]
73
+ return round(sum(vals) / len(vals), 1) if vals else None
74
+
75
+ return {
76
+ "pass_at_1": (sum(scored) / len(scored)) if scored else None,
77
+ "scorable": len(scored),
78
+ "solved": len(solved),
79
+ "tool_calls_solved_mean": mean("tool_calls"),
80
+ "turns_solved_mean": mean("turns"),
81
+ "completion_tokens_solved_mean": mean("completion_tokens"),
82
+ "prompt_tokens_solved_mean": mean("prompt_tokens_total"),
83
+ }
84
 
85
 
86
  def main():
87
  fs = repo_files()
88
  # 1. Base-model cells, once. Every checkpoint compares against these.
89
+ # Recomputed if the cached file predates per-cell metrics.
90
+ if "eval/base_cells.json" in fs:
91
+ local = api.hf_hub_download(REPO, "eval/base_cells.json")
92
+ Path("/tmp/base_cells.json").write_text(Path(local).read_text())
93
+ if "eval/base_cells.json" not in fs or any(
94
+ c.get("metrics") is None
95
+ for c in json.loads(Path("/tmp/base_cells.json").read_text())["cells"].values()
96
+ if c["correct"]
97
+ ):
98
  print("[watch] evaluating base model first", flush=True)
99
  run_eval(BASE_MODEL, "/tmp/base_cells.json")
100
  upload("/tmp/base_cells.json", "eval/base_cells.json")
 
 
 
 
101
 
102
  summary = {}
103
  if "eval/summary.json" in fs:
 
124
  print(f"[watch] {ckpt} eval failed ({exc}); will retry next poll", flush=True)
125
  continue
126
  upload(f"/tmp/{ckpt}_cells.json", f"eval/{ckpt}_results.json")
127
+ a, b = agg(f"/tmp/{ckpt}_cells.json"), agg("/tmp/base_cells.json")
128
+ entry = dict(a)
129
+ entry["base_pass_at_1"] = b["pass_at_1"]
130
+ if a["pass_at_1"] is not None and b["pass_at_1"] is not None:
131
+ entry["delta"] = a["pass_at_1"] - b["pass_at_1"]
132
+ for key in ("tool_calls_solved_mean", "turns_solved_mean",
133
+ "completion_tokens_solved_mean", "prompt_tokens_solved_mean"):
134
+ if a.get(key) is not None and b.get(key):
135
+ entry[key.replace("_mean", "_vs_base")] = round(a[key] / b[key], 3)
136
+ summary[ckpt] = entry
137
  Path("/tmp/summary.json").write_text(json.dumps(summary, indent=2))
138
  upload("/tmp/summary.json", "eval/summary.json")
139
+ print(f"[watch] {ckpt}: pass@1={a['pass_at_1']} (base {b['pass_at_1']}), "
140
+ f"tool_calls={a['tool_calls_solved_mean']} (base {b['tool_calls_solved_mean']})",
141
+ flush=True)
142
  time.sleep(POLL_S)
143
 
144