
crit
io.github.crit-labs/crit
Build, evaluate and serve a bespoke LLM inference engine for your own GPU with crit's commands.
Documentation
Why wait for someone to optimize an LLM for your GPU when an AI agent can build a specialized inference engine in a few hours?
Summary • Quick start • What crit solves • Case studies •
How crit works • API • CLI • crit-hub • Limitations • Documentation
Summary
crit is a toolkit for optimizing or building inference engines tailored to your hardware and desired model.
[!IMPORTANT] Combines a runtime foundation, reusable techniques as plugins, kernel discovery and physics-based evaluation.
In our latest case study, a coding agent built an engine for Qwen3.8-27B on one RTX 5090 in under 4 hours and five candidates, reaching 189 tok/s sampled decode and 8,295 tok/s prefill with the model's full 262,144-token context.
Follow the building guide yourself, or give your coding agent the agent instructions, target model and hardware.
Quick start
Give one of these prompts to your coding agent and specify your target LLM or target engine:
Use github.com/crit-labs/crit to build an optimized inference engine for <huggingface_url>.
Use github.com/crit-labs/crit to optimize my own inference engine in <engine_reference>.
What crit solves
The boring stuff has already been built ⏱️
Requests, memory management and cleanup eat up development time before optimization even starts.
critsupplies a clean foundation so you can get to the work that makes your engine fast.
Curated high-impact inference strategies 📊
Don't rebuild inference strategies from scratch. We studied established inference engines and distilled their core speedup strategies into reusable code, so you can apply what works to your model and hardware quickly. Each strategy is a plugin with its own tests, and an ablation switches each one off to measure what it adds to your engine.
Physics-based evaluation suite 🔬
A benchmark score alone doesn't tell you what's still left on the table.
critmeasures your hardware's limits and shows how close your engine gets, with quality and correctness checks along the way.
Case Studies
[!NOTE] The engines of these case studies were built with crit 0.16, before the plugin architecture. They are being rebuilt as plugin packages and return to
crit-hubas they are.
We gave Claude Opus 5.5 a target LLM along with the crit repository and let it cook autonomously.
| What it shows | Evidence |
|---|---|
| Fast engines | Qwen3.8-27B on one RTX 5090: 189 tok/s sampled decode, 4.4x its own first working candidate, and 8,295 tok/s prefill, with the full 262,144-token context |
| Quality is your call | The same model built once per quality mode: speed prefills 2.3x faster, preserve stays within 0.4% of the exact reference's perplexity, and both decode at the same speed |
| Hours, not weeks | 3h 55m and five candidates from the run's first command to the selected engine, $51 at API list prices |
| Every step is evidence | Each candidate is sealed with its exact source and its Eval result, so the tables below are a record, not a recollection |
| Honest about what is left | Decode runs at 53% of the ceiling Eval measures for it, prefill at 46%; Eval reports each gap next to the score |
| Next | Gemma 4 31B (dense), and Qwen3.8-Flash-Next and Nemotron 3.5 Super 120B, mixture-of-experts models larger than VRAM |
Dev box details
| Part | Spec |
|---|---|
| GPU | NVIDIA GeForce RTX 5090, 32 GB, 450 W power limit |
| PCIe | Gen 4 x16 (the card supports Gen 5; the board's slot is Gen 4) |
| CPU | AMD Ryzen 5 9600, 6 cores, 12 threads |
| RAM | 96 GB DDR5 (2x 48 GB) at 4,800 MT/s |
| OS | Windows 11 Home (build 26200), engines run in WSL 2 on Ubuntu 24.04 |
| Driver and CUDA | NVIDIA driver 596.49 (CUDA 13.2), CUDA toolkit 13.1 |
📋 Dense LLM, Qwen architecture
Goal: Get to a fast, working engine for a dense model as quickly as possible. Limit: A fixed candidate budget.
|
3h 55m Development time |
189.0 tok/s Sampled decode |
8,295 tok/s Prefill |
154 ms TTFT |
+3.6% Perplexity vs reference |
0.134 Mean KL divergence |
mtp4-nvfp4-mma
runs Qwen3.8-27B in its original NVFP4 weights on one RTX 5090, with the model's full 262,144
tokens of context, in the speed quality mode: the engine may trade more
drift from the checkpoint for speed (crit-eval-v0.2.1, which measured it, judged that mode against the
checkpoint run with the 4-bit and 8-bit activations it declares). One coding agent built it on crit's current main as a Python engine on Core:
cuBLASLt for the prompt's 4-bit and 8-bit matrix multiplies, flash-linear-attention for the
gated-delta layers, and its own Triton and CUDA kernels for attention and decode. Each round drafts 4
tokens with the model's own built-in draft layer and verifies them with rejection sampling on the
GPU, so sampled requests draft too. Its own tensor-core NVFP4 GEMV reads the 4-bit weights at
1.2–1.4 TB/s, against 0.9–1.0 TB/s for cuBLASLt at the same shapes.
| Candidate | What changed | Sampled decode | Prefill | TTFT | Dev time, cumulative | Agent tokens, cumulative | API equivalent, cumulative |
|---|---|---|---|---|---|---|---|
| 1 | Working engine: reads the checkpoint as stored, 8-bit KV cache, one CUDA graph per token, no drafting | 43 tok/s | 6,027 tok/s | 290 ms | 0h 55m | 39.2M | $13.96 |
| 2 | Drafts 3 tokens ahead with the model's draft layer, 8-bit decode GEMVs, fused prompt kernels (missed a gate: short prompts reached their first token 12% later at 65,536 tokens of context than at 262,144) | 134 tok/s | 7,771 tok/s | 219 ms | 1h 49m | 69.6M | $23.26 |
| 3 | Drafts 4 tokens ahead; short prompts run as captured CUDA graphs | 155 tok/s | 7,660 tok/s | 169 ms | 2h 44m | 97.7M | $31.51 |
| 4 | Its own tensor-core NVFP4 GEMV for decode and verify, using the GPU's FP4 conversion instruction | 181 tok/s | 8,218 tok/s | 156 ms | 3h 27m | 135.3M | $47.15 |
| 5 | Drafts from NVFP4 copies of the draft layer and output head; the verify pass keeps the stored weights | 189 tok/s | 8,295 tok/s | 154 ms | 3h 55m | 149.3M | $50.75 |
The last three columns are running totals from the run's first command (06:28 UTC) to each
candidate's seal, and for candidate 5 to its selection, so its row is the whole run. The run spent
its budget of three candidates on 1 to 3, then resumed for two more; development time leaves out the
3h 26m in between. Candidates 1 to 3 were built from crit and crit-eval alone; before planning 4 and
5 the agent had read the notes of earlier runs on this model. Agent tokens and API equivalent count
the agent's messages in that span, from its Claude Code session transcript: Claude Opus 5.5 for
every message, priced at Anthropic's API list prices ($4/MTok input, $20/MTok output, $8/MTok 1-hour
cache writes, $0.20/MTok cache reads; 99% of the tokens are cache reads).
Measured with crit-eval (crit-eval-v0.2.1) at the full 262,144-token context. Decode is sampled at the
model's own settings (temperature 1.0, top-p 0.95, top-k 20), the geometric mean over six prompts,
from a chat question to a 14K-token agent session; Eval decodes each engine's own output.
- Long context: a prompt that fills all 262,144 tokens gets its first token after 144 s, then decodes at 110 tok/s. On prompts outside Eval's speed suite, in 8 languages and code, it decodes at 131 tok/s.
- Quality: the worst path uses 70% of the
speeddrift budget. Greedy decoding reaches 194 tok/s, and drafting never changes the output: it is token for token what plain decoding produces. - What is left: a verify pass takes 15.1 ms against the 12.5 ms its weight reads need, and a draft step 1.24 ms against 0.62 ms. Eval projects 251 tok/s if both reached their floors.
📋 Quality modes comparison
Goal: Show what each quality mode buys, on the same model, GPU and agent. Limit: Five candidates per run.
crit judges an engine's quality in one of two modes, chosen per run: how much output drift from the
checkpoint you select the engine may trade for speed, whatever format that checkpoint is stored in.
speed, the default: the engine picks its precision (4-bit and 8-bit
activations, a lower-precision KV cache or head) within a budget that only keeps out a broken engine
(mean KLD 0.4), and output differences that part at near-ties are allowed. preserve holds the output
close to the checkpoint's, within a 5% drift budget, so the prompt's activations stay in 16 bits unless
quality shows they can go lower. (The two engines below were built when the speed budget was 10% and
that mode judged against the checkpoint run with its declared activations; today both modes judge
against the same exact reference.) We built Qwen3.8-27B on one RTX
5090 once in each mode: mtp4-nvfp4-mma
from the case study above, and
mtp-adaptive-width,
which moves between 2 and 8 drafts per round: wider after a round that kept every draft, narrower
after one that kept fewer.
speed | preserve | |
|---|---|---|
| Sampled decode | 189.0 tok/s | 185.3 tok/s |
| Greedy decode | 194.3 tok/s | 195.5 tok/s |
| Prefill | 8,295 tok/s | 3,580 tok/s |
| TTFT | 154 ms | 337 ms |
| Decode at 262,144 tokens of context | 110 tok/s | 79 tok/s |
| Perplexity vs its mode's reference | +3.6% | +0.4% |
| Mean KL divergence, worst path | 0.134 | 0.022 |
| Share of its mode's drift budget | 70% | 57% |
| Development time, five candidates | 3h 55m | 3h 20m |
| API equivalent | $50.75 | $36.81 |
- Decode is a tie. A decode pass reads every weight once, and both engines read the same stored 4-bit and 8-bit weights, so the quality mode barely moves it.
- Prefill is where the modes split. A long prompt is compute-bound.
speedmay feed 4-bit activations to the GPU's 4-bit tensor cores;preservekeeps the 4-bit layers' activations in 16 bits and widens their weights to match. Eval prices each mode's ceiling accordingly:preservereaches 77% of its 4,676 tok/s,speed46% of its 17,841 tok/s. - Quality follows the reference.
preservestays within 0.4% of the checkpoint's exact perplexity.speedwas measured against the checkpoint run with its declared 4-bit activations, whose own run-to-run spread on long prompts is wide, so Eval judged each of its texts against twice that spread. - Both runs were honest about failures. Two of
preserve's five candidates failed before Eval: one read log-probabilities at the wrong stride, and one kept its first engine in memory when Eval reopened it at the full context. Each fix became the next candidate.
Pick preserve when answers must stay close to the checkpoint you selected; keep speed, the
default, when speed matters more, as with agents that send long prompts.
preserve candidate | What changed | Sampled decode | Prefill | TTFT | Dev time, cumulative | Agent tokens, cumulative | API equivalent, cumulative |
|---|---|---|---|---|---|---|---|
| 1 | Working engine: its own GEMVs on the stored 8-bit and 4-bit weights in a CUDA-graphed step, 16-bit prompt matrix multiplies, 8-bit KV cache | 52 tok/s | 2,763 tok/s | 488 ms | 0h 45m | 31.1M | $11.41 |
| 2 | Drafts with the model's draft layer, each round in one CUDA graph; the GPU's FP4 conversion instruction in the NVFP4 GEMV (failed the serving check: log-probabilities read at the wrong stride) | – | – | – | 1h 33m | 61.4M | $19.78 |
| 3 | Candidate 2 with the stride fixed (failed Eval: the first engine stayed in memory when Eval reopened it at 262,144 tokens) | – | – | – | 1h 54m | 79.9M | $24.40 |
| 4 | Memory released on close, an 8-bit draft layer, 8-bit prompt activations where quality allows | 160 tok/s | 3,269 tok/s | 361 ms | 2h 26m | 90.4M | $27.26 |
| 5 | Draft width moves between 2 and 8 with the previous round's acceptance, drafting up to the context's end, a faster prompt convolution | 185 tok/s | 3,580 tok/s | 337 ms | 3h 20m | 127.2M | $36.81 |
Measured like the case study above. The preserve run was built from crit and crit-eval alone, from
its first candidate to its last.
How crit works
An agent, or you, builds the engine as a series of candidates. Each candidate is a complete, servable engine, measured the same way, and each next one is chosen from the last one's numbers. A candidate is assembled from plugins, one per slot (attention, KV cache, drafter, ...). Before Eval measures it, crit smoke checks its quality paths in minutes, and an ablation serves it with each plugin switched off and alone to show what each one adds, costs or breaks.
flowchart LR
recon["<b>Reconnaissance</b><br/><small>read model spec, hardware config, workload, crit guides and docs</small>"] --> limits["<b>Evaluation</b><br/><small>measure GPU<br/>limits per KPI</small>"]
limits --> build
subgraph loop ["Candidate loop"]
direction LR
build["<b>Build candidate</b><br/><small>Core foundation<br/>plus Inference plugins</small>"] --> check["<b>Checks</b><br/><small>crit smoke,<br/>ablation per plugin</small>"]
check --> measure["<b>Evaluation</b><br/><small>scores vs limits,<br/>quality gates</small>"]
measure --> decide{"<b>Decision<br/>algorithm</b><br/><small>prioritize biggest<br/>gap first</small>"}
decide -- "decision:<br/>new candidate needed" --> build
end
decide -- "decision:<br/>exit condition met" --> final["<b>Final candidate</b><br/><small>sealed and served<br/>by crit serve</small>"]
[!NOTE] Every step is recorded through the CLI: each candidate freezes its source by hash, each measurement points at that exact package, and the run's cost is counted from the agent's own transcripts (run records).
📗 Core → Full guide
Core is the plumbing every engine needs, distilled by studying multiple inference engines.
We took apart how each engine handles requests, memory, text and model files, kept the base features they all rebuild, and turned them into one native SDK. Your engine keeps what makes it fast: the model, its kernels, its layouts and its execution strategy.
| Part | What it does |
|---|---|
| Runtime | Admits, schedules, cancels and retires requests, and publishes their output |
| Engine ABI | Core's engine C ABI, which crit_core_steps exports for the engines Core's loader assembles |
| Memory | Resource budgets, reservations and host-memory admission |
| Text | Tokenizer and chat template loading, stop strings and streamed reply text |
| Sampling | Sampling rules, penalties, log-probabilities and greedy draft acceptance |
| State cache | KV pages, recurrent state slots and prefix-cache snapshots |
| Model files | Safetensors and GGUF checkpoints, config.json, generation defaults and stop tokens |
| Files | Strict JSON, read-only, chunked and mapped file reads, SHA-256 |
| Structured output | Token masks for choices, JSON schemas and grammars |
| Device graphs | CUDA graph capture and replay to cut launch overhead |
| CUDA backend | Allocations, streams, shared device memory and device time per category |
| Tracing | A bounded lifecycle trace with operator ranges and NVTX |
| Contracts | Who owns what between Core and your engine |
| Plugins | Slots and their interfaces, plugin manifests and the checked assembly an engine built from plugins loads with |
Try the Core + Inference walkthrough.
📕 Inference → Full guide
Inference is a curated collection of high-performance strategies.
We studied bespoke inference engines and what makes them fast and turned every strategy that holds beyond one model or GPU into an entry. Your agent picks the entries that match its engine's bottleneck and builds them into the engine on Core, each in its slot as a plugin.
Examples:
| Bottleneck | Example entries |
|---|---|
| One token per pass | Speculative decoding, draft trees, device-side rounds, EAGLE-3 and DFlash drafters |
| Weight bytes per pass | Quantized projections, with the GEMM and GEMV kernels from Kernels' providers |
| Long context | Attention state, prefix checkpoints, session scheduling |
| Model larger than VRAM | Recon names the placement scenario. Sparse models: a device expert cache with expert residency and the checkpoint's drafter from the first candidate, misses computed on the CPU or copied by measured dispatch; dense models: dense layer offload |
| Embedding and n-gram tables | Lookup table rows: rows read on demand from disk, no read-ahead |
| Hybrid recurrent layers | Recurrent state recovery |
All entries are in the full guide, or run crit inference search <query>.
Every entry has the same layout:
| Part | What it holds |
|---|---|
README.md | When to use it, how it works, a worked example, how to adapt it, its costs and how to validate it |
code/ | Real implementation code, C++, CUDA or Python, with runnable examples |
entry.json | When to use and avoid it, what the code provides, what the engine must supply and what it must preserve |
plugin.json, plugin.py, tests/ | For an entry that fills an engine slot: the entry as a plugin, a host reference with tests against the slot's base plugin, which crit plugins add copies into an engine |
What gets in: contribution criteria
| Criterion | Means |
|---|---|
| Shown value | A measured gain, a reproduced and fixed bug, or a capability engines had to rebuild themselves |
| No overfitting | Nothing in it is true only for the model or GPU where it was found |
| Broad use | Dense, MoE, hybrid and multimodal models and other CUDA GPUs use it unchanged or are unaffected |
📘 Kernels → Full guide
Kernels is a register of kernel collections.
crit hosts no compute kernels. Your agent takes each operation's kernel from an established
provider, or writes its own when none fits, then measures it against its floor in Eval's kernel
profile and against the best other provider for the same operation.
| Provider | Covers |
|---|---|
| Hugging Face Kernel Hub | Community kernels for attention, GEMMs, activations, normalization, MoE and more, built per framework version and backend (CUDA, ROCm, Metal, XPU, CPU) |
| cuBLASLt | GEMMs in BF16/FP16, FP8 (per-tensor and per-row scales), block-scaled NVFP4 and MXFP8 on the GPUs that have them; epilogues |
| CUTLASS | GEMM and convolution templates for layouts and fusions cuBLASLt does not take, grouped GEMMs, mixed-input GEMMs (4- or 8-bit weights widened to 16 bits inside the main loop) |
| cuDNN | Fused attention (SDPA), normalization |
| FlashAttention | Prompt and decode attention, paged KV, sliding windows, softcap |
| FlashInfer | Paged and ragged attention, FP8 KV, MLA, cascade attention, top-k / top-p / min-p sampling, some GEMMs |
| flash-linear-attention | Chunked prefill and recurrent decode for gated delta, Mamba-style, RWKV and other linear attention |
| Triton | Writing a missing kernel in Python, compiled per device; ahead-of-time compilation for native engines |
| Marlin | Mixed-input GEMMs: 4-bit weights (GPTQ-style groups) against 16-bit activations |
| ggml | GGUF block formats (Q4_0 .. Q8_0, K-quants, IQ) on CUDA, Metal, Vulkan, ROCm and CPU |
| OpenBLAS, oneDNN | CPU GEMMs and fused CPU operators |
| Composable Kernel, hipBLASLt | GEMMs and attention on AMD GPUs |
| MLX, Metal Performance Shaders | GEMMs, attention and quantized matmuls on Apple GPUs |
Kernel workbench
When the agent writes a kernel, or adapts a provider's, crit kernels new gives it a workbench folder with
the checkpoint's own shapes, an FP64 reference and a yardstick such as cuBLAS. crit eval workbench check then
compares the kernel with the reference on every shape and requires the same bits on a repeat, and bench times
it against the yardstick and the GPU's floor. See the workbench.
📚 Eval → Full guide
crit-eval is a speed and quality measurement tool to compare inference engines honestly.
There is no shared way to measure a bespoke inference engine.
Different bespoke inference engines report vastly different types of numbers. An honest comparison requires downloading and running the engines yourself.
crit-evalfinally makes apples-to-apples comparison possible.
Physics-based evaluation lets you see what performance is left on the table.
crit-evalmeasures your GPU's memory bandwidth and matrix-multiply rates and turns them into a limit for each score. Its evaluation report can tell your coding agent how much performance can still be squeezed out of your inference engine.
Speed that can't hide a quality loss.
Many inference engines push decode and prefill speed with no regard for quality.
crit-evaltreats quality as a first-class KPI. It measures not only inference speed but also quality regressions.
A hardware report rounds out the evaluation.
The same model on the same inference engine but on two different machines can also lead to different inference speeds, even if the GPU is the same. We make this transparent by also reporting the runner's hardware configuration.
🔌 API → Overview · Full guide
crit serve loads a sealed candidate, keeps it resident and answers OpenAI-compatible
chat requests, including streaming, tool calls, stop strings and structured output. The same engine
also speaks the Anthropic Messages, OpenAI Responses and /api/chat APIs,
so Claude Code, Codex and local chat UIs need only a base URL, and GET /metrics serves
Prometheus metrics. Eval measures every candidate through this same
server, so the numbers you read are the numbers you get. Engines implement the
Core's resident adapter.
📓 CLI → Overview
One crit command drives everything: crit run keeps the run's records (plans, candidates,
measurements, ablations, selection, cost), crit eval runs the common suite, crit smoke checks a served
engine in minutes, crit plugins checks and tests an engine's plugins, crit serve serves an engine,
crit pull installs a crit-hub engine, and crit kernels, crit models and crit inference reach the other
parts. Records are written only through it, so each plan, candidate, Eval and seal is checked as it is recorded.
Miscellaneous
🔭 crit-hub → Repository
crit-hub collects bespoke inference engines in one repository
to make discovery super easy. Each engine pins its runnable source, the exact checkpoint it was built on, what
was measured, its known limits and a link to the repository of whoever contributed it.
Use them as jump-off point for further optimization.
crit pull OWNER/NAME installs one: it checks the card's requirements against your GPU and CUDA,
fetches the source at its pinned commit, builds it, fetches and checks the checkpoint, and then
crit serve --engine OWNER/NAME serves it (Installed engines).
crit bench measures it on your machine beside its card's numbers, and crit hub report adds your result to the
card for a pull request (Benchmark your machine).
🚫 Limitations
| Area | Current status |
|---|---|
| Multi-GPU setups | Core's device sets (peer access, copies, ordering, barrier), crit eval probe --devices and Eval's floors for weights split across GPUs are in; partitioning and collectives are the engine's. Tested on one GPU only, not end to end on two. |
| macOS | Not yet tested. Current CI covers Windows and Linux. |
| AMD and Apple GPUs | No bundled ROCm or Metal backend. The current GPU backend uses CUDA. |
| Model formats | Hugging Face safetensors, PyTorch state dicts (.bin, .pt) and Optimum ONNX exports, unquantized or quantized as FP8, NVFP4, compressed-tensors int, AWQ, GPTQ, MLX, bitsandbytes or MatMulNBits, and GGUF, a lone .gguf included: its config, tokenizer and chat template are derived from its metadata for the llama, qwen2, qwen3, qwen3moe, qwen35, qwen35moe, deepseek4 and qwen4exp architectures (the config by the rules crit Eval gives Core) and byte-level BPE tokenizers (others need the base model's files beside it), and Eval's reference reads those architectures. A new format is one ModelFormat in crit_core.model. Core's C++ model library reads safetensors only; engines read a GGUF's tensors through crit_core.model's offsets. |
Documentation
| To… | Read |
|---|---|
| Build and compare engines | Building engines · Agent instructions · Run records |
| Integrate the native runtime | Core · Contracts · Core + Inference walkthrough |
| Choose or adapt a technique | Inference |
| Build an engine from plugins and measure each one | Plugins · Smoke tests |
| Find and adapt kernels | Kernels |
| Quantize weights and record the transform | Models |
| Compare speed, quality and speculative output | Eval |
Serve an engine through the OpenAI, Anthropic or /api/chat API | API · Serving · Core's resident adapter |
Find a crit command | CLI |
Change crit or run its tests | What belongs in crit · Contributing · Licensing |
| See release history | Changelog |
critpip install crit