Master the Shepherd Model Gateway
SMG is the open-source traffic controller for large-scale LLM inference — a Rust gateway that sits between your applications and your GPU fleet. This tutorial takes you from "never heard of it" to "can read a production deployment config" in about an hour. It has stories, live demos, and an exam at the end.
model_gateway/, crates/). Chapter 11 adds a real production case study you won't find anywhere else.The problem nobody warns you about
Congratulations — your team got GPUs. You fire up an open model on vLLM or SGLang, point your app at it, and everything works… on your laptop. Then reality shows up:
😰 "One GPU isn't enough"
You now run 8, 50, 300 replicas of the model. Which one should each request go to? Your app can't know. Round-robin DNS it is… and performance is mysteriously bad.
🔥 "The GPUs are busy but slow"
Every request re-processes the same 4,000-token system prompt from scratch. The engines literally remember having seen it (their KV cache) — but requests land on random machines, so the memory is wasted.
🧩 "Every model speaks a dialect"
DeepSeek "thinks" in <think> tags, Llama emits tool calls with <|python_tag|>, Qwen does XML. Your app now contains if/else spaghetti for every model family.
🚪 "We also use OpenAI for some things"
Half your traffic goes to self-hosted models, half to cloud APIs. Two SDKs, two auth schemes, two retry logics, zero shared observability.
💥 "A worker died at 3am"
Requests kept getting routed to the corpse. You invented circuit breakers at 3:15am. They were buggy.
🏢 "Legal says history stays in-house"
Chat history, rate limits per customer, audit trails — suddenly "just call the model" became an infrastructure company.
Every one of these is the same missing layer: something should sit between apps and engines, own routing + translation + reliability, and do it at line rate. That something is a model gateway. SMG is the best open-source one — written in Rust, born from the SGLang ecosystem, and battle-tested in front of fleets of hundreds of GPU replicas.
Think of it as a pizza chain
Forget computers for two minutes. SMG's whole design fits in one analogy — a pizza delivery business:
The cashier (SMG) does everything except cooking: applies the chat template, tokenizes your text, picks which oven to use, then detokenizes the output and wraps it as a clean OpenAI-style response. The oven (engine) does only the heavy math on GPUs.
And a real business has many shops. SMG knows every shop's menu, how busy each oven is, and — crucially — what dough each oven already has prepped. If your order looks like yesterday's, SMG sends you back to the same oven so it reuses the prep instead of starting from scratch. That's "cache-aware routing", and it's SMG's superpower (Chapter 6).
The 8 words you actually need
Everything in SMG's docs and configs reduces to these terms. Learn them now, skim the full glossary later.
1 · Token
The atom of LLM I/O. Models don't read text — they read integer IDs. "Hello world" → [9906, 1917]. Converting text↔tokens is tokenization/detokenization. SMG does this at the gateway, with a two-level cache, so engines don't have to.
2 · Worker
One running engine instance SMG can route to (a URL + metadata + health state). A deployment = a fleet of workers behind one gateway. Workers register in SMG's Worker Registry, get health-checked, and get picked by policies.
3 · Engine / Backend
The software doing inference on the worker: SGLang, vLLM, TensorRT-LLM, TokenSpeed, MLX, or any OpenAI-compatible server. SMG is engine-agnostic — it speaks HTTP to all of them, and high-performance gRPC to the majors.
4 · KV cache
The engine's short-term memory: processed tokens leave computed state ("KV") in GPU memory, so continuing a familiar prompt is far cheaper than starting over. Prefix reuse = massive speedup. SMG tracks which worker holds which prefixes.
5 · TTFT
Time To First Token — how long until the user sees the first word. Dominated by re-processing the prompt ("prefill"). Reusing KV cache slashes TTFT (~70% in SMG's benchmarks). The metric every inference team lives and dies by.
6 · Routing policy
The algorithm that picks a worker per request: cache_aware, round_robin, power_of_two, least_load, … 10 of them, pluggable per model (Chapter 6).
7 · Chat template
Every model family wants messages formatted differently (Llama vs Qwen vs DeepSeek). SMG applies the model's Jinja2 template at the gateway — your app just sends OpenAI-style messages.
8 · PD disaggregation
Splitting inference into Prefill (process the prompt) and Decode (generate tokens) on different workers, shipping the KV cache between them. An advanced scaling trick SMG supports natively on both HTTP and gRPC.
One request, end to end — with a live walkthrough
Here is the entire system. Every request your app ever sends walks this exact path. Press Next step and watch it happen — 8 steps, ~40 seconds.
gateway = full server
gateway = smart proxy
gateway = cloud router
Notice the shape: one shared front half (server → middleware → brain), one of three paths through the middle, one shared back half (response processing → stream). Once you see this shape, every SMG config file and every source module becomes obvious.
🔍 Deep dive — where each box lives in the source code
- HTTP server:
model_gateway/src/server.rs—startup()builds the Axum app, routes, and AppState. - Middleware:
model_gateway/src/middleware/— auth.rs, token_bucket.rs, wasm.rs, tenant_resolution.rs, concurrency.rs, metrics.rs. - RouterManager:
routers/router_manager.rs+routers/factory.rs— 7 router implementations behind oneRouterTrait. - Paths:
routers/grpc/,routers/http/,routers/openai/+anthropic/,gemini/. - Response processing:
routers/grpc/regular/processor.rs(non-stream) andstreaming.rs(SSE).
Gateway = full server, smart proxy, or cloud router
The RouterManager's choice depends on what kind of worker serves the requested model. There are exactly three possibilities:
gRPC path — "full server"
When: worker speaks SMG's gRPC protocol (SGLang, vLLM, TRT-LLM, TokenSpeed, MLX with the shim).
SMG does: chat template, tokenization (+cache), worker pick, detokenization, reasoning & tool parsing, MCP loop. The worker does raw inference only — tokens in, tokens out.
Why it's the flagship: token-level streaming, token-aware routing, and all model quirks handled in one place instead of in every app.
HTTP path — "smart proxy"
When: worker runs its own full OpenAI-compatible HTTP server.
SMG does: load balancing, retries, circuit breakers, failover — the engine handles its own templating/tokenization.
Party trick: PD mode — send prefill to one worker, decode to another, transfer the KV cache between them via routing headers.
External path — "cloud router"
When: the "worker" is OpenAI, Anthropic, Gemini, xAI, OCI, Bedrock, or any OpenAI-compatible provider.
SMG does: provider abstraction — one endpoint, one auth scheme, model discovery via /v1/models, dialect translation (e.g. Anthropic Messages vs OpenAI Chat).
Result: mix self-hosted and cloud behind a single API.
| Responsibility | gRPC mode | HTTP mode |
|---|---|---|
| Chat template | Gateway | Worker |
| Tokenization | Gateway (L0/L1 cached) | Worker |
| Load balancing | Token-aware | Request-count aware |
| Reasoning / tool parsing | Gateway | Worker |
| MCP tool execution | Gateway | n/a |
--enable-igw), SMG inspects which workers serve the requested model and picks: external provider → gRPC-PD → HTTP-PD → gRPC → HTTP.Seven stages, one conveyor belt
On the gRPC path, every request is assembled by a RequestPipeline — an ordered list of stages, each with one job. This is the heart of SMG; if you understand these 7 stages, you understand the codebase.
| # | Stage | What happens | Source |
|---|---|---|---|
| 1 | Preparation | Validate tools, apply the Jinja2 chat template, tokenize (with L0/L1 cache), expand multimodal inputs (images/video) | regular/stages/preparation.rs |
| 2 | Worker selection | Routing policy picks one worker (or a prefill+decode pair) from the Worker Registry | common/stages/worker_selection.rs |
| 3 | Client acquisition | Grab the tonic gRPC client for that worker | common/stages/client_acquisition.rs |
| 4 | Request building | Build the engine's proto request (GenerateReqInput), attach LoRA adapters & grammar constraints | regular/stages/request_building.rs |
| 5 | Dispatch metadata | Attach request IDs & routing metadata for tracing | common/stages/dispatch_metadata.rs |
| 6 | Execution | Fire the gRPC call — unary (full response) or streaming (token-by-token) | common/stages/request_execution.rs |
| 7 | Response processing | Detokenize, extract reasoning spans, parse tool calls, assemble SSE chunks or final JSON | regular/processor.rs · regular/streaming.rs |
Cache-aware routing — the superpower, live
SMG ships 10 policies. Nine of them you already half-understand (round_robin, random, least_load, power_of_two = sample two workers pick the lighter, consistent_hashing = sticky by a key, prefix_hash, bucket, manual, passthrough). The one that makes SMG famous is cache_aware:
- Tokenize the incoming request's prefix.
- Search a radix tree per worker for the longest matching cached prefix.
- If the match covers enough of the prompt → route to that worker (its KV cache will be reused → tiny TTFT).
- Otherwise → route to the worker with the most free capacity / least load.
- If the fleet gets imbalanced → fall back to least-loaded.
Don't read about it — play with it. Below are three workers with their current KV caches. Fire requests and watch the policy choose. Flip between cache_aware and round_robin and watch the "tokens recomputed" counter explode:
Fire a request:
crates/kv_index), considers queued token work and KV pressure for the fallback, and can stream live cache events from the engines via a KV event monitor. The toy above uses word chunks, but the decision logic is the same shape.Which policy when?
| Policy | How it picks | Use it when |
|---|---|---|
cache_aware | Radix-tree prefix match + load | Default for production chat — shared system prompts, multi-turn chats, agents |
bucket | Request-length buckets | PD disaggregation deployments |
power_of_two | Sample two, pick lighter | Heterogeneous load, no prefix reuse |
least_load | Least busy worker | Simple load-aware setups |
consistent_hashing | Hash ring + virtual nodes | Session affinity that survives scaling events |
prefix_hash | Hash of prefix tokens | Lightweight cache locality without radix trees |
manual | Explicit routing-key map | Stateful chat pinned by your own key |
round_robin / random | Cycling / uniform | Even distribution; testing |
passthrough | As-is, no balancing logic | Single-worker or externally-managed setups |
Teaching one gateway to speak 20 model dialects
Modern models emit more than prose. Two extras matter most — and every model family formats them differently:
🧠 Reasoning ("thinking")
DeepSeek-R1, Qwen3, Kimi, GLM-4.5… produce chain-of-thought before the answer, wrapped in family-specific markers. SMG's 16 reasoning parsers split the stream into reasoning_content vs content while streaming — auto-detected from the model name, overridable with --reasoning-parser.
🔧 Tool calls
Llama uses <|python_tag|>, DeepSeek uses XML, Qwen3-Coder uses XML with parameter tags, others emit raw JSON. SMG's 21 tool-call parsers extract and validate calls against your tool schemas — and can feed them straight into the MCP loop (next chapter).
Before SMG, every application carried per-model if/else spaghetti for this. After SMG, the gateway absorbs dialects and your app sees one clean OpenAI-style shape regardless of which engine or model answered.
# what your app receives (separate_reasoning: true) — regardless of model family { "choices": [{ "message": { "role": "assistant", "reasoning_content": "Let me think step by step…", "content": "The answer is 42.", "tool_calls": [ … ] } }] }
MCP, chat history, WASM plugins, multi-tenancy
🔌 MCP — tools that actually run
Connect Model Context Protocol servers (stdio/SSE/HTTP). When a model emits a tool call, SMG can execute it via MCP, inject the result, and continue generation — the full agentic loop inside the gateway, with approval policies and audit logging. Exposed via the Responses API.
💾 Chat history — in your infra
Pluggable storage backends (PostgreSQL, Oracle, Redis, in-memory) with schema migrations. Conversations and Responses state live behind your privacy boundary — never at a third party. The data_connector crate owns this.
🧩 WASM plugins
Custom request/response logic in WebAssembly (wasmtime component model): auth enrichments, PII redaction, logging, header mutation — loaded as middleware, hot-manageable via admin APIs, no gateway recompiles.
🏢 Multi-tenant control
API-key auth, OIDC/JWT validation, per-tenant rate limits (token bucket), concurrency caps with queuing, and priority admission scheduling with preemption for the data plane.
🖼️ Multimodal
Image (and optional video) fetch + preprocessing for vision models, with a separate encode disaggregation stage — heavy vision encoding can run on dedicated workers.
🎯 Everything OpenAI-shaped
Chat Completions, Completions, Embeddings, Rerank, Classify, Responses & Conversations APIs, Anthropic Messages, Gemini Interactions, Realtime (WebSocket/WebRTC), audio transcription, tokenize/detokenize utilities.
Staying alive: HA mesh, resilience, observability
🕸️ Mesh HA
Run many SMG nodes as one logical gateway. SWIM gossip handles membership + failure detection (1s heartbeats); CRDT stores replicate worker registry, rate-limit counters, and cache-aware radix trees — so any node routes as well as any other. Rolling updates with request draining = zero downtime. Kubernetes pod discovery via label selectors.
🛡️ Resilience
Per-worker circuit breakers (stop routing to the sick), retries with exponential backoff + jitter, background health probes, graceful shutdown with drain windows, and crash record & replay to reproduce failures offline.
📊 Observability
90+ Prometheus metrics (per-pipeline-stage timings, mesh convergence, token counts, policy decisions), OpenTelemetry traces with W3C context propagated into the engines over HTTP and gRPC, and structured JSON logs with request correlation.
# a 3-node HA cluster is this boring to start (boring = good) smg launch --enable-mesh --mesh-advertise-host 10.0.0.11 --worker-urls http://w1:8000 smg launch --enable-mesh --mesh-advertise-host 10.0.0.12 --mesh-peer-urls 10.0.0.11:39527 --worker-urls http://w2:8000 smg launch --enable-mesh --mesh-advertise-host 10.0.0.13 --mesh-peer-urls 10.0.0.11:39527 --worker-urls http://w3:8000 # then: curl http://any-node:30000/ha/status
Install it, run it, break it
1 · Install (pick one)
# Docker docker pull lightseekorg/smg:latest # Kubernetes helm install smg oci://ghcr.io/smg-project/charts/smg # Python (embeds the Rust binary) pip install smg # Rust (needs protoc) cargo install smg
2 · Point it at workers
# one worker smg launch --worker-urls http://localhost:8000 # a fleet, with the superpower on smg launch --worker-urls http://gpu1:8000 http://gpu2:8000 --policy cache_aware
3 · Call it like it's OpenAI
curl http://localhost:30000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"llama3","messages":[{"role":"user","content":"Hello!"}]}'
4 · The gRPC flagship (what production actually runs)
# engine side: expose SGLang/vLLM over gRPC using the bundled Python shim (grpc_servicer) vllm serve meta-llama/Llama-3.1-8B --grpc # or: sglang serve --model-path … --grpc-mode # gateway side: full-server mode with parsers + tokenizer cache smg launch --worker-urls grpc://worker:50051 \ --reasoning-parser deepseek_r1 \ --tool-call-parser llama \ --tokenizer-cache-enable-l0 --tokenizer-cache-enable-l1 \ --policy cache_aware
--policy round_robin, then twice with cache_aware. Compare smg_router_stage_duration_seconds and TTFT in the metrics. You just reproduced Chapter 6 for real.SMG in production Kubernetes — the two-tier pattern
Large serving platforms typically run SMG (or a fork of it) inside every model pod, with a separate cluster-level router tier in front. Here is the deployment shape you'll meet in the wild — and the part that confuses everyone:
Cluster router ≠ SMG (often same ancestors, different jobs)
The cluster tier is frequently built from the same gateway codebase — same boot/config/metrics skeleton. But it stays HTTP-only: it never tokenizes; it load-balances across pods and carries platform concerns (auth hot path, metering, usage logging). SMG lives inside each pod as the actual server, with the full gRPC pipeline. Dispatcher vs cashier.
How to read any deployment config in 4 lines
Pod command runs smg → the pod's front door is SMG.--backend sglang (or vllm/trtllm) → which engine is inside.--connection-mode grpc → full-server mode (SMG tokenizes).
A router tier named in the ingress path → who dispatches across pods.
Prove you own this
Eight questions. Instant feedback. 6+ correct = you understand SMG better than most people running it.
Mastery checklist
- I can explain why a gateway exists (Chapter 0) in one sentence.
- I can draw the request lifecycle: server → middleware → RouterManager → 3 paths → response processing.
- I know when the gateway is a "full server" vs a "smart proxy" — and who tokenizes in each.
- I can name the 7 gRPC pipeline stages in order.
- I can explain cache-aware routing with the radix tree and why it cuts TTFT.
- I know what reasoning/tool parsers do and why apps no longer need per-model code.
- I can start SMG against workers and hit it with curl.
- I can read a production deployment config: which process fronts the pod, which engine is inside, HTTP vs gRPC mode, and which tier dispatches across pods.
Glossary
Shepherd Model Gateway — the Rust LLM gateway this tutorial covers. OSS: smg-project/smg.
A registered engine instance SMG routes to (URL + health + metadata).
Inference software on a worker: SGLang, vLLM, TRT-LLM, TokenSpeed, MLX, Ollama…
Integer unit of model I/O; the component converting text↔tokens. SMG caches it two levels deep (L0 exact, L1 prefix).
Engine-side memory of processed prefixes; reuse = skipping prefill compute.
Time to first token — the latency users feel most; cache-aware routing's main victim.
The two phases of inference: digesting the prompt vs generating tokens.
Running prefill and decode on different workers with KV transfer between them.
SMG's dispatch brain; chooses gRPC/HTTP/external router per request.
Worker-selection algorithm (cache_aware, power_of_two, …). Per-model, pluggable.
Prefix tree structure (kv_index crate) mapping token prefixes → workers for cache-aware routing.
SMG's flagship 7-stage request assembly line for token-level engine streaming.
Python shim inside vLLM/SGLang that exposes the engine over SMG's gRPC protocol.
Jinja2 formatting that turns OpenAI-style messages into a model's exact prompt format.
Extracts "thinking" spans per model family (16 supported, auto-detected).
Extracts function calls per model family (21 supported) and validates arguments.
Model Context Protocol — standard for external tool servers; SMG can execute tools mid-generation.
OpenAI's agentic API (tool loops, conversations) — hosted by SMG with MCP.
SMG's multi-node HA mode: SWIM gossip + CRDT state replication.
Conflict-free replicated data type — merges state across nodes without locks.
Per-worker switch that stops routing to a failing worker until it recovers.
WebAssembly middleware module for custom request/response logic.
A separate router in front of many SMG pods (often the same gateway codebase); picks pods, never tokenizes.
--enable-igw: multi-model gateway mode — several routers, per-model policies.
Cheat sheet
# ── run ── smg launch --worker-urls http://w1:8000 http://w2:8000 --policy cache_aware smg launch --worker-urls grpc://w1:50051 --reasoning-parser deepseek_r1 --tool-call-parser llama smg launch --enable-mesh --mesh-advertise-host 10.0.0.1 --mesh-peer-urls 10.0.0.2:39527 # ── call ── curl localhost:30000/v1/chat/completions -d '{"model":"m","messages":[…]}' curl localhost:30000/v1/models # discovery curl localhost:30000/ha/status # mesh cluster state # ── the four-line production-config reader ── # pod command runs smg → pod's front door is SMG # --backend sglang → engine = SGLang | --connection-mode grpc → full-server mode # ingress/router tier → who dispatches requests across pods # ── the one-sentence summary, memorize it ── # Axum HTTP → middleware → RouterManager picks gRPC/HTTP/external → # gRPC path: template → tokenize(+cache) → policy pick → build → execute → detokenize+parse