The landscape
Desktop runtimes optimize for "runs on any PC", and server runtimes optimize for throughput across many requests.
| Runtime | Model format | GPU backends | Optimized for | Notes |
|---|---|---|---|---|
| llama.cpp | GGUF | CUDA, Vulkan, Metal, SYCL, CPU | running anything, anywhere | llama-server speaks the OpenAI API; the reference for quantized local inference |
| Ollama | GGUF (pulls from its library or Hugging Face) | CUDA, Metal, ROCm (+ Vulkan) | developer ergonomics | ollama run, REST on :11434, OpenAI-compatible /v1, Modelfile |
| LM Studio | GGUF, MLX | llama.cpp and MLX engines | a GUI for end users and developers | lms CLI; local server with an OpenAI-compatible API |
| Windows ML / ONNX Runtime | ONNX | execution providers incl. TensorRT for RTX, DirectML | shipping AI inside Windows apps | the OS-level path to the RTX installed base |
| vLLM | safetensors (HF), AWQ/GPTQ, FP8 | CUDA (also ROCm and others) | throughput for many users | PagedAttention, continuous batching, prefix caching |
| SGLang | safetensors, FP8, AWQ | CUDA | structured, agentic workloads | RadixAttention prefix cache, fast constrained decoding |
| TensorRT-LLM | HF → optimized checkpoints/engines | CUDA (NVIDIA only) | peak NVIDIA performance | FP8 and NVFP4, in-flight batching, speculative decoding |
| NIM | packaged | CUDA | enterprise deployment | containers with optimized engines and standard APIs |
NVIDIA supports both groups. CUDA paths in llama.cpp and Ollama serve the broad installed base. TensorRT-LLM, TensorRT for RTX and NIM serve cases that need peak performance.
Lab: one client, any engine
The OpenAI-compatible API is the standard socket, so the client code does not change between engines. Point base_url at Ollama (:11434/v1), llama-server (:8080/v1), LM Studio (:1234/v1) or vLLM (:8000/v1). A gateway such as LiteLLM offers the same socket in front of several engines and the cloud (Chapter 14).
import json, time, urllib.request
def chat(base_url, model, prompt, max_tokens=64):
body = {"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens, "stream": False}
req = urllib.request.Request(base_url + "/chat/completions", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
start = time.perf_counter()
with urllib.request.urlopen(req, timeout=300) as r:
data = json.loads(r.read())
return data["choices"][0]["message"]["content"], data.get("usage", {}), time.perf_counter() - start
ENGINES = {"Ollama": "http://127.0.0.1:11434/v1", "llama-server": "http://127.0.0.1:8080/v1", "LM Studio": "http://127.0.0.1:1234/v1", "vLLM": "http://127.0.0.1:8000/v1"}
for engine, url in ENGINES.items():
try:
with urllib.request.urlopen(url + "/models", timeout=1) as r:
listed = [m["id"] for m in json.loads(r.read())["data"]]
print(f"{engine:>12}: up · models {listed[:4]}")
except OSError:
print(f"{engine:>12}: not running")
model = local.need_chat_model()
if model:
text, usage, seconds = chat(ENGINES["Ollama"], model, "In one sentence: why does VRAM bandwidth matter?")
print(f"\n{text}\n{usage} · {seconds:.2f} s")
Starting each engine (reference)
Each engine starts with one command.
# llama.cpp, all layers on the GPU, 16k context, OpenAI API on :8080
llama-server -m qwen3-8b-Q4_K_M.gguf -ngl 99 -c 16384 --port 8080
# Ollama
ollama serve # API on :11434
ollama run qwen3:8b # pulls the GGUF, then chats
# LM Studio
lms server start # API on :1234
# vLLM (Linux + NVIDIA)
vllm serve Qwen/Qwen3-8B --max-model-len 16384 --gpu-memory-utilization 0.9
# SGLang
python -m sglang.launch_server --model-path Qwen/Qwen3-8B --port 30000
# TensorRT-LLM (OpenAI-compatible server)
trtllm-serve Qwen/Qwen3-8B
Choosing, by scenario
Choose the runtime by who you serve.
| Scenario | Recommendation | Why |
|---|---|---|
| Indie dev adds local chat to a desktop app on Windows, macOS and Linux | Ollama or embedded llama.cpp | one GGUF everywhere; excellent CUDA path on RTX |
| ISV ships an AI feature to millions of Windows RTX PCs | Windows ML (ONNX) with the TensorRT for RTX execution provider | OS-managed, small footprint, vendor-optimized |
| Team serves an internal coding model to 30 engineers from one RTX PRO 6000 | vLLM or SGLang (or TensorRT-LLM for peak) | continuous batching and prefix caching |
| Agent platform with long, shared system prompts | SGLang or vLLM with prefix caching | reuses KV across steps |
| Model developer launch | publish GGUF + safetensors (+ FP8/NVFP4); day-0 Ollama and llama.cpp support | meet every runtime where it is |
Explain it
- "Ollama versus vLLM?" Ollama serves one user on broad hardware. vLLM serves many users with PagedAttention and continuous batching on Linux GPUs.
- "How does NVIDIA reach the Windows app ecosystem?" Windows ML with the TensorRT for RTX provider reaches app developers, and CUDA paths in llama.cpp and Ollama reach open-source desktops. NIM and TensorRT-LLM cover workstations and servers.
- "An app hardcodes an OpenAI endpoint. How hard is local?" Often only the
base_urland the model name change. The hard parts are the model choice, its chat template and tool-call format, and the hardware.
Exercises
- Start
llama-serverwith a GGUF, point the lab client at it, and compare tokens/s with Ollama on the same file. - Write a one-page decision tree that picks a runtime in five minutes.