Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ uvicorn meridian.api.main:app --host 0.0.0.0 --port 8080
# meridian --config configs/local_gpu.yaml
```

Published images: `ghcr.io/imv-in/meridian:0.9.3` / `:latest`
Published images: `ghcr.io/imv-in/meridian:0.12.0` / `:latest`

```bash
docker run --rm -p 8080:8080 \
-v "$(pwd)/configs/mock_demo.yaml:/app/config.yaml:ro" \
-e MERIDIAN_CONFIG=/app/config.yaml \
ghcr.io/imv-in/meridian:0.9.3
ghcr.io/imv-in/meridian:0.12.0
```
(You’ll still need reachable backends in that config.)

Expand All @@ -77,7 +78,22 @@ export MERIDIAN_CONFIG=configs/local_gpu.yaml
uvicorn meridian.api.main:app --host 0.0.0.0 --port 8080
```

Load / overhead numbers: [`docs/LOAD.md`](docs/LOAD.md)
### Measured real-engine overhead

Meridian v0.12.0 was tested against Ollama 0.31.1 with `qwen2.5:0.5b` on
an RTX 4060 Laptop GPU. All direct and gateway sync/stream checks passed with
zero benchmark errors.

| Concurrency | Requests | Direct p50 | Meridian p50 | p50 delta | RPS change |
|------------:|---------:|-----------:|-------------:|----------:|-----------:|
| 1 | 30 | 174.1 ms | 180.5 ms | +6.4 ms | -3.9% |
| 4 | 40 | 368.5 ms | 375.5 ms | +7.0 ms | -2.2% |
| 8 | 80 | 243.0 ms | 247.2 ms | +4.1 ms | -1.7% |

These are sequential single-host runs, not a cross-row scaling curve. Ollama
GPU warm state and dynamic batching affect absolute latency. See the
[`methodology and raw evidence`](docs/LOAD.md) and
[`reproducible Ollama/vLLM validation`](docs/REAL_ENGINE_VALIDATION.md).

## Documentation map

Expand Down
79 changes: 39 additions & 40 deletions docs/LOAD.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Load & overhead numbers (0.9.3)
# Load and overhead numbers

How much latency Meridian adds in front of a backend, and how to re-measure
on your hardware for ~1k-user capacity planning.
Expand All @@ -24,33 +24,13 @@ The script:
### Recipe

```bash
# 1) Backend
ollama pull qwen2.5:0.5b
ollama serve # default http://127.0.0.1:11434

# 2) Meridian (config already points at Ollama)
MERIDIAN_CONFIG=configs/local_gpu.yaml \
uvicorn meridian.api.main:app --host 127.0.0.1 --port 18080

# 3) Smoke (stream + non-stream + headers)
python scripts/smoke_test.py --url http://127.0.0.1:18080 --model qwen2.5:0.5b

# 4) Overhead (direct Ollama vs via Meridian)
python scripts/bench_overhead.py \
--backend-url http://127.0.0.1:11434 \
--gateway-url http://127.0.0.1:18080 \
--model qwen2.5:0.5b \
--requests 30 --concurrency 1 --warmup 3

# Optional light concurrency:
python scripts/bench_overhead.py \
--backend-url http://127.0.0.1:11434 \
--gateway-url http://127.0.0.1:18080 \
--model qwen2.5:0.5b \
--requests 20 --concurrency 4 --warmup 2
PYTHON=.venv/bin/python sh scripts/validate_ollama.sh
```

If `auth.enabled`, add `--auth mrdn_...` to the bench and smoke commands.
The profile handles backend readiness, a disposable Meridian process, smoke
checks, and the serial overhead run. See
[`REAL_ENGINE_VALIDATION.md`](./REAL_ENGINE_VALIDATION.md) for the complete
Ollama/vLLM release matrix and environment overrides.

> Real-model **absolute** latency is dominated by the engine. Use these numbers
> to confirm **gateway overhead stays small relative to generation time**, not
Expand Down Expand Up @@ -92,40 +72,59 @@ assert absolute ms (hardware variance).

## Reference numbers (Ollama, real path)

Recorded **2026-07-10** on the same Linux host:
Recorded **2026-07-30**. Complete machine-readable results:
[`serial`](./validation/ollama-v0.12.0.json),
[`concurrency 4`](./validation/ollama-v0.12.0-c4.json), and
[`concurrency 8`](./validation/ollama-v0.12.0-c8.json).

| Host detail | Value |
|-------------|--------|
| GPU | NVIDIA GeForce RTX 4060 Laptop (8 GiB) |
| Backend | Ollama `qwen2.5:0.5b` on `127.0.0.1:11434` |
| Meridian | **v0.9.3**, `configs/local_gpu.yaml`, port **18080** (no auth/budgets/cost) |
| Meridian | **v0.12.0**, generated validation config, port **18080** (no auth/budgets/cost) |
| Ollama | **0.31.1** |
| Python | **3.12.11** |
| NVIDIA driver | **580.159.03** |
| Request shape | non-stream chat, `max_tokens=8`, message `"bench"` |

### Serial isolation (`n=30`, `concurrency=1`)

| Path | p50 (ms) | p95 (ms) | mean (ms) | RPS | errors |
|------|----------|----------|-----------|-----|--------|
| Direct → Ollama | 151.0 | 153.2 | 151.2 | 6.6 | 0 |
| Via Meridian | 153.0 | 155.3 | 153.3 | 6.5 | 0 |
| **Overhead** | **~1.9 ms** | **~2.1 ms** | **~2.1 ms** | — | — |
| Direct → Ollama | 174.1 | 185.0 | 173.9 | 5.75 | 0 |
| Via Meridian | 180.5 | 186.2 | 181.1 | 5.52 | 0 |
| **Overhead** | **6.4 ms** | **1.3 ms** | **7.2 ms** | — | — |

**Takeaway:** gateway adds ~**2 ms** (~**1.3%** of end-to-end p50). Engine time is the budget.
**Takeaway:** this run added **6.4 ms** at p50, about **3.7%** of direct
end-to-end p50. Engine generation remained the dominant latency component.

### Light concurrent (`n=20`, `concurrency=4`)
### Concurrent load (`n=40`, `concurrency=4`)

| Path | p50 (ms) | p95 (ms) | mean (ms) | RPS | errors |
|------|----------|----------|-----------|-----|--------|
| Direct Ollama | 180.7 | 243.1 | 189.1 | 20.2 | 0 |
| Via Meridian | 186.7 | 235.5 | 192.8 | 19.9 | 0 |
| **Delta p50** | **~5.9 ms** | (noisy) | — | ~same RPS | — |
| Direct -> Ollama | 368.5 | 400.5 | 361.9 | 10.89 | 0 |
| Via Meridian | 375.5 | 401.4 | 367.1 | 10.66 | 0 |
| **Delta / ratio** | **7.0 ms** | **0.9 ms** | **5.2 ms** | **-2.2%** | — |

Under concurrency, engine queueing dominates; Meridian RPS tracks direct within ~2%.
### Concurrent load (`n=80`, `concurrency=8`)

| Path | p50 (ms) | p95 (ms) | mean (ms) | RPS | errors |
|------|----------|----------|-----------|-----|--------|
| Direct -> Ollama | 243.0 | 321.5 | 252.9 | 30.44 | 0 |
| Via Meridian | 247.2 | 337.8 | 257.4 | 29.92 | 0 |
| **Delta / ratio** | **4.1 ms** | **16.3 ms** | **4.6 ms** | **-1.7%** | — |

The concurrency runs show no material throughput loss through Meridian. The
concurrency-8 p95 increase is more variable than the serial and concurrency-4
runs, so it should be treated as a host and engine observation rather than a
gateway capacity limit. Repeat this matrix on the target deployment hardware
before making capacity commitments.

### Functional proof (same stack)

`scripts/smoke_test.py --url http://127.0.0.1:18080 --model qwen2.5:0.5b` — pass
(`/meridian/status`, `/meridian/version` **0.9.3**, non-stream + stream/`[DONE]`,
`x-meridian-backend=ollama-4070`).
The v0.12.0 profile passed direct models, non-stream, and stream checks, then
passed gateway status/version/models, non-stream, stream/`[DONE]`, and required
`x-request-id` / `x-meridian-backend` header checks.

### How to interpret for ~1000 users

Expand Down
96 changes: 96 additions & 0 deletions docs/REAL_ENGINE_VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Real-engine release validation

Meridian release candidates are validated against live Ollama and vLLM servers,
not only the in-process mock backend. The validation harness checks the backend
first, starts a disposable Meridian process, runs the existing gateway smoke
test, and records a direct-versus-gateway benchmark as JSON.

## Validation matrix

| Engine | Runtime | Model | Status for v0.12.0 | Evidence |
|--------|---------|-------|--------------------|----------|
| Ollama | 0.31.1 | `qwen2.5:0.5b` | Passed at concurrency 1, 4, and 8 | [`serial`](./validation/ollama-v0.12.0.json), [`c4`](./validation/ollama-v0.12.0-c4.json), [`c8`](./validation/ollama-v0.12.0-c8.json) |
| vLLM | `vllm/vllm-openai:v0.10.2` | `Qwen/Qwen2.5-0.5B-Instruct` | Pending local GPU run | Evidence added after a passing run |

The vLLM profile pins model revision
`7ae557604adf67be50417f59c2c2f167def9a775` so a later model update cannot
silently change release evidence.

## Checks performed

Each profile verifies:

1. Direct `GET /v1/models` returns at least one model.
2. Direct non-stream chat returns at least one choice.
3. Direct streaming chat ends with `data: [DONE]`.
4. Meridian starts from a generated, isolated configuration.
5. `scripts/smoke_test.py` passes models, sync, stream, and response-header checks.
6. `scripts/bench_overhead.py` completes with no direct or gateway errors.
7. Evidence records Meridian, engine, Python, platform, GPU, and benchmark details.

Prompts and generated text are not written to the evidence file.

## Ollama

Prerequisites are Ollama, `curl`, and a Meridian development installation. The
profile uses an existing Ollama server when available. Otherwise, it starts one
for the validation and stops it afterward.

```bash
python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
PYTHON=.venv/bin/python sh scripts/validate_ollama.sh
```

Override the defaults with environment variables:

```bash
MODEL=llama3.2:3b REQUESTS=20 CONCURRENCY=1 \
OUTPUT=/tmp/ollama-validation.json \
PYTHON=.venv/bin/python sh scripts/validate_ollama.sh
```

## vLLM

Prerequisites are Docker, the NVIDIA Container Toolkit, an NVIDIA GPU, `curl`,
and enough disk space for the pinned image and model. The profile removes its
container on exit and reuses the host Hugging Face cache.

```bash
PYTHON=.venv/bin/python sh scripts/validate_vllm.sh
```

Useful overrides for constrained GPUs:

```bash
GPU_MEMORY_UTILIZATION=0.65 MAX_MODEL_LEN=2048 \
PYTHON=.venv/bin/python sh scripts/validate_vllm.sh
```

## Generic OpenAI-compatible backend

For an already-running server, call the common harness directly:

```bash
.venv/bin/python scripts/validate_real_backend.py \
--engine custom \
--engine-version 1.0.0 \
--backend-url http://127.0.0.1:8000 \
--model my-model \
--output /tmp/custom-validation.json
```

Use a free `--gateway-port` if port `18080` is occupied.

## Release procedure

1. Check out the exact release tag on the GPU validation host.
2. Run both profiles without changing their pinned defaults.
3. Confirm every check is `passed` and both benchmark error counts are zero.
4. Review the environment metadata and benchmark values for obvious anomalies.
5. Commit the evidence files with the release documentation.

Latency is informational because it varies by host, engine state, thermals, and
driver version. Functional failures and non-zero request errors block release;
an absolute latency threshold does not.
52 changes: 52 additions & 0 deletions docs/validation/ollama-v0.12.0-c4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"schema_version": 1,
"recorded_at": "2026-07-30T17:56:45.403619+00:00",
"meridian_version": "0.12.0",
"engine": "ollama",
"engine_version": "0.31.1",
"backend_url": "http://127.0.0.1:11434",
"model": "qwen2.5:0.5b",
"model_revision": null,
"host": {
"platform": "Linux-7.0.0-28-generic-x86_64-with-glibc2.39",
"python": "3.12.11",
"gpu": {
"name": "NVIDIA GeForce RTX 4060 Laptop GPU",
"memory_mib": "8188",
"driver": "580.159.03"
}
},
"checks": {
"direct_models": "passed",
"direct_chat": "passed",
"direct_stream": "passed",
"gateway_smoke": "passed"
},
"benchmark": {
"mode": "external",
"backend_url": "http://127.0.0.1:11434",
"gateway_url": "http://127.0.0.1:18080",
"requests": 40,
"concurrency": 4,
"direct": {
"n": 40,
"p50_ms": 368.46687649995147,
"p95_ms": 400.45164840039433,
"p99_ms": 409.4095587598713,
"mean_ms": 361.8813320751542,
"rps": 10.89368797209658,
"errors": 0
},
"via_meridian": {
"n": 40,
"p50_ms": 375.4659959995479,
"p95_ms": 401.36569595015317,
"p99_ms": 406.304408490123,
"mean_ms": 367.06462375004776,
"rps": 10.656981113434352,
"errors": 0
},
"overhead_p50_ms": 6.999,
"overhead_p95_ms": 0.914
}
}
52 changes: 52 additions & 0 deletions docs/validation/ollama-v0.12.0-c8.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"schema_version": 1,
"recorded_at": "2026-07-30T17:57:42.790348+00:00",
"meridian_version": "0.12.0",
"engine": "ollama",
"engine_version": "0.31.1",
"backend_url": "http://127.0.0.1:11434",
"model": "qwen2.5:0.5b",
"model_revision": null,
"host": {
"platform": "Linux-7.0.0-28-generic-x86_64-with-glibc2.39",
"python": "3.12.11",
"gpu": {
"name": "NVIDIA GeForce RTX 4060 Laptop GPU",
"memory_mib": "8188",
"driver": "580.159.03"
}
},
"checks": {
"direct_models": "passed",
"direct_chat": "passed",
"direct_stream": "passed",
"gateway_smoke": "passed"
},
"benchmark": {
"mode": "external",
"backend_url": "http://127.0.0.1:11434",
"gateway_url": "http://127.0.0.1:18080",
"requests": 80,
"concurrency": 8,
"direct": {
"n": 80,
"p50_ms": 243.04335550004907,
"p95_ms": 321.52103000053097,
"p99_ms": 409.1220455409346,
"mean_ms": 252.8889539500824,
"rps": 30.441044313314393,
"errors": 0
},
"via_meridian": {
"n": 80,
"p50_ms": 247.18422350088076,
"p95_ms": 337.83950110109794,
"p99_ms": 430.61965636992664,
"mean_ms": 257.4414281375084,
"rps": 29.92078287226416,
"errors": 0
},
"overhead_p50_ms": 4.141,
"overhead_p95_ms": 16.318
}
}
Loading
Loading