Interstellar007 commited on
Commit
f775fa2
·
verified ·
1 Parent(s): bde364d

Final single-file submission: vLLM + SOAR-14B + heuristics (target 33.47%+)

Browse files
Files changed (1) hide show
  1. submission.py +690 -0
submission.py ADDED
@@ -0,0 +1,690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ARC-AGI-2 Kaggle Submission — INTERNET OFF, 4× L4 GPUs, 12h
4
+ Target: 33.47%+
5
+
6
+ BEFORE SUBMITTING — you need these attached to your Kaggle notebook:
7
+ 1. Kaggle Model: julien31/Soar-qwen-14b (Import from HuggingFace)
8
+ 2. Kaggle Dataset: your pip wheels (vllm + peft)
9
+
10
+ In your Kaggle notebook, just 2 cells:
11
+
12
+ Cell 1:
13
+ !pip install --no-index --find-links /kaggle/input/arc-pip-wheels vllm peft --quiet
14
+
15
+ Cell 2:
16
+ !python submission.py
17
+ """
18
+
19
+ import os, sys, json, time, copy, random, traceback, gc
20
+ from typing import List, Dict, Tuple, Optional, Any
21
+ from collections import defaultdict, Counter
22
+ import numpy as np
23
+
24
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
25
+ os.environ["HF_HUB_OFFLINE"] = "1"
26
+ os.environ["HF_DATASETS_OFFLINE"] = "1"
27
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
28
+
29
+ # ======================== CONFIG ========================
30
+
31
+ # Model — try these paths in order until one exists
32
+ SOAR_PATHS = [
33
+ "/kaggle/input/soar-qwen-14b/transformers/default/1",
34
+ "/kaggle/input/soar-qwen-14b",
35
+ "/kaggle/input/soar-qwen-7b/transformers/default/1",
36
+ "/kaggle/input/soar-qwen-7b",
37
+ ]
38
+ # Competition data
39
+ DATA_PATHS = [
40
+ "/kaggle/input/arc-prize-2026-arc-agi-2",
41
+ "/kaggle/input/arc-prize-2025",
42
+ ]
43
+ OUTPUT = "/kaggle/working/submission.json"
44
+
45
+ # Budget per task
46
+ N_SAMPLES = 200 # programs to sample
47
+ N_REFINE = 100 # refinement attempts
48
+ TEMP_SAMPLE = 0.9
49
+ TEMP_REFINE = 0.7
50
+ MAX_TOKENS = 2048
51
+ TOTAL_HOURS = 11.5
52
+ TP_SIZE = 4 # tensor parallel across all 4 L4 GPUs
53
+
54
+ START = time.time()
55
+ def tleft(): return TOTAL_HOURS*3600 - (time.time()-START)
56
+
57
+ def find(paths):
58
+ for p in paths:
59
+ if os.path.exists(p): return p
60
+ return paths[-1]
61
+
62
+ # ======================== GRID UTILS ========================
63
+
64
+ def geq(a,b):
65
+ if a is None or b is None: return False
66
+ if len(a)!=len(b): return False
67
+ return all(list(r1)==list(r2) for r1,r2 in zip(a,b))
68
+
69
+ def ghash(g):
70
+ return tuple(tuple(r) for r in g) if g else None
71
+
72
+ def gnp(g): return str(np.array(g))
73
+
74
+ # ======================== SAFE EXEC ========================
75
+
76
+ def safe_exec(code, inp):
77
+ try:
78
+ ns = {}
79
+ exec("import numpy as np\nfrom collections import Counter,defaultdict\nimport copy,itertools,math\n"+code, ns)
80
+ if "transform" not in ns: return None
81
+ r = ns["transform"](copy.deepcopy(inp))
82
+ if isinstance(r, np.ndarray): r = r.tolist()
83
+ if not isinstance(r, list) or not r: return None
84
+ out = []
85
+ for row in r:
86
+ if isinstance(row, np.ndarray): row = row.tolist()
87
+ if not isinstance(row, list): return None
88
+ out.append([int(c) for c in row])
89
+ if any(any(c<0 or c>9 for c in row) for row in out): return None
90
+ return out
91
+ except: return None
92
+
93
+ def eval_code(code, task):
94
+ ok = 0; results = []
95
+ for p in task["train"]:
96
+ pred = safe_exec(code, p["input"])
97
+ c = pred is not None and geq(pred, p["output"])
98
+ if c: ok += 1
99
+ results.append({"output":pred,"correct":c,"is_test":False})
100
+ acc = ok/len(task["train"]) if task["train"] else 0
101
+ to = safe_exec(code, task["test"][0]["input"]) if task.get("test") else None
102
+ results.append({"output":to,"correct":None,"is_test":True})
103
+ return acc, results, to
104
+
105
+ def extract_code(text):
106
+ if not text: return None
107
+ if "```python" in text:
108
+ for part in text.split("```python")[1:]:
109
+ end = part.find("```")
110
+ c = part[:end].strip() if end!=-1 else part.strip()
111
+ if "def transform" in c: return c
112
+ if "```" in text:
113
+ parts = text.split("```")
114
+ for i in range(1, len(parts), 2):
115
+ c = parts[i].strip()
116
+ if c.startswith("python\n"): c = c[7:]
117
+ if "def transform" in c: return c
118
+ if "def transform" in text:
119
+ s = text.index("def transform")
120
+ lines = text[s:].split("\n"); fl = [lines[0]]
121
+ for l in lines[1:]:
122
+ if l.strip() and not l[0].isspace() and l.startswith(("def ","class ","```")): break
123
+ fl.append(l)
124
+ return "\n".join(fl).rstrip()
125
+ return None
126
+
127
+ # ======================== SOAR PROMPTS ========================
128
+
129
+ CINFO = ("The number in the input grid can be mapped to the following colors: "
130
+ "0:Black; 1:Blue; 2:Red; 3:Green; 4:Yellow; 5:Grey; 6:Pink; "
131
+ "7:Orange; 8:Purple; 9:Brown\n")
132
+
133
+ def fmt(task):
134
+ p = ["# Task to solve:"]
135
+ for i,pr in enumerate(task["train"]):
136
+ inp,out = pr["input"],pr["output"]
137
+ p.append(f"## Input {i+1} (grid shape: {len(inp)} by {len(inp[0])}):")
138
+ p.append(gnp(inp))
139
+ p.append(f"## Output {i+1} (grid shape: {len(out)} by {len(out[0])}):")
140
+ p.append(gnp(out))
141
+ for i,tp in enumerate(task["test"]):
142
+ inp = tp["input"]
143
+ p.append(f"## Test Input {i+1} (grid shape: {len(inp)} by {len(inp[0])}):")
144
+ p.append(gnp(inp))
145
+ return "\n".join(p)
146
+
147
+ def sample_prompt(task):
148
+ return (
149
+ "You are an AI assistant specialized in solving Abstract Reasoning Corpus "
150
+ "(ARC-AGI) tasks by generating Python code.\n"
151
+ "Your goal is to analyze input-output grid pairs. The outputs were produced "
152
+ "by applying a transformation rule to the inputs. Implement the transformation "
153
+ "rules as a Python function.\n"
154
+ "You should only write the implemented the transformation in code.\n"
155
+ "You must write code in triple backticks (```python and then ```). "
156
+ "You must write a function called `transform` which takes a single argument, "
157
+ "the input grid as `list[list[int]]`, and returns the transformed grid "
158
+ "(also as `list[list[int]]`).\n"
159
+ "You should make sure that you implement a version of the transformation "
160
+ "that works in general (at least for all given input-output pairs and test input pairs).\n"
161
+ f"{CINFO}\nNow, solve the following ARC-AGI task:\n\n{fmt(task)}"
162
+ )
163
+
164
+ def refine_prompt(task, code, results):
165
+ ts = fmt(task)
166
+ nc = sum(1 for r in results if r.get("correct"))
167
+ nt = sum(1 for r in results if not r.get("is_test"))
168
+ parts = [f"```python\n{code}\n```",
169
+ f"This implementation correctly worked on {nc}/{nt} train pairs.",
170
+ "Detailed results:"]
171
+ bad = []
172
+ for i,r in enumerate(results):
173
+ if r.get("is_test"):
174
+ o = gnp(r["output"]) if r.get("output") else "EXECUTION ERROR"
175
+ parts.append(f"## Test Output (unknown correctness):\n{o}")
176
+ elif r.get("correct"):
177
+ parts.append(f"## Output {i+1}: CORRECT")
178
+ else:
179
+ o = gnp(r["output"]) if r.get("output") else "EXECUTION ERROR"
180
+ parts.append(f"## Output {i+1}: INCORRECT\n{o}")
181
+ bad.append(f"Output {i+1}")
182
+ if bad:
183
+ parts.append(f"\nFix code for: {', '.join(bad)}")
184
+ return (
185
+ "You are an AI assistant specialized in solving Abstract Reasoning Corpus "
186
+ "(ARC-AGI) tasks by repairing Python code implementations.\n"
187
+ "Fix the `transform` function to work correctly for all inputs.\n"
188
+ f"{CINFO}\n**Task:**\n{ts}\n\n"
189
+ f"**Previous implementation:**\n" + "\n".join(parts)
190
+ )
191
+
192
+ # ======================== HEURISTICS ========================
193
+
194
+ class Heur:
195
+ def solve(self, t):
196
+ for fn in [self._id,self._cmap,self._rot,self._flip,self._trans,
197
+ self._crop,self._scale,self._tile,self._grav,self._fill,
198
+ self._overlay,self._rmcol,self._mirror]:
199
+ try:
200
+ r = fn(t)
201
+ if r and len(r)>0 and all(len(row)>0 for row in r):
202
+ if all(all(isinstance(c,int) and 0<=c<=9 for c in row) for row in r):
203
+ return r
204
+ except: pass
205
+ return None
206
+
207
+ def _id(s,t): return copy.deepcopy(t["test"][0]["input"]) if all(p["input"]==p["output"] for p in t["train"]) else None
208
+ def _cmap(s,t):
209
+ i0,o0=t["train"][0]["input"],t["train"][0]["output"]
210
+ if len(i0)!=len(o0) or len(i0[0])!=len(o0[0]): return None
211
+ cm={}
212
+ for r in range(len(i0)):
213
+ for c in range(len(i0[0])):
214
+ k,v=i0[r][c],o0[r][c]
215
+ if k in cm and cm[k]!=v: return None
216
+ cm[k]=v
217
+ for p in t["train"][1:]:
218
+ if len(p["input"])!=len(p["output"]) or len(p["input"][0])!=len(p["output"][0]): return None
219
+ for r in range(len(p["input"])):
220
+ for c in range(len(p["input"][0])):
221
+ if cm.get(p["input"][r][c])!=p["output"][r][c]: return None
222
+ return [[cm.get(c,c) for c in row] for row in t["test"][0]["input"]]
223
+ def _rot(s,t):
224
+ for k in [1,2,3]:
225
+ if all(np.rot90(np.array(p["input"]),k=-k).tolist()==p["output"] for p in t["train"]):
226
+ return np.rot90(np.array(t["test"][0]["input"]),k=-k).tolist()
227
+ return None
228
+ def _flip(s,t):
229
+ for fn in [np.fliplr,np.flipud]:
230
+ if all(fn(np.array(p["input"])).tolist()==p["output"] for p in t["train"]):
231
+ return fn(np.array(t["test"][0]["input"])).tolist()
232
+ return None
233
+ def _trans(s,t):
234
+ return np.array(t["test"][0]["input"]).T.tolist() if all(np.array(p["input"]).T.tolist()==p["output"] for p in t["train"]) else None
235
+ def _crop(s,t):
236
+ for p in t["train"]:
237
+ a=np.array(p["input"]); nz=np.argwhere(a!=0)
238
+ if len(nz)==0: return None
239
+ r1,c1=nz.min(0); r2,c2=nz.max(0)
240
+ if a[r1:r2+1,c1:c2+1].tolist()!=p["output"]: return None
241
+ a=np.array(t["test"][0]["input"]); nz=np.argwhere(a!=0)
242
+ if len(nz)==0: return None
243
+ r1,c1=nz.min(0); r2,c2=nz.max(0)
244
+ return a[r1:r2+1,c1:c2+1].tolist()
245
+ def _scale(s,t):
246
+ for f in [2,3,4,5]:
247
+ if all(np.array_equal(np.repeat(np.repeat(np.array(p["input"]),f,0),f,1),np.array(p["output"])) for p in t["train"]):
248
+ return np.repeat(np.repeat(np.array(t["test"][0]["input"]),f,0),f,1).tolist()
249
+ return None
250
+ def _tile(s,t):
251
+ for nr in range(1,6):
252
+ for nc in range(1,6):
253
+ if nr==1 and nc==1: continue
254
+ if all(np.array_equal(np.tile(np.array(p["input"]),(nr,nc)),np.array(p["output"])) for p in t["train"]):
255
+ return np.tile(np.array(t["test"][0]["input"]),(nr,nc)).tolist()
256
+ return None
257
+ def _grav(s,t):
258
+ for d in ['down','up','left','right']:
259
+ ok=True
260
+ for p in t["train"]:
261
+ a=np.array(p["input"]); o=np.array(p["output"])
262
+ if a.shape!=o.shape: ok=False; break
263
+ bg=Counter(a.flatten().tolist()).most_common(1)[0][0]
264
+ r=np.full_like(a,bg); h,w=a.shape
265
+ for idx in range(h if d in ['down','up'] else w):
266
+ if d=='down':
267
+ nb=[a[rr,idx] for rr in range(h) if a[rr,idx]!=bg]
268
+ for i,v in enumerate(nb): r[h-len(nb)+i,idx]=v
269
+ elif d=='up':
270
+ nb=[a[rr,idx] for rr in range(h) if a[rr,idx]!=bg]
271
+ for i,v in enumerate(nb): r[i,idx]=v
272
+ elif d=='right':
273
+ nb=[a[idx,cc] for cc in range(w) if a[idx,cc]!=bg]
274
+ for i,v in enumerate(nb): r[idx,w-len(nb)+i]=v
275
+ elif d=='left':
276
+ nb=[a[idx,cc] for cc in range(w) if a[idx,cc]!=bg]
277
+ for i,v in enumerate(nb): r[idx,i]=v
278
+ if not np.array_equal(r,o): ok=False; break
279
+ if ok:
280
+ a=np.array(t["test"][0]["input"]); bg=Counter(a.flatten().tolist()).most_common(1)[0][0]
281
+ r=np.full_like(a,bg); h,w=a.shape
282
+ for idx in range(h if d in ['down','up'] else w):
283
+ if d=='down':
284
+ nb=[a[rr,idx] for rr in range(h) if a[rr,idx]!=bg]
285
+ for i,v in enumerate(nb): r[h-len(nb)+i,idx]=v
286
+ elif d=='up':
287
+ nb=[a[rr,idx] for rr in range(h) if a[rr,idx]!=bg]
288
+ for i,v in enumerate(nb): r[i,idx]=v
289
+ elif d=='right':
290
+ nb=[a[idx,cc] for cc in range(w) if a[idx,cc]!=bg]
291
+ for i,v in enumerate(nb): r[idx,w-len(nb)+i]=v
292
+ elif d=='left':
293
+ nb=[a[idx,cc] for cc in range(w) if a[idx,cc]!=bg]
294
+ for i,v in enumerate(nb): r[idx,i]=v
295
+ return r.tolist()
296
+ return None
297
+ def _fill(s,t):
298
+ from collections import deque
299
+ for p in t["train"]:
300
+ if len(p["input"])!=len(p["output"]) or len(p["input"][0])!=len(p["output"][0]): return None
301
+ for fc in range(10):
302
+ ok=True
303
+ for p in t["train"]:
304
+ a=np.array(p["input"]); o=np.array(p["output"]); h,w=a.shape
305
+ bg=Counter(a.flatten().tolist()).most_common(1)[0][0]
306
+ vis=np.zeros((h,w),dtype=bool); q=deque()
307
+ for rr in range(h):
308
+ for c in [0,w-1]:
309
+ if a[rr,c]==bg and not vis[rr,c]: q.append((rr,c)); vis[rr,c]=True
310
+ for c in range(w):
311
+ for rr in [0,h-1]:
312
+ if a[rr,c]==bg and not vis[rr,c]: q.append((rr,c)); vis[rr,c]=True
313
+ while q:
314
+ rr,c=q.popleft()
315
+ for dr,dc in [(-1,0),(1,0),(0,-1),(0,1)]:
316
+ nr,nc=rr+dr,c+dc
317
+ if 0<=nr<h and 0<=nc<w and not vis[nr,nc] and a[nr,nc]==bg: vis[nr,nc]=True; q.append((nr,nc))
318
+ e=a.copy()
319
+ for rr in range(h):
320
+ for c in range(w):
321
+ if a[rr,c]==bg and not vis[rr,c]: e[rr,c]=fc
322
+ if not np.array_equal(e,o): ok=False; break
323
+ if ok:
324
+ a=np.array(t["test"][0]["input"]); h,w=a.shape; bg=Counter(a.flatten().tolist()).most_common(1)[0][0]
325
+ vis=np.zeros((h,w),dtype=bool); q=deque()
326
+ for rr in range(h):
327
+ for c in [0,w-1]:
328
+ if a[rr,c]==bg and not vis[rr,c]: q.append((rr,c)); vis[rr,c]=True
329
+ for c in range(w):
330
+ for rr in [0,h-1]:
331
+ if a[rr,c]==bg and not vis[rr,c]: q.append((rr,c)); vis[rr,c]=True
332
+ while q:
333
+ rr,c=q.popleft()
334
+ for dr,dc in [(-1,0),(1,0),(0,-1),(0,1)]:
335
+ nr,nc=rr+dr,c+dc
336
+ if 0<=nr<h and 0<=nc<w and not vis[nr,nc] and a[nr,nc]==bg: vis[nr,nc]=True; q.append((nr,nc))
337
+ r=a.copy()
338
+ for rr in range(h):
339
+ for c in range(w):
340
+ if a[rr,c]==bg and not vis[rr,c]: r[rr,c]=fc
341
+ return r.tolist()
342
+ return None
343
+ def _overlay(s,t):
344
+ for sp in ['h','v']:
345
+ for op in ['or','and']:
346
+ ok=True
347
+ for p in t["train"]:
348
+ a=np.array(p["input"]); o=np.array(p["output"]); h,w=a.shape
349
+ if sp=='h' and h%2==0: t1,t2=a[:h//2],a[h//2:]
350
+ elif sp=='v' and w%2==0: t1,t2=a[:,:w//2],a[:,w//2:]
351
+ else: ok=False; break
352
+ if o.shape!=t1.shape: ok=False; break
353
+ e=np.where(t1!=0,t1,t2) if op=='or' else np.where((t1!=0)&(t2!=0),t1,0)
354
+ if not np.array_equal(e,o): ok=False; break
355
+ if ok:
356
+ a=np.array(t["test"][0]["input"]); h,w=a.shape
357
+ if sp=='h': t1,t2=a[:h//2],a[h//2:]
358
+ else: t1,t2=a[:,:w//2],a[:,w//2:]
359
+ return (np.where(t1!=0,t1,t2) if op=='or' else np.where((t1!=0)&(t2!=0),t1,0)).tolist()
360
+ return None
361
+ def _rmcol(s,t):
362
+ for rc in range(1,10):
363
+ ok=True
364
+ for p in t["train"]:
365
+ if len(p["input"])!=len(p["output"]) or len(p["input"][0])!=len(p["output"][0]): ok=False; break
366
+ for r in range(len(p["input"])):
367
+ for c in range(len(p["input"][0])):
368
+ ic,oc=p["input"][r][c],p["output"][r][c]
369
+ if ic==rc:
370
+ if oc!=0: ok=False; break
371
+ elif ic!=oc: ok=False; break
372
+ if not ok: break
373
+ if not ok: break
374
+ if ok: return [[0 if c==rc else c for c in row] for row in t["test"][0]["input"]]
375
+ return None
376
+ def _mirror(s,t):
377
+ for ax in ['h','v']:
378
+ ok=True
379
+ for p in t["train"]:
380
+ a=np.array(p["input"]); o=np.array(p["output"])
381
+ if a.shape!=o.shape: ok=False; break
382
+ m=np.fliplr(a) if ax=='h' else np.flipud(a)
383
+ e=a.copy(); mask=a==0; e[mask]=m[mask]
384
+ if not np.array_equal(e,o): ok=False; break
385
+ if ok:
386
+ a=np.array(t["test"][0]["input"])
387
+ m=np.fliplr(a) if ax=='h' else np.flipud(a)
388
+ r=a.copy(); mask=a==0; r[mask]=m[mask]; return r.tolist()
389
+ return None
390
+
391
+ # ======================== vLLM INFERENCE ENGINE ========================
392
+
393
+ class VLLMEngine:
394
+ """Wraps vLLM for batched SOAR inference."""
395
+ def __init__(self, model_path, tp=4):
396
+ from vllm import LLM
397
+ print(f"Loading vLLM: {model_path} (TP={tp})")
398
+ self.llm = LLM(
399
+ model=model_path,
400
+ tensor_parallel_size=tp,
401
+ dtype="bfloat16",
402
+ trust_remote_code=True,
403
+ max_model_len=8192,
404
+ gpu_memory_utilization=0.92,
405
+ )
406
+ print(" ✓ vLLM loaded")
407
+
408
+ def generate(self, prompts, temp=0.9, max_tokens=2048):
409
+ from vllm import SamplingParams
410
+ params = SamplingParams(
411
+ temperature=temp, top_p=0.95, max_tokens=max_tokens,
412
+ repetition_penalty=1.05, stop=["```\n\n", "\n\n\n\n"],
413
+ )
414
+ outputs = self.llm.generate(prompts, params, use_tqdm=False)
415
+ return [o.outputs[0].text for o in outputs]
416
+
417
+ def generate_chat(self, messages_list, temp=0.9, max_tokens=2048):
418
+ """Generate from list of chat message dicts."""
419
+ from vllm import SamplingParams
420
+ params = SamplingParams(
421
+ temperature=temp, top_p=0.95, max_tokens=max_tokens,
422
+ repetition_penalty=1.05,
423
+ )
424
+ # Apply chat template
425
+ tokenizer = self.llm.get_tokenizer()
426
+ prompts = []
427
+ for msgs in messages_list:
428
+ text = tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
429
+ prompts.append(text)
430
+ outputs = self.llm.generate(prompts, params, use_tqdm=False)
431
+ return [o.outputs[0].text for o in outputs]
432
+
433
+
434
+ class TransformersEngine:
435
+ """Fallback: plain transformers (slower but always works)."""
436
+ def __init__(self, model_path):
437
+ import torch
438
+ from transformers import AutoModelForCausalLM, AutoTokenizer
439
+ print(f"Loading transformers: {model_path}")
440
+ self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
441
+ self.model = AutoModelForCausalLM.from_pretrained(
442
+ model_path, dtype=torch.bfloat16, device_map="auto", trust_remote_code=True)
443
+ self.model.eval()
444
+ print(" ✓ transformers loaded")
445
+
446
+ def generate_chat(self, messages_list, temp=0.9, max_tokens=2048):
447
+ import torch
448
+ results = []
449
+ for msgs in messages_list:
450
+ text = self.tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
451
+ inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=8192)
452
+ inputs = {k:v.to(self.model.device) for k,v in inputs.items()}
453
+ with torch.no_grad():
454
+ out = self.model.generate(**inputs, max_new_tokens=max_tokens, temperature=temp,
455
+ top_p=0.95, do_sample=True, pad_token_id=self.tokenizer.eos_token_id,
456
+ repetition_penalty=1.05)
457
+ results.append(self.tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
458
+ return results
459
+
460
+
461
+ # ======================== SOAR SOLVER ========================
462
+
463
+ def soar_solve_batch(engine, tasks_dict, n_samples=200, n_refine=100):
464
+ """
465
+ Solve ALL tasks using batched vLLM inference.
466
+ Key insight: vLLM is fastest when you submit big batches, not one-at-a-time.
467
+ """
468
+ task_ids = list(tasks_dict.keys())
469
+ all_programs = {tid: [] for tid in task_ids} # tid -> [(code, acc, test_out, exec_results)]
470
+
471
+ # ---- Phase 1: Batched sampling ----
472
+ print(f"\n SOAR Phase 1: Sampling {n_samples} programs per task across {len(task_ids)} tasks")
473
+
474
+ for wave in range(0, n_samples, 10): # Sample in waves of 10
475
+ if tleft() < TOTAL_HOURS*3600*0.25: break # Reserve 25% for refine + TTT
476
+
477
+ # Build batch: 10 prompts per unsolved task
478
+ batch_prompts = []
479
+ batch_tids = []
480
+ for tid in task_ids:
481
+ # Skip if already solved perfectly
482
+ if any(p[1] == 1.0 for p in all_programs[tid]): continue
483
+ prompt = sample_prompt(tasks_dict[tid])
484
+ msgs = [{"role": "user", "content": prompt}]
485
+ for _ in range(min(10, n_samples - wave)):
486
+ batch_prompts.append(msgs)
487
+ batch_tids.append(tid)
488
+
489
+ if not batch_prompts: break
490
+
491
+ # Generate entire batch at once (vLLM handles this efficiently)
492
+ try:
493
+ responses = engine.generate_chat(batch_prompts, temp=TEMP_SAMPLE, max_tokens=MAX_TOKENS)
494
+ except Exception as e:
495
+ print(f" Wave {wave} error: {e}")
496
+ continue
497
+
498
+ # Evaluate all generated programs
499
+ for tid, text in zip(batch_tids, responses):
500
+ code = extract_code(text)
501
+ if code:
502
+ acc, er, to = eval_code(code, tasks_dict[tid])
503
+ all_programs[tid].append((code, acc, to, er))
504
+
505
+ # Progress
506
+ solved = sum(1 for tid in task_ids if any(p[1]==1.0 for p in all_programs[tid]))
507
+ partial = sum(1 for tid in task_ids if all_programs[tid] and not any(p[1]==1.0 for p in all_programs[tid]))
508
+ print(f" Wave {wave+10}: solved={solved}, partial={partial}, "
509
+ f"unsolved={len(task_ids)-solved-partial}, time_left={tleft()/3600:.2f}h")
510
+
511
+ # ---- Phase 2: Batched refinement ----
512
+ print(f"\n SOAR Phase 2: Refining top programs")
513
+
514
+ # Collect tasks that need refinement (not perfectly solved)
515
+ needs_refine = [tid for tid in task_ids
516
+ if all_programs[tid] and not any(p[1]==1.0 for p in all_programs[tid])]
517
+
518
+ for wave in range(0, n_refine, 5):
519
+ if tleft() < TOTAL_HOURS*3600*0.15: break # Reserve 15% for TTT
520
+
521
+ batch_prompts = []
522
+ batch_tids = []
523
+ for tid in needs_refine:
524
+ if any(p[1]==1.0 for p in all_programs[tid]): continue
525
+ # Pick best program to refine
526
+ best = max(all_programs[tid], key=lambda x: x[1])
527
+ rp = refine_prompt(tasks_dict[tid], best[0], best[3])
528
+ msgs = [{"role": "user", "content": rp}]
529
+ for _ in range(min(5, n_refine - wave)):
530
+ batch_prompts.append(msgs)
531
+ batch_tids.append(tid)
532
+
533
+ if not batch_prompts: break
534
+
535
+ try:
536
+ responses = engine.generate_chat(batch_prompts, temp=TEMP_REFINE, max_tokens=MAX_TOKENS)
537
+ except Exception as e:
538
+ print(f" Refine wave {wave} error: {e}")
539
+ continue
540
+
541
+ for tid, text in zip(batch_tids, responses):
542
+ code = extract_code(text)
543
+ if code:
544
+ acc, er, to = eval_code(code, tasks_dict[tid])
545
+ all_programs[tid].append((code, acc, to, er))
546
+
547
+ solved = sum(1 for tid in task_ids if any(p[1]==1.0 for p in all_programs[tid]))
548
+ print(f" Refine wave {wave+5}: solved={solved}, time_left={tleft()/3600:.2f}h")
549
+
550
+ # ---- Phase 3: Vote ----
551
+ results = {}
552
+ for tid in task_ids:
553
+ scores = defaultdict(float)
554
+ for code, acc, to, _ in all_programs[tid]:
555
+ if to is None: continue
556
+ k = ghash(to)
557
+ scores[k] += 1 + 1000*acc # SOAR scoring
558
+ ranked = sorted(scores.items(), key=lambda x: -x[1])
559
+ preds = [[list(row) for row in k] for k,_ in ranked[:2]]
560
+ results[tid] = preds
561
+
562
+ return results
563
+
564
+
565
+ # ======================== DATA LOADING ========================
566
+
567
+ def load_tasks():
568
+ tasks = {}
569
+ for base in DATA_PATHS:
570
+ if not os.path.exists(base): continue
571
+ # Single JSON file
572
+ for fname in ["arc-agi-2_test_challenges.json","test_challenges.json"]:
573
+ p = os.path.join(base, fname)
574
+ if os.path.exists(p):
575
+ with open(p) as f: tasks = json.load(f)
576
+ print(f"Loaded {len(tasks)} tasks from {fname}")
577
+ return tasks
578
+ # Directory of JSONs
579
+ for f in sorted(os.listdir(base)):
580
+ if f.endswith(".json") and "solution" not in f and "sample" not in f:
581
+ fp = os.path.join(base, f)
582
+ with open(fp) as fh: d = json.load(fh)
583
+ if isinstance(d, dict) and "train" in d:
584
+ tasks[f.replace(".json","")] = d
585
+ elif isinstance(d, dict):
586
+ tasks.update(d)
587
+ if tasks:
588
+ print(f"Loaded {len(tasks)} tasks from {base}")
589
+ return tasks
590
+ # HuggingFace fallback (needs internet)
591
+ print("Falling back to HuggingFace...")
592
+ from datasets import load_dataset
593
+ ds = load_dataset("arc-agi-community/arc-agi-2", split="train")
594
+ for i,row in enumerate(ds):
595
+ tasks[f"task_{i:04d}"] = {"train":row["fewshots"],"test":row["question"]}
596
+ print(f"Loaded {len(tasks)} tasks")
597
+ return tasks
598
+
599
+
600
+ # ======================== MAIN ========================
601
+
602
+ def main():
603
+ global START
604
+ START = time.time()
605
+ print("="*70)
606
+ print("ARC-AGI-2 SOLVER — TARGET: 33.47%+")
607
+ print("="*70)
608
+
609
+ # Load tasks
610
+ tasks = load_tasks()
611
+ ids = sorted(tasks.keys())
612
+ print(f"Total tasks: {len(ids)}")
613
+
614
+ submission = {}
615
+ stats = defaultdict(int)
616
+ heur = Heur()
617
+
618
+ # ---- Phase 0: Heuristics (instant) ----
619
+ print("\n--- Phase 0: Heuristics ---")
620
+ heur_solved = {}
621
+ remaining = {}
622
+ for tid in ids:
623
+ pred = heur.solve(tasks[tid])
624
+ if pred:
625
+ submission[tid] = {"attempt_1": pred, "attempt_2": pred}
626
+ heur_solved[tid] = True
627
+ stats["heuristic"] += 1
628
+ else:
629
+ remaining[tid] = tasks[tid]
630
+ print(f"Heuristic: {len(heur_solved)}/{len(ids)}")
631
+ print(f"Remaining: {len(remaining)}")
632
+
633
+ # ---- Phase 1: SOAR Program Synthesis (vLLM) ----
634
+ print("\n--- Phase 1: SOAR Program Synthesis ---")
635
+ model_path = find(SOAR_PATHS)
636
+ print(f"Model: {model_path}")
637
+
638
+ engine = None
639
+ try:
640
+ engine = VLLMEngine(model_path, tp=TP_SIZE)
641
+ except Exception as e:
642
+ print(f"vLLM failed: {e}")
643
+ try:
644
+ engine = TransformersEngine(model_path)
645
+ except Exception as e2:
646
+ print(f"Transformers also failed: {e2}")
647
+
648
+ soar_results = {}
649
+ if engine and remaining:
650
+ soar_results = soar_solve_batch(engine, remaining, N_SAMPLES, N_REFINE)
651
+
652
+ # Build submission from SOAR results
653
+ for tid, preds in soar_results.items():
654
+ if preds:
655
+ while len(preds) < 2: preds.append(preds[0])
656
+ submission[tid] = {"attempt_1": preds[0], "attempt_2": preds[1]}
657
+ stats["soar"] += 1
658
+ else:
659
+ # Fallback: return input unchanged
660
+ submission[tid] = {
661
+ "attempt_1": copy.deepcopy(tasks[tid]["test"][0]["input"]),
662
+ "attempt_2": copy.deepcopy(tasks[tid]["test"][0]["input"]),
663
+ }
664
+ stats["unsolved"] += 1
665
+
666
+ # Fill any missing tasks
667
+ for tid in ids:
668
+ if tid not in submission:
669
+ submission[tid] = {
670
+ "attempt_1": copy.deepcopy(tasks[tid]["test"][0]["input"]),
671
+ "attempt_2": copy.deepcopy(tasks[tid]["test"][0]["input"]),
672
+ }
673
+ stats["unsolved"] += 1
674
+
675
+ # ---- Save ----
676
+ os.makedirs(os.path.dirname(OUTPUT) or ".", exist_ok=True)
677
+ with open(OUTPUT, "w") as f:
678
+ json.dump(submission, f)
679
+
680
+ elapsed = time.time() - START
681
+ print(f"\n{'='*70}")
682
+ print(f"DONE in {elapsed/3600:.2f}h")
683
+ print(f" heuristic={stats['heuristic']} | soar={stats['soar']} | unsolved={stats['unsolved']}")
684
+ print(f" Total: {len(submission)}/{len(ids)}")
685
+ print(f" Output: {OUTPUT}")
686
+ print(f"{'='*70}")
687
+
688
+
689
+ if __name__ == "__main__":
690
+ main()