sasukeUchiha123 commited on
Commit
f2dd0a9
Β·
verified Β·
1 Parent(s): 6920f9b

Upload agent/tools/compare_runs.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. agent/tools/compare_runs.py +234 -0
agent/tools/compare_runs.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """compare_runs tool β€” build the side-by-side Report from two RunMetrics + Patch.
2
+
3
+ Mostly a pure transform. Resilient to one specific LLM failure mode:
4
+ sometimes the model collapses the Patch dict down to its ``new_config`` fields
5
+ when forwarding it between turns (passing
6
+ ``patch={"precision": "bf16", "attention_impl": "flash_rocm", ...}`` instead of
7
+ ``patch={"new_config": {...}, "diff": ..., "rationale": [...], ...}``).
8
+ ``_normalize_patch`` detects that shape and either:
9
+
10
+ 1. Substitutes the cached ``propose_patch`` result if available
11
+ (preferred β€” preserves rationale, diff, predicted speedup, confidence).
12
+ 2. Wraps the flat config as a minimal Patch (fallback β€” Report has empty
13
+ rationale and no predicted speedup, but still ships).
14
+
15
+ Either way the audit produces a Report instead of bailing on a Pydantic
16
+ ValidationError.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Any
22
+
23
+ from agent.schemas import (
24
+ MetricDelta,
25
+ Patch,
26
+ Report,
27
+ RunMetrics,
28
+ ToolResult,
29
+ WorkloadConfig,
30
+ )
31
+ from agent.tools import Tool
32
+
33
+
34
+ # Patch-shape sentinel keys: a real Patch dict has at minimum new_config + diff.
35
+ _PATCH_KEYS = {"new_config", "diff", "rationale", "expected_speedup_low"}
36
+
37
+ # WorkloadConfig sentinel keys: presence of any of these (without _PATCH_KEYS)
38
+ # means the LLM passed a flat WorkloadConfig instead of a Patch envelope.
39
+ # Broader than just the "always-set" fields β€” Qwen has been observed to send
40
+ # only the *changed* fields after propose_patch (e.g. just the dataloader
41
+ # group), so we accept any WorkloadConfig field name as a signal.
42
+ _FLAT_CONFIG_KEYS = frozenset(
43
+ {
44
+ "model_name",
45
+ "precision",
46
+ "attention_impl",
47
+ "batch_size",
48
+ "grad_accum_steps",
49
+ "seq_len",
50
+ "optimizer",
51
+ "gradient_checkpointing",
52
+ "lora_rank",
53
+ "dataloader_workers",
54
+ "dataloader_pin_memory",
55
+ "dataloader_prefetch_factor",
56
+ "dataloader_persistent_workers",
57
+ "torch_compile",
58
+ "lr",
59
+ "warmup_steps",
60
+ "env_vars",
61
+ }
62
+ )
63
+
64
+
65
+ def _looks_like_flat_config(d: dict[str, Any]) -> bool:
66
+ """Return True iff `d` looks like a flat WorkloadConfig (or partial diff)
67
+ rather than a Patch envelope.
68
+
69
+ A real Patch always carries at least one of `_PATCH_KEYS` (`new_config`,
70
+ `diff`, etc.). If none of those are present and at least one
71
+ WorkloadConfig field is, the LLM almost certainly forwarded a flat config
72
+ or a partial diff. ``_normalize_patch`` then recovers via the cached
73
+ propose_patch result.
74
+ """
75
+ if not isinstance(d, dict):
76
+ return False
77
+ if any(k in d for k in _PATCH_KEYS):
78
+ return False
79
+ return any(k in d for k in _FLAT_CONFIG_KEYS)
80
+
81
+
82
+ def _normalize_patch(patch: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
83
+ """Return ``(patch_dict, notes)`` β€” never raises on a malformed input.
84
+
85
+ If the LLM passed a flat WorkloadConfig dict instead of a Patch envelope,
86
+ we recover by checking ``propose_patch.latest_patch()`` first (full
87
+ fidelity) and falling back to wrapping the flat config (low fidelity).
88
+ """
89
+ notes: list[str] = []
90
+ if isinstance(patch, dict) and any(k in patch for k in _PATCH_KEYS):
91
+ return patch, notes
92
+
93
+ if _looks_like_flat_config(patch):
94
+ # Try the cached patch first β€” preserves rationale + diff + uplift.
95
+ from agent.tools.propose_patch import latest_patch as _latest_patch
96
+
97
+ cached = _latest_patch()
98
+ if cached is not None:
99
+ notes.append(
100
+ "patch arg looked like a flat WorkloadConfig; substituted the "
101
+ "cached propose_patch result so rationale and diff survive."
102
+ )
103
+ return cached, notes
104
+
105
+ # No cache β€” wrap the flat config minimally so Report still renders.
106
+ try:
107
+ wrapped_cfg = WorkloadConfig.model_validate(
108
+ {"model_name": "unknown", **patch}
109
+ ).model_dump()
110
+ except Exception:
111
+ wrapped_cfg = patch
112
+ notes.append(
113
+ "patch arg looked like a flat WorkloadConfig and no cached "
114
+ "propose_patch result was available; synthesized a minimal "
115
+ "Patch (rationale empty, no predicted speedup)."
116
+ )
117
+ return (
118
+ {
119
+ "new_config": wrapped_cfg,
120
+ "diff": "(diff unavailable β€” patch was passed as flat config)",
121
+ "rationale": [],
122
+ "expected_speedup_low": 1.0,
123
+ "expected_speedup_high": 1.0,
124
+ "confidence": 0.0,
125
+ },
126
+ notes,
127
+ )
128
+
129
+ # Some other malformed shape β€” let pydantic produce a clear error.
130
+ return patch, notes
131
+
132
+
133
+ def _compare_runs(
134
+ workload_name: str, before: dict, after: dict, patch: dict
135
+ ) -> ToolResult:
136
+ patch_dict, patch_notes = _normalize_patch(patch)
137
+
138
+ try:
139
+ before_m = RunMetrics.model_validate(before)
140
+ after_m = RunMetrics.model_validate(after)
141
+ patch_m = Patch.model_validate(patch_dict)
142
+ except Exception as exc:
143
+ return ToolResult(ok=False, error=f"{type(exc).__name__}: {exc}")
144
+
145
+ speedup = (
146
+ after_m.tokens_per_sec / before_m.tokens_per_sec
147
+ if before_m.tokens_per_sec
148
+ else 0.0
149
+ )
150
+
151
+ deltas = [
152
+ MetricDelta(
153
+ name="tokens_per_sec",
154
+ before=before_m.tokens_per_sec,
155
+ after=after_m.tokens_per_sec,
156
+ unit="tok/s",
157
+ ),
158
+ MetricDelta(
159
+ name="mfu_pct", before=before_m.mfu_pct, after=after_m.mfu_pct, unit="%"
160
+ ),
161
+ MetricDelta(
162
+ name="hbm_peak_gb",
163
+ before=before_m.hbm_peak_gb,
164
+ after=after_m.hbm_peak_gb,
165
+ unit="GB",
166
+ ),
167
+ MetricDelta(
168
+ name="gpu_util_pct",
169
+ before=before_m.gpu_util_pct,
170
+ after=after_m.gpu_util_pct,
171
+ unit="%",
172
+ ),
173
+ ]
174
+
175
+ summary = (
176
+ f"Tokens/sec: {before_m.tokens_per_sec:.0f} β†’ {after_m.tokens_per_sec:.0f} "
177
+ f"({speedup:.2f}Γ—). MFU: {before_m.mfu_pct:.0f}% β†’ {after_m.mfu_pct:.0f}%."
178
+ )
179
+
180
+ report = Report(
181
+ workload_name=workload_name,
182
+ before=before_m,
183
+ after=after_m,
184
+ patch=patch_m,
185
+ metric_deltas=deltas,
186
+ waste_budget_before=before_m.waste_budget,
187
+ waste_budget_after=after_m.waste_budget,
188
+ speedup_actual=round(speedup, 2),
189
+ speedup_predicted_low=patch_m.expected_speedup_low,
190
+ speedup_predicted_high=patch_m.expected_speedup_high,
191
+ confidence=patch_m.confidence,
192
+ summary_line=summary,
193
+ )
194
+ payload = report.model_dump()
195
+ if patch_notes:
196
+ payload["notes"] = patch_notes
197
+ return ToolResult(ok=True, result=payload)
198
+
199
+
200
+ COMPARE_RUNS = Tool(
201
+ name="compare_runs",
202
+ description=(
203
+ "Build the final side-by-side Report from a baseline RunMetrics, an "
204
+ "optimized RunMetrics, and the Patch that connects them. Pure function β€” "
205
+ "always call this last.\n"
206
+ "\n"
207
+ "`patch` should be the FULL Patch dict you got back from propose_patch "
208
+ "(with `new_config`, `diff`, `rationale`, `expected_speedup_low`, etc.) "
209
+ "β€” NOT just the optimized config fields. If you forward a flat config "
210
+ "by mistake, compare_runs will recover by looking up the cached "
211
+ "propose_patch result, but you'll lose detail."
212
+ ),
213
+ input_schema={
214
+ "type": "object",
215
+ "properties": {
216
+ "workload_name": {
217
+ "type": "string",
218
+ "description": "Human-readable workload label for the report header.",
219
+ },
220
+ "before": {"type": "object", "description": "Baseline RunMetrics dict."},
221
+ "after": {"type": "object", "description": "Optimized RunMetrics dict."},
222
+ "patch": {
223
+ "type": "object",
224
+ "description": (
225
+ "FULL Patch dict from propose_patch β€” must include "
226
+ "`new_config`, `diff`, `rationale`, `expected_speedup_low/high`, "
227
+ "`confidence`. Do not pass just the optimized config fields."
228
+ ),
229
+ },
230
+ },
231
+ "required": ["workload_name", "before", "after", "patch"],
232
+ },
233
+ fn=_compare_runs,
234
+ )