2026-09-17 03:02:00 +00:00
|
|
|
|
# festbench-web
|
|
|
|
|
|
|
|
|
|
|
|
Web viewer for [FestBench](../llm_benchmark) results — LLM benchmark datapoints
|
|
|
|
|
|
submitted by the `festbench` CLI, displayed in a fast, sortable table.
|
|
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
Stack: Laravel 12 · Livewire 4 + Volt · Flux UI (Pro) · FrankenPHP · PostgreSQL 17
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
## Architecture (v2)
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
**One table per bench suite**, monthly-partitioned on Postgres:
|
|
|
|
|
|
|
|
|
|
|
|
| Table | Suite-specific columns |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `softbench_results` | section, tool_calls |
|
|
|
|
|
|
| `infrabench_results` | kind (recall/constraint) |
|
|
|
|
|
|
| `prosebench_results` | section, checks_passed/total, judge, prefill, prompt_tokens |
|
|
|
|
|
|
| `psychbench_results` | section, checks_passed/total |
|
|
|
|
|
|
| `scenario_runs` | scenario, outcome, turns, commands, submissions, rejected, t_pass, verdicts |
|
|
|
|
|
|
|
|
|
|
|
|
All share the core metric columns (ok, decode, user_tps, ttft, t_answer, wall,
|
|
|
|
|
|
reasoning, visible, total, finish) plus a `payload JSONB` preserving the
|
|
|
|
|
|
complete source row — future festbench fields need no migration.
|
|
|
|
|
|
|
|
|
|
|
|
**Why:** each suite has a different shape; partitioning keeps appends on a hot
|
|
|
|
|
|
partition; decommissioning a bench detaches whole partitions; and Postgres
|
|
|
|
|
|
inlines the `all_results` UNION view so the combined home query uses each
|
|
|
|
|
|
suite table's indexes directly.
|
|
|
|
|
|
|
|
|
|
|
|
### Suite registry — decommissioning & adding benches
|
|
|
|
|
|
|
|
|
|
|
|
`config/festbench.php` is the single source of truth: suite → model, `active`
|
|
|
|
|
|
flag, `in_all_results` flag.
|
|
|
|
|
|
|
|
|
|
|
|
- **Decommission a bench**: flip `active => false`, run
|
|
|
|
|
|
`php artisan festbench:suites:refresh`. The suite vanishes from the home
|
|
|
|
|
|
table and suite filter; historical data stays queryable and old run-detail
|
|
|
|
|
|
links keep rendering.
|
|
|
|
|
|
- **Add a bench**: migration + model, registry entry, refresh.
|
|
|
|
|
|
|
|
|
|
|
|
### Scale design (built lightly, ready for 10k submissions/day)
|
|
|
|
|
|
|
|
|
|
|
|
- `all_results` view — combined home table; suite-filtered queries bypass it
|
|
|
|
|
|
and hit the concrete table's indexes.
|
|
|
|
|
|
- `home_snapshot` — flat precomputed default view, refreshed every 5 minutes
|
|
|
|
|
|
(`festbench:snapshot`). At high traffic the request path never runs a UNION.
|
|
|
|
|
|
- TTL staleness caching (60–300s), **never event-flush** — at high submission
|
|
|
|
|
|
rates event flushes would storm the cache; benchmark data a couple of minutes
|
|
|
|
|
|
old is correct.
|
|
|
|
|
|
- Guest pages carry `Cache-Control: public, s-maxage=…, stale-while-revalidate`
|
|
|
|
|
|
so a CDN absorbs the bulk of views. Authenticated views are `private,
|
|
|
|
|
|
no-store` (they embed the user's private runs).
|
|
|
|
|
|
- Queue-based CLI ingestion + Redis driver: documented as the required shape
|
|
|
|
|
|
for the future submit endpoint (auth session is the prerequisite).
|
|
|
|
|
|
- Retention: everything is kept forever.
|
|
|
|
|
|
- Monthly partitions exist for ±1 year at migration time; a scheduled
|
|
|
|
|
|
partition-creation command is the follow-up when real submission volume
|
|
|
|
|
|
approaches the horizon.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
## Visibility rules
|
|
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
Runs are public by default. Guests see public runs only; authenticated users
|
|
|
|
|
|
additionally see their own private runs. Festbench-submitted metrics are
|
|
|
|
|
|
immutable — enforced by a model guard and covered by tests; owners can only
|
|
|
|
|
|
add notes and toggle visibility.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
## Local development
|
|
|
|
|
|
|
|
|
|
|
|
Requirements: `frankenphp` binary on PATH, Node 20+, Docker/Podman.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-18 19:06:15 +00:00
|
|
|
|
docker compose -f docker/develop.compose.yaml up -d # Postgres 17 on 127.0.0.1:5432
|
2026-09-17 03:02:00 +00:00
|
|
|
|
./dev.sh # frankenphp :8080 + vite HMR
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Composer runs through frankenphp's embedded PHP (the host php.ini disables
|
|
|
|
|
|
most extensions):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
frankenphp php-cli $(pwd)/vendor/bin/composer install
|
|
|
|
|
|
frankenphp php-cli $(pwd)/vendor/bin/pest
|
|
|
|
|
|
frankenphp php-cli $(pwd)/vendor/bin/pint
|
|
|
|
|
|
frankenphp php-cli artisan <command>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
### Importing festbench data
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# everything from a local benchmarks.db
|
|
|
|
|
|
php artisan festbench:import results/benchmarks.db --user=you@example.com
|
|
|
|
|
|
|
|
|
|
|
|
# one session, surgically, purging existing data first
|
|
|
|
|
|
php artisan festbench:import results/benchmarks.db \
|
|
|
|
|
|
--user=you@example.com --label="deterministic sampling, repeat 1" --fresh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`--label` filters by the run's label; `--fresh` deletes all existing benchmark
|
|
|
|
|
|
data first; rows route to per-suite tables via the registry; unknown suites
|
|
|
|
|
|
keep their run but warn about skipped rows.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
## Production (Coolify)
|
|
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
Build pack: **Dockerfile**. Single FrankenPHP process — no nginx, no php-fpm,
|
|
|
|
|
|
no supervisor.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
- 3 stages: composer (Flux Pro auth as a BuildKit **secret**), node assets,
|
|
|
|
|
|
final `dunglas/frankenphp:1.12-php8.5` + `install-php-extensions pdo_pgsql
|
|
|
|
|
|
pgsql intl zip opcache`.
|
|
|
|
|
|
- Entrypoint: `migrate --force` (retries) → `festbench:suites:refresh` →
|
|
|
|
|
|
`festbench:snapshot` → `frankenphp run`.
|
|
|
|
|
|
- Caddyfile listens on `{$SERVER_NAME::8080}`; Coolify maps its port.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
Secrets/env to set in Coolify:
|
|
|
|
|
|
|
|
|
|
|
|
| Variable | Notes |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `APP_KEY` | generate once, keep stable |
|
2026-09-18 19:06:15 +00:00
|
|
|
|
| `DB_HOST` / `DB_PORT` / `DB_DATABASE` / `DB_USERNAME` / `DB_PASSWORD` | the nearby Postgres |
|
|
|
|
|
|
| `SESSION_DRIVER=database`, `CACHE_STORE=database` | no redis needed yet |
|
|
|
|
|
|
| `FESTBENCH_HOME_CACHE_TTL` | seconds; default 300 |
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
Flux Pro authentication: BuildKit secret `composer_auth` (contents = the
|
|
|
|
|
|
project's `auth.json`). The license key is never committed.
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
Health check: `GET /up`.
|
|
|
|
|
|
|
|
|
|
|
|
## Submitting results (CLI integration — separate session)
|
|
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
The schema mirrors festbench's output exactly; `festbench:import` is the
|
|
|
|
|
|
reference implementation of the mapping. The authed ingestion endpoint for
|
|
|
|
|
|
`festbench --submit` is future work (queue-based, Redis-backed at volume).
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
## Notes
|
2026-09-17 03:02:00 +00:00
|
|
|
|
|
2026-09-18 19:06:15 +00:00
|
|
|
|
- Scheduler must run in production for `festbench:snapshot` (Coolify's
|
|
|
|
|
|
scheduled task or `schedule:work` in a sidecar when needed).
|
|
|
|
|
|
- Worker mode toggle: `FRANKENPHP_CONFIG="worker …"` — documented option, off
|
|
|
|
|
|
by default.
|
|
|
|
|
|
- Tests run on in-memory SQLite (plain tables); partitioning is Postgres-only.
|