<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Fring dev notes]]></title><description><![CDATA[Fring dev notes]]></description><link>https://fring.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 29 Oct 2026 04:08:04 GMT</lastBuildDate><atom:link href="https://fring.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why I dropped Ollama for llama-server on a Tesla P40 (and got 2× faster vision)]]></title><description><![CDATA[Qwen3-VL-8B on a 2016 Pascal card: 1.3 s per photo instead of 2.9 s, 6.4 GB instead of 7.6 GB]]></description><link>https://fring.hashnode.dev/why-i-dropped-ollama-for-llama-server-on-a-tesla-p40-and-got-2-faster-vision</link><guid isPermaLink="true">https://fring.hashnode.dev/why-i-dropped-ollama-for-llama-server-on-a-tesla-p40-and-got-2-faster-vision</guid><category><![CDATA[llm]]></category><category><![CDATA[GPU]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[ollama]]></category><category><![CDATA[self-hosted]]></category><dc:creator><![CDATA[Sevan Safarian]]></dc:creator><pubDate>Mon, 26 Oct 2026 18:58:52 GMT</pubDate><content:encoded><![CDATA[<p>I run a small free web app, <a href="https://fring.sevan.zone/en?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=llamacpp-p40">Fring</a>, a digital wardrobe: you photograph your clothes and a vision model fills in type, colour, material and a short description, then suggests outfits from what you own. All the AI runs on one second-hand <strong>Tesla P40</strong> (24 GB, Pascal, 2016) in my home lab. No cloud.</p>
<p>For months the vision model ran under Ollama. In September I moved it to plain <code>llama-server</code> from llama.cpp. Here is why, with the numbers.</p>
<h2>The card sets the rules</h2>
<p>The P40 is cheap VRAM with old silicon:</p>
<ul>
<li>compute capability <strong>6.1</strong>, no tensor cores, no bf16;</li>
<li>fp16 runs at <strong>1/64 of fp32</strong> (I <a href="https://fring.hashnode.dev/on-a-tesla-p40-fp16-made-my-diffusion-model-14x-slower-than-fp32">learned that the expensive way</a>);</li>
<li>my Debian 13 host has NVIDIA driver <strong>550</strong>. The R580 branch is the last one that supports Pascal, and CUDA 13 dropped it.</li>
</ul>
<p>That last point matters more than it looks: it freezes which versions of everything you can run.</p>
<h2>Where Ollama got stuck</h2>
<p>Ollama is a convenient layer on top of llama.cpp. On this card the layer started to cost more than it gave:</p>
<ol>
<li><strong>Version freeze.</strong> Ollama 0.13+ needs driver 570 for Pascal. I was stuck on 0.12.x, so newer models (and newer vision support) were out of reach.</li>
<li><strong>A regression on P40s.</strong> Versions 0.30 to 0.32 loaded everything on the CPU with P40s (ollama#17290). Upgrading was not an option either.</li>
<li><strong>Forced context.</strong> With <code>qwen3-vl</code>, Ollama 0.12 set an 8192-token context whatever <code>num_ctx</code> said. That is about <strong>12 GB of VRAM</strong> for an 8B model, on a card that also has to host a virtual try-on model.</li>
</ol>
<p>I didn't use Ollama's model catalogue or its model swapping: one model, always loaded. So I was paying for features I didn't need.</p>
<h2>What else runs on Pascal? Not much</h2>
<p>Before switching I checked the alternatives:</p>
<table>
<thead>
<tr>
<th>Engine</th>
<th>On a P40?</th>
</tr>
</thead>
<tbody><tr>
<td>vLLM</td>
<td>No: compute capability 7.0 minimum</td>
</tr>
<tr>
<td>SGLang, TGI</td>
<td>No, same generation</td>
</tr>
<tr>
<td>TensorRT-LLM</td>
<td>No: Ampere minimum</td>
</tr>
<tr>
<td>ExLlamaV2 / TabbyAPI</td>
<td>Technically, but computes in fp16, so 1/64 speed</td>
</tr>
<tr>
<td>mistral.rs</td>
<td>In theory; no flash attention below 8.0, no P40 reports found</td>
</tr>
<tr>
<td>KoboldCpp, LocalAI, LM Studio</td>
<td>Yes, but they wrap llama.cpp</td>
</tr>
<tr>
<td><strong>llama.cpp <code>llama-server</code></strong></td>
<td><strong>Yes</strong>, official <code>server-cuda</code> image works with driver 550</td>
</tr>
</tbody></table>
<p>Everything that works on Pascal is llama.cpp underneath. So I went to the source.</p>
<h2>The setup</h2>
<p>One container, one model, loaded at start and never unloaded:</p>
<ul>
<li><strong>Qwen3-VL-8B-Instruct</strong>, GGUF <strong>Q4_K_M</strong>, with the <strong>mmproj</strong> in Q8_0 for vision;</li>
<li><code>llama-server -c 4096</code>: the context I actually need, set for real;</li>
<li>the OpenAI-compatible API (<code>/v1/chat/completions</code>), so the app code became a standard client.</li>
</ul>
<p>Porting the app meant changing two call sites from Ollama's <code>/api/generate</code> to the OpenAI format, behind an environment variable so I could switch back instantly. The old Ollama container is still there, stopped, as the rollback path. Never run both at once: they fight for the same VRAM.</p>
<h2>The numbers</h2>
<p>Benchmark on 26 real clothing photos, same prompts:</p>
<table>
<thead>
<tr>
<th></th>
<th>Ollama 0.12 + qwen2.5vl</th>
<th>llama-server + Qwen3-VL-8B</th>
</tr>
</thead>
<tbody><tr>
<td>Garment type correct</td>
<td>26/26</td>
<td>26/26</td>
</tr>
<tr>
<td>Colour correct</td>
<td>22/26</td>
<td>22/26</td>
</tr>
<tr>
<td>Time per photo</td>
<td>2.9 s</td>
<td><strong>1.3 s</strong></td>
</tr>
<tr>
<td>VRAM</td>
<td>7.6 GB</td>
<td><strong>6.4 GB</strong></td>
</tr>
</tbody></table>
<p>Same accuracy, more than twice as fast, and 1.2 GB back for the try-on model.</p>
<h2>Four traps I hit</h2>
<ol>
<li><strong>Use the <code>-Instruct</code> model.</strong> The <code>qwen3-vl:8b</code> tag in Ollama is the "thinking" variant, which returned empty answers for my JSON prompts.</li>
<li><strong>llama-server doesn't read WebP.</strong> My app stores WebP; I convert to JPEG (with sharp) before sending the image.</li>
<li><strong>Phone photos blow the context.</strong> A full-resolution phone photo turns into more image tokens than a 4096 context can hold. Resizing to 1024 px on the long side fixed it.</li>
<li><strong>Count VRAM at peak, not at rest.</strong> The card also hosts a try-on model and a background remover. Weights fit easily; activations during a run are what pushed it to 22.5/23 GB once. Budget for the worst moment.</li>
</ol>
<h2>Takeaway</h2>
<p>If you have an old Pascal card and only serve one model, Ollama's convenience may be costing you context, VRAM and access to new models. <code>llama-server</code> is a single binary with an OpenAI API, and on this card it was faster at the same accuracy.</p>
<p>The app this runs is <a href="https://fring.sevan.zone/en?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=llamacpp-p40">Fring</a>, free, with no ads. Questions about the P40 setup are welcome in the comments.</p>
<p><em>This article was drafted with AI assistance.</em></p>
]]></content:encoded></item><item><title><![CDATA[On a Tesla P40, fp16 made my diffusion model 14x slower than fp32]]></title><description><![CDATA[On a Pascal Tesla P40, fp16 runs at 1/64 of fp32: loading a diffusion model in fp32 took renders from 21 minutes to 85 seconds. Plus two Docker traps.]]></description><link>https://fring.hashnode.dev/on-a-tesla-p40-fp16-made-my-diffusion-model-14x-slower-than-fp32</link><guid isPermaLink="true">https://fring.hashnode.dev/on-a-tesla-p40-fp16-made-my-diffusion-model-14x-slower-than-fp32</guid><category><![CDATA[GPU]]></category><category><![CDATA[pytorch]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Sevan Safarian]]></dc:creator><pubDate>Fri, 09 Oct 2026 13:11:59 GMT</pubDate><content:encoded><![CDATA[<p>I run the AI for a small side project, <a href="https://fring.sevan.zone/en">Fring</a>, a free wardrobe app, on a second-hand Tesla P40 in my homelab. The virtual try-on uses Leffa, a diffusion model. For a month every render took about 21 minutes, and I assumed that was just what a 2016 card could do.</p>
<p>It wasn't. The model was loaded in fp16, because "use half precision to save VRAM" is the default advice everywhere. On the P40 (GP102, compute capability 6.1), fp16 runs at roughly 1/64 of fp32 throughput. Same inputs, same seed:</p>
<table>
<thead>
<tr>
<th></th>
<th>s / diffusion step</th>
<th>VRAM</th>
<th>20-step render</th>
</tr>
</thead>
<tbody><tr>
<td>fp16</td>
<td>63.8</td>
<td>6.6 GB</td>
<td>1241 s</td>
</tr>
<tr>
<td>fp32</td>
<td>4.7</td>
<td>9.8 GB</td>
<td>85 s</td>
</tr>
</tbody></table>
<p>The images were practically identical (mean difference 0.77/255). The card never needed the VRAM savings.</p>
<h2>Two more traps from the same month</h2>
<ul>
<li><strong>A CPU-only torch wheel</strong> in the Dockerfile, inherited from an older 4 GB card. <code>nvidia-smi</code> saw the GPU while <code>torch.cuda.is_available()</code> returned <code>False</code>. Check inside the container, not on the host.</li>
<li><strong>An unpinned <code>mediapipe</code></strong> upgrade removed <code>mediapipe.solutions</code>. The health check only tested the import, so it kept reporting pose estimation as healthy while the service silently fell back to a centred paste.</li>
</ul>
<h2>What I changed</h2>
<p>The service now picks fp16 only when the GPU actually accelerates it (Volta and newer, or P100), or when fp32 would not fit in VRAM. An environment variable can force either.</p>
<p>If you're buying a P40 today: the R580 driver branch is the last one to support Pascal, CUDA 13 dropped it, and PyTorch cu128/cu129 wheels no longer ship sm_61 kernels. Treat it as a frozen target. For LLMs, llama.cpp's llama-server built against CUDA 12 runs well on it: Qwen3-VL-8B at Q4_K_M answers in 1.3 s using 6.4 GB.</p>
<p>The general lesson for old datacenter cards: measure before applying the usual advice.</p>
<p><em>Written with AI assistance, reviewed and published by the author.</em></p>
]]></content:encoded></item></channel></rss>