From f9a5c231ed856143e309cf0da5199553ea16f1be Mon Sep 17 00:00:00 2001 From: Lumpiasty Date: Thu, 10 Sep 2026 17:06:01 +0200 Subject: [PATCH] scripts: add the RX 580 benchmark harness Encodes the production config, fixed corpus slices, repeat/median discipline and the noise floor, so the measurement method does not have to be rediscovered each time. Runs the corpus prefill test through llama-server and the llama-bench sweep as a controlled cross-check, with interleaved A/B. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PZz44SLQvTXMyWGio6t9DZ --- scripts/rx580-bench/README.md | 200 ++++++++++++++++++++++++++ scripts/rx580-bench/bench.sh | 167 +++++++++++++++++++++ scripts/rx580-bench/lbsweep.sh | 69 +++++++++ scripts/rx580-bench/ppbench.py | 239 +++++++++++++++++++++++++++++++ scripts/rx580-bench/run.sh | 91 ++++++++++++ scripts/rx580-bench/summarize.py | 95 ++++++++++++ 6 files changed, 861 insertions(+) create mode 100644 scripts/rx580-bench/README.md create mode 100755 scripts/rx580-bench/bench.sh create mode 100755 scripts/rx580-bench/lbsweep.sh create mode 100755 scripts/rx580-bench/ppbench.py create mode 100755 scripts/rx580-bench/run.sh create mode 100755 scripts/rx580-bench/summarize.py diff --git a/scripts/rx580-bench/README.md b/scripts/rx580-bench/README.md new file mode 100644 index 000000000..f029c6a8f --- /dev/null +++ b/scripts/rx580-bench/README.md @@ -0,0 +1,200 @@ +# rx580-bench + +Prompt-processing benchmark harness for the RX 580 (Polaris) serving pod. + +It exists so that no future session has to rediscover how to measure this +machine. It encodes the production config, fixed corpus slices, the repeat +and median discipline, and the noise floor. + +## What it measures + +Two things, deliberately: + +1. **Corpus prompt processing** (the number we actually care about). Real + Polish prose from `pan-tadeusz.txt` pushed through `llama-server`'s + `/completion` endpoint with `n_predict: 1`, `cache_prompt: false`. This + mimics agentic tool-result parsing: a big blob of real text arriving cold. + Reported as `timings.prompt_per_second` at prompt lengths of about 4k, 16k + and 32k tokens. +2. **`llama-bench` sweep** (the controlled cross-check). Synthetic random + tokens, `pp2048`, `pp8192`, `tg32`. This is what prior work on this box + recorded, so it is the continuity metric. + +They do not measure the same thing and they do not agree. `llama-bench` +pp2048 is a cold depth-0 batch; the corpus numbers include the cost of a +growing KV cache, so they fall off with prompt length. Both are useful. + +## How to run it + +One command from the workstation: + +``` +./scripts/rx580-bench/run.sh --build /root/arms/new-clean/build --label new-clean +``` + +Interleaved A/B, which is the only valid way to compare two builds: + +``` +./scripts/rx580-bench/run.sh \ + --build /root/arms/new-clean/build --label new-clean \ + --build-b /root/arms/new-fork/build --label-b new-fork \ + --rounds 3 +``` + +Summarize whatever has accumulated: + +``` +./scripts/rx580-bench/run.sh --summarize +``` + +`run.sh` copies the harness onto the pod and execs it there. The wrapper is +thin on purpose: all the methodology lives in the pod-side scripts, so it can +also be driven directly on the pod if kubectl is inconvenient. + +The run is detached on the pod and then tailed, so Ctrl-C on the wrapper does +not orphan a half-finished run. `bench.sh` restarts `llama-swap` from an EXIT +trap on success, failure and interrupt alike. + +### Pod access + +``` +export KUBECONFIG=/home/user/Projects/klaster/talos/generated/kubeconfig +kubectl -n llama exec -i deploy/supervisord -- sh -c '' +``` + +The pod has `curl` and `python3` but **no `jq`**; the harness uses python3 for +all JSON. + +## Files + +| file | side | what | +|---|---|---| +| `run.sh` | workstation | one-command wrapper; installs and execs the rest | +| `bench.sh` | pod | orchestrator: stops llama-swap, waits for idle GPU, runs both harnesses, restarts llama-swap via trap | +| `ppbench.py` | pod | the corpus prompt-processing harness | +| `lbsweep.sh` | pod | the llama-bench sweep | +| `summarize.py` | pod | turns `results.txt` into a median/min-max table with delta significance | + +Results append to `/root/bench/results.txt` as parseable `RESULT` / +`SUMMARY` / `LBRESULT` lines. Server logs land in `/root/bench/server-*.log`. + +## The config it assumes + +Production serving config, matching `/root/config.yaml`: + +``` +-t 6 -ngl 99 --n-cpu-moe 40 -b 2048 -ub 2048 -fa 1 --no-mmap +--ctx-size 40960 --no-warmup +``` + +Model: + +``` +/root/.cache/huggingface/hub/models--unsloth--Qwen3.6-35B-A3B-GGUF/snapshots/a483e9e6cbd595906af30beda3187c2663a1118c/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf +``` + +Qwen3.6-35B-A3B, 20.8 GiB, 40 layers, 256 experts of which 8 are used. +Machine: AMD RX 580 8 GB (Polaris/GCN, RADV, no fp16 accel), PCIe 3.0 x16, +12 CPU cores, 62 GB RAM. + +The `llama-bench` equivalent of that config is: + +``` +-t 6 -ngl 99 -ncmoe 40 -b 2048 -ub 2048 -fa 1 -mmp 0 -p 2048,8192 -n 32 -r 3 +``` + +## The corpus slices are constants, not recalibrated + +`pan-tadeusz.txt` is 447334 chars / 482907 bytes of Polish text. Against this +model's tokenizer it runs **2.6702 chars/token**, and `/completion` reports +`prompt_n` identical to `/tokenize` (BOS offset 0). + +The slices are `corpus[0:NCHARS]` and are baked into `ppbench.py`: + +| target tokens | chars | utf-8 bytes | +|---|---|---| +| 4096 | 10776 | 11608 | +| 16384 | 43858 | 47175 | +| 32768 | 87165 | 93843 | + +These hit the target `prompt_n` exactly. Every build is therefore measured on +byte-identical input. `ppbench.py` warns loudly if the reported `prompt_n` +ever stops matching the target, which is the signal that the table is stale. +Only recalibrate (`ppbench.py