Repository avatar
AI Tools
v0.17.2
active

crit

io.github.crit-labs/crit

Build, evaluate and serve a bespoke LLM inference engine for your own GPU with crit's commands.

Documentation

crit

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?

Version 0.17.2 License Apache-2.0 crit-hub engines CI

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. crit supplies 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. crit measures 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-hub as they are.

We gave Claude Opus 5.5 a target LLM along with the crit repository and let it cook autonomously.

What it showsEvidence
Fast enginesQwen3.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 callThe 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 weeks3h 55m and five candidates from the run's first command to the selected engine, $51 at API list prices
Every step is evidenceEach 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 leftDecode runs at 53% of the ceiling Eval measures for it, prefill at 46%; Eval reports each gap next to the score
NextGemma 4 31B (dense), and Qwen3.8-Flash-Next and Nemotron 3.5 Super 120B, mixture-of-experts models larger than VRAM
Dev box details
PartSpec
GPUNVIDIA GeForce RTX 5090, 32 GB, 450 W power limit
PCIeGen 4 x16 (the card supports Gen 5; the board's slot is Gen 4)
CPUAMD Ryzen 5 9600, 6 cores, 12 threads
RAM96 GB DDR5 (2x 48 GB) at 4,800 MT/s
OSWindows 11 Home (build 26200), engines run in WSL 2 on Ubuntu 24.04
Driver and CUDANVIDIA 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.

Sampled decode and prefill of each of the five candidates over the run's 3h 55m: decode jumps from 43 to 134 tok/s when drafting arrives with candidate 2 and ends at 189 tok/s; prefill climbs from 6,027 to 8,295 tok/s

CandidateWhat changedSampled decodePrefillTTFTDev time, cumulativeAgent tokens, cumulativeAPI equivalent, cumulative
1Working engine: reads the checkpoint as stored, 8-bit KV cache, one CUDA graph per token, no drafting43 tok/s6,027 tok/s290 ms0h 55m39.2M$13.96
2Drafts 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/s7,771 tok/s219 ms1h 49m69.6M$23.26
3Drafts 4 tokens ahead; short prompts run as captured CUDA graphs155 tok/s7,660 tok/s169 ms2h 44m97.7M$31.51
4Its own tensor-core NVFP4 GEMV for decode and verify, using the GPU's FP4 conversion instruction181 tok/s8,218 tok/s156 ms3h 27m135.3M$47.15
5Drafts from NVFP4 copies of the draft layer and output head; the verify pass keeps the stored weights189 tok/s8,295 tok/s154 ms3h 55m149.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 speed drift 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.

Sampled decode and prefill of both runs' candidates over development time: decode rises to about 185-189 tok/s in both modes; prefill ends at 8,295 tok/s in speed and 3,580 tok/s in preserve

speedpreserve
Sampled decode189.0 tok/s185.3 tok/s
Greedy decode194.3 tok/s195.5 tok/s
Prefill8,295 tok/s3,580 tok/s
TTFT154 ms337 ms
Decode at 262,144 tokens of context110 tok/s79 tok/s
Perplexity vs its mode's reference+3.6%+0.4%
Mean KL divergence, worst path0.1340.022
Share of its mode's drift budget70%57%
Development time, five candidates3h 55m3h 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. speed may feed 4-bit activations to the GPU's 4-bit tensor cores; preserve keeps the 4-bit layers' activations in 16 bits and widens their weights to match. Eval prices each mode's ceiling accordingly: preserve reaches 77% of its 4,676 tok/s, speed 46% of its 17,841 tok/s.
  • Quality follows the reference. preserve stays within 0.4% of the checkpoint's exact perplexity. speed was 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 candidateWhat changedSampled decodePrefillTTFTDev time, cumulativeAgent tokens, cumulativeAPI equivalent, cumulative
1Working 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 cache52 tok/s2,763 tok/s488 ms0h 45m31.1M$11.41
2Drafts 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 33m61.4M$19.78
3Candidate 2 with the stride fixed (failed Eval: the first engine stayed in memory when Eval reopened it at 262,144 tokens)–––1h 54m79.9M$24.40
4Memory released on close, an 8-bit draft layer, 8-bit prompt activations where quality allows160 tok/s3,269 tok/s361 ms2h 26m90.4M$27.26
5Draft width moves between 2 and 8 with the previous round's acceptance, drafting up to the context's end, a faster prompt convolution185 tok/s3,580 tok/s337 ms3h 20m127.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.

PartWhat it does
RuntimeAdmits, schedules, cancels and retires requests, and publishes their output
Engine ABICore's engine C ABI, which crit_core_steps exports for the engines Core's loader assembles
MemoryResource budgets, reservations and host-memory admission
TextTokenizer and chat template loading, stop strings and streamed reply text
SamplingSampling rules, penalties, log-probabilities and greedy draft acceptance
State cacheKV pages, recurrent state slots and prefix-cache snapshots
Model filesSafetensors and GGUF checkpoints, config.json, generation defaults and stop tokens
FilesStrict JSON, read-only, chunked and mapped file reads, SHA-256
Structured outputToken masks for choices, JSON schemas and grammars
Device graphsCUDA graph capture and replay to cut launch overhead
CUDA backendAllocations, streams, shared device memory and device time per category
TracingA bounded lifecycle trace with operator ranges and NVTX
ContractsWho owns what between Core and your engine
PluginsSlots 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:

BottleneckExample entries
One token per passSpeculative decoding, draft trees, device-side rounds, EAGLE-3 and DFlash drafters
Weight bytes per passQuantized projections, with the GEMM and GEMV kernels from Kernels' providers
Long contextAttention state, prefix checkpoints, session scheduling
Model larger than VRAMRecon 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 tablesLookup table rows: rows read on demand from disk, no read-ahead
Hybrid recurrent layersRecurrent state recovery

All entries are in the full guide, or run crit inference search <query>.

Every entry has the same layout:

PartWhat it holds
README.mdWhen 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.jsonWhen 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

CriterionMeans
Shown valueA measured gain, a reproduced and fixed bug, or a capability engines had to rebuild themselves
No overfittingNothing in it is true only for the model or GPU where it was found
Broad useDense, 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.

ProviderCovers
Hugging Face Kernel HubCommunity kernels for attention, GEMMs, activations, normalization, MoE and more, built per framework version and backend (CUDA, ROCm, Metal, XPU, CPU)
cuBLASLtGEMMs in BF16/FP16, FP8 (per-tensor and per-row scales), block-scaled NVFP4 and MXFP8 on the GPUs that have them; epilogues
CUTLASSGEMM 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)
cuDNNFused attention (SDPA), normalization
FlashAttentionPrompt and decode attention, paged KV, sliding windows, softcap
FlashInferPaged and ragged attention, FP8 KV, MLA, cascade attention, top-k / top-p / min-p sampling, some GEMMs
flash-linear-attentionChunked prefill and recurrent decode for gated delta, Mamba-style, RWKV and other linear attention
TritonWriting a missing kernel in Python, compiled per device; ahead-of-time compilation for native engines
MarlinMixed-input GEMMs: 4-bit weights (GPTQ-style groups) against 16-bit activations
ggmlGGUF block formats (Q4_0 .. Q8_0, K-quants, IQ) on CUDA, Metal, Vulkan, ROCm and CPU
OpenBLAS, oneDNNCPU GEMMs and fused CPU operators
Composable Kernel, hipBLASLtGEMMs and attention on AMD GPUs
MLX, Metal Performance ShadersGEMMs, 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-eval finally makes apples-to-apples comparison possible.

Physics-based evaluation lets you see what performance is left on the table.

crit-eval measures 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-eval treats 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
AreaCurrent status
Multi-GPU setupsCore'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.
macOSNot yet tested. Current CI covers Windows and Linux.
AMD and Apple GPUsNo bundled ROCm or Metal backend. The current GPU backend uses CUDA.
Model formatsHugging 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 enginesBuilding engines · Agent instructions · Run records
Integrate the native runtimeCore · Contracts · Core + Inference walkthrough
Choose or adapt a techniqueInference
Build an engine from plugins and measure each onePlugins · Smoke tests
Find and adapt kernelsKernels
Quantize weights and record the transformModels
Compare speed, quality and speculative outputEval
Serve an engine through the OpenAI, Anthropic or /api/chat APIAPI · Serving · Core's resident adapter
Find a crit commandCLI
Change crit or run its testsWhat belongs in crit · Contributing · Licensing
See release historyChangelog
PYPI
crit
Install Command
pip install crit
Runtime: uvx