Inspiration

Every developer deploying an LLM on an Arm cloud server hits the same wall: the fastest configuration is a hardware fact, not folklore. Q4 versus Q8, two threads versus four — the answer changes between a four-core Neoverse and a sixty-four-core Graviton, and nobody hand-tests twenty-four combinations with identical prompts, warmup, seeds, and a quality gate. So we built the tool we wanted to exist: an autotuner for LLM serving on Arm.

What it does

ArmTune Serve measures how an LLM actually behaves on your exact Arm64 machine, then hands you the deployment command. It pulls a GGUF model from Hugging Face, fingerprints the CPU (NEON, I8MM, SVE2, cores, NUMA), runs a controlled sweep on a real llama-server process built with Arm KleidiAI kernels, captures Arm Performix hardware counters during inference, rejects configurations below the quality threshold, and prints a copy-paste launch command.

The recommendation problem we solve, formally: over the candidate configuration space $C$ (quantization × threads × concurrency), we select

$$ c^* = \arg\max_{c \in C} \; s_{\text{obj}}(c) \quad \text{subject to} \quad q(c) \geq q_{\min} = 0.5 $$

where $s_{\text{obj}}$ is the objective score (latency, throughput, memory, or balanced) and $q(c)$ is the structured-output quality of configuration $c$. Without the constraint, a fast-but-broken config always wins.

How we built it

The runtime is abstracted behind an adapter interface; the primary adapter drives llama-server through its OpenAI-compatible API so we benchmark the exact binary compiled with GGML_CPU_KLEIDIAI=ON and -mcpu=native, and capture its startup evidence in every result. The CI pipeline builds generic and KleidiAI variants, then runs the identical workload matrix on both. The dashboard console provides a guided pipeline — live Hugging Face search, model cards, streaming terminal — so no one copy-pastes repo IDs.

Measured results (Neoverse-N2, 4 cores, 15.6 GB, GitHub ARM64 runner)

Metric Q4_K_M · 4 threads Q4_0 · 4 threads Change
Decode tokens/sec 26.1 33.0 +26%
Time to first token 0.45 s 0.33 s -27%
P95 latency 2.52 s 1.91 s -24%
Peak process RSS 1701 MB 1616 MB -5%
Quality score 1.00 1.00 gate held

Thread sweep: 1 thread 8.1 tok/s → 2 threads 15.1 tok/s → 4 threads 28.1 tok/s (3.5×). Full artifacts are committed under docs/evidence/.

Challenges we ran into

  • The Arm Performix CLI subcommands vary by version — we built a transparent probe that records exactly what ran, so failures are never silent.
  • Quality must be measurable or autotuning is meaningless: raw completions ramble, so prompts route through the instruct chat template and outputs are scored as strict JSON against expected labels.
  • A silent fallback bug let CI produce mock numbers once; we caught it, added a regression test, and made the CLI refuse to be quiet about it.

Accomplishments we're proud of

The whole pipeline runs on free, public, native-ARM64 infrastructure and any claim in this write-up is regenerable with one click. And the honest finding is the best part: on this hardware the winning move was quantization plus thread count — not the kernel flag — because decode is memory-bound. The data proved it. That is exactly what measurement tools are for.

What we learned

On Arm, quantization choice is a kernel-fit question, not a size question. Prefill and decode want different thread counts. Oversubscription shows up in P95 before it shows up in the mean. And hardware counters turn "it's faster" into "it's faster because IPC rose and LLC misses fell" — the difference between a benchmark and an explanation.

What's next

Pin exact Performix CLI flags, NUMA and affinity sweeps on larger instances, LM-Eval-Harness scoring, a vLLM adapter, and the top-down layer this enables: fleet scheduling on measured per-replica capacity curves instead of CPU-utilization guesswork.

Built With

  • arm-performix
  • arm64
  • aws-graviton
  • azure-cobalt
  • gguf
  • github-actions
  • google-axion
  • gradio
  • httpx
  • hugging-face
  • inference-optimization
  • llama.cpp
  • matplotlib
  • neoverse
  • nextjs
  • open-source
  • pandas
  • psutil
  • pydantic
  • python
  • quantized-llm
  • react
  • typer
  • vercel
Share this project:

Updates

Submission history