narcolepticchicken commited on
Commit
87be1ca
·
verified ·
1 Parent(s): 70b6a39

Upload jobs/run_real_llm_standalone_v6.py

Browse files
Files changed (1) hide show
  1. jobs/run_real_llm_standalone_v6.py +339 -0
jobs/run_real_llm_standalone_v6.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Self-contained GPU job for real LLM code benchmark — V6.
3
+ CRITICAL FIX: The HumanEval prompt already contains imports and function stub.
4
+ When the model outputs a complete function (def + body), we must use ONLY the
5
+ generated code + test, NOT prompt + generated code + test (which causes duplicates).
6
+ When the model outputs only body, we prepend the prompt.
7
+ """
8
+ import json
9
+ import os
10
+ import re
11
+ import subprocess
12
+ import sys
13
+ import tempfile
14
+ import time
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ from datasets import load_dataset
21
+ from transformers import AutoModelForCausalLM, AutoTokenizer
22
+ import torch
23
+
24
+
25
+ # --- CORE (INLINE) ---
26
+
27
+ @dataclass
28
+ class OracleResult:
29
+ raw_score: float; cost_adjusted_score: float; confidence: float
30
+ evidence: Dict[str, Any]; reason: str
31
+ failure_tags: List[str] = field(default_factory=list)
32
+ reward_value: float = 0.0
33
+
34
+
35
+ class ImpactOracle:
36
+ def __init__(self, compute_penalty_rate=0.0001, gaming_penalty=2.0):
37
+ self.compute_penalty_rate = compute_penalty_rate
38
+ self.gaming_penalty = gaming_penalty
39
+
40
+ def score(self, mode, action, context, result, agent_id=""):
41
+ correctness = result.get("correctness", 0.0)
42
+ compute_cost = result.get("compute_cost", 0.0)
43
+ public_pass = result.get("public_pass", correctness)
44
+ hidden_pass = result.get("hidden_tests_pass", correctness)
45
+ failure_tags = []
46
+ if public_pass and not hidden_pass: failure_tags.append("gaming_hidden_tests")
47
+ raw = correctness * 1.0 - compute_cost * self.compute_penalty_rate
48
+ if "gaming_hidden_tests" in failure_tags: raw -= self.gaming_penalty
49
+ cost_adj = raw - compute_cost * self.compute_penalty_rate
50
+ return OracleResult(raw, cost_adj, result.get("confidence", correctness),
51
+ {"correctness": correctness}, f"corr={correctness:.2f}, cost={compute_cost}", failure_tags, cost_adj)
52
+
53
+
54
+ @dataclass
55
+ class LedgerEntry:
56
+ agent_id: str; task_id: str; action_id: str; earned_credit: float; spent_credit: float
57
+ decayed_credit: float; remaining_credit: float; reason: str; oracle_score: float
58
+ compute_cost: float; timestamp: float; capability_scope: str = "global"
59
+
60
+
61
+ class CreditLedger:
62
+ def __init__(self, decay_lambda=0.05):
63
+ self.entries = []; self.balances = {}; self.decay_lambda = decay_lambda
64
+
65
+ def earn(self, agent_id, task_id, action_id, amount, oracle_score, compute_cost, reason, capability_scope="global"):
66
+ now = time.time()
67
+ self._apply_decay(agent_id, now, capability_scope)
68
+ current = self._get(agent_id, capability_scope)
69
+ new_bal = current + amount
70
+ self.entries.append(LedgerEntry(agent_id, task_id, action_id, amount, 0.0, 0.0, new_bal, reason, oracle_score, compute_cost, now, capability_scope))
71
+ self._set(agent_id, capability_scope, new_bal)
72
+
73
+ def spend(self, agent_id, task_id, action_id, amount, capability_scope="global", reason="spend"):
74
+ now = time.time()
75
+ self._apply_decay(agent_id, now, capability_scope)
76
+ current = self._get(agent_id, capability_scope)
77
+ if current < amount: return False
78
+ new_bal = current - amount
79
+ self.entries.append(LedgerEntry(agent_id, task_id, action_id, 0.0, amount, 0.0, new_bal, reason, 0.0, 0.0, now, capability_scope))
80
+ self._set(agent_id, capability_scope, new_bal)
81
+ return True
82
+
83
+ def balance(self, agent_id, capability_scope="global"):
84
+ now = time.time(); self._apply_decay(agent_id, now, capability_scope)
85
+ return self._get(agent_id, capability_scope)
86
+
87
+ def _get(self, agent_id, cap): return self.balances.get(agent_id, {}).get(cap, 0.0)
88
+ def _set(self, agent_id, cap, val):
89
+ if agent_id not in self.balances: self.balances[agent_id] = {}
90
+ self.balances[agent_id][cap] = val
91
+ def _apply_decay(self, agent_id, now, cap):
92
+ current = self._get(agent_id, cap)
93
+ if current <= 0: return
94
+ decayed = current * (1 - self.decay_lambda)
95
+ if decayed < current:
96
+ self.entries.append(LedgerEntry(agent_id, "decay", "decay", 0.0, 0.0, current - decayed, decayed, "credit_decay", 0.0, 0.0, now, cap))
97
+ self._set(agent_id, cap, decayed)
98
+
99
+
100
+ class Decision(Enum):
101
+ ALLOW = "allow"; DENY = "deny"; REQUIRE_APPROVAL = "require_approval"
102
+ DOWNGRADE = "downgrade"; ESCALATE = "escalate"; ASK_JUSTIFICATION = "ask_justification"
103
+
104
+
105
+ @dataclass
106
+ class ResourceDecision:
107
+ decision: Decision; reason: str; capability: str; downgrade_to: Optional[str] = None
108
+
109
+
110
+ class ResourceBroker:
111
+ RESOURCE_RISK = {"model_call": "medium", "retrieval_call": "low", "verifier_call": "medium",
112
+ "debate_turn": "low", "file_write": "high", "shell_execute": "high",
113
+ "memory_write": "medium", "human_escalation": "high", "larger_model": "medium"}
114
+ DEFAULT_THRESHOLDS = {"low": 0.5, "medium": 2.0, "high": 5.0}
115
+
116
+ def __init__(self, thresholds=None, urgency_boost=0.5):
117
+ self.thresholds = thresholds or self.DEFAULT_THRESHOLDS.copy()
118
+ self.urgency_boost = urgency_boost
119
+ self.denial_history = {}
120
+
121
+ def request(self, capability, agent_id, credit_balance, task_state=None, risk_score=0.0, gaming_flags=None):
122
+ task_state = task_state or {}; gaming_flags = gaming_flags or []
123
+ risk_class = self.RESOURCE_RISK.get(capability, "medium")
124
+ threshold = self.thresholds.get(risk_class, 2.0)
125
+ urgency = task_state.get("urgency", 0.0)
126
+ adjusted = max(0.1, threshold - urgency * self.urgency_boost)
127
+ if gaming_flags: return ResourceDecision(Decision.DENY, f"Gaming: {gaming_flags}", capability)
128
+ if risk_class == "high" and risk_score > 0.7: return ResourceDecision(Decision.REQUIRE_APPROVAL, f"High risk {risk_score:.2f}", capability)
129
+ if credit_balance >= adjusted: return ResourceDecision(Decision.ALLOW, f"Balance {credit_balance:.2f} >= {adjusted:.2f}", capability)
130
+ if credit_balance >= adjusted * 0.5:
131
+ if risk_class == "medium": return ResourceDecision(Decision.DOWNGRADE, f"Downgrading from {capability}", capability, "retrieval_call")
132
+ return ResourceDecision(Decision.ASK_JUSTIFICATION, f"Justification required", capability)
133
+ denials = self.denial_history.get(agent_id, 0)
134
+ if denials > 3: return ResourceDecision(Decision.ESCALATE, f"Denied {denials} times", capability)
135
+ self.denial_history[agent_id] = denials + 1
136
+ return ResourceDecision(Decision.DENY, f"Balance {credit_balance:.2f} < {adjusted:.2f}", capability)
137
+
138
+
139
+ # --- HELPERS ---
140
+
141
+ def strip_markdown_fences(text: str) -> str:
142
+ text = text.strip()
143
+ if text.startswith("```"):
144
+ lines = text.splitlines()
145
+ if lines[0].startswith("```"): lines = lines[1:]
146
+ if lines and lines[-1].strip() == "```": lines = lines[:-1]
147
+ text = "\n".join(lines)
148
+ return text.strip()
149
+
150
+
151
+ def contains_function_definition(code: str, entry_point: str) -> bool:
152
+ """Check if code contains the function definition for entry_point."""
153
+ return bool(re.search(rf'\bdef\s+{re.escape(entry_point)}\b', code))
154
+
155
+
156
+ def prepare_test_file(code: str, test_code: str) -> str:
157
+ """Prepare full Python file: code + test + check() call."""
158
+ return code + "\n\n" + test_code + "\n\ncheck()\n"
159
+
160
+
161
+ def run_test_file(code: str, test_code: str, timeout: int = 15):
162
+ full = prepare_test_file(code, test_code)
163
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
164
+ f.write(full)
165
+ tmp = f.name
166
+ try:
167
+ result = subprocess.run(['python', tmp], capture_output=True, text=True, timeout=timeout)
168
+ passed = result.returncode == 0
169
+ error = result.stderr[:400] if not passed else ""
170
+ except subprocess.TimeoutExpired:
171
+ passed = False; error = "Timeout"
172
+ except Exception as e:
173
+ passed = False; error = str(e)[:400]
174
+ finally:
175
+ os.unlink(tmp)
176
+ return passed, error
177
+
178
+
179
+ def wrap_prompt_chat(prompt: str, tokenizer) -> str:
180
+ system = "You are an expert Python programmer. Write the COMPLETE solution including function signature, docstring if needed, and body."
181
+ messages = [
182
+ {"role": "system", "content": system},
183
+ {"role": "user", "content": prompt.strip()},
184
+ ]
185
+ if hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template:
186
+ return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
187
+ return f"system\n{system}\nuser\n{prompt.strip()}\nassistant\n"
188
+
189
+
190
+ # --- BENCHMARK ---
191
+
192
+ class RealLLMBenchmarkV6:
193
+ def __init__(self, model_name="Qwen/Qwen2.5-Coder-0.5B-Instruct", n_problems=20, seed=42):
194
+ self.model_name = model_name
195
+ self.n_problems = n_problems
196
+ self.seed = seed
197
+ self.oracle = ImpactOracle()
198
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
199
+ print(f"Using device: {self.device}")
200
+
201
+ def load_problems(self):
202
+ ds = load_dataset("evalplus/humanevalplus", split="test")
203
+ return [{"task_id": item["task_id"], "prompt": item["prompt"], "test": item["test"], "entry_point": item["entry_point"]}
204
+ for i, item in enumerate(ds) if i < self.n_problems]
205
+
206
+ def load_model(self):
207
+ print(f"Loading {self.model_name}...")
208
+ tok = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True)
209
+ model = AutoModelForCausalLM.from_pretrained(
210
+ self.model_name, trust_remote_code=True,
211
+ torch_dtype=torch.bfloat16 if self.device == "cuda" else torch.float32,
212
+ device_map="auto" if self.device == "cuda" else None,
213
+ )
214
+ if self.device == "cpu": model = model.to("cpu").float()
215
+ print(f"Model loaded. Chat template: {bool(tok.chat_template)}")
216
+ return model, tok
217
+
218
+ def generate(self, model, tok, prompt_raw: str, max_new_tokens: int = 300):
219
+ chat_prompt = wrap_prompt_chat(prompt_raw, tok)
220
+ inputs = tok(chat_prompt, return_tensors="pt").to(model.device)
221
+ with torch.no_grad():
222
+ outputs = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=False, pad_token_id=tok.eos_token_id)
223
+ gen = tok.decode(outputs[0], skip_special_tokens=True)
224
+ prompt_decoded = tok.decode(inputs.input_ids[0], skip_special_tokens=True)
225
+ code = gen[len(prompt_decoded):].strip()
226
+ return code
227
+
228
+ def evaluate_one(self, problem, model, tok, max_new_tokens=300):
229
+ raw = self.generate(model, tok, problem["prompt"], max_new_tokens=max_new_tokens)
230
+ tokens = len(tok.encode(raw))
231
+ code = strip_markdown_fences(raw)
232
+
233
+ # CRITICAL FIX: If model generated complete function, use code as-is.
234
+ # If not, prepend the HumanEval prompt (which has imports + stub).
235
+ if contains_function_definition(code, problem["entry_point"]):
236
+ test_code = code
237
+ else:
238
+ test_code = problem["prompt"] + code
239
+
240
+ passed, error = run_test_file(test_code, problem["test"])
241
+
242
+ # Also try the alternative if first attempt failed
243
+ if not passed:
244
+ alt_code = problem["prompt"] + code if contains_function_definition(code, problem["entry_point"]) else code
245
+ passed2, error2 = run_test_file(alt_code, problem["test"])
246
+ if passed2:
247
+ passed = True; error = ""
248
+ else:
249
+ # Keep the better error message (shorter usually means syntax error closer to start)
250
+ error = error if len(error) < len(error2) else error2
251
+
252
+ return passed, tokens, raw, error
253
+
254
+ def run_baseline(self, problems, model, tok, max_new_tokens=300):
255
+ results = []; total_compute = 0
256
+ for problem in problems:
257
+ passed, tokens, raw, error = self.evaluate_one(problem, model, tok, max_new_tokens)
258
+ total_compute += tokens
259
+ results.append({"task_id": problem["task_id"], "passed": passed, "tokens": tokens, "raw": raw[:200], "error": error[:200]})
260
+ print(f" {problem['task_id']}: passed={passed}, tokens={tokens}")
261
+ if not passed: print(f" error={error[:150]!r}")
262
+ return {"accuracy": sum(1 for r in results if r["passed"]) / len(results), "total_compute": total_compute, "results": results}
263
+
264
+ def run_occ(self, problems, model, tok, max_new_tokens_first=200, max_new_tokens_retry=300):
265
+ ledger = CreditLedger(decay_lambda=0.02)
266
+ broker = ResourceBroker()
267
+ ledger.earn("code_agent", "seed", "seed", 25.0, 0.0, 0.0, "initial", "model_call")
268
+ results = []; total_compute = 0
269
+
270
+ for problem in problems:
271
+ budget_remaining = 2500; attempts = 0; passed = False
272
+ while budget_remaining > 100 and attempts < 3 and not passed:
273
+ attempts += 1
274
+ balance = ledger.balance("code_agent", "model_call")
275
+ dec = broker.request("model_call", "code_agent", balance,
276
+ task_state={"attempts": attempts, "budget_remaining": budget_remaining})
277
+ if dec.decision == Decision.DENY: break
278
+ max_tok = max_new_tokens_first if attempts == 1 else max_new_tokens_retry
279
+ code_raw = self.generate(model, tok, problem["prompt"], max_new_tokens=max_tok)
280
+ tokens = len(tok.encode(code_raw)); budget_remaining -= tokens; total_compute += tokens
281
+ code = strip_markdown_fences(code_raw)
282
+ if contains_function_definition(code, problem["entry_point"]):
283
+ test_code = code
284
+ else:
285
+ test_code = problem["prompt"] + code
286
+ passed, error = run_test_file(test_code, problem["test"])
287
+ score = 1.0 if passed else 0.0
288
+ ora = self.oracle.score("code", {"attempt": attempts}, {},
289
+ {"correctness": score, "compute_cost": tokens, "public_pass": passed, "hidden_tests_pass": passed}, "code_agent")
290
+ if passed: ledger.earn("code_agent", problem["task_id"], f"att_{attempts}", 5.0, ora.raw_score, tokens, "pass", "model_call")
291
+ else: ledger.spend("code_agent", problem["task_id"], f"att_{attempts}", 1.0, "model_call", "fail")
292
+ if attempts >= 2 and not passed: break
293
+ results.append({"task_id": problem["task_id"], "passed": passed, "attempts": attempts})
294
+ print(f" {problem['task_id']}: passed={passed}, attempts={attempts}")
295
+ return {"accuracy": sum(1 for r in results if r["passed"]) / len(results), "total_compute": total_compute, "results": results}
296
+
297
+ def run_all(self):
298
+ problems = self.load_problems()
299
+ print(f"Loaded {len(problems)} problems")
300
+ model, tok = self.load_model()
301
+ print("\n--- Baseline ---")
302
+ baseline = self.run_baseline(problems, model, tok)
303
+ print(f"Baseline: acc={baseline['accuracy']:.3f}, compute={baseline['total_compute']}")
304
+ print("\n--- OCC ---")
305
+ occ = self.run_occ(problems, model, tok)
306
+ print(f"OCC: acc={occ['accuracy']:.3f}, compute={occ['total_compute']}")
307
+ return {
308
+ "baseline": baseline, "occ": occ,
309
+ "comparison": {
310
+ "baseline_accuracy": baseline["accuracy"], "occ_accuracy": occ["accuracy"],
311
+ "baseline_compute": baseline["total_compute"], "occ_compute": occ["total_compute"],
312
+ "compute_reduction": 1.0 - (occ["total_compute"] / max(baseline["total_compute"], 1)),
313
+ "accuracy_delta": occ["accuracy"] - baseline["accuracy"],
314
+ }
315
+ }
316
+
317
+
318
+ def main():
319
+ bench = RealLLMBenchmarkV6(n_problems=20, seed=42)
320
+ results = bench.run_all()
321
+ print("\n" + "=" * 60)
322
+ print("REAL LLM CODE BENCHMARK (V6)")
323
+ print("=" * 60)
324
+ comp = results["comparison"]
325
+ print(f"Baseline accuracy: {comp['baseline_accuracy']:.3f}")
326
+ print(f"OCC accuracy: {comp['occ_accuracy']:.3f}")
327
+ print(f"Baseline compute: {comp['baseline_compute']}")
328
+ print(f"OCC compute: {comp['occ_compute']}")
329
+ print(f"Compute reduction: {comp['compute_reduction']:.1%}")
330
+ print(f"Accuracy delta: {comp['accuracy_delta']:+.3f}")
331
+ out_dir = Path("/app/occ/reports")
332
+ out_dir.mkdir(parents=True, exist_ok=True)
333
+ with open(out_dir / "benchmark_code_real_llm_v6_results.json", "w") as f:
334
+ json.dump(results, f, indent=2, default=str)
335
+ print(f"\nSaved to {out_dir / 'benchmark_code_real_llm_v6_results.json'}")
336
+
337
+
338
+ if __name__ == "__main__":
339
+ main()