Benchmarks
How the Wäg gateway performs against LiteLLM under identical conditions. This page sets out the method in full and publishes the measurements it produced. We do not publish multipliers.
How this was measured
Everything ran in Docker on one machine, each component pinned to its own cores so nothing competed for CPU.
- Both gateways sit in front of the same upstream with identical model configuration, so neither gets an easier path.
- Each gateway runs as its own documentation recommends: LiteLLM per its production guide, with workers set to the number of vCPUs; Wäg with shipped defaults. Response caching is off on both.
- Closed-loop tests measure maximum throughput. Open-loop tests fire on a fixed schedule and count latency from the intended send time, so a gateway that queues cannot hide the queueing.
- Every response is validated: status, body shape, and for streams the expected chunk count and terminator. Anything else counts as an error, never as a fast response.
- Each scenario runs three times and the tables show the median. With no gateway in the path the same load reaches about 90,000 req/s, far above either gateway, so the results are gateway-bound rather than harness-bound.
Footprint
What each gateway costs before it serves anything: memory at rest, time to ready, image size, and the latency it adds to a single request against an instant upstream.
What it means: Memory decides how many replicas fit on a node, and image size decides how fast a rollout is. At 3.8 MB resident and a 123 MB image, Wäg fits in a sidecar and is ready before a health check times out; 2.3 GB and 11 seconds is a pod you plan around.
Added latency is the gateway's own p50 minus the p50 measured with no gateway in the path.
Under a realistic upstream
A real model answers in hundreds of milliseconds, so thousands of requests are in flight at once. Here the upstream takes 300 ms and requests arrive at a fixed rate whatever the gateway does. At 2,000 req/s roughly 600 requests are in flight at any moment.
| requests per second | Wäg p50 | Wäg p99 | Wäg Errors | Wäg CPU | LiteLLM p50 | LiteLLM p99 | LiteLLM Errors | LiteLLM CPU | × |
|---|---|---|---|---|---|---|---|---|---|
| 100 | 303 ms | 305 ms | 0.00% | 10.2% | 305 ms | 308 ms | 0.00% | 38.7% | 1.0× |
| 500 | 301 ms | 303 ms | 0.00% | 15.6% | 304 ms | 321 ms | 0.00% | 140.5% | 1.1× |
| 1,000 | 301 ms | 302 ms | 0.00% | 20.2% | 317 ms | 605 ms | 0.00% | 263.7% | 2.0× |
| 2,000 | 301 ms | 302 ms | 0.00% | 31.7% | 3.26 s | 28 s | 7.99% | 391.9% | 92× |
What it means: This is the shape of real traffic, where a model answers in hundreds of milliseconds and thousands of requests are in flight. A gateway that queues turns a 300 ms answer into a multi-second one, and nobody notices until peak. Wäg stays at the floor through 2,000 req/s on under a third of its CPU budget. LiteLLM is already queueing at 1,000 and fails 8% of requests at 2,000.
The upstream alone takes 300 ms, so 300 ms is the floor. Everything above it is time spent queueing inside the gateway. The × column always reads the same way: higher means Wäg is ahead. It is Wäg ÷ LiteLLM for throughput and LiteLLM ÷ Wäg for latency.
Throughput and latency
Closed loop against an instant upstream: pure gateway cost, with no model latency to hide behind. These are the numbers a capacity plan needs. They are not the reason to choose a control plane.
4 vCPU, no database
| Concurrent clients | Wäg req/s | Wäg p99 | Wäg CPU | LiteLLM req/s | LiteLLM p99 | LiteLLM CPU | × |
|---|---|---|---|---|---|---|---|
| 1 | 6,232 | 0.27 ms | 63.4% | 463 | 3.2 ms | 98.1% | 13× |
| 8 | 25,711 | 0.70 ms | 311.5% | 1,237 | 15 ms | 286.1% | 21× |
| 32 | 25,093 | 2.6 ms | 321.8% | 1,621 | 46 ms | 385.1% | 15× |
| 128 | 23,639 | 11 ms | 328.3% | 1,669 | 111 ms | 396.2% | 14× |
| 512 | 22,407 | 45 ms | 336.9% | 1,569 | 749 ms | 391.6% | 14× |
1 vCPU, no database
| Concurrent clients | Wäg req/s | Wäg p99 | LiteLLM req/s | LiteLLM p99 | × |
|---|---|---|---|---|---|
| 1 | 6,589 | 0.35 ms | 459 | 3.2 ms | 14× |
| 8 | 16,594 | 1.3 ms | 484 | 26 ms | 34× |
| 32 | 15,226 | 3.8 ms | 506 | 85 ms | 30× |
| 128 | 13,264 | 15 ms | 505 | 438 ms | 26× |
What it means: This sets how many replicas a traffic level needs, and therefore what the gateway costs to run. The 1 vCPU table is the one to read for Kubernetes: that is the per-core efficiency a pod actually gets.
Streaming, payloads, embeddings, failover
4 vCPU, no database. Streams are 50 tokens 5 ms apart; the soak runs 200 streams per second of roughly 1.2-second answers for three minutes.
| Scenario | Wäg | Wäg p99 | Wäg Errors | LiteLLM | LiteLLM p99 | LiteLLM Errors | × |
|---|---|---|---|---|---|---|---|
| Streaming, 64 clients | 233 | 286 ms | 0.00% | 179 | 674 ms | 0.00% | 1.3× |
| Streaming, 256 clients | 911 | 288 ms | 0.00% | 307 | 1.09 s | 0.00% | 3.0× |
| 64 KB conversation history, 32 clients | 3,583 | 29 ms | 0.00% | 568 | 106 ms | 0.00% | 6.3× |
| Embeddings (1,536 dims), 32 clients | 13,558 | 4.3 ms | 0.00% | 740 | 69 ms | 0.00% | 18× |
| Fallback after upstream 500, 16 clients | 19,816 | 1.8 ms | 0.00% | 584 | 62 ms | 0.00% | 34× |
| Soak: 200 streams/s for 3 minutes | 200 | 1.27 s | 0.00% | 176 | 20 s | 12.09% | 1.1× |
What it means: Streaming, long prompts, embeddings and failover are where a production workload spends its time. Read the error columns before the throughput ones: a failed request is a failed user request, not a slow one.
The 64 KB payload figure sits at the harness ceiling: with no gateway in the path the same load reaches 3,711 req/s of 64 KB bodies on one core, so the Wäg number there is a lower bound rather than a maximum.
What this does not show
Every benchmark describes one setup. These are the limits of this one.
- Absolute numbers depend on the host. Both gateways ran on the same machine with pinned cores, so the comparison between them holds, but expect different absolute figures on your own hardware.
- The harness that drove these runs is not published, so you cannot repeat them command for command. The method above is described in enough detail to rebuild an equivalent.
- LiteLLM ships most weeks. These figures were measured against v1.104.2, the current stable release. When they ship a release that changes the forwarding path, we measure it again and publish whatever we find.
- LiteLLM was configured from its production guide, not by its maintainers. A better configuration may exist, and if you find one we will re-run and republish.
- Both gateways ran on one machine with pinned cores. Nothing here measures behaviour across a network, under Kubernetes scheduling, or with noisy neighbours.