Skip to main content
This guide sets up a campaign that makes a model’s serving faster while its quality holds, then adds plugins for that work. Its example serves a small model on a CPU, so it runs on a laptop:
The implementers edit 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:
Each line is a workload of its own, cheapest first:
A rung sends its requests again until it has run for its minimum seconds. A fixed number of requests would take a few milliseconds once the server is a hundred times faster, and noise would decide the score. 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 tokens and seconds, and piloop computes tokens_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 = 3 runs 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_ms may get 25% worse, and the short load test stops a candidate only when its median latency is 50% worse than the bar’s.
  • Quality. match may drop by margin for each keep, and never below floor. 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.
Add ideas, and watch:
The first keep had this verdict:
A later commit was twice as slow as its bar at the load test. Its climb stopped there, and it never ran the full benchmark:

Run the benchmark on GPUs

On a real model, the agents run on the server and the evaluations on GPU workers. In the server’s config.toml:
Each pair of a 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 package examples/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:
Select each provider in 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
The stop rule’s 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:
The fact source and the section read the campaign’s tables. Run piloop serve --check to check that they load, and piloop status to see their output.