Skip to content

Qwen3-4B / 8B / 32B

Qwen3-4B is the default openinfer model line: pure Rust + CUDA, no Python at build time or runtime, full-attention GQA, paged KV cache, prefix caching, CUDA Graph decode, optional pegaflow KV offload, and DSpark speculative decoding.

From the openinfer workspace root:

huggingface-cli download Qwen/Qwen3-4B --local-dir models/Qwen3-4B
export CUDA_HOME=/usr/local/cuda
cargo run --release

The default model path is models/Qwen3-4B, and openinfer-server is the workspace default member. To pass an explicit model path or port:

cargo run --release -p openinfer-server -- \
--model-path models/Qwen3-4B \
--port 8000

The server exposes an OpenAI-compatible /v1/completions endpoint:

curl -s http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"prompt": "The capital of France is", "max_tokens": 32}'

Streaming:

curl -N http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"prompt": "Write a haiku about Rust:", "max_tokens": 64, "stream": true}'

Useful Qwen3 flags:

# Disable CUDA Graph for debugging
cargo run --release -- --cuda-graph=false
# Pure host-tier KV offload benchmark mode
cargo run --release -- \
--kv-offload \
--kv-offload-host-gib 16 \
--no-prefix-cache
# DSpark speculative decoding (greedy, single-GPU)
cargo run --release -- \
--model-path models/Qwen3-4B \
--dflash-draft-model-path models/dspark_qwen3_4b_block7

Qwen3-8B uses the same architecture (4096 hidden, 12288 intermediate, 36 layers) and runs on the same single GPU — just point --model-path at the 8B weights. No feature flags or build changes needed.

cargo run --release -- --model-path models/Qwen3-8B

Qwen3-32B’s BF16 weights (~63 GB) need a single large-VRAM GPU (GH200/H200 class).

huggingface-cli download Qwen/Qwen3-32B --local-dir models/Qwen3-32B
cargo run --release -- --model-path models/Qwen3-32B

Tool calling goes through /v1/chat/completions with a tools array; a get_weather round-trip returns:

{"choices":[{"message":{"role":"assistant","tool_calls":[{"function":{"name":"get_weather",
"arguments":"{\"city\": \"Paris\"}"}}]},"finish_reason":"tool_calls"}]}

Measured on 1x RTX 5090 32GB, driver 590.48.01, CUDA 13.1 build, Qwen3-4B BF16 weights, TP1. openinfer main 70888b2, vLLM 0.24.0, same vllm bench serve client, same host, same GPU, prefix cache on, seed 42, input 1024 / output 128 for the QPS sweep. Reproducible via tools/bench/run_serving_bench.sh in the repo.

MetricopeninfervLLM 0.24.0
RSS before stress, loaded and idle771 MB3814 MB
RSS after stress1064 MB3863 MB
Startup to HTTP ready, cold2.99 s70.0 s
Startup, warm compile cache~3.0 s32.7 s
GPU memory, default utilization28832 MiB30290 MiB

openinfer is a single process; vLLM RSS is summed over its process tree. The openinfer RSS peak during load is transient while reading safetensors through mmap; steady-state settles at 771 MB after load.

Poisson arrivals, 1024-token prompts, 128-token outputs, greedy (--temperature 0):

QPSopeninfer out tok/svLLM out tok/sopeninfer TTFT p50vLLM TTFT p50openinfer TPOT p50vLLM TPOT p50
1126.3126.245.2 ms54.9 ms6.53 ms6.71 ms
2252.3252.230.3 ms38.4 ms6.93 ms7.08 ms
4504.1503.348.8 ms38.7 ms8.30 ms7.95 ms
81007.81006.951.1 ms66.9 ms11.39 ms11.97 ms
101258.31256.353.4 ms76.3 ms13.55 ms14.11 ms
121507.71506.260.0 ms106.0 ms16.75 ms18.36 ms
161979.91687.9203.8 ms3832.3 ms46.92 ms79.42 ms

Low load (QPS 1–4) is comparable. At QPS 8–12 openinfer leads on both TTFT and TPOT. At QPS 16 both systems are overloaded, but openinfer edges ahead on throughput (1980 vs 1688 output tok/s) and stays 19× lower on TTFT.

Same harness, Qwen3-8B BF16, single RTX 5090 (32 GB). The 8B model is 2× the weights of 4B; throughput scales accordingly until the GPU saturates around QPS 8:

QPSopeninfer out tok/svLLM out tok/sopeninfer TTFT p50vLLM TTFT p50openinfer TPOT p50vLLM TPOT p50
1125.1125.082.2 ms97.4 ms11.55 ms11.63 ms
2249.9250.054.1 ms61.5 ms11.46 ms11.57 ms
4498.6498.588.1 ms103.6 ms16.08 ms16.24 ms
8991.9990.4148.0 ms235.1 ms30.97 ms35.56 ms

Measured on 1x GH200 120GB (aarch64, sm_90), openinfer main 5959f05, Qwen3-32B BF16, TP1, CUDA Graph on. Load to HTTP-ready is 46 s; the profiled KV budget is 21.4 GB (5360 blocks) next to the 63 GB of weights. QPS sweep with vllm-bench, Poisson arrivals, 1024-token prompts, 128-token outputs, greedy, seed 42 — reproducible via tools/bench/run_serving_bench.sh in the repo.

loadreq/sout tok/sTTFT p50 / p99TPOT p50 / p99
c=10.3545134 / 137 ms21.1 / 21.1 ms
QPS 10.95122154 / 316 ms25.3 / 29.3 ms
QPS 21.91244103 / 289 ms24.9 / 34.3 ms
c=41.24159286 / 296 ms24.0 / 25.8 ms
QPS 43.77482202 / 593 ms59.4 / 74.8 ms
c=82.02258294 / 462 ms28.9 / 30.0 ms
QPS 85.2266816.2 / 28.0 s107.2 / 107.5 ms

c=N rows hold N requests in flight; QPS n rows are Poisson arrivals. The single GPU saturates around 5.3 req/s and 680 output tok/s at this shape; past that (QPS 10–16) throughput stays flat and TTFT grows with queueing.

Greedy output matches HF transformers (bf16, same GPU) token-for-token on 4 of 5 test prompts over the first 20 tokens. The fifth diverges at the second generated token, where HF’s own top-4 logits sit within a 0.375 spread and openinfer emits HF’s second-ranked token, 0.25 below the top.

For multi-turn chat and agent workloads, most of the prompt often lands as a warm prefix-cache hit. In this sweep, the same prompt group is sent cold once to populate GPU KV cache, then sent warm:

Input lengthopeninfer coldopeninfer warm p50openinfer warm p99vLLM warm p50vLLM warm p99
25616.2 ms8.5 ms8.8 ms14.5 ms19.1 ms
51224.6 ms8.6 ms8.8 ms16.0 ms16.4 ms
102444.0 ms9.2 ms9.5 ms18.4 ms19.0 ms
204892.0 ms10.4 ms10.8 ms23.7 ms24.4 ms
4096211.5 ms12.7 ms13.4 ms34.1 ms36.2 ms
8192460.0 ms21.6 ms22.8 ms58.6 ms59.9 ms
163841143.9 ms26.3 ms27.9 ms95.6 ms98.2 ms

openinfer wins warm TTFT at every measured length; the 16k warm-cache path is 3.6× faster than vLLM p50.

With --kv-offload, sealed Qwen3 KV blocks can be restored from the pegaflow host tier instead of recomputing full prefill. The pure-L2 mode below disables cross-request HBM prefix reuse, so every prefix hit is restored from host DRAM:

cargo run --release -- \
--kv-offload \
--kv-offload-host-gib 16 \
--no-prefix-cache
Input lengthCold full prefillL2 warm p50, host restoreSpeedup
25625.4 ms9.8 ms2.6x
51225.6 ms11.6 ms2.2x
102445.3 ms15.4 ms2.9x
204892.5 ms22.9 ms4.0x
4096211.1 ms37.5 ms5.6x
8192461.3 ms71.4 ms6.5x
163841140.5 ms125.5 ms9.1x

At 16k, the tiering picture is: HBM hit about 26 ms, host-tier restore about 126 ms, cold prefill about 1.14 s.

DSpark (DeepSeek-AI, Jun 2026) adds a semi-autoregressive Markov head to a DFlash parallel drafter, raising accepted draft length by conditioning each block position on the previously sampled token. openinfer supports it behind --dflash-draft-model-path — the drafter checkpoint goes in, the target model serves as-is, and greedy verify keeps output lossless.

# Download the released DSpark block7 drafter
huggingface-cli download deepseek-ai/dspark_qwen3_4b_block7 \
--local-dir models/dspark_qwen3_4b_block7
# https://huggingface.co/deepseek-ai/dspark_qwen3_4b_block7
# Launch with speculative decoding (greedy, single-GPU)
cargo run --release -- \
--model-path models/Qwen3-4B \
--dflash-draft-model-path models/dspark_qwen3_4b_block7

Single-stream TPOT drops from 5.8 ms to 3.0 ms — roughly 2× decode speedup from amortizing target forwards over accepted drafts. Concurrency sweep, greedy, sharegpt + SPEED-Bench (coding) datasets:

ShareGPT:

Concurrencybaseline tok/sDSpark tok/sbaseline TPOT p50DSpark TPOT p50
11703815.83 ms2.96 ms
457612886.72 ms3.59 ms

SPEED-Bench (coding):

Concurrencybaseline tok/sDSpark tok/sbaseline TPOT p50DSpark TPOT p50
11643145.87 ms3.07 ms
45749886.73 ms3.77 ms

DSpark roughly doubles throughput and halves TPOT across both datasets.

DFlash (the non-Markov predecessor, dflash_qwen3_4b_block7) is also supported via the same flag with a DFlash-format drafter checkpoint. DSpark is the recommended drafter for Qwen3-4B.

  • Full attention with grouped-query attention: 32 query heads, 8 KV heads, head dim 128, 36 layers. Qwen3-32B scales this to 64 query heads and 64 layers (GQA group 8).
  • Qwen3-4B, Qwen3-8B, and Qwen3-32B are the default pure Rust + CUDA build, with no Python build dependency.
  • Paged KV cache uses full-lifetime admission, so requests that cannot fit are rejected instead of hanging under memory pressure.
  • Prefix cache is on by default; --no-prefix-cache disables GPU prefix matching, or becomes pure-L2 host restore mode when combined with --kv-offload.
  • CUDA Graph decode uses pre-allocated buffers and can be disabled with --cuda-graph=false for debugging.
  • DSpark/DFlash speculative decoding is single-GPU, greedy-only, and forces prefix caching off (the drafter needs clean target hidden states).