# 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