ADORSYS-GIS / ADORSYS-GIS/ai-helm

[Ticket]: P1 — raise --max-num-seqs and re-run the sweep (the knee is a config constant, not the GPU)

Đang mở
#1,104 2 bình luận 0 reaction 1 người được giao Được @rustnew nhận Xem trên GitHub
ticket
Ngôn ngữ chính
Go Template
Star
3
Fork
1
Merge trung bình
19 giờ 24 phút
Pull request đã merge (30 ngày)
80

Mô tả

### Type

Performance

### Summary

Raise `--max-num-seqs` on `qwen3-5-2b` and re-measure. The P0 baseline shows the fleet's throughput is capped by that one number, not by the GPU — and there is **6× of latency budget going unused** at the cap.

> 📘 **This ticket is written to be learned from, not just executed.** It explains why each step exists. Ask in the ticket if any step is unclear — an unclear step here is a bug in the ticket, not in you.

### Intent

Prove or disprove one specific claim: *`--max-num-seqs 4` is what limits this fleet, and raising it converts unused latency budget into throughput.*

### Source of truth (links)

- P0 baseline, the measurement this builds on: https://github.com/ADORSYS-GIS/inference-ops/blob/main/docs/benchmarks/2026-09-03-qwen3-5-2b-p0-baseline-sweep.md
- The SLO you are measuring against: `docs/adr/0013-serving-slo.md`
- Why the context window must be capped first: `docs/adr/0011-serve-the-small-model-at-32k.md`
- Every measured number on the fleet: `docs/reference/measured-numbers.md`
- Parent epic: #1103

### Current Behavior

From the P0 sweep — read this table before doing anything, it is the whole argument:

| Concurrency | TTFT p95 | req/s | out tok/s | SLO met |
|---|---|---|---|---|
| 4 | **0.166 s** | 1.162 | 204.9 | 100 % |
| 8 | 3.559 s | 1.164 | 201.1 | 14 % |
| 32 | 24.286 s | 1.157 | 180.5 | 8 % |

**What this says.** Throughput stops improving after concurrency 4 — 1.162, 1.164, 1.162, 1.157 req/s. It is flat. Meanwhile TTFT explodes. That combination means requests 5+ are not being *served slowly*; they are not being served at all until a slot frees. That is **admission queueing**, and `--max-num-seqs 4` is the admission limit.

The give-away is that **TPOT stays flat at ~0.040 s at every level**. Once a request is inside the batch, generation speed does not care how many others are waiting. If the GPU itself were the bottleneck, TPOT would degrade too. It doesn't — so the GPU has room.

And at concurrency 4 the p95 time-to-first-token is 0.166 s against a **1.000 s** SLO. Five sixths of the budget is unspent.

### Expected Behavior

A higher `--max-num-seqs` and a re-run sweep showing more goodput at the same SLO — or evidence that something else binds first, which is an equally good result if it is measured.

### Acceptance Criteria

- [ ] `--max-model-len` capped to **32768** first (ADR-0011). ⚠️ **Do this before raising `--max-num-seqs`.** At the current 262144 window a single request may claim a quarter of the whole KV pool, so raising the sequence limit without capping the window risks the pool running out. Capping first lifts pool-limited concurrency from **3.80× to ~30×**.
- [ ] `--max-num-seqs` raised. Suggested first step **4 → 16**; do not jump straight to 32 — a change you cannot attribute is worth very little.
- [ ] The P0 sweep re-run **unchanged** (same script, same levels, same 200 output tokens) so the two runs are comparable. Changing the workload and the config together makes the result uninterpretable.
- [ ] A new dated benchmark report in `docs/benchmarks/`, following the existing P0 report's shape, with raw evidence committed alongside.
- [ ] `docs/reference/measured-numbers.md` updated with the new goodput.
- [ ] An ADR recording the chosen `--max-num-seqs` and why.
- [ ] If goodput did **not** improve: say so plainly and write down what bound instead. A measured "no improvement" is a completed ticket, not a failed one.

### Out of Scope

- Quantization (FP8 weights, fp8 KV) — later phases of #1103; they change quality and need their own gate.
- Changing the model, the engine image, or `gpuMemoryUtilization`.
- Anything touching LMCache. It is settled for now (`docs/adr/0012-lmcache-is-restart-insurance.md`).
- Gateway-side changes.

### Technical Context

The two settings live together in `ai-helm-values`, `environments/prod/values/inference.yaml`, under the `qwen3-5-2b` entry (`serving.contextSize` and the max-num-seqs setting). **Change both in one PR** — see Constraints.

Useful background on why the KV pool matters: vLLM reserves a pool of GPU memory for the KV cache, sized by `gpuMemoryUtilization` — **not** by `--max-model-len`. On this fleet that pool is **995,932 tokens** (read it yourself from the engine's startup log; do not trust this number, verify it). `--max-model-len` decides how much of that pool one request may consume, so it sets how many requests can coexist. 995,932 ÷ 262,144 ≈ 3.8; 995,932 ÷ 32,768 ≈ 30.

Re-running the sweep:

```bash
POD=$(kubectl --context hetzner-prod -n inference get pods \
-l app.kubernetes.io/name=inference-qwen3-5-2b -o jsonpath='{.items[0].metadata.name}')

# read the pool size for yourself — this is the habit that matters
kubectl --context hetzner-prod -n inference logs "$POD" -c model | grep "GPU KV cache size"

# the same sweep P0 ran
echo '{"levels":[1,2,4,8,16,32,64],"seconds":20,"out_tokens":200,"slo_ttft":1.0,"slo_tpot":0.050}' \
| kubectl --context hetzner-prod -n inference exec -i "$POD" -c model -- \
python3 -c "$(cat docs/benchmarks/evidence/2026-09-03-p0-baseline-sweep/sweep.py)"
```

### Risks

- **This is production.** `qwen3-5-2b-local` is the only self-hosted model the gateway serves, the Deployment is `strategy: Recreate` on a single GPU, and **every config change is a ~3.5 min outage**. Batch your changes into one PR; do not merge one flag at a time.
- The sweep itself **saturates the model for ~3 minutes**. Real traffic queues behind it. Run it deliberately, not during a busy period.
- Raising `--max-num-seqs` too far trades TTFT for throughput. That is exactly the trade the SLO exists to arbitrate — if TTFT p95 crosses 1 s, you have gone too far, and that is a finding worth writing down rather than an embarrassment.
- Expect the in-place restart defect (#1101) if you restart a container by hand rather than deploying.

### Test Plan

1. Read the current pool size and `--max-num-seqs` off the running pod.
2. One PR: `contextSize` → 32768 **and** max-num-seqs → 16. Governance PR template applies.
3. Wait for the rollout (~6–7 min from merge for ArgoCD to poll, then ~3.5 min).
4. Re-read the pool size — confirm max concurrency moved from 3.80× to ~30×.
5. Re-run the sweep with the same script, adding a `64` level since the ceiling should have moved.
6. Compare against the P0 table. Report goodput, and where the new knee is.

### Verification evidence

Numbers to beat, from P0:

```text
goodput 1.162 req/s @ concurrency 4
output throughput 204.9 tok/s
TTFT p95 at knee 0.166 s (budget 1.000 s)
TPOT p95 ~0.040 s (budget 0.050 s)
knee exactly --max-num-seqs 4
```

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.