{"id":1256,"date":"2026-10-24T09:50:00","date_gmt":"2026-10-23T22:50:00","guid":{"rendered":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/"},"modified":"2026-11-03T21:47:02","modified_gmt":"2026-11-03T10:47:02","slug":"local-llm-memory-sizing-guide","status":"publish","type":"post","link":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/","title":{"rendered":"How much memory does a local LLM need? A sizing guide"},"content":{"rendered":"\n<p class=\"has-pale-cyan-blue-background-color has-background wp-block-paragraph\">A local model needs memory for its weights, for a KV cache that grows with context and parallel slots, and for runtime overhead. Here are the formulas, three worked examples from official model configs, the numbers we measured on our lab server, and the commands to check what your models really use.<\/p>\n\n\n\n<div class=\"wp-block-group has-background\" style=\"background-color:#f4f6f8;padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<p class=\"wp-block-paragraph\"><strong>Related guides<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><a href=\"https:\/\/catharsis.net.au\/blog\/private-ai-vs-chatgpt-vs-copilot\/\" target=\"_blank\" rel=\"noreferrer noopener\">Private AI, ChatGPT or Copilot? A decision guide for Australia<\/a><\/li>\n\n\n<li><a href=\"https:\/\/catharsis.net.au\/blog\/local-ai-server-freezes-unified-memory\/\" target=\"_blank\" rel=\"noreferrer noopener\">Why Does a Local AI Server Freeze? Sizing Memory for LLMs (Part 3 of 11)<\/a><\/li>\n\n\n<li><a href=\"https:\/\/catharsis.net.au\/blog\/local-ai-litellm-setup-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">How Do You Set Up Local AI? A Step-by-Step LiteLLM Guide<\/a><\/li>\n\n\n<li><a href=\"https:\/\/catharsis.net.au\/blog\/why-run-ai-on-your-own-hardware\/\" target=\"_blank\" rel=\"noreferrer noopener\">Local AI or Cloud AI? Why We Built a Private AI Server (Part 1 of 11)<\/a><\/li>\n\n<\/ul>\n\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-group has-background\" style=\"background-color:#f4f6f8;padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<h3 class=\"wp-block-heading\">TL;DR<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Memory = weights + KV cache + overhead. Weights depend on parameter count and quantisation; the KV cache depends on context length \u00d7 parallel slots, and it is the part that catches people out.<\/li>\n\n\n<li>Weights in GB \u2248 parameters in billions \u00d7 bits per weight \u00f7 8. KV cache in bytes = 2 \u00d7 layers \u00d7 KV heads \u00d7 head dimension \u00d7 bytes per element \u00d7 context \u00d7 slots.<\/li>\n\n\n<li>In Ollama every parallel slot reserves the full context. On our lab server one 30B model went from 20.3 GB at one 32k slot to about 33 GB at four 64k slots, and that helped freeze the machine.<\/li>\n\n\n<li>The arithmetic gets you close, but measure: <code>ollama ps<\/code>, <code>\/api\/ps<\/code> and <code>nvidia-smi<\/code> tell you what a model actually took.<\/li>\n\n\n<li>Keep a large share of memory free. We budget against about 120 GB usable on a 128 GB DGX Spark and never let the warm models go past what leaves 40 GB spare.<\/li>\n\n<\/ul>\n\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-group has-background\" style=\"background-color:#f4f6f8;padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<h3 class=\"wp-block-heading\">In this post<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><a href=\"#what-uses-the-memory\">What uses the memory?<\/a><\/li>\n\n\n<li><a href=\"#the-formulas\">The formulas<\/a><\/li>\n\n\n<li><a href=\"#context-length-and-parallel-slots-where-budgets-go-wrong\">Context length and parallel slots: where budgets go wrong<\/a><\/li>\n\n\n<li><a href=\"#worked-examples-from-official-model-configs\">Worked examples from official model configs<\/a><\/li>\n\n\n<li><a href=\"#what-does-quantisation-cost-you\">What does quantisation cost you?<\/a><\/li>\n\n\n<li><a href=\"#our-lab-s-measured-numbers\">Our lab&#8217;s measured numbers<\/a><\/li>\n\n\n<li><a href=\"#measure-don-t-derive\">Measure, don&#8217;t derive<\/a><\/li>\n\n\n<li><a href=\"#a-budget-template-you-can-copy\">A budget template you can copy<\/a><\/li>\n\n\n<li><a href=\"#key-takeaways\">Key takeaways<\/a><\/li>\n\n\n<li><a href=\"#sources\">Sources<\/a><\/li>\n\n<\/ul>\n\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-uses-the-memory\">What uses the memory?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Three things, in this order of size for most setups:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li><strong>Weights.<\/strong> The model&#8217;s parameters, loaded once per model. A 30-billion-parameter model at 4-bit is about 19 GB whether one person uses it or ten.<\/li>\n\n\n<li><strong>KV cache.<\/strong> The keys and values the model keeps for every token in its context, so it doesn&#8217;t recompute them for each new token. It grows linearly with context length and with the number of requests the server will handle at once.<\/li>\n\n\n<li><strong>Overhead.<\/strong> The runtime, compute buffers and temporary memory for processing a long prompt. It is smaller, but it spikes during prompt processing, which is exactly when a tight budget fails.<\/li>\n\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">On a discrete GPU, running out usually means the model spills to the CPU and slows to a crawl. On a unified-memory machine like the NVIDIA DGX Spark, where CPU and GPU share one pool, it can take the whole system down: our lab server froze hard, with no out-of-memory error, while a 65k-token prompt was being processed (<a href=\"https:\/\/catharsis.net.au\/blog\/local-ai-server-freezes-unified-memory\/\" target=\"_blank\" rel=\"noreferrer noopener\">the full post-mortem<\/a>).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-formulas\">The formulas<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Weights<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>weights (GB) \u2248 parameters (billions) \u00d7 bits per weight \u00f7 8\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use the real bits per weight of the quantisation format, not the name. The llama.cpp project publishes these for Llama 3.1 8B:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Format<\/th>\n<th>Bits per weight<\/th>\n<th>30.5B model<\/th>\n<th>32.8B model<\/th>\n<th>72.7B model<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>F16<\/td>\n<td>16.0<\/td>\n<td>61.0 GB<\/td>\n<td>65.6 GB<\/td>\n<td>145.4 GB<\/td>\n<\/tr>\n<tr>\n<td>Q8_0<\/td>\n<td>8.5<\/td>\n<td>32.4 GB<\/td>\n<td>34.9 GB<\/td>\n<td>77.3 GB<\/td>\n<\/tr>\n<tr>\n<td>Q6_K<\/td>\n<td>6.56<\/td>\n<td>25.0 GB<\/td>\n<td>26.9 GB<\/td>\n<td>59.6 GB<\/td>\n<\/tr>\n<tr>\n<td>Q5_K_M<\/td>\n<td>5.70<\/td>\n<td>21.7 GB<\/td>\n<td>23.4 GB<\/td>\n<td>51.8 GB<\/td>\n<\/tr>\n<tr>\n<td>Q4_K_M<\/td>\n<td>4.89<\/td>\n<td>18.7 GB<\/td>\n<td>20.1 GB<\/td>\n<td>44.5 GB<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The mixed &quot;K&quot; formats vary a little from model to model, so treat these as close estimates. For a mixture-of-experts model, count <strong>all<\/strong> parameters: every expert sits in memory even though only a few run for each token.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>KV cache<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>KV cache (bytes) = 2 \u00d7 layers \u00d7 KV heads \u00d7 head dimension \u00d7 bytes per element \u00d7 context \u00d7 slots\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The 2 is one key and one value. Bytes per element is 2 for an f16 cache, 1.0625 for Ollama&#8217;s 8-bit <code>q8_0<\/code> cache and 0.5625 for <code>q4_0<\/code> (llama.cpp stores each block of 32 values with a 2-byte scale, so 34 and 18 bytes per 32). Ollama&#8217;s documentation says <code>q8_0<\/code> uses approximately half the memory of <code>f16<\/code> and <code>q4_0<\/code> approximately a quarter, and that the cache is only quantised when flash attention is enabled.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Models with sliding-window attention (Gemma 3 and gpt-oss, for example) hold only the last window of tokens on their local layers, so for those layers use the window size instead of the full context. That is why their cache grows much more slowly than the parameter count suggests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll find the inputs in the model&#8217;s <code>config.json<\/code> on its official model page:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><code>num_hidden_layers<\/code>: layers<\/li>\n\n\n<li><code>num_key_value_heads<\/code>: KV heads (models using grouped-query attention have far fewer KV heads than attention heads, which is what keeps the cache manageable)<\/li>\n\n\n<li><code>head_dim<\/code>, or if it&#8217;s missing, <code>hidden_size \u00f7 num_attention_heads<\/code><\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Overhead<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no clean formula. Leave a margin on top of the KV figure, then measure. In our lab the textbook KV formula was about 19% optimistic on one model, which is the direction that freezes a server. The calculator below adds 0.5 GB plus 10% of the KV cache for buffers and margin, which put it 3 to 4% above every figure we measured on our main model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"context-length-and-parallel-slots-where-budgets-go-wrong\">Context length and parallel slots: where budgets go wrong<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ollama&#8217;s FAQ is explicit: required memory scales with <code>OLLAMA_NUM_PARALLEL<\/code> \u00d7 <code>OLLAMA_CONTEXT_LENGTH<\/code>. Each parallel slot gets the full context and reserves its KV cache whether or not anyone is using it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is how our lab server ran out. The main model had a 64k context written into its own settings, which overrode the server-wide limit, and the server allowed four parallel slots. One client was using one slot, but Ollama had reserved KV cache for all four: 262,144 tokens&#8217; worth. The model&#8217;s footprint grew to about 33 GB, a second model sat beside it, and a long prompt tipped the machine over.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix was one slot per model and a 32,768-token context. A week of measurement afterwards (1,342 requests, 52 overlaps, never more than four at once) showed the extra slots had rarely been used. The cost is a queue: a second caller now waits a median of 18 seconds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Check:<\/strong> if you raise <code>OLLAMA_NUM_PARALLEL<\/code> from 1 to 4, multiply your KV figure by four before you change anything.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"worked-examples-from-official-model-configs\">Worked examples from official model configs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">All three use the architecture published in each model&#8217;s <code>config.json<\/code> and model card on Hugging Face. Figures are in decimal GB (10\u2079 bytes), the unit <code>ollama ps<\/code> reports.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Qwen3-Coder-30B-A3B (the model behind <code>qwen3-coder<\/code> in Ollama)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>30.5B parameters in total, 3.3B active per token; 48 layers; 4 KV heads; head dimension 128; native context 262,144 tokens.<\/li>\n\n\n<li>KV per token: 2 \u00d7 48 \u00d7 4 \u00d7 128 \u00d7 2 bytes = 98,304 bytes (f16), or about 52,000 bytes with an 8-bit cache.<\/li>\n\n\n<li>Weights at Q4_K_M: about 18.7 GB.<\/li>\n\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>KV cache (8-bit)<\/th>\n<th>Weights + KV<\/th>\n<th>We measured<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>32k context \u00d7 1 slot<\/td>\n<td>1.7 GB<\/td>\n<td>20.4 GB<\/td>\n<td><strong>20.3 GB<\/strong><\/td>\n<\/tr>\n<tr>\n<td>64k context \u00d7 4 slots<\/td>\n<td>13.7 GB<\/td>\n<td>32.4 GB<\/td>\n<td><strong>about 33 GB<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This model has only 4 KV heads, so its cache is small for its size. That is why the arithmetic and our measurements line up so closely here.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Qwen3-32B (dense)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>32.8B parameters; 64 layers; 8 KV heads; head dimension 128; native context 32,768 tokens.<\/li>\n\n\n<li>KV per token: 2 \u00d7 64 \u00d7 8 \u00d7 128 \u00d7 2 = 262,144 bytes (f16), almost three times the coder model.<\/li>\n\n\n<li>Weights at Q4_K_M: about 20.1 GB.<\/li>\n\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>KV cache (f16)<\/th>\n<th>Weights + KV, before overhead<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>32k \u00d7 1 slot<\/td>\n<td>8.6 GB<\/td>\n<td>28.7 GB<\/td>\n<\/tr>\n<tr>\n<td>32k \u00d7 4 slots<\/td>\n<td>34.4 GB<\/td>\n<td>54.4 GB<\/td>\n<\/tr>\n<tr>\n<td>32k \u00d7 4 slots, 8-bit cache<\/td>\n<td>18.3 GB<\/td>\n<td>38.3 GB<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Similar weights to the coder model, very different cache. Four slots at f16 cost more memory than the weights.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Qwen2.5-72B-Instruct (dense)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>72.7B parameters; 80 layers; 8 KV heads; head dimension 128 (8,192 \u00f7 64); context up to 131,072 tokens.<\/li>\n\n\n<li>KV per token: 2 \u00d7 80 \u00d7 8 \u00d7 128 \u00d7 2 = 327,680 bytes (f16).<\/li>\n\n\n<li>Weights: 44.5 GB at Q4_K_M, 77.3 GB at Q8_0, 145.4 GB at F16.<\/li>\n\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>Weights + KV, before overhead<\/th>\n<th>On ~120 GB usable, keeping 40 GB free<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Q4_K_M, 32k \u00d7 1 slot, f16 cache<\/td>\n<td>55.2 GB<\/td>\n<td>Fits, with room for a small second model<\/td>\n<\/tr>\n<tr>\n<td>Q4_K_M, 128k \u00d7 1 slot, f16 cache<\/td>\n<td>87.4 GB<\/td>\n<td>Breaks the 40 GB floor<\/td>\n<\/tr>\n<tr>\n<td>Q8_0, 32k \u00d7 1 slot, f16 cache<\/td>\n<td>88.0 GB<\/td>\n<td>Breaks the floor<\/td>\n<\/tr>\n<tr>\n<td>F16, any context<\/td>\n<td>over 145 GB<\/td>\n<td>Won&#8217;t load<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Run your own numbers in the calculator below. It uses the same formulas (including sliding windows), with presets taken from official configs, and adds the buffer margin described above, calibrated against the lab figures. It shows <strong>Fits<\/strong> up to 85% of the memory you allow, <strong>Tight<\/strong> from 85 to 100%, and <strong>Will not fit<\/strong> above that. Estimates are approximate; measure before you commit.<\/p>\n\n\n\n<div id=\"cat-ai-memory\">\n<style>\n#cat-ai-memory{background:#f4f6f8;border-radius:12px;padding:20px;margin:8px 0 24px;color:#32373c}\n#cat-ai-memory *{box-sizing:border-box}\n#cat-ai-memory fieldset{border:1px solid #dde3ea;border-radius:10px;margin:0 0 14px;padding:12px 14px 14px;background:#fff;min-width:0}\n#cat-ai-memory legend{font-weight:700;font-size:16px;padding:0 6px}\n#cat-ai-memory .cat-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end}\n#cat-ai-memory .cat-wide{grid-column:1 \/ -1}\n#cat-ai-memory label{display:block;font-size:15px;font-weight:600;margin:0 0 6px}\n#cat-ai-memory input,#cat-ai-memory select{width:100%;max-width:100%;font-size:17px;padding:10px 12px;border:1px solid #b8c2cc;border-radius:8px;background:#fff;color:#32373c}\n#cat-ai-memory input{font-family:ui-monospace,Menlo,Consolas,monospace}\n#cat-ai-memory input[aria-invalid=true]{border-color:#a4002b;border-width:2px}\n#cat-ai-memory input:focus,#cat-ai-memory select:focus,#cat-ai-memory button:focus-visible{outline:3px solid #0a66c2;outline-offset:1px}\n#cat-ai-memory .cat-hint{font-size:13px;color:#55606b;margin:4px 0 0;font-weight:400}\n#cat-ai-memory .cat-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0}\n#cat-ai-memory .cat-chips button{background:#fff;color:#32373c;border:1px solid #b8c2cc;border-radius:999px;padding:4px 11px;font-size:13px;cursor:pointer}\n#cat-ai-memory .cat-err{color:#a4002b;font-weight:600;margin:12px 0 0}\n#cat-ai-memory .cat-note{font-size:14px;color:#55606b;margin:10px 0 0}\n#cat-ai-memory .cat-verdict{font-size:17px;font-weight:700;margin:4px 0 12px;padding:12px 14px;border-radius:8px;border-left:6px solid #55606b;background:#fff}\n#cat-ai-memory .cat-fits{border-left-color:#1d7a3a;color:#14582a}\n#cat-ai-memory .cat-tight{border-left-color:#b36b00;color:#7a4600}\n#cat-ai-memory .cat-nofit{border-left-color:#a4002b;color:#a4002b}\n#cat-ai-memory .cat-bar{position:relative;display:flex;height:22px;border-radius:6px;background:#e3e8ee;overflow:hidden;margin:6px 0 4px}\n#cat-ai-memory .cat-bar span{display:block;height:100%}\n#cat-ai-memory .cat-bw{background:#0a5fb4}\n#cat-ai-memory .cat-bk{background:#a35200}\n#cat-ai-memory .cat-bo{background:#7b8794}\n#cat-ai-memory .cat-limit{position:absolute;top:0;bottom:0;width:3px;margin-left:-2px;background:#32373c}\n#cat-ai-memory .cat-key{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:13px;color:#55606b;margin:0 0 6px;padding:0;list-style:none}\n#cat-ai-memory .cat-key i{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:5px;vertical-align:-1px}\n#cat-ai-memory table{width:100%;border-collapse:collapse;margin-top:10px;background:#fff;font-size:15px;table-layout:fixed}\n#cat-ai-memory th,#cat-ai-memory td{padding:7px 10px;border-bottom:1px solid #e3e8ee;text-align:left;vertical-align:top;overflow-wrap:anywhere}\n#cat-ai-memory th{width:36%;font-weight:600}\n#cat-ai-memory td .cat-big{font-family:ui-monospace,Menlo,Consolas,monospace;font-weight:700}\n#cat-ai-memory td .cat-sub{display:block;font-size:13px;color:#55606b}\n@media (max-width:640px){#cat-ai-memory{padding:14px}#cat-ai-memory .cat-grid{grid-template-columns:1fr}#cat-ai-memory fieldset{padding:10px}#cat-ai-memory th,#cat-ai-memory td{padding:6px}}\n<\/style>\n<form id=\"cat-aim-form\" novalidate>\n<fieldset>\n<legend>Model<\/legend>\n<div class=\"cat-grid\">\n<div class=\"cat-wide\"><label for=\"cat-aim-model\">Model<\/label><select id=\"cat-aim-model\"><\/select><\/div>\n<div><label for=\"cat-aim-params\">Parameters (billions)<\/label><input id=\"cat-aim-params\" inputmode=\"decimal\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"9\"><\/div>\n<div><label for=\"cat-aim-quant\">Weight quantisation<\/label><select id=\"cat-aim-quant\"><\/select><\/div>\n<div><label for=\"cat-aim-layers\">Layers<\/label><input id=\"cat-aim-layers\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"3\"><\/div>\n<div><label for=\"cat-aim-kvheads\">KV heads<\/label><input id=\"cat-aim-kvheads\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"3\"><\/div>\n<div><label for=\"cat-aim-headdim\">Head dimension<\/label><input id=\"cat-aim-headdim\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"4\"><\/div>\n<div><label for=\"cat-aim-swa\">Sliding-window layers<\/label><input id=\"cat-aim-swa\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"3\"><\/div>\n<div><label for=\"cat-aim-win\">Sliding window (tokens)<\/label><input id=\"cat-aim-win\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"7\"><\/div>\n<\/div>\n<p class=\"cat-hint\">Presets fill these from the model&#8217;s published configuration. Change any of them for a model that is not listed; most models have 0 sliding-window layers.<\/p>\n<\/fieldset>\n<fieldset>\n<legend>Context and cache<\/legend>\n<div class=\"cat-grid\">\n<div><label for=\"cat-aim-ctx\">Context length (tokens)<\/label><input id=\"cat-aim-ctx\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"9\" value=\"32768\"><\/div>\n<div><label for=\"cat-aim-slots\">Parallel slots<\/label><input id=\"cat-aim-slots\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"2\" value=\"1\"><\/div>\n<div class=\"cat-wide\"><label for=\"cat-aim-kv\">KV cache type<\/label><select id=\"cat-aim-kv\"><\/select><\/div>\n<\/div>\n<div class=\"cat-chips\" role=\"group\" aria-label=\"Common context lengths\"><button type=\"button\" data-ctx=\"4096\">4k<\/button><button type=\"button\" data-ctx=\"8192\">8k<\/button><button type=\"button\" data-ctx=\"16384\">16k<\/button><button type=\"button\" data-ctx=\"32768\">32k<\/button><button type=\"button\" data-ctx=\"65536\">64k<\/button><button type=\"button\" data-ctx=\"131072\">128k<\/button><\/div>\n<p class=\"cat-hint\">With Ollama, every parallel slot (OLLAMA_NUM_PARALLEL) reserves the full context, so the cache is context \u00d7 slots even when only one request is running. An 8-bit or 4-bit cache needs flash attention turned on.<\/p>\n<\/fieldset>\n<fieldset>\n<legend>Hardware<\/legend>\n<div class=\"cat-grid\">\n<div class=\"cat-wide\"><label for=\"cat-aim-hw\">Hardware<\/label><select id=\"cat-aim-hw\"><\/select><\/div>\n<div><label for=\"cat-aim-mem\">Memory (GB)<\/label><input id=\"cat-aim-mem\" inputmode=\"decimal\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"7\"><\/div>\n<div><label for=\"cat-aim-reserve\">Keep free (GB)<\/label><input id=\"cat-aim-reserve\" inputmode=\"decimal\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"7\"><\/div>\n<\/div>\n<p class=\"cat-hint\">On unified memory (such as the DGX Spark) the operating system, apps and containers share the same pool, so keep more free than on a dedicated GPU.<\/p>\n<\/fieldset>\n<\/form>\n<p id=\"cat-aim-error\" class=\"cat-err\" role=\"alert\" hidden><\/p>\n<div id=\"cat-aim-out\">\n<p id=\"cat-aim-verdict\" class=\"cat-verdict\" aria-live=\"polite\"><\/p>\n<div class=\"cat-bar\" aria-hidden=\"true\"><span id=\"cat-aim-bar-w\" class=\"cat-bw\"><\/span><span id=\"cat-aim-bar-kv\" class=\"cat-bk\"><\/span><span id=\"cat-aim-bar-o\" class=\"cat-bo\"><\/span><span id=\"cat-aim-bar-limit\" class=\"cat-limit\"><\/span><\/div>\n<ul class=\"cat-key\" aria-hidden=\"true\"><li><i class=\"cat-bw\"><\/i>Weights<\/li><li><i class=\"cat-bk\"><\/i>KV cache<\/li><li><i class=\"cat-bo\"><\/i>Buffers and margin<\/li><li><i style=\"background:#32373c;width:3px\"><\/i>Memory available<\/li><\/ul>\n<table><tbody>\n<tr><th scope=\"row\">Model weights<\/th><td><span class=\"cat-big\" data-k=\"weights\"><\/span><span class=\"cat-sub\" data-k=\"weightsNote\"><\/span><\/td><\/tr>\n<tr><th scope=\"row\">KV cache<\/th><td><span class=\"cat-big\" data-k=\"kvText\"><\/span><span class=\"cat-sub\" data-k=\"kvNote\"><\/span><\/td><\/tr>\n<tr><th scope=\"row\">Buffers and margin<\/th><td><span class=\"cat-big\" data-k=\"overhead\"><\/span><span class=\"cat-sub\" data-k=\"overheadNote\"><\/span><\/td><\/tr>\n<tr><th scope=\"row\">Estimated total<\/th><td><span class=\"cat-big\" data-k=\"total\"><\/span><\/td><\/tr>\n<tr><th scope=\"row\">Memory available<\/th><td><span class=\"cat-big\" data-k=\"available\"><\/span><\/td><\/tr>\n<tr><th scope=\"row\">KV cache per slot<\/th><td><span class=\"cat-big\" data-k=\"perSlot\"><\/span><\/td><\/tr>\n<\/tbody><\/table>\n<p id=\"cat-aim-swa-note\" class=\"cat-note\" hidden><\/p>\n<\/div>\n<p class=\"cat-note\"><strong>Estimates are approximate.<\/strong> Real use varies by runtime, version and settings, and prompt processing needs extra memory on top. Measure the real footprint (for example with ollama ps) before you rely on a number, and leave a margin. 1 GB here is 1,000,000,000 bytes.<\/p>\n<p class=\"cat-note\">Everything is calculated in your browser; nothing you type is sent anywhere. Share your settings by copying the page address.<\/p>\n<script>(function () {\n  'use strict';\n  \/\/ Catharsis local AI memory calculator. Runs entirely in the browser: no network requests, no storage, no eval.\n  \/\/ Every value shown is written with textContent (never parsed as HTML) and all input is validated against strict patterns.\n  \/\/ No ampersand character anywhere: WordPress rewrites it in post content.\n\n  var GB = 1e9;\n\n  \/\/ Architecture numbers from each model's official config.json or model card (sources in README.md).\n  \/\/ swa = layers that use a sliding-window cache, win = window in tokens; native = published weight size in GB.\n  var PRESETS = [\n    { id: 'qwen3-coder-30b', label: 'Qwen3-Coder 30B-A3B (MoE)', params: 30.5, layers: 48, kvHeads: 4, headDim: 128, swa: 0, win: 0 },\n    { id: 'qwen3-8b', label: 'Qwen3 8B', params: 8.2, layers: 36, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'qwen3-14b', label: 'Qwen3 14B', params: 14.8, layers: 40, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'qwen3-32b', label: 'Qwen3 32B', params: 32.8, layers: 64, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'llama3.1-8b', label: 'Llama 3.1 8B', params: 8.03, layers: 32, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'llama3.3-70b', label: 'Llama 3.3 70B', params: 70.6, layers: 80, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'gemma3-12b', label: 'Gemma 3 12B', params: 12.2, layers: 48, kvHeads: 8, headDim: 256, swa: 40, win: 1024 },\n    { id: 'gemma3-27b', label: 'Gemma 3 27B', params: 27.4, layers: 62, kvHeads: 16, headDim: 128, swa: 52, win: 1024 },\n    { id: 'mistral-small-24b', label: 'Mistral Small 3.2 24B', params: 24.0, layers: 40, kvHeads: 8, headDim: 128, swa: 0, win: 0 },\n    { id: 'gpt-oss-20b', label: 'gpt-oss-20b (MoE)', params: 21, layers: 24, kvHeads: 8, headDim: 64, swa: 12, win: 128, native: 13.8 },\n    { id: 'gpt-oss-120b', label: 'gpt-oss-120b (MoE)', params: 117, layers: 36, kvHeads: 8, headDim: 64, swa: 18, win: 128, native: 65.2 }\n  ];\n\n  \/\/ Bits per weight measured by llama.cpp (tools\/quantize README) for GGUF types; FP16\/BF16, FP8 and MXFP4 by definition.\n  var QUANTS = [\n    { id: 'bf16', label: 'FP16 \/ BF16 (16 bits per weight)', bpw: 16 },\n    { id: 'fp8', label: 'FP8 (8 bits)', bpw: 8 },\n    { id: 'q8_0', label: 'Q8_0 (about 8.5 bits)', bpw: 8.5008 },\n    { id: 'q6_k', label: 'Q6_K (about 6.6 bits)', bpw: 6.5633 },\n    { id: 'q5_k_m', label: 'Q5_K_M (about 5.7 bits)', bpw: 5.7036 },\n    { id: 'q4_k_m', label: 'Q4_K_M (about 4.9 bits)', bpw: 4.8944 },\n    { id: 'mxfp4', label: 'MXFP4 (gpt-oss as released)', bpw: 4.25 },\n    { id: 'iq4_xs', label: 'IQ4_XS (about 4.5 bits)', bpw: 4.4597 },\n    { id: 'q3_k_m', label: 'Q3_K_M (about 4.0 bits)', bpw: 3.996 },\n    { id: 'q2_k', label: 'Q2_K (about 3.2 bits)', bpw: 3.1593 }\n  ];\n\n  \/\/ Bytes per cached element. q8_0 and q4_0 store a 16-bit scale per block of 32 values (ggml-common.h).\n  var KVTYPES = [\n    { id: 'f16', label: 'f16 (16-bit, Ollama default)', bytes: 2 },\n    { id: 'q8_0', label: 'q8_0 (8-bit)', bytes: 34 \/ 32 },\n    { id: 'fp8', label: 'fp8 (8-bit, vLLM)', bytes: 1 },\n    { id: 'q4_0', label: 'q4_0 (4-bit)', bytes: 18 \/ 32 }\n  ];\n\n  var HARDWARE = [\n    { id: 'spark', label: 'DGX Spark (128 GB unified)', gb: 128, reserve: 8, unified: true },\n    { id: 'gpu16', label: 'GPU with 16 GB', gb: 16, reserve: 1, unified: false },\n    { id: 'gpu24', label: 'GPU with 24 GB', gb: 24, reserve: 1, unified: false },\n    { id: 'gpu32', label: 'GPU with 32 GB', gb: 32, reserve: 1, unified: false },\n    { id: 'gpu48', label: 'GPU with 48 GB', gb: 48, reserve: 1, unified: false },\n    { id: 'gpu80', label: 'GPU with 80 GB', gb: 80, reserve: 1, unified: false }\n  ];\n\n  \/\/ Runtime buffers and margin: a fixed allowance for compute buffers plus a share of the KV cache (see README calibration).\n  var FIXED_OVERHEAD_GB = 0.5;\n  var KV_MARGIN = 0.10;\n  var FIT_RATIO = 0.85;\n\n  function find(list, id) { for (var i = 0; i < list.length; i++) { if (list[i].id === id) return list[i]; } return null; }\n\n  \/\/ Strict number parsing. kind: 'int' or 'dec'. Returns a number, or null when the text is not acceptable.\n  function num(text, kind, min, max) {\n    var s = String(text === undefined || text === null ? '' : text).trim().replace(\/,\/g, '');\n    var re = kind === 'int' ? \/^[0-9]{1,8}$\/ : \/^[0-9]{1,5}(\\.[0-9]{1,3})?$\/;\n    if (!re.test(s)) return null;\n    var v = +s;\n    if (v < min) return null;\n    if (v > max) return null;\n    return v;\n  }\n  \/\/ Context length accepts 32768, 32,768 or 32k (k = 1,024 tokens).\n  function parseContext(text) {\n    var s = String(text === undefined || text === null ? '' : text).trim().replace(\/,\/g, '');\n    var m = \/^([0-9]{1,4})[kK]$\/.exec(s);\n    var v = m ? (+m[1]) * 1024 : (\/^[0-9]{1,8}$\/.test(s) ? +s : null);\n    if (v === null) return null;\n    if (v < 128) return null;\n    if (v > 1048576) return null;\n    return v;\n  }\n\n  function fmtGb(bytesOrGb) {\n    var v = bytesOrGb;\n    if (v >= 100) return Math.round(v).toLocaleString('en-AU') + ' GB';\n    if (v >= 0.1) return v.toFixed(1) + ' GB';\n    return v.toFixed(2) + ' GB';\n  }\n  function fmtInt(n) { return Math.round(n).toLocaleString('en-AU'); }\n\n  \/**\n   * Estimate memory. o holds raw input text: params, layers, kvHeads, headDim, swa, win, quant, kv, ctx, slots, memGb, reserveGb, model.\n   * Returns { error, field } or the breakdown.\n   *\/\n  function estimate(o) {\n    var params = num(o.params, 'dec', 0.1, 2000);\n    if (params === null) return { error: 'Parameters must be a number of billions between 0.1 and 2000, for example 30.5.', field: 'params' };\n    var layers = num(o.layers, 'int', 1, 512);\n    if (layers === null) return { error: 'Layers must be a whole number from 1 to 512.', field: 'layers' };\n    var kvHeads = num(o.kvHeads, 'int', 1, 256);\n    if (kvHeads === null) return { error: 'KV heads must be a whole number from 1 to 256.', field: 'kvHeads' };\n    var headDim = num(o.headDim, 'int', 8, 1024);\n    if (headDim === null) return { error: 'Head dimension must be a whole number from 8 to 1024.', field: 'headDim' };\n    var swa = num(o.swa === '' ? '0' : o.swa, 'int', 0, 512);\n    if (swa === null) return { error: 'Sliding-window layers must be a whole number (0 if the model has none).', field: 'swa' };\n    if (swa > layers) return { error: 'Sliding-window layers cannot be more than the total number of layers.', field: 'swa' };\n    var win = num(o.win === '' ? '0' : o.win, 'int', 0, 1048576);\n    if (win === null) return { error: 'The sliding window must be a whole number of tokens.', field: 'win' };\n    if (swa > 0) { if (win < 1) return { error: 'Enter the sliding window size in tokens (for example 1024), or set sliding-window layers to 0.', field: 'win' }; }\n    var quant = find(QUANTS, String(o.quant));\n    if (!quant) return { error: 'Choose a weight quantisation.', field: 'quant' };\n    var kv = find(KVTYPES, String(o.kv));\n    if (!kv) return { error: 'Choose a KV cache type.', field: 'kv' };\n    var ctx = parseContext(o.ctx);\n    if (ctx === null) return { error: 'Context length must be from 128 to 1,048,576 tokens (you can type 32k).', field: 'ctx' };\n    var slots = num(o.slots, 'int', 1, 64);\n    if (slots === null) return { error: 'Parallel slots must be a whole number from 1 to 64.', field: 'slots' };\n    var memGb = num(o.memGb, 'dec', 1, 4096);\n    if (memGb === null) return { error: 'Memory must be a number of GB from 1 to 4096.', field: 'memGb' };\n    var reserveGb = num(o.reserveGb === '' ? '0' : o.reserveGb, 'dec', 0, 4096);\n    if (reserveGb === null) return { error: 'Keep free must be a number of GB (0 or more).', field: 'reserveGb' };\n    if (reserveGb >= memGb) return { error: 'Keep free must be less than the total memory.', field: 'reserveGb' };\n\n    var preset = find(PRESETS, String(o.model || ''));\n    var weightsGb, weightsNote;\n    if (quant.id === 'mxfp4') {\n      if (preset) { if (preset.native) { if (preset.params === params) { weightsGb = preset.native; weightsNote = 'published size of the released MXFP4 weights'; } } }\n    }\n    if (weightsGb === undefined) { weightsGb = params * GB * quant.bpw \/ 8 \/ GB; weightsNote = params + ' B parameters \u00d7 ' + quant.bpw.toFixed(2) + ' bits \u00f7 8'; }\n\n    var fullLayers = layers - swa;\n    var perTokenFull = 2 * fullLayers * kvHeads * headDim * kv.bytes;\n    var perTokenSwa = 2 * swa * kvHeads * headDim * kv.bytes;\n    var swaTokens = Math.min(ctx, win);\n    var perSlotBytes = perTokenFull * ctx + perTokenSwa * swaTokens;\n    var kvGb = perSlotBytes * slots \/ GB;\n    var overheadGb = FIXED_OVERHEAD_GB + KV_MARGIN * kvGb;\n    var totalGb = weightsGb + kvGb + overheadGb;\n    var usableGb = memGb - reserveGb;\n    var verdict = totalGb <= usableGb * FIT_RATIO ? 'fits' : (totalGb <= usableGb ? 'tight' : 'nofit');\n    var headroom = usableGb - totalGb;\n    var verdictText;\n    if (verdict === 'fits') verdictText = 'Fits: about ' + fmtGb(totalGb) + ' of ' + fmtGb(usableGb) + ' available, leaving ' + fmtGb(headroom) + ' spare.';\n    else if (verdict === 'tight') verdictText = 'Tight: about ' + fmtGb(totalGb) + ' of ' + fmtGb(usableGb) + ' available, only ' + fmtGb(headroom) + ' spare. Long prompts or a second model could push it over.';\n    else verdictText = 'Will not fit: about ' + fmtGb(totalGb) + ' needed but only ' + fmtGb(usableGb) + ' available (' + fmtGb(-headroom) + ' short). Try a smaller quantisation, a shorter context, fewer slots or an 8-bit cache.';\n\n    var perTokenAvg = perSlotBytes \/ ctx;\n    return {\n      weightsGb: weightsGb, kvGb: kvGb, overheadGb: overheadGb, totalGb: totalGb, usableGb: usableGb, headroomGb: headroom,\n      verdict: verdict, verdictText: verdictText,\n      ctx: ctx, slots: slots, tokens: ctx * slots,\n      weights: fmtGb(weightsGb), weightsNote: weightsNote,\n      kvText: fmtGb(kvGb), kvNote: fmtInt(ctx) + ' tokens \u00d7 ' + slots + (slots === 1 ? ' slot' : ' slots') + ' \u00d7 ' + (perTokenAvg \/ 1000).toFixed(1) + ' KB per token (' + kv.id + ')',\n      overhead: fmtGb(overheadGb), overheadNote: FIXED_OVERHEAD_GB + ' GB compute buffers + ' + Math.round(KV_MARGIN * 100) + '% of the KV cache',\n      total: fmtGb(totalGb),\n      perToken: (perTokenAvg \/ 1000).toFixed(1) + ' KB',\n      perSlot: fmtGb(perSlotBytes \/ GB),\n      swaNote: swa > 0 ? (swa + ' of ' + layers + ' layers use a ' + fmtInt(win) + '-token sliding window, so only ' + fullLayers + ' layers grow with the full context.') : '',\n      available: fmtGb(usableGb)\n    };\n  }\n\n  var api = { estimate: estimate, parseContext: parseContext, num: num, PRESETS: PRESETS, QUANTS: QUANTS, KVTYPES: KVTYPES, HARDWARE: HARDWARE, FIXED_OVERHEAD_GB: FIXED_OVERHEAD_GB, KV_MARGIN: KV_MARGIN };\n  \/\/ Node export only when there is no document, so an element with id=\"module\" on the page (DOM clobbering) cannot stop the tool from starting.\n  if (typeof module !== 'undefined') { if (module.exports) { if (typeof document === 'undefined') { module.exports = api; return; } } }\n\n  \/\/ ---------- page wiring (DOM only via textContent \/ createElement) ----------\n  function el(tag, text) { var e = document.createElement(tag); if (text !== undefined) e.textContent = text; return e; }\n  function fill(select, list, extraLabel) {\n    while (select.firstChild) select.removeChild(select.firstChild);\n    for (var i = 0; i < list.length; i++) { var op = el('option', list[i].label); op.value = list[i].id; select.appendChild(op); }\n    if (extraLabel) { var c = el('option', extraLabel); c.value = 'custom'; select.appendChild(c); }\n  }\n\n  function init() {\n    var root = document.getElementById('cat-ai-memory');\n    if (!root) return;\n    var q = function (s) { return root.querySelector(s); };\n    var f = {\n      model: q('#cat-aim-model'), params: q('#cat-aim-params'), layers: q('#cat-aim-layers'), kvHeads: q('#cat-aim-kvheads'), headDim: q('#cat-aim-headdim'),\n      swa: q('#cat-aim-swa'), win: q('#cat-aim-win'), quant: q('#cat-aim-quant'), kv: q('#cat-aim-kv'), ctx: q('#cat-aim-ctx'), slots: q('#cat-aim-slots'),\n      hw: q('#cat-aim-hw'), memGb: q('#cat-aim-mem'), reserveGb: q('#cat-aim-reserve')\n    };\n    var err = q('#cat-aim-error'), out = q('#cat-aim-out'), verdict = q('#cat-aim-verdict');\n    fill(f.model, PRESETS, 'Custom model');\n    fill(f.quant, QUANTS);\n    fill(f.kv, KVTYPES);\n    fill(f.hw, HARDWARE, 'Custom memory size');\n\n    function applyPreset(id) {\n      var p = find(PRESETS, id);\n      if (!p) return;\n      f.params.value = String(p.params); f.layers.value = String(p.layers); f.kvHeads.value = String(p.kvHeads); f.headDim.value = String(p.headDim);\n      f.swa.value = String(p.swa); f.win.value = String(p.win);\n      if (p.native) f.quant.value = 'mxfp4'; else if (f.quant.value === 'mxfp4') f.quant.value = 'q4_k_m';\n    }\n    function applyHardware(id) {\n      var h = find(HARDWARE, id);\n      if (!h) return;\n      f.memGb.value = String(h.gb); f.reserveGb.value = String(h.reserve);\n    }\n    function values() {\n      return { model: f.model.value, params: f.params.value, layers: f.layers.value, kvHeads: f.kvHeads.value, headDim: f.headDim.value, swa: f.swa.value, win: f.win.value,\n        quant: f.quant.value, kv: f.kv.value, ctx: f.ctx.value, slots: f.slots.value, memGb: f.memGb.value, reserveGb: f.reserveGb.value };\n    }\n    \/\/ Only called after estimate() has accepted the values. Commas are dropped so the link passes the hash pattern.\n    function hv(input, dflt) { return input.value.trim().replace(\/,\/g, '') || dflt; }\n    function hashOf() {\n      var parts = ['m=' + f.model.value];\n      if (f.model.value === 'custom') parts.push('p=' + hv(f.params, ''), 'l=' + hv(f.layers, ''), 'h=' + hv(f.kvHeads, ''), 'd=' + hv(f.headDim, ''), 'sw=' + hv(f.swa, '0'), 'w=' + hv(f.win, '0'));\n      parts.push('q=' + f.quant.value, 'kv=' + f.kv.value, 'c=' + hv(f.ctx, ''), 's=' + hv(f.slots, ''), 'hw=' + f.hw.value);\n      if (f.hw.value === 'custom') parts.push('mem=' + hv(f.memGb, ''), 'r=' + hv(f.reserveGb, '0'));\n      return '#' + parts.join(';');\n    }\n    function setBar(sel, gb, total) { var b = q(sel); b.style.width = (total > 0 ? Math.max(0, Math.min(100, gb \/ total * 100)) : 0).toFixed(2) + '%'; }\n\n    function render(updateHash) {\n      var r = estimate(values());\n      var inputs = root.querySelectorAll('input, select');\n      for (var i = 0; i < inputs.length; i++) inputs[i].removeAttribute('aria-invalid');\n      if (r.error) {\n        err.textContent = r.error; err.hidden = false; out.hidden = true;\n        if (f[r.field]) f[r.field].setAttribute('aria-invalid', 'true');\n        return;\n      }\n      err.hidden = true; err.textContent = '';\n      verdict.textContent = r.verdictText;\n      verdict.className = 'cat-verdict cat-' + r.verdict;\n      var cells = out.querySelectorAll('[data-k]');\n      for (var k = 0; k < cells.length; k++) cells[k].textContent = r[cells[k].getAttribute('data-k')];\n      var swaRow = q('#cat-aim-swa-note');\n      swaRow.textContent = r.swaNote; swaRow.hidden = r.swaNote === '';\n      var scale = Math.max(r.totalGb, r.usableGb);\n      setBar('#cat-aim-bar-w', r.weightsGb, scale); setBar('#cat-aim-bar-kv', r.kvGb, scale); setBar('#cat-aim-bar-o', r.overheadGb, scale);\n      var mark = q('#cat-aim-bar-limit');\n      mark.style.left = (r.usableGb \/ scale * 100).toFixed(2) + '%';\n      out.hidden = false;\n      if (updateHash) { try { history.replaceState(null, '', hashOf()); } catch (e) { \/* ignore *\/ } }\n    }\n    var timer = null;\n    function soon() { clearTimeout(timer); timer = setTimeout(function () { render(true); }, 150); }\n\n    f.model.addEventListener('change', function () { applyPreset(f.model.value); render(true); });\n    f.hw.addEventListener('change', function () { applyHardware(f.hw.value); render(true); });\n    var arch = ['params', 'layers', 'kvHeads', 'headDim', 'swa', 'win'];\n    for (var a = 0; a < arch.length; a++) f[arch[a]].addEventListener('input', function () { f.model.value = 'custom'; soon(); });\n    f.memGb.addEventListener('input', function () { f.hw.value = 'custom'; soon(); });\n    f.reserveGb.addEventListener('input', soon);\n    f.ctx.addEventListener('input', soon); f.slots.addEventListener('input', soon);\n    f.quant.addEventListener('change', function () { render(true); }); f.kv.addEventListener('change', function () { render(true); });\n    q('#cat-aim-form').addEventListener('submit', function (e) { e.preventDefault(); render(true); });\n    var chips = root.querySelectorAll('[data-ctx]');\n    for (var c = 0; c < chips.length; c++) chips[c].addEventListener('click', function (e) { f.ctx.value = e.currentTarget.getAttribute('data-ctx'); render(true); });\n\n    \/\/ Defaults, then a shared link (#m=...;q=...) validated like typed input.\n    f.model.value = 'qwen3-coder-30b'; applyPreset('qwen3-coder-30b'); f.quant.value = 'q4_k_m'; f.kv.value = 'q8_0';\n    f.hw.value = 'spark'; applyHardware('spark');\n    var h = '';\n    try { h = decodeURIComponent(String(location.hash || '').slice(1)); } catch (e) { h = ''; }\n    if (h.length > 0) {\n      if (h.length <= 300) {\n        var keys = { m: 1, p: 1, l: 1, h: 1, d: 1, sw: 1, w: 1, q: 1, kv: 1, c: 1, s: 1, hw: 1, mem: 1, r: 1 };\n        var got = {}, okHash = true, pairs = h.split(';');\n        for (var i = 0; i < pairs.length; i++) {\n          var m = \/^([a-z]{1,3})=([0-9A-Za-z._-]{1,24})$\/.exec(pairs[i]);\n          if (!m) { okHash = false; break; }\n          if (keys[m[1]] !== 1) { okHash = false; break; }\n          got[m[1]] = m[2];\n        }\n        if (okHash) {\n          var saved = values(), savedHw = f.hw.value;\n          var model = got.m === 'custom' ? 'custom' : (find(PRESETS, got.m) ? got.m : null);\n          var hw = got.hw === 'custom' ? 'custom' : (find(HARDWARE, got.hw) ? got.hw : null);\n          if (model) {\n            if (hw) {\n              f.model.value = model;\n              if (model === 'custom') { f.params.value = got.p || ''; f.layers.value = got.l || ''; f.kvHeads.value = got.h || ''; f.headDim.value = got.d || ''; f.swa.value = got.sw || '0'; f.win.value = got.w || '0'; } else applyPreset(model);\n              if (find(QUANTS, got.q)) f.quant.value = got.q;\n              if (find(KVTYPES, got.kv)) f.kv.value = got.kv;\n              if (got.c) f.ctx.value = got.c;\n              if (got.s) f.slots.value = got.s;\n              f.hw.value = hw;\n              if (hw === 'custom') { f.memGb.value = got.mem || ''; f.reserveGb.value = got.r || '0'; } else applyHardware(hw);\n              if (estimate(values()).error) {\n                \/\/ Invalid shared link: put the defaults back.\n                f.model.value = saved.model; f.params.value = saved.params; f.layers.value = saved.layers; f.kvHeads.value = saved.kvHeads; f.headDim.value = saved.headDim;\n                f.swa.value = saved.swa; f.win.value = saved.win; f.quant.value = saved.quant; f.kv.value = saved.kv; f.ctx.value = saved.ctx; f.slots.value = saved.slots;\n                f.hw.value = savedHw; f.memGb.value = saved.memGb; f.reserveGb.value = saved.reserveGb;\n              }\n            }\n          }\n        }\n      }\n    }\n    render(false);\n  }\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();\n})();\n<\/script>\n<\/div>\n\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-does-quantisation-cost-you\">What does quantisation cost you?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Two things change when you drop from 16-bit to 4-bit weights.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Speed goes up.<\/strong> Generating each token means reading the active weights from memory, so on a bandwidth-limited machine fewer bytes means more tokens a second. The DGX Spark's memory bandwidth is 273 GB\/s. In llama.cpp's own table for Llama 3.1 8B, text generation ran at about 29 tokens a second at F16 and about 72 at Q4_K_M on the same (unnamed) hardware. In our lab, <code>qwen3-coder<\/code> at Q4_K_M generated about 60 to 75 tokens a second on the DGX Spark's GPU (about 15 on the CPU, before the GPU worked).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Quality goes down as the bits go down.<\/strong> How much depends on the model and the task, and llama.cpp notes that quantisation &quot;may introduce some accuracy loss&quot;. We ran every workhorse model at Q4_K_M, and for routing, extraction, summaries and tool calls the quality was good enough. Before you go below 4 bits, or trust 4-bit with work that needs careful reasoning, run your own tasks through both versions and compare.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The KV cache has its own quantisation switch (<code>OLLAMA_KV_CACHE_TYPE<\/code>). It is set per Ollama server, not per model. An 8-bit cache roughly halves the cache with what Ollama calls &quot;a very small loss in precision&quot;, and we ran one model we didn't trust in a separate instance with an unquantised f16 cache, one slot and a 16k context, to keep its behaviour conservative.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"our-lab-s-measured-numbers\">Our lab's measured numbers<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Item<\/th>\n<th>Measured<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Total memory \/ usable<\/td>\n<td>128 GB unified; roughly 120 GB usable as reported by the system<\/td>\n<\/tr>\n<tr>\n<td><code>qwen3-coder<\/code>, 64k \u00d7 4 slots, 8-bit cache<\/td>\n<td>about 33 GB (about 19 GB weights + about 14 GB KV)<\/td>\n<\/tr>\n<tr>\n<td>Same model, 2 slots (interim)<\/td>\n<td>22.3 GB<\/td>\n<\/tr>\n<tr>\n<td>Same model, 32k \u00d7 1 slot<\/td>\n<td>20.3 GB<\/td>\n<\/tr>\n<tr>\n<td><code>gemma4<\/code> (26B), 32k \u00d7 1 slot<\/td>\n<td>17.5 GB<\/td>\n<\/tr>\n<tr>\n<td>Two models kept warm together<\/td>\n<td>37.8 GB<\/td>\n<\/tr>\n<tr>\n<td>Kept free at all times<\/td>\n<td>at least 40 GB, for document OCR (about 7 GB on demand), containers (about 15 GB) and prompt processing<\/td>\n<\/tr>\n<tr>\n<td>Cost of doubling context<\/td>\n<td>3.22 GB on one 14B model, 0.80 GB on a 12B model of a different architecture<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That last row is the reason to read the config rather than guess from parameter count: two models of similar size differed fourfold in what extra context cost them. Note too that the 40 GB floor is a policy, not a proven threshold: three of our five freezes happened with more than 80 GB available, and one model that froze on load was eventually sandboxed rather than fixed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"measure-don-t-derive\">Measure, don't derive<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Load the model at the exact context and slot count you plan to run, then read what it took.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li>\n<p><strong>Record the baseline.<\/strong><\/p>\n<pre><code>free -h\n<\/code><\/pre>\n<p><strong>Check:<\/strong> note &quot;used&quot; and &quot;available&quot; before any model is loaded.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Load the model with a short request at your target context.<\/strong><\/p>\n<pre><code>curl -s http:\/\/localhost:11434\/api\/generate \\\n  -d '{&quot;model&quot;:&quot;qwen3-coder&quot;,&quot;prompt&quot;:&quot;hi&quot;,&quot;stream&quot;:false,&quot;options&quot;:{&quot;num_ctx&quot;:32768}}' &gt; \/dev\/null\n<\/code><\/pre>\n<p>A request with a different <code>num_ctx<\/code> reloads the model, so use the value your applications will send, or set it once in a Modelfile (<code>PARAMETER num_ctx 32768<\/code>) or with <code>OLLAMA_CONTEXT_LENGTH<\/code>.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Read what Ollama reports.<\/strong><\/p>\n<pre><code>ollama ps\n<\/code><\/pre>\n<p><strong>Check:<\/strong> PROCESSOR says <code>100% GPU<\/code> (anything showing CPU means it didn't fit or didn't reach the GPU), CONTEXT shows the context you asked for, and SIZE is the figure for your budget.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Get exact bytes from the API.<\/strong><\/p>\n<pre><code>curl -s http:\/\/localhost:11434\/api\/ps | jq '.models[] | {name, size_gb: (.size\/1e9), vram_gb: (.size_vram\/1e9), context_length, quant: .details.quantization_level}'\n<\/code><\/pre>\n<p>This needs <code>jq<\/code>; without it, pipe to <code>python3 -m json.tool<\/code> and read the fields by eye.\n<strong>Check:<\/strong> <code>size_vram<\/code> close to <code>size<\/code> means the model is fully on the GPU. Older Ollama versions omit <code>context_length<\/code>; read it from <code>ollama ps<\/code> instead.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Cross-check with the GPU driver.<\/strong><\/p>\n<pre><code>nvidia-smi --query-gpu=name,memory.used,memory.total --format=csv\nnvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n<\/code><\/pre>\n<p><strong>Check:<\/strong> on a discrete GPU, <code>memory.used<\/code> rises by roughly the model's size. On the DGX Spark and other integrated-GPU systems, NVIDIA documents that <code>nvidia-smi<\/code> shows &quot;Memory-Usage: Not Supported&quot; while still listing per-process memory, so use the second command plus <code>free -h<\/code>.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Measure the KV cost directly.<\/strong> Repeat steps 2 to 4 at double the context (and again with your planned slot count).\n<strong>Check:<\/strong> the difference in <code>size_vram<\/code> is your real cost per extra context or slot. If it disagrees with the formula, trust the measurement and keep the margin.<\/p>\n<\/li>\n\n\n<li>\n<p><strong>Watch it under a long prompt.<\/strong> Send the largest prompt you'll admit and run <code>free -h<\/code> during processing.\n<strong>Check:<\/strong> available memory never drops into the floor you've set.<\/p>\n<\/li>\n\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"a-budget-template-you-can-copy\">A budget template you can copy<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Model<\/th>\n<th>Quant<\/th>\n<th>Context<\/th>\n<th>Slots<\/th>\n<th>KV type<\/th>\n<th>Estimated<\/th>\n<th>Measured (<code>\/api\/ps<\/code>)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Warm models total<\/strong><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Other services<\/strong> (OCR, databases, containers)<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Floor kept free<\/strong><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Must be \u2264 usable memory<\/strong><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Fill the Measured column from step 4, not from the formula. Then make the server enforce it: cap <code>OLLAMA_NUM_PARALLEL<\/code>, <code>OLLAMA_CONTEXT_LENGTH<\/code> and <code>OLLAMA_MAX_LOADED_MODELS<\/code>, and reject prompts larger than the context before they reach the model (<a href=\"https:\/\/catharsis.net.au\/blog\/local-ai-litellm-setup-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">how we did that<\/a>).<\/p>\n\n\n\n<div class=\"wp-block-group has-pale-cyan-blue-background-color has-background\" style=\"padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px;margin-top:32px;margin-bottom:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<p class=\"wp-block-paragraph\"><strong>Want AI that keeps your data in-house?<\/strong> We design, build and secure private AI on hardware you control, with the guardrails our lab series describes.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/catharsis.net.au\/local-ai\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=value-guides&amp;utm_content=local-llm-memory-sizing-guide-local-ai\" target=\"_blank\" rel=\"noreferrer noopener\">Explore private AI<\/a><\/div>\n\n<\/div>\n\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">If you're weighing a private model against a cloud service in the first place, our <a href=\"https:\/\/catharsis.net.au\/blog\/private-ai-vs-chatgpt-vs-copilot\/\" target=\"_blank\" rel=\"noreferrer noopener\">decision guide to private AI, ChatGPT and Copilot<\/a> covers data handling and residency.<\/p>\n\n\n\n<div class=\"wp-block-group has-pale-cyan-blue-background-color has-background\" style=\"padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px;margin-top:32px;margin-bottom:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<p class=\"wp-block-paragraph\"><strong>Using AI at work?<\/strong> We help you set AI rules people will follow, check what data your tools can reach, and meet your privacy obligations.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/catharsis.net.au\/ai-security-governance\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=value-guides&amp;utm_content=local-llm-memory-sizing-guide-ai-security-governance\" target=\"_blank\" rel=\"noreferrer noopener\">See AI security and governance<\/a><\/div>\n\n<\/div>\n\n<\/div><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"key-takeaways\">Key takeaways<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Budget weights, KV cache and overhead separately; the KV cache scales with context \u00d7 slots.<\/li>\n\n\n<li>In Ollama every parallel slot reserves the full context, so four slots cost four times the cache even when one person is using the server.<\/li>\n\n\n<li>Read layers, KV heads and head dimension from the model's <code>config.json<\/code>: similar-sized models can differ fourfold in cache cost.<\/li>\n\n\n<li>4-bit weights need a little over half the memory of 8-bit and run faster on bandwidth-limited hardware; test quality on your own tasks.<\/li>\n\n\n<li>Measure with <code>ollama ps<\/code>, <code>\/api\/ps<\/code>, <code>nvidia-smi<\/code> and <code>free -h<\/code>, then enforce the budget with server limits and a floor of free memory.<\/li>\n\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sources\">Sources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Ollama FAQ (parallel requests, context length, K\/V cache quantisation): <a href=\"https:\/\/docs.ollama.com\/faq\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/docs.ollama.com\/faq<\/a><\/li>\n\n\n<li>Ollama API, list running models (<code>\/api\/ps<\/code>): <a href=\"https:\/\/docs.ollama.com\/api\/ps\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/docs.ollama.com\/api\/ps<\/a><\/li>\n\n\n<li>llama.cpp, quantize README (bits per weight and speed by format): <a href=\"https:\/\/github.com\/ggml-org\/llama.cpp\/blob\/master\/tools\/quantize\/README.md\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/ggml-org\/llama.cpp\/blob\/master\/tools\/quantize\/README.md<\/a><\/li>\n\n\n<li>Qwen3-Coder-30B-A3B-Instruct model card and config: <a href=\"https:\/\/huggingface.co\/Qwen\/Qwen3-Coder-30B-A3B-Instruct\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/huggingface.co\/Qwen\/Qwen3-Coder-30B-A3B-Instruct<\/a><\/li>\n\n\n<li>Qwen3-32B model card and config: <a href=\"https:\/\/huggingface.co\/Qwen\/Qwen3-32B\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/huggingface.co\/Qwen\/Qwen3-32B<\/a><\/li>\n\n\n<li>Qwen2.5-72B-Instruct model card and config: <a href=\"https:\/\/huggingface.co\/Qwen\/Qwen2.5-72B-Instruct\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/huggingface.co\/Qwen\/Qwen2.5-72B-Instruct<\/a><\/li>\n\n\n<li>NVIDIA DGX Spark specifications: <a href=\"https:\/\/www.nvidia.com\/en-us\/products\/workstations\/dgx-spark\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.nvidia.com\/en-us\/products\/workstations\/dgx-spark\/<\/a><\/li>\n\n\n<li>NVIDIA DGX Spark known issues (<code>nvidia-smi<\/code> on integrated GPUs): <a href=\"https:\/\/docs.nvidia.com\/dgx\/dgx-spark\/known-issues.html\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/docs.nvidia.com\/dgx\/dgx-spark\/known-issues.html<\/a><\/li>\n\n\n<li>Catharsis lab series, Part 3: why a local AI server freezes: \/blog\/local-ai-server-freezes-unified-memory\/<\/li>\n\n<\/ul>\n\n\n\n<div class=\"wp-block-group has-light-green-cyan-background-color has-background\" style=\"padding-top:28px;padding-right:32px;padding-bottom:28px;padding-left:32px;margin-top:40px;margin-bottom:32px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-40de7cf3 wp-block-group-is-layout-constrained\">\n\n<h2 class=\"wp-block-heading\">Want a second pair of eyes?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Catharsis Cyber Security is a Sydney cyber security company, certified to ISO\/IEC 27001:2022. We help organisations protect, test and run their technology, and explain it in plain English.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/catharsis.net.au\/contact-us\/?type=contact&amp;service=consult&amp;utm_source=blog&amp;utm_medium=article&amp;utm_campaign=value-guides&amp;utm_content=local-llm-memory-sizing-guide-contact\" target=\"_blank\" rel=\"noreferrer noopener\">Talk to us<\/a><\/div>\n\n\n<div class=\"wp-block-button is-style-outline is-style-outline--1\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/catharsis.net.au\/contact-us\/?type=contact&amp;service=security-health-check&amp;utm_source=blog&amp;utm_medium=article&amp;utm_campaign=value-guides&amp;utm_content=local-llm-memory-sizing-guide-end-health-check\" target=\"_blank\" rel=\"noreferrer noopener\">Free security health check<\/a><\/div>\n\n<\/div>\n\n<\/div><\/div>\n\n\n\n<style>pre.wp-block-code{max-width:100%;overflow-x:auto}pre.wp-block-code code{white-space:pre}.wp-block-list a,.wp-block-table a{overflow-wrap:anywhere;word-break:break-word}<\/style>\n","protected":false},"excerpt":{"rendered":"<p>A local model needs memory for its weights, for a KV cache that grows with context and parallel slots, and for runtime overhead. Here are the formulas, three worked examples from official model configs, the numbers we measured on our lab server, and the commands to check what your models really use.<\/p>\n","protected":false},"author":2,"featured_media":1255,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[69],"tags":[72,229,90,227,75,80,77,228,79],"class_list":["post-1256","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","tag-dgx-spark","tag-gpu-sizing","tag-kv-cache","tag-llm-memory-calculator","tag-local-llm","tag-ollama","tag-private-ai","tag-quantisation","tag-unified-memory"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How much memory does a local LLM need? A sizing guide - Catharsis | Blog<\/title>\n<meta name=\"description\" content=\"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How much memory does a local LLM need? A sizing guide - Catharsis | Blog\" \/>\n<meta property=\"og:description\" content=\"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Catharsis | Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-23T22:50:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-11-03T10:47:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1280\" \/>\n\t<meta property=\"og:image:height\" content=\"720\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Sajal Jain\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sajal Jain\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/\"},\"author\":{\"name\":\"Sajal Jain\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#\\\/schema\\\/person\\\/f0826efb92a7aaf8d46b24715990e099\"},\"headline\":\"How much memory does a local LLM need? A sizing guide\",\"datePublished\":\"2026-10-23T22:50:00+00:00\",\"dateModified\":\"2026-11-03T10:47:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/\"},\"wordCount\":2608,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/local-llm-memory-sizing-guide.jpg\",\"keywords\":[\"dgx spark\",\"GPU sizing\",\"KV cache\",\"LLM memory calculator\",\"local LLM\",\"Ollama\",\"private AI\",\"quantisation\",\"unified memory\"],\"articleSection\":[\"Artificial Intelligence\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/\",\"name\":\"How much memory does a local LLM need? A sizing guide - Catharsis | Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/local-llm-memory-sizing-guide.jpg\",\"datePublished\":\"2026-10-23T22:50:00+00:00\",\"dateModified\":\"2026-11-03T10:47:02+00:00\",\"description\":\"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#primaryimage\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/local-llm-memory-sizing-guide.jpg\",\"contentUrl\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/local-llm-memory-sizing-guide.jpg\",\"width\":1280,\"height\":720,\"caption\":\"How much memory does a local LLM need? A sizing guide\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/local-llm-memory-sizing-guide\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How much memory does a local LLM need? A sizing guide\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/\",\"name\":\"Catharsis | Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#organization\",\"name\":\"Catharsis | Blog\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/01\\\/catharsis_au.jpg\",\"contentUrl\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/01\\\/catharsis_au.jpg\",\"width\":215,\"height\":60,\"caption\":\"Catharsis | Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/#\\\/schema\\\/person\\\/f0826efb92a7aaf8d46b24715990e099\",\"name\":\"Sajal Jain\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/01\\\/Sajal-copy-96x96.jpg\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/01\\\/Sajal-copy-96x96.jpg\",\"contentUrl\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/01\\\/Sajal-copy-96x96.jpg\",\"caption\":\"Sajal Jain\"},\"description\":\"Cyber Security Enthusiast | OSCP | CEH | CHFI | CNSS | Master of IT Security &amp; Master of IT Management\",\"url\":\"https:\\\/\\\/catharsis.net.au\\\/blog\\\/author\\\/sajal-jain\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How much memory does a local LLM need? A sizing guide - Catharsis | Blog","description":"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/","og_locale":"en_US","og_type":"article","og_title":"How much memory does a local LLM need? A sizing guide - Catharsis | Blog","og_description":"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.","og_url":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/","og_site_name":"Catharsis | Blog","article_published_time":"2026-10-23T22:50:00+00:00","article_modified_time":"2026-11-03T10:47:02+00:00","og_image":[{"width":1280,"height":720,"url":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg","type":"image\/jpeg"}],"author":"Sajal Jain","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Sajal Jain","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#article","isPartOf":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/"},"author":{"name":"Sajal Jain","@id":"https:\/\/catharsis.net.au\/blog\/#\/schema\/person\/f0826efb92a7aaf8d46b24715990e099"},"headline":"How much memory does a local LLM need? A sizing guide","datePublished":"2026-10-23T22:50:00+00:00","dateModified":"2026-11-03T10:47:02+00:00","mainEntityOfPage":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/"},"wordCount":2608,"commentCount":0,"publisher":{"@id":"https:\/\/catharsis.net.au\/blog\/#organization"},"image":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg","keywords":["dgx spark","GPU sizing","KV cache","LLM memory calculator","local LLM","Ollama","private AI","quantisation","unified memory"],"articleSection":["Artificial Intelligence"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/","url":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/","name":"How much memory does a local LLM need? A sizing guide - Catharsis | Blog","isPartOf":{"@id":"https:\/\/catharsis.net.au\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#primaryimage"},"image":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg","datePublished":"2026-10-23T22:50:00+00:00","dateModified":"2026-11-03T10:47:02+00:00","description":"How much memory does a local LLM need? Weights, KV cache and overhead formulas, worked examples, lab measurements and the commands to check real usage.","breadcrumb":{"@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#primaryimage","url":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg","contentUrl":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2026\/11\/local-llm-memory-sizing-guide.jpg","width":1280,"height":720,"caption":"How much memory does a local LLM need? A sizing guide"},{"@type":"BreadcrumbList","@id":"https:\/\/catharsis.net.au\/blog\/local-llm-memory-sizing-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/catharsis.net.au\/blog\/"},{"@type":"ListItem","position":2,"name":"How much memory does a local LLM need? A sizing guide"}]},{"@type":"WebSite","@id":"https:\/\/catharsis.net.au\/blog\/#website","url":"https:\/\/catharsis.net.au\/blog\/","name":"Catharsis | Blog","description":"","publisher":{"@id":"https:\/\/catharsis.net.au\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/catharsis.net.au\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/catharsis.net.au\/blog\/#organization","name":"Catharsis | Blog","url":"https:\/\/catharsis.net.au\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/catharsis.net.au\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2019\/01\/catharsis_au.jpg","contentUrl":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2019\/01\/catharsis_au.jpg","width":215,"height":60,"caption":"Catharsis | Blog"},"image":{"@id":"https:\/\/catharsis.net.au\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/catharsis.net.au\/blog\/#\/schema\/person\/f0826efb92a7aaf8d46b24715990e099","name":"Sajal Jain","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2019\/01\/Sajal-copy-96x96.jpg","url":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2019\/01\/Sajal-copy-96x96.jpg","contentUrl":"https:\/\/catharsis.net.au\/blog\/wp-content\/uploads\/2019\/01\/Sajal-copy-96x96.jpg","caption":"Sajal Jain"},"description":"Cyber Security Enthusiast | OSCP | CEH | CHFI | CNSS | Master of IT Security &amp; Master of IT Management","url":"https:\/\/catharsis.net.au\/blog\/author\/sajal-jain\/"}]}},"_links":{"self":[{"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/posts\/1256","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/comments?post=1256"}],"version-history":[{"count":1,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/posts\/1256\/revisions"}],"predecessor-version":[{"id":1288,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/posts\/1256\/revisions\/1288"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/media\/1255"}],"wp:attachment":[{"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/media?parent=1256"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/categories?post=1256"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/catharsis.net.au\/blog\/wp-json\/wp\/v2\/tags?post=1256"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}