<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Llama-Cpp on vishctl</title><link>https://vishctl.dev/tags/llama-cpp/</link><description>Recent content in Llama-Cpp on vishctl</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 03 Sep 2026 10:30:00 +0800</lastBuildDate><atom:link href="https://vishctl.dev/tags/llama-cpp/index.xml" rel="self" type="application/rss+xml"/><item><title>Running llama.cpp on a 32 GB MacBook Air: A Direct Comparison with Ollama</title><link>https://vishctl.dev/posts/running-llama-cpp-on-32gb-macbook-air/</link><pubDate>Thu, 03 Sep 2026 10:30:00 +0800</pubDate><guid>https://vishctl.dev/posts/running-llama-cpp-on-32gb-macbook-air/</guid><category>ai</category><category>local-llm</category><category>llama-cpp</category><category>ollama</category><category>apple-silicon</category><category>benchmarks</category><description>Compile llama.cpp with Metal support on Apple Silicon, serve local GGUF models, and benchmark prompt eval and generation speeds head-to-head against Ollama.</description><content:encoded><![CDATA[<p>In the <a href="https://vishctl.dev/posts/running-ollama-on-32gb-macbook-air/">previous post</a>, 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 <a href="https://github.com/ggml-org/llama.cpp">llama.cpp</a>.</p>
<p>In this post, we go one level down: building and running llama.cpp directly, offloading inference to Apple Silicon&rsquo;s Metal GPU, and comparing performance numbers side by side with Ollama on the exact same model and quantization level.</p>
<h3 id="step-1-build-llamacpp-from-source">Step 1: Build llama.cpp from Source</h3>
<p>Building llama.cpp from source on macOS is fast and straightforward. On Apple Silicon, CMake automatically enables Metal support (<code>GGML_METAL=ON</code>) and compiles GPU compute kernels tailored for Apple&rsquo;s unified memory architecture.</p>
<p>Clone the repository and compile the release binaries using all available CPU cores:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/ggml-org/llama.cpp
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> llama.cpp
</span></span><span class="line"><span class="cl">cmake -B build -DCMAKE_BUILD_TYPE<span class="o">=</span>Release
</span></span><span class="line"><span class="cl">cmake --build build --config Release -j<span class="k">$(</span>sysctl -n hw.logicalcpu<span class="k">)</span>
</span></span></code></pre></div><p>Once the build finishes, the binaries live in <code>build/bin</code>. Add them to your current session&rsquo;s <code>$PATH</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span><span class="s2">/build/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>To make this permanent across terminal sessions, append it to your shell configuration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;export PATH=\&#34;</span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span><span class="s2">/build/bin:\$PATH\&#34;&#34;</span> &gt;&gt; ~/.zshrc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.zshrc
</span></span></code></pre></div><p>Confirm that the build succeeded:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server --version
</span></span></code></pre></div><h3 id="step-2-serve-the-model-out-of-the-box-baseline">Step 2: Serve the Model (Out-of-the-Box Baseline)</h3>
<p>llama.cpp can download GGUF models directly from Hugging Face Hub using the <code>-hf</code> flag, caching weights under <code>~/.cache/huggingface/hub</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -hf ornith-ai/Ornith-1.5-9B-GGUF --port <span class="m">8080</span> -ngl <span class="m">99</span>
</span></span></code></pre></div><p>Here is what the flags do:</p>
<ul>
<li><code>-hf ornith-ai/Ornith-1.5-9B-GGUF</code>: Resolves the model on Hugging Face and downloads the default <code>Q4_K_M</code> quant.</li>
<li><code>-ngl 99</code>: Offloads all 99 model layers to the GPU (Metal on Apple Silicon unified memory).</li>
<li><code>--port 8080</code>: Binds the OpenAI-compatible HTTP server to port 8080.</li>
</ul>
<figure class="post-screenshot">
    <img loading="lazy" src="https://vishctl.dev/images/posts/llama-cpp-32gb-macbook-air/01-llama-server-start.png"
         alt="Starting llama-server and downloading Ornith 1.5 9B from Hugging Face"/> <figcaption>
            <p>Starting llama-server with automatic Hugging Face download and full Metal GPU offload.</p>
        </figcaption>
</figure>

<p>During startup, llama.cpp downloads both the multimodal vision projector (<code>mmproj-Ornith-1.5-9B-BF16.gguf</code>) and the main model weights (<code>Ornith-1.5-9B-Q4_K_M.gguf</code>), offloads the layers into Metal, initializes inference slots, and listens on port 8080.</p>
<p>We test this default setup with a short greeting prompt through the OpenAI-compatible API:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl http://localhost:8080/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;model&#34;: &#34;ornith-1.5-9b&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Hello!&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">  }&#39;</span>
</span></span></code></pre></div><figure class="post-screenshot">
    <img loading="lazy" src="https://vishctl.dev/images/posts/llama-cpp-32gb-macbook-air/02-llama-server-first-request.png"
         alt="Calling the llama-server chat completions API"/> <figcaption>
            <p>First chat completion request via curl, returning detailed server-side timing breakdowns.</p>
        </figcaption>
</figure>

<p>The response returns a complete timing breakdown in the <code>timings</code> object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">prompt eval time = 496.21 ms / 12 tokens (24.18 tokens per second)
</span></span><span class="line"><span class="cl">eval time        = 5527.17 ms / 96 tokens (17.19 tokens per second)
</span></span></code></pre></div><p>Compare that to Ollama&rsquo;s numbers on the same model and quant from the <a href="https://vishctl.dev/posts/running-ollama-on-32gb-macbook-air/">earlier post</a>:</p>
<table>
	<thead>
			<tr>
					<th>Metric</th>
					<th>Ollama (Default)</th>
					<th>llama.cpp (Default, no <code>-fa</code>)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Prompt Eval Speed</strong></td>
					<td><strong>37.13 tokens/s</strong></td>
					<td>24.18 tokens/s</td>
			</tr>
			<tr>
					<td><strong>Generation Speed</strong></td>
					<td>16.68 tokens/s</td>
					<td><strong>17.19 tokens/s</strong></td>
			</tr>
	</tbody>
</table>
<p>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&rsquo;s 37.13 tokens/s).</p>
<p>Ollama enables FlashAttention and sensible batch sizes by default, while raw llama.cpp keeps conservative baseline settings unless configured explicitly.</p>
<h3 id="step-3-verifying-the-quants">Step 3: Verifying the Quants</h3>
<p>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.</p>
<p>Check the quant llama.cpp loaded:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s http://localhost:8080/v1/models <span class="p">|</span> jq
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;ftype&#34;</span><span class="err">:</span> <span class="s2">&#34;Q4_K - Medium&#34;</span>
</span></span></code></pre></div><p>Check the quant Ollama is using:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s http://localhost:11434/api/show -d <span class="s1">&#39;{&#34;model&#34;: &#34;ornith-1.5:9b&#34;}&#39;</span> <span class="p">|</span> jq <span class="s1">&#39;.details.quantization_level&#39;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;Q4_K_M&#34;</span>
</span></span></code></pre></div><p>Both runtimes are confirmed to be executing <code>Q4_K_M</code>. Precision and model size are strictly identical.</p>
<h3 id="step-4-leveling-the-playing-field-with-flash-attention--batch-tuning">Step 4: Leveling the Playing Field with Flash Attention &amp; Batch Tuning</h3>
<p>To give llama.cpp parity with Ollama&rsquo;s runtime optimizations, we restart <code>llama-server</code> with FlashAttention enabled and explicit batch sizes configured:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -hf ornith-ai/Ornith-1.5-9B-GGUF --port <span class="m">8080</span> -ngl <span class="m">99</span> -fa on -b <span class="m">2048</span> -ub <span class="m">512</span>
</span></span></code></pre></div><p>Here is what these tuning flags configure:</p>
<ul>
<li><code>-fa on</code> (or <code>--flash-attn on</code>): Enables FlashAttention kernels for Apple Silicon Metal. This significantly accelerates prompt evaluation (prefill) and reduces memory bandwidth overhead.</li>
<li><code>-b 2048</code> (<code>--batch-size</code>): Sets the logical batch size for prompt evaluation.</li>
<li><code>-ub 512</code> (<code>--ubatch-size</code>): Sets the physical micro-batch size dispatched to Metal compute passes, keeping the GPU pipelines fully saturated.</li>
</ul>
<h3 id="step-5-side-by-side-benchmark-on-longer-generation">Step 5: Side-by-Side Benchmark on Longer Generation</h3>
<p>Both engines were given the same prompt:</p>
<blockquote>
<p><code>&quot;give a 25 line story&quot;</code></p>
</blockquote>
<p>A longer generation produces several hundred tokens, giving a much more reliable measurement of steady-state generation speed than short one-liners.</p>
<p>Here are the side-by-side results on the 32 GB MacBook Air:</p>
<table>
	<thead>
			<tr>
					<th>Metric</th>
					<th>Ollama (<code>ornith-1.5:9b</code>)</th>
					<th>llama.cpp (<code>-fa on -b 2048 -ub 512</code>)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Prompt Eval Count</strong></td>
					<td>17 tokens</td>
					<td>18 tokens</td>
			</tr>
			<tr>
					<td><strong>Prompt Eval Speed</strong></td>
					<td>46.01 tokens/s</td>
					<td><strong>48.68 tokens/s</strong></td>
			</tr>
			<tr>
					<td><strong>Generation Count</strong></td>
					<td>496 tokens</td>
					<td>436 tokens</td>
			</tr>
			<tr>
					<td><strong>Generation Speed</strong></td>
					<td>16.42 tokens/s</td>
					<td><strong>17.07 tokens/s</strong></td>
			</tr>
			<tr>
					<td><strong>Total Duration</strong></td>
					<td>30.58 s</td>
					<td><strong>25.85 s</strong></td>
			</tr>
	</tbody>
</table>
<p>With FlashAttention and batch tuning enabled, llama.cpp matches or slightly edges out Ollama across both prompt evaluation (<strong>48.68 vs. 46.01 tokens/s</strong>) and token generation speed (<strong>17.07 vs. 16.42 tokens/s</strong>).</p>
<h4 id="ollama-command--stats">Ollama Command &amp; Stats</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ollama run ornith-1.5:9b --verbose
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; give a <span class="m">25</span> line story
</span></span><span class="line"><span class="cl">Thinking...
</span></span><span class="line"><span class="cl">...done thinking.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>story output omitted<span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">total duration:       30.582007541s
</span></span><span class="line"><span class="cl">load duration:        2.821208ms
</span></span><span class="line"><span class="cl">prompt <span class="nb">eval</span> count:    <span class="m">17</span> token<span class="o">(</span>s<span class="o">)</span>
</span></span><span class="line"><span class="cl">prompt <span class="nb">eval</span> duration: 369.481ms
</span></span><span class="line"><span class="cl">prompt <span class="nb">eval</span> rate:     46.01 tokens/s
</span></span><span class="line"><span class="cl"><span class="nb">eval</span> count:           <span class="m">496</span> token<span class="o">(</span>s<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">eval</span> duration:        30.204706s
</span></span><span class="line"><span class="cl"><span class="nb">eval</span> rate:            16.42 tokens/s
</span></span></code></pre></div><h4 id="llamacpp-server-startup--timing-logs">llama.cpp Server Startup &amp; Timing Logs</h4>
<p>Server startup and live slot execution log:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">➜  ~ llama-server -hf ornith-ai/Ornith-1.5-9B-GGUF --port 8080 -ngl 99 -fa on -b 2048 -ub 512
</span></span><span class="line"><span class="cl">0.00.729.018 I cmn  common_param: common_params_print_info: verbosity = 3 (adjust with the `-lv N` CLI arg)
</span></span><span class="line"><span class="cl">0.00.729.664 W srv  llama_server: -----------------
</span></span><span class="line"><span class="cl">0.00.729.667 W srv  llama_server: CORS is set to allow all origins (&#39;*&#39;) and no API key is set
</span></span><span class="line"><span class="cl">0.00.729.668 W srv  llama_server: this can be a security risk (cross-origin attacks)
</span></span><span class="line"><span class="cl">0.00.729.668 W srv  llama_server: more info: https://github.com/ggml-org/llama.cpp/pull/25655
</span></span><span class="line"><span class="cl">0.00.729.669 W srv  llama_server: -----------------
</span></span><span class="line"><span class="cl">0.00.731.435 I srv    load_model: loading model &#39;ornith-ai/Ornith-1.5-9B-GGUF&#39;
</span></span><span class="line"><span class="cl">0.01.177.317 W model has unused tensor blk.32.attn_norm.weight (size = 16384 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.324 W model has unused tensor blk.32.post_attention_norm.weight (size = 16384 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.329 W model has unused tensor blk.32.attn_q.weight (size = 18874368 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.331 W model has unused tensor blk.32.attn_k.weight (size = 2359296 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.333 W model has unused tensor blk.32.attn_v.weight (size = 3440640 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.338 W model has unused tensor blk.32.attn_output.weight (size = 9437184 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.340 W model has unused tensor blk.32.attn_q_norm.weight (size = 1024 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.341 W model has unused tensor blk.32.attn_k_norm.weight (size = 1024 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.343 W model has unused tensor blk.32.ffn_gate.weight (size = 28311552 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.345 W model has unused tensor blk.32.ffn_down.weight (size = 41287680 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.347 W model has unused tensor blk.32.ffn_up.weight (size = 28311552 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.350 W model has unused tensor blk.32.nextn.eh_proj.weight (size = 18874368 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.353 W model has unused tensor blk.32.nextn.enorm.weight (size = 16384 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.354 W model has unused tensor blk.32.nextn.hnorm.weight (size = 16384 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.177.359 W model has unused tensor blk.32.nextn.shared_head_norm.weight (size = 16384 bytes) -- ignoring
</span></span><span class="line"><span class="cl">0.01.799.972 I cmn          init: llama threadpool init, n_threads = 4
</span></span><span class="line"><span class="cl">0.01.959.375 W load_hparams: Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks
</span></span><span class="line"><span class="cl">0.01.959.377 W load_hparams: if you encounter problems with accuracy, try adding --image-min-tokens 1024
</span></span><span class="line"><span class="cl">0.01.959.377 W load_hparams: more info: https://github.com/ggml-org/llama.cpp/issues/16842
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0.02.172.437 I srv    load_model: loaded multimodal model, &#39;/Users/vishnuhd/.cache/huggingface/hub/models--ornith-ai--Ornith-1.5-9B-GGUF/snapshots/abdd624b12ebf020b767fff532ff44fe552b28c3/mmproj-Ornith-1.5-9B-BF16.gguf&#39;
</span></span><span class="line"><span class="cl">0.02.399.797 I srv    load_model: initializing, n_slots = 4, n_ctx_slot = 262144, kv_unified = &#39;true&#39;
</span></span><span class="line"><span class="cl">0.02.402.615 I srv          init: chat template supports preserving reasoning, consider enabling it via --reasoning-preserve
</span></span><span class="line"><span class="cl">0.02.402.621 I srv  llama_server: model loaded
</span></span><span class="line"><span class="cl">0.02.402.623 I srv  llama_server: listening on http://127.0.0.1:8080
</span></span><span class="line"><span class="cl">0.02.402.623 W srv  llama_server: NOTICE: server default port will be changed to :9931 in a future release
</span></span><span class="line"><span class="cl">0.02.402.623 W srv  llama_server:         ref: https://github.com/ggml-org/llama.cpp/pull/26508
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0.36.386.802 I slot get_availabl: id  3 | task -1 | selected slot by LRU, t_last = -1
</span></span><span class="line"><span class="cl">0.36.386.826 I slot launch_slot_: id  3 | task 0 | processing task, is_child = 0
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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
</span></span><span class="line"><span class="cl">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)
</span></span><span class="line"><span class="cl">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)
</span></span><span class="line"><span class="cl">1.02.233.157 I slot print_timing: id  3 | task 0 |       total time =   25845.92 ms /   454 tokens
</span></span><span class="line"><span class="cl">1.02.233.159 I slot print_timing: id  3 | task 0 |    graphs reused =        434
</span></span><span class="line"><span class="cl">1.02.233.187 I slot      release: id  3 | task 0 | stop processing: n_tokens = 453, truncated = 0
</span></span></code></pre></div><p>And the raw curl request (with story output trimmed):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl http://localhost:8080/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;model&#34;: &#34;ornith-1.5-9b&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Give me a 25 line story&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">  }&#39;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;choices&#34;</span><span class="p">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;finish_reason&#34;</span><span class="p">:</span> <span class="s2">&#34;stop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;index&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;assistant&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;[story text omitted]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;reasoning_content&#34;</span><span class="p">:</span> <span class="s2">&#34;[thinking omitted]&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="mi">1788423730</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;model&#34;</span><span class="p">:</span> <span class="s2">&#34;ornith-ai/Ornith-1.5-9B-GGUF&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;system_fingerprint&#34;</span><span class="p">:</span> <span class="s2">&#34;b10712-daef7b687&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;chat.completion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;usage&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;completion_tokens&#34;</span><span class="p">:</span> <span class="mi">436</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_tokens&#34;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;total_tokens&#34;</span><span class="p">:</span> <span class="mi">454</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_tokens_details&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;cached_tokens&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;chatcmpl-dLoeYkd6rvuFieOvUSr9dBxB798twvZi&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;timings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cache_n&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_n&#34;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_ms&#34;</span><span class="p">:</span> <span class="mf">369.742</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_per_token_ms&#34;</span><span class="p">:</span> <span class="mf">20.54</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prompt_per_second&#34;</span><span class="p">:</span> <span class="mf">48.68</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predicted_n&#34;</span><span class="p">:</span> <span class="mi">436</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predicted_ms&#34;</span><span class="p">:</span> <span class="mf">25476.174</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predicted_per_token_ms&#34;</span><span class="p">:</span> <span class="mf">58.57</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predicted_per_second&#34;</span><span class="p">:</span> <span class="mf">17.07</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="key-takeaways">Key Takeaways</h3>
<ol>
<li><strong>Ollama is not faster than llama.cpp</strong>: 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.</li>
<li><strong>FlashAttention is essential on Apple Silicon</strong>: Enabling <code>-fa on</code> doubled prompt evaluation speed from <strong>24.18 tokens/s to 48.68 tokens/s</strong>, drastically cutting time-to-first-token.</li>
<li><strong>Consistent throughput</strong>: At 9B parameters with Q4_K_M quantization, Apple Silicon unified memory sustains a rock-solid <strong>~17.1 tokens/sec</strong>.</li>
<li><strong>Why run llama.cpp directly?</strong>: 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 (<code>-ctk</code>, <code>-ctv</code>), and immediate access to upstream features and bugfixes.</li>
</ol>
<p>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.</p>
]]></content:encoded></item></channel></rss>