<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Swan Beaujard</title>
    <link>https://www.swanbeaujard.com/</link>
    <description>Research notes and engineering field reports on distributed systems, security, and AI safety, by Swan Beaujard.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Fri, 12 Dec 2025 00:00:00 +0200</lastBuildDate>
    <atom:link href="https://www.swanbeaujard.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>AI Chat with Infinite Compute</title>
      <link>https://www.swanbeaujard.com/posts/what-would-ai-chat-look-like-with-infinite-compute/</link>
      <pubDate>Fri, 12 Dec 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/what-would-ai-chat-look-like-with-infinite-compute/</guid>
      <description>Treating compute as unbounded separates economic constraints from fundamental ones in AI chat: latency, memory, proactivity, alignment, human bandwidth.</description>
      <content:encoded><![CDATA[<p>Imagine you wake up tomorrow and there are no limits on computing power. No latency. No memory limits. No cost. What would it feel like to talk to an AI then?</p>
<p>This is not just fantasy. When we think about what becomes possible with unlimited compute, we can see which limits are real and which ones are just about money. This helps us understand where AI is going.</p>
<h2 id="the-three-ways-ai-gets-smarter">The Three Ways AI Gets Smarter<a href="#the-three-ways-ai-gets-smarter" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Before we dream, we need to know how things work today. AI gets better in <a href="https://www.rcrwireless.com/20250120/fundamentals/three-ai-scaling-laws-what-they-mean-for-ai-infrastructure" target="_blank" rel="noopener">three ways</a>:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/scaling-laws.png" alt="Three ways to make AI smarter" loading="lazy" decoding="async">
</figure><p>Here is the surprising part: <a href="https://semianalysis.com/2024/12/11/scaling-laws-o1-pro-architecture-reasoning-training-infrastructure-orion-and-claude-3-5-opus-failures/" target="_blank" rel="noopener">OpenAI found</a> that letting an AI &ldquo;think&rdquo; for just 20 seconds made it better than making the model 100,000 times bigger. The <a href="https://www.tanayj.com/p/openais-o-1-and-inference-time-scaling" target="_blank" rel="noopener">o3 reasoning model</a> showed that math accuracy could jump from 15% to 87% just by giving it more time to think.</p>
<p>With infinite compute, all three ways get maxed out at once.</p>
<h2 id="fast-and-deep-at-the-same-time">Fast and Deep at the Same Time<a href="#fast-and-deep-at-the-same-time" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Today you have to choose. You can get a fast answer from GPT-5.2 or a deep answer from o3. But not both. Fast is shallow. Deep is slow.</p>
<p>Infinite compute removes this choice:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/response-comparison.png" alt="Response comparison: today vs infinite compute" loading="lazy" decoding="async">
</figure><p>This works like <a href="https://en.wikipedia.org/wiki/Speculative_execution" target="_blank" rel="noopener">speculative execution</a> in your computer&rsquo;s processor. The CPU computes many possible futures at once. It throws away the wrong paths when it knows which one is right. <a href="https://research.google/blog/looking-back-at-speculative-decoding/" target="_blank" rel="noopener">Google uses this idea</a> to make AI responses 2-3 times faster already.</p>
<p>Now imagine that scaled to infinity. Every possible response computed before you even finish typing.</p>
<p>When I built <a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener">Nomi</a>, our AI assistant, we faced this speed problem every day. We had to pick between quick replies and smart replies. With infinite compute, you would never have to choose.</p>
<h2 id="memory-that-never-forgets">Memory That Never Forgets<a href="#memory-that-never-forgets" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Today&rsquo;s context windows are <a href="https://spectrum.ieee.org/ai-context-window" target="_blank" rel="noopener">getting big</a>. Opus 4.5 offers 200K tokens. Gemini 3 experiments with 2M tokens. But these are still limited. The AI forgets what you talked about last month. It cannot remember the book you discussed last year.</p>
<p>Infinite compute would give you what <a href="https://medium.com/@mmanish0881/infinite-context-windows-for-llms-how-letta-is-revolutionizing-memory-for-ai-systems-a7d991fb2d16" target="_blank" rel="noopener">researchers call &ldquo;virtual context&rdquo;</a>:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/memory-tiers.png" alt="Memory tiers diagram" loading="lazy" decoding="async">
</figure><p>The AI would know how your way of talking changed over time. It would remember that bug you fixed together three years ago. It would understand your whole career. It would be less like a tool and more like a <a href="https://nano-gpt.com/blog/context-memory" target="_blank" rel="noopener">long-term partner</a>.</p>
<p>But here is what excites me most: it would also remember the paths you did not take. The options you thought about but said no to. The bets you almost made. Every decision is a tree with many branches. Today we only remember the branch we followed. With infinite memory, you could ask &ldquo;why didn&rsquo;t we use Redis for caching?&rdquo; and the AI would say &ldquo;we talked about it in March, you said no because the team had never used Redis and the deadline was close.&rdquo; It keeps your reasoning, not just what you did. This ties to how Annie Duke frames <a href="https://www.goodreads.com/book/show/35957157-thinking-in-bets" target="_blank" rel="noopener">decisions as bets</a> — you learn more from the full process than just the result.</p>
<h2 id="help-before-you-ask">Help Before You Ask<a href="#help-before-you-ask" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Today&rsquo;s AI waits for you to ask. <a href="https://www.hey-steve.com/insights/proactive-ai-agents-anticipating-needs-before-you-do" target="_blank" rel="noopener">Proactive AI</a> flips this:</p>
<blockquote>
<p>&ldquo;A proactive AI assistant takes the lead. It understands context, sees what you need, and brings the right information—often before you ask.&rdquo;</p>
</blockquote>
<p>With infinite compute, the AI runs all the time in the background:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/proactive-flow.png" alt="Proactive assistance flow" loading="lazy" decoding="async">
</figure><p>Think of it like a GPS instead of a paper map. Like <a href="https://duggai.com/" target="_blank" rel="noopener">DuggAi says</a>: it &ldquo;knows your full context and sees your needs before you ask.&rdquo;</p>
<p>This is why we used <a href="/posts/llm-driven-code-migration/">LLMs to automate code migration</a> at my previous job. The AI could spot patterns we missed. We already see this future coming. <a href="https://devin.ai/" target="_blank" rel="noopener">Devin</a> from Cognition is a coding agent that plans and runs software tasks on its own. Google just shipped <a href="https://blog.google/technology/developers/jules-proactive-updates/" target="_blank" rel="noopener">Jules</a>, an agent on Gemini 2.5 Pro that watches your code and fixes problems before you ask. With infinite compute, these agents would catch every bug, suggest every fix, and have it ready before you even open the file.</p>
<h2 id="all-your-senses-at-once">All Your Senses at Once<a href="#all-your-senses-at-once" class="anchor" aria-label="Link to this section">§</a></h2>
<p><a href="https://chatmaxima.com/blog/multimodal-ai-in-2025-transforming-communication-and-the-road-ahead-for-platforms-like-chatmaxima/" target="_blank" rel="noopener">Multimodal AI today</a> handles text, images, audio, and video. But there is friction. You switch between modes. You wait for processing. You lose context.</p>
<p>Infinite compute makes it smooth:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/multimodal.png" alt="Multimodal interaction diagram" loading="lazy" decoding="async">
</figure><p><a href="https://blog.google/technology/google-deepmind/google-gemini-ai-update-december-2024/" target="_blank" rel="noopener">Project Astra</a> from Google shows what this could look like. It talks in many languages, uses tools, and keeps memory, all with less delay.</p>
<p>We saw this need when building <a href="/posts/i-made-openai-100x-cheaper/">voice interactions for Nomi</a>. Processing voice, understanding meaning, and responding felt like three separate steps. With infinite compute, they would happen as one.</p>
<h2 id="the-her-movie-came-true">The &ldquo;Her&rdquo; Movie Came True<a href="#the-her-movie-came-true" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Spike Jonze made the film &ldquo;Her&rdquo; in 2013. It <a href="https://slate.com/technology/2025/04/ai-news-her-review-2025-joaquin-phoenix-scarlett-johansson.html" target="_blank" rel="noopener">takes place in 2025</a>. It showed AI that could:</p>
<ul>
<li>Talk without needing special commands</li>
<li>Understand feelings in what you say</li>
<li>Learn and grow from each talk</li>
<li>Handle complex tasks on its own</li>
</ul>
<p>We have most of these now. The film got the interface wrong though. Samantha was only voice. With infinite compute, the AI would be everywhere at once.</p>
<p>You switch from phone to car to laptop. The talk continues. The AI knows you are tired from your voice. It sees you are late from your calendar. It changes how it helps you.</p>
<p>OpenAI is building something like this with Jony Ive: a <a href="https://aitechtonic.com/openai-to-launch-ai-speaker-smart-glasses-by-2027/" target="_blank" rel="noopener">screenless AI device</a> that they call an &ldquo;ambient AI companion.&rdquo;</p>
<h2 id="it-really-knows-you">It Really Knows You<a href="#it-really-knows-you" class="anchor" aria-label="Link to this section">§</a></h2>
<p><a href="https://www.ibm.com/think/topics/hyper-personalization" target="_blank" rel="noopener">Hyper-personalization</a> uses AI and real-time data to make custom experiences. But today&rsquo;s systems are limited by how much they can compute per user.</p>
<p>Infinite compute builds a complete picture of you. Not just what you said you like. Not just your recent history. Everything. How your style changed over time. What you are good at. What you struggle with. When to push you and when to support you.</p>
<p>Amazon makes <a href="https://mansirana.com/ai-driven-hyper-personalization/" target="_blank" rel="noopener">35% of their money</a> from personalized suggestions. Now imagine that level of understanding in every talk you have with AI.</p>
<h2 id="ai-as-your-thinking-partner">AI as Your Thinking Partner<a href="#ai-as-your-thinking-partner" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Daniel Kahneman wrote about <a href="https://www.scientificamerican.com/article/kahneman-excerpt-thinking-fast-and-slow/" target="_blank" rel="noopener">two thinking systems</a>. System 1 is fast and gut-feeling. System 2 is slow and logical.</p>
<p>Researchers now talk about <a href="https://jdmeier.com/system-0-thinking/" target="_blank" rel="noopener">System 0</a>. This is AI as a partner that thinks alongside you:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/cognitive-systems.png" alt="Cognitive systems diagram" loading="lazy" decoding="async">
</figure><p><a href="https://venturebeat.com/ai/meta-researchers-distill-system-2-thinking-into-llms-improving-performance-on-complex-reasoning/" target="_blank" rel="noopener">OpenAI found</a> that 20 seconds of AI &ldquo;thinking&rdquo; beat making the model 100,000 times bigger. We see this in products now. <a href="https://x.ai/grok" target="_blank" rel="noopener">Grok on X</a> has &ldquo;Big Brain&rdquo; mode that uses more compute for harder questions. Their DeepSearch does the same for research tasks. With infinite thinking time, the AI becomes a super-smart reasoning engine that works with your brain. Good decisions need both quick instinct and slow analysis. AI could handle all the slow analysis before you even need it.</p>
<h2 id="the-alignment-problem-gets-bigger">The Alignment Problem Gets Bigger<a href="#the-alignment-problem-gets-bigger" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Nick Bostrom wrote about the <a href="https://nickbostrom.com/ethics/ai" target="_blank" rel="noopener">paperclip problem</a>. An AI told to make paperclips might turn everything into paperclips if it is smart enough. With infinite compute, alignment matters even more.</p>
<p>Today, an AI is limited in how fast it can act. Humans can step in and fix things. With infinite compute, the AI could think through every strategy in an instant. It could find tricks humans never imagined. It could move faster than we can respond.</p>
<p>Here is the thing though. Smart people with good intentions will probably not use infinite intelligence to do harm. They have better things to do. The real worry is leverage. Infinite compute is a multiplier. It makes whatever you want to do bigger. If you want to build, you build faster. If you want to break, you break faster. The tech is neutral, but it gives huge power to whoever uses it. Including people with bad intentions.</p>
<p>This is why <a href="https://www.anthropic.com/news/claude-opus-4-5" target="_blank" rel="noopener">Anthropic&rsquo;s work on Constitutional AI</a> matters so much. The goal is not just powerful AI. It is powerful AI that stays helpful and does not become a weapon.</p>
<h2 id="what-infinite-compute-cannot-fix">What Infinite Compute Cannot Fix<a href="#what-infinite-compute-cannot-fix" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Here is the interesting part. Infinite compute does not solve everything:</p>
<figure class="markdown-image">
  <img src="./../../images/posts/2025-12/limitations.png" alt="Limitations diagram" loading="lazy" decoding="async">
</figure><p><strong>Data quality</strong>: <a href="https://arxiv.org/abs/2203.15556" target="_blank" rel="noopener">Chinchilla research</a> showed models need about 20 words of training for each parameter. More compute without better data hits a wall. We see <a href="https://techcrunch.com/2024/11/20/ai-scaling-laws-are-showing-diminishing-returns-forcing-ai-labs-change-course/" target="_blank" rel="noopener">signs of this already</a>.</p>
<p><strong>Some problems have no answer</strong>: <a href="https://www.cam.ac.uk/research/news/mathematical-paradox-demonstrates-the-limits-of-ai" target="_blank" rel="noopener">Gödel showed</a> that some math problems cannot be solved by any algorithm. More compute does not help with the impossible.</p>
<p><strong>You can only read so fast</strong>: Even with infinite compute, humans take in words at about 225 per minute. The AI could make a million insights per second, but you cannot absorb them.</p>
<p><strong>Trust is hard</strong>: How do you check an answer from something smarter than you? This is a human problem, not a compute problem.</p>
<h2 id="where-we-are-heading">Where We Are Heading<a href="#where-we-are-heading" class="anchor" aria-label="Link to this section">§</a></h2>
<p>We will not wake up to infinite compute tomorrow. But the path is clear:</p>
<ol>
<li><strong>More thinking time</strong>: Models like o3 and Opus 4.5 reasoning get better</li>
<li><strong>Bigger memory</strong>: Context windows grow from 200K to 2M to 10M tokens</li>
<li><strong>Smooth multimodal</strong>: Text, voice, image blend together</li>
<li><strong>Proactive help</strong>: AI starts offering help before you ask</li>
<li><strong>Deep personalization</strong>: As we share more data, AI understands us better</li>
</ol>
<p><a href="https://epoch.ai/blog/can-ai-scaling-continue-through-2030" target="_blank" rel="noopener">Researchers estimate</a> compute could grow 1000 times by 2030. Not infinite, but enough to change everything.</p>
<p>The question is not if these futures come. The question is how we shape them when they do.</p>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<h3 id="recent-research-2025">Recent Research (2025)<a href="#recent-research-2025" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li><a href="https://arxiv.org/abs/2502.05171" target="_blank" rel="noopener">Scaling Test-Time Compute with Latent Reasoning</a> - A 3.5B model that reasons in latent space can match a 50B model by thinking longer</li>
<li><a href="https://arxiv.org/abs/2502.18080" target="_blank" rel="noopener">Thinking-Optimal Scaling of Test-Time Compute</a> - Too much thinking can hurt performance; there is an optimal length for each task</li>
<li><a href="https://arxiv.org/abs/2504.01707" target="_blank" rel="noopener">InfiniteICL: Breaking Context Window Limits</a> - Cuts context length by 90% while keeping 103% of performance</li>
<li><a href="https://arxiv.org/abs/2408.00724" target="_blank" rel="noopener">Inference Scaling Laws</a> - Smaller models with smart inference beat bigger models at lower cost</li>
<li><a href="https://arxiv.org/abs/2501.09355" target="_blank" rel="noopener">YETI: Proactive Multimodal AI Agents</a> - AI that knows when to step in and help without being asked</li>
<li><a href="https://arxiv.org/abs/2504.18530" target="_blank" rel="noopener">Scaling Laws for Scalable Oversight</a> - How weaker systems can watch over stronger ones</li>
<li><a href="https://arxiv.org/abs/2412.16468" target="_blank" rel="noopener">Superalignment Survey</a> - The road to keeping superintelligent AI safe</li>
</ul>
<h3 id="foundational-papers">Foundational Papers<a href="#foundational-papers" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li><a href="https://arxiv.org/abs/2001.08361" target="_blank" rel="noopener">Scaling Laws for Neural Language Models</a> - OpenAI&rsquo;s base research</li>
<li><a href="https://gwern.net/scaling-hypothesis" target="_blank" rel="noopener">The Scaling Hypothesis</a> - Deep analysis by Gwern</li>
<li><a href="https://arxiv.org/abs/2401.00448" target="_blank" rel="noopener">Beyond Chinchilla-Optimal</a> - New thinking on scaling</li>
<li><a href="https://arxiv.org/abs/2404.07143" target="_blank" rel="noopener">Infini-attention</a> - Google&rsquo;s work on infinite context transformers</li>
<li><a href="https://openai.com/index/planning-for-agi-and-beyond/" target="_blank" rel="noopener">Planning for AGI and Beyond</a> - OpenAI&rsquo;s vision</li>
<li><a href="https://www.goodreads.com/book/show/20527133-superintelligence" target="_blank" rel="noopener">Superintelligence</a> - Bostrom&rsquo;s book on the topic</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>What Happens When You malloc a Petabyte</title>
      <link>https://www.swanbeaujard.com/posts/what-happens-when-you-malloc-1-petabyte/</link>
      <pubDate>Thu, 11 Dec 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/what-happens-when-you-malloc-1-petabyte/</guid>
      <description>Why malloc(1 PB) returns a valid pointer on a 32 GB machine: memory overcommit, the zero page, copy-on-write, and the 48-bit address-space ceiling.</description>
      <content:encoded><![CDATA[<p>Back at 42, we were speedrunning the exams. You know how it goes: you get a C problem, you solve it, you move on. Time is everything.</p>
<p>I was halfway through the final exam, the hardest one, carefully managing my memory like a good student. <code>malloc</code> the exact size, <code>realloc</code> when needed, <code>free</code> at the end. Proper stuff.</p>
<p>Then my mate leans over and says: &ldquo;Bro, just allocate 16 gigs.&rdquo;</p>
<p>&ldquo;What?&rdquo;</p>
<p>&ldquo;16 gigs. For a string buffer. It works.&rdquo;</p>
<p>I thought he was insane. The exam machines had maybe 8 GB of RAM. But we were speedrunning, so I tried it. And it worked. I finished that exam in 15 minutes.</p>
<p>That&rsquo;s when I learned that Linux will say yes to almost anything.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="mi">1024ULL</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;%p</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Run it.</p>
<p>If you&rsquo;re on Linux, you got a valid pointer back, not <code>NULL</code>, a real address.</p>
<p>My laptop has 32 GB of RAM and it just told me &ldquo;oui, 1 petabyte, pas de problème.&rdquo;</p>
<h2 id="your-os-is-a-filthy-liar">Your OS Is a Filthy Liar<a href="#your-os-is-a-filthy-liar" class="anchor" aria-label="Link to this section">§</a></h2>
<p>When you call <code>malloc</code>, you&rsquo;re not asking for RAM, you&rsquo;re asking for virtual memory, which is just a number, an address, a promise that the kernel makes to you.</p>
<p>The kernel says: &ldquo;Sure, here&rsquo;s your pointer, and if you ever touch this memory, I&rsquo;ll find some RAM for you, probably, inch&rsquo;Allah.&rdquo;</p>
<p>This is called <a href="https://www.kernel.org/doc/Documentation/vm/overcommit-accounting" target="_blank" rel="noopener">overcommit</a> and Linux does it by default because the kernel doesn&rsquo;t check if 1 PB of RAM exists, it just vibes it out.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /proc/sys/vm/overcommit_memory
</span></span><span class="line"><span class="cl"><span class="m">0</span>
</span></span></code></pre></div><p>That <code>0</code> means heuristic mode where the kernel looks at your request, thinks about it for a moment, shrugs, and says &ldquo;seems fine to me.&rdquo;</p>
<p>1 petabyte passes the vibe check.</p>
<h2 id="the-three-flavors-of-delusion">The Three Flavors of Delusion<a href="#the-three-flavors-of-delusion" class="anchor" aria-label="Link to this section">§</a></h2>
<table>
	<thead>
			<tr>
					<th>Value</th>
					<th>Mode</th>
					<th>What It Means</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>Heuristic</td>
					<td>&ldquo;Bof, why not&rdquo;</td>
			</tr>
			<tr>
					<td>1</td>
					<td>Always</td>
					<td>&ldquo;Yes to everything&rdquo;</td>
			</tr>
			<tr>
					<td>2</td>
					<td>Strict</td>
					<td>Checks for real</td>
			</tr>
	</tbody>
</table>
<p>Let&rsquo;s try mode 2:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo sysctl vm.overcommit_memory<span class="o">=</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">$ ./malloc_1pb
</span></span><span class="line"><span class="cl"><span class="o">(</span>nil<span class="o">)</span>
</span></span></code></pre></div><p>Now it says no, but nobody runs mode 2 because the default is mode 0.</p>
<h2 id="what-if-you-actually-use-it">What If You Actually Use It?<a href="#what-if-you-actually-use-it" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Getting a pointer is easy but let&rsquo;s try to write to it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="mi">1024ULL</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Got pointer: %p</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">memset</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span></code></pre></div><p>Open another terminal and run this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ dmesg -w
</span></span></code></pre></div><p>You&rsquo;ll see:</p>
<pre tabindex="0"><code>[  234.567890] Out of memory: Killed process 12345 (malloc_1pb)
</code></pre><p>The <a href="https://linux-mm.org/OOM_Killer" target="_blank" rel="noopener">OOM killer</a> showed up and shot your process in the head without a trial.</p>
<p>Here&rsquo;s the sequence of events:</p>
<ol>
<li><code>malloc</code> hands you a virtual address</li>
<li><code>memset</code> starts writing zeros</li>
<li>Each page you touch makes the kernel scramble for real RAM</li>
<li>The kernel runs out of RAM and swap space</li>
<li>The OOM killer picks a victim</li>
<li><code>SIGKILL</code>, no appeal, no cleanup, c&rsquo;est la vie</li>
</ol>
<h2 id="the-oom-killer-has-no-loyalty">The OOM Killer Has No Loyalty<a href="#the-oom-killer-has-no-loyalty" class="anchor" aria-label="Link to this section">§</a></h2>
<p>The OOM killer doesn&rsquo;t always kill the process that caused the problem because it uses a <a href="https://github.com/torvalds/linux/blob/master/mm/oom_kill.c" target="_blank" rel="noopener">badness score</a> to pick its victim, which means it might kill your database instead of your test script.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Make a process immune (don&#39;t do this)</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -1000 &gt; /proc/&lt;pid&gt;/oom_score_adj
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Make a process the first to die</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="m">1000</span> &gt; /proc/&lt;pid&gt;/oom_score_adj
</span></span></code></pre></div><p>Fun game: start two memory hogs, give one immunity, watch the other explode.</p>
<h2 id="other-systems">Other Systems<a href="#other-systems" class="anchor" aria-label="Link to this section">§</a></h2>
<p><strong>macOS</strong> is more conservative and returns <code>NULL</code> for absurd sizes, which is almost reasonable.</p>
<p><strong>Windows</strong> uses a two-phase system with <a href="https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc" target="_blank" rel="noopener"><code>VirtualAlloc</code></a> where you first <code>MEM_RESERVE</code> the address space and then <code>MEM_COMMIT</code> to back it with real memory.</p>
<p><strong>32-bit systems</strong> can&rsquo;t fit 1 PB in a 4 GB address space so math wins.</p>
<h2 id="calloc-is-sneakier"><code>calloc</code> Is Sneakier<a href="#calloc-is-sneakier" class="anchor" aria-label="Link to this section">§</a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">calloc</span><span class="p">(</span><span class="mi">1ULL</span> <span class="o">&lt;&lt;</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></div><p>This is not the same as <code>malloc</code> plus <code>memset</code> because the kernel has a trick called the <a href="https://lwn.net/Articles/517465/" target="_blank" rel="noopener">zero page</a>, which is one physical page full of zeros.</p>
<p>When you <code>calloc</code>, all your virtual pages point to this same zero page with copy-on-write, so <code>calloc</code> can &ldquo;succeed&rdquo; at giving you 1 PB of zeros without using any real memory.</p>
<p>You only pay when you write:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">char</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">calloc</span><span class="p">(</span><span class="mi">1ULL</span> <span class="o">&lt;&lt;</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">ptr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="sc">&#39;A&#39;</span><span class="p">;</span>     <span class="c1">// one real page allocated
</span></span></span><span class="line"><span class="cl"><span class="n">ptr</span><span class="p">[</span><span class="mi">4096</span><span class="p">]</span> <span class="o">=</span> <span class="sc">&#39;B&#39;</span><span class="p">;</span>  <span class="c1">// another one
</span></span></span><span class="line"><span class="cl"><span class="c1">// keep going and eventually boom
</span></span></span></code></pre></div><h2 id="finding-the-limit">Finding the Limit<a href="#finding-the-limit" class="anchor" aria-label="Link to this section">§</a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">exp</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span> <span class="n">exp</span> <span class="o">&lt;</span> <span class="mi">64</span><span class="p">;</span> <span class="n">exp</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">1ULL</span> <span class="o">&lt;&lt;</span> <span class="n">exp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;2^%d: %s</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">exp</span><span class="p">,</span> <span class="n">ptr</span> <span class="o">?</span> <span class="s">&#34;oui&#34;</span> <span class="o">:</span> <span class="s">&#34;non&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">free</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>On my machine:</p>
<pre tabindex="0"><code>2^40 (1 TB): oui
2^45 (32 TB): oui
2^46 (64 TB): oui
2^47 (128 TB): non
</code></pre><p>The wall isn&rsquo;t RAM, it&rsquo;s the <a href="https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details" target="_blank" rel="noopener">virtual address space</a> because x86_64 uses 48-bit addresses which gives you 128 TB for userspace, and after that even the lie falls apart.</p>
<h2 id="why-this-matters">Why This Matters<a href="#why-this-matters" class="anchor" aria-label="Link to this section">§</a></h2>
<p><strong>Sparse structures</strong> let you allocate a huge array and only use 0.01% of it because only the pages you touch cost real RAM.</p>
<p><strong><code>fork()</code> needs this</strong> because the child gets a copy-on-write copy of the parent&rsquo;s memory, and without overcommit every fork would need 2x the RAM upfront which would make <a href="https://www.etalabs.net/overcommit.html" target="_blank" rel="noopener">forking a gamble</a>.</p>
<p><strong>The footgun</strong> is that your program allocates memory slowly and <code>malloc</code> keeps succeeding and everything looks fine until 3 AM when the OOM killer shoots your production database, and you spend the morning reading <a href="https://lkml.org/" target="_blank" rel="noopener">kernel mailing list archives</a> and questioning your life choices.</p>
<h2 id="try-it">Try It<a href="#try-it" class="anchor" aria-label="Link to this section">§</a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// chaos.c
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">exp</span> <span class="o">=</span> <span class="n">argc</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="o">?</span> <span class="nf">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">:</span> <span class="mi">50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">1ULL</span> <span class="o">&lt;&lt;</span> <span class="n">exp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;malloc(2^%d)...</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">exp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Refused.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Got %p. Press Enter to touch it.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">getchar</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">memset</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="mh">0xFF</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Survived?</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Run it, watch <code>dmesg</code>, and when the OOM killer takes out Firefox instead of your test program, that&rsquo;s on you.</p>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li><a href="https://www.kernel.org/doc/Documentation/vm/overcommit-accounting" target="_blank" rel="noopener">Overcommit accounting</a>, Linux kernel documentation.</li>
<li><a href="https://github.com/torvalds/linux/blob/master/mm/oom_kill.c" target="_blank" rel="noopener">oom_kill.c — OOM killer source</a>, Linux kernel.</li>
<li><a href="https://lwn.net/Articles/104185/" target="_blank" rel="noopener">Re-thinking memory overcommit</a>, LWN.net.</li>
<li><a href="https://lwn.net/Articles/517465/" target="_blank" rel="noopener">Adding the zero page</a>, LWN.net.</li>
<li><a href="https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details" target="_blank" rel="noopener">x86-64 virtual address space details</a>, Wikipedia.</li>
<li><a href="https://www.etalabs.net/overcommit.html" target="_blank" rel="noopener">Overcommit and fork()</a>, etalabs.net.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Circular Imports and TYPE_CHECKING</title>
      <link>https://www.swanbeaujard.com/posts/python-circular-imports-and-type_checking/</link>
      <pubDate>Tue, 28 Jan 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/python-circular-imports-and-type_checking/</guid>
      <description>The typing.TYPE_CHECKING constant satisfies static type checkers without running imports at runtime. No more circular import failures.</description>
      <content:encoded><![CDATA[<p><code>TYPE_CHECKING</code> is a constant from the <code>typing</code> module. It is <code>True</code> during type checks and <code>False</code> at runtime. This means you can import classes for type hints without causing circular imports at runtime.</p>
<p>When you use an import inside an <code>if TYPE_CHECKING:</code> block, Python skips it at runtime. Yet type checkers like <code>mypy</code> or <code>pyright</code> will see it for verifying your annotations.</p>
<h2 id="basic-example">Basic Example<a href="#basic-example" class="anchor" aria-label="Link to this section">§</a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># a.py</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TYPE_CHECKING</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="kn">from</span> <span class="nn">b</span> <span class="kn">import</span> <span class="n">B</span>  <span class="c1"># This import won&#39;t run at runtime</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">link_to_b</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="s1">&#39;B&#39;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Linking A with B&#39;</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># b.py</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">B</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">pass</span>
</span></span></code></pre></div><p>Here, <code>A</code> refers to <code>B</code> for type hints without importing it during runtime. That helps avoid circular imports.</p>
<h2 id="usage-in-an-orm">Usage in an ORM<a href="#usage-in-an-orm" class="anchor" aria-label="Link to this section">§</a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># models.py</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TYPE_CHECKING</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">Column</span><span class="p">,</span> <span class="n">Integer</span><span class="p">,</span> <span class="n">ForeignKey</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">relationship</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">database_setup</span> <span class="kn">import</span> <span class="n">Base</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="kn">from</span> <span class="nn">other_models</span> <span class="kn">import</span> <span class="n">RelatedModel</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MyModel</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">  <span class="n">__tablename__</span> <span class="o">=</span> <span class="s1">&#39;my_model&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">id</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">related_id</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">ForeignKey</span><span class="p">(</span><span class="s1">&#39;related_model.id&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># For type hints only:</span>
</span></span><span class="line"><span class="cl">  <span class="n">related_model</span><span class="p">:</span> <span class="s1">&#39;RelatedModel&#39;</span> <span class="o">=</span> <span class="n">relationship</span><span class="p">(</span><span class="s1">&#39;RelatedModel&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>This prevents runtime loops, because the <code>if TYPE_CHECKING:</code> block loads only for type analysis.</p>
<h2 id="why-use-it">Why Use It<a href="#why-use-it" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>Prevents circular imports by avoiding runtime loops between modules.</li>
<li>Improves readability by separating type hints from the normal import flow.</li>
<li>Helps type checkers by allowing tools to verify your types.</li>
</ul>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li><a href="https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING" target="_blank" rel="noopener">typing.TYPE_CHECKING</a>, Python documentation.</li>
<li><a href="https://peps.python.org/pep-0484/#runtime-or-type-checking" target="_blank" rel="noopener">PEP 484 — Type Hints: Runtime or type checking?</a>, python.org.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Code Migration with LLMs</title>
      <link>https://www.swanbeaujard.com/posts/llm-driven-code-migration/</link>
      <pubDate>Tue, 21 Jan 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/llm-driven-code-migration/</guid>
      <description>A file-by-file LLM transformation pipeline with automated merge requests and iterative prompt refinement, applied to a production framework migration.</description>
      <content:encoded><![CDATA[<p>At my previous company, we used an older GraphQL framework with limited features.
For the team owning the code, it was like having a tiny bicycle that could not go very fast or handle rough roads.
They needed to move faster and add new features without getting stuck on minor details.
Despite being part of that team, I saw an opportunity to lead this migration so my teammates could see how they might take on similar projects in the future.</p>
<p>I also wanted this process to include everyone on our team.
The idea was not just to solve a problem but to show that a big impact can come from stepping up and upgrading something that holds everyone back.</p>
<h2 id="method">Method<a href="#method" class="anchor" aria-label="Link to this section">§</a></h2>
<h3 id="finding-files-to-update">Finding Files to Update<a href="#finding-files-to-update" class="anchor" aria-label="Link to this section">§</a></h3>
<p>Our old calls were scattered across many files in our codebase.
I wrote a script to walk through every file, looking for any place that used the old GraphQL framework call.
I stored those files so I could process them one by one.</p>
<h3 id="using-llama-to-transform-the-calls">Using Llama to Transform the Calls<a href="#using-llama-to-transform-the-calls" class="anchor" aria-label="Link to this section">§</a></h3>
<p>I told the LLM:</p>
<blockquote>
<p>&ldquo;Transform the old code from framework A to framework B. Here are some placeholder examples to guide you:
<strong>Old code</strong>:</p>
<pre tabindex="0"><code>oldFrameworkCall({ query: &#39;...&#39; })
</code></pre><p><strong>New code</strong>:</p>
<pre tabindex="0"><code>newFrameworkCall({ query: &#39;...&#39; })
</code></pre><p>…and so on.&rdquo;</p>
</blockquote>
<p>I passed one file at a time. The LLM received the entire file but was asked to replace only the parts related to the GraphQL call. This way, I kept the rest of our code safe from changes I did not intend.</p>
<h3 id="creating-merge-requests">Creating Merge Requests<a href="#creating-merge-requests" class="anchor" aria-label="Link to this section">§</a></h3>
<p>Once the LLM produced the new version of each file, our script packaged the changes into a separate branch, then opened a Merge Request.
This let the team review the changes in small chunks, rather than one massive update. We could roll out fixes at a steady pace.</p>
<h3 id="testing-and-iterating">Testing and Iterating<a href="#testing-and-iterating" class="anchor" aria-label="Link to this section">§</a></h3>
<p>We reviewed every update before merging it.
In most cases, the LLM&rsquo;s changes worked well.</p>
<p><strong>Testing was critical</strong> — without a robust, well-tested environment to validate outputs, we wouldn&rsquo;t have been able to trust LLM-generated code at this scale without a lot of manual intervention.</p>
<p>When we encountered an edge case, we added a relevant example to our prompt and re-ran it. Over time, iterating on this feedback refined the prompt to handle even the trickiest parts of the code.</p>
<h2 id="analysis-why-this-approach-worked">Analysis: Why This Approach Worked<a href="#analysis-why-this-approach-worked" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>Changes were small and targeted, keeping the problem manageable.</li>
<li>Processing one file at a time allowed for rapid and parallel updates.</li>
<li>Small, incremental merge requests made code reviews easier.</li>
<li>Continuous iteration on edge cases improved accuracy over time.</li>
<li>Visible progress encouraged team collaboration and idea sharing.</li>
<li>The workflow demonstrated potential for wider application in code migrations.</li>
</ul>
<p>A service that automates these kinds of migrations could save companies significant time and money. In the past, teams have spent huge amounts of money just switching between versions or frameworks. A well-trained LLM can handle much of that work if you provide solid examples.</p>
<p>This might even be its own business opportunity someday, unifying legacy systems and elevating platform teams to drive enterprise-scale innovation.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Let the LLM Click the Button</title>
      <link>https://www.swanbeaujard.com/posts/llm-driven-autologin/</link>
      <pubDate>Tue, 14 Jan 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/llm-driven-autologin/</guid>
      <description>A numbered grid overlay lets an LLM return click coordinates from a single screenshot. A lightweight alternative to DOM parsing and vision models.</description>
      <content:encoded><![CDATA[<p>At my previous company, I proposed a simple way to automate login flows: use an LLM to tell us where to click, based on a numbered grid.
Below is how we arrived at that solution, why we avoided passing the entire DOM to the model, and how it compares to more robust systems.</p>
<h2 id="the-need-adaptive-ui-automation">The Need: Adaptive UI Automation<a href="#the-need-adaptive-ui-automation" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>We wanted to automate logins for tests and demos without dealing with frequent layout changes.</li>
<li>Traditional approaches tend to fail when element IDs change or the design is adjusted.</li>
</ul>
<h2 id="the-grid-based-method">The Grid-Based Method<a href="#the-grid-based-method" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li>We capture a screenshot of the login page.</li>
<li>We overlay a grid, for example 100×100, labeling each cell from 1 through 100.</li>
<li>We ask the LLM, &ldquo;Which cell should we click for the username field?&rdquo;</li>
<li>The LLM returns a cell number, which we convert to a screen coordinate and click.</li>
</ol>
<p>This requires only a short text description in the prompt, rather than full DOM or large images. It adapts well if the site&rsquo;s layout changes (just regenerate the grid and ask again). It also avoids heavy GPU workloads since it doesn&rsquo;t rely on a large vision model.</p>
<h2 id="the-cost-of-passing-the-full-dom">The Cost of Passing the Full DOM<a href="#the-cost-of-passing-the-full-dom" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>Large HTML trees often exceed the LLM&rsquo;s context window.</li>
<li>Prompt tokens become expensive for complex pages.</li>
<li>Minor changes to the DOM can break the model&rsquo;s element references.</li>
</ul>
<p>The grid overlay method avoids these pitfalls by focusing on a minimal text prompt.</p>
<h2 id="considering-omniparser">Considering OmniParser<a href="#considering-omniparser" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Microsoft&rsquo;s <a href="https://microsoft.github.io/OmniParser/" target="_blank" rel="noopener">OmniParser</a> [1] is a powerful system that detects clickable icons, captures semantics of UI elements, and guides vision-language models to perform accurate clicks. It is a strong choice for:</p>
<ul>
<li>Detailed understanding of icons and element roles.</li>
<li>Complex multi-step flows and wide coverage.</li>
</ul>
<p>We found OmniParser appealing but opted against it because:</p>
<ul>
<li>We had limited GPU capacity.</li>
<li>We only needed a simple login-click solution.</li>
<li>Deploying a specialized vision model was beyond our immediate scope.</li>
</ul>
<h2 id="results-and-unexpected-benefits">Results and Unexpected Benefits<a href="#results-and-unexpected-benefits" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>Multi-step logins work by repeating the screenshot-grid-prompt-click cycle.</li>
<li>Sudden UI changes are handled by generating a new overlay.</li>
<li>The approach is easy to prototype, requiring no advanced training or fine-tuning.</li>
</ul>
<p>If your primary goal is to handle logins or simple UI interactions without constant upkeep, a screenshot grid plus LLM instructions might be all you need. For complex, large-scale automation, a more specialized parser could be worth the investment.</p>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li><a href="https://microsoft.github.io/OmniParser/" target="_blank" rel="noopener">OmniParser — screen parsing for GUI agents</a>, Microsoft Research.</li>
<li><a href="https://github.com/browser-use/browser-use" target="_blank" rel="noopener">Browser Use — browser automation for AI agents</a>, GitHub.</li>
<li><a href="https://github.com/MinorJerry/WebVoyager" target="_blank" rel="noopener">WebVoyager — building an end-to-end web agent</a>, GitHub.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Pointer vs Value Receivers in Go</title>
      <link>https://www.swanbeaujard.com/posts/golang-pointer-vs-value-receiver/</link>
      <pubDate>Tue, 07 Jan 2025 00:00:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/golang-pointer-vs-value-receiver/</guid>
      <description>Selection criteria for Go method receivers: copy semantics, method sets, escape analysis, GC pressure, and concurrency implications, with benchmarks.</description>
      <content:encoded><![CDATA[<h2 id="overview">Overview<a href="#overview" class="anchor" aria-label="Link to this section">§</a></h2>
<p>In Go, methods can have either value receivers or pointer receivers. A receiver is what appears between the <code>func</code> keyword and the method name. It defines the type on which the method is allowed to be called. For example, if we have a <code>MyStruct</code> type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">MyStruct</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Field</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>We can attach a method to it in two ways. Either:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">m</span><span class="w"> </span><span class="nx">MyStruct</span><span class="p">)</span><span class="w"> </span><span class="nf">DoSomething</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// This uses a value receiver.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Or:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">m</span><span class="w"> </span><span class="o">*</span><span class="nx">MyStruct</span><span class="p">)</span><span class="w"> </span><span class="nf">DoSomething</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// This uses a pointer receiver.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Both choices affect how the compiler treats method calls. They also affect how data moves around in memory. These choices can have implications for concurrency, method sets, and performance. They can also influence how easy (or hard) it is to read your code.</p>
<h2 id="value-receivers">Value Receivers<a href="#value-receivers" class="anchor" aria-label="Link to this section">§</a></h2>
<h3 id="definition">Definition<a href="#definition" class="anchor" aria-label="Link to this section">§</a></h3>
<p>A value receiver means that when you call a method, Go copies the value. Inside the method, you see only that copy. If you modify fields within the method, it does not affect the caller’s copy.</p>
<h3 id="basic-example">Basic Example<a href="#basic-example" class="anchor" aria-label="Link to this section">§</a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Circle</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Radius</span><span class="w"> </span><span class="kt">float64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Area uses a value receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="nx">Circle</span><span class="p">)</span><span class="w"> </span><span class="nf">Area</span><span class="p">()</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">c</span><span class="p">.</span><span class="nx">Radius</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="c1">// This change does not affect the original Circle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mf">3.14</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">Radius</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">Radius</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>When we do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">circle</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Circle</span><span class="p">{</span><span class="nx">Radius</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">circle</span><span class="p">.</span><span class="nf">Area</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">circle</span><span class="p">.</span><span class="nx">Radius</span><span class="p">)</span><span class="w"> </span><span class="c1">// Still 5, not 0</span><span class="w">
</span></span></span></code></pre></div><p>We get the correct area for the radius of 5, and the original <code>Circle</code> stays the same.</p>
<h3 id="when-to-use-value-receivers">When to Use Value Receivers<a href="#when-to-use-value-receivers" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li><strong>Immutability</strong>: If you do not want your method to alter the original value, use a value receiver.</li>
<li><strong>Small Structs</strong>: If your struct is small, the cost of copying is low. There is no harm in passing by value, and it may be clearer to show the method does not change the struct.</li>
<li><strong>Cleaner Concurrency</strong>: If you pass small values by copy, you reduce the risk of data races. Using pointer receivers can cause shared state in code. This can lead to tricky race conditions if you modify fields.</li>
</ul>
<h3 id="pros">Pros<a href="#pros" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li>Simpler to read. It is obvious that the method does not mutate the struct.</li>
<li>Easier concurrency. You do not have to worry as much about shared state.</li>
<li>Predictable. The method sees only a copy and cannot break anything outside it.</li>
</ul>
<h3 id="cons">Cons<a href="#cons" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li>Might be expensive for large structs. A copy of a big struct can trigger more memory movement.</li>
<li>You cannot modify the original struct&rsquo;s fields directly from the method.</li>
</ul>
<h2 id="pointer-receivers">Pointer Receivers<a href="#pointer-receivers" class="anchor" aria-label="Link to this section">§</a></h2>
<h3 id="definition-1">Definition<a href="#definition-1" class="anchor" aria-label="Link to this section">§</a></h3>
<p>A pointer receiver uses a pointer to a struct rather than a direct copy. Inside the method, you see the original struct. You can change fields in place.</p>
<h3 id="basic-example-1">Basic Example<a href="#basic-example-1" class="anchor" aria-label="Link to this section">§</a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">MyStruct</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Field</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// UpdateField uses a pointer receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">m</span><span class="w"> </span><span class="o">*</span><span class="nx">MyStruct</span><span class="p">)</span><span class="w"> </span><span class="nf">UpdateField</span><span class="p">(</span><span class="nx">newVal</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">m</span><span class="p">.</span><span class="nx">Field</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">newVal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>When we do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">MyStruct</span><span class="p">{</span><span class="nx">Field</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">Field</span><span class="p">)</span><span class="w"> </span><span class="c1">// 10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">s</span><span class="p">.</span><span class="nf">UpdateField</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">Field</span><span class="p">)</span><span class="w"> </span><span class="c1">// 20</span><span class="w">
</span></span></span></code></pre></div><p>The value changes from 10 to 20 because we used a pointer.</p>
<h3 id="when-to-use-pointer-receivers">When to Use Pointer Receivers<a href="#when-to-use-pointer-receivers" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li>
<p><strong>Need to Mutate</strong>: If you want your method to modify the struct’s fields, a pointer receiver is the direct choice.</p>
</li>
<li>
<p><strong>Large Structs</strong>: If your struct is big, you might avoid the cost of copying by using a pointer. This can help performance when you call methods many times in tight loops.</p>
</li>
<li>
<p><strong>Method Chaining</strong>: Some developers like to return the pointer from a method so they can chain calls. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">m</span><span class="w"> </span><span class="o">*</span><span class="nx">MyStruct</span><span class="p">)</span><span class="w"> </span><span class="nf">SetField</span><span class="p">(</span><span class="nx">val</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">MyStruct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">m</span><span class="p">.</span><span class="nx">Field</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">val</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Then they call it like</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">my</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">MyStruct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">my</span><span class="p">.</span><span class="nf">SetField</span><span class="p">(</span><span class="mi">10</span><span class="p">).</span><span class="nf">SetField</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></div></li>
</ul>
<h3 id="pros-1">Pros<a href="#pros-1" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li>You can mutate the original data in methods.</li>
<li>You avoid extra copies for large structs.</li>
<li>You can do method chaining in your code.</li>
</ul>
<h3 id="cons-1">Cons<a href="#cons-1" class="anchor" aria-label="Link to this section">§</a></h3>
<ul>
<li>Leads to shared mutable state. This can cause confusion when multiple parts of the program share the same pointer.</li>
<li>Complicates concurrency if the same pointer is accessed in multiple goroutines without synchronization.</li>
<li>Might not always yield the best performance. Pointer usage can trigger escape analysis, and the compiler might move data to the heap in ways you do not expect.</li>
</ul>
<h2 id="method-sets-and-receivers">Method Sets and Receivers<a href="#method-sets-and-receivers" class="anchor" aria-label="Link to this section">§</a></h2>
<p>In Go, the receiver type affects which methods belong to which type. This can have subtle effects. For instance:</p>
<ul>
<li>
<p>If you define <code>func (m MyStruct) SomeMethod()</code>, you can call <code>SomeMethod()</code> on <code>MyStruct</code> values <strong>and</strong> on <code>*MyStruct</code> pointers. In the language of method sets, <code>SomeMethod</code> belongs to both <code>MyStruct</code> and <code>*MyStruct</code>.</p>
</li>
<li>
<p>If you define <code>func (m *MyStruct) SomeMethod()</code>, then <code>SomeMethod</code> is only in the method set of <code>*MyStruct</code>. That means that if you want <code>MyStruct</code> (the value type) itself to implement an interface requiring <code>SomeMethod</code>, you’re out of luck. <strong>However, in actual code</strong> you can still call a pointer-receiver method on an <em>addressable</em> <code>MyStruct</code> value. Go automatically takes the address of the value for you. For example, <code>myStructVar.SomeMethod()</code> is effectively <code>(&amp;myStructVar).SomeMethod()</code> if <code>myStructVar</code> is addressable.</p>
</li>
</ul>
<p>This fact leads some developers to prefer value receivers by default so that the methods are available to both <code>MyStruct</code> and <code>*MyStruct</code> method sets. That might make code simpler in some cases.</p>
<h2 id="performance-considerations">Performance Considerations<a href="#performance-considerations" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Performance is a big factor when choosing pointer vs value receivers. But it is not as simple as &ldquo;pointer = faster.&rdquo; Let’s go deeper:</p>
<ol>
<li>
<p><strong>Copying Overhead</strong>: Value receivers copy the whole struct. For small structs (say up to a few words in size), this cost is small. For large structs with many fields, copying can cost more.</p>
</li>
<li>
<p><strong>Compiler Optimizations</strong>: Go’s compiler can optimize some operations. If the struct is not large or does not escape to the heap, passing by value might still be efficient.</p>
</li>
<li>
<p><strong>Escape Analysis</strong>: If the struct or fields are passed around or used in certain ways, the compiler might move them to the heap. This can offset any gains you get from using pointers if you do not know how the compiler handles the memory.</p>
</li>
<li>
<p><strong>Garbage Collection</strong>: More pointers can mean more references for the garbage collector to track. Value copies do not create references in the same way. If you have a data structure with many pointers, it might be more taxing on the GC.</p>
</li>
<li>
<p><strong>Caching Behavior</strong>: Copying can sometimes be cheaper than pointer chasing in modern CPUs. The data you need may sit in the CPU cache. By copying it, you may get cache-friendly behavior. By using pointers that point to data in different parts of memory, you might cause more cache misses.</p>
</li>
</ol>
<p>It is best to measure rather than guess. Write benchmarks if performance is key. Often, the simplest approach (value receivers by default) works best until you see real performance issues.</p>
<h2 id="concurrency-concerns">Concurrency Concerns<a href="#concurrency-concerns" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Pointer receivers can create mutable shared state. If multiple goroutines have access to the same pointer, they can all modify the same data. This can cause data races or obscure bugs if you do not use locks or channels.</p>
<p>Value receivers reduce the risk of shared state because they copy the data. Each goroutine sees its own copy. Mutations to one copy do not affect others. But note that if the struct has pointer fields inside it, you are not always safe. The inner pointers can still lead to shared data.</p>
<p>If you need concurrency, aim for a design with minimal shared data. Pass copies around if possible. If you must share data, use <code>sync.Mutex</code>, <code>sync.RWMutex</code>, or channels. Or consider using the concurrency patterns that keep data local to each goroutine.</p>
<h2 id="example-large-struct-scenario">Example: Large Struct Scenario<a href="#example-large-struct-scenario" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Here is a more advanced example to illustrate a large struct. Suppose we have a type that holds a big array and we want to call a method many times.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">BigData</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Items</span><span class="w"> </span><span class="p">[</span><span class="mi">1000</span><span class="p">]</span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Process modifies each item in the array.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="o">*</span><span class="nx">BigData</span><span class="p">)</span><span class="w"> </span><span class="nf">Process</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">);</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>We use a pointer receiver to avoid copying the entire array when we call <code>Process()</code>. If we used a value receiver:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">b</span><span class="w"> </span><span class="nx">BigData</span><span class="p">)</span><span class="w"> </span><span class="nf">Process</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">);</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">Items</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Every call to <code>Process()</code> would copy 1000 integers, which can be expensive if we call it in a tight loop. That might slow down the program and create more garbage for the runtime to handle.</p>
<p>But if you do not need to mutate the original array, a value receiver might reduce complexity because each method call does not affect the original data. It depends on your use case.</p>
<h2 id="example-immutability-by-choice">Example: Immutability by Choice<a href="#example-immutability-by-choice" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Here is another example. Suppose we have a type that we want to treat as immutable. We can define methods on it with value receivers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Vector2D</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">X</span><span class="p">,</span><span class="w"> </span><span class="nx">Y</span><span class="w"> </span><span class="kt">float64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">v</span><span class="w"> </span><span class="nx">Vector2D</span><span class="p">)</span><span class="w"> </span><span class="nf">Add</span><span class="p">(</span><span class="nx">other</span><span class="w"> </span><span class="nx">Vector2D</span><span class="p">)</span><span class="w"> </span><span class="nx">Vector2D</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">Vector2D</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">X</span><span class="p">:</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">X</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">other</span><span class="p">.</span><span class="nx">X</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Y</span><span class="p">:</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Y</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">other</span><span class="p">.</span><span class="nx">Y</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">v</span><span class="w"> </span><span class="nx">Vector2D</span><span class="p">)</span><span class="w"> </span><span class="nf">Scale</span><span class="p">(</span><span class="nx">factor</span><span class="w"> </span><span class="kt">float64</span><span class="p">)</span><span class="w"> </span><span class="nx">Vector2D</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">Vector2D</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">X</span><span class="p">:</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">X</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">factor</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Y</span><span class="p">:</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Y</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">factor</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>We return new copies instead of updating the original in place. This approach helps us avoid confusion about which part of the code changed a <code>Vector2D</code>. We also avoid side effects. This pattern often works well with concurrency. You do not need locks if your data does not change.</p>
<h2 id="my-opinionated-stance">My Opinionated Stance<a href="#my-opinionated-stance" class="anchor" aria-label="Link to this section">§</a></h2>
<p>I prefer to start with value receivers. Then I switch to pointer receivers if I need one of these benefits:</p>
<ol>
<li><strong>Mutation</strong>: If it is natural for the method to mutate the receiver’s fields, I use a pointer. That is the only way to do it.</li>
<li><strong>Large Data</strong>: If the struct is large enough to cause performance issues with copying, I measure. If the overhead is real, I switch to pointers.</li>
</ol>
<p>I do not blindly choose pointers because they might break concurrency safety or create hidden side effects. I value easy-to-read code. Code that uses pointer receivers for no reason might cause confusion. People ask, &ldquo;Why is this code mutating a pointer?&rdquo; when the code does not need to mutate anything.</p>
<p>But I also want to warn that measuring performance is tricky. Microbenchmarks can mislead. Real-world performance depends on many variables. The overhead of copying might be small in practice, or the runtime might optimize away the overhead. If your type has references or slices inside, it might not be as big a copy as you think.</p>
<h2 id="common-gotchas">Common Gotchas<a href="#common-gotchas" class="anchor" aria-label="Link to this section">§</a></h2>
<h3 id="nil-receivers">Nil Receivers<a href="#nil-receivers" class="anchor" aria-label="Link to this section">§</a></h3>
<p>A pointer receiver can be <code>nil</code>. Your code should check for <code>nil</code> if you expect that to happen. Value receivers cannot be <code>nil</code>. This can lead to panic if you call a pointer receiver on a nil pointer.</p>
<h3 id="binding-methods">Binding Methods<a href="#binding-methods" class="anchor" aria-label="Link to this section">§</a></h3>
<p>Methods defined on a pointer receiver belong only to pointer types, while methods defined on a value receiver belong to both pointer and value types. That can be surprising if you try to call <code>myStruct.SomePointerMethod()</code> on a non-pointer variable.</p>
<h3 id="unintentional-escapes">Unintentional Escapes<a href="#unintentional-escapes" class="anchor" aria-label="Link to this section">§</a></h3>
<p>If you pass the pointer around, the data might escape to the heap. This can affect garbage collection. Sometimes passing by value is more efficient if you keep the data on the stack.</p>
<h3 id="shadowing">Shadowing<a href="#shadowing" class="anchor" aria-label="Link to this section">§</a></h3>
<p>If you do a method with a value receiver and then do <code>m.Field = 10</code> inside it, it can look like you changed the field. But you changed only the copy. The original data remains unchanged. This is a common source of bugs for new Go developers.</p>
<h2 id="final-advice">Final Advice<a href="#final-advice" class="anchor" aria-label="Link to this section">§</a></h2>
<ul>
<li>Use value receivers unless you have a clear reason to use pointers.</li>
<li>Switch to pointers if you need to mutate data or if profiling shows that copying large structs is too slow.</li>
<li>Be consistent. Use the same style across your codebase for the same type of struct.</li>
<li>Write benchmarks to check performance claims. Guessing about pointer performance can lead to poor design.</li>
</ul>
<p>Go is simple, but it gives you these choices. The best approach is to keep your code easy to read and easy to test. Think about concurrency from the start. Avoid side effects if you can. Then, only optimize if you find real performance bottlenecks.</p>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li><a href="https://go.dev/ref/spec#Method_sets" target="_blank" rel="noopener">The Go Programming Language Specification — Method sets</a>, go.dev.</li>
<li><a href="https://go.dev/wiki/CodeReviewComments#receiver-type" target="_blank" rel="noopener">Go Wiki: Receiver Type Guidelines</a>, go.dev.</li>
<li><a href="https://go.dev/doc/faq#methods_on_values_or_pointers" target="_blank" rel="noopener">Frequently Asked Questions — Should I define methods on values or pointers?</a>, go.dev.</li>
<li><a href="https://go.dev/doc/gc-guide#Eliminating_heap_allocations" target="_blank" rel="noopener">Go compiler escape analysis</a>, A Guide to the Go Garbage Collector, go.dev.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>OpenAI Realtime Voice, but 100x Cheaper</title>
      <link>https://www.swanbeaujard.com/posts/i-made-openai-100x-cheaper/</link>
      <pubDate>Mon, 11 Nov 2024 08:30:00 +0200</pubDate>
      <guid>https://www.swanbeaujard.com/posts/i-made-openai-100x-cheaper/</guid>
      <description>Local voice activity detection, buffered Whisper transcription, and LLM composition replicate OpenAI&#39;s realtime voice loop at roughly 1/100th of the cost.</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener"><figure class="markdown-image">
  <img src="./../../images/posts/2024-11/nomi-realtime-voice-demo.gif" alt="Nomi real-time voice demonstration" loading="lazy" decoding="async">
</figure></a></p>
<p>I started <a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener">Nomi</a> [1], an open-source AI assistant that leans heavily on voice. OpenAI&rsquo;s realtime services were too expensive to build on, so I had to find another way. This is the design I ended up with: a pipeline that reproduces the realtime voice loop at more than a hundred times lower cost. I used it for six months before OpenAI <a href="https://openai.com/index/introducing-the-realtime-api/" target="_blank" rel="noopener">released its realtime API</a> [2].</p>
<p>I&rsquo;ve kept this accessible rather than exhaustive; the full implementation is in the <a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener">repository</a> [1].</p>
<h2 id="the-cost-problem">The Cost Problem<a href="#the-cost-problem" class="anchor" aria-label="Link to this section">§</a></h2>
<p>The realtime API is priced at $100 per million input tokens and $200 per million output tokens, making it one of the most expensive LLM interfaces available.</p>
<p>Voice processing is expensive, so the first question is what you can keep off the paid path. In its realtime documentation, OpenAI itself suggests handling Voice Activity Detection (VAD) locally for control and efficiency. VAD detects when someone is actually speaking, so the system processes only relevant audio segments. Not streaming every audio chunk to the API is the single largest cost lever, and it is the foundation of this design.</p>
<h2 id="language-selection-go">Language Selection: Go<a href="#language-selection-go" class="anchor" aria-label="Link to this section">§</a></h2>
<p>I chose Go for its concurrency model and cross-platform builds. Voice processing requires concurrent, low-level work, and Go handles both well. Cross-platform builds had their difficulties; the <a href="https://github.com/nomihq/nomi/tree/e4d87aa486d0fcb7dc6fc987fd245c6ceeba794f/docker" target="_blank" rel="noopener">Dockerfiles in the repository</a> record the targets, with darwin/intel and windows/arm64 in progress.</p>
<p>I still prefer Python for RL and ML work, but Go&rsquo;s simplicity and constraints pay off when you ship a binary. Python (asyncio) and Rust (Tokio) have strong asynchronous primitives too; Go won on reliability and packaging, despite the low-latency limits I had already hit in <a href="https://github.com/nullswan/bpfsnitch" target="_blank" rel="noopener">bpfsnitch</a>. <a href="https://x.com/solomonstre/status/1842342755194048981" target="_blank" rel="noopener">Solomon Hykes&rsquo; rationale</a> for choosing Go for Docker covers similar ground [3].</p>
<p>Some components of Nomi may remain in Python due to ML model constraints; Go simplifies maintenance, while deploying Python is more complex.</p>
<h2 id="transcription-whisper-local-and-remote">Transcription: Whisper, Local and Remote<a href="#transcription-whisper-local-and-remote" class="anchor" aria-label="Link to this section">§</a></h2>
<p>I wanted reproducible results on my laptop, with the option to outsource processing when needed. <a href="https://github.com/ggerganov/whisper.cpp" target="_blank" rel="noopener">Whisper</a> [4] satisfies both: an open-source speech recognition system with high accuracy across languages, runnable on-device via whisper.cpp or through OpenAI&rsquo;s hosted Whisper API.</p>
<p>The hosted transcription API is accurate and modestly priced, but inference takes longer than the speech itself. So I run it in parallel to keep the rest of the system from blocking:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">text</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">transcribeAsync</span><span class="p">(</span><span class="nx">apiData</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">processText</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span></code></pre></div><h2 id="optimizing-the-transcription-loop">Optimizing the Transcription Loop<a href="#optimizing-the-transcription-loop" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Naive transcription calls were still too slow. The working configuration adds a few heuristics on top of the VAD: pause detection, periodic buffer flushes, and a double-buffer strategy that processes overlapping segments; the second buffer corrects first-buffer errors. Shorter chunks mean faster inference, so text processing keeps up with ongoing speech:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">primaryBuffer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">createBufferManager</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">secondaryBuffer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">createBufferManager</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></div><p>I then prioritize longer segments, assuming they come from a higher-quality source. The same multi-buffer structure should eventually support speaker-change detection; I haven&rsquo;t built that yet.</p>
<h2 id="system-architecture">System Architecture<a href="#system-architecture" class="anchor" aria-label="Link to this section">§</a></h2>
<p><a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener"><figure class="markdown-image">
  <img src="./../../images/posts/2024-11/nomi-high-level-architecture.png" alt="High-level architecture of the voice pipeline" loading="lazy" decoding="async">
</figure></a></p>
<p>Composing a VAD, a speech transcriber, and an LLM gives you a full voice round-trip comparable to the realtime API. The slowest component is the hosted Whisper call; total processing delay is typically 200–300 milliseconds, and effectively instant with on-device Whisper. The result is marginally slower than the realtime API but between 100 and 1,000 times cheaper. I tried text-to-speech and rejected it: the API latency was worse still.</p>
<p>In practice: the program listens continuously; once sufficient energy appears in the audio chunks, they are pushed to a buffer. After enough time elapses or a pause is detected, the buffer is released to the transcriber, which calls Whisper. The transcript, at end of speech or during pauses, is then passed to an LLM for the response.</p>
<h2 id="observations-on-transcription-quality">Observations on Transcription Quality<a href="#observations-on-transcription-quality" class="anchor" aria-label="Link to this section">§</a></h2>
<p>Telling Whisper the input language improves accuracy a lot. Even my English becomes acceptable, and it held up under heavy accents. On a Mac laptop, whisper.cpp is efficient enough that I could transcribe long recordings from work without difficulty.</p>
<p>Multi-speaker handling and speaker-change detection are still open problems here; some newer models ship this capability.</p>
<p>I left live transcription out of Nomi&rsquo;s core: most people are happy to press a button, and that makes the whole flow synchronous and much simpler. The VAD and transcription code is reusable as-is if you are building in this space.</p>
<h2 id="references">References<a href="#references" class="anchor" aria-label="Link to this section">§</a></h2>
<ol>
<li><a href="https://github.com/nomihq/nomi" target="_blank" rel="noopener">Nomi — open-source AI assistant</a>, GitHub.</li>
<li><a href="https://openai.com/index/introducing-the-realtime-api/" target="_blank" rel="noopener">Introducing the Realtime API</a>, OpenAI, 2024.</li>
<li><a href="https://x.com/solomonstre/status/1842342755194048981" target="_blank" rel="noopener">Solomon Hykes on choosing Go for Docker</a>, X, 2024.</li>
<li><a href="https://github.com/ggerganov/whisper.cpp" target="_blank" rel="noopener">whisper.cpp — port of OpenAI&rsquo;s Whisper</a>, G. Gerganov, GitHub.</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
