I run a local LLM on a CPU-only server with no GPU. Two days of benchmarks changed how I build agent workflows, and it can be summed up in one line:

The LLM is the brain, not the hands. It plans, reasons and explains. Scripts execute.

Hardware: 2 × Xeon E5-2680 v3 (a 2014-era part), 125 GB RAM, no GPU. Inference with llama.cpp. (I’ve written up the setup in Running a Local LLM on CPU.)


Part 1 — Getting the model to respond at all

ModelTestContext tokensTimeResult
Qwen3-14B Q4_K_M (9.0 GB)Simple “Hi”2~3 sOK
Qwen3-14B Q4_K_MRAG context368> 600 sTimeout
Qwen3-14B Q4_K_MFull context8000> 600 sTimeout
Qwen3-8B Q4_K_M (4.7 GB)Simple query4321.7 sOK
Qwen3-8B Q4_K_MRAG context36839.9 sOK
Qwen3-8B Q4_K_MFull context8000~3–5 minSlow but works

Switching from 14B to 8B was the single biggest win. The model is about half the size, prompt processing (prefill) is 15–20× faster by my timings, and generation speed is similar at roughly 8–10 tokens/s. A RAG query went from “never finishes” to “about 40 seconds”.

The RAG pipeline itself was straightforward: ChromaDB with all-MiniLM-L6-v2 embeddings over 86 chunks of internal documentation. Retrieval quality was good in my spot checks (all five returned chunks were relevant). The real gain is context size: retrieval cut the prompt from about 8000 tokens to 368, roughly 95% less, and on a slow CPU, prompt tokens are what hurt.

Takeaways:

  1. A 14B model on this CPU is unusable; even a few hundred tokens of context timed out.
  2. An 8B model on the same box is usable, and RAG makes it practical.
  3. The bottleneck was inference speed on old hardware, not tokenisation.
  4. A GPU would still help enormously. My own estimate is that a used 24 GB card would bring RAG queries under 5 seconds. I haven’t tested that, so treat it as a guess and check current prices before buying.

Part 2 — Then I asked: why use the LLM at all for this?

Many of my agent’s jobs are not reasoning. They’re “fetch this list from an API”, “check this VM’s status”, “export this table”. I benchmarked the LLM against a plain script on the same tasks (a cloud model handled orchestration and planning in both cases):

TaskScriptLLM (8B + RAG)Winner
Export a data report2 s61 sScript, ~30×
List fees for a class1 s45–60 sScript, ~45×
Full data query3 s90–120 sScript, ~40×
Proxmox VM status3 s40–60 sScript, ~15×
System health check1 sn/a (no context)Script
Troubleshooting / reasoningn/a20–40 sLLM, the only option
Code generation or fixesn/a30–90 sLLM, the only option
Open-ended Q&An/a30–60 sLLM, the only option

These are single-machine timings from one session, so read them as orders of magnitude rather than precise ratios.

ScriptLLM
NatureDeterministicProbabilistic
AccuracySame every run, if coded rightMy rough estimate: 80–95% depending on context
LatencyMilliseconds to seconds20 to 600+ seconds on this hardware
HallucinationNot possiblePossible
MaintenanceEdit the scriptEdit the skill docs

The accuracy row matters most for me. When a task touches real money or real infrastructure, a wrong answer costs more than a slow one.


The architecture that came out of it

ORCHESTRATOR (cloud LLM)
  • understands intent
  • picks the right script
  • composes the final reply
  • troubleshoots failures, writes new code when needed
      │
      ├─► DATA tier     scripts that query business systems and export reports
      ├─► INFRA tier    proxmox_status.py, system_health.sh, ad-hoc SSH
      ├─► DOCS tier     scripts that wrap the Notion API
      └─► UTILITY tier  spreadsheet/PDF generators, file operations, the RAG pipeline

The decision tree is short: is it data retrieval, an infrastructure check, or a document operation? Then run a script. Only if it genuinely requires reasoning does it go to an LLM, and even then the LLM plans and explains while a human (or a script) executes.

Rules for adding a script

  1. Read-only by default. Writes (saving a file, exporting a sheet) need an explicit --write or --excel flag.
  2. Every script has --json for machine-readable output.
  3. Credentials from the environment, never hardcoded.
  4. One script per domain, one place to fix bugs.
  5. Test on creation: run --help and one real call before relying on it.

Why not just use the LLM for everything?

On this hardware, a “Hi” takes 3 s, a 368-token RAG prompt takes 40 s, and an 8000-token prompt takes minutes. A script that logs in, queries, filters and writes a spreadsheet takes 2 s, and produces the same answer every time. Scripts have no hardware dependency. The LLM does.

That doesn’t make the LLM less useful — it makes it more precise about where it belongs. A script can’t tell you why a request timed out, or write the script you don’t have yet. That’s the LLM’s job.

Principles I kept

  1. LLM for reasoning, scripts for execution. Never the other way round.
  2. Deterministic beats probabilistic whenever the output is structured data.
  3. Speed is a feature. A 1-second script beats a 60-second LLM that gives the same answer.
  4. Accuracy is not negotiable when errors cost real money.
  5. One source of truth per domain.

A companion experiment, Does Memory Plus Skills Close the Gap for a Small LLM?, looks at the other half of the same idea: giving a small model the knowledge it lacks.