{
  "id": 694256,
  "title": "NeverGiveUp: Majority-First Tool-Augmented Reasoning for AIMO3",
  "content": "<h1>NeverGiveUp: Majority-First Tool-Augmented Reasoning for AIMO3</h1>\n<h2>Acknowledgments</h2>\n<p>This solution was developed for the AI Mathematical Olympiad - Progress Prize 3 competition. My selected notebook, <strong>AIMO3-NeverGiveUp</strong>, achieved a private score of <strong>42</strong> using <code>gpt-oss-120b</code> without fine-tuning.</p>\n<p>The notebook is an original, independent implementation. It draws high-level inspiration from publicly shared AIMO3 community approaches that explored the combination of vLLM serving, Harmony tool-calling, Python sandbox execution, and multi-attempt reasoning. One reference that helped me understand this general pattern was:</p>\n<ul>\n<li><strong>Inspired by:</strong> <code>[44/50] Skills Optional, Luck Necessary</code></li>\n<li><strong>Author:</strong> Andreas Bisiadis</li>\n<li><strong>Link:</strong> <a href=\"https://www.kaggle.com/code/nihilisticneuralnet/44-50-skills-optional-luck-necessary\" target=\"_blank\">https://www.kaggle.com/code/nihilisticneuralnet/44-50-skills-optional-luck-necessary</a></li>\n</ul>\n<p>All code structure, class design, naming, prompts, deterministic seeding, aggregation logic, timeout handling, and implementation details in my notebook were written independently and materially differ from the referenced work.</p>\n<p>Also, my thanks to the AIMO organizers, Kaggle, XTX Markets, and the AIMO3 community for making this competition possible and for encouraging detailed technical sharing.</p>\n<hr>\n<h2>Overview</h2>\n<p>My solution focuses on inference-time engineering rather than model fine-tuning. The solver runs <code>gpt-oss-120b</code> on a single Kaggle H100 through vLLM, using fp8 KV cache, prefix caching, Harmony encoding, and a stateful Python/Jupyter tool layer.</p>\n<p>For each problem, the system launches 8 independent reasoning attempts. Each attempt receives the same problem, but uses deterministic per-problem seeding to encourage controlled diversity across attempts. The model can call a Python sandbox during generation to perform computation, verification, symbolic manipulation, or brute-force checks.</p>\n<p>Once attempts produce candidate answers, the solver applies a majority-first aggregation strategy. If one answer receives the highest agreement, it is selected. If multiple answers tie, the solver uses the lowest mean token entropy as the tie-breaker.</p>\n<p>The main idea is simple: a strong mathematical reasoning model still needs a reliable execution system around it. In this competition, performance depended not only on the model, but also on how inference was orchestrated, how tools were exposed, how time was managed, and how candidate answers were selected.</p>\n<hr>\n<h2>System Architecture</h2>\n<p>The solution has four main components:</p>\n<ol>\n<li>Offline dependency setup using the AIMO3 utility wheels</li>\n<li>Local vLLM server for <code>gpt-oss-120b</code></li>\n<li>Harmony prompt wrapper and Python tool interface</li>\n<li>Multi-attempt reasoning engine with majority-first aggregation</li>\n</ol>\n<p>The model is served through a local OpenAI-compatible vLLM endpoint. The solver then communicates with the server through the OpenAI client API.</p>\n<p>Before starting inference, the notebook prewarms model files by reading the model checkpoint files into the OS page cache. This reduces server startup friction. The notebook then initializes 16 Python kernels, each preloaded with common mathematical libraries.</p>\n<hr>\n<h2>Configuration</h2>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Value</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Model</td>\n<td><code>gpt-oss-120b</code></td>\n<td>Kaggle-hosted model</td>\n</tr>\n<tr>\n<td>Inference engine</td>\n<td>vLLM</td>\n<td>OpenAI-compatible local server</td>\n</tr>\n<tr>\n<td>Hardware</td>\n<td>1× Kaggle H100</td>\n<td>Competition environment</td>\n</tr>\n<tr>\n<td>KV cache</td>\n<td><code>fp8_e4m3</code></td>\n<td>Memory-efficient serving</td>\n</tr>\n<tr>\n<td>Context length</td>\n<td>65,536 tokens</td>\n<td><code>max_model_len</code></td>\n</tr>\n<tr>\n<td>Reasoning effort</td>\n<td>HIGH</td>\n<td>Harmony system content</td>\n</tr>\n<tr>\n<td>Temperature</td>\n<td>0.9</td>\n<td>Used in selected notebook</td>\n</tr>\n<tr>\n<td><code>min_p</code></td>\n<td>0.02</td>\n<td>Sampling filter</td>\n</tr>\n<tr>\n<td>Attempts per problem</td>\n<td>8</td>\n<td>Parallel independent attempts</td>\n</tr>\n<tr>\n<td>Worker kernels</td>\n<td>16</td>\n<td>Python sandbox pool</td>\n</tr>\n<tr>\n<td>Turns per attempt</td>\n<td>96</td>\n<td>Chosen to limit drift and cost</td>\n</tr>\n<tr>\n<td>Early-stop threshold</td>\n<td>4 agreements</td>\n<td>Stop when enough attempts converge</td>\n</tr>\n<tr>\n<td>Base timeout</td>\n<td>300 s</td>\n<td>Reserved per problem</td>\n</tr>\n<tr>\n<td>Max timeout</td>\n<td>900 s</td>\n<td>If enough global time remains</td>\n</tr>\n<tr>\n<td>Notebook runtime limit</td>\n<td>17,400 s</td>\n<td>Conservative limit below full wall clock</td>\n</tr>\n<tr>\n<td>Aggregation</td>\n<td>Majority-first + entropy tie-break</td>\n<td>Agreement first, confidence second</td>\n</tr>\n<tr>\n<td>Seed</td>\n<td>42</td>\n<td>Base seed</td>\n</tr>\n<tr>\n<td>Answer range</td>\n<td><code>[0, 99999]</code></td>\n<td>Competition format</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Prompts</h2>\n<p>The prompts were intentionally compact, and the system was intended to define the task, answer format, and tool availability without forcing the model into a rigid reasoning template.</p>\n<h3>System prompt</h3>\n<pre><code>You are an elite math problem solver.\nSolve the problem carefully and verify your result.\nIf computation helps, call the python tool.\n\n### Rules:\n- Final answer must be an integer in [0, 99999]\n- Put final answer in \\boxed{...}\n</code></pre>\n<p><strong>Tool prompt</strong></p>\n<pre><code>Python tool: use it for computation, brute force, algebra (sympy), or checks.\nExplain briefly what we're doing, then run code. Use print() for outputs.\n</code></pre>\n<p><strong>Preference prompt</strong></p>\n<pre><code>Use math/numpy/sympy/mpmath. Prefer exact reasoning, then verify.\n</code></pre>\n<p>The design principle was to give the model enough structure to behave reliably, but not so much structure that the prompt starts controlling the reasoning path too aggressively. In my experiments, compact prompting was easier to manage under strict runtime limits.</p>\n<hr>\n<h2>Python Tool Layer</h2>\n<p>Each reasoning attempt has access to a stateful Python sandbox through Harmony tool messages. Every sandbox preloads:</p>\n<ul>\n<li>math</li>\n<li>numpy</li>\n<li>sympy</li>\n<li>itertools</li>\n<li>collections</li>\n<li>mpmath with 64-digit precision</li>\n</ul>\n<p>This tool layer is important because many AIMO-style problems require exact arithmetic, symbolic simplification, modular checks, combinatorial enumeration, or high-precision numerical validation.</p>\n<p>Also, added a helper that wraps the final expression in print() when appropriate. This avoids silent Python cells and makes outputs visible to the model.</p>\n<hr>\n<h2>Parallel Attempts</h2>\n<p>For each problem, the solver launches 8 independent attempts. Each attempt has the same model, prompt, and tool access, but receives a deterministic seed based on the problem text and attempt index.</p>\n<p>The seed is computed as:</p>\n<pre><code>h = hashlib.md5(problem_text.encode(\"utf-8\")).hexdigest()\nbase = int(h[:8], 16)\nseed = (base + 9973 * attempt_idx) % (2**31 - 1)\n</code></pre>\n<p>Used this design because two things were intended at the same time:</p>\n<ul>\n<li>Diversity between attempts</li>\n<li>More stable behavior across reruns</li>\n</ul>\n<p>This was especially useful because stochastic inference can produce noticeable leaderboard variance.</p>\n<hr>\n<h2>Streaming Answer Extraction</h2>\n<p>The model output is streamed token by token. During streaming, the solver scans recent text whenever a closing brace appears. This allows an attempt to terminate as soon as a valid answer is found instead of waiting for the full message to finish.</p>\n<p>The primary answer pattern is:</p>\n<pre><code>\\boxed{...}\n</code></pre>\n<p>The regex used is:</p>\n<pre><code>r\"\\\\boxed\\s*\\{\\s*([0-9,]+)\\s*\\}\"\n</code></pre>\n<p>If no boxed answer is found, the fallback pattern checks for:</p>\n<pre><code>final answer is &lt;integer&gt;\n</code></pre>\n<p>Only integers in [0, 99999] are accepted. Attempts that do not produce a valid integer answer do not contribute to the final vote.</p>\n<hr>\n<h2>Aggregation</h2>\n<p>The final submission uses a majority-first aggregation rule.</p>\n<p>The process is:</p>\n<ul>\n<li>Collect valid answers from all completed attempts.</li>\n<li>Count how many times each answer appears.</li>\n<li>Select the answer with the highest count.</li>\n<li>If multiple answers tie, select the tied answer with the lowest mean token entropy.</li>\n</ul>\n<p>The relevant idea is:</p>\n<pre><code>counts = Counter(answers)\ntop_cnt = counts.most_common(1)[0][1]\ntop_answers = [a for a, c in counts.items() if c == top_cnt]\n</code></pre>\n<p>If there is only one top answer, it is selected directly. If there is a tie, entropy is used as a confidence signal.</p>\n<p>This is chosen because agreement between independent attempts is an interpretable reliability signal. Entropy is useful, but in this system, it's used as a tie-breaker rather than allowing one low-entropy answer to override a stronger majority.</p>\n<hr>\n<h2>Early Stopping</h2>\n<p>The solver stops early once 4 valid attempts agree on the same answer.</p>\n<pre><code>if Counter(valid).most_common(1)[0][1] &gt;= self.cfg.early_stop:\n    stop_evt.set()\n</code></pre>\n<p>This helped save time on problems where the answer stabilized quickly. It also helped preserve runtime for harder problems later in the evaluation.</p>\n<hr>\n<h2>Runtime Budgeting</h2>\n<p>The notebook uses a global runtime limit of 17,400 seconds. For each problem, it computes the remaining time and reserves a base budget for the remaining problems.</p>\n<p>The budget is capped between:</p>\n<ul>\n<li>300 seconds minimum</li>\n<li>900 seconds maximum</li>\n</ul>\n<p>This prevents one difficult problem from consuming too much of the global budget.</p>\n<p>The budgeting logic is:</p>\n<pre><code>elapsed = time.time() - self.start_time\nleft = self.cfg.notebook_limit - elapsed\nothers = max(0, self.problems_remaining - 1)\nreserve = others * self.cfg.base_problem_timeout\n\nbudget = left - reserve\nbudget = min(budget, self.cfg.high_problem_timeout)\nbudget = max(budget, self.cfg.base_problem_timeout)\n</code></pre>\n<hr>\n<h2>What Worked</h2>\n<p>The strongest parts of the system were:</p>\n<ul>\n<li>Compact prompts</li>\n<li>Tool-augmented reasoning</li>\n<li>8 parallel attempts</li>\n<li>Deterministic per-problem seeds</li>\n<li>Majority-first aggregation</li>\n<li>Early stopping at 4 agreements</li>\n<li>Runtime budgeting across the evaluation</li>\n</ul>\n<p>The model performed best when it used Python to verify computations instead of relying only on text reasoning.</p>\n<hr>\n<h2>Future Improvements</h2>\n<p>Several directions are identified for further exploration in future iterations:</p>\n<ul>\n<li>Difficulty-aware scheduling</li>\n</ul>\n<p>The current system starts with the same number of attempts for every problem. A stronger version could estimate problem difficulty from early attempts and allocate extra compute only when needed.</p>\n<ul>\n<li>Better verification</li>\n</ul>\n<p>The current verifier is implicit: agreement and entropy are used as signals. A future version could verify consistency between the final answer, Python outputs, and the reasoning trace.</p>\n<ul>\n<li>More detailed logging</li>\n</ul>\n<p>The current notebook focuses on producing valid submissions. A research version should log attempt-level traces, Python call counts, entropy, time per attempt, and answer agreement patterns.</p>\n<ul>\n<li>Adaptive second wave</li>\n</ul>\n<p>If early attempts strongly disagree, a second wave could be launched selectively. I did not include this in the selected notebook because the additional runtime risk was not clearly worth it.</p>\n<hr>\n<h2>Reproducibility</h2>\n<p>The selected submission was run in the Kaggle competition environment.</p>\n<ul>\n<li>Hardware: 1× NVIDIA H100</li>\n<li>Model: gpt-oss-120b</li>\n<li>Model source: Kaggle model input</li>\n<li>Utility wheels: AIMO 3 Utils</li>\n<li>Inference engine: vLLM</li>\n<li>Tool interface: Harmony + Jupyter kernel</li>\n<li>Fine-tuning: None</li>\n</ul>\n<hr>\n<h2>Results</h2>\n<table>\n<thead>\n<tr>\n<th>Split</th>\n<th>Score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Private score</td>\n<td>42 / 50</td>\n</tr>\n<tr>\n<td>Public score</td>\n<td>42 / 50</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Conclusion</h2>\n<p>This solution shows that a competitive mathematical reasoning system can be built without fine-tuning the base model. The main contribution is the inference harness around the model: vLLM serving, Harmony tool use, Python sandboxing, deterministic attempt diversity, early stopping, time budgeting, and majority-first aggregation.</p>\n<p>My main takeaway from AIMO3 is that strong LLM reasoning is not only a modeling problem. It is also a systems problem. The model must be paired with the right execution environment, tool interface, answer extraction logic, and aggregation strategy to perform reliably under competition constraints.</p>",
  "comments": [],
  "comment_count": 0,
  "messages": [
    {
      "id": 3447849,
      "postDate": "2026-04-24T04:53:15.140Z",
      "content": "<h1>NeverGiveUp: Majority-First Tool-Augmented Reasoning for AIMO3</h1>\n<h2>Acknowledgments</h2>\n<p>This solution was developed for the AI Mathematical Olympiad - Progress Prize 3 competition. My selected notebook, <strong>AIMO3-NeverGiveUp</strong>, achieved a private score of <strong>42</strong> using <code>gpt-oss-120b</code> without fine-tuning.</p>\n<p>The notebook is an original, independent implementation. It draws high-level inspiration from publicly shared AIMO3 community approaches that explored the combination of vLLM serving, Harmony tool-calling, Python sandbox execution, and multi-attempt reasoning. One reference that helped me understand this general pattern was:</p>\n<ul>\n<li><strong>Inspired by:</strong> <code>[44/50] Skills Optional, Luck Necessary</code></li>\n<li><strong>Author:</strong> Andreas Bisiadis</li>\n<li><strong>Link:</strong> <a href=\"https://www.kaggle.com/code/nihilisticneuralnet/44-50-skills-optional-luck-necessary\" target=\"_blank\">https://www.kaggle.com/code/nihilisticneuralnet/44-50-skills-optional-luck-necessary</a></li>\n</ul>\n<p>All code structure, class design, naming, prompts, deterministic seeding, aggregation logic, timeout handling, and implementation details in my notebook were written independently and materially differ from the referenced work.</p>\n<p>Also, my thanks to the AIMO organizers, Kaggle, XTX Markets, and the AIMO3 community for making this competition possible and for encouraging detailed technical sharing.</p>\n<hr>\n<h2>Overview</h2>\n<p>My solution focuses on inference-time engineering rather than model fine-tuning. The solver runs <code>gpt-oss-120b</code> on a single Kaggle H100 through vLLM, using fp8 KV cache, prefix caching, Harmony encoding, and a stateful Python/Jupyter tool layer.</p>\n<p>For each problem, the system launches 8 independent reasoning attempts. Each attempt receives the same problem, but uses deterministic per-problem seeding to encourage controlled diversity across attempts. The model can call a Python sandbox during generation to perform computation, verification, symbolic manipulation, or brute-force checks.</p>\n<p>Once attempts produce candidate answers, the solver applies a majority-first aggregation strategy. If one answer receives the highest agreement, it is selected. If multiple answers tie, the solver uses the lowest mean token entropy as the tie-breaker.</p>\n<p>The main idea is simple: a strong mathematical reasoning model still needs a reliable execution system around it. In this competition, performance depended not only on the model, but also on how inference was orchestrated, how tools were exposed, how time was managed, and how candidate answers were selected.</p>\n<hr>\n<h2>System Architecture</h2>\n<p>The solution has four main components:</p>\n<ol>\n<li>Offline dependency setup using the AIMO3 utility wheels</li>\n<li>Local vLLM server for <code>gpt-oss-120b</code></li>\n<li>Harmony prompt wrapper and Python tool interface</li>\n<li>Multi-attempt reasoning engine with majority-first aggregation</li>\n</ol>\n<p>The model is served through a local OpenAI-compatible vLLM endpoint. The solver then communicates with the server through the OpenAI client API.</p>\n<p>Before starting inference, the notebook prewarms model files by reading the model checkpoint files into the OS page cache. This reduces server startup friction. The notebook then initializes 16 Python kernels, each preloaded with common mathematical libraries.</p>\n<hr>\n<h2>Configuration</h2>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Value</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Model</td>\n<td><code>gpt-oss-120b</code></td>\n<td>Kaggle-hosted model</td>\n</tr>\n<tr>\n<td>Inference engine</td>\n<td>vLLM</td>\n<td>OpenAI-compatible local server</td>\n</tr>\n<tr>\n<td>Hardware</td>\n<td>1× Kaggle H100</td>\n<td>Competition environment</td>\n</tr>\n<tr>\n<td>KV cache</td>\n<td><code>fp8_e4m3</code></td>\n<td>Memory-efficient serving</td>\n</tr>\n<tr>\n<td>Context length</td>\n<td>65,536 tokens</td>\n<td><code>max_model_len</code></td>\n</tr>\n<tr>\n<td>Reasoning effort</td>\n<td>HIGH</td>\n<td>Harmony system content</td>\n</tr>\n<tr>\n<td>Temperature</td>\n<td>0.9</td>\n<td>Used in selected notebook</td>\n</tr>\n<tr>\n<td><code>min_p</code></td>\n<td>0.02</td>\n<td>Sampling filter</td>\n</tr>\n<tr>\n<td>Attempts per problem</td>\n<td>8</td>\n<td>Parallel independent attempts</td>\n</tr>\n<tr>\n<td>Worker kernels</td>\n<td>16</td>\n<td>Python sandbox pool</td>\n</tr>\n<tr>\n<td>Turns per attempt</td>\n<td>96</td>\n<td>Chosen to limit drift and cost</td>\n</tr>\n<tr>\n<td>Early-stop threshold</td>\n<td>4 agreements</td>\n<td>Stop when enough attempts converge</td>\n</tr>\n<tr>\n<td>Base timeout</td>\n<td>300 s</td>\n<td>Reserved per problem</td>\n</tr>\n<tr>\n<td>Max timeout</td>\n<td>900 s</td>\n<td>If enough global time remains</td>\n</tr>\n<tr>\n<td>Notebook runtime limit</td>\n<td>17,400 s</td>\n<td>Conservative limit below full wall clock</td>\n</tr>\n<tr>\n<td>Aggregation</td>\n<td>Majority-first + entropy tie-break</td>\n<td>Agreement first, confidence second</td>\n</tr>\n<tr>\n<td>Seed</td>\n<td>42</td>\n<td>Base seed</td>\n</tr>\n<tr>\n<td>Answer range</td>\n<td><code>[0, 99999]</code></td>\n<td>Competition format</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Prompts</h2>\n<p>The prompts were intentionally compact, and the system was intended to define the task, answer format, and tool availability without forcing the model into a rigid reasoning template.</p>\n<h3>System prompt</h3>\n<pre><code>You are an elite math problem solver.\nSolve the problem carefully and verify your result.\nIf computation helps, call the python tool.\n\n### Rules:\n- Final answer must be an integer in [0, 99999]\n- Put final answer in \\boxed{...}\n</code></pre>\n<p><strong>Tool prompt</strong></p>\n<pre><code>Python tool: use it for computation, brute force, algebra (sympy), or checks.\nExplain briefly what we're doing, then run code. Use print() for outputs.\n</code></pre>\n<p><strong>Preference prompt</strong></p>\n<pre><code>Use math/numpy/sympy/mpmath. Prefer exact reasoning, then verify.\n</code></pre>\n<p>The design principle was to give the model enough structure to behave reliably, but not so much structure that the prompt starts controlling the reasoning path too aggressively. In my experiments, compact prompting was easier to manage under strict runtime limits.</p>\n<hr>\n<h2>Python Tool Layer</h2>\n<p>Each reasoning attempt has access to a stateful Python sandbox through Harmony tool messages. Every sandbox preloads:</p>\n<ul>\n<li>math</li>\n<li>numpy</li>\n<li>sympy</li>\n<li>itertools</li>\n<li>collections</li>\n<li>mpmath with 64-digit precision</li>\n</ul>\n<p>This tool layer is important because many AIMO-style problems require exact arithmetic, symbolic simplification, modular checks, combinatorial enumeration, or high-precision numerical validation.</p>\n<p>Also, added a helper that wraps the final expression in print() when appropriate. This avoids silent Python cells and makes outputs visible to the model.</p>\n<hr>\n<h2>Parallel Attempts</h2>\n<p>For each problem, the solver launches 8 independent attempts. Each attempt has the same model, prompt, and tool access, but receives a deterministic seed based on the problem text and attempt index.</p>\n<p>The seed is computed as:</p>\n<pre><code>h = hashlib.md5(problem_text.encode(\"utf-8\")).hexdigest()\nbase = int(h[:8], 16)\nseed = (base + 9973 * attempt_idx) % (2**31 - 1)\n</code></pre>\n<p>Used this design because two things were intended at the same time:</p>\n<ul>\n<li>Diversity between attempts</li>\n<li>More stable behavior across reruns</li>\n</ul>\n<p>This was especially useful because stochastic inference can produce noticeable leaderboard variance.</p>\n<hr>\n<h2>Streaming Answer Extraction</h2>\n<p>The model output is streamed token by token. During streaming, the solver scans recent text whenever a closing brace appears. This allows an attempt to terminate as soon as a valid answer is found instead of waiting for the full message to finish.</p>\n<p>The primary answer pattern is:</p>\n<pre><code>\\boxed{...}\n</code></pre>\n<p>The regex used is:</p>\n<pre><code>r\"\\\\boxed\\s*\\{\\s*([0-9,]+)\\s*\\}\"\n</code></pre>\n<p>If no boxed answer is found, the fallback pattern checks for:</p>\n<pre><code>final answer is &lt;integer&gt;\n</code></pre>\n<p>Only integers in [0, 99999] are accepted. Attempts that do not produce a valid integer answer do not contribute to the final vote.</p>\n<hr>\n<h2>Aggregation</h2>\n<p>The final submission uses a majority-first aggregation rule.</p>\n<p>The process is:</p>\n<ul>\n<li>Collect valid answers from all completed attempts.</li>\n<li>Count how many times each answer appears.</li>\n<li>Select the answer with the highest count.</li>\n<li>If multiple answers tie, select the tied answer with the lowest mean token entropy.</li>\n</ul>\n<p>The relevant idea is:</p>\n<pre><code>counts = Counter(answers)\ntop_cnt = counts.most_common(1)[0][1]\ntop_answers = [a for a, c in counts.items() if c == top_cnt]\n</code></pre>\n<p>If there is only one top answer, it is selected directly. If there is a tie, entropy is used as a confidence signal.</p>\n<p>This is chosen because agreement between independent attempts is an interpretable reliability signal. Entropy is useful, but in this system, it's used as a tie-breaker rather than allowing one low-entropy answer to override a stronger majority.</p>\n<hr>\n<h2>Early Stopping</h2>\n<p>The solver stops early once 4 valid attempts agree on the same answer.</p>\n<pre><code>if Counter(valid).most_common(1)[0][1] &gt;= self.cfg.early_stop:\n    stop_evt.set()\n</code></pre>\n<p>This helped save time on problems where the answer stabilized quickly. It also helped preserve runtime for harder problems later in the evaluation.</p>\n<hr>\n<h2>Runtime Budgeting</h2>\n<p>The notebook uses a global runtime limit of 17,400 seconds. For each problem, it computes the remaining time and reserves a base budget for the remaining problems.</p>\n<p>The budget is capped between:</p>\n<ul>\n<li>300 seconds minimum</li>\n<li>900 seconds maximum</li>\n</ul>\n<p>This prevents one difficult problem from consuming too much of the global budget.</p>\n<p>The budgeting logic is:</p>\n<pre><code>elapsed = time.time() - self.start_time\nleft = self.cfg.notebook_limit - elapsed\nothers = max(0, self.problems_remaining - 1)\nreserve = others * self.cfg.base_problem_timeout\n\nbudget = left - reserve\nbudget = min(budget, self.cfg.high_problem_timeout)\nbudget = max(budget, self.cfg.base_problem_timeout)\n</code></pre>\n<hr>\n<h2>What Worked</h2>\n<p>The strongest parts of the system were:</p>\n<ul>\n<li>Compact prompts</li>\n<li>Tool-augmented reasoning</li>\n<li>8 parallel attempts</li>\n<li>Deterministic per-problem seeds</li>\n<li>Majority-first aggregation</li>\n<li>Early stopping at 4 agreements</li>\n<li>Runtime budgeting across the evaluation</li>\n</ul>\n<p>The model performed best when it used Python to verify computations instead of relying only on text reasoning.</p>\n<hr>\n<h2>Future Improvements</h2>\n<p>Several directions are identified for further exploration in future iterations:</p>\n<ul>\n<li>Difficulty-aware scheduling</li>\n</ul>\n<p>The current system starts with the same number of attempts for every problem. A stronger version could estimate problem difficulty from early attempts and allocate extra compute only when needed.</p>\n<ul>\n<li>Better verification</li>\n</ul>\n<p>The current verifier is implicit: agreement and entropy are used as signals. A future version could verify consistency between the final answer, Python outputs, and the reasoning trace.</p>\n<ul>\n<li>More detailed logging</li>\n</ul>\n<p>The current notebook focuses on producing valid submissions. A research version should log attempt-level traces, Python call counts, entropy, time per attempt, and answer agreement patterns.</p>\n<ul>\n<li>Adaptive second wave</li>\n</ul>\n<p>If early attempts strongly disagree, a second wave could be launched selectively. I did not include this in the selected notebook because the additional runtime risk was not clearly worth it.</p>\n<hr>\n<h2>Reproducibility</h2>\n<p>The selected submission was run in the Kaggle competition environment.</p>\n<ul>\n<li>Hardware: 1× NVIDIA H100</li>\n<li>Model: gpt-oss-120b</li>\n<li>Model source: Kaggle model input</li>\n<li>Utility wheels: AIMO 3 Utils</li>\n<li>Inference engine: vLLM</li>\n<li>Tool interface: Harmony + Jupyter kernel</li>\n<li>Fine-tuning: None</li>\n</ul>\n<hr>\n<h2>Results</h2>\n<table>\n<thead>\n<tr>\n<th>Split</th>\n<th>Score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Private score</td>\n<td>42 / 50</td>\n</tr>\n<tr>\n<td>Public score</td>\n<td>42 / 50</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Conclusion</h2>\n<p>This solution shows that a competitive mathematical reasoning system can be built without fine-tuning the base model. The main contribution is the inference harness around the model: vLLM serving, Harmony tool use, Python sandboxing, deterministic attempt diversity, early stopping, time budgeting, and majority-first aggregation.</p>\n<p>My main takeaway from AIMO3 is that strong LLM reasoning is not only a modeling problem. It is also a systems problem. The model must be paired with the right execution environment, tool interface, answer extraction logic, and aggregation strategy to perform reliably under competition constraints.</p>",
      "rawMarkdown": "# NeverGiveUp: Majority-First Tool-Augmented Reasoning for AIMO3\n\n## Acknowledgments\n\nThis solution was developed for the AI Mathematical Olympiad - Progress Prize 3 competition. My selected notebook, **AIMO3-NeverGiveUp**, achieved a private score of **42** using `gpt-oss-120b` without fine-tuning.\n\nThe notebook is an original, independent implementation. It draws high-level inspiration from publicly shared AIMO3 community approaches that explored the combination of vLLM serving, Harmony tool-calling, Python sandbox execution, and multi-attempt reasoning. One reference that helped me understand this general pattern was:\n\n- **Inspired by:** `[44/50] Skills Optional, Luck Necessary`\n- **Author:** Andreas Bisiadis\n- **Link:** https://www.kaggle.com/code/nihilisticneuralnet/44-50-skills-optional-luck-necessary\n\nAll code structure, class design, naming, prompts, deterministic seeding, aggregation logic, timeout handling, and implementation details in my notebook were written independently and materially differ from the referenced work.\n\nAlso, my thanks to the AIMO organizers, Kaggle, XTX Markets, and the AIMO3 community for making this competition possible and for encouraging detailed technical sharing.\n\n---\n\n## Overview\n\nMy solution focuses on inference-time engineering rather than model fine-tuning. The solver runs `gpt-oss-120b` on a single Kaggle H100 through vLLM, using fp8 KV cache, prefix caching, Harmony encoding, and a stateful Python/Jupyter tool layer.\n\nFor each problem, the system launches 8 independent reasoning attempts. Each attempt receives the same problem, but uses deterministic per-problem seeding to encourage controlled diversity across attempts. The model can call a Python sandbox during generation to perform computation, verification, symbolic manipulation, or brute-force checks.\n\nOnce attempts produce candidate answers, the solver applies a majority-first aggregation strategy. If one answer receives the highest agreement, it is selected. If multiple answers tie, the solver uses the lowest mean token entropy as the tie-breaker.\n\nThe main idea is simple: a strong mathematical reasoning model still needs a reliable execution system around it. In this competition, performance depended not only on the model, but also on how inference was orchestrated, how tools were exposed, how time was managed, and how candidate answers were selected.\n\n---\n\n## System Architecture\n\nThe solution has four main components:\n\n1. Offline dependency setup using the AIMO3 utility wheels\n2. Local vLLM server for `gpt-oss-120b`\n3. Harmony prompt wrapper and Python tool interface\n4. Multi-attempt reasoning engine with majority-first aggregation\n\nThe model is served through a local OpenAI-compatible vLLM endpoint. The solver then communicates with the server through the OpenAI client API.\n\nBefore starting inference, the notebook prewarms model files by reading the model checkpoint files into the OS page cache. This reduces server startup friction. The notebook then initializes 16 Python kernels, each preloaded with common mathematical libraries.\n\n---\n\n## Configuration\n\n| Parameter | Value | Notes |\n|---|---:|---|\n| Model | `gpt-oss-120b` | Kaggle-hosted model |\n| Inference engine | vLLM | OpenAI-compatible local server |\n| Hardware | 1× Kaggle H100 | Competition environment |\n| KV cache | `fp8_e4m3` | Memory-efficient serving |\n| Context length | 65,536 tokens | `max_model_len` |\n| Reasoning effort | HIGH | Harmony system content |\n| Temperature | 0.9 | Used in selected notebook |\n| `min_p` | 0.02 | Sampling filter |\n| Attempts per problem | 8 | Parallel independent attempts |\n| Worker kernels | 16 | Python sandbox pool |\n| Turns per attempt | 96 | Chosen to limit drift and cost |\n| Early-stop threshold | 4 agreements | Stop when enough attempts converge |\n| Base timeout | 300 s | Reserved per problem |\n| Max timeout | 900 s | If enough global time remains |\n| Notebook runtime limit | 17,400 s | Conservative limit below full wall clock |\n| Aggregation | Majority-first + entropy tie-break | Agreement first, confidence second |\n| Seed | 42 | Base seed |\n| Answer range | `[0, 99999]` | Competition format |\n\n---\n\n## Prompts\n\nThe prompts were intentionally compact, and the system was intended to define the task, answer format, and tool availability without forcing the model into a rigid reasoning template.\n\n### System prompt\n\n```text\nYou are an elite math problem solver.\nSolve the problem carefully and verify your result.\nIf computation helps, call the python tool.\n\n### Rules:\n- Final answer must be an integer in [0, 99999]\n- Put final answer in \\boxed{...}\n```\n\n**Tool prompt**\n```\nPython tool: use it for computation, brute force, algebra (sympy), or checks.\nExplain briefly what we're doing, then run code. Use print() for outputs.\n```\n\n**Preference prompt**\n```\nUse math/numpy/sympy/mpmath. Prefer exact reasoning, then verify.\n```\n\nThe design principle was to give the model enough structure to behave reliably, but not so much structure that the prompt starts controlling the reasoning path too aggressively. In my experiments, compact prompting was easier to manage under strict runtime limits.\n\n---\n\n## Python Tool Layer\n\nEach reasoning attempt has access to a stateful Python sandbox through Harmony tool messages. Every sandbox preloads:\n\n- math\n- numpy\n- sympy\n- itertools\n- collections\n- mpmath with 64-digit precision\n\nThis tool layer is important because many AIMO-style problems require exact arithmetic, symbolic simplification, modular checks, combinatorial enumeration, or high-precision numerical validation.\n\nAlso, added a helper that wraps the final expression in print() when appropriate. This avoids silent Python cells and makes outputs visible to the model.\n\n---\n\n## Parallel Attempts\n\nFor each problem, the solver launches 8 independent attempts. Each attempt has the same model, prompt, and tool access, but receives a deterministic seed based on the problem text and attempt index.\n\nThe seed is computed as:\n```\nh = hashlib.md5(problem_text.encode(\"utf-8\")).hexdigest()\nbase = int(h[:8], 16)\nseed = (base + 9973 * attempt_idx) % (2**31 - 1)\n```\n\nUsed this design because two things were intended at the same time:\n\n- Diversity between attempts\n- More stable behavior across reruns\n\nThis was especially useful because stochastic inference can produce noticeable leaderboard variance.\n\n---\n\n## Streaming Answer Extraction\n\nThe model output is streamed token by token. During streaming, the solver scans recent text whenever a closing brace appears. This allows an attempt to terminate as soon as a valid answer is found instead of waiting for the full message to finish.\n\nThe primary answer pattern is:\n```\n\\boxed{...}\n```\n\nThe regex used is:\n```\nr\"\\\\boxed\\s*\\{\\s*([0-9,]+)\\s*\\}\"\n```\n\nIf no boxed answer is found, the fallback pattern checks for:\n```\nfinal answer is <integer>\n```\n\nOnly integers in [0, 99999] are accepted. Attempts that do not produce a valid integer answer do not contribute to the final vote.\n\n---\n\n## Aggregation\n\nThe final submission uses a majority-first aggregation rule.\n\nThe process is:\n\n- Collect valid answers from all completed attempts.\n- Count how many times each answer appears.\n- Select the answer with the highest count.\n- If multiple answers tie, select the tied answer with the lowest mean token entropy.\n\nThe relevant idea is:\n```\ncounts = Counter(answers)\ntop_cnt = counts.most_common(1)[0][1]\ntop_answers = [a for a, c in counts.items() if c == top_cnt]\n```\n\nIf there is only one top answer, it is selected directly. If there is a tie, entropy is used as a confidence signal.\n\nThis is chosen because agreement between independent attempts is an interpretable reliability signal. Entropy is useful, but in this system, it's used as a tie-breaker rather than allowing one low-entropy answer to override a stronger majority.\n\n---\n\n## Early Stopping\n\nThe solver stops early once 4 valid attempts agree on the same answer.\n```\nif Counter(valid).most_common(1)[0][1] >= self.cfg.early_stop:\n    stop_evt.set()\n```\n\nThis helped save time on problems where the answer stabilized quickly. It also helped preserve runtime for harder problems later in the evaluation.\n\n---\n\n## Runtime Budgeting\n\nThe notebook uses a global runtime limit of 17,400 seconds. For each problem, it computes the remaining time and reserves a base budget for the remaining problems.\n\nThe budget is capped between:\n\n- 300 seconds minimum\n- 900 seconds maximum\n\nThis prevents one difficult problem from consuming too much of the global budget.\n\nThe budgeting logic is:\n```\nelapsed = time.time() - self.start_time\nleft = self.cfg.notebook_limit - elapsed\nothers = max(0, self.problems_remaining - 1)\nreserve = others * self.cfg.base_problem_timeout\n\nbudget = left - reserve\nbudget = min(budget, self.cfg.high_problem_timeout)\nbudget = max(budget, self.cfg.base_problem_timeout)\n```\n\n---\n\n## What Worked\n\nThe strongest parts of the system were:\n\n- Compact prompts\n- Tool-augmented reasoning\n- 8 parallel attempts\n- Deterministic per-problem seeds\n- Majority-first aggregation\n- Early stopping at 4 agreements\n- Runtime budgeting across the evaluation\n\nThe model performed best when it used Python to verify computations instead of relying only on text reasoning.\n\n---\n\n## Future Improvements\n\nSeveral directions are identified for further exploration in future iterations:\n\n- Difficulty-aware scheduling\n\nThe current system starts with the same number of attempts for every problem. A stronger version could estimate problem difficulty from early attempts and allocate extra compute only when needed.\n\n- Better verification\n\nThe current verifier is implicit: agreement and entropy are used as signals. A future version could verify consistency between the final answer, Python outputs, and the reasoning trace.\n\n- More detailed logging\n\nThe current notebook focuses on producing valid submissions. A research version should log attempt-level traces, Python call counts, entropy, time per attempt, and answer agreement patterns.\n\n- Adaptive second wave\n\nIf early attempts strongly disagree, a second wave could be launched selectively. I did not include this in the selected notebook because the additional runtime risk was not clearly worth it.\n\n---\n\n## Reproducibility\n\nThe selected submission was run in the Kaggle competition environment.\n\n- Hardware: 1× NVIDIA H100\n- Model: gpt-oss-120b\n- Model source: Kaggle model input\n- Utility wheels: AIMO 3 Utils\n- Inference engine: vLLM\n- Tool interface: Harmony + Jupyter kernel\n- Fine-tuning: None\n\n---\n\n## Results\n\n|Split                 | Score   |\n|------------------------|\n|Private score\t| 42 / 50  |\n|                                       |\n|Public score\t| 42 / 50  |           \n\n---\n\n## Conclusion\n\nThis solution shows that a competitive mathematical reasoning system can be built without fine-tuning the base model. The main contribution is the inference harness around the model: vLLM serving, Harmony tool use, Python sandboxing, deterministic attempt diversity, early stopping, time budgeting, and majority-first aggregation.\n\nMy main takeaway from AIMO3 is that strong LLM reasoning is not only a modeling problem. It is also a systems problem. The model must be paired with the right execution environment, tool interface, answer extraction logic, and aggregation strategy to perform reliably under competition constraints."
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-20T04:25:27Z",
  "competition_id": 118448,
  "competition_slug": "ai-mathematical-olympiad-progress-prize-3",
  "team_id": 14852057,
  "solution_url": "/competitions/ai-mathematical-olympiad-progress-prize-3/writeups/nevergiveup-majority-first-tool-augmented-reasoni"
}