Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
375 lines
19 KiB
Markdown
375 lines
19 KiB
Markdown
# llmgw
|
||
|
||
A single-binary LLM gateway for clusters with multiple InfiniBand fabrics,
|
||
NVLink domains and heterogeneous GPUs.
|
||
|
||
It serves LLMs running on your own GPU and CPU servers (vLLM, SGLang,
|
||
llama.cpp, TensorRT-LLM) behind one OpenAI- and Anthropic-compatible
|
||
endpoint, and answers from your own documents. Design document: [llmgw-plan.md](llmgw-plan.md).
|
||
|
||
## Getting started
|
||
|
||
On the machine that will be the control plane, with the release binary
|
||
installed:
|
||
|
||
```sh
|
||
sudo llmgw
|
||
```
|
||
|
||
The setup screens install one machine or three (high availability), check
|
||
them first, and leave a model answering. After that the same command opens
|
||
the console. [docs/quick-install.md](docs/quick-install.md) has the six steps
|
||
from empty servers to a first request; everything else is below.
|
||
|
||
## Status
|
||
|
||
Releases track the roadmap phases; see [CHANGELOG.md](CHANGELOG.md).
|
||
|
||
| Version | Phase | Scope | State |
|
||
|---|---|---|---|
|
||
| v0.1.0 | 0 | Skeleton and sim-engine | released |
|
||
| v0.2.0 | 1 | Static router (filter/score/pick, streaming proxy) | released |
|
||
| v0.3.0 | 1.5 | Admission control (priority bands) | released |
|
||
| v0.4.0 | 2 | Agent: GPU/IB discovery, engine supervision | released as part of v0.5.0 |
|
||
| v0.5.0 | 3 | Control plane: catalog, placement, reconciler, ctl | released |
|
||
| v0.6.0 | 4 | Multi-node placement and fabric awareness | released |
|
||
| v0.7.0 | — | Conversations, agent memory, disk management | released |
|
||
| v0.8.0 | — | PostgreSQL, leader election, agent failover | released |
|
||
| v0.9.0 | — | Managing people and the tenancy tree | released |
|
||
| v0.10.0 | — | InfiniBand fabrics and NVLink, doctor, node add, engines installed by the nodes | released |
|
||
| v0.11.0 | — | High availability from setup, the setup and console screens, usage sums and per-tenant alerts | released |
|
||
| v0.12.0 | — | The cluster's settings in the store, in force when changed; the firewall switch; wipe | released |
|
||
| v0.13.0 | — | The whole system stopped and started from one command; Qwen3Guard judges, on a GPU and CPUs together | released |
|
||
| v0.14.0 | — | Knowledge bases: documents from people's own machines, kept apart per tenant, answered from | released (v0.14.1: adapters, the tree over the API, the firewall on every machine; v0.14.2: pictures from ComfyUI; v0.14.3: 3D models as jobs; v0.14.4: any ComfyUI workflow as a job, presets, licences; v0.14.5: picture and 3D models that add themselves; v0.14.6: setup that explains every part and lets it be chosen, the gateway on all three HA machines; v0.14.7: the judge sized for its machine; v0.14.8: sign-in with Active Directory, several keys for one outside provider; v0.14.9: sign-in set up from the console, a node's console that shows the machine) |
|
||
| v0.15.0 | — | The company's MCP tools and VS Code through the gateway; guardrails that ask outside services and put redacted values back; answers to similar questions | released (v0.15.1: Strata, a 125B model of experts on one card; a second mirror kept in step; pickled weights refused; a served model's context changed; v0.15.2: a served model moved to more cards; v0.15.3: a model stopped and kept, or removed with its files) |
|
||
| v0.16.0 | — | Weights pinned to a commit and checked against what Hugging Face published; documents embedded by the gateway; Strata offered where models are chosen; cluster upgrades that say how far they are | released |
|
||
| v1.0.0 | 5 | What phase 5 has left: precise KV routing proven on vLLM, the block cost picker, defragmentation across NVLink domains, prefill/decode disaggregation | planned |
|
||
|
||
As of v0.6.0 a cluster runs unattended: agents report their hardware, the
|
||
control plane places replicas — including ones larger than a single machine —
|
||
models load when someone asks for them and stop when nobody does, and keys,
|
||
budgets and rate limits decide who may use any of it.
|
||
|
||
Since v0.11.0 (each release's detail is in [CHANGELOG.md](CHANGELOG.md)):
|
||
|
||
- **Setup that explains, and high availability that is whole** (v0.14.6).
|
||
`sudo llmgw` lists the parts of an install, each with what it is for and
|
||
what is missing without it, and every one can be turned off or set: the
|
||
starter model and its context, the mirror's directory, the judge's
|
||
categories and what it does when it does not answer, limits, the
|
||
portal's language, pictures and 3D. With three machines each runs the
|
||
control plane, with a floating address on the leader, and any one can
|
||
be lost: on three VMs, cutting the store primary's power lost no
|
||
request. One ComfyUI serves an environment's models, pictures, 3D and
|
||
workflows are MCP tools, and `/v1/messages` gives a model's reasoning as
|
||
thinking blocks.
|
||
- **Picture and 3D models that add themselves** (v0.14.5). Where a node's
|
||
card fits flux-schnell or triposr and the catalog makes nothing of that
|
||
kind, the leader adds it on demand, so a new cluster's portal has its
|
||
Images and 3D pages; the cluster page and the console show each preset
|
||
and add it by hand, and `presets.auto` turns it off. Stable Fast 3D's
|
||
colours can be brought to the picture's (`albedo_match`), and every mesh
|
||
carries its whole licence.
|
||
- **Any ComfyUI workflow, and models by name** (v0.14.4). `POST
|
||
/v1/workflows/runs` runs a ComfyUI model's workflow as a job, its inputs
|
||
(text, numbers, choices, images, masks, audio, video, files) declared in
|
||
the catalog and listed in `/v1/models`. `llmgw ctl model add triposr` or
|
||
`flux-schnell` adds a tested model with no YAML; a profile may name the
|
||
card it needs instead of a class, and ComfyUI now installs on V100s. A
|
||
licence carries what a machine can match, and a person can leave
|
||
licences out on the profile page so no key of theirs reaches such a
|
||
model. A 3D job waiting for the one card says what holds it and for
|
||
about how long.
|
||
- **3D models as jobs** (v0.14.3). `POST /v1/3d/generations` answers with a
|
||
job, asked after until its mesh is ready to download. ComfyUI makes it on
|
||
the cluster's own cards, TripoSR being the tested model. The job says how
|
||
far along it is, the model's licence is written into the GLB, and
|
||
`per_mesh` prices it. People make them at `/3d` on the portal; `llmgw ctl
|
||
3d` shows every owner's jobs. A model profile may also load weights of
|
||
its own, and an admission band may wait longer than the rest, for bulk
|
||
work.
|
||
- **Pictures from ComfyUI** (v0.14.2). `POST /v1/images/generations` is made
|
||
by ComfyUI, behind a gate that demands the replica's key, as an engine the
|
||
agents install from its source checked against a digest. `per_image`
|
||
prices it, and people make pictures at `/images` on the portal.
|
||
- **Adapters, and the tenancy tree over the API** (v0.14.1). `llmgw ctl
|
||
kb dataset` and `llmgw ctl train` make a LoRA adapter from a collection
|
||
on a node's spare GPU memory, kept on the mirror and seen only by its
|
||
owner. Organizations, teams, projects, keys and memberships are in the
|
||
admin API and the Terraform provider. The agents' settings are edited
|
||
with the cluster's, on the portal too, and the firewall setting reaches
|
||
every machine.
|
||
- **Knowledge bases** (v0.14.0). People put a company's documents in from
|
||
their own machines with `llmgw-kb`, a separate download for Windows,
|
||
macOS and Linux that reads PDF, Office, OpenDocument, mail and many more
|
||
formats and sends text and vectors, never the files. Collections are kept
|
||
apart per tenant, down to row-level security in PostgreSQL. A chat
|
||
request, or a part of the tree by default, is answered from them with
|
||
its sources: [docs/knowledge.md](docs/knowledge.md).
|
||
- **One command for the whole system** (v0.13.0): `llmgw stop`, `start` and
|
||
`restart`. Qwen3Guard is the guardrails' judge, on a GPU and on CPUs
|
||
together.
|
||
- **Settings that apply when changed** (v0.12.0). The cluster's
|
||
configuration lives in the store, the same on every control plane and in
|
||
force within seconds (`llmgw ctl config`, the console's Settings). The
|
||
firewall can be switched on and off for the whole cluster.
|
||
- **Security hardening** (v0.11.2): signed releases only, a token on the
|
||
admin API, engines not run as root, TLS for the agent port, etcd and
|
||
Patroni, a firewall for llmgw's internal ports, encrypted backups,
|
||
bounded connections, sign-in throttling, a Content-Security-Policy.
|
||
- **Guardrails** (v0.11.2). Policies check requests and answers for PII,
|
||
secrets, prompt injection, deny patterns, and with a judge model; they
|
||
observe or enforce, and attach to any part of the tenancy tree:
|
||
[docs/guardrails.md](docs/guardrails.md).
|
||
- **Administration** (v0.11.2):
|
||
- an audit log of every change (`ctl audit`);
|
||
- staff roles (viewer, operator, billing, admin);
|
||
- single sign-on through OpenID Connect and provisioning through SCIM
|
||
2.0;
|
||
- key lifetime limits, and rotation with overlap (`keys ttl`, `keys
|
||
rotate`);
|
||
- usage export as CSV (`ctl usage export`).
|
||
- **Notifications** (v0.11.2): webhooks, signed or Slack-shaped, for a
|
||
model parked, a node lost, a budget spent and new weights published, and
|
||
Alertmanager receiver templates.
|
||
- **Traffic and models** (v0.11.2):
|
||
- request tags, canaries of new weights with automatic rollback, and
|
||
quality suites gating updates;
|
||
- a response cache, kept and replayed requests, and batches;
|
||
- Anthropic's Messages API, and more of OpenAI's (structured output,
|
||
images, rerank, audio, moderations);
|
||
- scaling by the clock, and SLO-aware admission;
|
||
- TensorRT-LLM, MIG, and models imported from Ollama;
|
||
- routing by what each vLLM replica's cache holds, and LoRA adapters
|
||
loaded on demand;
|
||
- other llmgw clusters as providers, and placement that knows racks,
|
||
PDUs and power caps.
|
||
- **Operations** (v0.11.2):
|
||
- `server restore`, off-machine backup copies, and secrets in Vault;
|
||
- forgetting a person, and keeping a project's requests in the cluster;
|
||
- offline installs from a bundle, and a Helm chart;
|
||
- tracing from the control plane to the engines;
|
||
- the admin API in OpenAPI, with a Terraform provider;
|
||
- the portal in English and Turkish, with its docs at /docs.
|
||
|
||
Every one of these is in the console as well as on the command line:
|
||
[docs/console.md](docs/console.md).
|
||
|
||
What is still ahead is in [BACKLOG.md](BACKLOG.md). Two things that look like
|
||
gaps are not: a model falls through to its fallbacks when there is no
|
||
capacity as well as when a budget is spent, and routing profiles are chosen
|
||
by the key rather than by a request header on purpose — a header would let
|
||
any client promote itself into the top priority band.
|
||
|
||
One thing is worth saying plainly:
|
||
multi-node replicas are proven on a simulated cluster and on real hardware
|
||
without a fabric. The InfiniBand, NVLink and NCCL paths — telling fabrics apart
|
||
by their subnet managers, mapping them, finding NVLink domains, launching across
|
||
nodes — are written against the formats and failure modes those tools actually
|
||
have and tested against them, but they have not yet run on a machine that has
|
||
them. On such a machine, run `llmgw doctor --json` first and keep the report;
|
||
[docs/fabrics.md](docs/fabrics.md) says what to look at after that.
|
||
|
||
## Documentation
|
||
|
||
Every page is also in the binary: `llmgw docs`, or **Manual** in the console.
|
||
|
||
**Installing**
|
||
|
||
- [docs/quick-install.md](docs/quick-install.md) — from empty servers to a
|
||
first request, in six steps: start here
|
||
- [docs/install.md](docs/install.md) — the same with every option explained,
|
||
and running the cluster after: monitoring, notifications, single sign-on,
|
||
the firewall, upgrades, backups
|
||
- [docs/node-onboarding.md](docs/node-onboarding.md) — adding nodes to a
|
||
cluster that exists
|
||
- [docs/kubernetes.md](docs/kubernetes.md) — the control plane on
|
||
Kubernetes with the Helm chart, and why GPU nodes stay outside it
|
||
- [docs/kong.md](docs/kong.md) — llmgw behind Kong Gateway, or Kong behind
|
||
llmgw: the configuration, what Kong counts, and which to choose
|
||
- [docs/fabrics.md](docs/fabrics.md) — InfiniBand fabrics and NVLink: what
|
||
llmgw needs of them, checking a node with `llmgw doctor`, and what to look at
|
||
when a replica spanning nodes will not run
|
||
|
||
**Running it**
|
||
|
||
- [docs/console.md](docs/console.md) — the console that bare `llmgw` opens:
|
||
every view, and the command each of its actions runs
|
||
- [docs/onboarding.md](docs/onboarding.md) — accounts, keys, limits, staff
|
||
roles and single sign-on
|
||
- [docs/clients.md](docs/clients.md) — using the gateway from code with the
|
||
official OpenAI and Anthropic SDKs, and llmgw's extensions
|
||
- [docs/guardrails.md](docs/guardrails.md) — checking requests and answers:
|
||
policies, detectors, a judge model, and where each applies
|
||
- [docs/knowledge.md](docs/knowledge.md) — knowledge bases: collections,
|
||
who may read them, `llmgw-kb` on people's machines, and answers from
|
||
documents
|
||
- [docs/choosing-models.md](docs/choosing-models.md) — which models to run,
|
||
how many of each, and where the weights should live
|
||
|
||
**Reference**
|
||
|
||
- [docs/reference.md](docs/reference.md) — every configuration key, generated
|
||
from the structs and checked by a test, so it cannot describe a setting that
|
||
does not exist or miss one that does
|
||
- [docs/data.md](docs/data.md) — what llmgw stores, what it does not, and
|
||
what leaves the machine; written for a security review
|
||
- [docs/admin-api.md](docs/admin-api.md) and its OpenAPI document — the
|
||
admin API, for scripts; [terraform-provider-llmgw/](terraform-provider-llmgw/)
|
||
manages models, quotas and guardrails with Terraform or OpenTofu
|
||
- [deploy/](deploy/) — [a mirror](deploy/mirror/) on its own machine,
|
||
[Caddy and nginx](deploy/proxy/) in front of the client port, [two control
|
||
planes](deploy/ha/) behind a balancer, and [Prometheus, Alertmanager and
|
||
Grafana](deploy/monitoring/)
|
||
|
||
**Working on llmgw**
|
||
|
||
- [docs/development.md](docs/development.md) — built from source on one
|
||
workstation, no systemd and no `sudo`
|
||
- [llmgw-plan.md](llmgw-plan.md) — the design; [BACKLOG.md](BACKLOG.md) —
|
||
what is next
|
||
|
||
## Building
|
||
|
||
```sh
|
||
make build # bin/llmgw and bin/llmgw-kb
|
||
make test # unit tests
|
||
make race # with the race detector
|
||
```
|
||
|
||
## Operating a cluster
|
||
|
||
```sh
|
||
llmgw ctl nodes # capacity, health, what is running where
|
||
llmgw ctl models # catalog and replica counts
|
||
llmgw ctl replicas --model llama # replicas and their shards
|
||
llmgw ctl explain-placement llama # why a model has not been placed
|
||
llmgw ctl drain gpu-a-01 # move work off a node, one replica at a time
|
||
llmgw ctl cordon gpu-a-01 # stop new placement, leave what runs
|
||
sudo llmgw doctor # on a node: what its GPUs, NVLink and fabric need
|
||
llmgw ctl audit --since 24h # who changed what
|
||
llmgw ctl usage export --from 2026-09-01 --to 2026-10-01 --by project,model
|
||
llmgw ctl kb list # knowledge-base collections, grants, defaults
|
||
```
|
||
|
||
Bare `llmgw` opens the console, which does all of this from one screen.
|
||
|
||
`ctl` talks to the admin port (`:9090` by default), not the client-facing one.
|
||
Add `--json` to any command for machine-readable output.
|
||
|
||
`explain-placement` is the one worth knowing about: "unschedulable" on its own
|
||
says nothing about which of six constraints bit, so it prints every candidate
|
||
considered and the reason it was rejected.
|
||
|
||
## What llmgw adds to a request
|
||
|
||
Measured with `llmgw ctl bench overhead`. It starts an engine that answers
|
||
at once and a gateway in front of it, in one process on loopback. It sends
|
||
the same load straight to the engine and through the gateway, and reports
|
||
what the gateway added. Run on an Apple M5 (10 CPUs, load about 2) on
|
||
2026-10-16, 10 s a step, 72,000 requests with none failed:
|
||
|
||
| Through the gateway | added at p50 | added at p99 |
|
||
|---|---|---|
|
||
| bare | 0.06–0.24 ms | 0.10–0.33 ms |
|
||
| with authentication and usage accounting | 0.05–0.32 ms | 0.09–0.37 ms |
|
||
| with the guardrails' pattern checks as well | 0.10–0.42 ms | 0.15–0.49 ms |
|
||
|
||
The range is across 100 to 2,000 requests a second. The overhead was
|
||
*lowest* at 2,000, most likely because a busier processor keeps its cores
|
||
awake. The guardrails' judge model is not in these figures: a judgment
|
||
takes seconds on CPUs (see [install.md](docs/install.md)) and is a model's
|
||
time, not the gateway's.
|
||
|
||
The command also prints the gateway's CPU per thousand requests. Gateway,
|
||
engine and load share one process there, so that figure is rough (0.04 to
|
||
0.65 s per thousand in the run above); the latency is the one to quote.
|
||
Figures from a VM on a busy host move with the host: run it where you will
|
||
deploy, and keep the load it prints beside the result. CI runs a one-second
|
||
version on every change (`TestTheGatewaysOverheadIsMeasured`).
|
||
|
||
## Running the router
|
||
|
||
```sh
|
||
# Two fake engines and a router in front of them.
|
||
./bin/llmgw sim-engine --listen :8001 --model llama &
|
||
./bin/llmgw sim-engine --listen :8002 --model llama &
|
||
./bin/llmgw server \
|
||
--endpoint e1=http://127.0.0.1:8001=llama \
|
||
--endpoint e2=http://127.0.0.1:8002=llama
|
||
|
||
curl -N localhost:8080/v1/chat/completions \
|
||
-H 'content-type: application/json' \
|
||
-H 'x-session-id: demo' \
|
||
-d '{"model":"llama","messages":[{"role":"user","content":"hello"}],"stream":true}'
|
||
```
|
||
|
||
For anything real use a config file; [deploy/examples/server.yaml](deploy/examples/server.yaml)
|
||
is commented throughout. Metrics, health and pprof are on the admin port (`:9090`
|
||
by default), deliberately separate from the client-facing one.
|
||
|
||
Requests carry a priority band (`x-llmgw-band`). Under load, low bands wait at
|
||
the gateway while high bands keep flowing, so the cluster degrades from the
|
||
bottom up instead of everywhere at once. Measured effect: interactive p99 stays
|
||
within 1.24x of idle while 500 concurrent batch requests run, against 58x with
|
||
admission off.
|
||
|
||
Routing profiles select how endpoints are scored: `chat` favours session
|
||
affinity and the replica that has been answering soonest (its time to first
|
||
token, smoothed), `batch` favours the shortest queue, `long-context` favours
|
||
free KV cache. Pick one per model in the config, or per request with the
|
||
`x-llmgw-profile` header.
|
||
|
||
## sim-engine
|
||
|
||
Simulates the inference-engine behaviours **the router cares about**, so the
|
||
router can be developed and tested without GPUs. What it models:
|
||
|
||
- continuous batching (bounded number of concurrent sequences)
|
||
- KV cache block accounting, queueing once blocks run out (head-of-line
|
||
blocking included)
|
||
- automatic prefix caching (chained block hashes plus an LRU) and its effect
|
||
on time to first token
|
||
- per-token latency growing with batch size
|
||
- dropping a request and freeing its blocks when the client disconnects
|
||
- `/metrics` using vLLM's metric names
|
||
|
||
```sh
|
||
./bin/llmgw sim-engine --listen :8000 --model llama-3.3-70b
|
||
|
||
curl -N localhost:8000/v1/chat/completions \
|
||
-H 'content-type: application/json' \
|
||
-d '{"messages":[{"role":"user","content":"hello"}],"max_tokens":8,"stream":true}'
|
||
```
|
||
|
||
Tune load and latency behaviour with `--max-num-seqs`, `--num-gpu-blocks`,
|
||
`--prefill-tps`, `--decode-tps`, `--batch-contention` and
|
||
`--prefix-cache-blocks`. For chaos testing use `--fail-rate`, and `--seed` for
|
||
deterministic runs.
|
||
|
||
### The engine metric contract
|
||
|
||
For the router to make routing decisions an engine **must** expose these three
|
||
metrics (plan §7.6). sim-engine publishes them under vLLM's real names:
|
||
|
||
```
|
||
vllm:num_requests_running
|
||
vllm:num_requests_waiting
|
||
vllm:kv_cache_usage_perc
|
||
```
|
||
|
||
Adding support for a new engine means satisfying that contract. Metric names
|
||
drift between versions, so the name mapping is configurable.
|
||
|
||
## Module path
|
||
|
||
`gitea.mynodes.xyz/ilker/llmgw`. To change it:
|
||
|
||
```sh
|
||
make rename NEW=git.example.com/team/llmgw
|
||
```
|
||
|
||
## Language
|
||
|
||
Code, comments, tests, commit messages and documentation are in English.
|