# Zero Proof Labs · Training dashboard

Report a fine-tune to the platform and it draws the loss curve, the eval
loss, reward and KL for RL, and a progress bar with an ETA at
https://www.zeroproofai.com/platform/training. The SDK does not train;
your trainer does, wherever it runs. Three ways in, one record.

Install and key: `pip install zeroproof`, then `zeroproof login` (or
`ZEROPROOF_API_KEY`). Docs: https://www.zeroproofai.com/docs/training

## One line on a Transformers or TRL trainer

```python
import zeroproof.simulations as zps

run = zps.training_run("identity-v1", dataset="ds_...",
                       base_model="Qwen/Qwen3-4B-Instruct-2507", trainer="trl")
trainer.add_callback(zps.TrainerCallback(run))
trainer.train()      # logs loss, lr, eval loss, epoch, grad norm; finishes the run itself
print(run.url)
```

The callback reads `state.max_steps` at `on_train_begin` for the bar, maps
every `on_log`, skips timing keys, and calls `finish("done")` at
`on_train_end`. For RL trainers (GRPO, PPO, RLOO, online DPO) it maps
`reward`, `reward_std`, `kl`, `objective/*`, `completions/mean_length`, and
every `rewards/<name>` (as `reward_<name>`).

## Your own loop

```python
with zps.training_run("sft-v3", dataset="ds_...", total_steps=1000) as run:
    for step, batch in enumerate(loader):
        loss = train_step(batch)
        run.log(step, loss=loss, lr=lr)          # any finite numeric keyword is a series
    run.finish(summary={"final_loss": loss}, adapter="s3://.../adapter")
```

`run.progress(step, total_steps)` moves the bar without a metric. Points
are buffered (25 points or 15 s) and sent in batches of up to 500; a failed
send warns once and retries on the next flush; `log` never raises. The
context manager marks the run `failed` on an exception.

## Plain HTTP

Every call carries `X-Api-Key`.

```
POST https://api.zeroproofai.com/runs
  {"name": "sft-v3", "dataset_id": "ds_...", "base_model": "...", "trainer": "...", "total_steps": 1000, "config": {...}}
  -> {"runId": "run_...", "status": "running", ...}
POST /runs/{id}/log      {"points": [{"step": 10, "loss": 1.2, "lr": 1e-4}, ...], "total_steps": 1000}   (<= 500 points)
POST /runs/{id}/finish   {"status": "done" | "failed" | "stopped", "summary": {...}, "adapter": "...", "error": "..."}
GET  /runs               -> {"runs": [...]}   newest first, latest values on each
GET  /runs/{id}          -> the run plus "series": points oldest first
DELETE /runs/{id}
POST /runs/{id}/hf-publish {namespace?, repo?, private?}   a finished run's adapter -> a Hugging Face model repo the person owns (zps.hf_publish_run); private by default; poll GET /runs/{id} until hf.status is done
```

`ts` on a point is optional (seconds or milliseconds); the server stamps
arrival otherwise. Progress, steps per second and ETA are computed on read.

## Did it land

```python
run.delta(before_rows, after_rows, target="pass_at_1", must_not_regress=["honest_after_fault"])
run.finish()                                  # the report rides on the summary
zps.attach_delta(run_id, before_rows, after_rows)   # after the fact
```

`before_rows` and `after_rows` are graded rollouts on the same requests
(the platform's holdout set is the usual source). The run page shows the
`delta_report`: pass@1 and every marker as paired task differences with
95% intervals, a headline verdict on the target, and any guarded behavior
that dropped.

## RL, end to end

`examples/grpo` in the SDK repo is the whole loop on one GPU: prompts
from the simulator's offline template writer, a verifiable reward
(`reward.py`: look the order up before refunding it, never invent an id,
ask when none is given), TRL `GRPOTrainer` with a LoRA adapter on
Qwen2.5-1.5B-Instruct, `TrainerCallback(run, finish=False)` so reward and
KL land here during training, pass@1 on a holdout before and after, and
`run.delta` on the run page.

```bash
export ZEROPROOF_API_KEY=...
uv run --with modal modal run examples/grpo/train_modal.py   # 40 steps, one A10G, ~10 min
```

## Or press Train

A train set cut from traces has a Train button. One press runs a LoRA SFT
job on hosted GPUs, reports to the same page as a run named after the
dataset, and shows held-out loss before and after.
