uncloseai.
How We Run Inference
How we run inference
Optional reading, for anyone reproducing our setup or contributing GPU time.
Topology
Two consumer cards, one model. Either hostname gives you turboderp/Qwen3.8-27B-exl3 (read live from /v1/models):
| Hostname | Card | Model |
|---|---|---|
hermes.ai.unturf.com | RTX 4090 (Ada) | turboderp/Qwen3.8-27B-exl3 |
qwen.ai.unturf.com | RTX 3090 (Ampere) | turboderp/Qwen3.8-27B-exl3 |
Yes, hermes.ai.unturf.com serves Qwen, deliberately. Hostnames are routing labels; always read id from /v1/models.
One port per model identity. Qwen binds 18888, Hermes 18889. A hostname whose model is down gets a 502 instead of silently answering as whatever else is up.
vLLM Setup: Qwen3.8-27B EXL3 on a 24 GiB card
TL;DR: turboderp/Qwen3.8-27B-exl3, a 3.0 bpw exllamav3 trellis checkpoint, served by stock vLLM 0.26 through our vllm-exl3 plugin (--quantization exl3): vLLM keeps the scheduler, paged fp8 KV, prefix caching and the OpenAI API; exllamav3's CUDA kernels run the linears. Same 64k window and 12 sequences as the INT4 build it replaced. Measured 2026-09-23 on the 3090 against that INT4 build with the same flags on an idle box: decode +21% to +41% per stream, prefill +26% (1,653 vs 1,317 prompt tok/s), eight concurrent 7k-prompt tasks in 30.5 s vs 46.0 s, tool-call fidelity 6/6 on both.
- Model: 3.0 bpw. The 4.0 bpw checkpoint was 15% slower single-stream and lost at eight concurrent streams, so the smaller one serves.
--quantization exl3: registered by the vllm-exl3 plugin. The exllamav3 1.5.1 wheel is built on each box (glibc differs per box) and the plugin is pinned to the commit the entry was measured with.--kv-cache-dtype fp8,--dtype float16,--max-model-len 65536,--max-num-seqs 12. The 64k window is a hard floor: agent frameworks refuse endpoints under it. Twelve sequences is measured concurrency with headroom; sixteen was neutral.--gpu-memory-utilization 0.80on both cards. exllamav3 keeps about 0.5 GiB of scratch outside vLLM's accounting, so the 0.975 the INT4 build ran at fails while profiling the KV pool. The pool is still 5.6 GiB against INT4's 2.9.VLLM_EXL3_BACKEND=exllamav3,VLLM_EXL3_MGEMM=3-32,VLLM_EXL3_CUBLAS16_ROWS=256: exllamav3's kernels; a fused sliced matmul for the fused projections at 3 to 32 rows (+7% at four to eight streams, slower below three); prefill GEMM on cuBLAS with fp16 accumulation, 1.5x on GeForce Ampere and Ada at about 0.5% numerical error, inside the int8 decode path's own 0.75%.VLLM_PREFIX_CACHE_RETENTION_INTERVAL=6400: this hybrid model checkpoints its recurrent state only at block boundaries, so a growing agent conversation kept missing the prefix cache; one snapshot per 6,400 tokens took reuse from 68% to 79% and four agents from 31 to 42 turns/min.- Off on purpose: MTP speculative decoding (k=1 won every single-stream benchmark, then cost prefix reuse and KV on the fleet's real shape: 60 to 85 turns/min at eight agents without it; greedy speculation is output-identical, so the switch changes speed, never answers), the 4 bpw checkpoint, tensor parallel (the plugin refuses it; one card, one engine), and the INT4-era
--tool-call-parser: this entry passes no extra arguments.
# the exllamav3 wheel built on this box and the vllm-exl3 plugin, into the vLLM venv
pip install --no-deps /home/fox/exl3-build/dist/exllamav3-1.5.1-cp312-cp312-linux_x86_64.whl
pip install --no-deps -e /home/fox/exl3/vllm-exl3
vllm serve turboderp/Qwen3.8-27B-exl3 \
--host 0.0.0.0 --port 18888 \
--max-model-len 65536 \
--max-num-seqs 12 \
--gpu-memory-utilization 0.80 \
--kv-cache-dtype fp8 \
--dtype float16 \
--quantization exl3
Our systemd unit adds (CUDA 13.3 on the 4090, 12.8 on the 3090):
[Service]
User=fox
Environment=CUDA_HOME=/usr/local/cuda-13.3
Environment=PATH=/usr/local/cuda-13.3/bin:/usr/local/cuda-13.3/nvvm/bin:/usr/bin:/bin
Environment=CPATH=/usr/local/cuda-13.3/include
Environment=LIBRARY_PATH=/usr/local/cuda-13.3/lib64
Environment=LD_LIBRARY_PATH=/usr/local/cuda-13.3/lib64
Environment=VLLM_EXL3_BACKEND=exllamav3
Environment=VLLM_EXL3_MGEMM=3-32
Environment=VLLM_EXL3_CUBLAS16_ROWS=256
Environment=VLLM_PREFIX_CACHE_RETENTION_INTERVAL=6400
Environment=PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
TimeoutStartSec=3600
Restart=always
RestartSec=5
MemoryMax=16G
OOMScoreAdjust=-500
Why ninja and our CUDA env: vLLM 0.26 JIT-compiles fp8 kernels through FlashInfer at warmup, which needs ninja, nvcc with its nvvm/bin siblings, headers and -lcudart. Missing any one kills our engine at warmup. Single-quote JSON args in a unit file; systemd strips bare double quotes.
vLLM Setup: Hermes-3-Llama-3.1-8B
Our previous production model, kept as a one-line swap. solidrust/Hermes-3-Llama-3.1-8B-AWQ: ~6 GiB weights, Marlin INT4 kernels native on Ampere and Ada. (FP8-Dynamic quants token-loop on a 3090; sm_86 has no fp8 tensor cores.)
--kv-cache-dtype float16must match Marlin's compute dtype;bfloat16crashes attention withquery and key must have the same dtype.--gpu-memory-utilization 0.72leaves ~4.6 GiB for an on-box F5-TTS co-tenant.--max-num-seqs 32. vLLM's default 256 OOMs during sampler warmup on 24 GiB.--tool-call-parser hermesmatches Hermes-3's<tool_call>{...}</tool_call>format.
vllm serve solidrust/Hermes-3-Llama-3.1-8B-AWQ \
--host 127.0.0.1 --port 18889 \
--max-model-len 64000 \
--max-num-seqs 32 \
--gpu-memory-utilization 0.72 \
--kv-cache-dtype float16 \
--dtype auto \
--quantization awq_marlin \
--enable-auto-tool-choice \
--tool-call-parser hermes
Proxy Setup
An edge Caddy terminates TLS and rate-limits, then forwards over TLS to a Caddy beside each GPU, which proxies to vLLM on loopback. vLLM never binds a public interface.
keepalive off on every hop that reaches vLLM. A pooled connection can be half-open; Go will not retry a POST on it, so our request vanishes and our client hangs forever. A fresh handshake is trivial next to a multi-second generation.
hermes.ai.unturf.com {
rate_limit {
zone hermes_public {
key {remote_host}
events 3
window 1s
}
}
reverse_proxy https://<gpu-host> {
header_up X-Real-IP {http.request.remote.host}
header_up X-Forwarded-For {http.request.remote.host}
transport http {
keepalive off
dial_timeout 5s
response_header_timeout 1260s
}
}
}
Access logs delete Authorization, Cookie and api-key headers via Caddy's format filter.
Model Discovery
Swagger docs at /docs: hermes.ai.unturf.com/docs, qwen.ai.unturf.com/docs.
curl https://hermes.ai.unturf.com/v1/models
curl https://hermes.ai.unturf.com/version
{
"object": "list",
"data": [
{
"id": "turboderp/Qwen3.8-27B-exl3",
"object": "model",
"owned_by": "vllm",
"max_model_len": 65536
}
]
}
Pass id as your model name. We omit --served-model-name: an alias hides which checkpoint you have.
Thinking mode: roughly 8x slower. Our clients send "chat_template_kwargs": {"enable_thinking": false} by default.
Rate Limiting & Access
hermes.ai.unturf.com: public, 3 requests per second per IP.qwen.ai.unturf.com: API key required; anonymous traffic gets 403.
Next Steps
Ready to add text-to-speech to your application?