<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>llm on Saurabh Kumar</title><link>https://saurabh-kumar.com/tags/llm/</link><description>Recent content in llm on Saurabh Kumar</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright © 2025, Saurabh Kumar.</copyright><lastBuildDate>Mon, 19 Oct 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://saurabh-kumar.com/tags/llm/index.xml" rel="self" type="application/rss+xml"/><item><title>EmbeddingGemma 2</title><link>https://saurabh-kumar.com/til/1792411200/</link><pubDate>Mon, 19 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1792411200/</guid><description>&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/embeddinggemma-2/" rel="noopener external"&gt;EmbeddingGemma 2&lt;/a&gt; puts text, code, images, video and audio (or a mix) into one 768-dimension vector, from a 740M open model under Apache 2.0. Image, video and audio tokens are spliced into the text sequence and run through one encoder, so a text query can find a video moment or a scanned PDF page without captioning or transcribing first.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;It&amp;rsquo;s not the quality leader. Text is flat against v1; the gain is code (MTEB Code 68.8 to 78.7). Audio (49) and video (51) scores are modest. All numbers are Google&amp;rsquo;s own.&lt;/li&gt;&#10;&lt;li&gt;Every major vendor ships one-space multimodal embeddings now: &lt;a href="https://www.amazon.science/publications/amazon-nova-multimodal-embeddings-technical-report-and-model-card" rel="noopener external"&gt;Amazon Nova&lt;/a&gt;, Gemini Embedding 2, Voyage, Cohere, and the open Qwen3-VL-Embedding, which reports higher image and document scores but has no audio. Pick by constraint. EmbeddingGemma 2&amp;rsquo;s niche is open weights plus audio.&lt;/li&gt;&#10;&lt;li&gt;Matryoshka truncation isn&amp;rsquo;t free: its MMEB score falls from 59.0 at 768 dimensions to 45.7 at 128.&lt;/li&gt;&#10;&lt;li&gt;The interesting part is the system around the model. &lt;a href="https://github.com/ARahim3/DigUp" rel="noopener external"&gt;DigUp&lt;/a&gt; is local file search on it: one vector per photo, PDF page, video shot and 30 s of audio, each pointing back to its page or second, next to an FTS5 keyword index, because no embedding holds an invoice number exactly.&lt;/li&gt;&#10;&lt;li&gt;DigUp fuses the two by turning cosine into a per-query z-score (how far a match stands out for this query), gating on it, and adding a word-coverage bonus. It beat reciprocal-rank fusion on their evals (R@1 0.88 vs 0.73) and can honestly say &amp;ldquo;nothing found&amp;rdquo;. Small, author-run eval.&lt;/li&gt;&#10;&lt;li&gt;It also pins llama.cpp, records the model version in the index, and checks every upgrade against stored reference vectors. Vectors from two runtimes don&amp;rsquo;t mix, and nothing errors when they do.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Rampart</title><link>https://saurabh-kumar.com/til/1791720000/</link><pubDate>Sun, 11 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791720000/</guid><description>&lt;p&gt;Small on-device models are getting good enough to sit in front of the big ones. &lt;a href="https://ndstudio.gov/posts/say-hello-to-rampart" rel="noopener external"&gt;Rampart&lt;/a&gt; from the National Design Studio redacts PII in the browser before a prompt reaches any LLM: regexes with checksum or structural checks for cards (Luhn), SSNs, emails, URLs and IPs, plus a fine-tuned MiniLM for names, phones, account numbers, government IDs and street addresses.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;14.7 MB Q4 ONNX (~15 MB with tokenizer), 98.4% private-term recall across seven Latin-script languages. p50 is 3.9 ms on WebGPU but 12.6 ms on the default WASM backend. OpenAI&amp;rsquo;s Privacy Filter is ~2.8 GB; Presidio scores 65% on the same test set.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;guard.protect()&lt;/code&gt; swaps PII for placeholders like &lt;code&gt;[GIVEN_NAME_1]&lt;/code&gt;, and &lt;code&gt;guard.reveal()&lt;/code&gt; fills them back into the reply. The LLM never sees the name; the user still does.&lt;/li&gt;&#10;&lt;li&gt;Default-deny: every detected label is redacted except city, state and ZIP, so the assistant keeps coarse location without the street line.&lt;/li&gt;&#10;&lt;li&gt;Know the gaps: names in non-Latin scripts recall ~14% (Hindi 6%, Arabic 5%), and it scores 86.4% on a hostile-input suite. The repo calls it harm reduction, not perfect protection.&lt;/li&gt;&#10;&lt;li&gt;Same size class, different job: &lt;a href="https://cactuscompute.com/blog/whistle" rel="noopener external"&gt;Cactus Whistle&lt;/a&gt; is 16.9 MB speech-to-text in seven languages, 11 ms to first token on an M4 Pro CPU (Whisper base: 145 MB, 73 ms). It loads into the same engine as &lt;a href="https://huggingface.co/Cactus-Compute/needle3" rel="noopener external"&gt;Needle 3&lt;/a&gt;, their tool-calling model, so one call takes audio in and returns &lt;code&gt;set_lights(room=&amp;quot;kitchen&amp;quot;, on=false)&lt;/code&gt;. Voice commands for home automation with no cloud round trip.&lt;/li&gt;&#10;&lt;li&gt;The pattern: a tiny specialist at the edge does one narrow job (redact, transcribe, route), and the big model only sees what it needs. Both sets of benchmarks are vendor-reported.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Sebastian Raschka's history of text classifiers</title><link>https://saurabh-kumar.com/til/1791633601/</link><pubDate>Sat, 10 Oct 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791633601/</guid><description>&lt;p&gt;&lt;a href="https://magazine.sebastianraschka.com/p/classifier-history-and-jev" rel="noopener external"&gt;Sebastian Raschka&amp;rsquo;s history of text classifiers&lt;/a&gt; puts numbers on why &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener external"&gt;Jev&lt;/a&gt; feels new. On IMDb, bag-of-words + logistic regression gets 89.9%, a fine-tuned ModernBERT about 95%, and Jev 96.5% zero-shot for $0.65 across 25k reviews. Under the hood it is likely still a pretrained transformer with a one-number head that scores each answer option. The hard part is broad training data and calibrated probabilities. What changes is the cost of starting: no labelling, training or hosting per task. Specialists still win on huge volume, the last point of accuracy, or data that can&amp;rsquo;t leave your network. Within a month &lt;a href="https://developers.openai.com/api/docs/guides/decisions" rel="noopener external"&gt;OpenAI&lt;/a&gt; and &lt;a href="https://commandline.microsoft.com/microsoft-decision-1-model-foundry/" rel="noopener external"&gt;Microsoft&lt;/a&gt; shipped the same yes/no, choice and score contract.&lt;/p&gt;&#10;</description></item><item><title>baidu/Unlimited-OCR</title><link>https://saurabh-kumar.com/til/1782475200/</link><pubDate>Fri, 26 Jun 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1782475200/</guid><description>&lt;p&gt;&lt;a href="https://github.com/baidu/Unlimited-OCR" rel="noopener external"&gt;baidu/Unlimited-OCR&lt;/a&gt; (&lt;a href="https://arxiv.org/abs/2606.23050" rel="noopener external"&gt;paper&lt;/a&gt;) parses dozens of pages in one forward pass by fixing the real bottleneck in end-to-end OCR.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The problem: an OCR decoder types out the page token-by-token, and its KV cache grows unbounded with output length — so memory climbs and speed decays the longer the doc, forcing page-by-page loops that wipe memory each step.&lt;/li&gt;&#10;&lt;li&gt;The trick (Reference Sliding Window Attention): split context into two zones with different retention. Vision/prompt tokens stay fully visible and pinned forever (the &amp;ldquo;source book&amp;rdquo;); each token attends to only the last 128 of its own outputs (the &amp;ldquo;last few words you wrote&amp;rdquo;). KV cache becomes a fixed-size queue, so memory and TPS stay flat regardless of output length.&lt;/li&gt;&#10;&lt;li&gt;The non-obvious part: discarding history &lt;em&gt;improves&lt;/em&gt; accuracy (+6% on OmniDocBench, beating Qwen2.5-VL-72B at 3B/0.5B-active) — full attention can diverge on long dense output, and pinned vision tokens never blur.&lt;/li&gt;&#10;&lt;li&gt;Cheap to build: freeze DeepSeek-OCR&amp;rsquo;s encoder, fine-tune only the decoder with the swapped attention.&lt;/li&gt;&#10;&lt;li&gt;Generalizes past OCR: separate &lt;em&gt;what you reference&lt;/em&gt; from &lt;em&gt;what you remember&lt;/em&gt;.&lt;/li&gt;&#10;&lt;li&gt;Ran it on an M1 Mac (32GB) with R-SWA intact, no NVIDIA. The official HF model (&lt;code&gt;trust_remote_code&lt;/code&gt;) already ships R-SWA as a &lt;strong&gt;pure-PyTorch eager ring buffer&lt;/strong&gt; (&lt;code&gt;SlidingWindowLlamaAttention&lt;/code&gt;, picked via &lt;code&gt;attn_implementation=&amp;quot;eager&amp;quot;&lt;/code&gt;) — not buried in a CUDA/FlashAttention kernel. So the &amp;ldquo;port&amp;rdquo; was just stripping &lt;code&gt;.cuda()&lt;/code&gt;/&lt;code&gt;torch.autocast(&amp;quot;cuda&amp;quot;)&lt;/code&gt; and running bf16 on MPS; zero MPS op-gaps hit. bf16 works on MPS in torch 2.12.&lt;/li&gt;&#10;&lt;li&gt;Proof R-SWA is live: instrument the KV cache during decode — on a 540-token page it plateaus at &lt;code&gt;prefill(277) + window(128) = 405&lt;/code&gt; and never grows (full MHA would hit 817). That constant-cache assertion + Distinct-35≈1.0 are the decisive signals, not edit distance (too noisy vs a single reference).&lt;/li&gt;&#10;&lt;li&gt;Gotcha: feeding all pages to one &lt;code&gt;generate()&lt;/code&gt; (&lt;code&gt;infer_multi&lt;/code&gt;) &lt;strong&gt;loops at the document level&lt;/strong&gt; — the 128-token decode window forgets the doc was already transcribed while the vision prefix still shows every page, so it restarts from page 1. Fix = one &lt;code&gt;generate()&lt;/code&gt; per page (matches official &lt;code&gt;infer.py&lt;/code&gt; &lt;code&gt;PAGE_SIZE=1&lt;/code&gt;). Accuracy then lands at/below gemini-vs-docai inter-annotator noise (~0.31 mean edit). ~35–55 s/page on M1.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>How Prompt Caching Works</title><link>https://saurabh-kumar.com/articles/2026/06/how-prompt-caching-works/</link><pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/06/how-prompt-caching-works/</guid><description>&lt;p&gt;Today I learned how prompt caching actually works under the hood in LLM APIs&#10;(Anthropic, OpenAI, Bedrock, etc.), and why it makes long agent loops 5–10×&#10;cheaper and noticeably faster.&lt;/p&gt;&#10;&lt;h2 id="the-problem-it-solves"&gt;The problem it solves&lt;/h2&gt;&#10;&lt;p&gt;In an agent / chat loop, every request to the model looks almost identical to&#10;the previous one:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The same big &lt;strong&gt;system prompt&lt;/strong&gt; (rules, tool descriptions, skills…)&lt;/li&gt;&#10;&lt;li&gt;The same set of &lt;strong&gt;tool definitions&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;The same &lt;strong&gt;history&lt;/strong&gt; of past user/assistant/tool messages, with only a few&#10;new tokens appended at the end&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Without caching, the provider has to re-tokenize and run a full attention&#10;pre-fill over all of that prefix on every turn. For a 100k-token context, that&#10;prefill dominates both cost and latency.&lt;/p&gt;&#10;&lt;h2 id="what-gets-cached-the-kv-state"&gt;What gets cached: the KV state&lt;/h2&gt;&#10;&lt;p&gt;When a transformer processes a prompt, it computes a &lt;strong&gt;key/value tensor pair&#10;for every token in every attention layer&lt;/strong&gt;. That KV state is what attention&#10;queries against on the next decoded token.&lt;/p&gt;&#10;&lt;p&gt;Prompt caching is, mechanically, &amp;ldquo;save those KV tensors for a prefix on the&#10;server, and reuse them next time the same prefix shows up.&amp;rdquo; Output tokens are&#10;never cached — only the prompt-side KV state.&lt;/p&gt;&#10;&lt;h2 id="the-lifecycle"&gt;The lifecycle&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Mark a cache breakpoint.&lt;/strong&gt; The client places a &lt;code&gt;cache_control&lt;/code&gt; marker&#10;(Anthropic) or relies on automatic prefix detection (OpenAI). Everything&#10;&lt;em&gt;before&lt;/em&gt; the marker is the cacheable prefix.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;First request → cache write.&lt;/strong&gt; The provider hashes the cacheable prefix&#10;(model id + tools + messages + cache key, byte-exact), runs the prefill,&#10;and stores the resulting KV tensors keyed by that hash. You&amp;rsquo;re billed&#10;&lt;code&gt;cache_write&lt;/code&gt; tokens, usually a bit &lt;em&gt;more&lt;/em&gt; than normal input&#10;(~1.25× for Anthropic 5-minute cache, ~2× for the 1-hour &amp;ldquo;extended&amp;rdquo;&#10;cache).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Next request → cache read.&lt;/strong&gt; If the new request starts with exactly the&#10;same prefix, the provider looks up the hash, loads the stored KV state,&#10;and only runs fresh compute for the suffix after the last hit. You&amp;rsquo;re&#10;billed &lt;code&gt;cache_read&lt;/code&gt; tokens — often &lt;strong&gt;~10% of the normal input price&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;TTL.&lt;/strong&gt; Entries expire after a TTL (Anthropic: 5 min default, 1 hour&#10;opt-in; OpenAI: a few minutes, automatic). Each hit typically refreshes&#10;the TTL, so an active session keeps the cache warm.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="what-invalidates-the-cache"&gt;What invalidates the cache&lt;/h2&gt;&#10;&lt;p&gt;The hash is over the &lt;strong&gt;exact bytes&lt;/strong&gt; of the prefix. Anything that changes&#10;those bytes forces a fresh &lt;code&gt;cache_write&lt;/code&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Editing the system prompt (even reordering sentences or adjusting&#10;whitespace)&lt;/li&gt;&#10;&lt;li&gt;Adding, removing, or reordering a tool definition&lt;/li&gt;&#10;&lt;li&gt;Inserting/editing/reordering an earlier message in the history&lt;/li&gt;&#10;&lt;li&gt;Switching model id or provider&lt;/li&gt;&#10;&lt;li&gt;Changing the cache key (some providers expose one for tenant isolation)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The corollary: to maximize hit rate, agents should &lt;strong&gt;only append at the end&lt;/strong&gt;&#10;and keep system prompt and tools byte-stable across turns.&lt;/p&gt;&#10;&lt;h2 id="reading-the-usage-block"&gt;Reading the usage block&lt;/h2&gt;&#10;&lt;p&gt;Most providers return a usage object on every response with at least these&#10;fields:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;312&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// fresh, non-cached prompt tokens this turn&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;845&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// generated tokens&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cacheRead&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;47210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// prompt tokens served from cache&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cacheWrite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// prompt tokens written to cache this turn&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A useful per-turn metric is the &lt;strong&gt;cache hit ratio&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;cacheHitRate = cacheRead / (input + cacheRead + cacheWrite)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;0%&lt;/code&gt; → nothing reused. Normal on the very first turn, or right after the&#10;prefix changed (tool list updated, history compacted, model swapped).&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;70–95%&lt;/code&gt; → healthy steady state for a long agent loop.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cacheWrite &amp;gt; 0, cacheRead = 0&lt;/code&gt; → you just primed the cache; the next turn&#10;should be cheap if you don&amp;rsquo;t break the prefix.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="pricing-back-of-the-envelope"&gt;Pricing back-of-the-envelope&lt;/h2&gt;&#10;&lt;p&gt;Rough Anthropic numbers (Claude Sonnet, illustrative):&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Token type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Price multiplier vs. input&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1×&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cache_write&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~1.25× (5 min) / ~2× (1h)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cache_read&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~0.1×&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~5×&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;So a 100k-token prefix that&amp;rsquo;s hit 20 times across a session costs roughly:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;1 × 1.25 × 100k (one write)&#10;+ 19 × 0.1 × 100k (nineteen reads)&#10;= 1.25 × 100k + 1.9 × 100k&#10;≈ 3.15 × 100k input-equivalent&#10;&#10;vs. uncached:&#10;20 × 1 × 100k = 20 × 100k&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A ~6× saving on the prompt side, plus a big latency win because the server&#10;skips the prefill.&lt;/p&gt;&#10;&lt;h2 id="gotchas"&gt;Gotchas&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Minimum prefix size.&lt;/strong&gt; Most providers require the cacheable prefix to be&#10;above some threshold (e.g. 1024 tokens for Anthropic) before caching kicks&#10;in at all.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Position of the breakpoint matters.&lt;/strong&gt; Putting it after volatile content&#10;(like the latest user message) means nothing ever gets reused.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Tool definitions are part of the prefix.&lt;/strong&gt; Dynamically generated tool&#10;lists with shuffled order will silently destroy your hit rate.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Streaming vs. non-streaming&lt;/strong&gt; use the same cache; cache fields show up&#10;in the final usage event.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cost accounting:&lt;/strong&gt; &lt;code&gt;cache_write&lt;/code&gt; is more expensive than &lt;code&gt;input&lt;/code&gt;, so a&#10;flaky prefix that constantly re-writes can be &lt;em&gt;worse&lt;/em&gt; than no caching at&#10;all.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>My Local LLM On A 32 GB M1 Pro</title><link>https://saurabh-kumar.com/articles/2026/06/my-local-llm-on-a-32-gb-m1-pro/</link><pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/06/my-local-llm-on-a-32-gb-m1-pro/</guid><description>&lt;p&gt;My pi setup has a &lt;code&gt;fast&lt;/code&gt; mode: a slot I wired into my own config for the small, high-volume questions, pointed at Bedrock&amp;rsquo;s Haiku 4.5. It is a good model. Cheap, responsive, and it answers the kind of small, contained question I throw at it twenty times a day without complaining. It runs on third-party hardware, and those questions send code to a third party. I wanted to try running that slot locally.&lt;/p&gt;&#10;&lt;p&gt;None of that is a crisis. Bedrock is fast and it works. But the machine that could do the same job is sitting on my desk, and when Kyle Howells &lt;a href="https://ikyle.me/blog/2026/how-to-setup-a-local-coding-agent-on-macos" rel="noopener external"&gt;got Gemma 4 26B-A4B running locally on an M1 Max at 72 tokens per second&lt;/a&gt; and the &lt;a href="https://news.ycombinator.com/item?id=48507020" rel="noopener external"&gt;Hacker News thread&lt;/a&gt; filled up with people on smaller Macs reporting the same setup was workable, I wanted to know whether my own laptop could host that slot.&lt;/p&gt;&#10;&lt;p&gt;The catch is that my laptop is an M1 Pro with 32 GB of unified memory: half the RAM and roughly half the memory bandwidth of the machine in Kyle&amp;rsquo;s post. Copying his commands verbatim was never going to work. I needed to know what to change, in what order, and what to give up. So I did not copy the commands. I gave pi the article and asked it to build me a plan for my machine, and to interrogate me before it wrote a line.&lt;/p&gt;&#10;&lt;h2 id="i-handed-the-guide-to-an-llm-and-made-it-grill-me"&gt;I Handed The Guide To An LLM And Made It Grill Me&lt;/h2&gt;&#10;&lt;p&gt;The whole thing started with one message. Typos and all, this is what I actually sent:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Act local AI setup expert working on M1 silicon with 32GB ram. Read though [the article] and also pull all the comments on this article on news.ycombinator.com article id 48507020. I&amp;rsquo;ve ollama and muse already installed there are models installed as well. Prep and action plan to set this up in the most performant and maintaibale way. Not sure quite sure if i need to uninstall previous ollama app and it&amp;rsquo;s modetls. Create and write a plan after analysing things. &lt;code&gt;/grill-me&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The &lt;code&gt;/grill-me&lt;/code&gt; at the end is a small skill in my pi setup. It is two paragraphs of instructions that tell the model to walk down the decision tree one node at a time, recommend an answer at each step, and wait for my reply before moving on. It is not magic. It is a way of forcing the conversation I would otherwise have with myself in a notebook, except the model will not let me skip a step.&lt;/p&gt;&#10;&lt;p&gt;The interview was mostly the model proposing and me confirming in monosyllables. My replies, verbatim, were things like:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;i would prefer ~/code/localLLM&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A, also note i&amp;rsquo;ve &amp;ldquo;llm&amp;rdquo; cli already installed, and alias would be nice.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;let&amp;rsquo;s go with recommendation, write the plan.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;That is the entire texture of it. A simple opening prompt that points at an existing guide and names my constraints, then a handful of one-word answers steering the branches. The model did the reading (the article and the full HN thread) and the arithmetic. I made the calls.&lt;/p&gt;&#10;&lt;p&gt;And it pushed back, which is the point. When I leaned toward the 65 K context window from the article, it did the memory math and showed me I would be swapping inside the first generation on 32 GB, so we settled on 32 K. When I waffled on keeping Ollama &amp;ldquo;just in case,&amp;rdquo; it pointed out I was at 89 % disk with a thirteen-gigabyte Ollama models directory I was not using. When I asked for an alias, it caught that &lt;code&gt;llm&lt;/code&gt; was already taken by &lt;a href="https://llm.datasette.io" rel="noopener external"&gt;Simon Willison&amp;rsquo;s tool&lt;/a&gt; and proposed &lt;code&gt;lcl&lt;/code&gt; instead. None of these are insights I lacked. They are checks I would have skipped because I was already mentally on the next step.&lt;/p&gt;&#10;&lt;p&gt;By the end there was a written record of every decision and its reason, in the repo, not in my head. That is where it should be.&lt;/p&gt;&#10;&lt;h2 id="the-decisions-that-fell-out"&gt;The Decisions That Fell Out&lt;/h2&gt;&#10;&lt;p&gt;The interview converged on a short list, and the reasoning behind each one is worth more than the choice itself.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Model: Gemma 4 26B-A4B, the MoE, not a dense model.&lt;/strong&gt; The single most important fact about local inference on a Mac is not how much RAM you have, it is how fast it can be read. Generation is bottlenecked on memory bandwidth. The M1 Max in Kyle&amp;rsquo;s article runs at 400 GB/s; my M1 Pro at roughly 200. Everything else being equal, I should expect about half his throughput. His 72 tokens per second becomes my 36. That is still faster than I read. It is also why a mixture-of-experts model is the right answer here: Gemma 4 26B-A4B holds 26 billion parameters but activates only four billion per token. The full weights sit in RAM, but each forward pass streams a fraction of them. The dense 27B sibling has the same disk footprint and meaningfully worse generation rate. It is probably smarter. It does not matter, because it is too slow for the slot I am filling.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Speculative decoding, on.&lt;/strong&gt; Gemma 4 ships an MTP draft head, a tiny network that predicts the next several tokens cheaply, which the main model accepts or rejects in one pass. When acceptance is high, you get most of those tokens almost for free. Kyle measured +24 % on his machine. The HN thread is full of caveats about acceptance rates and MoE models benefiting less. None of it changes the decision: turn it on, measure, keep what you get.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Context: 32 K, not 65 K.&lt;/strong&gt; The arithmetic the model made me do. A 65 K KV cache plus 16 GB of weights plus a draft model does not leave enough headroom on 32 GB once Chrome and an IDE are open.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;mise, not a shell script.&lt;/strong&gt; The article uses &lt;code&gt;huggingface-cli&lt;/code&gt; to fetch the GGUFs and a hand-written tmux script to launch the server. Both work. Both are also infrastructure you now own and will forget you configured. llama.cpp&amp;rsquo;s &lt;code&gt;-hf&lt;/code&gt; flag does the download without a Python virtualenv. mise&amp;rsquo;s &lt;code&gt;github:&lt;/code&gt; backend installs the prebuilt Metal binary and rolls it forward with &lt;code&gt;mise upgrade&lt;/code&gt;. The tmux script becomes a few named tasks in a &lt;code&gt;.mise.toml&lt;/code&gt;. The choice is not about elegance, it is about which moving piece you are willing to own. I already own mise.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Ollama goes.&lt;/strong&gt; I had it from earlier experiments, with &lt;code&gt;gemma4:latest&lt;/code&gt; and a couple of other models taking 13 GB, plus the app idling in the background on port 11434. Ollama is a wrapper around llama.cpp. Once you run &lt;code&gt;llama-server&lt;/code&gt; directly, the wrapper is in the way. So the app, the models directory, and the port all go, and the 13 GB comes back on a disk that was at 89 %.&lt;/p&gt;&#10;&lt;h2 id="the-setup-start-to-finish"&gt;The Setup, Start To Finish&lt;/h2&gt;&#10;&lt;p&gt;This is the reproducible part. You can follow it by hand, or point an LLM at this section and let it do the work. That is roughly how it got built in the first place. The numbers below are the actual results on my M1 Pro, not estimates.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;1. Decommission Ollama&lt;/strong&gt; (skip if you never had it):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;osascript -e &lt;span class="s1"&gt;&amp;#39;tell application &amp;#34;Ollama&amp;#34; to quit&amp;#39;&lt;/span&gt; 2&amp;gt;/dev/null&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkill -f Ollama&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf ~/.ollama /Applications/Ollama.app &lt;span class="c1"&gt;# reclaimed 13 GB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. Install llama.cpp via mise.&lt;/strong&gt; The prebuilt arm64 binary has Metal and Accelerate baked in:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mise use --global &lt;span class="s1"&gt;&amp;#39;github:ggml-org/llama.cpp@latest&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;llama-server --help &lt;span class="p"&gt;|&lt;/span&gt; grep -E -- &lt;span class="s1"&gt;&amp;#39;--spec-type|-hf&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# confirm MTP + HF download support&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The one thing worth verifying is that &lt;code&gt;--spec-type draft-mtp&lt;/code&gt; is present, since MTP support landed only recently. On my install (build &lt;code&gt;b9631&lt;/code&gt;) it was there. If it ever is not, a source build with &lt;code&gt;-DGGML_METAL=ON&lt;/code&gt; is the five-minute fallback.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;3. A project with mise tasks&lt;/strong&gt; at &lt;code&gt;~/code/localLLM/.mise.toml&lt;/code&gt;. The &lt;code&gt;[env]&lt;/code&gt; block makes the model cache live with the project; the tasks replace the shell script:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;LLAMA_CACHE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{{ config_root }}/models&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;github:ggml-org/llama.cpp&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Start llama-server (Gemma 4 26B-A4B + MTP) in tmux&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;tmux has-session -t llama 2&amp;gt;/dev/null &amp;amp;&amp;amp; { echo running; exit 0; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;tmux new-session -d -s llama -c &amp;#34;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; \\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&lt;/span&gt;&lt;span class="nx"&gt;llama-server&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-hf&lt;/span&gt; &lt;span class="nx"&gt;unsloth&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gemma-4-26B-A4B-it-GGUF&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;UD-Q4_K_XL&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-hfd&lt;/span&gt; &lt;span class="nx"&gt;unsloth&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gemma-4-26B-A4B-it-GGUF&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Q8_0-MTP&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--spec-type&lt;/span&gt; &lt;span class="nx"&gt;draft-mtp&lt;/span&gt; &lt;span class="nx"&gt;--spec-draft-n-max&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-ngl&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt; &lt;span class="nx"&gt;-fa&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;-c&lt;/span&gt; &lt;span class="mi"&gt;32768&lt;/span&gt; &lt;span class="nx"&gt;--parallel&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--reasoning&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt; &lt;span class="nx"&gt;--reasoning-budget&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--temp&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="nx"&gt;--repeat-penalty&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt; &lt;span class="nx"&gt;--repeat-last-n&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--host&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="nx"&gt;--port&lt;/span&gt; &lt;span class="mi"&gt;8082&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;tee&lt;/span&gt; &lt;span class="nx"&gt;-a&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;llama-server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmux kill-session -t llama 2&amp;gt;/dev/null &amp;amp;&amp;amp; echo stopped || echo &amp;#39;not running&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;curl -fsS http://127.0.0.1:8082/v1/models &amp;gt;/dev/null &amp;amp;&amp;amp; echo up || echo down&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A note on the port. The article uses 8080. On my machine 8080 and 8081 were already taken by Google &lt;code&gt;cloud-run&lt;/code&gt; processes, so the server silently answered on a port that was not mine until I noticed the responses carried a &lt;code&gt;Server: Google Frontend&lt;/code&gt; header. I moved everything to &lt;strong&gt;8082&lt;/strong&gt;. Check &lt;code&gt;lsof -i :8080&lt;/code&gt; before you assume a port is free.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;4. A one-key alias&lt;/strong&gt; in &lt;code&gt;~/.zshrc&lt;/code&gt;. &lt;code&gt;llm&lt;/code&gt; was taken, so &lt;code&gt;lcl&lt;/code&gt; for &amp;ldquo;local&amp;rdquo;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mise -C &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/code/localLLM&amp;#34;&lt;/span&gt; run &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl start &lt;span class="c1"&gt;# boot the server (first run downloads the model)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl status &lt;span class="c1"&gt;# is it up?&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl stop &lt;span class="c1"&gt;# free the ~22 GB of RAM&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;5. First boot and download.&lt;/strong&gt; &lt;code&gt;lcl start&lt;/code&gt; triggers the download through llama.cpp&amp;rsquo;s &lt;code&gt;-hf&lt;/code&gt;. The real footprint came in smaller than the article&amp;rsquo;s &amp;ldquo;~21 GB&amp;rdquo; estimate. The total was &lt;strong&gt;18 GB&lt;/strong&gt;: 16 GB for the Q4_K_XL main model, &lt;strong&gt;1.2 GB&lt;/strong&gt; for the MTP head (an auxiliary head, not a full second model), and a 441 MB multimodal projector that gets pulled automatically even though I do not need images. Add &lt;code&gt;--no-mmproj&lt;/code&gt; if you want to skip it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;6. Point pi at it.&lt;/strong&gt; A &lt;code&gt;local&lt;/code&gt; provider in &lt;code&gt;~/.pi/agent/models.json&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;providers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Local llama.cpp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;baseUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://127.0.0.1:8082/v1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;openai-completions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;apiKey&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;models&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Gemma 4 26B-A4B Q4 + MTP&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reasoning&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;contextWindow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;maxTokens&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cacheRead&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cacheWrite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And a &lt;code&gt;local&lt;/code&gt; mode in &lt;code&gt;modes.json&lt;/code&gt; pointing at it. The benchmark answers came back clean, so I gave it real work, and that is where it broke.&lt;/p&gt;&#10;&lt;h2 id="the-thinking-spiral"&gt;The Thinking Spiral&lt;/h2&gt;&#10;&lt;p&gt;The first real task I handed Gemma was trivial: rewrite a Hugo template so a list of tags renders as &amp;ldquo;tagged A, B and C&amp;rdquo; instead of &amp;ldquo;A B C&amp;rdquo;. A ten-line edit. Haiku does it in one tool call. Gemma read the file, started thinking, and never stopped. The session export shows a single reasoning block of &lt;strong&gt;32,000 characters&lt;/strong&gt;: it derived the correct off-by-one logic, then re-derived it, then second-guessed the whitespace, then re-read its own conclusion and started over. It never emitted the edit. I aborted it. I told it &amp;ldquo;you are going in loop.&amp;rdquo; It thought about that, too, and aborted again.&lt;/p&gt;&#10;&lt;p&gt;This is the failure mode the chat-window benchmarks hide. Gemma 4 IT ships with thinking on, and llama.cpp&amp;rsquo;s default reasoning budget is unlimited (&lt;code&gt;-1&lt;/code&gt;). In a chat that is fine; the model thinks, then answers. Inside an agent loop, where the next move is supposed to be a tool call, an unbounded thinking budget on a small model is a trap. There is nothing forcing the transition from reasoning to acting, and a 4B-active MoE is not big enough to find the exit on its own. The default sampling makes it worse: &lt;code&gt;--repeat-penalty&lt;/code&gt; defaults to &lt;code&gt;1.0&lt;/code&gt;, which is to say off, so nothing discourages the model from looping on its own reasoning.&lt;/p&gt;&#10;&lt;p&gt;My first instinct was the biggest hammer: turn thinking off entirely. It worked. But &amp;ldquo;it worked&amp;rdquo; is where most debugging stops and most of the understanding gets lost, so I made the model prove which flag was actually doing the work. I had pi reconstruct the exact failing scenario from the session export, same system prompt, same tools, same file, same prompt, and replay it against two server configs:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;--reasoning off --reasoning-budget 0 # A: no thinking&#10;--reasoning auto --reasoning-budget 1024 --repeat-penalty 1.1 # B: bounded thinking&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Same prompt, same machine, here is what came back:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Config&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Reasoning emitted&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Outcome&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Original (unbounded, no repeat penalty)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;32,000 chars&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;spiral, aborted, no edit&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A — reasoning off&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0 chars&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;clean &lt;code&gt;read&lt;/code&gt; → &lt;code&gt;edit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;B — bounded to 1024&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;518 chars&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;clean &lt;code&gt;read&lt;/code&gt; → &lt;code&gt;edit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The lesson is in the third row. Capping the budget alone stopped the spiral: the model thought for 518 characters, decided, and acted. So the bug was never &amp;ldquo;thinking is bad.&amp;rdquo; It was &amp;ldquo;&lt;em&gt;unbounded&lt;/em&gt; thinking is bad,&amp;rdquo; and &lt;code&gt;--reasoning off&lt;/code&gt; is just the most aggressive way to bound it to zero. Both configs produced the same edit, the same slightly-clumsy Hugo logic. The extra reasoning in config B bought nothing on this task; that is a 4B-active model hitting its ceiling, not a budget I can tune my way around.&lt;/p&gt;&#10;&lt;p&gt;Two more things I only learned by testing. &lt;code&gt;--repeat-penalty&lt;/code&gt; is an orthogonal fix and worth keeping regardless: at &lt;code&gt;1.0&lt;/code&gt; it is off, and it guards the &lt;em&gt;content&lt;/em&gt; against loops, not just the thinking. And &lt;code&gt;--reasoning off&lt;/code&gt; does not lobotomise the model the way the name suggests. I gave it a reasoning trap with thinking off, and it still worked the problem out, step by step, &lt;em&gt;in its visible answer&lt;/em&gt;, and got it right. What the flag removes is the separate, hidden, budget-less thinking phase, not the model&amp;rsquo;s ability to reason. For an agent loop that is the right trade: when reasoning happens it is visible in the content and bounded by the task, instead of a hidden phase that can run away.&lt;/p&gt;&#10;&lt;p&gt;So for the &lt;em&gt;fast&lt;/em&gt; slot I keep it off. The whole point of the slot is a quick, direct tool call; the test showed bounded thinking adds latency with no quality gain here, and if I want real reasoning I switch to a cloud mode. That is also why &lt;code&gt;&amp;quot;reasoning&amp;quot;&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; in the model config above, with thinking disabled at the server there is nothing for pi to render. The middle ground is real and now proven, though: if I ever stand up a &lt;code&gt;local-deep&lt;/code&gt; mode for harder offline work, &lt;code&gt;--reasoning auto --reasoning-budget 1024&lt;/code&gt; is a configuration I have watched stay out of the ditch. It just wants its own server on its own port, because &lt;code&gt;--reasoning off&lt;/code&gt; is a hard gate you cannot toggle back on per request.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;What it actually does&lt;/strong&gt;, measured on the M1 Pro:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Metric&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Result&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Generation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;34–36 tok/s&lt;/strong&gt; (predicted half of Kyle&amp;rsquo;s 72, got it)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Prompt ingestion&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;77 tok/s&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Time to first token&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~376 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MTP acceptance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;~65 %&lt;/strong&gt; (e.g. 1350 of 2090 drafted tokens accepted)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Half the bandwidth, half the tokens, just as predicted. The MTP acceptance beat what I feared. The article saw +24 % on a dense workload; here, two thirds of the drafted tokens landed.&lt;/p&gt;&#10;&lt;h2 id="how-i-actually-use-a-fast-model"&gt;How I Actually Use A Fast Model&lt;/h2&gt;&#10;&lt;p&gt;A &lt;code&gt;fast&lt;/code&gt; model in pi is not the thing I have a conversation with. It is the model that does the small, high-volume, latency-sensitive jobs around the edges of the real work, where Opus is overkill and the round-trip cost is what matters. Those are the jobs I would rather keep on my own machine, and the place where 36 tokens per second is plenty.&lt;/p&gt;&#10;&lt;p&gt;In my setup, that means:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;/answer&lt;/code&gt; and &lt;code&gt;Ctrl+.&lt;/code&gt;&lt;/strong&gt; — a &lt;a href="https://github.com/theskumar/agent-stuff/blob/main/extensions/answer.ts" rel="noopener external"&gt;small pi extension I run&lt;/a&gt; that grabs the last assistant message when it ends on a batch of clarifying questions, extracts them into structured JSON via a side LLM call, and opens a TUI so I can answer them one at a time. The extraction is a contained, schema-shaped task, the sort of thing a fast model handles well. It defaults to &lt;code&gt;claude-bridge/claude-haiku-4-5&lt;/code&gt; and is configurable at &lt;code&gt;~/.pi/agent/extensions/answer.config.json&lt;/code&gt;, the line I now point at &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Branch summaries and compaction.&lt;/strong&gt; When I navigate away from a branch on &lt;code&gt;/tree&lt;/code&gt;, or a session outgrows its context window, pi summarizes the work to carry forward. By default those calls run on the active session model, which means a &lt;code&gt;/tree&lt;/code&gt; hop can cost an Opus summary. A &lt;a href="https://github.com/theskumar/agent-stuff/blob/main/extensions/tree-fast-summary.ts" rel="noopener external"&gt;small extension I run&lt;/a&gt; reroutes both to whatever the &lt;code&gt;fast&lt;/code&gt; mode points at, so the throwaway housekeeping never touches the expensive model.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;/mode fast&lt;/code&gt;&lt;/strong&gt; for the genuinely small questions, the &amp;ldquo;what&amp;rsquo;s the flag for X&amp;rdquo; and &amp;ldquo;rename this&amp;rdquo; and quick grep-and-explain ones, where I switch the whole session to the fast slot and switch back.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;None of these need a frontier model. They need something that responds now and gets the structure right. That is the bar Gemma 4 has to clear to take the slot from Haiku.&lt;/p&gt;&#10;&lt;p&gt;So I gave it the slot. I went back and forth here. The cautious move was to add a separate &lt;code&gt;local&lt;/code&gt; mode, keep &lt;code&gt;fast&lt;/code&gt; on Haiku, and switch into the local one when I felt like testing it. I know how that goes: I would never remember to switch, and a week later I would have no data. So &lt;code&gt;fast&lt;/code&gt; now points at Gemma 4 and the Haiku mode moved to &lt;code&gt;lcl&lt;/code&gt;, there if I want to compare. Local models can be brilliant in a chat window and brittle inside an agent loop, as the thinking spiral already showed, and the only way I learn which one Gemma 4 is here is by living with it in the slot that fires twenty times a day. If it holds up, on tool calling, on diff quality, on the ten-line edits that fill my day, it stays. If it does not, the rollback is one line in &lt;code&gt;modes.json&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="what-i-am-not-doing-yet"&gt;What I Am Not Doing Yet&lt;/h2&gt;&#10;&lt;p&gt;I am not putting the server behind launchd. At ~22 GB resident on a 32 GB machine, leaving it running alongside Chrome and an IDE is asking for swap. On-demand &lt;code&gt;lcl start&lt;/code&gt; / &lt;code&gt;lcl stop&lt;/code&gt; is honest about the cost in a way always-on is not.&lt;/p&gt;&#10;&lt;p&gt;I am not running a second model on a second port. Maybe Qwen 3.6 35B-A3B earns a slot for the cases where Gemma is too weak. Maybe one model is enough. I will know in a week.&lt;/p&gt;&#10;&lt;h2 id="what-the-planning-helped-with"&gt;What the Planning Helped With&lt;/h2&gt;&#10;&lt;p&gt;The model researched the options; I chose the setup. The conversation made me write down why I chose each part, so I can refer back to it later.&lt;/p&gt;&#10;&lt;p&gt;I still need to see how the local model handles everyday work. If it does not hold up, I can switch the slot back.&lt;/p&gt;&#10;</description></item><item><title>fabric</title><link>https://saurabh-kumar.com/til/1780920001/</link><pubDate>Mon, 08 Jun 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1780920001/</guid><description>&lt;p&gt;&lt;a href="https://github.com/danielmiessler/fabric" rel="noopener external"&gt;faberic&lt;/a&gt; is a crowd-sourced collection of prompts bundeled with a cli to use them via piped interfaces.&lt;/p&gt;&#10;</description></item><item><title>Opus 4.6 launch</title><link>https://saurabh-kumar.com/til/1770379200/</link><pubDate>Fri, 06 Feb 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1770379200/</guid><description>&lt;p&gt;Opus 4.6 launch.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;context compaction (beta) and 1M context window, enables longer agentic tasks without loosing context.&lt;/li&gt;&#10;&lt;li&gt;they claim it has found 500 Zero-Day Flaws in open-source projects (yet to see the proofs though)&lt;/li&gt;&#10;&lt;li&gt;agent teams, multiple agent coordinates with a leader agent. &lt;a href="https://code.claude.com/docs/en/agent-teams" rel="noopener external"&gt;https://code.claude.com/docs/en/agent-teams&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>