server.py, the inference server. It reads a batch of prompts per line on stdin, and decodes the
model of model.py greedily. The baseline recomputes the whole context for each new token.
The evaluate command
evaluate.py starts server.py in its own process and load-tests it from outside. It writes one line for each rung of
the evaluation ladder, then one for the full benchmark:
seconds is the wall time of
the batches, and tokens counts every token served, so tokens ÷ seconds is the throughput at that concurrency. match is
the share of the checked tokens that equal the reference decoder’s: the quality. For a real model, start your server
(vLLM, SGLang or your own) in the evaluate command, wait until it is healthy, and drive it with your load generator at
a fixed concurrency. Score quality on a fixed prompt set against the outputs of a reference build. The lines stay the
same.
The metrics
- Throughput is a ratio. The evaluate command reports
tokensandseconds, and piloop computestokens_per_s. A rung that runs for a fixed time serves a different number of tokens on each candidate, and the ratio still compares them. - Noise. A benchmark of times is noisy.
repeats = 3runs each candidate three times, each next to a run of its bar on the same worker, and the verdict compares the ratio of each pair. The tolerances are wider than the noise:p95_msmay get 25% worse, and the short load test stops a candidate only when its median latency is 50% worse than the bar’s. - Quality.
matchmay drop bymarginfor each keep, and never belowfloor. An approximation, such as a lower precision, that changes a few tokens can keep. One that changes many cannot. - The ladder. The smoke rung stops a server that gives wrong tokens in seconds. The load rung stops a slow one before the full benchmark. A stop names a reported metric, never a ratio.
Run the benchmark on GPUs
On a real model, the agents run on the server and the evaluations on GPU workers. In the server’sconfig.toml:
repeats measurement runs on one worker, so both sides share a GPU. Workers shows the
network, the CAS and the tokens that the workers need.
Write plugins for it
A plugin is a Python package that adds providers to piloop. The packageexamples/plugins/inference adds three:
Import the contracts, their types and the built-in providers from
piloop.capabilities. A provider that reads the
candidates or the measurements imports their tables from the component that owns them,
piloop.plugins.campaign_type.kit.candidates.tables and piloop.plugins.campaign_type.kit.evaluation.tables.
piloop.db.models has core’s tables, such as Campaign.
Install the package next to piloop, and check that piloop finds it:
piloop.toml, as below, and run piloop init again. piloop plugins then names the campaign’s
selection:
A fact source: what the GPUs cost
gpu_hours sums the wall time of the campaign’s evaluations, and prices it:
pyproject.toml
piloop_inference/gpu_hours.py
hold_at then holds the campaign at a GPU budget:
piloop status shows the spend against the budget, at the end of the progress line: gpu_usd 0.1043/50.
A verdict rule: only real speedups
With three pairs, a gain of a few percent can be noise. Each keep moves the head, and every later run must beat it.min_speedup calls the built-in rule, and makes a keep below the fraction interesting:
pyproject.toml
piloop_inference/min_speedup.py
A section: latency for the agents
The verdict judges throughput.latency shows each implementer what earlier candidates did to the tail:
pyproject.toml
piloop_inference/latency.py
SHOWN_IN puts it in piloop status and in the brief of each run. Its table changes its parameters:
Test the plugins
A verdict rule is pure: test it with a case, without piloop running:piloop serve --check to check that they load, and
piloop status to see their output.