A high-performance, architecture-first LLM inference engine written in Rust.
OpenAI-compatible API, continuous batching, paged KV cache, and CUDA-graph decode
— built on a hexagonal, zero-cost multi-backend core that swaps CUDA for CPU at
compile time with no runtime penalty.
Performance
RustInfer outperforms vLLM on an online QPS sweep — Qwen3-4B, NVIDIA H200,
max_tokens=512, ignore_eos, matched CUDA-graph decode capture sizes. Across the
sweep, RustInfer (red) holds lower TTFT / TPOT / ITL and lower end-to-end latency
than vLLM (blue) at equal or higher throughput:
RustInfer beats vLLM on the tail, not just the median. Tail inter-token
latency (ITL p99) stays below vLLM at every arrival rate — 6.6 → 9.0 ms
vs 7.2 → 10.9 ms (qps 1 → 32) — alongside lower ITL / TPOT median and
end-to-end latency at matched or higher throughput. Bench harness under bench/.
Design philosophy
RustInfer is organized around a few principles, applied consistently top to bottom.
Hexagonal core (ports & adapters)
infer-core owns nothing but ports — trait definitions for everything the
inference path needs from hardware:
The backends are adapters that implement those ports: infer-backend-cuda
(.cu kernels + cuBLASLt + CUTLASS) and infer-backend-cpu (a pure-Rust reference
implementation, always linked, used as baseline and for tests). The core has zero
knowledge of CUDA; the entire GPU toolchain (nvcc / bindgen / cuDNN / CUTLASS) is
confined to the single infer-backend-cuda leaf crate.
Heterogeneous backends at zero cost
The model layer is generic over an LlmBackend trait and monomorphizes at
compile time to whichever backend is selected — CUDA or CPU. There is no virtual
dispatch on the inference hot path: dispatch cost is paid by the compiler, not per
op. The same model code runs on GPU in production and on the CPU reference backend
in unit tests, byte-for-byte the same call sites.
High cohesion, low coupling
Eight crates form an acyclic dependency graph with a GPU-free bottom. Each crate
has one job; cross-crate contact happens only through infer-protocol (wire types)
and infer-core (ports). Swapping a backend, a scheduler policy, or a transport
touches exactly one crate.
DDD layering inside the worker
The worker — the most complex crate — is split into Domain / Application /
Infrastructure, so pure inference logic never mixes with I/O or orchestration:
infer-worker/src/
domain/ model.rs, plan.rs, kv, forward_scratch, global_kv_alloc
→ pure inference logic; no I/O, no transport
application/ runtime, decode_engine, serve_loop, worker_scheduler,
sampler_stack, hosting → orchestration & lifecycle
infrastructure/ io, transport → ZMQ / MsgPack adapters
components/ attention, ffn, norm, embed, lm_head → reusable NN blocks
models/ llama3, qwen3, decoder, loader → composition
Model variation lives in the data, not in branches
A model’s specialness (quantization, hybrid attention, tied embeddings) is an
attribute of the operator/weight it lives on, not a conditional threaded through
higher layers. The weight loader is generic and name-driven — it reads exactly the
tensor names and shapes the checkpoint declares; only the model module knows how to
assemble them.
Architecture
Three cooperating processes share a single TOML config and communicate over
ZMQ (IPC) with MessagePack framing:
a local Hugging Face model directory containing config.json,
tokenizer.json, and the model weights.
Start the full scheduler + worker + OpenAI-compatible server stack with one
command. Replace the host model path and exposed model name as needed:
docker run --rm --gpus all \
-p 8000:8000 \
-v /absolute/path/to/Qwen3:/models/model:ro \
ghcr.io/vinci-hit/rustinfer:1.0.1 \
serve --model-name Qwen3
Wait for the model to load, then check readiness:
curl --fail http://127.0.0.1:8000/ready
Send a chat completion:
curl http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen3",
"messages":[{"role":"user","content":"What is the capital of France?"}],
"max_tokens":64}'
No Rust toolchain, CUDA Toolkit, nvcc, libclang, cuDNN headers, or local
operator compilation is required. The 1.0.1 image is linux/amd64 and
compiled for CUDA architecture sm_90.
Useful container settings include RUSTINFER_MAX_BATCH_TOKENS,
RUSTINFER_MAX_BATCH_SEQS, RUSTINFER_MAX_MODEL_LEN,
RUSTINFER_CHUNKED_PREFILL_SIZE, and RUST_LOG. A complete custom config can
instead be mounted and selected with --config.
Build the Docker image locally
To build from the current source instead of using the published image:
Launches scheduler + worker + server for a config, sends one chat completion,
prints the reply, and tears everything down:
scripts/e2e_smoke.sh run_qwen3.toml 8100 "Say hello in one short sentence."
The checked-in configs are portable templates. Set their model field to the
local Hugging Face model directory before launching; they bind to
127.0.0.1 and use cuda:0 by default.
curl http://127.0.0.1:8100/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen3-4B-Instruct-2507",
"messages":[{"role":"user","content":"What is the capital of France?"}]}'
The Python project keeps benchmark dependencies optional so building RustInfer
does not install another inference engine. Install only the group a script uses:
The vLLM and SGLang groups are reference-benchmark environments; they are not
runtime dependencies of RustInfer.
Configuration
Config is a single TOML shared by all three processes. Key fields:
Field
Meaning
model
Path to the HF model directory (config + safetensors + tokenizer).
model_name
Name reported by the /v1 API.
device
Rank-0 CUDA device, e.g. cuda:0; TP ranks use consecutive devices.
tensor_parallel_size
Number of GPUs in the single-process TP group (1 = disabled).
port
HTTP port for the server.
tp_operation_timeout_secs
Fail-stop deadline for one mirrored TP inference operation.
tp_startup_timeout_secs
Longer fail-stop deadline for NCCL and follower startup.
max_batch_tokens
Token budget per forward batch.
max_batch_seqs
Max concurrent sequences in a batch.
max_model_len
Max context length.
chunked_prefill_size
Chunked-prefill chunk size (0 = disabled).
enable_prefix_caching
Toggle the RadixTree prefix cache.
mem_fraction_static
Fraction of GPU memory reserved for weights + static buffers.
num_blocks
KV-cache blocks (0 = auto-size from a memory profile).
capture_sizes
Batch sizes to capture CUDA graphs for, e.g. [1,2,4,8,16,24,32].
mtp_num_draft_tokens
Opt-in Qwen3.5 text MTP; 0 disables it. Requires greedy sampling, TP1, max_batch_seqs=1, and prefix caching disabled. Verification runs eager.
ignore_eos
Ignore EOS (useful for fixed-length benchmarking).
Tensor parallelism
Tensor parallelism is disabled by default. To shard a dense BF16 model over two
GPUs, set the shared configuration to:
device = "cuda:0"
tensor_parallel_size = 2
One worker process owns the complete TP group. Rank 0 uses device; the other
ranks use consecutive CUDA device IDs, so this example uses cuda:0 and
cuda:1. The CUDA backend requires NCCL 2.24.3 or newer; the Docker image
already includes the matching development and runtime packages.
The implementation supports single-node dense BF16 and block-FP8 Llama/Qwen
decoders. Vocabulary size, query/KV head counts, MLP intermediate size, and FP8
weight/scale block boundaries must be evenly divisible by
tensor_parallel_size.
CUDA Graph capture is enabled for TP decode and single-sequence prefill. Every
rank captures its own device graph and replays the same NCCL collective sequence
in lockstep. Mixed prefill+decode batches currently stay eager. TP with AWQ
weights, speculative decoding, pipeline parallelism, data parallelism, and
expert parallelism are not implemented yet; unsupported combinations fail
during startup instead of silently falling back to replicated execution.
Status
RustInfer serves Llama-3.2, Qwen3, and Qwen3-AWQ end to end today.
Qwen3.5-4B runs hybrid Gated DeltaNet/full attention, text and PNG/JPEG data-URL
inputs, chunked prefill, and CUDA Graph decode at TP=1. The reproducible service
regression covers mixed text/image concurrency and streaming. Hardware/model
combinations still require GPU validation; a successful CUDA build alone does
not establish inference correctness.
Workers execute a bounded prefill/decode self-check before advertising ready.
/health reports HTTP process liveness; /ready requires a loaded Worker group
and a fresh scheduler engine heartbeat, and becomes unavailable during failure
or shutdown. /metrics exports Prometheus text; /metrics/system retains the
browser console’s JSON uptime summary. See metric semantics.
Run the local GPU regression, or provision the automatic master/nightly GPU
workflow using the validation guide:
RustInfer
A high-performance, architecture-first LLM inference engine written in Rust. OpenAI-compatible API, continuous batching, paged KV cache, and CUDA-graph decode — built on a hexagonal, zero-cost multi-backend core that swaps CUDA for CPU at compile time with no runtime penalty.
Performance
RustInfer outperforms vLLM on an online QPS sweep — Qwen3-4B, NVIDIA H200,
max_tokens=512,ignore_eos, matched CUDA-graph decode capture sizes. Across the sweep, RustInfer (red) holds lower TTFT / TPOT / ITL and lower end-to-end latency than vLLM (blue) at equal or higher throughput:Design philosophy
RustInfer is organized around a few principles, applied consistently top to bottom.
Hexagonal core (ports & adapters)
infer-coreowns nothing but ports — trait definitions for everything the inference path needs from hardware:The backends are adapters that implement those ports:
infer-backend-cuda(.cukernels + cuBLASLt + CUTLASS) andinfer-backend-cpu(a pure-Rust reference implementation, always linked, used as baseline and for tests). The core has zero knowledge of CUDA; the entire GPU toolchain (nvcc / bindgen / cuDNN / CUTLASS) is confined to the singleinfer-backend-cudaleaf crate.Heterogeneous backends at zero cost
The model layer is generic over an
LlmBackendtrait and monomorphizes at compile time to whichever backend is selected — CUDA or CPU. There is no virtual dispatch on the inference hot path: dispatch cost is paid by the compiler, not per op. The same model code runs on GPU in production and on the CPU reference backend in unit tests, byte-for-byte the same call sites.High cohesion, low coupling
Eight crates form an acyclic dependency graph with a GPU-free bottom. Each crate has one job; cross-crate contact happens only through
infer-protocol(wire types) andinfer-core(ports). Swapping a backend, a scheduler policy, or a transport touches exactly one crate.DDD layering inside the worker
The worker — the most complex crate — is split into Domain / Application / Infrastructure, so pure inference logic never mixes with I/O or orchestration:
Model variation lives in the data, not in branches
A model’s specialness (quantization, hybrid attention, tied embeddings) is an attribute of the operator/weight it lives on, not a conditional threaded through higher layers. The weight loader is generic and name-driven — it reads exactly the tensor names and shapes the checkpoint declares; only the model module knows how to assemble them.
Architecture
Three cooperating processes share a single TOML config and communicate over ZMQ (IPC) with MessagePack framing:
Workspace
infer-coreLlmBackendports. GPU-free — the bottom of the DAG.infer-protocolinfer-server/v1API, chat template, SSE streaming.infer-schedulerinfer-workerinfer-backend-cuda.cukernels + cuBLASLt; statically links the kernel set + CUTLASS.infer-backend-cpuinfer-frontendFeatures
/v1/chat/completionsand/v1/completions, with SSE streaming, chat templates, and HF tokenizers.Quick start
Run the prebuilt Docker image (H100 / H200)
The public image contains the compiled CUDA kernels, CUDA 12 runtime, cuBLAS, and cuDNN. The host only needs:
config.json,tokenizer.json, and the model weights.Start the full scheduler + worker + OpenAI-compatible server stack with one command. Replace the host model path and exposed model name as needed:
Wait for the model to load, then check readiness:
Send a chat completion:
No Rust toolchain, CUDA Toolkit,
nvcc, libclang, cuDNN headers, or local operator compilation is required. The1.0.1image islinux/amd64and compiled for CUDA architecturesm_90.Useful container settings include
RUSTINFER_MAX_BATCH_TOKENS,RUSTINFER_MAX_BATCH_SEQS,RUSTINFER_MAX_MODEL_LEN,RUSTINFER_CHUNKED_PREFILL_SIZE, andRUST_LOG. A complete custom config can instead be mounted and selected with--config.Build the Docker image locally
To build from the current source instead of using the published image:
Images are architecture-specific. Use
CUDA_ARCH=sm_80,sm_86, orsm_89when building for another supported NVIDIA GPU architecture.Build from source
Prerequisites
Build
Run (one-shot e2e smoke test)
Launches scheduler + worker + server for a config, sends one chat completion, prints the reply, and tears everything down:
The checked-in configs are portable templates. Set their
modelfield to the local Hugging Face model directory before launching; they bind to127.0.0.1and usecuda:0by default.Run (manual, three processes)
Each binary takes the same
--config:Then hit the OpenAI-compatible endpoint:
Ready-to-use configs:
run_qwen3.toml,run_qwen3_awq.toml(AWQ int4),run_llama1b.toml.Python benchmark tools
The Python project keeps benchmark dependencies optional so building RustInfer does not install another inference engine. Install only the group a script uses:
The vLLM and SGLang groups are reference-benchmark environments; they are not runtime dependencies of RustInfer.
Configuration
Config is a single TOML shared by all three processes. Key fields:
modelmodel_name/v1API.devicecuda:0; TP ranks use consecutive devices.tensor_parallel_size1= disabled).porttp_operation_timeout_secstp_startup_timeout_secsmax_batch_tokensmax_batch_seqsmax_model_lenchunked_prefill_size0= disabled).enable_prefix_cachingmem_fraction_staticnum_blocks0= auto-size from a memory profile).capture_sizes[1,2,4,8,16,24,32].mtp_num_draft_tokens0disables it. Requires greedy sampling, TP1,max_batch_seqs=1, and prefix caching disabled. Verification runs eager.ignore_eosTensor parallelism
Tensor parallelism is disabled by default. To shard a dense BF16 model over two GPUs, set the shared configuration to:
One worker process owns the complete TP group. Rank 0 uses
device; the other ranks use consecutive CUDA device IDs, so this example usescuda:0andcuda:1. The CUDA backend requires NCCL 2.24.3 or newer; the Docker image already includes the matching development and runtime packages.The implementation supports single-node dense BF16 and block-FP8 Llama/Qwen decoders. Vocabulary size, query/KV head counts, MLP intermediate size, and FP8 weight/scale block boundaries must be evenly divisible by
tensor_parallel_size.CUDA Graph capture is enabled for TP decode and single-sequence prefill. Every rank captures its own device graph and replays the same NCCL collective sequence in lockstep. Mixed prefill+decode batches currently stay eager. TP with AWQ weights, speculative decoding, pipeline parallelism, data parallelism, and expert parallelism are not implemented yet; unsupported combinations fail during startup instead of silently falling back to replicated execution.
Status
RustInfer serves Llama-3.2, Qwen3, and Qwen3-AWQ end to end today.
Qwen3.5-4B runs hybrid Gated DeltaNet/full attention, text and PNG/JPEG data-URL inputs, chunked prefill, and CUDA Graph decode at TP=1. The reproducible service regression covers mixed text/image concurrency and streaming. Hardware/model combinations still require GPU validation; a successful CUDA build alone does not establish inference correctness.
Workers execute a bounded prefill/decode self-check before advertising ready.
/healthreports HTTP process liveness;/readyrequires a loaded Worker group and a fresh scheduler engine heartbeat, and becomes unavailable during failure or shutdown./metricsexports Prometheus text;/metrics/systemretains the browser console’s JSON uptime summary. See metric semantics.Run the local GPU regression, or provision the automatic master/nightly GPU workflow using the validation guide:
License
Licensed under the Apache License 2.0. Vendored components retain their own terms; see Third-party notices.