HarpreetK commited on
Commit
e68f97d
·
verified ·
1 Parent(s): a0e83f1

Upload platform_agnostic_inference_code_score21.py

Browse files
platform_agnostic_inference_code_score21.py ADDED
@@ -0,0 +1,912 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Final-Submission -gpt-oss-score21.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1hMIhGYkVJqyB_Qv_GLgH3d21hInIvKZp
8
+
9
+ # Setup The Environment
10
+ """
11
+
12
+ # Commented out IPython magic to ensure Python compatibility.
13
+ # %%bash
14
+ # pip install paramiko math_verify litellm flashinfer-python vllm==0.11.2 openai_harmony
15
+ #
16
+ # pip install absl-py==2.4.0 \
17
+ # catalogue==2.0.10 \
18
+ # colorful==0.5.8 \
19
+ # contextlib2==21.6.0 \
20
+ # decorator==5.2.1 \
21
+ # deprecated==1.3.1 \
22
+ # distlib==0.4.0 \
23
+ # docker==7.1.0 \
24
+ # exceptiongroup==1.3.1 \
25
+ # fabric==3.2.2 \
26
+ # fiddle==0.3.0 \
27
+ # google-api-core==2.29.0 \
28
+ # google-auth==2.48.0 \
29
+ # googleapis-common-protos==1.72.0 \
30
+ # graphviz==0.21 \
31
+ # grpcio==1.78.0 \
32
+ # h2==4.3.0 \
33
+ # hf-xet==1.2.0 \
34
+ # hpack==4.1.0 \
35
+ # hyperframe==6.1.0 \
36
+ # inquirerpy==0.3.4 \
37
+ # ledoc-ui==0.1.0 \
38
+ # leptonai==0.27.0 \
39
+ # libcst==1.8.6 \
40
+ # mypy-extensions==1.1.0 \
41
+ # nemo-run==0.6.0 \
42
+ # omegaconf==2.3.0 \
43
+ # opencensus==0.11.4 \
44
+ # opencensus-context==0.1.3 \
45
+ # opentelemetry-api==1.39.1 \
46
+ # opentelemetry-exporter-prometheus==0.60b1 \
47
+ # opentelemetry-proto==1.39.1 \
48
+ # opentelemetry-sdk==1.39.1 \
49
+ # opentelemetry-semantic-conventions==0.60b1 \
50
+ # pfzy==0.3.4 \
51
+ # platformdirs==4.9.2 \
52
+ # prompt-toolkit==3.0.52 \
53
+ # proto-plus==1.27.1 \
54
+ # py-spy==0.4.1 \
55
+ # pyasn1==0.6.2 \
56
+ # pyasn1-modules==0.4.2 \
57
+ # pyre-extensions==0.0.32 \
58
+ # python-multipart==0.0.22 \
59
+ # rsa==4.9.1 \
60
+ # smart-open==7.5.0 \
61
+ # toml==0.10.2 \
62
+ # torchx==0.7.0 \
63
+ # typer-slim==0.24.0 \
64
+ # virtualenv==20.37.0 \
65
+ # wcwidth==0.6.0 \
66
+ # wrapt==2.1.1
67
+ #
68
+ # pip install openpyxl
69
+ #
70
+
71
+ # Track Overall Time
72
+ import time
73
+ global_deadline = time.perf_counter() + 5*3600
74
+ global_remaining = global_deadline - time.perf_counter()
75
+ cutoff_duration = global_remaining - 350
76
+ def get_global_remaining():
77
+ return max(0, global_deadline - time.perf_counter())
78
+
79
+ import os
80
+ os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
81
+ import torch
82
+
83
+ import asyncio
84
+ import torch
85
+ import subprocess
86
+ import warnings
87
+ import glob
88
+ import pandas as pd
89
+ import traceback
90
+ import nest_asyncio
91
+ import httpx
92
+ import re
93
+ import time
94
+ import copy
95
+ import json
96
+ import requests
97
+ import pandas as pd
98
+ import polars as pl
99
+ from collections import Counter
100
+ from typing import List
101
+ import secrets
102
+ import json
103
+ pd.set_option('display.max_colwidth', None)
104
+ warnings.filterwarnings("ignore", category=SyntaxWarning)
105
+ nest_asyncio.apply()
106
+ os.environ["TORCH_COMPILE_DISABLE"] = "1"
107
+ os.environ["TORCHDYNAMO_DISABLE"] = "1"
108
+ os.environ['TRANSFORMERS_NO_FLAX'] = '1'
109
+ os.environ['CUDA_VISIBLE_DEVICES'] = '0'
110
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
111
+ os.environ['TRITON_PTXAS_PATH'] = '/usr/local/cuda/bin/ptxas'
112
+ os.environ['TIKTOKEN_RS_CACHE_DIR']= "/content/harmony_encoding"
113
+ os.environ["TORCH_CUDA_ARCH_LIST"] = '9.0'
114
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"]="expandable_segments:True"
115
+ #os.environ["VLLM_USE_FLASHINFER_SAMPLER"]= "1"
116
+ from collections import Counter, defaultdict
117
+
118
+ # This will change in kaggle
119
+ os.environ["TORCHINDUCTOR_CACHE_DIR"] = "torch_cache"
120
+
121
+ import os, sys
122
+ original_pythonpath = os.environ.get("PYTHONPATH", "")
123
+ path1 = '/content/modified-nemo-skills'
124
+ merged_pythonpath = f"{path1}:{original_pythonpath}" if original_pythonpath else {path1}
125
+ os.environ["PYTHONPATH"] = merged_pythonpath
126
+ sys.path.append('/content/modified-nemo-skills')
127
+
128
+ from nemo_skills.code_execution.sandbox import get_sandbox
129
+ from nemo_skills.inference.model import get_code_execution_model
130
+ from nemo_skills.prompt.utils import get_prompt
131
+ from nemo_skills.inference.model import get_model
132
+
133
+ """# Configuration Parameters"""
134
+
135
+ host = "127.0.0.1"
136
+ port = 5000
137
+ tp_size = 1
138
+ max_public = 10
139
+ max_tokens = 38000
140
+ max_input_tokens = 2050
141
+ tokens_to_generate = 35950 - 10
142
+ max_batch_size = 8
143
+ timeout_seconds = 300
144
+ global_buffer = 350
145
+ finish_at_last_n = 2
146
+ max_code_output_characters = 1100
147
+ code_execution_timeout = 10
148
+ max_code_executions = 125
149
+ g_score = 0
150
+ g_count = 0
151
+ prompt_score = Counter()
152
+ sampling_params = {
153
+ "tokens_to_generate": tokens_to_generate,
154
+ "temperature": 1, # 0.2,
155
+ "top_p": 1,
156
+ }
157
+
158
+ thoughts = [""] * 50
159
+ thoughts = thoughts[:max_batch_size]
160
+ i = 0
161
+
162
+ model_path = "/content/model"
163
+
164
+ """# Start Server - Load Model & Sandbox"""
165
+
166
+ server_started = False
167
+ def load_model():
168
+ cmd = [
169
+ "python",
170
+ "-m",
171
+ "nemo_skills.inference.server.serve_vllm",
172
+ f"--model={model_path}",
173
+ "--port=5000",
174
+ "--num_gpus=1",
175
+ "--max-model-len=38000",
176
+ "--max-num-batched-tokens=16384",
177
+ "--max-num-seqs=11",
178
+ "--max-cudagraph-capture-size=2048",
179
+ "--gpu-memory-utilization=0.95",
180
+ "--kv-cache-dtype=auto",
181
+ "--stream-interval=200",
182
+ "--enable-prefix-caching",
183
+ "--uvicorn-log-level debug",
184
+ "--enable-log-requests",
185
+ "--enable-log-outputs",
186
+ "--async-scheduling",
187
+ ]
188
+
189
+ log_file = open("vllm.log", "w")
190
+ vllm_server = subprocess.Popen(
191
+ cmd,
192
+ stdout=log_file,
193
+ stderr=log_file,
194
+ text=True,
195
+ bufsize=1 # line-buffered
196
+ )
197
+ return vllm_server
198
+
199
+ vllm_server=load_model()
200
+
201
+ def wait_for_server(url=f"http://{host}:{port}", timeout=1400):
202
+ start = time.perf_counter()
203
+ while True:
204
+ try:
205
+ r = requests.get(f"{url}/docs")
206
+ if r.status_code == 200:
207
+ print("✅ Server is ready",time.perf_counter()-start)
208
+ return True
209
+ except Exception:
210
+ pass
211
+
212
+ if time.perf_counter() - start > timeout:
213
+ raise TimeoutError("Server did not start in time")
214
+
215
+ time.sleep(1)
216
+
217
+ def sandbox_server():
218
+ log_file = open("sandbox.log", "w")
219
+ sandbox_process = subprocess.Popen(
220
+ ["python", "-m", "nemo_skills.code_execution.local_sandbox.local_sandbox_server"],
221
+ stdout=log_file,
222
+ stderr=log_file,
223
+ text=True,
224
+ bufsize=1)
225
+
226
+ time.sleep(3)
227
+
228
+ time.sleep(2)
229
+ sandbox_server()
230
+ sandbox = get_sandbox() # localhost by default
231
+
232
+ """# Prompt Types and Updating Prompt"""
233
+
234
+ default_prompt = (
235
+ 'You are an elite mathematical problem solver with expertise at the International '
236
+ 'Mathematical Olympiad (IMO) level. Your goal is to find the correct answer through '
237
+ 'rigorous mathematical reasoning.\n\n'
238
+
239
+ '# Problem-Solving Approach:\n'
240
+ '1. UNDERSTAND: Carefully read and rephrase the problem in your own words. '
241
+ 'Identify what is given, what needs to be found, and any constraints.\n'
242
+ '2. EXPLORE: Consider multiple solution strategies. Think about relevant theorems, '
243
+ 'techniques, patterns, or analogous problems. Don\'t commit to one approach immediately.\n'
244
+ '3. PLAN: Select the most promising approach and outline key steps before executing.\n'
245
+ '4. EXECUTE: Work through your solution methodically. Show all reasoning steps clearly.\n'
246
+ '5. VERIFY: Check your answer by substituting back, testing edge cases, or using '
247
+ 'alternative methods. Ensure logical consistency throughout.\n\n'
248
+
249
+ '# Mathematical Reasoning Principles:\n'
250
+ '- Break complex problems into smaller, manageable sub-problems\n'
251
+ '- Look for patterns, symmetries, and special cases that provide insight\n'
252
+ '- Use concrete examples to build intuition before generalizing\n'
253
+ '- Consider extreme cases and boundary conditions\n'
254
+ '- If stuck, try working backwards from the desired result\n'
255
+ '- Be willing to restart with a different approach if needed\n\n'
256
+
257
+ '# Verification Requirements:\n'
258
+ '- Cross-check arithmetic and algebraic manipulations\n'
259
+ '- Verify that your solution satisfies all problem constraints\n'
260
+ '- Test your answer with simple cases or special values when possible\n'
261
+ '- Ensure dimensional consistency and reasonableness of the result\n\n'
262
+
263
+ "#RESPONSE FORMAT:\n\n"
264
+ "The final answer must be a non-negative integer.\n. Instead of the \\boxed{} format use json format. Follow the instructions for the format-"
265
+ ' "Answer": <non-negative integer>,"Confidence": <number between 0 and 1>'
266
+ "Do not output any additional reasoning after this JSON.\n"
267
+ )
268
+
269
+ answerext_prompt = (
270
+ "You are an answer extraction system for competition mathematics problems "
271
+ "(olympiad, Putnam, HMMT, AMC/AIME style and similar).\n"
272
+ "You will be given a PROBLEM and a MODEL_RESPONSE. The response may be "
273
+ "incomplete, truncated mid-reasoning, or cut off before a formal conclusion.\n\n"
274
+ "YOUR JOB:\n"
275
+ "1. Read the problem and identify the SOUGHT QUANTITY — this could be a "
276
+ "numerical value, an expression, a set of solutions, a function, a "
277
+ "characterization, a bound, a count, an extremal quantity, a geometric "
278
+ "measure, or a closed-form answer that the problem asks to find, "
279
+ "determine, or compute.\n"
280
+ "2. Extract the model's best answer to that sought quantity from the "
281
+ "response, even if:\n"
282
+ " - It is not explicitly labeled with 'the answer is' or 'therefore'\n"
283
+ " - It appears mid-sentence or mid-calculation\n"
284
+ " - The response was truncated before a formal conclusion\n"
285
+ " - It is stated with hedging language like 'seems to be,' 'so we get,' "
286
+ "or 'this gives'\n\n"
287
+ "RULES FOR IDENTIFYING THE ANSWER:\n"
288
+ "- The model's reasoning often explores multiple cases, subcases, or "
289
+ "candidate values. Distinguish between:\n"
290
+ " (a) INTERMEDIATE SUB-RESULTS: values computed within a single case "
291
+ "or step (e.g., 'LCM 60,' 'sum = 14,' 'this gives 42') that feed "
292
+ "into the broader argument but do not directly answer the problem.\n"
293
+ " (b) THE CANDIDATE ANSWER: the value, expression, or characterization "
294
+ "of the sought quantity that the model is building toward or "
295
+ "accumulating evidence for across its reasoning.\n"
296
+ " Extract (b), not (a).\n"
297
+ "- In optimization or extremal problems, the model may test many "
298
+ "configurations and compare them against a leading candidate value. "
299
+ "If the model repeatedly checks whether alternatives can 'beat,' "
300
+ "'exceed,' or 'improve upon' a particular value, and none do, "
301
+ "treat that value as the candidate answer — even if the response "
302
+ "ends before a formal conclusion.\n"
303
+ "- If the model arrives at the same value through multiple approaches "
304
+ "or repeatedly returns to it as the best result, that is strong "
305
+ "signal it is the intended answer.\n"
306
+ "- If the response is truncated but a candidate answer is visible "
307
+ "from the reasoning so far, extract it. A truncated response with "
308
+ "a clear leading candidate is better than no answer.\n"
309
+ "- If the problem asks to 'compute,' 'find,' or 'determine' a specific "
310
+ "quantity, look for the last/best concrete value of THAT SPECIFIC "
311
+ "quantity — not intermediate quantities used along the way.\n"
312
+ "- If the answer appears in LaTeX formatting (e.g., \\boxed{140}, "
313
+ "$\\frac{7}{3}$, or similar), extract the value inside the formatting.\n\n"
314
+ "RESPONSE FORMAT:\n"
315
+ "Your ONLY task is to output a single JSON object — no preamble, no explanation, no mathematical calculations.\n"
316
+ "The final answer must be a non-negative integer. Instead of the \\boxed{{}} format use json format. Follow the instructions for the format-"
317
+ ' {{"Answer": <non-negative integer>,"Confidence": <number between 0 and 1>}}'
318
+ "Do not output any additional reasoning after this JSON.\n"
319
+ )
320
+
321
+ # Below will change
322
+ system_message='{system_prompt}'
323
+ prompt_template = get_prompt(prompt_config='gpt-oss/math',system_message=system_message,tokenizer=model_path,code_tags="gpt-oss")
324
+ chat_template_kwargs = {
325
+ "builtin_tools": ["python"],
326
+ "reasoning_effort":"high"
327
+
328
+ }
329
+
330
+ def safe_concat(a, b,function_name):
331
+ if a is None or b is None:
332
+ raise ValueError(f"Cannot concatenate: a={a}, b={b}, Error Raised from function {function_name}")
333
+ return a + b
334
+
335
+ """# Data Extraction & Early Stopping"""
336
+
337
+ class Result:
338
+ def __init__(self):
339
+ self.early_stop_flag = False
340
+ def best_voted_answer(self):
341
+ return self.best_answer
342
+
343
+ def majority_voting(self, answer_list):
344
+ count = defaultdict(float)
345
+ # Keep raw list separate; filter into valid_answers
346
+ self.answer_list = answer_list
347
+ self.valid_answers = [x["Answer"] for x in self.answer_list if x["Answer"] != -1]
348
+ print("Answer_list after popping -1", self.valid_answers, "%%%%")
349
+
350
+ # BUG FIX: set fallback when all answers are invalid
351
+ if len(self.valid_answers) == 0:
352
+ self.best_answer = None
353
+ self.best_count = 0
354
+ self.second_count = 0
355
+ self.sorted_answers = []
356
+ return
357
+
358
+ for a in self.valid_answers:
359
+ count[a] += 1
360
+ self.sorted_answers = sorted(count.items(), key=lambda x: x[1], reverse=True)
361
+
362
+ self.best_answer, self.best_count = self.sorted_answers[0]
363
+ self.second_count = self.sorted_answers[1][1] if len(self.sorted_answers) > 1 else 0
364
+
365
+ if (
366
+ self.best_count == 1
367
+ and self.best_answer == 0
368
+ and len(self.sorted_answers) > 1
369
+ and self.sorted_answers[1] is not None
370
+ ):
371
+
372
+ self.best_answer, self.best_count = self.sorted_answers[1]
373
+
374
+
375
+ def early_stop(self, answer_list, num_done):
376
+ print("Num_done is",num_done)
377
+ self.num_done = num_done
378
+ self.majority_voting(answer_list)
379
+ n_valid = len(self.valid_answers)
380
+ best = self.best_count
381
+ gap = self.best_count - self.second_count
382
+ print(f"Num done: {self.num_done}, Valid answers: {n_valid}, "
383
+ f"Best count: {best}, Second count: {self.second_count}")
384
+
385
+ if n_valid == 0:
386
+ return False
387
+
388
+ if best >= 4 and gap >= 2:
389
+ self.early_stop_flag = True
390
+ print(f">>> EARLY STOP at {self.num_done} completions | "
391
+ f"best={self.best_answer} (count={best}, gap={gap})")
392
+
393
+ return self.early_stop_flag
394
+
395
+ def get_best_answer(self,answer_list, num_done, flag):
396
+ if not flag:
397
+ self.majority_voting(answer_list)
398
+ else:
399
+ self.early_stop(answer_list, num_done)
400
+ return self.best_voted_answer(), self.early_stop_flag
401
+
402
+ import re, requests
403
+
404
+ class Answer:
405
+ def __init__(self):
406
+ self.best_answer = None
407
+ self.input_message = ""
408
+ self.best_count = 0
409
+ self.second_count = 0
410
+ self.answer_list = [] # ← was None, init as empty list
411
+ self.early_stop_flag = False
412
+ self.sorted_answers = []
413
+ self.valid_answers = [] # ← filtered list (no -1s), kept separate
414
+ self.sampling_param = {
415
+ "tokens_to_generate": 7000,
416
+ "temperature": 0.9, # 0.2,
417
+ "top_p": 0.95,
418
+ }
419
+ self.timeout = httpx.Timeout(
420
+ connect=60.0,
421
+ read=300.0,
422
+ write=60.0,
423
+ pool=120.0,
424
+ )
425
+
426
+ def clean_messages(self, text):
427
+ cleaned = re.sub(r'<\|[^|]*\|>', '', text)
428
+ return cleaned.strip()
429
+
430
+
431
+ async def extract_answer(self, question, model_output):
432
+ answer = -1
433
+ confidence = -0.1
434
+ seed = secrets.randbits(32)
435
+ input_message = self.clean_messages(model_output)
436
+ rid = secrets.token_hex(8)
437
+ message = prompt_template.fill(
438
+ input_dict={
439
+ "problem": safe_concat(question,input_message,"extract_answer"),
440
+ "system_prompt": answerext_prompt,
441
+ },
442
+ chat_template_kwargs = chat_template_kwargs,
443
+ format_as_string=True
444
+ )
445
+ print(prompt_template)
446
+ print("textd was called")
447
+ try:
448
+ data, completion_tokens = await server_obj.generate_response(
449
+ prompt=message,
450
+ random_seed=seed,
451
+ stream=True,
452
+ calling_function = "extract_answer",
453
+ extra_body={"request_id": rid},
454
+ timeout = self.timeout,
455
+ **self.sampling_param,
456
+ )
457
+
458
+ if data is not None and isinstance(data, dict):
459
+ return data
460
+ else:
461
+ return {"Answer":-1, "Confidence":-0.1}
462
+
463
+ except Exception as e:
464
+ print(f"[extract_answer failed] {type(e).__name__}: {e}")
465
+ return {"Answer":answer,"Confidence": confidence}
466
+
467
+ """# Inference"""
468
+
469
+ # Below will change in kaggle
470
+ #Instantiate Server Object
471
+ server_obj = get_code_execution_model(server_type = 'vllm',
472
+ model=model_path,
473
+ base_url="http://127.0.0.1:5000/v1",
474
+ api_key='EMPTY',
475
+ sandbox=sandbox,
476
+ code_execution={
477
+ 'max_code_output_characters': max_code_output_characters,
478
+ 'code_execution_timeout': code_execution_timeout,
479
+ 'max_code_executions': max_code_executions,
480
+ })
481
+
482
+ async def abort_request(request_ids: str | list[str]):
483
+ """Sequential best-effort server-side abort.
484
+ Uses short timeouts so a slow/down server doesn't block.
485
+ Silently ignores failures.
486
+ """
487
+ if isinstance(request_ids, str):
488
+ request_ids = [request_ids]
489
+
490
+ timeout = httpx.Timeout(connect=1.0, read=2.0, write=1.0, pool=1.0)
491
+
492
+ async with httpx.AsyncClient(timeout=timeout) as client:
493
+ for rid in request_ids:
494
+ try:
495
+ await client.delete(f"http://{host}:{port}/v1/requests/{rid}")
496
+ except Exception:
497
+ # optionally log instead of silent pass
498
+ pass
499
+ await asyncio.sleep(0.05) # cooperative yield
500
+
501
+ class ClientClass:
502
+ def __init__(self, prompt):
503
+ global sampling_params
504
+ self.thresh_hold = 4 # minimum completions before checking early stop
505
+ self.system_prompt = prompt
506
+ self.answer = {}
507
+ self.randomseed_list = []
508
+ self.num_done = 0
509
+ self.sampling_param = copy.deepcopy(sampling_params)
510
+ self.question = ""
511
+ self.finished_generations = []
512
+ self.final_answer = None
513
+ self.early_stop_flag = False
514
+ self.flattened_prompt_list = []
515
+ self.list_of_questions = []
516
+ self.answer_list = []
517
+ self.request_ids = [] # per-task IDs for server-side abort
518
+ self.tasks = []
519
+ self.timeout = httpx.Timeout(
520
+ connect=30.0,
521
+ read= 500.0 ,
522
+ write=30.0,
523
+ pool=120.0,
524
+ )
525
+ self.answerobj = Answer()
526
+
527
+ async def send_request_to_server(self):
528
+ print("Request sent")
529
+ self.request_ids = [secrets.token_hex(8) for _ in self.list_of_questions]
530
+ self.randomseed_list = [k for k in range(len(self.list_of_questions))]
531
+ for prompt, seed, rid in zip(self.list_of_questions, self.randomseed_list, self.request_ids):
532
+ task = asyncio.create_task(
533
+ server_obj.generate_async(
534
+ prompt=prompt,
535
+ random_seed=seed,
536
+ timeout=self.timeout,
537
+ remove_stop_phrases=False,
538
+ stream = True,
539
+ extra_body={"request_id": rid},
540
+ **prompt_template.get_code_execution_args(),
541
+ **self.sampling_param,
542
+ )
543
+ )
544
+ self.tasks.append(task)
545
+
546
+ try:
547
+ processed = set()
548
+ for completed in asyncio.as_completed(self.tasks):
549
+ try:
550
+ result = await completed
551
+ print("Total number of generated tokens", result["total_num_generated_tokens"])
552
+ self.num_done += 1
553
+ processed.add(completed) # this adds the task to processed
554
+ self.finished_generations.append(result["generation"])
555
+ if result["answer"] is not None:
556
+ self.answer = json.loads(result["answer"])
557
+ print("The answer and confidence after json parsing", self.answer)
558
+ yield self.answer
559
+ else:
560
+ self.answer = await self.answerobj.extract_answer(self.question, result["generation"])
561
+ print("The answer and confidence after interaction with 2nd model",self.answer)
562
+ yield self.answer
563
+ except GeneratorExit:
564
+ return
565
+ except Exception as e:
566
+ traceback.print_exc()
567
+ error_type = type(e).__name__
568
+ print(f"[ERROR] {error_type}")
569
+ traceback.print_exc()
570
+ self.answer = {
571
+ "Answer": -1,
572
+ "Confidence": -0.1,
573
+ }
574
+ yield self.answer
575
+
576
+ finally:
577
+ #fallback in the Pipeline timeout handler. Timout
578
+ for t in self.tasks:
579
+ if t.done() and t not in processed:
580
+ try:
581
+ if not t.cancelled() and t.exception() is None:
582
+ self.res = t.result()
583
+
584
+ elif t.exception() is not None:
585
+ # optional: handle failed tasks
586
+ pass
587
+ except Exception:
588
+ pass
589
+ elif not t.done():
590
+ t.cancel()
591
+ asyncio.create_task(abort_request(self.request_ids))
592
+
593
+ # Fire server-side abort independently — survives parent cancellation
594
+
595
+ def flatten_prompt_list(self):
596
+ global max_batch_size
597
+ self.flattened_prompt_list = [
598
+ self.system_prompt
599
+ # for system_prompt in self.prompts_list
600
+ for _ in range(max_batch_size)
601
+ ]
602
+
603
+ def generate_question_copies(self, question):
604
+ self.question = question
605
+ self.list_of_questions = [
606
+ prompt_template.fill(
607
+ input_dict={
608
+ "problem": question,
609
+ "system_prompt": system_prompt,
610
+ },
611
+ chat_template_kwargs = chat_template_kwargs,
612
+ format_as_string=True
613
+ )
614
+ for system_prompt in self.flattened_prompt_list
615
+ ]
616
+
617
+
618
+ async def predict_for_question(self, question):
619
+ self.flatten_prompt_list()
620
+ self.generate_question_copies(question)
621
+
622
+ gen = self.send_request_to_server()
623
+
624
+ try:
625
+ async for answer in gen:
626
+ yield answer
627
+
628
+ except Exception as e:
629
+ print("Error in predict_for_question:", e)
630
+ raise
631
+
632
+ finally:
633
+ try:
634
+ await gen.aclose()
635
+ except Exception:
636
+ pass
637
+
638
+ import math
639
+
640
+ class BufferBorrower:
641
+ """
642
+ Dynamic buffer-time borrowing strategy for inference.
643
+
644
+ Borrows from buffer time based on task difficulty and step-back
645
+ token usage, using a sigmoid curve for smooth allocation.
646
+
647
+ Parameters
648
+ ----------
649
+ max_difficulty : int or float
650
+ The upper bound of the difficulty scale (e.g., 5 or 1.0).
651
+ alpha : float
652
+ Weight for the difficulty signal (default 0.6).
653
+ beta : float
654
+ Weight for the step-back token signal (default 0.4).
655
+ b_max : float
656
+ Maximum fraction of buffer that can be borrowed (default 0.7).
657
+ k : float
658
+ Steepness of the sigmoid transition (default 6).
659
+ threshold : float
660
+ Midpoint of the sigmoid curve (default 0.4).
661
+ """
662
+
663
+ def __init__(
664
+ self,
665
+ b_max: float = 0.85,
666
+ k: float = 6.0,
667
+ threshold: float = 0.4,
668
+ total_questions: int = 50,
669
+ total_available_time: int = 15720,
670
+ ):
671
+
672
+ self.b_max = b_max
673
+ self.k = k
674
+ self.threshold = threshold
675
+ self.total_questions = total_questions
676
+ self.total_available_time = total_available_time
677
+
678
+ def compute_time_pressure(
679
+ self,
680
+ remaining_time: float,
681
+ questions_completed: int,
682
+ global_buffer: float = 0.0,
683
+ ) -> float:
684
+ remaining_q = max(1, self.total_questions - questions_completed)
685
+ if remaining_time <= 0:
686
+ return 1.5
687
+ ideal_pace = self.total_available_time / self.total_questions
688
+ available_pace = remaining_time / remaining_q
689
+ pressure = ideal_pace / available_pace
690
+ return max(0.3, min(1.5, pressure))
691
+
692
+ def allocate_time(
693
+ self,
694
+ remaining_time: float,
695
+ questions_completed: int,
696
+ global_buffer: float = 0.0,
697
+ allowed_time : float = 320,
698
+ ) -> dict:
699
+ """
700
+ Allocate effective inference and remaining buffer time.
701
+
702
+ Parameters
703
+ ----------
704
+ allowed_time : float
705
+ Base inference time budget.
706
+ global_buffer : float
707
+ global buffer time budget.
708
+ difficulty : float
709
+ Task difficulty score.
710
+ stepback_tokens : int
711
+ Tokens used in step-back phase.
712
+ stepback_budget : int
713
+ Total step-back token budget.
714
+
715
+ Returns
716
+ -------
717
+ dict
718
+ Keys: effective_inference, remaining_buffer, borrowed,
719
+ borrow_fraction.
720
+ """
721
+ pressure = self.compute_time_pressure(
722
+ remaining_time,
723
+ questions_completed,
724
+ global_buffer
725
+ )
726
+ borrow_fraction = 1/pressure
727
+ max_borrowable = 130
728
+ print("borrow fraction", borrow_fraction)
729
+ borrowed = min(pressure * global_buffer, max_borrowable)
730
+
731
+
732
+ return {
733
+ "effective_inference": allowed_time + borrowed,
734
+ "global_buffer": global_buffer - borrowed,
735
+ "borrowed": borrowed,
736
+ "borrow_fraction": borrow_fraction,
737
+ }
738
+
739
+ class TimeBudget:
740
+ def __init__(self, total_seconds):
741
+ self.start = time.perf_counter()
742
+ self.deadline = self.start + total_seconds
743
+
744
+ @property
745
+ def remaining(self):
746
+ return max(0, self.deadline - time.perf_counter())
747
+
748
+ @property
749
+ def elapsed(self):
750
+ return time.perf_counter() - self.start
751
+
752
+ @property
753
+ def expired(self):
754
+ return self.remaining <= 0
755
+
756
+ class Pipeline:
757
+ def __init__(self):
758
+ self.budget_seconds = 0
759
+ self.k = 1
760
+ self.budget_seconds = 0
761
+ async def get_prediction(self, problem_text):
762
+ global global_buffer, i, borrower, max_batch_size,last_30, sampling_param
763
+ budgetobj = None
764
+ timeout = 60
765
+ # Timeout at this level - see if needs to be implemented
766
+ thresh_hold = 3
767
+ num_done = 0
768
+ max_generation_count = self.k*max_batch_size
769
+ answer_list = []
770
+ finalanswerobj = Result()
771
+ print("Pipeline step 1")
772
+ deadline = 0
773
+ allowed_time = 320
774
+ self.budget_seconds = allowed_time
775
+ if global_buffer> 0:
776
+ result = borrower.allocate_time(
777
+ remaining_time = get_global_remaining(),
778
+ questions_completed = i,
779
+ allowed_time = allowed_time,
780
+ global_buffer = global_buffer
781
+ )
782
+
783
+ self.budget_seconds = result["effective_inference"]
784
+ global_buffer = result["global_buffer"]
785
+ print(f'borrowed={result["borrowed"]:.0f}')
786
+ print(f"Budget: base={allowed_time:.0f}s "
787
+ f"= {self.budget_seconds:.0f}s (global remaining: {get_global_remaining():.0f}s)")
788
+ budgetobj = TimeBudget(self.budget_seconds)
789
+
790
+ clientobj = ClientClass(default_prompt)
791
+ deadline = max(deadline, budgetobj.remaining)
792
+ operation_start_time = time.perf_counter()
793
+ print("Deadline is", deadline)
794
+ gen = clientobj.predict_for_question(problem_text)
795
+ try:
796
+ async with asyncio.timeout(deadline):
797
+ async for answer in gen:
798
+ answer_list.append(answer)
799
+ print("Answer list on timeout is:-")
800
+ print(answer_list)
801
+ num_done = len(answer_list)
802
+ if num_done >= thresh_hold and num_done < max_generation_count:
803
+ prediction, early_stop_flag = finalanswerobj.get_best_answer(answer_list, num_done, True)
804
+ if early_stop_flag:
805
+ return prediction
806
+
807
+ elif num_done == max_generation_count:
808
+ prediction, _ = finalanswerobj.get_best_answer(answer_list, num_done, False)
809
+ return prediction
810
+ else:
811
+ continue
812
+ except (TimeoutError, asyncio.TimeoutError):
813
+ traceback.print_exc()
814
+ prediction, _ = finalanswerobj.get_best_answer(answer_list, num_done, False)
815
+ return prediction
816
+
817
+ except Exception as e:
818
+ traceback.print_exc()
819
+ print(f"UNEXPECTED ERROR: {type(e).__name__} {e}")
820
+ if answer_list:
821
+ prediction, _ = finalanswerobj.get_best_answer(answer_list, num_done, False)
822
+ return prediction
823
+ return None
824
+
825
+ finally:
826
+ await gen.aclose()
827
+ print("Operation duration", time.perf_counter()-operation_start_time)
828
+ if budgetobj.elapsed > self.budget_seconds:
829
+ global_buffer -= (budgetobj.elapsed - self.budget_seconds)
830
+ else:
831
+ global_buffer += (self.budget_seconds - budgetobj.elapsed)
832
+
833
+ def predict(id_: pl.Series, problem: pl.Series) -> pl.DataFrame | pd.DataFrame:
834
+ """Make a prediction."""
835
+ global server_started, i
836
+ start_pred_time = time.perf_counter()
837
+ pipelineobj = Pipeline()
838
+ if server_started is False:
839
+ server_started = wait_for_server()
840
+
841
+ id_ = id_.item(0)
842
+ problem_text: str = problem.item(0)
843
+
844
+ # BUG FIX: compare duration to duration (was comparing duration to absolute timestamp)
845
+ if get_global_remaining() < 30:
846
+ return pl.DataFrame({"id": id_, "answer": 29443})
847
+ loop = asyncio.get_event_loop()
848
+ prediction = loop.run_until_complete(pipelineobj.get_prediction(problem_text))
849
+
850
+ # If prediction is still None after everything, use fallback
851
+ if prediction is None:
852
+ prediction = 29443
853
+
854
+ i = i + 1
855
+
856
+ print("Returned dataframe is ", pl.DataFrame({"id": id_, "answer": prediction}))
857
+ return pl.DataFrame({"id": id_, "answer": prediction})
858
+
859
+ borrower = ""
860
+
861
+ def run_local_inference(file_path: str, output_path: str = "submission.csv"):
862
+ global borrower
863
+ import pandas as pd
864
+ import polars as pl
865
+ borrower = BufferBorrower(total_questions = 50, total_available_time = get_global_remaining())
866
+ # Load file
867
+ if file_path.endswith(".xlsx"):
868
+ df = pd.read_excel(file_path)
869
+ else:
870
+ df = pd.read_csv(file_path)
871
+
872
+ # Basic validation
873
+ assert "problem" in df.columns, "Column 'problem' is required"
874
+ df = df.dropna(subset=["problem"])
875
+
876
+ # Optional: also remove rows where problem is just whitespace
877
+ df = df[df["problem"].str.strip() != ""]
878
+
879
+ if "id" not in df.columns:
880
+ df["id"] = range(len(df))
881
+
882
+ results = []
883
+
884
+ for idx, row in df.iterrows():
885
+ id_val = row["id"]
886
+ problem_text = row["problem"]
887
+
888
+ # Convert to polars Series (since your predict expects that)
889
+ id_series = pl.Series([id_val])
890
+ problem_series = pl.Series([problem_text])
891
+
892
+ try:
893
+ pred_df = predict(id_series, problem_series)
894
+
895
+ if isinstance(pred_df, pl.DataFrame):
896
+ pred = pred_df.to_pandas()
897
+ else:
898
+ pred = pred_df
899
+
900
+ results.append(pred.iloc[0])
901
+
902
+ except Exception as e:
903
+ print(f"Error at row {idx}: {e}")
904
+ results.append({"id": id_val, "answer": 29443})
905
+
906
+ final_df = pd.DataFrame(results)
907
+ final_df.to_csv(output_path, index=False)
908
+
909
+ print(f"✅ Saved predictions to {output_path}")
910
+ return final_df
911
+
912
+ run_local_inference("/content/AIMO_ReferenceProblems.xlsx")