<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vishnu Hari Dadhich</title>
    <description>The latest articles on DEV Community by Vishnu Hari Dadhich (@vishnuhd).</description>
    <link>https://dev.to/vishnuhd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1462935%2Fe3c25478-2553-4cbb-92cc-adb4778764e9.jpg</url>
      <title>DEV Community: Vishnu Hari Dadhich</title>
      <link>https://dev.to/vishnuhd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vishnuhd"/>
    <language>en</language>
    <item>
      <title>Running a 35B MoE Model on an 8 GB Laptop GPU: Testing FreeToken</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Mon, 07 Sep 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/vishnuhd/running-a-35b-moe-model-on-an-8-gb-laptop-gpu-testing-freetoken-37ib</link>
      <guid>https://dev.to/vishnuhd/running-a-35b-moe-model-on-an-8-gb-laptop-gpu-testing-freetoken-37ib</guid>
      <description>&lt;p&gt;Running large language models locally usually comes down to a hard hardware boundary: video RAM. If a model does not fit into your GPU’s VRAM, performance usually falls off a cliff as soon as standard runtimes fall back to system memory over the PCIe bus.&lt;/p&gt;

&lt;p&gt;Mixture of Experts (MoE) architectures offer an attractive theoretical escape hatch. While the total parameter count can be large (30B to 70B+), only a sparse subset of expert layers activates for any given token. However, standard local runtimes still require loading the entire weight footprint into memory, which puts 30B+ models out of reach for everyday consumer laptops with 8 GB of VRAM.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://github.com/FlashML-org/FreeToken" rel="noopener noreferrer"&gt;FreeToken&lt;/a&gt; by FlashML, backed by their research paper (&lt;a href="https://arxiv.org/abs/2608.16157" rel="noopener noreferrer"&gt;arXiv:2608.16157&lt;/a&gt;). FreeToken is an edge inference runtime designed specifically to run frontier MoE models on consumer hardware by dynamically managing a hierarchical cache between GPU VRAM and host RAM.&lt;/p&gt;

&lt;p&gt;I recently downloaded FreeToken to put their claims to the test on my modest laptop GPU. Here is what the setup looked like, how FreeToken handles memory under the hood, and the real-world generation numbers I observed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Test Rig
&lt;/h3&gt;

&lt;p&gt;My test machine is a portable laptop, not a high-end multi-GPU workstation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU:&lt;/strong&gt; 13th Gen Intel Core i9-13900H (14 cores, 20 threads)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU:&lt;/strong&gt; NVIDIA GeForce RTX 4070 Laptop GPU (8.0 GiB VRAM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host RAM:&lt;/strong&gt; 32 GB DDR5 (31.7 GiB usable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operating System:&lt;/strong&gt; Windows 11&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; FreeToken Desktop (v0.2.0-beta.17)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd367gulytijkx9md6xzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd367gulytijkx9md6xzf.png" alt="FreeToken website download page" width="800" height="450"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;The FreeToken landing page highlights bringing frontier models to consumer edge hardware.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On paper, an 8 GB VRAM budget makes running a 35-billion parameter model look impossible. In traditional setups, 8 GB VRAM limits you to 7B or 8B parameter models in 4-bit quantizations (such as Q4_K_M). Attempting to load a 35B model typically triggers out-of-memory errors or slows inference to a crawl.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing FreeToken and Exploring the Library
&lt;/h3&gt;

&lt;p&gt;The FreeToken desktop app is available to download directly from the &lt;a href="https://www.flashml.ai/" rel="noopener noreferrer"&gt;FlashML website&lt;/a&gt;. It provides a self-contained installer that is quick to set up on Windows. When you launch it, the interface automatically detects your hardware specs, available VRAM, and system RAM, and presents a curated library of models optimized for edge offloading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fweqhvybt44jqoncqe1n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fweqhvybt44jqoncqe1n6.png" alt="FreeToken hardware detection and model library" width="800" height="450"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;FreeToken accurately detects the RTX 4070 Laptop GPU (8 GB) and 32 GB RAM, recommending compatible models.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For this test, I selected &lt;code&gt;Qwen3.6-35B-A3B NVFP4&lt;/code&gt; (&lt;code&gt;nvidia/Qwen3.6-35B-A3B-NVFP4&lt;/code&gt;). This is a 35-billion parameter MoE model using NVIDIA’s 4-bit floating point (NVFP4) format, with an initial download size of 21.9 GiB.&lt;/p&gt;

&lt;p&gt;Before loading the model, the console dashboard showed clean baseline resource usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPU VRAM:&lt;/strong&gt; 0.4 / 8.0 GiB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host RAM:&lt;/strong&gt; 11.0 / 31.7 GiB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU Temp:&lt;/strong&gt; 42°C at idle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pnltwzc8rvgl2y6xk5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pnltwzc8rvgl2y6xk5c.png" alt="System resources at idle before model loading" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Idle console telemetry: 0.4 GiB VRAM used, ready for model weights.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Weight Conversion: The FTW Format
&lt;/h3&gt;

&lt;p&gt;Once the 21.9 GiB download finished, FreeToken flagged the model as needing conversion before it could be launched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls063lnop1brrmbf4xy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls063lnop1brrmbf4xy2.png" alt="Download complete prompt indicating conversion is required" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Raw model download complete (21.9 GiB), prompting for weight conversion.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;FreeToken converts raw Hugging Face weights into its proprietary format called &lt;strong&gt;FTW&lt;/strong&gt; (FreeToken Weight format). During this process, the engine repacks and organizes the tensors into memory-mapped structures optimized for rapid streaming between host RAM and GPU memory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff76vrgikjx5gvfft7i8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff76vrgikjx5gvfft7i8r.png" alt="Converting raw weights into FTW format" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Repacking raw weights into FTW format for fast memory streaming.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The conversion took just over a minute on the i9-13900H and NVMe storage, producing a compact 19.5 GiB weight package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9oklwq5pihn6268a17qu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9oklwq5pihn6268a17qu.png" alt="Conversion complete showing 19.5 GiB repacked size" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Conversion complete: 19.5 GiB ready for execution.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading the Model and Memory Allocation
&lt;/h3&gt;

&lt;p&gt;Starting the model triggers the weight allocation phase. Instead of attempting to cram the entire 19.5 GiB into the 8 GB VRAM, FreeToken partitions the workload across both memory tiers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrg79g6n0nq9icbkvzzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrg79g6n0nq9icbkvzzf.png" alt="Loading model weights into VRAM" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Starting model and populating active weights into VRAM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once loaded, the memory profile was eye-opening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VRAM Usage:&lt;/strong&gt; 6.8 GiB / 8.0 GiB (85% utilization, leaving a healthy buffer for OS display compositing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host RAM Usage:&lt;/strong&gt; 30.2 GiB / 31.7 GiB (95% utilization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU Status:&lt;/strong&gt; Running at 45°C, drawing only 7W at idle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl33oy02bvhpdnl58jzih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl33oy02bvhpdnl58jzih.png" alt="Model running with VRAM and RAM utilization" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Model running: 6.8 GiB in VRAM and 30.2 GiB in system RAM.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cache Architecture Under the Hood
&lt;/h3&gt;

&lt;p&gt;The console view provides detailed insight into how FreeToken manages this memory footprint:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MoE Expert Cache:&lt;/strong&gt; FreeToken allocated 916 active expert slots in VRAM out of a total pool of 10,240 slots, consuming 1.51 GiB of VRAM. As different experts are needed during generation, they are dynamically paged in and out from the host RAM cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KV Cache:&lt;/strong&gt; 8K tokens of context were allocated in VRAM, consuming just 0.16 GiB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mamba State Slots:&lt;/strong&gt; 24 state slots were reserved, using 1.44 GiB of VRAM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frji5uu6378ll6y9a14r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frji5uu6378ll6y9a14r0.png" alt="Cache architecture telemetry in console" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Detailed cache breakdown: MoE expert cache, KV cache, and Mamba state slots in VRAM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This hierarchical design is the core reason the model can run without crashing. Inactive experts reside in system RAM (filling ~30 GB of system memory), while the active attention layers, KV cache, and currently triggered experts reside in the 8 GB VRAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Performance &amp;amp; Generation Speed
&lt;/h3&gt;

&lt;p&gt;To evaluate real-world performance, I tested a conversational prompt with reasoning enabled:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; “hi, how are you ?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model engaged its full thinking process, taking 1 minute and 9 seconds to plan and structure its response, and then generated 393 tokens at &lt;strong&gt;4.3 tokens per second&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fthmli302y7d89y924fx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fthmli302y7d89y924fx7.png" alt="Chat inference test output and token speed" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Chat generation benchmark: 393 tokens generated at 4.3 tok/s with full chain-of-thought thinking.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While 4.3 tokens per second is not real-time voice conversational speed, it is remarkably steady for running a &lt;strong&gt;35-billion parameter model on an 8 GB laptop GPU&lt;/strong&gt;. For tasks like code review, background agent execution, document analysis, or local drafting, 4.3 tok/s is completely practical.&lt;/p&gt;

&lt;p&gt;The model detail sheet confirms the architecture and licensing details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base Repository:&lt;/strong&gt; &lt;code&gt;nvidia/Qwen3.6-35B-A3B-NVFP4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Length:&lt;/strong&gt; 262,144 tokens (256K context window)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6v7bo8erbvsu9in0zl9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6v7bo8erbvsu9in0zl9v.png" alt="Model detail card on FreeToken" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Model details: 262K context window, NVFP4 quantization, and Apache 2.0 license.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in Agentic Tooling &amp;amp; Local Endpoints
&lt;/h3&gt;

&lt;p&gt;FreeToken also includes built-in support for developer tools and coding agents.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Apps&lt;/strong&gt; tab exposes both OpenAI-compatible and Anthropic-compatible local HTTP endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Endpoint:&lt;/strong&gt; &lt;code&gt;http://127.0.0.1:1919/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic Messages Endpoint:&lt;/strong&gt; &lt;code&gt;http://127.0.0.1:1919/v1/messages&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq5crllaxnalcz9c15qqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq5crllaxnalcz9c15qqk.png" alt="FreeToken Apps tab showing local endpoints and integrations" width="800" height="500"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Local OpenAI and Anthropic compatible endpoints with one-click configurations for Claude Code, Codex, opencode, and more.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;FreeToken provides quick configuration presets for popular coding assistants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; Configurable via local Anthropic endpoint routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex / opencode / openclaw:&lt;/strong&gt; One-click environment setups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hermes / DeepSeek Harness:&lt;/strong&gt; Direct command-line integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having drop-in compatibility with both OpenAI and Anthropic API schemas means you can point tools like Claude Code directly to your localhost port without running a reverse proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where FreeToken Fits
&lt;/h3&gt;

&lt;p&gt;FreeToken is a relatively young product, but its approach solves a real constraint for local inference.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strengths
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bypasses the VRAM limit:&lt;/strong&gt; Running a 35B MoE model on an 8 GB laptop GPU without manual layer-by-layer offload tuning works out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical caching:&lt;/strong&gt; The dynamic MoE expert cache keeps VRAM usage around 6.8 GiB, preventing driver resets and CUDA out-of-memory crashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local developer endpoints:&lt;/strong&gt; Dual support for OpenAI and Anthropic endpoints makes integration with modern coding agents straightforward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple setup on Windows:&lt;/strong&gt; No manual compilation, no CUDA toolkit troubleshooting, and a clean desktop GUI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Limitations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System RAM is the real requirement:&lt;/strong&gt; While VRAM requirements drop significantly, your host RAM must be large enough to hold the repacked weights. On my machine, the model took 30.2 GB of system RAM. If your laptop only has 16 GB of RAM, you will not be able to run 35B models this way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial conversion step:&lt;/strong&gt; Each new model requires a one-time repacking step into the FTW format before the first run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation throughput:&lt;/strong&gt; At roughly 4.3 tokens per second, it is well suited for asynchronous workloads, coding agents, and complex reasoning queries, but not for instant conversational back-and-forth.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;If you have a laptop with 32 GB of system RAM and a modest 8 GB NVIDIA GPU, FreeToken is worth checking out. It makes 35B-class MoE models runnable on consumer hardware that would otherwise choke on them, keeping your data completely local.&lt;/p&gt;

&lt;p&gt;You can download the desktop app directly from &lt;a href="https://www.flashml.ai/" rel="noopener noreferrer"&gt;FlashML&lt;/a&gt;, check out the project on &lt;a href="https://github.com/FlashML-org/FreeToken" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and read their research paper on &lt;a href="https://arxiv.org/abs/2608.16157" rel="noopener noreferrer"&gt;arXiv:2608.16157&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>localllm</category>
      <category>freetoken</category>
      <category>gpu</category>
    </item>
    <item>
      <title>Kubernetes is Adding a K to YAML: Why KYAML Makes Sense</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/vishnuhd/kubernetes-is-adding-a-k-to-yaml-why-kyaml-makes-sense-25lf</link>
      <guid>https://dev.to/vishnuhd/kubernetes-is-adding-a-k-to-yaml-why-kyaml-makes-sense-25lf</guid>
      <description>&lt;p&gt;Kubernetes 1.37 (“Garhwal”) just made KYAML Stable. YAML with a K bolted on, and it’s a genuinely useful feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;YAML has two well known landmines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type coercion.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt; &lt;span class="c1"&gt;# becomes boolean false&lt;/span&gt;
&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# becomes boolean true&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.10&lt;/span&gt; &lt;span class="c1"&gt;# becomes float 3.1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those values were meant to be strings. YAML quietly turned them into something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whitespace as structure.&lt;/strong&gt; Indentation defines meaning in normal YAML. Get one space wrong and the file still parses. It just means something else now, silently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt; &lt;span class="c1"&gt;# one space off, this attaches to the wrong container&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No error. No warning. Just wrong.&lt;/p&gt;

&lt;p&gt;Helm makes this worse, since you’re injecting indentation from outside the YAML context and hoping it lines up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What KYAML actually changes
&lt;/h2&gt;

&lt;p&gt;Nothing about the language. It’s YAML’s existing “flow style,” just enforced. Every KYAML file is valid YAML. Old tooling, old parsers, old &lt;code&gt;kubectl&lt;/code&gt; versions all read it fine.&lt;/p&gt;

&lt;p&gt;Three rules: objects use &lt;code&gt;{ }&lt;/code&gt;, arrays use &lt;code&gt;[]&lt;/code&gt;, strings are always quoted. Numbers and booleans stay bare, since they’re actually meant to be those types.&lt;/p&gt;

&lt;p&gt;One example, everything at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# before&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;hostNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REGION&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt;


&lt;span class="c1"&gt;# after (KYAML)&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/v1"&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deployment"&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;hostNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
        &lt;span class="pi"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nginx"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}],&lt;/span&gt;
          &lt;span class="nv"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REGION"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NO"&lt;/span&gt; &lt;span class="pi"&gt;}],&lt;/span&gt;
        &lt;span class="pi"&gt;},&lt;/span&gt;
      &lt;span class="pi"&gt;],&lt;/span&gt;
    &lt;span class="pi"&gt;},&lt;/span&gt;
  &lt;span class="pi"&gt;},&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything in one shot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{ }&lt;/code&gt; for objects (&lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;spec&lt;/code&gt;, each container)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[]&lt;/code&gt; for arrays (&lt;code&gt;containers&lt;/code&gt;, &lt;code&gt;ports&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;strings always quoted (&lt;code&gt;"api"&lt;/code&gt;, &lt;code&gt;"nginx"&lt;/code&gt;, &lt;code&gt;"REGION"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas: 2&lt;/code&gt; and &lt;code&gt;containerPort: 8080&lt;/code&gt; stay bare, they’re real numbers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;automountServiceAccountToken: false&lt;/code&gt; and &lt;code&gt;hostNetwork: true&lt;/code&gt; stay bare, they’re real booleans&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;value: "NO"&lt;/code&gt; is quoted, because it’s a string that would otherwise get read as &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;KYAML doesn’t remove types, it just stops guessing which one you meant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Since structure comes from &lt;code&gt;{ }&lt;/code&gt; and &lt;code&gt;[]&lt;/code&gt;, not from spaces, indentation stops being load bearing. Squash a KYAML file onto one line and it means exactly the same thing. That kills the Helm whitespace problem outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployment api &lt;span class="nt"&gt;-o&lt;/span&gt; kyaml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convert existing files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/google/yamlfmt/cmd/yamlfmt@latest
yamlfmt &lt;span class="nt"&gt;--kyaml&lt;/span&gt; manifest.yaml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also ships as a pre-commit hook and a Docker image for CI.&lt;/p&gt;

&lt;p&gt;Nothing forces the switch. Plain YAML keeps working. Mixed repos, some converted, some not, are fine.&lt;/p&gt;

&lt;p&gt;My plan if I touch this on a real repo: one directory first, verify round-trip parsing, check Helm and Kustomize don’t reformat it differently, then expand once it’s boring.&lt;/p&gt;

&lt;p&gt;Not required. Cheap upgrade. Worth exploring.&lt;/p&gt;

&lt;p&gt;For a broader breakdown of what else landed in this release, check out my notes on &lt;a href="https://vishctl.dev/posts/kubernetes-v1-37-stability/" rel="noopener noreferrer"&gt;Kubernetes v1.37: An Operator’s Look at Garhwal&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>yaml</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running llama.cpp on a 32 GB MacBook Air: A Direct Comparison with Ollama</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Thu, 03 Sep 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vishnuhd/running-llamacpp-on-a-32-gb-macbook-air-a-direct-comparison-with-ollama-4mc9</link>
      <guid>https://dev.to/vishnuhd/running-llamacpp-on-a-32-gb-macbook-air-a-direct-comparison-with-ollama-4mc9</guid>
      <description>&lt;p&gt;In the &lt;a href="https://vishctl.dev/posts/running-ollama-on-32gb-macbook-air/" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, I ran Ornith 1.5 9B on my 32 GB MacBook Air using Ollama and recorded baseline token-generation speeds on short prompts. Ollama is great for getting up and running quickly, but under the hood, its inference engine is built on &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we go one level down: building and running llama.cpp directly, offloading inference to Apple Silicon’s Metal GPU, and comparing performance numbers side by side with Ollama on the exact same model and quantization level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Build llama.cpp from Source
&lt;/h3&gt;

&lt;p&gt;Building llama.cpp from source on macOS is fast and straightforward. On Apple Silicon, CMake automatically enables Metal support (&lt;code&gt;GGML_METAL=ON&lt;/code&gt;) and compiles GPU compute kernels tailored for Apple’s unified memory architecture.&lt;/p&gt;

&lt;p&gt;Clone the repository and compile the release binaries using all available CPU cores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ggml-org/llama.cpp
&lt;span class="nb"&gt;cd &lt;/span&gt;llama.cpp
cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DCMAKE_BUILD_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Release
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; hw.logicalcpu&lt;span class="si"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the build finishes, the binaries live in &lt;code&gt;build/bin&lt;/code&gt;. Add them to your current session’s &lt;code&gt;$PATH&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/build/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this permanent across terminal sessions, append it to your shell configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export PATH=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/build/bin:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;PATH&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the build succeeded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Serve the Model (Out-of-the-Box Baseline)
&lt;/h3&gt;

&lt;p&gt;llama.cpp can download GGUF models directly from Hugging Face Hub using the &lt;code&gt;-hf&lt;/code&gt; flag, caching weights under &lt;code&gt;~/.cache/huggingface/hub&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="nt"&gt;-hf&lt;/span&gt; ornith-ai/Ornith-1.5-9B-GGUF &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;-ngl&lt;/span&gt; 99

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what the flags do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-hf ornith-ai/Ornith-1.5-9B-GGUF&lt;/code&gt;: Resolves the model on Hugging Face and downloads the default &lt;code&gt;Q4_K_M&lt;/code&gt; quant.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-ngl 99&lt;/code&gt;: Offloads all 99 model layers to the GPU (Metal on Apple Silicon unified memory).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--port 8080&lt;/code&gt;: Binds the OpenAI-compatible HTTP server to port 8080.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhbgigy0kulpp4ctuhaf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhbgigy0kulpp4ctuhaf3.png" alt="Starting llama-server and downloading Ornith 1.5 9B from Hugging Face" width="800" height="356"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Starting llama-server with automatic Hugging Face download and full Metal GPU offload.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;During startup, llama.cpp downloads both the multimodal vision projector (&lt;code&gt;mmproj-Ornith-1.5-9B-BF16.gguf&lt;/code&gt;) and the main model weights (&lt;code&gt;Ornith-1.5-9B-Q4_K_M.gguf&lt;/code&gt;), offloads the layers into Metal, initializes inference slots, and listens on port 8080.&lt;/p&gt;

&lt;p&gt;We test this default setup with a short greeting prompt through the OpenAI-compatible API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8080/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "ornith-1.5-9b",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj38yy89oe3k9gzt9q5sf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj38yy89oe3k9gzt9q5sf.png" alt="Calling the llama-server chat completions API" width="800" height="150"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;First chat completion request via curl, returning detailed server-side timing breakdowns.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The response returns a complete timing breakdown in the &lt;code&gt;timings&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt eval time = 496.21 ms / 12 tokens (24.18 tokens per second)
eval time = 5527.17 ms / 96 tokens (17.19 tokens per second)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that to Ollama’s numbers on the same model and quant from the &lt;a href="https://vishctl.dev/posts/running-ollama-on-32gb-macbook-air/" rel="noopener noreferrer"&gt;earlier post&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Ollama (Default)&lt;/th&gt;
&lt;th&gt;llama.cpp (Default, no &lt;code&gt;-fa&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt Eval Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37.13 tokens/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24.18 tokens/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generation Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.68 tokens/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17.19 tokens/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generation speed is practically neck-and-neck (~17 tokens/s). However, prompt evaluation (prefill latency) was noticeably slower on vanilla llama.cpp out of the box (24.18 tokens/s vs. Ollama’s 37.13 tokens/s).&lt;/p&gt;

&lt;p&gt;Ollama enables FlashAttention and sensible batch sizes by default, while raw llama.cpp keeps conservative baseline settings unless configured explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verifying the Quants
&lt;/h3&gt;

&lt;p&gt;Before comparing benchmark numbers, it is critical to confirm that both engines are executing the exact same quantization format. Comparing different quants (e.g. Q4_K_M vs Q8_0) would invalidate any performance conclusions.&lt;/p&gt;

&lt;p&gt;Check the quant llama.cpp loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8080/v1/models | jq


&lt;span class="s2"&gt;"ftype"&lt;/span&gt;: &lt;span class="s2"&gt;"Q4_K - Medium"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the quant Ollama is using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/show &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "ornith-1.5:9b"}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.details.quantization_level'&lt;/span&gt;


&lt;span class="s2"&gt;"Q4_K_M"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both runtimes are confirmed to be executing &lt;code&gt;Q4_K_M&lt;/code&gt;. Precision and model size are strictly identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Leveling the Playing Field with Flash Attention &amp;amp; Batch Tuning
&lt;/h3&gt;

&lt;p&gt;To give llama.cpp parity with Ollama’s runtime optimizations, we restart &lt;code&gt;llama-server&lt;/code&gt; with FlashAttention enabled and explicit batch sizes configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="nt"&gt;-hf&lt;/span&gt; ornith-ai/Ornith-1.5-9B-GGUF &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;-ngl&lt;/span&gt; 99 &lt;span class="nt"&gt;-fa&lt;/span&gt; on &lt;span class="nt"&gt;-b&lt;/span&gt; 2048 &lt;span class="nt"&gt;-ub&lt;/span&gt; 512

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what these tuning flags configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-fa on&lt;/code&gt; (or &lt;code&gt;--flash-attn on&lt;/code&gt;): Enables FlashAttention kernels for Apple Silicon Metal. This significantly accelerates prompt evaluation (prefill) and reduces memory bandwidth overhead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b 2048&lt;/code&gt; (&lt;code&gt;--batch-size&lt;/code&gt;): Sets the logical batch size for prompt evaluation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-ub 512&lt;/code&gt; (&lt;code&gt;--ubatch-size&lt;/code&gt;): Sets the physical micro-batch size dispatched to Metal compute passes, keeping the GPU pipelines fully saturated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Side-by-Side Benchmark on Longer Generation
&lt;/h3&gt;

&lt;p&gt;Both engines were given the same prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;"give a 25 line story"&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A longer generation produces several hundred tokens, giving a much more reliable measurement of steady-state generation speed than short one-liners.&lt;/p&gt;

&lt;p&gt;Here are the side-by-side results on the 32 GB MacBook Air:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Ollama (&lt;code&gt;ornith-1.5:9b&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;llama.cpp (&lt;code&gt;-fa on -b 2048 -ub 512&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt Eval Count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17 tokens&lt;/td&gt;
&lt;td&gt;18 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt Eval Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;46.01 tokens/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.68 tokens/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generation Count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;496 tokens&lt;/td&gt;
&lt;td&gt;436 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generation Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.42 tokens/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17.07 tokens/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Duration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30.58 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25.85 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With FlashAttention and batch tuning enabled, llama.cpp matches or slightly edges out Ollama across both prompt evaluation ( &lt;strong&gt;48.68 vs. 46.01 tokens/s&lt;/strong&gt; ) and token generation speed ( &lt;strong&gt;17.07 vs. 16.42 tokens/s&lt;/strong&gt; ).&lt;/p&gt;

&lt;h4&gt;
  
  
  Ollama Command &amp;amp; Stats
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run ornith-1.5:9b &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; give a 25 line story
Thinking...
...done thinking.

&lt;span class="o"&gt;[&lt;/span&gt;story output omitted]

total duration: 30.582007541s
load duration: 2.821208ms
prompt &lt;span class="nb"&gt;eval &lt;/span&gt;count: 17 token&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
prompt &lt;span class="nb"&gt;eval &lt;/span&gt;duration: 369.481ms
prompt &lt;span class="nb"&gt;eval &lt;/span&gt;rate: 46.01 tokens/s
&lt;span class="nb"&gt;eval &lt;/span&gt;count: 496 token&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;eval &lt;/span&gt;duration: 30.204706s
&lt;span class="nb"&gt;eval &lt;/span&gt;rate: 16.42 tokens/s

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  llama.cpp Server Startup &amp;amp; Timing Logs
&lt;/h4&gt;

&lt;p&gt;Server startup and live slot execution log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;➜ ~ llama-server -hf ornith-ai/Ornith-1.5-9B-GGUF --port 8080 -ngl 99 -fa on -b 2048 -ub 512
0.00.729.018 I cmn common_param: common_params_print_info: verbosity = 3 (adjust with the `-lv N` CLI arg)
0.00.729.664 W srv llama_server: -----------------
0.00.729.667 W srv llama_server: CORS is set to allow all origins ('*') and no API key is set
0.00.729.668 W srv llama_server: this can be a security risk (cross-origin attacks)
0.00.729.668 W srv llama_server: more info: https://github.com/ggml-org/llama.cpp/pull/25655
0.00.729.669 W srv llama_server: -----------------
0.00.731.435 I srv load_model: loading model 'ornith-ai/Ornith-1.5-9B-GGUF'
0.01.177.317 W model has unused tensor blk.32.attn_norm.weight (size = 16384 bytes) -- ignoring
0.01.177.324 W model has unused tensor blk.32.post_attention_norm.weight (size = 16384 bytes) -- ignoring
0.01.177.329 W model has unused tensor blk.32.attn_q.weight (size = 18874368 bytes) -- ignoring
0.01.177.331 W model has unused tensor blk.32.attn_k.weight (size = 2359296 bytes) -- ignoring
0.01.177.333 W model has unused tensor blk.32.attn_v.weight (size = 3440640 bytes) -- ignoring
0.01.177.338 W model has unused tensor blk.32.attn_output.weight (size = 9437184 bytes) -- ignoring
0.01.177.340 W model has unused tensor blk.32.attn_q_norm.weight (size = 1024 bytes) -- ignoring
0.01.177.341 W model has unused tensor blk.32.attn_k_norm.weight (size = 1024 bytes) -- ignoring
0.01.177.343 W model has unused tensor blk.32.ffn_gate.weight (size = 28311552 bytes) -- ignoring
0.01.177.345 W model has unused tensor blk.32.ffn_down.weight (size = 41287680 bytes) -- ignoring
0.01.177.347 W model has unused tensor blk.32.ffn_up.weight (size = 28311552 bytes) -- ignoring
0.01.177.350 W model has unused tensor blk.32.nextn.eh_proj.weight (size = 18874368 bytes) -- ignoring
0.01.177.353 W model has unused tensor blk.32.nextn.enorm.weight (size = 16384 bytes) -- ignoring
0.01.177.354 W model has unused tensor blk.32.nextn.hnorm.weight (size = 16384 bytes) -- ignoring
0.01.177.359 W model has unused tensor blk.32.nextn.shared_head_norm.weight (size = 16384 bytes) -- ignoring
0.01.799.972 I cmn init: llama threadpool init, n_threads = 4
0.01.959.375 W load_hparams: Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks
0.01.959.377 W load_hparams: if you encounter problems with accuracy, try adding --image-min-tokens 1024
0.01.959.377 W load_hparams: more info: https://github.com/ggml-org/llama.cpp/issues/16842

0.02.172.437 I srv load_model: loaded multimodal model, '/Users/vishnuhd/.cache/huggingface/hub/models--ornith-ai--Ornith-1.5-9B-GGUF/snapshots/abdd624b12ebf020b767fff532ff44fe552b28c3/mmproj-Ornith-1.5-9B-BF16.gguf'
0.02.399.797 I srv load_model: initializing, n_slots = 4, n_ctx_slot = 262144, kv_unified = 'true'
0.02.402.615 I srv init: chat template supports preserving reasoning, consider enabling it via --reasoning-preserve
0.02.402.621 I srv llama_server: model loaded
0.02.402.623 I srv llama_server: listening on http://127.0.0.1:8080
0.02.402.623 W srv llama_server: NOTICE: server default port will be changed to :9931 in a future release
0.02.402.623 W srv llama_server: ref: https://github.com/ggml-org/llama.cpp/pull/26508

0.36.386.802 I slot get_availabl: id 3 | task -1 | selected slot by LRU, t_last = -1
0.36.386.826 I slot launch_slot_: id 3 | task 0 | processing task, is_child = 0
0.42.586.497 I slot print_timing: id 3 | task 0 | n_gen = 100, tg = 16.98 t/s, tg_3s = 17.15 t/s
0.45.633.605 I slot print_timing: id 3 | task 0 | n_gen = 152, tg = 17.01 t/s, tg_3s = 17.07 t/s
0.48.679.586 I slot print_timing: id 3 | task 0 | n_gen = 204, tg = 17.03 t/s, tg_3s = 17.07 t/s
0.51.718.658 I slot print_timing: id 3 | task 0 | n_gen = 256, tg = 17.04 t/s, tg_3s = 17.11 t/s
0.54.746.036 I slot print_timing: id 3 | task 0 | n_gen = 308, tg = 17.07 t/s, tg_3s = 17.18 t/s
0.57.778.672 I slot print_timing: id 3 | task 0 | n_gen = 360, tg = 17.08 t/s, tg_3s = 17.15 t/s
1.00.817.109 I slot print_timing: id 3 | task 0 | n_gen = 412, tg = 17.08 t/s, tg_3s = 17.11 t/s
1.02.233.148 I slot print_timing: id 3 | task 0 | prompt eval time = 369.74 ms / 18 tokens ( 20.54 ms per token, 48.68 tokens per second)
1.02.233.155 I slot print_timing: id 3 | task 0 | eval time = 25476.17 ms / 436 tokens ( 58.57 ms per token, 17.07 tokens per second)
1.02.233.157 I slot print_timing: id 3 | task 0 | total time = 25845.92 ms / 454 tokens
1.02.233.159 I slot print_timing: id 3 | task 0 | graphs reused = 434
1.02.233.187 I slot release: id 3 | task 0 | stop processing: n_tokens = 453, truncated = 0

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the raw curl request (with story output trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;http://localhost:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/chat/completions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ornith-1.5-9b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Give me a 25 line story"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[story text omitted]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[thinking omitted]"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1788423730&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ornith-ai/Ornith-1.5-9B-GGUF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"system_fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b10712-daef7b687"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;454&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens_details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-dLoeYkd6rvuFieOvUSr9dBxB798twvZi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;369.742&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_per_token_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20.54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_per_second"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;48.68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25476.174&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_per_token_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;58.57&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_per_second"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;17.07&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ollama is not faster than llama.cpp&lt;/strong&gt; : Under the hood, Ollama is llama.cpp. Its initial out-of-the-box advantage in prompt evaluation comes entirely from default runtime tuning (FlashAttention and batch sizes), not a secret runtime or different quantization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FlashAttention is essential on Apple Silicon&lt;/strong&gt; : Enabling &lt;code&gt;-fa on&lt;/code&gt; doubled prompt evaluation speed from &lt;strong&gt;24.18 tokens/s to 48.68 tokens/s&lt;/strong&gt; , drastically cutting time-to-first-token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent throughput&lt;/strong&gt; : At 9B parameters with Q4_K_M quantization, Apple Silicon unified memory sustains a rock-solid &lt;strong&gt;~17.1 tokens/sec&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why run llama.cpp directly?&lt;/strong&gt; : Ollama provides exceptional developer ergonomics for local apps and testing. But direct llama.cpp gives you full control over context allocation, slot limits, KV cache quantization (&lt;code&gt;-ctk&lt;/code&gt;, &lt;code&gt;-ctv&lt;/code&gt;), and immediate access to upstream features and bugfixes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the next post, we will explore KV cache quantization and memory profiling to see how far we can stretch long-context windows on a 32 GB machine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>localllm</category>
      <category>llamacpp</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Running Ollama on a 32 GB MacBook Air: A Practical First Setup</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Thu, 03 Sep 2026 01:30:00 +0000</pubDate>
      <link>https://dev.to/vishnuhd/running-ollama-on-a-32-gb-macbook-air-a-practical-first-setup-2bc0</link>
      <guid>https://dev.to/vishnuhd/running-ollama-on-a-32-gb-macbook-air-a-practical-first-setup-2bc0</guid>
      <description>&lt;p&gt;I have a 32 GB MacBook Air. It is not a workstation GPU box, but its unified memory makes it a surprisingly capable machine for local models, provided I choose models that fit and keep expectations sensible.&lt;/p&gt;

&lt;p&gt;This is the first post in a small, practical series about running models locally. I am starting with Ollama because it gets a model running quickly without building a runtime from source or hand-managing dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Ollama
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; manages model downloads, exposes a straightforward CLI, and starts a local HTTP API. On Apple silicon, it supports the Apple GPU; Ollama’s current macOS requirement is Sonoma (14) or newer. The app stores models and configuration under &lt;code&gt;~/.ollama&lt;/code&gt;. &lt;a href="https://docs.ollama.com/macos" rel="noopener noreferrer"&gt;The macOS documentation&lt;/a&gt; is the useful reference for install, storage, and logs.&lt;/p&gt;

&lt;p&gt;Under the hood, Ollama packages model management and an API around inference backends including &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;. That distinction is useful: Ollama is the convenient front door; llama.cpp is a lower-level route I can use later when I want to compare runtimes directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Ollama
&lt;/h3&gt;

&lt;p&gt;Download the macOS app from &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;ollama.com/download&lt;/a&gt;, mount the DMG, and drag Ollama to &lt;code&gt;/Applications&lt;/code&gt;. Start it once. If the CLI is not already available, the app will offer to add it to your path.&lt;/p&gt;

&lt;p&gt;Confirm that both the CLI and the local server are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama &lt;span class="nt"&gt;--version&lt;/span&gt;
ollama list
ollama ps

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a new installation, &lt;code&gt;ollama list&lt;/code&gt; and &lt;code&gt;ollama ps&lt;/code&gt; should be empty. The first reports downloaded models; the second reports models currently loaded into memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Pull a Model
&lt;/h3&gt;

&lt;p&gt;For this machine, I started with Ornith 1.5 9B:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull ornith-1.5:9b

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels deliberately familiar if you work with containers: &lt;code&gt;ollama pull&lt;/code&gt; downloads the model and its layers, while &lt;code&gt;ollama run&lt;/code&gt; starts an interactive session. The current Ollama build of &lt;code&gt;ornith-1.5:9b&lt;/code&gt; is 6.6 GB with a 256K context window, which is a comfortable starting point on a 32 GB laptop. &lt;a href="https://ollama.com/library/ornith-1.5" rel="noopener noreferrer"&gt;Ollama’s model page&lt;/a&gt; lists the available tags; the 35B download is 23 GB, so I would not make that the default on an Air.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqj1zv6qp2ef0qtdjzb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqj1zv6qp2ef0qtdjzb9.png" alt="Pulling Ornith 1.5 9B, then confirming the local model" width="800" height="204"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Pulling Ornith 1.5 9B, then confirming it is available locally.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After the pull completes, confirm it is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Run It Interactively
&lt;/h3&gt;

&lt;p&gt;Start a chat session with the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run ornith-1.5:9b

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a question that resembles the work you actually do. I tested a simple greeting first, then moved on to infrastructure questions. Exit the interactive prompt with &lt;code&gt;/exit&lt;/code&gt; or &lt;code&gt;Ctrl-D&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu2w838wlqel8rgnlhxfl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu2w838wlqel8rgnlhxfl.png" alt="An interactive Ornith 1.5 9B session in the terminal" width="799" height="172"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;A first interactive conversation with the locally running model.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For an initial sanity check, the model was responsive and produced a natural answer. That is useful confirmation that the model loads and runs locally, but it is not a benchmark. A real comparison needs the same prompt, context length, generation settings, and output length.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Inspect Performance with &lt;code&gt;--verbose&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Ollama’s &lt;code&gt;--verbose&lt;/code&gt; flag is a quick way to see timings after every response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run ornith-1.5:9b &lt;span class="nt"&gt;--verbose&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On this MacBook Air, my short greeting test produced 114 output tokens at &lt;strong&gt;16.68 tokens/sec&lt;/strong&gt; , with a total duration of &lt;strong&gt;7.22 seconds&lt;/strong&gt;. The model’s thinking trace was visible before its answer.&lt;/p&gt;

&lt;p&gt;I ran the same kind of test with Gemma 4 E4B:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run gemma4:e4b &lt;span class="nt"&gt;--verbose&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That run produced 228 output tokens at &lt;strong&gt;27.62 tokens/sec&lt;/strong&gt; , with a total duration of &lt;strong&gt;8.49 seconds&lt;/strong&gt;. It is faster in this small test, but it also generated a different and longer response. These figures are useful as a personal baseline, not as an apples-to-apples model ranking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ornith 1.5 9B vs. Gemma 4 E4B
&lt;/h3&gt;

&lt;p&gt;Both models fit well on a 32 GB MacBook Air, but they are aimed at slightly different trade-offs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;What I observed&lt;/th&gt;
&lt;th&gt;Practical fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ornith-1.5:9b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6.6 GB download; 16.68 tokens/sec in my short verbose run&lt;/td&gt;
&lt;td&gt;A capable 9B-class, text-and-image model with plenty of memory headroom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma4:e4b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.6 GB download; 27.62 tokens/sec in my different short verbose run&lt;/td&gt;
&lt;td&gt;An efficient edge model for local chat, reasoning, coding, and multimodal work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;E&lt;/code&gt; in Gemma 4 E4B means &lt;strong&gt;effective&lt;/strong&gt; parameters. Ollama describes E4B as a 4.5B-effective-parameter edge model (8B including embeddings), with a 128K context window and text, image, and audio support. It is designed to do useful local work without the memory cost of the larger Gemma 4 workstation models. &lt;a href="https://ollama.com/library/gemma4" rel="noopener noreferrer"&gt;The Gemma 4 library page&lt;/a&gt; has the current tags, sizes, and capabilities.&lt;/p&gt;

&lt;p&gt;In practice, I would start with &lt;code&gt;ornith-1.5:9b&lt;/code&gt; if I want the smaller download and a roomy 256K context window, or &lt;code&gt;gemma4:e4b&lt;/code&gt; if I want the efficient Gemma 4 feature set. Neither of these two quick runs says which model is universally better; use the prompts you care about and record the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Call the Local API
&lt;/h3&gt;

&lt;p&gt;The terminal chat is only the first test. Ollama exposes an API locally at &lt;code&gt;http://localhost:11434/api&lt;/code&gt;, so the model can be part of a script or an application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;http://localhost:&lt;/span&gt;&lt;span class="mi"&gt;11434&lt;/span&gt;&lt;span class="err"&gt;/api/chat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ornith-1.5:9b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello!"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fye0n6w8m0c0qv4e2j0yi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fye0n6w8m0c0qv4e2j0yi.png" alt="Calling the local Ollama chat API with curl" width="799" height="136"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Calling the same model through Ollama’s local chat API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The response includes the answer and useful timing fields. Keep this endpoint local by default. If I later expose it to another device, I will put authentication and a proper reverse proxy in front of it. I will not publish port 11434 directly. &lt;a href="https://docs.ollama.com/api/introduction" rel="noopener noreferrer"&gt;Ollama’s API documentation&lt;/a&gt; covers the local base URL and client libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Try the App UI
&lt;/h3&gt;

&lt;p&gt;The CLI is great for testing and scripts, but the Ollama app also gives me a simple chat interface. Here, Ornith is selected in the model picker and used for a weather question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5gizfz91sgs3ur8kfz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5gizfz91sgs3ur8kfz0.png" alt="The Ollama app with Ornith 1.5 9B selected" width="800" height="503"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;The Ollama app with Ornith 1.5 9B selected for a web-assisted prompt.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The app can also give a model access to web tools. In this example, Ornith searched for current Singapore weather before answering. That extends the model with fresh online information when the tool is enabled, but it does not replace the model’s built-in training knowledge. Treat retrieved results as sources to verify, especially for technical or time-sensitive answers.&lt;/p&gt;

&lt;p&gt;The UI is useful when I want to compare prompts casually. The local API is the path I will use when I want to integrate models into tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands Worth Remembering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download without entering an interactive chat&lt;/span&gt;
ollama pull ornith-1.5:9b

&lt;span class="c"&gt;# Start an interactive chat&lt;/span&gt;
ollama run ornith-1.5:9b

&lt;span class="c"&gt;# Show downloaded models and their disk usage&lt;/span&gt;
ollama list

&lt;span class="c"&gt;# Show models currently loaded by the runner&lt;/span&gt;
ollama ps

&lt;span class="c"&gt;# Remove a model I no longer need&lt;/span&gt;
ollama &lt;span class="nb"&gt;rm &lt;/span&gt;ornith-1.5:9b

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Models are not small. Treat &lt;code&gt;ollama pull&lt;/code&gt; the same way you would a sizeable &lt;code&gt;docker pull&lt;/code&gt;: check disk space before collecting a pile of models “just in case.”&lt;/p&gt;

&lt;h3&gt;
  
  
  What This MacBook Air Is Good At
&lt;/h3&gt;

&lt;p&gt;An 8B to 9B class model is a good fit for private note summarisation, explaining logs, drafting YAML, lightweight coding help, and experimenting with local integrations. This is not where I expect a 70B-class model to be effortless or where I would host production inference.&lt;/p&gt;

&lt;p&gt;There are many ways to run models locally, but Ollama is an excellent Apple-silicon starting point. It gets the plumbing out of the way so I can spend time evaluating the models themselves.&lt;/p&gt;

&lt;p&gt;Next, I will run llama.cpp directly on the same machine, then see whether a vLLM setup is worth comparing. Stay tuned.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>localllm</category>
      <category>ollama</category>
      <category>applesilicon</category>
    </item>
    <item>
      <title>Kubernetes v1.37: An Operator's Look at Garhwal</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Wed, 02 Sep 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/vishnuhd/kubernetes-v137-an-operators-look-at-garhwal-a53</link>
      <guid>https://dev.to/vishnuhd/kubernetes-v137-an-operators-look-at-garhwal-a53</guid>
      <description>&lt;p&gt;Kubernetes v1.37 landed recently under the release theme &lt;strong&gt;Garhwal&lt;/strong&gt; , named after the Himalayan region of Uttarakhand, India. The release packages 67 enhancements across alpha, beta, and stable.&lt;/p&gt;

&lt;p&gt;The upstream changelog is massive, but as someone who spends most of the day managing control planes, dealing with storage, and watching cloud bills, only a handful of changes stand out as immediately relevant to day-to-day work.&lt;/p&gt;

&lt;p&gt;Here are the features I am keeping an eye on, along with practical examples for how they work.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. HPA Can Finally Scale to Zero (&lt;code&gt;minReplicas: 0&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;For years, one of the most frustrating limitations in vanilla Kubernetes autoscaling was that &lt;code&gt;HorizontalPodAutoscaler&lt;/code&gt; could not scale below one replica. If you had a queue consumer waiting on messages in SQS or RabbitMQ, or a GPU worker waiting on batch jobs, you had to keep at least one pod running around the clock, or pull in third-party tools like KEDA just to do basic scale-to-zero.&lt;/p&gt;

&lt;p&gt;In v1.37, &lt;strong&gt;HPA scale to zero&lt;/strong&gt; graduated to Beta and is enabled by default. You can now configure &lt;code&gt;minReplicas: 0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;autoscaling/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;queue-worker-hpa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;queue-worker&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;External&lt;/span&gt;
      &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws_sqs_approximate_number_of_messages_visible&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Value&lt;/span&gt;
          &lt;span class="na"&gt;averageValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the queue is empty, the controller scales the deployment down to 0 pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa queue-worker-hpa
&lt;span class="c"&gt;# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE&lt;/span&gt;
&lt;span class="c"&gt;# queue-worker-hpa Deployment/queue-worker 0/30 (avg) 0 10 0 18m&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, the HPA controller adds a &lt;code&gt;ScaledToZero&lt;/code&gt; condition so you can easily distinguish an automated scale-down from a manual shutdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;currentReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;desiredReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledToZero&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledToZero&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pods&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scaled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;down&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;based&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metrics"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt; This only works with object or external metrics. It does not work with CPU or memory metrics, because a deployment with zero running pods produces no CPU or memory data to trigger an upscale. For event-driven workers and GPU workloads, this saves real money on cloud bills.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Finding Orphaned PVCs Just Got Much Easier
&lt;/h3&gt;

&lt;p&gt;PersistentVolumeClaims have a habit of outliving the workloads that created them. Someone deletes a Deployment or StatefulSet, but the PVC stays behind in the namespace. Weeks or months later, dozens of unused EBS volumes or cloud disks are quietly burning infrastructure budget.&lt;/p&gt;

&lt;p&gt;Until now, Kubernetes gave you no native way to know when a PVC was last used without writing custom scripts to inspect every pod spec in the cluster.&lt;/p&gt;

&lt;p&gt;Kubernetes v1.37 addresses this by graduating &lt;strong&gt;PVC last-used tracking&lt;/strong&gt; to Beta (enabled by default). The PVC protection controller now adds an &lt;code&gt;Unused&lt;/code&gt; condition to &lt;code&gt;status.conditions&lt;/code&gt; on PersistentVolumeClaims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;abandoned-redis-data&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analytics&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bound&lt;/span&gt;
  &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unused&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NoPodsUsingPVC&lt;/span&gt;
      &lt;span class="na"&gt;lastTransitionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T09:30:00Z"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When the last pod referencing a PVC terminates, &lt;code&gt;Status&lt;/code&gt; flips to &lt;code&gt;"True"&lt;/code&gt; with reason &lt;code&gt;NoPodsUsingPVC&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;lastTransitionTime&lt;/code&gt; tells you the exact timestamp since the volume became idle.&lt;/li&gt;
&lt;li&gt;As soon as a new pod attaches, the condition flips back to &lt;code&gt;False&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes does not delete anything automatically (which is good; you do not want the control plane guessing whether data is safe to purge). But this gives you a clean field to query across your cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all PVCs across all namespaces that are currently unused, with their idle timestamp&lt;/span&gt;
kubectl get pvc &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; custom-columns&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'NAMESPACE:.metadata.namespace,\
NAME:.metadata.name,\
CAPACITY:.status.capacity.storage,\
UNUSED:.status.conditions[?(@.type=="Unused")].status,\
IDLE_SINCE:.status.conditions[?(@.type=="Unused")].lastTransitionTime'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With one command, you can spot storage that has been abandoned for 30+ days and clean it up safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. ClusterTrustBundles Go GA: Retire Your CA Sync Cronjobs
&lt;/h3&gt;

&lt;p&gt;If you manage enterprise clusters with internal PKI, private container registries, or corporate proxies, you have almost certainly solved the root certificate problem with a workaround. Most teams either run a custom controller that clones a secret into every namespace as a &lt;code&gt;ConfigMap&lt;/code&gt;, or bake internal certificates into container base images.&lt;/p&gt;

&lt;p&gt;In v1.37, &lt;strong&gt;ClusterTrustBundles&lt;/strong&gt; and &lt;strong&gt;Pod Certificates&lt;/strong&gt; officially graduated to Stable (GA) under &lt;code&gt;certificates.k8s.io&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;ClusterTrustBundle&lt;/code&gt; is a cluster-scoped object that holds trusted X.509 certificate anchors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certificates.k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterTrustBundle&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corporate-root-ca&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;signerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;corp.internal/pki"&lt;/span&gt;
  &lt;span class="na"&gt;trustBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;-----BEGIN CERTIFICATE-----&lt;/span&gt;
    &lt;span class="s"&gt;MIICjTCCAjSgAwIBAgIUTkX...&lt;/span&gt;
    &lt;span class="s"&gt;-----END CERTIFICATE-----&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workloads in any namespace can project this bundle directly into their filesystem via standard projected volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;internal-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-registry.internal/api:v1&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Point common TLS libraries to the projected CA bundle&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSL_CERT_FILE&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/corporate-root-ca.crt&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trusted-certs&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/corporate-root-ca.crt&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corporate-root-ca.crt&lt;/span&gt;
              &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trusted-certs&lt;/span&gt;
          &lt;span class="na"&gt;projected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;clusterTrustBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corporate-root-ca&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kubelet handles mounting and rotation natively. That is one less custom controller or sync cronjob to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Control Plane Breathing Room: etcd RangeStream &amp;amp; Concurrent Decode
&lt;/h3&gt;

&lt;p&gt;When large clusters experience network hiccups or controller restarts, hundreds of controllers can hit the API server with list requests at the exact same moment. Historically, etcd built full responses in memory before sending them, and the API server processed watch events serially on a single goroutine. During recovery storms, control plane memory would spike, occasionally sending nodes into Out-Of-Memory (OOM) loops.&lt;/p&gt;

&lt;p&gt;In v1.37, two features land on by default to fix this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;etcd RangeStream (Beta):&lt;/strong&gt; Instead of buffering massive key-value sets in memory, etcd streams responses back in chunks. This turns what used to be a memory-heavy dump into a smooth stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent Watch Object Decode (Beta):&lt;/strong&gt; The API server now decodes incoming watch events across a pool of worker goroutines rather than processing them one by one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you interact with etcd 3.7+ directly, you can test the new chunked streaming RPC via &lt;code&gt;etcdctl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stream keys in chunks rather than buffering everything into a single gRPC response&lt;/span&gt;
etcdctl get /registry/pods/ &lt;span class="nt"&gt;--prefix&lt;/span&gt; &lt;span class="nt"&gt;--stream&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In upstream benchmarks across 150,000 pods, these two improvements combined cut cache initialization time by more than 50%. For operators of large clusters or CRD-heavy environments, this means significantly less control plane jitter during rolling restarts.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;kubectl get -o kyaml&lt;/code&gt; Goes Stable
&lt;/h3&gt;

&lt;p&gt;YAML has plenty of parsing quirks (such as unquoted country codes like &lt;code&gt;NO&lt;/code&gt; being interpreted as boolean &lt;code&gt;false&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In v1.37, &lt;strong&gt;KYAML&lt;/strong&gt; reaches Stable. KYAML is a safer, unambiguous subset of YAML tailored specifically for Kubernetes. You do not need to change any of your existing manifests or pipelines; KYAML is strictly backwards compatible with standard YAML.&lt;/p&gt;

&lt;p&gt;You can now run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Output clean, normalized YAML without parser ambiguity&lt;/span&gt;
kubectl get deployment queue-worker &lt;span class="nt"&gt;-o&lt;/span&gt; kyaml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comparing the output against regular YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Standard YAML output often strips quotes from country codes or booleans:&lt;/span&gt;
&lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt; &lt;span class="c1"&gt;# Parsed by some YAML 1.1 loaders as boolean false!&lt;/span&gt;

&lt;span class="c1"&gt;# KYAML output strictly normalizes and quotes scalar types:&lt;/span&gt;
&lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NO"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It formats cleanly, avoids parser edge cases, and provides predictable output for gitops diffs and scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. SELinuxMount Reaches GA
&lt;/h3&gt;

&lt;p&gt;If your nodes run SELinux (common in Red Hat, Rocky, or Fedora environments), volume mounts historically required recursive file relabeling on every pod start, which could cause painfully slow startup times on large volumes.&lt;/p&gt;

&lt;p&gt;In v1.37, &lt;strong&gt;SELinuxMount&lt;/strong&gt; is now Stable and enabled by default for CSI drivers that support it. Volumes are mounted with a single mount context rather than recursively walking the directory tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt; A volume mount can only carry one SELinux context. If you run multiple pods with different SELinux labels that share the same volume on the same node, those pods may now fail to mount.&lt;/p&gt;

&lt;p&gt;If your workloads rely on shared volumes with mixed labels, you can preserve the legacy recursive relabeling behavior in your Pod spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared-data-consumer&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;seLinuxChangePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recursive&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared-storage&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared-storage&lt;/span&gt;
      &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared-data-pvc&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where This Leaves Us
&lt;/h3&gt;

&lt;p&gt;Do not rush to upgrade production this weekend. If you use a managed provider (EKS, GKE, Rancher), wait for &lt;code&gt;.1&lt;/code&gt; or &lt;code&gt;.2&lt;/code&gt; patch releases. If you run physical data centers like me and manage the control plane yourself, be even more conservative: test locally, promote through lower environments, and comfortably stay on n-1 or n-2.&lt;/p&gt;

&lt;p&gt;The real reason to track v1.37 today is preventing fresh technical debt. If your team was about to build custom controllers to sync CA certs or write scripts to hunt dead storage, tell them to hold off. Upstream is solving it natively.&lt;/p&gt;

&lt;p&gt;Let the early patches bake, keep your clusters healthy, and start planning which legacy workarounds you get to delete.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloudnative</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Multi-region YugabyteDB deployment on AWS EKS with Istio</title>
      <dc:creator>Vishnu Hari Dadhich</dc:creator>
      <pubDate>Thu, 02 May 2024 06:34:52 +0000</pubDate>
      <link>https://dev.to/vishnuhd/multi-region-yugabytedb-deployment-on-aws-eks-with-istio-2ng5</link>
      <guid>https://dev.to/vishnuhd/multi-region-yugabytedb-deployment-on-aws-eks-with-istio-2ng5</guid>
      <description>&lt;p&gt;In today’s distributed cloud landscape, deploying applications across multiple regions and clusters is crucial for scalability, reliability, and performance. This blog post will guide you through setting up a multi-region, multi-cluster YugabyteDB deployment on AWS EKS with Istio service mesh.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY YUGABYTEDB?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.yugabyte.com/" rel="noopener noreferrer"&gt;YugabyteDB&lt;/a&gt; is a transactional database that brings together four must-have needs of cloud native apps – namely SQL as a flexible query language, low-latency performance, continuous availability, and globally-distributed scalability. Other databases do not serve all 4 of these needs simultaneously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolithic SQL databases offer SQL and low-latency reads, but neither have the ability to tolerate failures, nor can they scale writes across multiple nodes, zones, regions, and clouds.&lt;/li&gt;
&lt;li&gt;Distributed NoSQL databases offer read performance, high availability, and write scalability, but give up on SQL features such as relational data modelling and ACID transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WHY AWS EKS AND ISTIO?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;AWS EKS&lt;/a&gt; provides a managed Kubernetes service, simplifying cluster management and deployment. &lt;a href="https://istio.io/" rel="noopener noreferrer"&gt;Istio&lt;/a&gt;, an open-source service mesh, enables traffic management, security, and observability across microservices.&lt;/p&gt;

&lt;p&gt;Combining Yugabyte with AWS EKS and Istio creates a robust, scalable, and secure cloud-native architecture that spans across multiple regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  DEPLOYMENT OVERVIEW
&lt;/h2&gt;

&lt;p&gt;Our deployment consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three AWS regions (Singapore, Mumbai and Hyderabad).&lt;/li&gt;
&lt;li&gt;One EKS cluster in each region.&lt;/li&gt;
&lt;li&gt;One YugabyteDB master and one YugabyteDB tserver are deployed in each EKS cluster.&lt;/li&gt;
&lt;li&gt;Istio is deployed in each cluster with an east-west gateway to provide a multi-cluster service mesh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk54aoeavtasdf0u4myag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk54aoeavtasdf0u4myag.png" alt=" " width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DEPLOYMENT STEPS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS account with at least three regions enabled&lt;/li&gt;
&lt;li&gt;AWS user with access to create VPC and EKS using eksctl
eksctl, aws cli, kubectl and git installed on your local system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone the following repo to follow along with this blog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/vishnuhd/yugabyte-multiregion-aws-eks-istio.git
cd yugabyte-multiregion-aws-eks-istio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy AWS EKS clusters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deploy EKS clusters in three different regions (namely Singapore, Mumbai and Hyderabad) using eksctl:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Creating EKS cluster in ${region}...\n"
    eksctl create cluster -f ${region}/cluster-config.yaml
    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Rename the kube contexts for the simplicity of this demo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config rename-context 'yb-mumbai.ap-south-1.eksctl.io' mumbai
kubectl config rename-context 'yb-singapore.ap-southeast-1.eksctl.io' singapore
kubectl config rename-context 'yb-hyderabad.ap-south-2.eksctl.io' hyderabad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; By default, EKS does not provide EBS permissions, follow this article to enable EKS PVC dynamic provisioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup Istio
&lt;/h3&gt;

&lt;p&gt;When configuring a production deployment of Istio, key considerations include whether the mesh will be in single or multiple clusters, Istio control plane setup for high availability, and the choice between a single multicluster service mesh or federated multi-mesh deployment. These factors represent independent dimensions of configuration for Istio deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://istio.io/latest/docs/ops/deployment/deployment-models/" rel="noopener noreferrer"&gt;This&lt;/a&gt; guide describes the various options and considerations when configuring your Istio deployment. For this demo, we are gonna &lt;a href="https://istio.io/latest/docs/setup/install/multicluster/multi-primary_multi-network/" rel="noopener noreferrer"&gt;Install Multi-Primary on different networks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Download Istio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://istio.io/downloadIstio | sh -
cd istio-1.21.0
export PATH=$PWD/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Plug in CA Certificates for Istio
&lt;/h3&gt;

&lt;p&gt;In a multi-cluster environment, we would want to set up one root CA and use the root CA to issue intermediate certificates to the Istio CAs that run in each cluster. This would ensure the services behind it can only be accessed by services with a trusted mTLS certificate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a cert directory :
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p istio-1.21.0/certs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate the root CA certificate and key:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd istio-1.21.0/certs
make -f ../tools/certs/Makefile.selfsigned.mk root-ca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;For each cluster, generate an intermediate certificate and key for the Istio CA:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd istio-1.21.0/certs

{
  for region in mumbai singapore hyderabad; do
    echo -e "Generating certs for cluster - ${region}...\n"
    make -f ../tools/certs/Makefile.selfsigned.mk yb-${region}-cacerts
    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In each cluster, create a secret called &lt;code&gt;cacerts&lt;/code&gt; including all the input files &lt;code&gt;ca-cert.pem&lt;/code&gt;, &lt;code&gt;ca-key.pem&lt;/code&gt;, &lt;code&gt;root-cert.pem&lt;/code&gt; and &lt;code&gt;cert-chain.pem&lt;/code&gt; :
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Creating namespace and secret for cluster - ${region}...\n"

    kubectl --context ${region} create namespace istio-system
    kubectl --context ${region} create secret generic cacerts -n istio-system \
          --from-file=istio-1.21.0/certs/yb-${region}/ca-cert.pem \
          --from-file=istio-1.21.0/certs/yb-${region}/ca-key.pem \
          --from-file=istio-1.21.0/certs/yb-${region}/root-cert.pem \
          --from-file=istio-1.21.0/certs/yb-${region}/cert-chain.pem

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this step completed, we are now prepared to install Istio on every cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Istio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install Istio using istioctl:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Installing istio for cluster - ${region}...\n"

    istioctl install --context ${region} -f ./${region}/istio.yaml

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install a gateway in each cluster, that is dedicated to &lt;a href="https://en.wikipedia.org/wiki/East-west_traffic" rel="noopener noreferrer"&gt;east-west&lt;/a&gt; traffic. By default, this gateway will be public on the Internet. Production systems may require additional access restrictions (e.g. via firewall rules) to prevent external attacks.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Installing the east-west gateway for cluster - ${region}...\n"

    ./istio-1.21.0/samples/multicluster/gen-eastwest-gateway.sh \
        --mesh mesh1 --cluster yb-${region} --network network-${region} | \
        istioctl --context ${region} install -y -f -

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Since the clusters are on separate networks, we need to expose all services (&lt;code&gt;*.local&lt;/code&gt;) on the east-west gateway in all three clusters. While this gateway is public on the Internet, services behind it can only be accessed by services with a trusted mTLS certificate and workload ID, just as if they were on the same network.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Exposing the services for cluster - ${region}...\n"

    kubectl --context ${region} apply -n istio-system -f \
        ./istio-1.21.0/samples/multicluster/expose-services.yaml

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install a remote secret in each cluster that provides access to the other cluster’s Kube API server.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region1 in mumbai singapore hyderabad; do
    for region2 in mumbai singapore hyderabad; do
      if [[ "${region1}" == "${region2}" ]]; then continue; fi
      echo -e "Create remote secret of ${region1} in ${region2}...\n"

      istioctl create-remote-secret \
        --context ${region1} \
        --name=yb-${region1} | \
        kubectl apply -f - --context ${region2}

      echo -e "-------------\n"
    done
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install YugabyteDB
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To install YugabyteDB using helm charts, add the chart repo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add yugabytedb https://charts.yugabyte.com
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a YugabyteDB namespace (&lt;code&gt;yb-demo&lt;/code&gt;) in each cluster and enable Istio injection:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Creating namespace for cluster - ${region}...\n"

    kubectl --context ${region} create namespace yb-demo
    kubectl label --context ${region} namespace yb-demo istio-injection=enabled

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install YugabyteDB:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Installing YugabyteDB in cluster - ${region}...\n"

    helm upgrade --install ${region} yugabytedb/yugabyte \
        --version 2.19.3 \
        --namespace yb-demo \
        -f ${region}/overrides.yaml \
        --kube-context ${region} --wait

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install YugabyteDB in each EKS cluster with 1 master and 1 tserver connected with each other through the Istio service mesh. At this point, it is important to understand each parameter being set in the &lt;a href="https://github.com/vishnuhd/yugabyte-multiregion-aws-eks-istio/blob/main/singapore/overrides.yaml" rel="noopener noreferrer"&gt;overrides.yaml&lt;/a&gt; file. Each master and tserver pod needs to know all the master addresses to replicate data and elect leaders.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In addition to the Istio setup, an extra step is required, which involves creating identical Kubernetes services in all clusters to enable DNS service discovery. More information can be found &lt;a href="https://istio.io/latest/docs/ops/deployment/deployment-models/#dns-with-multiple-clusters" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Therefore, we need to replicate the yugabyte-master and yugabyte-tserver services present in the Mumbai region to both the Singapore and Hyderabad regions, and vice versa.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region1 in mumbai singapore hyderabad; do
    for region2 in mumbai singapore hyderabad; do
      if [[ "${region1}" == "${region2}" ]]; then continue; fi
      echo -e "Creating services of ${region2} in ${region1}...\n"

      kubectl --context ${region1} apply -f ${region1}/services-${region2}.yaml -n yb-demo

      echo -e "-------------\n"
    done
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check the YugabyteDB pods and services, all of them should be up and running:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Checking YugabyteDB pods and svcs for cluster - ${region}...\n"

    kubectl --context ${region} get pods,svc -A

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, we need to configure global data distribution, for Yugabyte to handle the data distribution properly across regions:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl --context mumbai exec -n yb-demo mumbai-yugabyte-yb-master-0 -- bash \
-c "/home/yugabyte/master/bin/yb-admin --master_addresses mumbai-yugabyte-yb-master-0.yb-demo.svc.cluster.local,hyderabad-yugabyte-yb-master-0.yb-demo.svc.cluster.local,singapore-yugabyte-yb-master-0.yb-demo.svc.cluster.local modify_placement_info aws.ap-south-1.ap-south-1a,aws.ap-south-2.ap-south-2a,aws.ap-southeast-1.ap-southeast-1a 3"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voila, your YugabyteDB multi-regional setup is now complete!&lt;/p&gt;

&lt;h2&gt;
  
  
  Access the YugabyteDB UI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Find the yb-master-ui service in yb-demo namespace for any cluster and open it in the browser along with the port 7000:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq5k6du2a13ov8i0qokk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq5k6du2a13ov8i0qokk.png" alt=" " width="799" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see the masters are spread across regions, with Hyderabad one as the leader.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore the tablet servers:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dniin0za79c9iwz469w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dniin0za79c9iwz469w.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, we can see the tablet servers being distributed across multiple regions, each of them able to hand synchronous reads and writes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a sample Yugabyte application in any of the clusters:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl run yb-sample-apps \
    -it --rm \
    --image yugabytedb/yb-sample-apps \
    --namespace yb-demo \
    --context singapore \
    --command -- sh

java -jar yb-sample-apps.jar java-client-sql \
    --workload SqlInserts \
    --nodes yb-tserver-common.yb-demo.svc.cluster.local:5433 \
    --num_threads_write 1 \
    --num_threads_read 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we are targeting yb-tserver-common service for reads and writes, which will choose any of the tserver in any of the regions randomly. This also helps in load-balancing the traffic across regions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can also see the tables created by this sample app:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvq2q4b1z8pqxcbuuu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvq2q4b1z8pqxcbuuu7.png" alt=" " width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DISASTER RECOVERY
&lt;/h2&gt;

&lt;p&gt;Disasters can happen anytime, YugabyteDB provides us with a feature called Replication Factor (RF). Configurations usually include a Replication Factor (RF) of 3. In this setup, a write to the leader requires an acknowledgement from one follower before being committed, as the leader and one follower together constitute the majority. In the event of a failure, operational replicas in the Raft group can support consistent reads and writes, while those that have become separated from the Raft consensus quorum cannot make progress. We will now see this feature of Yugabyte in action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current leaders for master and tservers:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycv2kzitwrb3zx3jijwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycv2kzitwrb3zx3jijwj.png" alt=" " width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghhd1jlktxwcwnkhfgw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghhd1jlktxwcwnkhfgw6.png" alt=" " width="720" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, the master pod in the Hyderabad region is the LEADER, while most of the transactions are handled by the tablet server in the Mumbai region.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let’s make the Hyderabad region go down:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl scale sts hyderabad-yugabyte-yb-master-0 --replicas 0 -n yb-demo --context hyderabad
kubectl scale sts hyderabad-yugabyte-yb-tserver-0 --replicas 0 -n yb-demo --context hyderabad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as we decrease the pod replicas to 0 for both master and tserver, we can see the errors in the Yugabyte master UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgwiz5wu0pnhi2aftzsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgwiz5wu0pnhi2aftzsy.png" alt=" " width="720" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tlrki89ovvb2mr6eh9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tlrki89ovvb2mr6eh9x.png" alt=" " width="720" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see that the master server from the Mumbai region is elected as a new Leader and all transactions are still operating even if one whole region is down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Yugabyte also keeps track of the under-replicated tables, so that they can be replicated to the region as soon as it comes back online:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95zwq5jg9ygaezmr218h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95zwq5jg9ygaezmr218h.png" alt=" " width="720" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the region comes online again, the data is replicated back to the Hyderabad region:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxn3jjrhbwg6si6ot92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxn3jjrhbwg6si6ot92.png" alt=" " width="720" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgge9hlllzsv94vkqs9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgge9hlllzsv94vkqs9j.png" alt=" " width="720" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywcigexaok8uzcyo94j0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywcigexaok8uzcyo94j0.png" alt=" " width="720" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome, now you have a multi-regional fault-tolerant YugabyteDB setup on AWS EKS clusters using Istio as a service mesh.&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS: SETUP KIALI FOR OBSERVABILITY
&lt;/h2&gt;

&lt;p&gt;The Istio download package comes by default with Kiali and Prometheus, let’s set it up to have a nice view of all the services in the mesh.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Kiali and Prometheus:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Checking Kiali and Prometheus for cluster - ${region}...\n"

    kubectl apply -f istio-1.21.0/samples/addons/prometheus.yaml --context ${region}
    kubectl apply -f istio-1.21.0/samples/addons/kiali.yaml --context ${region}

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Open Kiali dashboard:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;istioctl dashboard kiali --context singapore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh05al0h097vqpocfxi7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh05al0h097vqpocfxi7t.png" alt=" " width="720" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above Kiali graph shows the various clusters and services from the POV of EKS cluster in the Singapore region.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLEANING UP THE RESOURCES
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Uninstall YugabyteDB:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Un-installing YugabyteDB in cluster - ${region}...\n"

    helm uninstall ${region} --namespace yb-demo --kube-context ${region}
    kubectl delete pvc --namespace yb-demo \
      --selector component=yugabytedb,release=${region} \
      --context ${region}

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Delete additional YB services:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region1 in mumbai singapore hyderabad; do
    for region2 in mumbai singapore hyderabad; do
      if [[ "${region1}" == "${region2}" ]]; then continue; fi
      echo -e "Deleting services of ${region2} in ${region1}...\n"

      kubectl --context ${region1} delete -f ${region1}/services-${region2}.yaml -n yb-demo

      echo -e "-------------\n"
    done
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Un-install Kiali and Prometheus:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Checking Kiali and Prometheus for cluster - ${region}...\n"

    kubectl delete -f istio-1.21.0/samples/addons/prometheus.yaml --context ${region}
    kubectl delete -f istio-1.21.0/samples/addons/kiali.yaml --context ${region}

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uninstall Istio:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  for region in mumbai singapore hyderabad; do
    echo -e "Un-installing Istio in cluster - ${region}...\n"

    istioctl uninstall --purge -y --context ${region}

    echo -e "-------------\n"
  done
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Delete EKS clusters:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl delete cluster yb-mumbai --region ap-south-1
eksctl delete cluster yb-singapore --region ap-southeast-1
eksctl delete cluster yb-hyderabad --region ap-south-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CONCLUSION
&lt;/h2&gt;

&lt;p&gt;A multi-region, multi-cluster YugabyteDB deployment on AWS EKS with Istio provides a highly available, scalable, and secure architecture for distributed applications. By leveraging YugabyteDB deployed on multiple AWS regions and EKS clusters, this setup ensures redundancy and failover capabilities, minimizing downtime and ensuring business continuity. Istio’s service mesh capabilities provide advanced traffic management, security, and observability features, allowing for fine-grained control and monitoring of the application traffic. This setup is ideal for organizations requiring a robust and resilient infrastructure for their critical applications.&lt;/p&gt;

&lt;p&gt;The original tech blog is &lt;a href="https://dvops.wordpress.com/2024/04/27/multi-region-yugabytedb-deployment-on-aws-eks-with-istio/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, please follow/subscribe to get notifications directly in your inbox when new content goes live. You can also find me on LinkedIn @ &lt;a href="https://www.linkedin.com/in/vishnuhd/" rel="noopener noreferrer"&gt;in/vishnuhd&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>istio</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>yugabytedb</category>
    </item>
  </channel>
</rss>
