Xyntetik xyntetik

Xyntetik Runner, a Zenova AB product

Local inference you can prove.

Runner is a single-binary engine in plain C that serves, scores and trains GGUF models on hardware you own. It closes tool calls that run out of tokens so they still parse, trains LoRA adapters through the same quantized weights it serves, and records any run as a signed receipt that replays.

Every number on this site names the document it comes from and the day it was measured, including the rows where Runner loses.

Apache 2.0, free forever CPU, CUDA and Metal No Python, no pip, no ggml v0.4.9, released 2026-09-05

The mark is an ensö, the Zen circle drawn in one uninhibited stroke and often left open, and Xyntetik tries to work the way it is drawn. One stroke: plain tools that do one thing whole, a single binary with no stack hidden underneath. Whole, imperfections included: every number carries its measurement, the rows where we lose too. Open where the work is not finished: what is not claimed is written down as carefully as what is. And present: models run here, on hardware you own, in the moment.

Why Runner exists

Serving tokens locally is solved. Trusting what came out is not.

Excellent local engines already exist and Runner rides their formats rather than competing with them. It exists because four things go wrong the moment a local model has to do real work, and none of them is a speed problem.

An agent loop dies at the token budget

When max_tokens runs out in the middle of a tool call's JSON, most engines hand back nothing usable: an empty call, framing leaked into the prose, or arguments that do not parse. On a local model, where context is tight and generation is slow, the agent retries from scratch and burns the window it was short of.

Runner closes the call to the smallest schema-legal document instead, so the arguments parse and the loop continues. How, and what six engines do

What you trained is not what you ship

The usual fine-tuning path trains through an FP16 copy and then serves a quantized file. Two numerical paths means the policy you sampled is not the policy you trained, and a 4-bit merge can quietly erase the adaptation while perplexity still looks fine.

Runner trains through the frozen quantized GGUF it serves. One codepath, and a reproducibility contract with named edges. The measured claim and its caveat

A log is not evidence

If you have to show what a model said, what it learned from, or which file you actually deployed, a printout of a chat is not going to hold. You need the model hash, the adapter hash, the binary, the seed, the exact bytes, and a way for someone else to replay them.

Runner writes that record as a signed, chained receipt, and a verifier answers VERIFIED, DIVERGED or UNVERIFIABLE with one exit code. Receipts and the determinism scope

The server takes over the workstation

A resident inference server on the machine you also work on wires down gigabytes and ticks in the background while it serves nothing. That is the reason people kill it every time.

Runner keeps weights as evictable mappings the OS can reclaim, wakes about once every two seconds while idle, and hands everything back on /unload. The cost is a slower cold first token, and that cost is published. The idle measurement

Where it wins, where it does not

Use llama.cpp. Then read this table.

llama.cpp is the ecosystem, and if you want maximum architecture coverage and raw speed you should use it. Runner adds contracts the ecosystem does not make. Every row below is a measurement from the repository, and the comparison covers the engines and versions that were actually measured.

QuestionRunnerllama.cppvLLM, Ollama, TensorRT-LLM, SGLang
A tool call is cut short by max_tokens. What does the caller get? Executable tool_calls, arguments parse, at every budget from 1 to 16 tokens No call, framing leaked into content; at 16 tokens a call whose arguments do not parse (b10488) No usable call at any truncated budget; Ollama returns HTTP 500 at 16 (versions on the evidence page)
Decode throughput, dense models, CUDA 81 to 93% of llama.cpp on the same slice and files Faster Not measured
Prefill throughput, CUDA 5 to 10% of llama.cpp. Runner's honest weak column, and the next piece of work An order of magnitude faster Not measured
Architecture coverage A named list; unknown architectures are refused by name, never guessed The widest Not compared
Memory held while idle beside your other work +8 MB wired on an 8 GB M1; +35 MB with a 63 GB model on a 128 GB M5 Max; /unload gives it all back +3,819 MB and +60.8 GB on the same two machines; no unload Not measured
Cold time to first token when the server is allowed to hold everything Slower: 3.2 s vs 0.15 s on the pressured M1, 10.1 s vs 0.25 s at 120B. Residency is what llama-server buys Faster Not measured
Train a LoRA through the quantized file you serve, reproducibly Yes. Same forward pass as serving; same data, seed and config write a byte-identical adapter Runner-trained adapters load and score identically in stock llama.cpp; community F16 adapters load back into Runner Not measured
Prove what ran: signed receipts, replay verdicts, model-signature checks Ed25519- or ML-DSA-44-signed chained receipts, VERIFIED / DIVERGED / UNVERIFIABLE replay, OpenSSF Model Signing verification of the loaded GGUF Not claimed Not claimed
Vulkan, TLS, remote bind Not implemented. Loopback only by design; put a reverse proxy in front for anything else Vulkan available Not compared

Speed rows: MIG 1g.24gb slice, runner 6d705e9 vs llama.cpp b10353, 2026-09-02. Truncation row: 2026-08-19, granite-4.1-3b, versions named on the evidence page. Idle rows: 2026-09-01 and the README's M1 measurement. Sources: docs/benchmarks.md, docs/truncation-benchmark.md, docs/idle-coexistence-120b-m5max-2026-09-01.md.

Choose Runner for the contracts, not the speed.

If the machine is dedicated to inference and you want the fastest prefill and the widest model list, llama.cpp is the right call and we mean that sincerely. If you need an agent loop that finishes on a small budget, adapters trained on the exact file you deploy, receipts that a third party can replay, or an engine that can live on your workstation all day, that is what Runner is for.

Proof

We tested this. Here is what happened.

Three of the measurements the project rests on. Each figure names its source document and date; the raw responses, scripts and pinned files are in the repository, and the regression gates run on every release.

executable tool call, arguments parse no usable call (empty, leaked framing, unparseable arguments, or HTTP 500)
Per token budget, does the client receive an executable tool call123581664 ctrlRunnerRunner, max_tokens 1: executable tool call, arguments parseRunner, max_tokens 2: executable tool call, arguments parseRunner, max_tokens 3: executable tool call, arguments parseRunner, max_tokens 5: executable tool call, arguments parseRunner, max_tokens 8: executable tool call, arguments parseRunner, max_tokens 16: executable tool call, arguments parseRunner, max_tokens 64: executable tool call, arguments parsevLLM 0.27.1vLLM 0.27.1, max_tokens 1: no usable callvLLM 0.27.1, max_tokens 2: no usable callvLLM 0.27.1, max_tokens 3: no usable callvLLM 0.27.1, max_tokens 5: no usable callvLLM 0.27.1, max_tokens 8: no usable callvLLM 0.27.1, max_tokens 16: no usable callvLLM 0.27.1, max_tokens 64: executable tool call, arguments parsellama.cpp b10488llama.cpp b10488, max_tokens 1: no usable callllama.cpp b10488, max_tokens 2: no usable callllama.cpp b10488, max_tokens 3: no usable callllama.cpp b10488, max_tokens 5: no usable callllama.cpp b10488, max_tokens 8: no usable callllama.cpp b10488, max_tokens 16: tool_calls present, arguments do not parse{ "llama.cpp b10488, max_tokens 64: executable tool call, arguments parseOllama 0.32.14Ollama 0.32.14, max_tokens 1: no usable callOllama 0.32.14, max_tokens 2: no usable callOllama 0.32.14, max_tokens 3: no usable callOllama 0.32.14, max_tokens 5: no usable callOllama 0.32.14, max_tokens 8: no usable callOllama 0.32.14, max_tokens 16: HTTP 500500Ollama 0.32.14, max_tokens 64: executable tool call, arguments parseTensorRT-LLM 1.2.1 †TensorRT-LLM 1.2.1 †, max_tokens 1: no usable callTensorRT-LLM 1.2.1 †, max_tokens 2: no usable callTensorRT-LLM 1.2.1 †, max_tokens 3: no usable callTensorRT-LLM 1.2.1 †, max_tokens 5: no usable callTensorRT-LLM 1.2.1 †, max_tokens 8: no usable callTensorRT-LLM 1.2.1 †, max_tokens 16: no usable callTensorRT-LLM 1.2.1 †, max_tokens 64: executable tool call, arguments parseSGLang 0.5.17 †SGLang 0.5.17 †, max_tokens 1: no usable callSGLang 0.5.17 †, max_tokens 2: no usable callSGLang 0.5.17 †, max_tokens 3: no usable callSGLang 0.5.17 †, max_tokens 5: no usable callSGLang 0.5.17 †, max_tokens 8: no usable callSGLang 0.5.17 †, max_tokens 16: no usable callSGLang 0.5.17 †, max_tokens 64: executable tool call, arguments parse
Tool calls that survive the token limit. Same box, same tool schema and prompt, tool_choice: required, temperature 0, max_tokens from 1 to 64. The 64-token control proves the failure is truncation, not misconfiguration: every engine completes there. Runner 0.1.19-alpha, vLLM 0.27.1, llama.cpp b10488, Ollama 0.32.14, TensorRT-LLM 1.2.1, SGLang 0.5.17. measured on a Qwen3-1.7B substitute because those registries did not carry granite-4.1-3b; recovery is a property of the runtime, so the substitution measures the engine. Measured 2026-08-19. Raw responses: tests/torture/truncation/. Method: docs/truncation-benchmark.md.
Held-out exact tool call score before and after training, and after merging0.000.250.500.751.00Base Qwen3-4B Q4_K_MBase Qwen3-4B Q4_K_M: exact-call score 0.690.69Base + adapter (--lora)Base + adapter (--lora): exact-call score 1.001.00Merged into F16Merged into F16: exact-call score 1.001.00Merged into Q8_0Merged into Q8_0: exact-call score 1.001.00Merged into Q4_0Merged into Q4_0: exact-call score 0.690.69 erased
A LoRA trained through 4-bit serving weights, and what merging does to it. Held-out exact tool-call score on Qwen3-4B. The adapter served beside the base with --lora takes the score from 0.69 to 1.00, and the same 1.00 is reproduced in stock llama.cpp. Merging into F16 or Q8_0 keeps it. Merging into the 4-bit base erases it: 98.55% of weight bytes round back to the base's own codes. Measured 2026-08-22. Artifact: Qwen3-4B-Runner-ToolUse-Q4_K_M. Method: docs/adaptation-engine.md.
Runner throughput as a percentage of llama.cpp, decode and prefillDecode tokens/s, runner as % of llama.cpp0%25%50%75%100%Llama-3.2-3B Q4_K_MLlama-3.2-3B Q4_K_M: runner 87.9 vs llama.cpp 101.8 tok/s (86%)86%Phi-4-mini Q8_0Phi-4-mini Q8_0: runner 53.8 vs llama.cpp 57.7 tok/s (93%)93%granite-3.3-8b Q4_K_Mgranite-3.3-8b Q4_K_M: runner 39.7 vs llama.cpp 45.4 tok/s (87%)87%granite-4.1-8b Q4_0granite-4.1-8b Q4_0: runner 41.7 vs llama.cpp 46.5 tok/s (90%)90%Phi-3.5-mini Q4_K_MPhi-3.5-mini Q4_K_M: runner 72.0 vs llama.cpp 83.2 tok/s (87%)87%gemma-4-12B Q4_K_Mgemma-4-12B Q4_K_M: runner 23.2 vs llama.cpp 28.8 tok/s (81%)81%Qwen3-30B-A3B Q4_K_M (MoE)Qwen3-30B-A3B Q4_K_M (MoE): runner 62.2 vs llama.cpp 86.6 tok/s (72%)72%gemma-4-26B-A4B Q4_0 (MoE)gemma-4-26B-A4B Q4_0 (MoE): runner 23.3 vs llama.cpp 56.2 tok/s (41%)41%Qwen2.5-32B Q3_K_SQwen2.5-32B Q3_K_S: runner 1.8 vs llama.cpp 14.3 tok/s (13%)13%Prefill tokens/s, runner as % of llama.cpp0%25%50%75%100%Llama-3.2-3B Q4_K_MLlama-3.2-3B Q4_K_M: runner 525.7 vs llama.cpp 6387.6 tok/s (8%)8%Phi-4-mini Q8_0Phi-4-mini Q8_0: runner 355.6 vs llama.cpp 5960.6 tok/s (6%)6%granite-3.3-8b Q4_K_Mgranite-3.3-8b Q4_K_M: runner 220.5 vs llama.cpp 2288.9 tok/s (10%)10%granite-4.1-8b Q4_0granite-4.1-8b Q4_0: runner 151.1 vs llama.cpp 2359.2 tok/s (6%)6%Phi-3.5-mini Q4_K_MPhi-3.5-mini Q4_K_M: runner 284.9 vs llama.cpp 5120.3 tok/s (6%)6%gemma-4-12B Q4_K_Mgemma-4-12B Q4_K_M: runner 123.7 vs llama.cpp 1439.2 tok/s (9%)9%Qwen3-30B-A3B Q4_K_M (MoE)Qwen3-30B-A3B Q4_K_M (MoE): runner 128.5 vs llama.cpp 2326.3 tok/s (6%)6%gemma-4-26B-A4B Q4_0 (MoE)gemma-4-26B-A4B Q4_0 (MoE): runner 34.3 vs llama.cpp 2429.7 tok/s (1%)1%Qwen2.5-32B Q3_K_SQwen2.5-32B Q3_K_S: runner 15.3 vs llama.cpp 469.0 tok/s (3%)3%
Speed, including the rows Runner loses. Runner 6d705e9 vs llama.cpp b10353 CUDA on one MIG 1g.24gb slice, same GGUF files, full offload verified on every row, 512-token prefill and 128-token greedy decode. Dense decode sits at 81 to 93% of llama.cpp; prefill at 5 to 10% is the published weak column; the Q3_K row still runs a naive token-identical kernel. Absolute numbers depend on the slice, the ratios are the result. Measured 2026-09-02. Source: docs/benchmarks.md, with RTX 3070 and M1 tables.

Published artifacts

Sixteen repositories on Hugging Face, gated the same way.

Nine carry weights, seven are measurement reports over third-party files with no weights republished. A derivative is published only after its stated gate against the named parent, and a near-miss stays published with its numbers in the open rather than being quietly removed.

  • Attention Q8_0, experts Q4_0, 17.99 GB. Passes the fidelity bar where the official uniform Q4_K_M fails it.
    passes bar
  • The LoRA behind the 0.69 to 1.00 claim, produced twice with the same sha256; the card carries the sidecar and the training command.
    reproduced
  • First sublayer-removal artifact: one attention sublayer physically dropped, 64 MiB of KV cache freed at 4k context, KLD 0.0223 on 44,413 held-out positions.
    KLD 0.0223
  • Expert-pruned derivative whose originally published number did not reproduce. Kept published with the measured status at the top of the card.
    fails current bar

About

A Zenova AB product, built in Sweden

Xyntetik is the program under which Zenova AB builds tooling for running capable models on hardware you already own. Runner is the released engine. Suite is the testing and evidence layer around it. Shade is the research lab that feeds both. The engine is free forever under Apache 2.0.

Runner, Suite, Shade and the company behind them →

Support the work

Open infrastructure, funded in the open

Runner is developed openly rather than around a subscription. Measurement time on real hardware, model hosting, cross-engine validation and the loaner machines behind the benchmark tables are what contributions pay for.

What contributions enable →