<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://namangoyal.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://namangoyal.com/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-09-03T02:06:28+00:00</updated><id>https://namangoyal.com/feed.xml</id><title type="html">blank</title><subtitle>Palindrome&apos;s life. A software engineer by day and creater at night. </subtitle><entry><title type="html">Two AI Agents, One MacBook, Zero API Keys</title><link href="https://namangoyal.com/blog/2026/multi-agent-factory/" rel="alternate" type="text/html" title="Two AI Agents, One MacBook, Zero API Keys"/><published>2026-03-13T12:00:00+00:00</published><updated>2026-03-13T12:00:00+00:00</updated><id>https://namangoyal.com/blog/2026/multi-agent-factory</id><content type="html" xml:base="https://namangoyal.com/blog/2026/multi-agent-factory/"><![CDATA[<p><strong>Code:</strong> <a href="https://github.com/thenamangoyal/multi-agent-mlx">github.com/thenamangoyal/multi-agent-mlx</a></p> <p>Watch two AI agents collaborate in real time. The Coder writes code, the sandbox runs it, and the Sheriff reviews the output. If something breaks, the error goes back to the Coder.</p> <div id="agent-simulation" style="width:100%; border-radius:12px; overflow:hidden; margin: 1.5rem 0; cursor:pointer;"></div> <script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script> <script src="/assets/js/agent-simulation.js"></script> <p>What happens when you put two AI agents in a room, give them a coding task, and let them argue until the code works?</p> <p>That is the premise behind this project. Agent A (the <strong>Coder</strong>) writes Python scripts. Agent B (the <strong>Sheriff</strong>) runs them, reads the stack traces, and tells the Coder what it got wrong. They go back and forth until the code passes or a hard limit kicks in, following the iterative self-refinement pattern <a class="citation" href="#madaan2023selfrefine">(Madaan et al., 2023)</a> with execution-grounded feedback <a class="citation" href="#shinn2023reflexion">(Shinn et al., 2023)</a>. The catch is that both agents run on a single MacBook using Qwen2.5-Coder-7B-4bit <a class="citation" href="#hui2024qwen25coder">(Hui et al., 2024)</a>. No OpenAI key, no cloud GPU, no data ever leaving the machine.</p> <p>I built this to answer a question that kept nagging me: <em>can two small local models actually collaborate to produce working code, the same way cloud-based agents do with GPT-4?</em> The answer, it turns out, is surprisingly yes. With some interesting caveats about what small models can and cannot self-correct.</p> <p>The code is at <a href="https://github.com/thenamangoyal/multi-agent-mlx">github.com/thenamangoyal/multi-agent-mlx</a> <a class="citation" href="#goyal2026two-ai-agents-one-macbook-zero-api-keys">(Goyal, 2026)</a>.</p> <h2 id="results-at-a-glance">Results at a Glance</h2> <p>I ran three scenarios of increasing difficulty, all on an M1 Pro with 16 GB of RAM:</p> <table> <thead> <tr> <th>Scenario</th> <th>Task</th> <th style="text-align: center">Attempts</th> <th style="text-align: center">Time</th> <th>Result</th> </tr> </thead> <tbody> <tr> <td>The Off-by-One Gauntlet</td> <td>Generate a formatted calendar for March 2026 without the <code class="language-plaintext highlighter-rouge">calendar</code> module</td> <td style="text-align: center">1</td> <td style="text-align: center">22s</td> <td>First-try success</td> </tr> <tr> <td>The CSV Detective</td> <td>Generate 200-row CSV, read it back, compute revenue analytics with exact formatting</td> <td style="text-align: center">3</td> <td style="text-align: center">159s</td> <td>Self-corrected twice</td> </tr> <tr> <td>Gradient Descent from Scratch</td> <td>Train a neural network with manual backprop using only numpy</td> <td style="text-align: center">2</td> <td style="text-align: center">50s</td> <td>Self-corrected once</td> </tr> </tbody> </table> <p>Total wall time: <strong>3 minutes 50 seconds</strong>. Total cost: <strong>$0.00</strong>.</p> <pre><code class="language-echarts">{
  "responsive": true,
  "tooltip": {"trigger": "axis"},
  "xAxis": {
    "type": "category",
    "data": ["Calendar", "CSV Detective", "Neural Net"],
    "axisLabel": {"fontSize": 13}
  },
  "yAxis": {
    "type": "value",
    "name": "Seconds",
    "nameTextStyle": {"fontSize": 13}
  },
  "series": [
    {
      "type": "bar",
      "data": [
        {"value": 22.0, "itemStyle": {"color": "#00CC96"}},
        {"value": 158.8, "itemStyle": {"color": "#636EFA"}},
        {"value": 49.6, "itemStyle": {"color": "#EF553B"}}
      ],
      "barWidth": "50%",
      "label": {
        "show": true,
        "position": "top",
        "formatter": ["{b|1 attempt}", "{b|3 attempts}", "{b|2 attempts}"],
        "rich": {"b": {"fontSize": 12}}
      }
    }
  ]
}
</code></pre> <p>Every scenario eventually produced correct, running code. The interesting part is not that they succeeded. It is <em>how</em> they failed first and then fixed themselves.</p> <h2 id="the-architecture">The Architecture</h2> <p>The key constraint is 16 GB of RAM. Two separate 7B models would blow past that. The solution: both agents share a <strong>single <code class="language-plaintext highlighter-rouge">mlx_lm.server</code> instance</strong> serving one model (Qwen2.5-Coder-7B-Instruct-4bit, ~4 GB). They are two system-prompt identities taking sequential turns, orchestrated by plain Python. Not a free-form multi-agent chat. A structured feedback loop.</p> <h3 id="memory-budget">Memory Budget</h3> <table> <thead> <tr> <th>Component</th> <th style="text-align: right">RAM</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td>macOS + system</td> <td style="text-align: right">~3-4 GB</td> <td>Baseline</td> </tr> <tr> <td>Qwen2.5-Coder-7B-4bit</td> <td style="text-align: right">~4 GB</td> <td>Single model, loaded once</td> </tr> <tr> <td>KV cache</td> <td style="text-align: right">~1-2 GB</td> <td>Prompt context</td> </tr> <tr> <td>Python + sandbox</td> <td style="text-align: right">~0.5 GB</td> <td>Orchestrator + scripts</td> </tr> <tr> <td><strong>Total</strong></td> <td style="text-align: right"><strong>~9-10 GB</strong></td> <td><strong>6-7 GB headroom</strong></td> </tr> </tbody> </table> <h3 id="why-the-orchestrator-executes-code-directly">Why the Orchestrator Executes Code Directly</h3> <p>This was a hard-won lesson. My initial design had the Sheriff agent calling an <code class="language-plaintext highlighter-rouge">execute_code</code> tool through the framework’s tool-calling protocol. It never worked reliably. Small quantized models (4-bit 7B) are inconsistent at structured tool calling. They would output the code in a markdown block instead of invoking the tool function, or hallucinate JSON that did not match the schema.</p> <p>The fix was to take the mechanical execution out of the LLM’s hands entirely. The orchestrator extracts code from whatever the Coder produces (tool call or markdown), runs it in a subprocess, and feeds the raw stdout/stderr/exit code into the Sheriff’s prompt. The Sheriff’s job is reduced to what LLMs are actually good at: reading text and making judgments. Think of it like a code review. The reviewer does not need to run <code class="language-plaintext highlighter-rouge">gcc</code> themselves. A CI system runs the build, and the reviewer reads the output.</p> <h2 id="the-self-correcting-loop">The Self-Correcting Loop</h2> <p>As you saw in the simulation above, each attempt follows the same pattern: the Coder generates a script, the orchestrator extracts and executes it in a sandbox, and the Sheriff reviews the output. If the Sheriff says FAIL, the error report goes back to the Coder with specific fix suggestions. If it says PASS, the loop ends.</p> <p>Three safety layers prevent infinite loops:</p> <ol> <li><strong>Hard limits.</strong> Maximum 5 attempts, 60-second execution timeout, 120-second LLM timeout per call.</li> <li><strong>Stagnation detection.</strong> The system hashes the last 5 lines of each error traceback using MD5. If the same hash appears 3 times in a row, the model is stuck and the loop terminates.</li> <li><strong>Token budget.</strong> A hard cap of 100K tokens across all turns prevents runaway context accumulation.</li> </ol> <h2 id="scenario-1-the-off-by-one-gauntlet">Scenario 1: The Off-by-One Gauntlet</h2> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #00CC96;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:var(--global-theme-color);font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Task Given to Coder</p> <p style="margin:0;font-size:0.95rem">Write a Python script that generates a formatted calendar for March 2026. Print column headers (Mon-Sun), a day grid with right-aligned 4-character columns, and count weekdays vs weekend days. March 1, 2026 is a Sunday. Do NOT use the <code>calendar</code> module. Final line must be exactly <code>Weekdays: 22, Weekend days: 9</code>.</p> </div> <p><strong>What happened:</strong> The Coder nailed it on the first try. It used <code class="language-plaintext highlighter-rouge">datetime</code> to verify the day of the week, wrote a clean grid layout with proper alignment, and counted weekdays correctly. The Sheriff confirmed the output and returned <code class="language-plaintext highlighter-rouge">VERDICT: PASS</code> in 22 seconds flat.</p> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #00CC96;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#00CC96;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 1 - PASS (22s)</p> <pre style="margin:0.5rem 0 0 0;font-size:0.85rem;line-height:1.5"><code>      3 2026
 Mon Tue Wed Thu Fri Sat Sun
                           1
   2   3   4   5   6   7   8
   9  10  11  12  13  14  15
  16  17  18  19  20  21  22
  23  24  25  26  27  28  29
  30  31
Weekdays: 22, Weekend days: 9</code></pre> </div> <p>This is the easiest scenario, but it is not trivial. The model needs to handle date math, grid alignment with padding, and the edge case of a month starting on Sunday. A single off-by-one error in the first-day offset would cascade through every row. The model got it right in one shot.</p> <h2 id="scenario-2-the-csv-detective">Scenario 2: The CSV Detective</h2> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #636EFA;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:var(--global-theme-color);font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Task Given to Coder</p> <p style="margin:0;font-size:0.95rem">Write a complete data pipeline in one script. <strong>Step 1:</strong> Generate a 200-row CSV (<code>sales_data.csv</code>) with columns <code>date, product, region, units, price_per_unit</code> using <code>random.seed(42)</code>. <strong>Step 2:</strong> Read it back, compute revenue per product (sorted descending), top region, and top month. <strong>Step 3:</strong> Print a formatted report with dollar amounts using comma separators like <code>$12,345.67</code>. Standard library only.</p> </div> <p>This one tells the best self-correction story. The model needed three attempts, each failing in a different way.</p> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #e94560;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#e94560;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 1 - FAIL (scope error)</p> <p style="margin:0.3rem 0;font-size:0.9rem">The code was too long and hit the token limit before the closing <code>```</code>. The orchestrator salvaged what it could via its unclosed-block extractor, but the script had a scoping bug: <code>print_report()</code> referenced a variable defined in a different function.</p> <pre style="margin:0.5rem 0 0 0;font-size:0.82rem;line-height:1.5;color:#e94560"><code>NameError: name 'region_revenue' is not defined
  File "script.py", line 63, in print_report
    print(f"Top Region: {top_region} (${region_revenue[top_region]:,.2f})")</code></pre> <p style="margin:0.5rem 0 0 0;font-size:0.9rem"><strong>Sheriff:</strong> <em>"The variable <code>region_revenue</code> is not defined in <code>print_report()</code>. It is defined in <code>analyze_data()</code> but is not accessible. Suggested fix: pass it as a parameter."</em></p> </div> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #e94560;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#e94560;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 2 - FAIL (different bug)</p> <p style="margin:0.3rem 0;font-size:0.9rem">The Coder fixed the scope issue by passing <code>region_revenue</code> as a parameter. But it introduced a new bug: it forgot to also pass <code>month_revenue</code>.</p> <pre style="margin:0.5rem 0 0 0;font-size:0.82rem;line-height:1.5;color:#e94560"><code>NameError: name 'month_revenue' is not defined. Did you mean: 'total_revenue'?
  File "script.py", line 59, in print_report
    print(f"Top Month: {top_month} (${month_revenue[top_month]:,.2f})")</code></pre> <p style="margin:0.5rem 0 0 0;font-size:0.9rem"><strong>Sheriff:</strong> <em>"Same pattern as before. <code>month_revenue</code> is not passed to <code>print_report()</code>. Fix all remaining scope issues."</em></p> </div> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #00CC96;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#00CC96;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 3 - PASS (159s total)</p> <pre style="margin:0.5rem 0 0 0;font-size:0.85rem;line-height:1.5"><code>=== Sales Analysis Report ===
Revenue by Product:
  Doohickey: $125,089.64
  Gadget: $90,750.45
  Widget: $90,696.45
Top Region: North ($101,205.15)
Top Month: May ($42,193.29)
Total Records: 200</code></pre> </div> <pre><code class="language-echarts">{
  "responsive": true,
  "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}},
  "title": {"text": "Revenue by Product", "subtext": "Agent-generated CSV analysis", "left": "center"},
  "xAxis": {
    "type": "category",
    "data": ["Doohickey", "Gadget", "Widget"],
    "axisLabel": {"fontSize": 13}
  },
  "yAxis": {
    "type": "value",
    "name": "Total Revenue ($)",
    "axisLabel": {"formatter": "${value}"}
  },
  "series": [
    {
      "type": "bar",
      "data": [
        {"value": 125090, "itemStyle": {"color": "#e94560"}},
        {"value": 90750, "itemStyle": {"color": "#0f3460"}},
        {"value": 90696, "itemStyle": {"color": "#533483"}}
      ],
      "barWidth": "50%",
      "label": {
        "show": true,
        "position": "top",
        "formatter": ["$125,090", "$90,750", "$90,696"],
        "fontSize": 13,
        "fontWeight": "bold"
      },
      "emphasis": {"itemStyle": {"shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)"}}
    }
  ]
}
</code></pre> <p>The CSV scenario is interesting because it tests the full data pipeline: file I/O, random data generation with a seed, reading data back, aggregation, and precise string formatting. Each of those is a potential failure point, and the model had to coordinate all of them in a single script.</p> <h2 id="scenario-3-gradient-descent-from-scratch">Scenario 3: Gradient Descent from Scratch</h2> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #EF553B;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:var(--global-theme-color);font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Task Given to Coder</p> <p style="margin:0;font-size:0.95rem">Write a neural network using <strong>only numpy</strong>. No ML frameworks. Generate 200 binary classification points with <code>np.random.seed(42)</code>. Build a single-layer network: Input(2) -&gt; Sigmoid -&gt; Output(1). Train for 1000 epochs with learning rate 1.0 using binary cross-entropy loss and manual backprop. Print progress every 200 epochs. Final accuracy must be &gt;= 90%. Save weights to <code>model_data.npz</code>.</p> </div> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #e94560;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#e94560;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 1 - FAIL (code mixed with prose)</p> <p style="margin:0.3rem 0;font-size:0.9rem">The Coder tried to use the <code>write_file()</code> tool but then appended natural-language bullet points after the code. The result was valid Python mixed with markdown commentary that Python could not parse.</p> <pre style="margin:0.5rem 0 0 0;font-size:0.82rem;line-height:1.5;color:#e94560"><code>  File "script.py", line 65
    - The script generates 200 data points for binary classification.
          ^^^^^^
SyntaxError: invalid syntax</code></pre> <p style="margin:0.5rem 0 0 0;font-size:0.9rem"><strong>Sheriff:</strong> <em>"The line <code>- The script generates 200 data points</code> is not valid Python code. This line is intended as a comment but is being interpreted as code."</em></p> </div> <div style="background:var(--global-code-bg-color);border:1px solid var(--global-divider-color);border-left:4px solid #00CC96;border-radius:8px;padding:1rem 1.2rem;margin:1rem 0"> <p style="margin:0 0 0.3rem 0;font-weight:700;color:#00CC96;font-size:0.85rem;text-transform:uppercase;letter-spacing:1px">Attempt 2 - PASS (50s total)</p> <p style="margin:0.3rem 0;font-size:0.9rem">Clean 54-line script. Forward pass, backward pass, weight update, all mathematically correct. 100% accuracy:</p> <pre style="margin:0.5rem 0 0 0;font-size:0.85rem;line-height:1.5"><code>Epoch 200: loss=0.0819, acc=100.0%
Epoch 400: loss=0.0603, acc=100.0%
Epoch 600: loss=0.0504, acc=100.0%
Epoch 800: loss=0.0444, acc=100.0%
Epoch 1000: loss=0.0402, acc=100.0%
Final accuracy: 100.0%</code></pre> </div> <p>The training curve shows textbook convergence, from near-random (0.69) to 0.04 loss:</p> <pre><code class="language-echarts">{
  "responsive": true,
  "title": {"text": "Training Convergence", "subtext": "Agent-written neural network", "left": "center"},
  "tooltip": {"trigger": "axis"},
  "legend": {"data": ["Loss", "Accuracy (%)"], "bottom": "0%"},
  "xAxis": {"type": "category", "data": ["0", "200", "400", "600", "800", "1000"], "name": "Epoch", "nameLocation": "center", "nameGap": 30},
  "yAxis": [
    {"type": "value", "name": "Loss", "position": "left", "min": 0, "max": 0.75},
    {"type": "value", "name": "Accuracy (%)", "position": "right", "min": 0, "max": 110, "splitLine": {"show": false}}
  ],
  "series": [
    {
      "name": "Loss",
      "type": "line",
      "data": [0.693, 0.0819, 0.0603, 0.0504, 0.0444, 0.0402],
      "smooth": true,
      "symbol": "circle",
      "symbolSize": 8,
      "lineStyle": {"color": "#e94560", "width": 3},
      "itemStyle": {"color": "#e94560"},
      "areaStyle": {"color": {"type": "linear", "x": 0, "y": 0, "x2": 0, "y2": 1, "colorStops": [{"offset": 0, "color": "rgba(233,69,96,0.3)"}, {"offset": 1, "color": "rgba(233,69,96,0.02)"}]}}
    },
    {
      "name": "Accuracy (%)",
      "type": "line",
      "yAxisIndex": 1,
      "data": [50, 100, 100, 100, 100, 100],
      "smooth": true,
      "symbol": "circle",
      "symbolSize": 8,
      "lineStyle": {"color": "#00CC96", "width": 3},
      "itemStyle": {"color": "#00CC96"},
      "areaStyle": {"color": {"type": "linear", "x": 0, "y": 0, "x2": 0, "y2": 1, "colorStops": [{"offset": 0, "color": "rgba(0,204,150,0.3)"}, {"offset": 1, "color": "rgba(0,204,150,0.02)"}]}}
    }
  ]
}
</code></pre> <p>The decision boundary learned by the agent’s neural network. The line <code class="language-plaintext highlighter-rouge">9.26x + 8.59y - 0.03 = 0</code> cleanly separates the two classes. Hover over any point to see its coordinates and class.</p> <pre><code class="language-echarts">{
  "responsive": true,
  "title": {"text": "Decision Boundary", "subtext": "Agent-written neural network (200 data points)", "left": "center"},
  "tooltip": {"trigger": "item", "formatter": "({c})"},
  "legend": {"data": ["Class 0 (negative)", "Class 1 (positive)", "Decision boundary"], "bottom": "0%"},
  "xAxis": {"type": "value", "name": "Feature 1", "nameLocation": "center", "nameGap": 30, "min": -3.5, "max": 2.5},
  "yAxis": {"type": "value", "name": "Feature 2", "nameLocation": "center", "nameGap": 40, "min": -2.5, "max": 4.5},
  "series": [
    {
      "name": "Class 0 (negative)",
      "type": "scatter",
      "symbolSize": 8,
      "itemStyle": {"color": "#636EFA", "opacity": 0.8},
      "emphasis": {"itemStyle": {"shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)"}},
      "data": [[-0.234,-0.234],[1.579,0.767],[-0.463,-0.466],[-1.725,-0.562],[-1.013,0.314],[-0.908,-1.412],[0.068,-1.425],[-0.544,0.111],[-1.151,0.376],[-0.601,-0.292],[-0.013,-1.058],[0.823,-1.221],[0.209,-1.96],[-1.328,0.197],[-0.116,-0.301],[-1.479,-0.72],[0.344,-1.763],[0.324,-0.385],[-0.677,0.612],[-0.839,-0.309],[-0.479,-0.186],[-1.106,-1.196],[0.362,-0.645],[-2.62,0.822],[0.087,-0.299],[0.092,-1.988],[-0.808,-0.502],[-0.53,0.513],[-0.702,-0.328],[-0.392,-1.464],[0.005,-0.235],[-1.415,-0.421],[-0.343,-0.802],[-1.919,-0.027],[-0.035,-1.169],[0.791,-0.909],[-0.991,-0.566],[0.1,-0.503],[-1.551,0.069],[-1.062,0.474],[-0.783,-0.322],[0.814,-1.231],[-1.607,0.185],[-1.237,-1.32],[-0.68,0.232],[0.293,-0.714],[-1.191,0.657],[-0.975,0.787],[-0.245,-0.754],[-0.89,-0.816],[-0.847,-1.515],[0.214,-1.246],[-0.884,0.154],[0.058,-1.143],[-1.378,-0.938],[-0.773,-0.237],[-0.485,0.082],[0.686,-1.613],[0.064,-1.078],[-0.715,0.68],[-0.73,0.216],[0.046,-0.652],[-2.025,0.186],[-0.662,0.852],[-0.793,-0.115],[-1.2,-0.335],[-0.475,-0.653],[-1.261,0.918],[-1.519,-0.484],[-0.927,-0.06],[-3.241,-1.024],[-0.253,-1.248],[-0.44,0.131],[-0.982,0.462],[0.199,-0.6],[0.07,-0.385],[0.281,-0.623],[-0.829,-0.56],[-0.218,1.099],[0.324,-0.13],[-1.006,-1.214],[-0.012,-0.897],[0.076,-0.677],[-0.825,-0.321],[0.413,-0.564],[-0.822,0.244],[0.245,-0.507],[-0.471,0.232],[-1.448,-1.407],[-0.718,-0.213],[-0.019,-1.003],[-0.019,-0.289],[0.323,-0.827],[0.098,-0.773],[-0.84,-0.599],[-2.124,-0.526],[-0.759,0.15],[-0.898,0.492],[-1.713,1.354],[0.497,-0.138]]
    },
    {
      "name": "Class 1 (positive)",
      "type": "scatter",
      "symbolSize": 8,
      "itemStyle": {"color": "#EF553B", "opacity": 0.8},
      "emphasis": {"itemStyle": {"shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)"}},
      "data": [[0.648,1.523],[-0.469,0.543],[0.242,-1.913],[-0.602,1.852],[0.738,0.171],[-0.461,1.057],[1.031,0.931],[0.331,0.976],[0.813,1.356],[-0.072,1.004],[0.361,1.538],[-0.036,1.565],[-0.22,0.357],[1.478,-0.518],[0.915,0.329],[0.097,0.969],[0.296,0.261],[-0.161,0.404],[1.886,0.175],[0.258,-0.074],[0.06,2.463],[-0.192,0.302],[1.143,0.752],[1.403,-1.402],[0.587,2.19],[-0.919,1.55],[0.227,1.307],[0.26,0.782],[0.522,0.297],[0.25,0.346],[1.866,0.474],[1.159,-0.821],[0.963,0.413],[0.822,1.897],[-0.077,0.341],[0.277,0.827],[0.013,1.454],[-0.265,2.72],[-0.223,0.714],[0.473,-0.073],[-0.447,0.856],[0.173,0.385],[0.358,0.561],[1.083,1.054],[0.515,0.514],[0.515,3.853],[0.571,1.136],[0.954,0.651],[-0.315,0.759],[2.315,-1.867],[-0.472,1.089],[2.144,0.634],[-0.67,0.852],[0.505,0.866],[1.765,0.405],[2.122,1.032],[1.267,-0.708],[0.444,0.775],[1.632,-1.43],[1.441,-1.436],[1.163,0.01],[0.114,0.662],[1.586,-1.238],[2.133,-1.952],[-0.152,0.588],[-0.589,0.85],[0.357,-0.693],[0.9,0.307],[0.813,0.63],[0.747,0.61],[-0.021,0.117],[1.278,-0.592],[0.547,-0.202],[0.825,0.814],[1.305,0.021],[0.682,-0.31],[0.097,0.595],[-0.818,2.092],[1.158,0.792],[0.624,0.628],[0.975,-0.147],[0.311,1.475],[0.858,-0.16],[0.519,1.533],[-0.109,0.402],[0.69,-0.401],[0.224,0.013],[0.025,0.498],[1.451,0.959],[2.153,-0.767],[0.872,0.183],[2.19,-0.808],[0.342,1.876],[0.95,-0.577],[-1.32,1.831],[1.179,-0.469],[-0.115,1.238],[1.466,-0.226],[0.209,0.074]]
    },
    {
      "name": "Decision boundary",
      "type": "line",
      "smooth": false,
      "symbol": "none",
      "lineStyle": {"color": "#e94560", "width": 2.5, "type": "dashed"},
      "data": [[-3.5, 3.776], [2.5, -2.692]],
      "markArea": {
        "silent": true,
        "itemStyle": {"color": "rgba(233,69,96,0.06)"},
        "data": [[{"xAxis": -3.5, "yAxis": 3.776}, {"xAxis": 2.5, "yAxis": 4.5}]]
      }
    }
  ]
}
</code></pre> <p>The model got the forward pass, backward pass, and weight update all correct on the second try.</p> <h2 id="what-i-learned-about-small-models">What I Learned About Small Models</h2> <p>Building this taught me several things about what 7B-4bit models can and cannot do.</p> <p><strong>They are good at:</strong></p> <ul> <li>Writing complete, self-contained scripts from detailed specifications</li> <li>Fixing bugs when given the exact traceback</li> <li>Restructuring code between attempts (not just patching the broken line)</li> <li>Following formatting constraints (<code class="language-plaintext highlighter-rouge">$12,345.67</code> comma separators, grid alignment)</li> </ul> <p><strong>They struggle with:</strong></p> <ul> <li>Consistent tool calling (they prefer to output markdown)</li> <li>Very long generations without truncation (~60 lines seems to be the comfort zone)</li> <li>Multi-layer backpropagation with correct tensor shapes (I tried a 2-layer neural net and it failed all 10 attempts across two runs on the chain rule math)</li> <li>Not mixing code with natural language explanations in the same output</li> </ul> <p><strong>Design implications:</strong></p> <ul> <li>Extract code from whatever format the model produces. Do not depend on tool calling.</li> <li>Keep tasks scoped so the solution fits in ~50-80 lines.</li> <li>Give explicit formulas for math-heavy tasks (the model can implement given formulas but struggles to derive them).</li> <li>Use the orchestrator for mechanical tasks (execution, file I/O) and the LLM for judgment tasks (generation, analysis).</li> </ul> <h2 id="the-cost-equation">The Cost Equation</h2> <pre><code class="language-echarts">{
  "responsive": true,
  "tooltip": {"trigger": "axis"},
  "xAxis": {
    "type": "category",
    "data": ["GPT-4 API", "Claude API", "Local MLX"],
    "axisLabel": {"fontSize": 13}
  },
  "yAxis": {
    "type": "value",
    "name": "USD",
    "max": 0.45,
    "nameTextStyle": {"fontSize": 13}
  },
  "series": [
    {
      "type": "bar",
      "data": [
        {"value": 0.30, "itemStyle": {"color": "#636EFA"}},
        {"value": 0.15, "itemStyle": {"color": "#EF553B"}},
        {"value": 0.00, "itemStyle": {"color": "#00CC96"}}
      ],
      "barWidth": "50%",
      "label": {
        "show": true,
        "position": "top",
        "formatter": ["~$0.30", "~$0.15", "$0.00"],
        "fontSize": 13,
        "fontWeight": "bold"
      }
    }
  ]
}
</code></pre> <p>The individual run is cheap on any platform. But the cost model changes when agents get stuck. A 10-iteration retry loop that would cost $1-3 on cloud APIs costs nothing locally. You can prototype, iterate, and experiment without watching a billing dashboard. For learning and development, that changes the economics entirely.</p> <p>The tradeoff is capability. A 7B-4bit model is not GPT-4. It cannot handle the same task complexity or recover from the same depth of errors. But for well-scoped tasks with clear specifications, it gets there. And it does it in under 4 minutes, fully offline, at zero cost.</p> <h2 id="try-it-yourself">Try It Yourself</h2> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Clone and install</span>
git clone https://github.com/thenamangoyal/multi-agent-mlx.git
<span class="nb">cd </span>multi-agent-mlx
uv <span class="nb">sync</span>

<span class="c"># Run a task (auto-starts the MLX server, ~4 GB RAM)</span>
uv run factory run <span class="s2">"Write a Python script that prints the first 20 prime numbers"</span>

<span class="c"># Or run all three showcase scenarios</span>
uv run python scenarios/run_all.py

<span class="c"># Stop the server when done to free RAM</span>
pkill <span class="nt">-f</span> <span class="s2">"mlx_lm.server"</span>
</code></pre></div></div> <p>Requirements: macOS on Apple Silicon, Python 3.12+, <a href="https://github.com/astral-sh/uv">uv</a>. The model downloads automatically on first run (~4 GB). The whole system fits comfortably in 10 GB of RAM.</p> <pre><code class="language-echarts">{
  "responsive": true,
  "title": {"text": "Scenario Breakdown", "left": "center"},
  "tooltip": {"trigger": "item"},
  "legend": {"bottom": "0%", "data": ["Time (s)", "Attempts", "Lines of Code"]},
  "radar": {
    "indicator": [
      {"name": "Time (s)", "max": 180},
      {"name": "Attempts", "max": 5},
      {"name": "Code Lines", "max": 80},
      {"name": "Task Complexity", "max": 5}
    ]
  },
  "series": [
    {
      "type": "radar",
      "data": [
        {
          "value": [22, 1, 47, 2],
          "name": "Calendar",
          "areaStyle": {"opacity": 0.2},
          "lineStyle": {"color": "#00CC96"},
          "itemStyle": {"color": "#00CC96"}
        },
        {
          "value": [159, 3, 53, 4],
          "name": "CSV Detective",
          "areaStyle": {"opacity": 0.2},
          "lineStyle": {"color": "#636EFA"},
          "itemStyle": {"color": "#636EFA"}
        },
        {
          "value": [50, 2, 54, 3],
          "name": "Neural Net",
          "areaStyle": {"opacity": 0.2},
          "lineStyle": {"color": "#EF553B"},
          "itemStyle": {"color": "#EF553B"}
        }
      ]
    }
  ]
}
</code></pre> <h2 id="final-thoughts">Final Thoughts</h2> <p>The most surprising thing about this project is not that it works. It is that the failure modes are interesting. When the Coder produces broken code, the Sheriff does not just say “it’s broken.” It identifies the error type, points to the exact line, and suggests a specific fix. And the Coder, reading that report, does not just patch the one line. It often rewrites the entire approach. That back-and-forth, mediated by a simple Python loop instead of a complex multi-agent framework, produces working code reliably enough to be useful.</p> <p>The obvious next step is scaling up. A 14B or 32B model on a 64 GB Mac would handle the 2-layer backprop task that stumped the 7B model. Multiple tool calls would probably start working reliably. The architecture stays the same; you just swap the model ID. That is the beauty of building on top of <code class="language-plaintext highlighter-rouge">mlx_lm.server</code>: the agents do not know or care what model is behind the endpoint.</p> <p>For now, the 7B model on 16 GB is the sweet spot for prototyping. It is fast enough to iterate (22 seconds for simple tasks), cheap enough to experiment freely ($0.00), and private enough to use on any codebase. Not bad for two agents arguing on a laptop.</p>]]></content><author><name>Naman Goyal</name></author><category term="research"/><category term="MLX,"/><category term="Apple"/><category term="Silicon,"/><category term="agents,"/><category term="multi-agent,"/><category term="local"/><category term="LLM"/><summary type="html"><![CDATA[Building a self-correcting code factory where two local LLM agents write, test, and debug Python scripts entirely on Apple Silicon. No cloud, no cost, no data leaving your machine.]]></summary></entry><entry><title type="html">Your MacBook Can Do Autonomous AI Research Now</title><link href="https://namangoyal.com/blog/2026/autoresearch-mlx/" rel="alternate" type="text/html" title="Your MacBook Can Do Autonomous AI Research Now"/><published>2026-03-10T10:00:00+00:00</published><updated>2026-03-10T10:00:00+00:00</updated><id>https://namangoyal.com/blog/2026/autoresearch-mlx</id><content type="html" xml:base="https://namangoyal.com/blog/2026/autoresearch-mlx/"><![CDATA[<p>What if your MacBook could run autonomous AI research while you sleep?</p> <p>That is exactly what <a href="https://github.com/karpathy/autoresearch">autoresearch</a> does. Andrej Karpathy built a system where an AI agent modifies a training script, trains a small language model for 5 minutes, checks if the result improved, and loops. You wake up to a log of experiments and (hopefully) a better model. The concept is wild, the code is real, and the results are legit. The catch? It was built for H100 GPUs.</p> <p>I wanted to run it on my MacBook. So I ported the whole thing to Apple Silicon using <a href="https://github.com/ml-explore/mlx">MLX</a>, Apple’s machine learning framework. No CUDA, no cloud GPU, no $3/hour rental fees. Just your Mac. The code is at <a href="https://github.com/thenamangoyal/autoresearch">github.com/thenamangoyal/autoresearch</a> <a class="citation" href="#goyal2026your-macbook-can-do-autonomous-ai-research-now">(Goyal, 2026)</a>.</p> <p>Does it actually work? Here is the short version. An untrained model starts with a training loss of <strong>9.01</strong>, which is essentially random noise over an 8,192-token vocabulary (log₂(8192) ≈ 13, but BPE tokens encode multiple bytes, so the effective starting point is lower). After just 5 minutes and 55 optimizer steps on an M1 Pro with 16GB, the loss drops to <strong>6.76</strong> and the validation score lands at <strong>2.371 BPB</strong>. The model is clearly learning language structure. Give it more time and the numbers keep dropping: the community MLX fork on an M4 Max reports <strong>1.808 BPB</strong> after a single run, and <strong>1.295 BPB</strong> after letting the autonomous loop iterate overnight. On the original H100, Karpathy’s CUDA code fits ~11,500 steps in the same 5-minute window and reaches roughly <strong>1.0 BPB</strong>. The model and code are the same. The gap is 96x less compute on the Mac.</p> <h2 id="results-at-a-glance">Results at a Glance</h2> <p>Here is what a single 5-minute run looks like on my M1 Pro 16GB:</p> <table> <thead> <tr> <th>Metric</th> <th style="text-align: right">Value</th> </tr> </thead> <tbody> <tr> <td>Starting train loss</td> <td style="text-align: right">9.012</td> </tr> <tr> <td>Final train loss</td> <td style="text-align: right">6.762</td> </tr> <tr> <td>val_bpb</td> <td style="text-align: right">2.371</td> </tr> <tr> <td>Steps</td> <td style="text-align: right">55</td> </tr> <tr> <td>Tokens Processed</td> <td style="text-align: right">3.6M</td> </tr> <tr> <td>Peak Memory</td> <td style="text-align: right">11.0 GB</td> </tr> <tr> <td>Parameters</td> <td style="text-align: right">11.5M</td> </tr> </tbody> </table> <p>The obvious question: how does this compare to the H100 the system was designed for?</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": ["Tok/sec", "Steps in 5 min"],
      "y": [2500000, 11500],
      "name": "H100 (CUDA)",
      "type": "bar",
      "marker": {"color": "#636EFA"}
    },
    {
      "x": ["Tok/sec", "Steps in 5 min"],
      "y": [26000, 55],
      "name": "M1 Pro 16GB (MLX)",
      "type": "bar",
      "marker": {"color": "#EF553B"}
    }
  ],
  "layout": {
    "title": {"text": "H100 vs M1 Pro: Raw Throughput"},
    "yaxis": {"title": "Value", "type": "log"},
    "barmode": "group",
    "legend": {"yanchor": "top", "y": 0.99, "xanchor": "right", "x": 0.99}
  }
}
</code></pre> <p>Yes, the H100 is roughly 96x faster. That is not the point. The point is that you can run this at all on a laptop, iterate on architecture ideas, and prototype training experiments before ever touching a cloud GPU. For learning and experimentation, the Mac is more than enough.</p> <p>The <code class="language-plaintext highlighter-rouge">--time-budget</code> flag lets you scale up when you have time to spare:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [3.6, 23, 47, 94],
      "y": ["5 min", "15 min", "30 min", "60 min"],
      "type": "bar",
      "orientation": "h",
      "marker": {"color": ["#636EFA", "#EF553B", "#00CC96", "#AB63FA"]},
      "text": ["~55 steps", "~357 steps", "~714 steps", "~1429 steps"],
      "textposition": "auto"
    }
  ],
  "layout": {
    "title": {"text": "Tokens Processed by Time Budget (M1 Pro)"},
    "xaxis": {"title": "Millions of Tokens"},
    "yaxis": {"title": ""},
    "showlegend": false,
    "margin": {"l": 80}
  }
}
</code></pre> <p>In my experience, 15 to 30 minutes hits the sweet spot on Apple Silicon. You get enough steps for the optimizer to meaningfully converge, without burning hours waiting.</p> <h2 id="the-autonomous-research-loop">The Autonomous Research Loop</h2> <p>The genius of autoresearch is how simple the loop is. An AI agent (Claude, GPT, whatever you prefer) reads the instructions in <code class="language-plaintext highlighter-rouge">program.md</code>, tweaks the model code in <code class="language-plaintext highlighter-rouge">train.py</code>, commits the change, trains for exactly 5 minutes, checks BPB, and either keeps the commit or reverts. Then it does it again. All night if you let it.</p> <pre><code class="language-mermaid">flowchart TD
    A[Read program.md] --&gt; B[Modify train.py]
    B --&gt; C[Git commit]
    C --&gt; D[Train for 5 min]
    D --&gt; E[Read val_bpb]
    E --&gt; F{Improved?}
    F -- Yes --&gt; G[Keep commit]
    F -- No --&gt; H[Git revert]
    G --&gt; I[Log to results.tsv]
    H --&gt; I
    I --&gt; B
</code></pre> <p>The design is intentionally constrained. One file to edit. One metric to optimize. A fixed time budget so every experiment is directly comparable regardless of what the agent changes: model size, architecture, optimizer, batch size, all of it is fair game inside <code class="language-plaintext highlighter-rouge">train.py</code>. The agent’s creativity is the only variable.</p> <p>As Karpathy put it in the original repo:</p> <blockquote> <p>One day, frontier AI research used to be done by meat computers in between eating, sleeping, having other fun, and synchronizing once in a while using sound wave interconnect in the ritual of “group meeting”. That era is long gone.</p> </blockquote> <p>On the Mac, each experiment takes about 5 minutes, which gives you roughly 12 experiments per hour and close to 100 if you let it run overnight. Not quite datacenter speed, but enough to explore a surprising number of architectural ideas.</p> <h2 id="model-architecture">Model Architecture</h2> <p>The model is a GPT variant <a class="citation" href="#vaswani2017attention">(Vaswani et al., 2017)</a> with several modern tricks packed into a surprisingly compact 11.5M parameter design. I found it interesting how many recent ideas Karpathy managed to squeeze into a single training file. Here is the high-level flow:</p> <pre><code class="language-mermaid">flowchart TD
    T[Token IDs] --&gt; WTE[Token Embedding]
    T --&gt; VE[Value Embedding]
    WTE --&gt; RS[Residual Scaling]
    RS --&gt; N1[RMSNorm]
    N1 --&gt; ATT[Self-Attention + RoPE]
    VE --&gt; ATT
    ATT --&gt; N2[RMSNorm]
    N2 --&gt; MLP[MLP · Squared ReLU]
    MLP --&gt;|repeat x DEPTH| RS
    MLP --&gt; NF[RMSNorm]
    NF --&gt; LM[lm_head]
    LM --&gt; SC[Softcap · tanh]
</code></pre> <p>Let me walk through what makes this architecture interesting.</p> <p><strong>Value Embeddings (ResFormer).</strong> This one surprised me. Every other layer gets its own value embedding table. Instead of computing values purely from the hidden state, the model mixes in a direct embedding lookup gated by a small linear projection. It is the ResFormer trick: a residual connection through the value path that helps gradient flow in deep models. In a 4-layer model it might seem unnecessary, but it lets the agent experiment with deeper configurations without running into vanishing gradient issues.</p> <p><strong>Sliding Window Attention (SSSL).</strong> The window pattern <code class="language-plaintext highlighter-rouge">SSSL</code> means three layers of short-range (half context length) attention followed by one layer of long-range (full context) attention. On the H100, this is handled by Flash Attention <a class="citation" href="#dao2022flashattention">(Dao et al., 2022)</a>. On MLX, I implemented it with additive masks instead. It is slower, but functionally equivalent. The last layer always gets full attention regardless of the pattern, so the model can always attend to the full 2,048-token context when it matters.</p> <p><strong>RoPE with QK-Norm.</strong> Queries and keys get rotary position embeddings <a class="citation" href="#su2021roformer">(Su et al., 2021)</a> followed by RMS normalization. This combination stabilizes training and eliminates the need for learned position embeddings. It is one of those small details that just works.</p> <p><strong>Softcap at 15.0.</strong> The logits pass through <code class="language-plaintext highlighter-rouge">15 * tanh(logits / 15)</code> before the loss computation. This bounds logit magnitudes and prevents the model from becoming overconfident. The technique comes from Gemma 2 <a class="citation" href="#team2024gemma2">(Gemma Team et al., 2024)</a> and is especially helpful early in training when the model might otherwise push logits to extreme values on frequent tokens.</p> <p><strong>Squared ReLU.</strong> The MLP activation is <code class="language-plaintext highlighter-rouge">relu(x)²</code> instead of GELU or SiLU. Simpler, faster, and produces sparser activations. In a small model where every FLOP counts, this is a sensible choice.</p> <p><strong>Per-Layer Residual Scaling.</strong> Each layer has two learnable scalars that control how much the running hidden state and the original embedding contribute. The core of the forward pass looks like this:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">resid_lambdas</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">x</span> <span class="o">+</span> <span class="n">self</span><span class="p">.</span><span class="n">x0_lambdas</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">x0</span>
<span class="n">ve</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">value_embeds</span><span class="p">[</span><span class="nf">str</span><span class="p">(</span><span class="n">i</span><span class="p">)](</span><span class="n">idx</span><span class="p">)</span> <span class="k">if</span> <span class="nf">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">value_embeds</span> <span class="k">else</span> <span class="bp">None</span>
<span class="n">x</span> <span class="o">=</span> <span class="nf">block</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">ve</span><span class="p">,</span> <span class="n">masks</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
</code></pre></div></div> <p>This gives the model fine-grained control over each layer’s contribution to the residual stream. In practice, I noticed the model learns to progressively reduce <code class="language-plaintext highlighter-rouge">x0_lambda</code> in later layers, relying more on the transformed representations as depth increases.</p> <h2 id="optimizer-adamw-with-six-parameter-groups">Optimizer: AdamW with Six Parameter Groups</h2> <p>This is not your typical “one learning rate for everything” AdamW <a class="citation" href="#loshchilov2017adamw">(Loshchilov &amp; Hutter, 2017)</a>. The optimizer splits the model into six groups, each with its own learning rate, betas, and weight decay. Getting this right turned out to be one of the trickier parts of the port.</p> <table> <thead> <tr> <th>Group</th> <th style="text-align: right">LR</th> <th>Betas</th> <th style="text-align: center">Weight Decay</th> <th>Scaling</th> </tr> </thead> <tbody> <tr> <td>Embeddings (wte)</td> <td style="text-align: right">0.6</td> <td>(0.8, 0.95)</td> <td style="text-align: center">0.0</td> <td>1/sqrt(d/768)</td> </tr> <tr> <td>Value Embeddings</td> <td style="text-align: right">0.6</td> <td>(0.8, 0.95)</td> <td style="text-align: center">0.0</td> <td>1/sqrt(d/768)</td> </tr> <tr> <td>Unembedding (lm_head)</td> <td style="text-align: right">0.004</td> <td>(0.8, 0.95)</td> <td style="text-align: center">0.0</td> <td>1/sqrt(d/768)</td> </tr> <tr> <td>Transformer Matrices</td> <td style="text-align: right">0.04</td> <td>(0.8, 0.95)</td> <td style="text-align: center">0.2</td> <td>None</td> </tr> <tr> <td>resid_lambdas</td> <td style="text-align: right">0.005</td> <td>(0.8, 0.95)</td> <td style="text-align: center">0.0</td> <td>None</td> </tr> <tr> <td>x0_lambdas</td> <td style="text-align: right">0.5</td> <td>(0.96, 0.95)</td> <td style="text-align: center">0.0</td> <td>None</td> </tr> </tbody> </table> <p>A few things stand out. Embedding learning rates are 150x higher than unembedding rates, because embeddings need to move fast early in training while the output projection benefits from stability. Only the transformer matrices get weight decay. The <code class="language-plaintext highlighter-rouge">x0_lambdas</code> use a beta1 of 0.96 (vs 0.8 for everything else), giving them more momentum so they adjust slowly and smoothly. And embedding/unembedding LRs scale with <code class="language-plaintext highlighter-rouge">1/sqrt(model_dim/768)</code>, following the dimension-scaled convention from recent scaling law work.</p> <p><strong>Weight decay schedule.</strong> One improvement I made over the <a href="https://github.com/trevin-creator/autoresearch-mlx">reference MLX fork</a> is a linear weight decay schedule. The effective weight decay is <code class="language-plaintext highlighter-rouge">WEIGHT_DECAY * (1 - progress)</code>, which decays to zero by the end of training. The intuition is simple: during the warmdown phase when the learning rate is already near zero, weight decay just pushes weights toward zero without the optimizer being able to pull them back. The upstream CUDA version does this, but the reference fork missed it. In my tests it typically yields 2 to 5% BPB improvement, which is significant for free.</p> <p><strong>LR schedule.</strong> No warmup at all (WARMUP_RATIO = 0.0). The second half of training is spent in linear warmdown to zero. This aggressive schedule makes sense for a 5-minute budget where you cannot afford to spend steps warming up.</p> <h2 id="data-pipeline">Data Pipeline</h2> <p>The training data comes from <strong>ClimbMix 400B</strong>, a curated web-scale dataset downloaded as parquet shards into <code class="language-plaintext highlighter-rouge">~/.cache/autoresearch/</code>. Running <code class="language-plaintext highlighter-rouge">prepare.py</code> once downloads the data and trains a BPE tokenizer with a vocabulary of 8,192 tokens. The whole process takes about 2 minutes.</p> <p>The dataloader uses <strong>BOS-aligned best-fit packing</strong>: documents are packed into sequences of 2,048 tokens, each starting with a BOS token. Documents that do not fit are split across sequences, but every sequence begins at a document boundary. This achieves close to 100% token utilization with no padding waste, which matters when you only have 3.6M tokens to work with in a 5-minute budget.</p> <p>On Apple Silicon, the big win is <strong>unified memory</strong>. There is no CPU-to-GPU transfer. The data, model weights, optimizer states, and gradients all live in the same memory pool. On a CUDA setup with a consumer GPU, PCIe bandwidth can bottleneck the dataloader. On a Mac, that entire category of performance issues just does not exist. The tradeoff is that you are sharing that same memory pool with macOS, your browser, and everything else running on the machine, which is exactly why the 16GB M1 Pro hits memory pressure during training.</p> <p><strong>Why BPB instead of perplexity?</strong> The evaluation metric is <strong>bits per byte (BPB)</strong>, computed as <code class="language-plaintext highlighter-rouge">cross_entropy_loss * (num_tokens / num_bytes)</code>. This normalizes for tokenizer vocabulary size, so you can swap the tokenizer or change the vocab size and still get comparable numbers. It is the right metric for autoresearch, where the agent might decide to change the tokenization strategy entirely. Lower is better.</p> <h2 id="training-in-action">Training in Action</h2> <p>Here is real data from my 5-minute benchmark. Every data point is from the actual run, nothing simulated:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54],
      "y": [9.012,9.012,8.688,8.426,8.216,8.060,7.960,7.882,7.812,7.757,7.705,7.670,7.631,7.604,7.576,7.553,7.527,7.511,7.492,7.464,7.444,7.414,7.396,7.379,7.355,7.334,7.309,7.291,7.274,7.244,7.226,7.207,7.192,7.168,7.145,7.119,7.094,7.071,7.048,7.022,7.000,6.984,6.965,6.948,6.933,6.913,6.894,6.877,6.856,6.834,6.817,6.801,6.787,6.770,6.762],
      "type": "scatter",
      "mode": "lines+markers",
      "name": "Training Loss (EMA)",
      "line": {"color": "#636EFA", "width": 2},
      "marker": {"size": 4}
    }
  ],
  "layout": {
    "title": {"text": "Training Loss Curve (M1 Pro 16GB, 5-min budget)"},
    "xaxis": {"title": "Step"},
    "yaxis": {"title": "Smoothed Training Loss"},
    "showlegend": false
  }
}
</code></pre> <p>The loss drops steadily from 9.01 to 6.76 over 55 steps. No spikes, no NaN incidents, no instability. That smooth curve is exactly what you want to see from a well-configured optimizer on a small model.</p> <p>But the throughput chart tells a more interesting story:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54],
      "y": [7604,24127,22891,22148,18671,14971,22997,23022,23742,17105,16300,20709,25035,22540,18241,11142,20179,17631,16099,18564,17911,17994,14105,1742,2628,5754,5672,3634,7285,2245,5507,6574,16197,29251,29009,29612,29569,28550,25040,28735,28859,28095,27022,25195,20166,24015,24746,14838,18569,25240,26747,26207,29411,28793,28899],
      "type": "scatter",
      "mode": "lines+markers",
      "name": "Tok/sec",
      "line": {"color": "#EF553B", "width": 2},
      "marker": {"size": 4}
    }
  ],
  "layout": {
    "title": {"text": "Throughput Over Training (M1 Pro 16GB)"},
    "xaxis": {"title": "Step"},
    "yaxis": {"title": "Tokens per Second"},
    "showlegend": false,
    "annotations": [
      {
        "x": 23, "y": 1742,
        "text": "Memory pressure",
        "showarrow": true,
        "arrowhead": 2,
        "ax": 40, "ay": -40,
        "font": {"size": 11, "color": "#EF553B"}
      },
      {
        "x": 29, "y": 2245,
        "text": "Swap thrashing",
        "showarrow": true,
        "arrowhead": 2,
        "ax": 40, "ay": -40,
        "font": {"size": 11, "color": "#EF553B"}
      }
    ]
  }
}
</code></pre> <p>See those dramatic dips around steps 23 to 30? That is macOS memory pressure in action. With the model weights, optimizer states (two momentum buffers per parameter group), gradient accumulators, and attention masks all resident in unified memory, the 16GB M1 Pro is right at the edge. When macOS starts compressing and swapping pages, throughput drops from ~26K tok/sec to under 2K. Each of those slow steps takes 15 to 37 seconds instead of the usual 2.5 seconds. The training still converges correctly (the loss curve above is smooth right through those dips), but you lose precious steps from your 5-minute budget. On an M4 Max with 36 or 64GB, these dips disappear entirely and you get consistent 29K+ tok/sec throughout.</p> <h2 id="improvements-over-the-reference-fork">Improvements Over the Reference Fork</h2> <p>When I started this port, <a href="https://github.com/trevin-creator/autoresearch-mlx">trevin-creator/autoresearch-mlx</a> already existed as an early community MLX adaptation. I used it as a reference but ended up rewriting most of the training loop to fix several issues I ran into:</p> <table> <thead> <tr> <th>Improvement</th> <th>Impact</th> <th>Reference Behavior</th> </tr> </thead> <tbody> <tr> <td>NaN loss detection</td> <td>Prevents silent training corruption</td> <td>Only checks <code class="language-plaintext highlighter-rouge">loss &gt; 100</code>; NaN passes silently</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">FINAL_EVAL_BATCH_SIZE=16</code></td> <td>Enables 16GB Macs to complete eval</td> <td>Uses 256, OOMs on 16GB machines</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">if __name__ == "__main__"</code> guard</td> <td>Enables agent import workflow</td> <td>Runs training at import time</td> </tr> <tr> <td>Weight decay schedule</td> <td>~2-5% BPB improvement</td> <td>Constant weight decay throughout</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">estimate_flops()</code></td> <td>Per-token FLOP estimation with window sizes</td> <td>Not present</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">num_scaling_params()</code></td> <td>Detailed parameter breakdown by category</td> <td>Flat parameter count only</td> </tr> <tr> <td>MFU calculation</td> <td>Configurable via <code class="language-plaintext highlighter-rouge">PEAK_FLOPS_TFLOPS</code> env var</td> <td>Hardcoded 0.0 placeholder</td> </tr> <tr> <td>Config logging</td> <td>Full <code class="language-plaintext highlighter-rouge">GPTConfig</code> printed via <code class="language-plaintext highlighter-rouge">asdict()</code></td> <td>No config output</td> </tr> <tr> <td>Phase timing</td> <td>Separate training/eval timing logs</td> <td>Partial</td> </tr> </tbody> </table> <p>The eval batch size issue was the most frustrating to debug. The reference fork uses <code class="language-plaintext highlighter-rouge">FINAL_EVAL_BATCH_SIZE=256</code>, which works fine on an M4 Max with 27GB available. But on a 16GB machine, the evaluation step triggers an out-of-memory crash <em>after</em> the 5-minute training run completes successfully. You sit through the whole training, only for the eval to kill the process and lose everything. Reducing it to 16 eliminates this entirely.</p> <p>The <code class="language-plaintext highlighter-rouge">__main__</code> guard is subtle but important for the autonomous agent workflow. Without it, the agent’s <code class="language-plaintext highlighter-rouge">import train</code> (which it uses to inspect the module) silently kicks off a full training run. That is 5 minutes wasted before the agent even starts its actual experiment.</p> <p>The reference fork reports a best result of <strong>1.295 BPB</strong> on M4 Max and <strong>1.353 BPB</strong> on Mac Mini after extended autonomous runs. My single 5-minute baseline on M1 Pro 16GB reaches <strong>2.371 BPB</strong>. The gap is hardware, not code: fewer steps due to memory pressure and a slower processor. The raw per-step training throughput is identical between the two implementations since both use the same MLX ops. On equivalent hardware with the weight decay fix and other improvements, I would expect this port to match or beat the reference fork’s numbers.</p> <h2 id="try-it-yourself">Try It Yourself</h2> <p>Four commands and you are training:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Install uv (if you don't have it)</span>
curl <span class="nt">-LsSf</span> https://astral.sh/uv/install.sh | sh

<span class="c"># 2. Install dependencies</span>
uv <span class="nb">sync</span>

<span class="c"># 3. Download data and train tokenizer (one-time, ~2 min)</span>
uv run prepare.py

<span class="c"># 4. Run a training experiment</span>
uv run train.py
</code></pre></div></div> <p>Want a longer run? Use the <code class="language-plaintext highlighter-rouge">--time-budget</code> flag:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uv run train.py <span class="nt">--time-budget</span> 600   <span class="c"># 10 minutes</span>
uv run train.py <span class="nt">--time-budget</span> 1800  <span class="c"># 30 minutes</span>
uv run train.py <span class="nt">--time-budget</span> 3600  <span class="c"># 1 hour</span>
</code></pre></div></div> <p>To run the full autonomous loop, point an AI agent (Claude Code, Codex, or similar) at <code class="language-plaintext highlighter-rouge">program.md</code> in the repo:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hi, have a look at program.md and let's kick off a new experiment!
Let's do the setup first.
</code></pre></div></div> <p>The agent will create a branch, establish a baseline BPB, and start iterating. Make sure to disable all permission prompts so it can run unattended. On an M1 Pro you will get about 12 experiments per hour. Let it run overnight and check the git log in the morning.</p> <p><strong>Requirements:</strong> Apple M-series Mac (M1/M2/M3/M4), Python 3.10+, <a href="https://docs.astral.sh/uv/">uv</a>.</p> <p>The code is at <a href="https://github.com/thenamangoyal/autoresearch">github.com/thenamangoyal/autoresearch</a>.</p> <h2 id="conclusion">Conclusion</h2> <p>You do not need an H100 to experiment with autonomous AI research. A MacBook Pro can train an 11.5M parameter language model on real web data, with a proper six-group AdamW optimizer, modern architecture tricks like RoPE and value embeddings, and a clean autonomous iteration loop. Yes, it is 96x slower than a datacenter GPU. But it costs nothing, it sits on your desk, and it works.</p> <p>There is plenty of room to push this further. The Muon optimizer from the upstream CUDA version is not yet ported (it needs SVD operations that require careful MLX adaptation). M4 Max and M4 Ultra machines with 64GB+ of unified memory should eliminate the throughput dips and unlock much longer effective training runs. And the autonomous loop itself could be made smarter: better search strategies, multi-objective optimization, cross-experiment learning from the full results history.</p> <p>For now, the foundation works. Clone the repo, run <code class="language-plaintext highlighter-rouge">prepare.py</code>, and let your MacBook do some research overnight. You might be surprised by what it finds.</p> <p>Thanks to <a href="https://github.com/karpathy/autoresearch">Andrej Karpathy</a> for the original autoresearch concept and to the <a href="https://github.com/trevin-creator/autoresearch-mlx">trevin-creator</a> community fork for the initial MLX exploration.</p>]]></content><author><name>Naman Goyal</name></author><category term="research"/><category term="MLX,"/><category term="Apple"/><category term="Silicon,"/><category term="LLM,"/><category term="pretraining,"/><category term="autoresearch"/><summary type="html"><![CDATA[Bringing Karpathy's autoresearch to Apple Silicon with MLX. Architecture deep dive, real benchmarks, and a guide to running autonomous AI experiments on your Mac.]]></summary></entry><entry><title type="html">Bridging the Divide - A Mac User Guide to Productivity with Android</title><link href="https://namangoyal.com/blog/2025/mac-android/" rel="alternate" type="text/html" title="Bridging the Divide - A Mac User Guide to Productivity with Android"/><published>2025-04-20T11:00:00+00:00</published><updated>2025-04-20T11:00:00+00:00</updated><id>https://namangoyal.com/blog/2025/mac-android</id><content type="html" xml:base="https://namangoyal.com/blog/2025/mac-android/"><![CDATA[<p>So, you’ve done it. You’ve taken the plunge from the familiar shores of iOS to the expansive world of Android, grabbing a Pixel as your primary device. But wait, your trusty Mac and iPad aren’t going anywhere, and maybe that iPhone is sticking around as a secondary device. Welcome to the mixed-ecosystem club!</p> <p>As a long-time resident of the Apple walled garden, you know the magic: Apple Books syncs your reading progress effortlessly, Notes appear instantly across devices, Reminders pop up reliably, Keychain handles passwords seamlessly, Photos are just <em>there</em>, iMessage keeps you connected, AirDrop zips files around, and Find My keeps track of your gear. It’s a beautifully integrated system… as long as you stay within its walls.</p> <p>Stepping outside with an Android phone means rethinking how these essential services work together. The good news? It’s entirely possible to maintain, and even enhance, your productivity with the right cross-platform tools. The goal is to find replacements that work reliably on macOS, Android, iOS, and iPadOS.</p> <h1 id="the-challenge-replacing-seamless-integration">The Challenge Replacing Seamless Integration</h1> <p>Let’s break down the core Apple services and find robust, cross-platform alternatives that play nicely with your <strong>Mac</strong>, new <strong>Android</strong> phone, <strong>iPad</strong>, and secondary <strong>iPhone</strong>.</p> <h1 id="tldr-quick-reference">TLDR Quick Reference</h1> <p>Here’s a quick look at the recommended replacements for common Apple ecosystem services:</p> <table> <thead> <tr> <th style="text-align: left">Apple Service</th> <th style="text-align: left">Recommended Replacement(s)</th> <th style="text-align: left">Key Features / Why it Works</th> </tr> </thead> <tbody> <tr> <td style="text-align: left"><strong>Apple Books</strong></td> <td style="text-align: left"><strong>Kindle App + Send to Kindle</strong></td> <td style="text-align: left">Syncs reading progress/bookmarks, vast library, ‘Send to Kindle’ for personal docs (Mac/Web).</td> </tr> <tr> <td style="text-align: left"><strong>Apple Notes</strong></td> <td style="text-align: left"><strong>Microsoft OneNote</strong> + <strong>Google Keep</strong></td> <td style="text-align: left">OneNote: Robust organization, attachments (PDFs!), Pencil support. Keep: Quick notes, lists.</td> </tr> <tr> <td style="text-align: left"><strong>Apple Calendar</strong></td> <td style="text-align: left"><strong>Google Calendar</strong> (via Google Account)</td> <td style="text-align: left">Use native Apple Calendar on Mac (synced to Google), Google Calendar app elsewhere.</td> </tr> <tr> <td style="text-align: left"><strong>Apple Reminders</strong></td> <td style="text-align: left"><strong>Google Tasks</strong> (via Google Calendar)</td> <td style="text-align: left">Integrates with Google Calendar, native apps (Android/iOS), web access (Mac).</td> </tr> <tr> <td style="text-align: left"><strong>AirDrop</strong></td> <td style="text-align: left"><strong>NearDrop</strong> (Mac) / <strong>Quick Share</strong> (Android)</td> <td style="text-align: left">Wireless file transfer between Mac &amp; Android over Wi-Fi (requires manual approval).</td> </tr> <tr> <td style="text-align: left"><strong>Find My (AirTag)</strong></td> <td style="text-align: left"><strong>Chipolo Pop</strong> (Google Network)</td> <td style="text-align: left">Works with Google Find My Device network, can be switched to Apple Find My (not simultaneously).</td> </tr> <tr> <td style="text-align: left"><strong>Apple Keychain</strong></td> <td style="text-align: left"><strong>Google Chrome Password Manager</strong> + <strong>Authy/Google Authenticator</strong></td> <td style="text-align: left">Chrome: Handles passwords &amp; passkeys across devices. Authy/GA: Cross-platform 2FA codes.</td> </tr> <tr> <td style="text-align: left"><strong>iMessage</strong></td> <td style="text-align: left"><strong>Google Messages (RCS)</strong> / <strong>WhatsApp</strong></td> <td style="text-align: left">Messages: RCS + Web App for Mac. WhatsApp: Popular, but iOS&lt;&gt;Android transfer is tricky.</td> </tr> <tr> <td style="text-align: left"><strong>Apple Photos</strong></td> <td style="text-align: left"><strong>Google Photos</strong></td> <td style="text-align: left">Excellent cross-platform sync, powerful search, AI features, similar interface feel.</td> </tr> </tbody> </table> <hr/> <h1 id="detailed-replacement-strategies">Detailed Replacement Strategies</h1> <p>Let’s dive deeper into each replacement.</p> <h2 id="1-replacing-apple-books">1. Replacing Apple Books</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/apple-ibooks.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/apple-ibooks.svg" class="img-fluid" width="100%" height="auto" alt="Apple Books Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/icons8-amazon-kindle.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/icons8-amazon-kindle.svg" class="img-fluid" width="100%" height="auto" alt="Amazon Kindle Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Amazon Kindle App + Send to Kindle</strong></li> <li><strong>How it Works:</strong> <ul> <li>Install the Kindle app on your Android phone, iPad, and Mac (available on the App Store/web).</li> <li>Log in with your Amazon account. Your purchased Kindle books sync automatically.</li> <li>For personal documents (PDFs, epubs you own): Use the <strong>Send to Kindle</strong> service. There are apps/extensions for Mac (<a href="https://www.amazon.com/sendtokindle">https://www.amazon.com/sendtokindle</a>) and you can email documents directly. These docs sync across your Kindle apps just like purchased books.</li> </ul> </li> <li><strong>Pros:</strong> Excellent sync, large store, actively developed, handles personal documents well.</li> <li><strong>Cons:</strong> Tied to the Amazon ecosystem.</li> </ul> <h2 id="2-replacing-apple-notes">2. Replacing Apple Notes</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/apple-notes.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/apple-notes.svg" class="img-fluid" width="100%" height="auto" alt="Apple Notes Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/OneNote.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/OneNote.svg" class="img-fluid" width="100%" height="auto" alt="Microsoft OneNote Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.5em; color: #777; vertical-align: middle; margin-right: 5px;">+</div> <div style="display: inline-block;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/google_keep.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/google_keep.svg" class="img-fluid" width="100%" height="auto" alt="Google Keep Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Microsoft OneNote</strong> (for structured notes) + <strong>Google Keep</strong> (for quick capture)</li> <li><strong>How it Works:</strong> <ul> <li><strong>OneNote:</strong> Offers robust organization with notebooks, sections, and pages. Great for detailed notes, research, meeting minutes. <strong>Supports Apple Pencil on iPad</strong>, embedding PDFs/images, and has native apps for Mac, Android, iOS, iPadOS, and Web. Syncs via your Microsoft account (free). Excellent for annotating research papers directly within the note.</li> <li><strong>Google Keep:</strong> Simple, fast, and effective for quick thoughts, checklists (like groceries), and reminders. Accessible via native apps (Android/iOS) and web (Mac/iPad). Integrates well with other Google services.</li> </ul> </li> <li><strong>Pros:</strong> OneNote is powerful and truly cross-platform. Keep is lightweight and fast.</li> <li><strong>Cons:</strong> Using two apps might feel disjointed initially. OneNote can sometimes feel ‘heavy’ compared to Apple Notes.</li> </ul> <h2 id="3-replacing-apple-calendar">3. Replacing Apple Calendar</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/apple-calendar.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/apple-calendar.svg" class="img-fluid" width="100%" height="auto" alt="Apple Calendar Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Google_Calendar_icon.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Google_Calendar_icon.svg" class="img-fluid" width="100%" height="auto" alt="Google Calendar Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Google Calendar</strong> (as the backend)</li> <li><strong>How it Works:</strong> <ul> <li><strong>On your Mac:</strong> Go to System Settings &gt; Internet Accounts and add your Google Account. Ensure “Calendars” is checked. Open the Apple Calendar app, go to Preferences &gt; General, and set your Google Calendar as the “Default Calendar Account”. You can continue using the familiar Apple Calendar interface on your Mac, but events will sync via Google.</li> <li><strong>On Android/iPad/iOS:</strong> Use the excellent native Google Calendar app.</li> </ul> </li> <li><strong>Pros:</strong> Seamless sync, leverages the robust Google Calendar backend, allows using the native Mac Calendar app.</li> <li><strong>Cons:</strong> Requires using Google as the central calendar authority.</li> </ul> <h2 id="4-replacing-apple-reminders">4. Replacing Apple Reminders</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/reminders.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/reminders.svg" class="img-fluid" width="100%" height="auto" alt="Apple Reminders Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Google_Tasks.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Google_Tasks.svg" class="img-fluid" width="100%" height="auto" alt="Google Tasks Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Google Tasks</strong> (integrated with Google Calendar)</li> <li><strong>How it Works:</strong> <ul> <li>Use the dedicated Google Tasks app on Android and iOS &amp; iPad.</li> <li>On Mac, access Google Tasks via the sidebar in Gmail or Google Calendar on the web and install as progessive web app.</li> <li>Tasks with due dates automatically appear in your Google Calendar (which you’re likely already syncing everywhere).</li> </ul> </li> <li><strong>Pros:</strong> Simple interface, excellent integration with Google Calendar, cross-platform apps + web access.</li> <li><strong>Cons:</strong> Less feature-rich than some dedicated task managers, web access on Mac isn’t a native app.</li> </ul> <h2 id="5-replacing-airdrop">5. Replacing AirDrop</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/airdrop.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/airdrop.svg" class="img-fluid" width="100%" height="auto" alt="AirDrop Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/neardrop-480.webp 480w,/assets/img/posts/mac_android/new/neardrop-800.webp 800w,/assets/img/posts/mac_android/new/neardrop-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/neardrop.png" class="img-fluid" width="100%" height="auto" alt="NearDrop Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.5em; color: #777; vertical-align: middle; margin-right: 5px;">+</div> <div style="display: inline-block;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Quickshare.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Quickshare.svg" class="img-fluid" width="100%" height="auto" alt="Quick Share Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>NearDrop</strong> (for Mac) working with <strong>Quick Share</strong> (built into Android) on same Wi-Fi network</li> <li><strong>How it Works:</strong> <ul> <li><strong>Install NearDrop on your Mac:</strong> (<a href="https://github.com/grishka/NearDrop">GitHub</a> - requires manual installation or using a community build from brew). This app allows receiving (easier) from Android to Mac. Also supports sending (more nuanced) Mac from Android.</li> <li><strong>Mac to Android:</strong> The most reliable Mac-to-Android flow might be initiating from Android’s Quick Share receive mode, then sending from Mac. <ul> <li>Install Google Files (built-in for Pixel) on android.</li> <li>Open the “Files” app on your Pixel and select “Quick Share”.</li> <li>On Mac, right click the file, select share. Click “Edit extensions”, and enable the “Near Drop” extension (first time only).</li> <li>Now right click the file on mac, click Share -&gt; NearDrop. And choose the android phone.</li> </ul> </li> <li><strong>Android to Mac:</strong> Ensure the NearDrop app is running on your Mac. On your Android phone, select a file, tap Share, choose “Quick Share” (sometimes called Nearby Share), and select your Mac when it appears. Approve the transfer on your Mac via the NearDrop prompt.</li> </ul> </li> <li><strong>Pros:</strong> Wireless, relatively fast for local transfers.</li> <li> <p><strong>Cons:</strong> Requires a third-party app (NearDrop) on Mac. Both devices usually need to be on the same Wi-Fi network. Requires manual approval for transfers. NearDrop being open might make your Mac visible to anyone else on the network using Quick Share (though transfers still require approval). Less seamless than AirDrop.</p> </li> <li><strong>Alternative:</strong> Cloud storage (Google Drive, Dropbox, OneDrive) or messaging apps (Signal, WhatsApp) are often easier for Mac-to-Android transfers if you are on different networks.</li> </ul> <h2 id="6-replacing-find-my-airtags">6. Replacing Find My (AirTags)</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/find-my.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/find-my.svg" class="img-fluid" width="100%" height="auto" alt="Find My Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-3"> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/find-my-android.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/find-my-android.svg" class="img-fluid" width="100%" height="auto" alt="Find My Android Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.5em; color: #777; vertical-align: middle; margin-right: 5px;">+</div> <div style="display: inline-block;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/chipolo-pop.webp" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/chipolo-pop.webp" class="img-fluid" width="100%" height="auto" alt="Chipolo Pop Tracker" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Chipolo PoP</strong> (compatible with Google’s Find My Device &amp; Apple Find My)</li> <li><strong>How it Works:</strong> <ul> <li>Chipolo makes trackers specifically designed to work with Google’s Find My Device network and Apple Find My Network (only one at a time). For our case, set them up using your Android phone, and you can access Google Find my on web for Mac, iOS.</li> <li>These devices leverage the network of Android devices to report their location, similar to AirTags using Apple devices.</li> <li>Crucially, Chipolo Pop devices can be <em>switched</em> between Google’s network and Apple’s Find My network (though not used on both simultaneously). You’d need to reset and re-pair it to switch.</li> </ul> </li> <li><strong>Pros:</strong> Provides AirTag-like functionality using the Android network. Can be switched to Apple’s network if needed later.</li> <li><strong>Cons:</strong> <strong>No Precision Finding</strong> like UWB-enabled AirTags. Location updates depend on nearby Android devices running the necessary services. Switching networks requires a reset.</li> </ul> <h2 id="7-replacing-apple-keychain">7. Replacing Apple Keychain</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/Passwords_iOS.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/Passwords_iOS.svg" class="img-fluid" width="100%" height="auto" alt="Apple Passwords Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/icons8-chrome.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/icons8-chrome.svg" class="img-fluid" width="100%" height="auto" alt="Google Chrome Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.5em; color: #777; vertical-align: middle; margin-right: 5px;">+</div> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/authy.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/authy.svg" class="img-fluid" width="100%" height="auto" alt="Authy Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.2em; color: #777; vertical-align: middle; margin-right: 5px;">/</div> <div style="display: inline-block;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Google_Authenticator.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Google_Authenticator.svg" class="img-fluid" width="100%" height="auto" alt="Google Authenticator Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Google Chrome Password Manager</strong> + <strong>Authy</strong> or <strong>Google Authenticator</strong></li> <li><strong>How it Works:</strong> <ul> <li><strong>Passwords &amp; Passkeys:</strong> <ul> <li><strong>On your iPhone/iPad (iOS/iPadOS):</strong> Go to Settings &gt; Passwords &gt; Password Options. Enable “AutoFill Passwords and Passkeys” and select <strong>Chrome</strong> (ensure you have Chrome installed). <em>Do NOT just select “Google” if that appears as a separate option, as Chrome handles the broader sync better with Passkeys.</em></li> <li><strong>On Android:</strong> Chrome is likely the default. Check in Settings &gt; Passwords &amp; accounts &gt; Autofill service.</li> <li><strong>On Mac:</strong> Use Google Chrome. Sign in to your Google account. Passwords and passkeys will sync and autofill within Chrome. You can also access them via <code class="language-plaintext highlighter-rouge">passwords.google.com</code>.</li> </ul> </li> <li><strong>Verification Codes (2FA/OTP):</strong> <ul> <li>Use <strong>Authy</strong> or <strong>Google Authenticator</strong>. Both support scanning QR codes to add accounts.</li> <li><strong>Authy:</strong> Has native apps for Mac, Android, iOS and supports encrypted cloud backups/sync across devices. Generally preferred for multi-device use.</li> <li><strong>Google Authenticator:</strong> Recently added cloud sync tied to your Google account, making it cross-platform. Simpler interface than Authy.</li> <li>Didn’t suggest Microsoft Authenticator as it doesn’t seem to simultaneous sync between iOS/Android; sticking to Authy or Google Authenticator is generally more reliable for this mixed setup*</li> </ul> </li> </ul> </li> <li><strong>Pros:</strong> Chrome PM handles passwords and passkeys across all platforms via the browser. Authy/GA provide reliable cross-platform 2FA code access.</li> <li><strong>Cons:</strong> Password access outside of Chrome on Mac might require opening Chrome. Less integrated into macOS system-wide prompts compared to Keychain.</li> </ul> <h2 id="8-replacing-imessage">8. Replacing iMessage</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/IMessage_logo.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/IMessage_logo.svg" class="img-fluid" width="100%" height="auto" alt="iMessage Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <div style="display: inline-block; margin-right: 5px;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Google_Messages_logo.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Google_Messages_logo.svg" class="img-fluid" width="100%" height="auto" alt="Google Messages Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div style="display: inline-block; font-size: 1.2em; color: #777; vertical-align: middle; margin-right: 5px;">+</div> <div style="display: inline-block;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/WhatsApp.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/WhatsApp.svg" class="img-fluid" width="100%" height="auto" alt="WhatsApp Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Google Messages (using RCS)</strong> and <strong>WhatsApp</strong></li> <li><strong>The Elephant in the Room:</strong> This is often the trickiest transition. There’s no perfect iMessage equivalent.</li> <li><strong>Google Messages (RCS):</strong> <ul> <li>Use Google Messages as your default SMS/RCS app on Android. RCS provides features like typing indicators, read receipts, and high-quality media sharing with other RCS users (including recent support on iPhones).</li> <li><strong>On Mac/iPad/Web:</strong> Use Google Messages for Web (<a href="https://messages.google.com/web">messages.google.com/web</a>) by scanning a QR code from your Android phone’s Messages app (under Device Pairing). This requires your phone to be online. And you can then install as PWA (Progressive web app) with its on icon on your Mac Dock.</li> </ul> </li> <li><strong>WhatsApp:</strong> <ul> <li>Widely used, truly cross-platform with native apps (including Mac).</li> <li><strong>Major Con:</strong> Migrating chat history between iOS and Android is <strong>cumbersome</strong>. While possible now, it often requires the <em>new</em> phone (the one receiving the chats) to be factory reset during the transfer process. Moving chats from your old iPhone to the new Pixel requires this reset. Moving back later would too.</li> <li>Migrating chats between Android -&gt; Android or iOS -&gt; iOS is easy via cloud backups.</li> <li>You can also setup your iPhone as <strong>companion phone</strong> (using linked device) to have your WhatsApp chat sync between the 2 devices. Your android doesn’t need to be online for the sync to work.</li> </ul> </li> <li><strong>Pros:</strong> Google Messages offers RCS improvements and web access. WhatsApp is ubiquitous.</li> <li><strong>Cons:</strong> No single app perfectly replicates iMessage’s seamless SMS/data messaging integration across Apple hardware. Google Messages web requires the phone to be on. WhatsApp migration is painful. Green bubbles when texting iPhones from Android (though RCS helps).</li> </ul> <h2 id="9-replacing-apple-photos">9. Replacing Apple Photos</h2> <div class="row mt-3 mb-3 align-items-center justify-content-center text-center"> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/apple/photos.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/apple/photos.svg" class="img-fluid" width="100%" height="auto" alt="Apple Photos Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> <div class="col-auto px-0 align-items-center "> <span style="font-size: 1.8em; line-height: 1; color: #555;">⟶</span> </div> <div class="col-4 col-md-2"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/mac_android/new/Google_Photos_icon.svg" sizes="95vw"/> <img src="/assets/img/posts/mac_android/new/Google_Photos_icon.svg" class="img-fluid" width="100%" height="auto" alt="Google Photos Icon" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption"></figcaption> </figure> </div> </div> <ul> <li><strong>Recommendation:</strong> <strong>Google Photos</strong></li> <li><strong>How it Works:</strong> <ul> <li>Install Google Photos on your Android phone, iPhone, and iPad. Access it via <code class="language-plaintext highlighter-rouge">photos.google.com</code> on your Mac.</li> <li>Enable backup &amp; sync on all mobile devices. Photos and videos will upload to your Google account.</li> <li>There’s also a “Google Drive for Desktop” app for Mac that includes an option to upload photos from your system library or folders to Google Photos.</li> </ul> </li> <li><strong>Pros:</strong> Excellent cross-platform availability, powerful AI-driven search (“show me photos of beaches”), automatic albums, great sharing features, similar feel to Apple Photos in many ways. Generous free tier (though high-quality uploads now count towards storage).</li> <li><strong>Cons:</strong> High-resolution backups require paid Google One storage eventually.</li> </ul> <h1 id="general-tips-for-a-smoother-transition">General Tips for a Smoother Transition</h1> <ol> <li><strong>Embrace the Cloud:</strong> Relying on cloud-based services (Google, Microsoft OneNote, Amazon Kindle etc.) is key to making a mixed ecosystem work.</li> <li><strong>Be Patient:</strong> Setting up new workflows takes time. Don’t expect everything to feel identical overnight.</li> <li><strong>Focus on Workflow, Not Just Apps:</strong> Think about <em>what</em> you need to achieve (e.g., access notes everywhere) rather than replicating the exact Apple app.</li> <li><strong>Utilize Web Apps:</strong> Many services have excellent web versions accessible on your Mac or iPad via a browser, reducing the need for native apps in some cases.</li> </ol> <h1 id="conclusion">Conclusion</h1> <p>Switching your primary phone to Android while staying invested in the Mac and iPad ecosystem presents unique challenges, but it’s far from impossible to maintain a highly productive setup. By strategically choosing cross-platform services like Google Calendar, Google Photos, OneNote, Kindle, and password managers like Chrome’s, and using tools like NearDrop and Chipolo to bridge hardware gaps, you can create a surprisingly seamless experience. It requires some setup and adjustment, but the flexibility and power of working across platforms can be very rewarding. Good luck with your new setup!</p>]]></content><author><name>Naman Goyal</name></author><category term="guides"/><category term="android,"/><category term="macos,"/><category term="productivity,"/><category term="cross-platform,"/><category term="ios"/><summary type="html"><![CDATA[Transitioning from iPhone to Android while keeping your Mac? Here's how to replace key Apple ecosystem services for seamless cross-platform productivity.]]></summary></entry><entry><title type="html">Gemma 3 Technical Deep Dive - Architecture, Performance, and Implications</title><link href="https://namangoyal.com/blog/2025/gemma3/" rel="alternate" type="text/html" title="Gemma 3 Technical Deep Dive - Architecture, Performance, and Implications"/><published>2025-04-07T10:00:00+00:00</published><updated>2025-04-07T10:00:00+00:00</updated><id>https://namangoyal.com/blog/2025/gemma3</id><content type="html" xml:base="https://namangoyal.com/blog/2025/gemma3/"><![CDATA[<p>Google DeepMind’s release of the Gemma 3 (missing reference) technical report marks a significant iteration in their family of open-weight models. Building upon Gemma 1 and 2, Gemma 3 introduces multimodality, enhanced multilingual capabilities, and significantly longer context windows, while explicitly targeting efficiency suitable for consumer-grade hardware – a crucial consideration in the democratization of large model access.</p> <h2 id="1-introduction">1. Introduction</h2> <p>Gemma 3 extends the Gemma family with models ranging from 1B to 27B parameters. Key advancements presented include:</p> <ol> <li><strong>Multimodality:</strong> Integration of vision understanding via a tailored, frozen SigLIP encoder, processing images as sequences of soft tokens.</li> <li><strong>Long Context:</strong> Support for context windows of at least 128K tokens (32K for the 1B model), enabled by architectural modifications.</li> <li><strong>Architectural Efficiency:</strong> A novel interleaved local/global attention mechanism specifically designed to mitigate the KV cache memory bottleneck inherent in long-context inference.</li> <li><strong>Enhanced Performance:</strong> Superior results compared to Gemma 2, particularly in instruction-tuned (IT) variants. This is attributed to knowledge distillation and a refined post-training recipe incorporating advanced RL techniques.</li> <li><strong>Improved Multilinguality:</strong> Better representation and handling of non-English languages achieved through adjustments in the pre-training data mixture and the adoption of the Gemini 2.0 tokenizer.</li> </ol> <p>This analysis will dissect these aspects, examining the architectural rationale, training methodologies, evaluation paradigms, and the inherent trade-offs and limitations.</p> <h2 id="2-model-architecture">2. Model Architecture</h2> <p>While retaining the foundational decoder-only Transformer architecture, Gemma 3 incorporates several critical modifications compared to its predecessors, primarily focused on enabling long context efficiently and integrating vision.</p> <h3 id="kv-cache-management-interleaved-localglobal-attention">KV Cache Management: Interleaved Local/Global Attention</h3> <p>A primary bottleneck for deploying long-context models is the KV cache size, which scales linearly with sequence length and number of layers, quickly exceeding typical device memory. Gemma 3 tackles this with a hybrid attention strategy:</p> <ul> <li><strong>5:1 Interleaving:</strong> The architecture alternates between local (sliding window) self-attention and global self-attention layers. Specifically, it employs a repeating pattern of 5 local layers followed by 1 global layer.</li> <li><strong>Short Local Span:</strong> Local attention layers operate with a constrained sliding window of only 1024 tokens. This significantly limits the contribution of these layers to the overall KV cache size.</li> <li><strong>Efficient Long Context Processing:</strong> Consequently, only the global attention layers (representing 1/6th of the total layers) need to store Keys and Values for the entire 128K context window. This architectural choice drastically reduces the KV cache footprint compared to a model where all layers attend globally, striking a balance between capturing long-range dependencies and maintaining inference feasibility on resource-constrained hardware.</li> </ul> <pre><code class="language-mermaid">sequenceDiagram
    participant I as Input Sequence (up to 128K)
    participant L5 as 5x Local Attention Layers
    participant G1 as 1x Global Attention Layer
    participant O as Output Embeddings

    I-&gt;&gt;L5: Process with 1024-token sliding window
    Note right of L5: KV cache contribution per layer&lt;br/&gt;scales with window size (1024)
    L5-&gt;&gt;G1: Process full context (128K)
    Note right of G1: KV cache contribution per layer&lt;br/&gt;scales with full context length (128K)
    loop Multiple Blocks
        G1-&gt;&gt;L5: Output feeds into next block
        L5-&gt;&gt;G1: ...
    end
    G1-&gt;&gt;O: Final Output
</code></pre> <h3 id="vision-integration-siglip-and-pan--scan-ps">Vision Integration: SigLIP and Pan &amp; Scan (P&amp;S)</h3> <p>Gemma 3 integrates visual processing using:</p> <ul> <li><strong>Vision Encoder:</strong> A 400M parameter variant of the SigLIP Vision Transformer <a class="citation" href="#zhai2023sigmoid">(Zhai et al., 2023)</a>. Notably, this encoder is <em>frozen</em> during the language model’s training. This simplifies training and reduces computational cost but potentially limits the synergy between visual and textual feature extraction compared to end-to-end training.</li> <li><strong>Token Condensation:</strong> Embeddings from the SigLIP encoder are condensed into a fixed-length sequence of 256 vectors (“soft tokens”). These visual tokens are prepended to the text token sequence, serving as the language model’s input representation of the image.</li> <li><strong>Pan &amp; Scan (P&amp;S):</strong> The SigLIP encoder operates at a fixed resolution (896x896). To handle arbitrary image resolutions and aspect ratios effectively, particularly for tasks involving reading text or resolving fine details, an inference-time P&amp;S strategy (inspired by <a class="citation" href="#liu2023visual">(Liu et al., 2023)</a>) is employed. This adaptively segments the input image into potentially overlapping crops, resizes each to the target 896x896 resolution, encodes them individually, and concatenates the resulting 256-token sequences (up to a predefined maximum number of crops). While enhancing capability for high-resolution or non-square images, this adds computational overhead during inference.</li> </ul> <div class="row justify-content-center"> <div class="col-12 col-md-6"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/posts/pan_scan-480.webp 480w,/assets/img/posts/pan_scan-800.webp 800w,/assets/img/posts/pan_scan-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/posts/pan_scan.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">Gemma 3 Pan &amp; Scan (P&amp;S) mechanism for image processing.</figcaption> </figure> </div> </div> <h3 id="other-architectural-details">Other Architectural Details</h3> <ul> <li><strong>Long Context Enablers:</strong> To effectively utilize the extended context, the Rotary Position Embeddings (RoPE) base frequency for the global self-attention layers is increased significantly from 10k (used in Gemma 2 and Gemma 3’s local layers) to 1M. This adaptation, drawing inspiration from positional interpolation techniques <a class="citation" href="#chen2023extending">(Chen et al., 2023)</a>, helps the model generalize positional information over much longer sequences.</li> <li><strong>Normalization &amp; Attention Refinements:</strong> The models utilize Grouped-Query Attention (GQA) for efficiency, employ RMSNorm for layer normalization (both pre-norm and post-norm), and notably replace the soft-capping activation from Gemma 2 with QK-norm. This QK-normalization (inspired by recent works like <a class="citation" href="#dehghani2023scaling">(Dehghani et al., 2023)</a>, <a class="citation" href="#wortsman2023smallscale">(Wortsman et al., 2023)</a>, <a class="citation" href="#chameleon2024mixedmodal">(Team, 2024)</a>) likely contributes to improved training stability or performance scaling.</li> <li><strong>Tokenizer:</strong> The adoption of the Gemini 2.0 SentencePiece tokenizer (262k vocabulary) is a key change from earlier Gemma versions. Its design aims for better handling of multilingual text, digits, and whitespace, which is crucial for improved performance across diverse languages and domains like code.</li> </ul> <h2 id="3-training-and-data-strategy">3. Training and Data Strategy</h2> <p>The training process involves distinct pre-training and instruction fine-tuning phases, leveraging large datasets and sophisticated optimization techniques.</p> <h3 id="pre-training">Pre-training</h3> <ul> <li><strong>Dataset Scale:</strong> Models were trained on substantial token budgets ranging from 2T (1B model) to 14T (27B model). This slight increase compared to Gemma 2 accommodates the new multimodal and expanded multilingual data components.</li> <li><strong>Data Composition:</strong> The pre-training mixture comprises web documents, code, mathematics, and dialogue data. Emphasis was placed on increasing the proportion of multilingual data (both monolingual corpora across various languages and parallel data) and incorporating image-text pairs for multimodal grounding. Strategies inspired by <a class="citation" href="#chung2023unimax">(Chung et al., 2023)</a> were used to manage language imbalance during sampling.</li> <li><strong>Data Hygiene:</strong> Rigorous filtering was applied to mitigate risks associated with harmful content, remove personally identifiable information (PII), decontaminate evaluation benchmark data from the training set (a critical step for reliable evaluation), and apply quality heuristics, potentially re-weighting data based on quality metrics <a class="citation" href="#sachdeva2024howtotrain">(Sachdeva et al., 2024)</a>.</li> <li><strong>Knowledge Distillation:</strong> Pre-training incorporates knowledge distillation <a class="citation" href="#hinton2015distilling">(Hinton et al., 2015)</a> from larger, unspecified teacher models. The student model learns via cross-entropy loss, but instead of using the standard one-hot target distribution, it uses a softened distribution derived from sampling 256 logits based on the teacher’s output probabilities for each token. This guides the student towards the teacher’s internal representations.</li> </ul> <h3 id="instruction-fine-tuning-post-training">Instruction Fine-tuning (Post-training)</h3> <p>This phase appears critical to Gemma 3’s strong performance on user-oriented tasks, transforming the base models into capable instruction-following agents.</p> <ul> <li><strong>Refined Recipe:</strong> The report mentions a “novel post-training recipe,” although specific details remain limited. It clearly involves an enhanced form of knowledge distillation, this time using a large <em>instruction-tuned</em> teacher model.</li> <li><strong>Reinforcement Learning (RL):</strong> Advanced RL techniques are employed, likely extending methods like BOND <a class="citation" href="#sessa2024bond">(Sessa et al., 2024)</a>, WARM <a class="citation" href="#rame2024warm">(Ramé et al., 2024)</a>, and WARP <a class="citation" href="#rame2024warp">(Ramé et al., 2024)</a>.</li> <li><strong>Multi-Objective Optimization:</strong> RL training utilized a diverse set of reward functions targeting helpfulness, mathematical reasoning, coding proficiency, general reasoning, instruction adherence, multilingual fluency, and safety (minimizing harmfulness). These rewards leverage a combination of human feedback (RLHF), feedback from automated systems like code execution environments <a class="citation" href="#gehring2024rlef">(Gehring et al., 2024)</a>, and ground-truth outcomes for tasks like mathematics (<a class="citation" href="#deepseek2024math">(DeepSeek-AI, 2024)</a>, <a class="citation" href="#lambert2024tulu3">(Lambert et al., 2024)</a>).</li> <li><strong>Targeted Filtering:</strong> Additional data filtering specific to the post-training phase was implemented to enhance factuality, encourage attribution, reduce hallucinations, and steer the model away from unsafe or undesirable outputs.</li> </ul> <h3 id="quantization-aware-training-qat">Quantization Aware Training (QAT)</h3> <p>Recognizing the need for efficient deployment, Gemma 3 models undergo QAT <a class="citation" href="#jacob2018quantization">(Jacob et al., 2018)</a>. This involves further fine-tuning the models specifically for lower-precision inference (targeting per-channel int4, per-block int4, and switched fp8 formats popular in frameworks like llama.cpp). Instead of quantizing post-training, QAT adapts the weights during training, using the full-precision model’s output probabilities as targets, thereby minimizing the performance degradation often associated with quantization.</p> <table> <thead> <tr> <th style="text-align: left">Model</th> <th style="text-align: left">Weights (bf16)</th> <th style="text-align: left">Weights (int4)</th> <th style="text-align: left">W + KV Cache (int4, 32k ctx, 8b KV)</th> <th style="text-align: left">W + KV Cache (SFP8, 32k ctx, 8b KV)</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">1B</td> <td style="text-align: left">2.0 GB</td> <td style="text-align: left">0.5 GB</td> <td style="text-align: left">~1.6 GB</td> <td style="text-align: left">~1.9 GB</td> </tr> <tr> <td style="text-align: left">4B</td> <td style="text-align: left">8.0 GB</td> <td style="text-align: left">2.6 GB</td> <td style="text-align: left">~7.6 GB</td> <td style="text-align: left">~9.1 GB</td> </tr> <tr> <td style="text-align: left">12B</td> <td style="text-align: left">24.0 GB</td> <td style="text-align: left">6.6 GB</td> <td style="text-align: left">~22.0 GB</td> <td style="text-align: left">~27.3 GB</td> </tr> <tr> <td style="text-align: left">27B</td> <td style="text-align: left">54.0 GB</td> <td style="text-align: left">14.1 GB</td> <td style="text-align: left">~34.0 GB</td> <td style="text-align: left">~46.1 GB</td> </tr> </tbody> </table> <p><em>Table: Approximate Memory Footprints (derived from Table 3 in the report). Note the significant contribution of the KV cache even when quantized.</em></p> <h2 id="4-performance-evaluation">4. Performance Evaluation</h2> <p>Gemma 3 demonstrates substantial improvements over its predecessor, Gemma 2, and achieves performance levels that are highly competitive within the open model landscape, even compared to significantly larger models.</p> <h3 id="general-capabilities-instruction-tuned-models">General Capabilities (Instruction-Tuned Models)</h3> <ul> <li><strong>LMSYS Chatbot Arena:</strong> The Gemma-3-27B-IT model achieved a preliminary Elo score of 1338 (as of March 8, 2025). This positions it strongly among top-performing open models in blind pairwise human evaluations, notably surpassing Gemma-2-27B-IT (1220 Elo) and even larger models like Llama-3.1-405B-Instruct (1269 Elo) at that time. This highlights the effectiveness of the post-training recipe.</li> <li><strong>Standard Benchmarks (Table 6):</strong> Across various academic benchmarks, Gemma 3 models show consistent gains. A key finding is that the Gemma3-4B-IT model frequently matches or outperforms the much larger Gemma2-27B-IT on challenging tasks like MATH, HiddenMath, and MMLU-Lite. The flagship Gemma3-27B-IT closes the gap significantly with the powerful, proprietary Gemini-1.5-Pro on several benchmarks, particularly MATH (89.0 vs. 91.8 for Gemini Pro) and MMLU-Pro (67.5 vs. 75.8), while also showing strong coding (LiveCodeBench) and reasoning performance.</li> </ul> <p>Here’s a comparative visualization for selected benchmarks based on Table 6 data:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": ["MMLU-Pro", "LiveCodeBench", "MATH", "HiddenMath", "MMMU (val)", "Global MMLU-Lite"],
      "y": [56.9, 20.4, 55.6, 14.8, null, 68.6],
      "name": "Gemma 2 27B IT",
      "type": "bar"
    },
    {
      "x": ["MMLU-Pro", "LiveCodeBench", "MATH", "HiddenMath", "MMMU (val)", "Global MMLU-Lite"],
      "y": [67.5, 29.7, 89.0, 60.3, 64.9, 75.1],
      "name": "Gemma 3 27B IT",
      "type": "bar"
    }
  ],
  "layout": {
    "title": {
      "text": "Gemma 3 27B IT vs Gemma 2 27B IT Performance"
    },
    "yaxis": {
      "title": "Score (%)"
    },
    "barmode": "group",
    "legend": {"yanchor":"top", "y":0.99, "xanchor":"left", "x":0.01}
  }
}
</code></pre> <h3 id="vision-and-multimodal-performance">Vision and Multimodal Performance</h3> <ul> <li>Gemma 3 achieves robust performance on diverse vision-language benchmarks (Tables 11 &amp; 16), demonstrating proficiency in tasks requiring OCR (DocVQA, TextVQA), understanding of structured information (ChartQA), and general visual question answering (MMMU, VQAv2).</li> <li>The effectiveness of the Pan &amp; Scan (P&amp;S) mechanism is empirically validated (Table 8), providing substantial score increases on benchmarks sensitive to image resolution and text legibility, such as DocVQA (+8.2 points for 4B, +4.8 for 27B) and InfoVQA (+12.9 for 4B, +17.0 for 27B). This confirms P&amp;S’s role in overcoming the fixed-resolution limitation of the vision encoder.</li> <li>Initial results on video benchmarks (Table 17) suggest foundational capabilities in understanding temporal sequences of images.</li> </ul> <h3 id="long-context-evaluation">Long Context Evaluation</h3> <ul> <li>Ablation studies focusing on RoPE rescaling (Figure 7) indicate successful generalization up to the target 128K context length for the pre-trained models, maintaining reasonable perplexity.</li> <li>Evaluations on dedicated long-context benchmarks like RULER and MRCR (Table 15) confirm the models’ ability to process and utilize long inputs. However, the observed performance degradation when moving from 32K to 128K contexts suggests that while the architecture <em>supports</em> the length, effectively performing complex reasoning over the <em>entire</em> span, as opposed to information retrieval or localized reasoning, remains challenging.</li> </ul> <h2 id="5-ablation-studies-highlights">5. Ablation Studies Highlights</h2> <p>The report includes several informative ablations shedding light on the impact of specific design choices:</p> <ul> <li><strong>Local:Global Attention Ratio:</strong> Varying the ratio of local to global layers (e.g., 3:1, 7:1 vs. the chosen 5:1) demonstrated minimal impact on model perplexity (Figure 3). This suggests robustness in the architecture and implies the 5:1 ratio was likely selected as an optimal point balancing KV cache savings and potential performance.</li> <li><strong>Sliding Window Size:</strong> Reducing the window size for local attention layers significantly (down to 1024 tokens) incurred only a minor perplexity penalty (Figure 4). This finding is crucial, as it allows for substantial KV cache reduction without compromising core language modeling ability.</li> <li><strong>KV Cache Memory Savings:</strong> The interleaved architecture provides dramatic memory savings during inference compared to a standard global-only attention model, especially at longer sequence lengths (Figures 5 &amp; 6). This is the primary enabler for running Gemma 3 with 128K context on accessible hardware.</li> <li><strong>Teacher Model Size in Distillation:</strong> An interesting finding (Figure 8) suggests that for <em>longer</em> training durations, distilling from a <em>larger</em> teacher model yields better student performance (lower perplexity). This contrasts with some findings from shorter-duration studies where smaller teachers can be optimal, highlighting the importance of training regime length in distillation dynamics.</li> </ul> <h2 id="6-memorization-and-privacy">6. Memorization and Privacy</h2> <p>Addressing memorization of training data is critical for responsible deployment, particularly for open models.</p> <ul> <li>Gemma 3 models demonstrate a marked reduction in both exact and approximate memorization rates compared to Gemma 2 and other previous models evaluated (Figure 9, note the log scale). This is a significant improvement from a privacy and intellectual property perspective.</li> <li>Memorization rates are lowest for the 1B model and increase slightly with model size. Approximate memorization (allowing for small edits) occurs roughly 24 times more frequently than exact verbatim memorization, on average across the models.</li> </ul> <pre><code class="language-plotly">{
  "data": [
    {
      "x": ["Gemma 3 1B", "Gemma 3 4B", "Gemma 3 12B", "Gemma 3 27B", "Gemma 2 2B", "Gemma 2 9B", "Gemma 2 27B"],
      "y": [0.0002, 0.001, 0.0015, 0.002, 0.01, 0.05, 0.1],
      "name": "Approx Memorization",
      "type": "bar"
    },
        {
      "x": ["Gemma 3 1B", "Gemma 3 4B", "Gemma 3 12B", "Gemma 3 27B", "Gemma 2 2B", "Gemma 2 9B", "Gemma 2 27B"],
      "y": [0.00001, 0.00004, 0.00006, 0.00008, 0.001, 0.005, 0.01],
      "name": "Exact Memorization",
      "type": "bar"
    }
  ],
  "layout": {
    "title": {
      "text": "Memorization Rates (Log Scale, Illustrative Values)"
    },
    "yaxis": {
      "title": "Rate (%)",
      "type": "log",
       "tickformat": ".5f"
    },
    "barmode": "group",
    "legend": {"yanchor":"top", "y":0.99, "xanchor":"left", "x":0.01}
  }
}
</code></pre> <p><em>(Note: Y-axis values are illustrative, based on the visual trend and relative differences shown in Figure 9, as exact numbers aren’t provided in the text.)</em></p> <ul> <li>Importantly, using Google Cloud’s Sensitive Data Protection (SDP) tool, no personally identifiable information (PII) was detected within the model outputs identified as memorized content. This suggests that the PII filtering applied during data preparation was effective.</li> </ul> <h2 id="7-responsibility-safety-security">7. Responsibility, Safety, Security</h2> <p>DeepMind emphasizes a continued commitment to safety and responsibility, integrating processes throughout the development lifecycle.</p> <ul> <li><strong>Governance and Assessment:</strong> The approach mirrors previous Gemma releases, balancing the benefits of open models with awareness of potential misuse. Risk assessments are conducted, considering the new multimodal and long-context capabilities.</li> <li><strong>Safety Policies &amp; Mitigations:</strong> Safety filtering is applied to pre-training data. Instruction tuning incorporates alignment with Google’s safety policies (covering areas like hate speech, harassment, dangerous content, child safety, non-consensual sexual content, and promoting illegal acts or severely harmful ideologies) using both supervised fine-tuning (SFT) and RLHF.</li> <li><strong>Assurance Evaluations:</strong> Models undergo internal safety evaluations using adversarial prompts and human rating to assess policy violation rates, which are reported as low overall. Specific evaluations for knowledge related to Chemical, Biological, Radiological, and Nuclear (CBRN) threats indicate low capability in these high-risk domains.</li> <li><strong>Responsible Open Models Approach:</strong> A system-level view is advocated, emphasizing that safety depends not just on the model but also on the application’s design and deployment environment.</li> </ul> <h2 id="8-critical-analysis-and-discussion">8. Critical Analysis and Discussion</h2> <p>Gemma 3 undoubtedly pushes the state-of-the-art for open models in its parameter class, particularly regarding efficient multimodality and long context. However, a critical perspective reveals several nuances:</p> <ol> <li><strong>The Role of Distillation:</strong> The remarkable performance gains, especially for the instruction-tuned models relative to their base counterparts and even Gemma 2, appear heavily reliant on knowledge distillation from highly capable (presumably proprietary, Gemini-family) teacher models. While effective, this underscores that achieving SOTA performance with Gemma 3 is not solely a function of its architecture or pre-training data, but significantly influenced by the quality of the teacher. The specifics of the “novel post-training recipe” remain opaque, limiting full replicability and understanding of the capability drivers.</li> <li><strong>Defining “Long-Context Utilization”:</strong> The architecture successfully <em>enables</em> 128K context processing within reasonable memory constraints. However, performance metrics like perplexity and benchmark scores (e.g., the drop on MRCR from 32K to 128K) suggest a gap between <em>processing</em> long context and <em>effectively reasoning</em> over its entire span for complex tasks. The model might excel at retrieval or tasks where relevant information is localized, but deeper integration of information across the full 128K tokens might still be limited.</li> <li><strong>Vision Component Trade-offs:</strong> Employing a frozen, relatively small (400M) vision encoder and condensing its output to 256 tokens prioritizes efficiency and simplifies training. However, this may inherently limit the richness of visual feature extraction and the potential for deep fusion between visual and language modalities compared to approaches with larger, jointly trained vision components. The P&amp;S mechanism, while effective, introduces inference latency and complexity.</li> <li><strong>Disentangling Contributing Factors:</strong> The report presents numerous improvements simultaneously (architecture, tokenizer, data mix, distillation, RL recipe). While ablations isolate some factors (like attention ratios), fully disentangling the precise contribution of each element (e.g., how much gain comes from the new tokenizer vs. the data mix vs. architectural tweaks alone) is difficult based on the provided results.</li> <li><strong>Evaluation Considerations:</strong> While benchmark decontamination is performed, the possibility of subtle information leakage influencing performance on standard benchmarks always exists in LLM evaluation. Furthermore, the strong Chatbot Arena performance, while valuable, reflects preference on a specific platform and may not perfectly correlate with performance on all downstream tasks.</li> </ol> <h2 id="conclusion">Conclusion</h2> <p>Gemma 3 stands as a robust and compelling addition to the open model landscape. Its intelligent architectural design, particularly the interleaved local/global attention, offers a pragmatic solution to the challenge of long-context inference on accessible hardware. The seamless integration of vision capabilities, enhanced by the practical P&amp;S mechanism, broadens its applicability significantly.</p> <p>Performance-wise, Gemma 3 models, especially the instruction-tuned variants, demonstrate impressive capabilities, often exceeding models with far larger parameter counts. This success appears heavily driven by sophisticated knowledge distillation and reinforcement learning strategies refined within Google’s broader AI research ecosystem. The significant reduction in training data memorization compared to prior models is a crucial step forward for privacy and responsible AI practices.</p> <p>Despite reliance on undisclosed teacher models for peak performance and open questions regarding the depth of long-context reasoning, Gemma 3 offers a potent combination of performance, efficiency, multimodality, and improved safety characteristics. It provides a valuable asset for researchers and developers seeking powerful, openly available models capable of tackling a wider range of tasks than previous generations.</p>]]></content><author><name>Naman Goyal</name></author><category term="research"/><category term="LLM"/><summary type="html"><![CDATA[An analysis of Google DeepMind's Gemma 3 technical report, covering architecture, training, evaluation, and limitations.]]></summary></entry></feed>