<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Clustering | Derek Armstrong — Payments Engineer · AI in Production</title><link>https://derekarmstrong.dev/tags/clustering/</link><atom:link href="https://derekarmstrong.dev/tags/clustering/index.xml" rel="self" type="application/rss+xml"/><description>Clustering</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Mon, 17 Aug 2026 00:00:00 +0000</lastBuildDate><image><url>https://derekarmstrong.dev/media/sharing.png</url><title>Clustering</title><link>https://derekarmstrong.dev/tags/clustering/</link></image><item><title>Highly Available AI Inference Cluster</title><link>https://derekarmstrong.dev/projects/ha-ai-inference-cluster/</link><pubDate>Mon, 17 Aug 2026 00:00:00 +0000</pubDate><guid>https://derekarmstrong.dev/projects/ha-ai-inference-cluster/</guid><description>
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;strong&gt;What this proves:&lt;/strong&gt; Resilience — enterprise-grade AI infrastructure at a single operator&amp;rsquo;s desk. Three servers, three GPUs, one routing plane that switches models and fails over without the agent ever noticing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;I started with the single-box build: a dual-RTX-3090 server running one model. It worked. Then I hit the two problems that kill single-box AI in practice: it&amp;rsquo;s a single point of failure that interrupts the agent mid-task, and one GPU can&amp;rsquo;t honestly serve three very different workloads.&lt;/p&gt;
&lt;p&gt;So this became what any serious database deployment would be: a &lt;strong&gt;fleet with a control plane in front of it&lt;/strong&gt;. Not three Ollama instances. A tiered compute setup — a heavy compute node, a production node running a vision model, and a dedicated control plane — with a custom routing layer that picks the model, translates the API, and moves traffic when something&amp;rsquo;s down. If a tier dies, the plane routes around it. My tools don&amp;rsquo;t know the difference.&lt;/p&gt;
&lt;p&gt;The routing layer didn&amp;rsquo;t exist off the shelf, so I built it: a Rust proxy that speaks both OpenAI and Anthropic protocols, routes by capability and by cost profile, and meters every request.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Claude Code / OpenCode / OpenWebUI / scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; switchyard :4000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Rust routing plane, control box)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; capability routing / staged escalation /
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A-B splits / failover / per-request metering
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +---------------+----------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; VADER VAULTTEC (wicket: monitoring,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dual 3090s Arc B70 telemetry + Grafana)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; vLLM 27B llama.cpp Gemma
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :4242 :4243
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-fleet"&gt;The Fleet&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Vader — heavy compute.&lt;/strong&gt; Ryzen 9 5950X, 128GB RAM, dual RTX 3090 (48GB combined). Runs vLLM with a 27B MoE flagship: tensor-parallel across both GPUs, FP8 KV cache, 100K context, speculative decoding. This is the workhorse for daily agentic coding — the one that reads a whole repo and doesn&amp;rsquo;t blink.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VaultTec — production.&lt;/strong&gt; EPYC 4345P, 64GB ECC, Unraid, a 12-bay hoard, and an Intel Arc B70 with 32GB. Runs llama.cpp locally with Gemma 4 26B — a vision model that powers my family&amp;rsquo;s daily chat in OpenWebUI. It&amp;rsquo;s been the most stable thing on the cluster: load and unload, long conversations, no drama. The production box also hosts the next home of the control plane; the fleet is consolidating, not spreading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wicket — control plane.&lt;/strong&gt; Ryzen 9 5900XT, 64GB, NVMe. Currently hosts the routing plane and the monitoring stack (Prometheus + Grafana) that give me per-request visibility into every target. It also runs the retired 35B tier — gone now. Models earn their slot or they get cut. That&amp;rsquo;s the rule that keeps the fleet honest.&lt;/p&gt;
&lt;h2 id="the-routing-plane"&gt;The Routing Plane&lt;/h2&gt;
&lt;p&gt;One endpoint for everything. The layer in front does the thinking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol translation.&lt;/strong&gt; OpenAI and Anthropic clients both hit the same URL. Claude Code, Codex, OpenWebUI, and scripts all work unchanged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability routing.&lt;/strong&gt; A classifier picks the model that fits the request — agentic tool-calling goes to the flagship, casual conversation goes to the smaller one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staged escalation.&lt;/strong&gt; Cheap-and-fast first. When tool-use signals show up in the conversation, it escalates to the capable tier. You pay for intelligence only when the request actually needs it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A/B splits.&lt;/strong&gt; Random routing between local and capable targets with per-target telemetry, so I can watch quality and cost side by side instead of guessing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failover and key rotation.&lt;/strong&gt; Per-target health, rotating API-key pools, and load-aware routing. A dead or busy node stops receiving traffic; nobody upstream notices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Built in Rust as a standalone binary, deployed as a container, monitored end to end.&lt;/p&gt;
&lt;h2 id="the-economics"&gt;The Economics&lt;/h2&gt;
&lt;p&gt;Last real session on the cluster, two hours of active agentic CLI work: &lt;strong&gt;12.1M input tokens, 84.8K output, 174 requests, 88.5% cache hit rate.&lt;/strong&gt; At frontier list pricing, a month of that pace is easily five figures a year in cloud spend — zero on local. And none of it leaves the house.&lt;/p&gt;
&lt;p&gt;Private, fast, metered, and mine. That&amp;rsquo;s the whole pitch.&lt;/p&gt;
&lt;h2 id="philosophy"&gt;Philosophy&lt;/h2&gt;
&lt;p&gt;The goal was never to run the most impressive AI stack. It&amp;rsquo;s to run an AI stack that behaves like the boring production infrastructure I already respect: several boxes, each with one job, a control plane in front, telemetry on everything, and a rule that nothing stays in the fleet unless it earns its slot.&lt;/p&gt;
&lt;p&gt;If the 2024 build was &amp;ldquo;I put a big GPU in a box,&amp;rdquo; this one is what happens when you treat AI like a real service. Up, load-balanced, observable, and switchable — at someone else&amp;rsquo;s cost, hyper-efficient, with my hands on every layer.&lt;/p&gt;</description></item></channel></rss>