<?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>Hardware on brodsbytes blog</title>
    <link>https://brodsbytes.blog/tags/hardware/</link>
    <description>Recent content in Hardware on brodsbytes blog</description>
    <generator>Hugo</generator>
    <language>en-au</language>
    <lastBuildDate>Thu, 09 Jul 2026 19:33:36 +1000</lastBuildDate>
    <atom:link href="https://brodsbytes.blog/tags/hardware/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Bosgame M4 Neo: Buying a Mini PC for Local LLMs</title>
      <link>https://brodsbytes.blog/posts/mini-pc-for-running-local-llms/</link>
      <pubDate>Thu, 09 Jul 2026 19:33:36 +1000</pubDate>
      <guid>https://brodsbytes.blog/posts/mini-pc-for-running-local-llms/</guid>
      <description>Why I bought a Mini PC - Bosgame M4 Neo (64GB RAM) to run 35b-class local LLMs on Linux.  benchmarks, thermals, Vulkan vs ROCm, and power draw vs a discrete-GPU desktop.</description>
      <content:encoded><![CDATA[<p><em>Some links in this post are affiliate links - if you buy through them I may earn a small commission, at no extra cost to you.</em></p>
<h2 id="why-i-chose-bosgame-m4-neo-for-llm-usage---other-mini-pc-options-">Why I chose Bosgame M4 Neo for LLM usage ( + other Mini PC options )</h2>
<p>A few months ago I decided local LLMs are getting so good, I wanted to be able to run them locally. Not only to save money in the long term with tokens or subscriptions, but because it unlocked a range of projects that contained data I wouldn&rsquo;t be comfortable moving off my device. For example, financial data: I made a personal finance assistant that now tracks all my spending &amp; net worth. Another common example is triaging your email inbox.</p>
<p>Because I love a deal, extensive research went into the goal of: running 35b-class models, at usable speeds, at the cheapest entry point possible. Bring in the <a href="https://amzn.to/4eUSQtK">Bosgame M4 Neo</a>, after much deliberation I pulled the trigger and purchased the 64GB RAM model at $1,400 AUD ($970 USD) in April 2026.</p>
<p>Why the <a href="https://amzn.to/4eUSQtK">Bosgame M4 Neo</a>? Two main things are needed for running LLMs on CPU/RAM - as opposed to a discrete graphics card. First, to run 35b models with long context windows during real-world tasks, 64GB DDR5 RAM is a must. Secondary is solid integrated graphics (iGPU), above the CPU &gt; RAM bandwidth bottleneck (more on this bottleneck further down). Another feature to look out for is a Oculink port which allows you to connect an external GPU later if you want more grunt.</p>
<p>This post is <strong>not</strong> a comprehensive review, this is focused on the things that matter for running local LLMs. Specifically talking on my experience running it on <strong>Linux</strong>. If you want a deep dive into different benchmarks, thermals, energy usage, build quality, etc - check out these write ups: <a href="https://www.notebookcheck.net/Bosgame-M4-Neo-test-the-affordable-alternative-to-expensive-mini-PCs.1068993.0.html">NotebookCheck M4 Neo test</a> &amp; <a href="https://www.linuxlinks.com/benchmarking-bosgame-m4-plus-mini-pc/">LinuxLinks Benchmarking BOSGAME M4 Plus</a>.<br>
Overall, I am satisfied with the built quality and performance, the only negative I&rsquo;ve encountered is how noisy this thing is - which we&rsquo;ll dive into later.</p>
<p><em>At the time of writing the M4 NEO is out of stock. There are of course many other options that fit the requirements needed to run 35b models locally. Here are a couple other Mini PCs in stock:</em></p>
<ul>
<li><a href="https://amzn.to/44i5JYH">Beelink SER9 MAX</a> - Basically the same to the Bosgame M4 Neo for local LLM speeds. The &ldquo;H 255&rdquo; is a rebadged Ryzen 7 8745H. Same Radeon 780M iGPU, so with 64GB of ram, right on par with the Bosgame M4 Neo 7840HS. It comes with a couple of minor feature improvements over the M4 Neo that aren&rsquo;t relevant here.</li>
<li><a href="https://amzn.to/4vkUWbe">GMKtec AI Mini PC Ultra 9</a> - Definitely more powerful than the Bosgame; multi-core: 16 cores  vs 8, ~20% faster overall CPU and 40%+ in multi-threaded work, plus a stronger Arc iGPU and Oculink. This is for anyone wanting to step up.</li>
</ul>
<h2 id="why-64gb-ram-to-run-35b-models">Why 64GB RAM to run 35b models?</h2>
<p>A 35b model at Q4 is ~23GB of weights, but you also need headroom for the KV cache. In layman&rsquo;s terms, the running context of a session, which grows the longer the chat goes on. You may have felt this in Claude or ChatGPT when a long-running conversation gets slower the more you go. That&rsquo;s the context window filling up which takes up more RAM locally.</p>
<p>64GB is the number that makes this work without a discrete graphics card. It fits a 35b model with a large context window, and still leaves enough for the OS to be a computer while the model runs. On 32GB you&rsquo;ll run out of memory. Being realistic about what you can&rsquo;t run: 70b-class models won&rsquo;t fit here no matter how you tune it. This is an accepted trade-off for the price point, though I&rsquo;m betting on local models continuing to get more intelligent, which has been paying off.</p>
<p>The one setting I changed is the dedicated iGPU VRAM in the BIOS. This is how much RAM is handed to the iGPU as dedicated VRAM. I set mine to 16GB, the max on the M4 Neo.  I don&rsquo;t need 64GB of RAM available to my OS for other tasks so this has worked fine for me as-is.</p>
<pre tabindex="0"><code>amdgpu: VRAM: 16384M ... (16384M used)
amdgpu: 23981M of GTT memory ready.
</code></pre><p>So with my setup, ollama sees ~37.6 GiB of GPU-usable memory, a 23GB model (35B) plus a 64K context fits comfortably. Don&rsquo;t get stuck on this though, mostly you don&rsquo;t <em>need</em> to change this to run local LLMs, the iGPU also borrows GTT memory from the remaining RAM automatically. So if I didn&rsquo;t change the dedicated VRAM, the model would still have plenty of usable memory. Every GB you carve out comes off the RAM the OS can see, and GTT is roughly half of whatever is left. A 35b fits behind almost any setting. I set mine to max given the LLM-heavy usage i&rsquo;ll be doing.</p>
<p>To see this in practice, I keep an eye on GPU/VRAM/RAM and the loaded model with a conky HUD overlay (<code>~/.config/conky/hud.conf</code>):</p>
<pre tabindex="0"><code>${color #ffffff}GPU${goto 60}${execi 3 radeontop -d - -l 1 2&gt;/dev/null | grep -oP &#39;gpu \K[0-9.]+&#39;}%
${color #ffffff}VRAM${goto 60}${execi 3 radeontop -d - -l 1 2&gt;/dev/null | grep -oP &#39;vram \K[0-9.]+&#39;}%
${color #e6e6e6}OLLAMA
${color #ffffff}${execi 5 ollama ps 2&gt;/dev/null | tail -n +2 | awk &#39;{print &#34;  &#34;$1&#34;  &#34;$3$4}&#39; | grep . || echo &#34;inactive&#34;}
</code></pre><figure>
    <img loading="lazy" src="/images/bosgame/conky-hud.png"
         alt="Conky HUD overlay showing CPU, RAM, GPU, VRAM and the loaded ollama model"/> <figcaption>
            <p>Conky HUD with <code>qwen3.6:35b</code> loaded - VRAM pegged at 100%, but still running because the model spills into GTT memory</p>
        </figcaption>
</figure>

<h2 id="ai-setup-vulkan-skip-rocm">AI setup: Vulkan, skip ROCm</h2>
<p>Vulkan and ROCm are the software interfaces apps use to talk to AMD GPUs (including our iGPU).</p>
<p><strong>Vulkan is the backend that makes sense,</strong> and there&rsquo;s <a href="https://docs.ollama.com/gpu">no setup required with ollama</a>.  Ollama added Vulkan support (opt-in) in the Nov 2025 release - 0.12.11 - and made it the default backend in v0.30, released May 2026. <code>OLLAMA_VULKAN=1</code> now does nothing, the config flag only works to disable vulkan: <code>OLLAMA_VULKAN=0</code>. What does matter on Linux is having a Vulkan driver present which you can do by installing Mesa RADV.</p>
<p>To confirm whether Ollama is offloading fully to the GPU - check Ollama&rsquo;s own logs. <code>journalctl -u ollama -f</code> if it&rsquo;s a systemd service, ~/.ollama/logs/server.log or the terminal output.</p>
<pre tabindex="0"><code>msg=&#34;gpu memory&#34; library=Vulkan available=&#34;37.6 GiB&#34; free=&#34;38.1 GiB&#34;
msg=&#34;offloaded 41/41 layers to GPU&#34;
</code></pre><p><code>41/41 layers</code> on a 35b = fully on GPU. This is the number to confirm, if you have multiple layers spilling back to CPU that will cause performance issues. Sometimes 1 (e.g., 40/41) is normal and expected.</p>
<p><strong>ROCm (AMD&rsquo;s software) - I&rsquo;d skip it</strong>.
There&rsquo;s the additional setup hassle, support varies from hardware to hardware and it depends on what OS you&rsquo;re running. 780M is gfx1103, not officially supported with ROCm so you have to adjust <code>HSA_OVERRIDE_GFX_VERSION</code> to pretend it&rsquo;s a 7900 XTX.
The main reason why ROCm isn&rsquo;t worth the hassle, is there are no performance gains if you&rsquo;re running LLMs on an iGPU &amp; RAM (i.e., <strong>not</strong> a dedicated graphics card).<br>
Token generation using iGPU &amp; RAM is memory-bandwidth bound, not compute bound. To emit one token the GPU streams the whole model through the same DDR5 as the CPU, so speed generally is bandwidth ÷ model size. For example: <code>qwen3.5:9b</code> (6.6GB, fully offloaded), benchmarks run at ~12 t/s. 12 t/s x 6.6GB = <strong>79 GB/s</strong>, vs dual-channel DDR5-5600 theoretical peak <strong>89.6 GB/s</strong>. <!-- raw HTML omitted -->
What does this mean? Vulkan is already pulling <strong>~88%</strong> of what the iGPU &gt; RAM bus can physically deliver. ROCm can only improve the last ~12%, which includes overheads processing the data coming back from RAM, leaving a very thin margin for ROCm to improve on. Therefore, ROCm is unlikely to increase performance.</p>
<p>Honest caveats: Bandwidth calculations are rough math, and I haven&rsquo;t tested ROCm to compare results directly since I ran into issues getting it working. The case still stands on the iGPU &gt; CPU Bandwidth being the bottleneck.
ROCm could still plausibly win with prompt processing, which unlike generation genuinely is compute-bound. If you&rsquo;re feeding your models enormous prompts, and you&rsquo;re not getting much output back, ROCm might be worth trying.</p>
<p>Mini PCs running an iGPU is bandwidth-starved from CPU &gt; RAM, so straying from the default software wont help. Using Vulkan or ROCm will produce marginal real world difference. What <em>does</em> help is how many bytes a model reads per token, which leads us to testing MoE (Mixture of Experts) models in the section below.</p>
<h2 id="benchmarks">Benchmarks</h2>
<figure>
    <img loading="lazy" src="/images/bosgame/qwen36-benchmarks.svg"
         alt="Bar chart of Artificial Analysis Intelligence Index: GPT-5.5 (55) and Claude Sonnet 5 (53) on free cloud tiers vs Qwen3.6-35B-A3B (32) and Gemma 4 26B (26) running locally on the Neo"/> <figcaption>
            <p><a href="https://artificialanalysis.ai/">Artificial Analysis Intelligence Index</a>, July 2026.</p>
        </figcaption>
</figure>

<p>A rough sense of scale: the free ChatGPT and Claude models still out-think anything the Neo runs - but Gemma4:26b and Qwen3.6-35B-A3B lead the local models I can run on this box: ~60% of a frontier free-tier chatbot, no paywall limits and fully offline. General reasoning is more Gemma4, coding is more Qwen3.6.</p>
<p>As frontier models get more intelligent, local models are quietly keeping up too. There are new free local models coming out all the time, continuously out-performing the last gen. a June challenger, Ornith-1.0-35B (also MoE, <code>ollama run ornith:35b</code>, 21GB), claims to edge out 3.6 on coding benchmarks, though it&rsquo;s coding-specialised and not yet independently indexed.</p>
<p>At the time of writing, here are the models I have on my system benchmarked. To test these models, I used the same fixed prompt every run, <code>num_predict=512</code>, <code>temperature=0</code>, via the ollama HTTP API.</p>
<p><em>These are short prompt runs (512 tokens), on a fresh session with 0 context. Speeds drop as context fills, so a long coding session with tens of thousands of tokens in the window will generate noticeably slower than the figures below. Also, The MoE speed advantage gap will close as context grows. Still a valid speed check and comparison across all the models.</em></p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Params</th>
					<th>Prompt eval (t/s)</th>
					<th>Generation (t/s)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>gemma4:e4b</td>
					<td>8.0B</td>
					<td>194.6</td>
					<td>19.0</td>
			</tr>
			<tr>
					<td><strong>gemma4:26b (MoE)</strong></td>
					<td><strong>25.8B</strong></td>
					<td><strong>103.1</strong></td>
					<td><strong>18.7</strong></td>
			</tr>
			<tr>
					<td><strong>qwen3.6:35b (MoE)</strong></td>
					<td><strong>35B</strong></td>
					<td><strong>58.4</strong></td>
					<td><strong>17.8</strong></td>
			</tr>
			<tr>
					<td>qwen3.6:35b-opencoder (MoE)</td>
					<td>35B</td>
					<td>85.0</td>
					<td>16.7</td>
			</tr>
			<tr>
					<td>qwen3.5:9b</td>
					<td>9.7B</td>
					<td>92.4</td>
					<td>12.0</td>
			</tr>
			<tr>
					<td>devstral-small-2:24b</td>
					<td>24B</td>
					<td>651.0</td>
					<td>5.5</td>
			</tr>
			<tr>
					<td>gemma4:31b</td>
					<td>31.3B</td>
					<td>44.5</td>
					<td>3.8</td>
			</tr>
	</tbody>
</table>
<p>Every model here ran fully on the iGPU - no layers spilled back to the CPU which makes this an even enough comparison. <code>qwen3.6:35b-opencoder</code> is the same base qwen3.6:35b, with double the default context window to 128k <code>num_ctx 131072</code> plus a generic &ldquo;keep reasoning concise&rdquo; system prompt to help reduce thinking tokens over time.</p>
<p>Interesting outlier, <code>devstral-small-2:24b</code> blazes through prompt processing at 651 t/s but crawls at generation. I&rsquo;ve never used it on my system so I did not troubleshoot the &ldquo;why&rdquo; (sorry if this one is your jam)</p>
<h3 id="moe-is-the-pick-active-params-matter---not-size-or-quants">MoE is the pick: active params matter - not size or quants</h3>
<p>Mixture of Experts (MoE) is the way on this setup. <code>gemma4:26b</code> (26B, MoE) generates at 18.7 t/s - <strong>5× faster</strong> than the dense <code>gemma4:31b</code> (3.8 t/s) despite being about the same size. Mixture of Experts is a way of dividing tasks among many different sub-models (aka experts). This increases the performance because only a fraction of the full model parameters are being executed at the same time.</p>
<p>Why it&rsquo;s good in our case specifically: what costs speed is active params per token, RAM is the resource in surplus and bandwidth is the bottleneck. MoE can load the entire model while only sending smaller amounts through the bandwidth bottleneck.</p>
<p><code>qwen3.5:9b</code> is not really a good pick anymore, considering it&rsquo;s slower and much less capable than the bigger, MoE models. The sweet spot for the M4 Neo and similar Mini PCs is MoE models in the 25–35B range. bigger-model answers at smaller-model speeds. Small dense models (<code>gemma4:e4b</code>, <code>qwen3.5:9b</code>) are still reasonably quick but don&rsquo;t need a 64GB box, they can be ran on cheaper, lower RAM hardware.</p>
<h2 id="idle-thermals-power---and-the-noise">Idle, thermals, power - and the <em>noise</em></h2>
<p>Biggest real-world gripe with the M4 Neo is it&rsquo;s noisy, even at idle, and none of the fixes below fully silenced it. The saga went, in order: buy rubber feet to lift it off my desk, notice it didn&rsquo;t change anything and lay it sideways, contact Bosgame support and replace the new CPU fan they sent, then an <a href="https://www.cdrtd.com/products/replacement-mini-pc-cpu-fan-for-bosgame-m4-dc5v-0-70a-new.html">aftermarket fan from Lindisparts</a>, then finally repasting the CPU myself. Repasting did drop thermals, but not enough to remove the noise at idle.<br>
Other reviewers have pointed to setting the fan to &ldquo;quiet&rdquo; mode in the BIOS, but given the temps I recorded, I&rsquo;m not keen on starving this thing of air.</p>
<p>Placement mattered as much as thermal paste - Peak temps under load (Psensor - <code>edge</code> = GPU/APU die, <code>Tctl</code> = CPU):</p>
<table>
	<thead>
			<tr>
					<th>Setup</th>
					<th>GPU edge (peak)</th>
					<th>CPU Tctl (peak)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Flat on desk (on rubber feet), pre-paste</td>
					<td>85°C</td>
					<td>82°C</td>
			</tr>
			<tr>
					<td>Turned on its side for airflow, pre-paste</td>
					<td>80°C</td>
					<td>80°C</td>
			</tr>
			<tr>
					<td>Hanging off the back of the desk + repaste</td>
					<td>68°C</td>
					<td>66°C</td>
			</tr>
	</tbody>
</table>
<p>Rubber feet weren&rsquo;t enough - flat still peaked at 85°C, and turning it on its side shaved ~5°C for free. Repasting + hanging it off the back of the desk with the vesa mount bracket is what I landed on. Constant airflow, ~15°C cooler peaks than flat, ~56–61°C under sustained load - and quieter to my ears by not being in front of me. These are real-world captures, not a controlled bench, so your mileage may vary.</p>
<figure>
    <img loading="lazy" src="/images/bosgame/temps-flat-prepaste.png"
         alt="Psensor readout, mini-PC flat on desk before repaste"/> <figcaption>
            <p>Flat on the desk, pre-paste: GPU edge peaks at 85°C, CPU Tctl at 82°C</p>
        </figcaption>
</figure>

<figure>
    <img loading="lazy" src="/images/bosgame/temps-sideways-prepaste.png"
         alt="Psensor readout, mini-PC on its side before repaste"/> <figcaption>
            <p>Turned on its side for airflow, pre-paste: peaks drop to ~80°C</p>
        </figcaption>
</figure>

<figure>
    <img loading="lazy" src="/images/bosgame/temps-hanging-postpaste.png"
         alt="Psensor readout, mini-PC hanging off desk after repaste, mid run"/> <figcaption>
            <p>Hanging off the desk back + repasted, mid-run: peaks down to 68°C / 66°C</p>
        </figcaption>
</figure>

<h2 id="running-cost-mini-pc-vs-a-gpu-tower">Running cost: mini PC vs a GPU tower</h2>
<p>The local-LLM pitch is a solid &ldquo;no cloud bill&rdquo;, but a desktop with a discrete GPU has a real, ongoing electricity cost, especially left on 24/7 to serve models or do other background jobs. I don&rsquo;t own a wall meter, so I&rsquo;m not going to pretend I measured this box. But the 7840HS has been measured to death by people who do, at the wall, whole-system. Idle sits at <strong>6–11W</strong>, and an all-core CPU burn peaks <strong>77–97W</strong> before settling to <strong>~45W</strong> sustained once it thermally throttles (<a href="https://www.servethehome.com/beelink-ser7-review-a-smaller-and-cheaper-amd-ryzen-7-7840hs-mini-pc/4/">ServeTheHome, Beelink SER7</a>; <a href="https://droix.net/blogs/minisforum-um780-xtx-review-with-video/">DROIX, UM780 XTX</a>). That all-core figure is the <em>worst</em> case though, and it isn&rsquo;t what inference actually does - generation is iGPU and memory-bandwidth bound, with the CPU cores mostly sat waiting on RAM. So treat ~90W as a ceiling I never actually hit, not a typical figure.</p>
<p>Compare that to a discrete-GPU desktop: one person actually serving local LLMs off a 7900 XTX clocks their whole box at <strong>~70W idle</strong>, rising to 150–250W mid-generation (<a href="https://www.xda-developers.com/run-local-llms-one-worlds-priciest-energy-markets/">XDA</a>). Under load it&rsquo;s worse again: an RTX 4070 <em>alone</em> averages <strong>186W</strong> gaming, on top of the rest of the tower (<a href="https://www.techpowerup.com/306765/nvidia-geforce-rtx-4070-has-an-average-gaming-power-draw-of-186-w">TechPowerUp</a>); a 4090 is a 450W card. The Neo&rsquo;s worst case - every core burning, ~90W for the entire system - is still less than half what an RTX 4070 pulls on its own.</p>
<p>To be clear, the 4070 here is a stand-in for a mid-range GPU&rsquo;s <em>power draw</em>, not an even LLM performance comparison. Nn inference the two barely overlap. Models that fit its 12GB (up to ~14B) are no contest: the 4070&rsquo;s GDDR6X (504 GB/s) buries the Neo&rsquo;s dual-channel DDR5-5600 (89.6 GB/s), 5.6× the bandwidth.<br>
But, a 35b at Q4 (~23GB) won&rsquo;t fit 12GB - it spills across PCIe into system RAM and crawls. That&rsquo;s where the 64GB of RAM in a Neo come in, it holds a 35b in unified memory and stays usable, where the 4070 falls off. So it depends 4070 12GB for small-and-fast, Neo for big-and-usable. You can definitely get a bigger GPU with more VRAM, but the price blows out moving up to a capable 24GB GPU.</p>
<p>Running an illustrative 24/7 cost (mostly idle, ~$0.30/kWh AU): the Neo at ~15W (rounding its 6–11W idle <em>up</em>, to cover a model sitting loaded) comes to ~130 kWh/yr, ~$40/yr. A GPU tower idling at ~70W comes to ~615 kWh/yr, ~$185/yr. That&rsquo;s roughly <strong>$145/yr</strong> in the Neo&rsquo;s favour before running a single prompt, the gap only increases the more you use it. Idle draw varies with drivers, monitors and settings, so treat this like the napkin math it is.</p>
<h2 id="how-to-get-started-with-local-llms-as-a-beginner">How to get started with local LLMs as a beginner</h2>
<p>Wrapping up now. How do you start tinkering with all this? Ollama is my preferred tool for &ldquo;managing&rdquo; your models. It&rsquo;s easy to setup and download the latest models. Easy to chat to the models for a quick test, and scales well in terms of running with automated jobs in your projects via the local API.</p>
<p>Opencode is my preferred code editor, which the models connect to. For a free open source tool it&rsquo;s very good - coming from someone who occasionally uses claude code. When you run a model in Ollama, the model doesn&rsquo;t have access to the right tools to perform file discovery, code edits, web searches and so on out of the box. Opencode is an editor that gives the model access to those tools, alongside the additional considerations of how to manage that access: plan mode, manually accept each edit/command, auto mode, etc.</p>
<p>In my opinion this is the best starting point as a beginner, both of these tools are easy to setup and in most cases don&rsquo;t require any config changes to get going.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
