v2.4: per-band scenariobench aggregation, band scenario detail page, nickname badges, orderBy fix
This commit is contained in:
parent
9f9327bb27
commit
06908e252d
6 changed files with 225 additions and 16 deletions
|
|
@ -73,6 +73,15 @@ public function handle(): int
|
||||||
$sessions[$key]['labels']->push($run->label);
|
$sessions[$key]['labels']->push($run->label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scenariobench is aggregated at band level (one row for all
|
||||||
|
// scenario runs in the band) rather than per-run, so collect the
|
||||||
|
// run rows and defer to a second pass below.
|
||||||
|
if ($run->suite === 'scenariobench') {
|
||||||
|
$sessions[$key]['scenario_runs'][] = $run;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$aggs = $this->aggregateRun($run);
|
$aggs = $this->aggregateRun($run);
|
||||||
|
|
||||||
$sessions[$key]['passed'] += $aggs->sum('passed');
|
$sessions[$key]['passed'] += $aggs->sum('passed');
|
||||||
|
|
@ -81,6 +90,20 @@ public function handle(): int
|
||||||
$sessions[$key]['runs'] = $sessions[$key]['runs']->merge($aggs);
|
$sessions[$key]['runs'] = $sessions[$key]['runs']->merge($aggs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second pass: emit one aggregated scenariobench row per band,
|
||||||
|
// covering all scenario runs in that band.
|
||||||
|
foreach ($sessions as $key => $band) {
|
||||||
|
$scenarioRuns = $band['scenario_runs'] ?? [];
|
||||||
|
if (empty($scenarioRuns)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$agg = $this->aggregateScenarioBand($scenarioRuns);
|
||||||
|
$sessions[$key]['passed'] += $agg['passed'];
|
||||||
|
$sessions[$key]['failed'] += $agg['failed'];
|
||||||
|
$sessions[$key]['tokens'] += $agg['total_tokens'];
|
||||||
|
$sessions[$key]['runs'] = $band['runs']->merge([$agg]);
|
||||||
|
}
|
||||||
|
|
||||||
DB::transaction(function () use ($sessions) {
|
DB::transaction(function () use ($sessions) {
|
||||||
DB::table('session_summaries')->truncate();
|
DB::table('session_summaries')->truncate();
|
||||||
|
|
||||||
|
|
@ -234,6 +257,40 @@ private function aggregateRun(object $run)
|
||||||
]]);
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate all scenariobench runs in a band into one summary row.
|
||||||
|
* Aggregates passed/failed/tokens and generated tok/s across every
|
||||||
|
* scenario run that belongs to the band.
|
||||||
|
*
|
||||||
|
* @param array<int, object> $runs
|
||||||
|
*/
|
||||||
|
private function aggregateScenarioBand(array $runs): array
|
||||||
|
{
|
||||||
|
$runPks = array_map(fn ($r) => $r->id, $runs);
|
||||||
|
$first = $runs[0];
|
||||||
|
|
||||||
|
$agg = DB::table('scenario_runs')
|
||||||
|
->whereIn('benchmark_run_id', $runPks)
|
||||||
|
->selectRaw('count(*) filter (where ok) as passed')
|
||||||
|
->selectRaw('count(*) filter (where not ok) as failed')
|
||||||
|
->selectRaw('sum(gen_tokens) as total_tokens')
|
||||||
|
->selectRaw('sum(gen_tokens) / nullif(sum(wall), 0) as gen_tps')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'run_pk' => $first->id,
|
||||||
|
'run_id' => $first->run_id,
|
||||||
|
'suite' => 'scenariobench',
|
||||||
|
'level' => null,
|
||||||
|
'passed' => (int) ($agg->passed ?? 0),
|
||||||
|
'failed' => (int) ($agg->failed ?? 0),
|
||||||
|
'avg_user_tps' => null,
|
||||||
|
'weighted_user_tps' => null,
|
||||||
|
'gen_tps' => $this->roundOrNull($agg->gen_tps),
|
||||||
|
'total_tokens' => (int) ($agg->total_tokens ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function emptyAgg(object $run): array
|
private function emptyAgg(object $run): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,11 @@ public function suiteResults(?string $level = null): Collection
|
||||||
|
|
||||||
$relation = Suites::runRelation($this->suite);
|
$relation = Suites::runRelation($this->suite);
|
||||||
|
|
||||||
|
$orderCol = $this->suite === 'scenariobench' ? 'scenario' : 'task';
|
||||||
|
|
||||||
return $this->{$relation}()
|
return $this->{$relation}()
|
||||||
->when($level !== null && $level !== '', fn ($q) => $q->where('level', $level))
|
->when($level !== null && $level !== '', fn ($q) => $q->where('level', $level))
|
||||||
->orderBy('task')
|
->orderBy($orderCol)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,12 @@ private function authorizeOwner(): void
|
||||||
<div>
|
<div>
|
||||||
<flux:heading size="xl">{{ $this->run->suite }} <span class="text-zinc-400">·</span> <span class="font-mono text-lg">{{ $this->run->model_id }}</span></flux:heading>
|
<flux:heading size="xl">{{ $this->run->suite }} <span class="text-zinc-400">·</span> <span class="font-mono text-lg">{{ $this->run->model_id }}</span></flux:heading>
|
||||||
<flux:subheading>
|
<flux:subheading>
|
||||||
@if ($this->run->model_quant)<flux:badge color="zinc" size="xs">{{ $this->run->model_quant }}</flux:badge> @endif
|
@if ($this->run->label)
|
||||||
@if ($this->run->engine)<flux:badge color="sky" size="xs">{{ $this->run->engine }}</flux:badge> @endif
|
<flux:badge color="amber" size="xs">{{ $this->run->label }}</flux:badge>
|
||||||
@if ($this->run->label)<flux:badge color="amber" size="xs">{{ $this->run->label }}</flux:badge> @endif
|
@else
|
||||||
|
@if ($this->run->model_quant)<flux:badge color="zinc" size="xs">{{ $this->run->model_quant }}</flux:badge> @endif
|
||||||
|
@if ($this->run->engine)<flux:badge color="sky" size="xs">{{ $this->run->engine }}</flux:badge> @endif
|
||||||
|
@endif
|
||||||
@if ($this->run->visibility === 'private')<flux:badge color="rose" size="xs">{{ __('Private') }}</flux:badge> @endif
|
@if ($this->run->visibility === 'private')<flux:badge color="rose" size="xs">{{ __('Private') }}</flux:badge> @endif
|
||||||
{{ optional($this->run->ran_at)->format('Y-m-d H:i') }}
|
{{ optional($this->run->ran_at)->format('Y-m-d H:i') }}
|
||||||
</flux:subheading>
|
</flux:subheading>
|
||||||
|
|
|
||||||
140
resources/views/livewire/sessions/scenarios.blade.php
Normal file
140
resources/views/livewire/sessions/scenarios.blade.php
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\BenchmarkRun;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
new #[Layout('components.layouts.app')] class extends Component {
|
||||||
|
public ?int $sessionId = null;
|
||||||
|
|
||||||
|
public function mount(int $session): void
|
||||||
|
{
|
||||||
|
$this->sessionId = $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function band(): object
|
||||||
|
{
|
||||||
|
return DB::table('session_summaries')
|
||||||
|
->where('id', $this->sessionId)
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->where('visibility', 'public');
|
||||||
|
if (auth()->user() !== null) {
|
||||||
|
$q->orWhere(function ($own) {
|
||||||
|
$own->where('visibility', 'private')
|
||||||
|
->where('user_id', auth()->id());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function runs()
|
||||||
|
{
|
||||||
|
$band = $this->band;
|
||||||
|
|
||||||
|
return BenchmarkRun::where('suite', 'scenariobench')
|
||||||
|
->where('model_id', $band->model_id)
|
||||||
|
->where('model_quant', $band->model_quant)
|
||||||
|
->where('gpu', $band->gpu)
|
||||||
|
->where('gpu_count', $band->gpu_count)
|
||||||
|
->where('user_id', $band->user_id)
|
||||||
|
->where('visibility', $band->visibility)
|
||||||
|
->where('engine', $band->engine)
|
||||||
|
->where(function ($q) use ($band) {
|
||||||
|
$q->whereNull('config_id')
|
||||||
|
->orWhere('config_id', $this->configIdFromName($band->config_name));
|
||||||
|
})
|
||||||
|
->orderBy('ran_at')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function configIdFromName(?string $name): ?int
|
||||||
|
{
|
||||||
|
if ($name === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DB::table('server_configs')->where('name', $name)->value('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function results()
|
||||||
|
{
|
||||||
|
$runIds = $this->runs->pluck('id');
|
||||||
|
|
||||||
|
return DB::table('scenario_runs')
|
||||||
|
->whereIn('benchmark_run_id', $runIds)
|
||||||
|
->orderBy('scenario')
|
||||||
|
->orderBy('benchmark_run_id')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="xl">{{ __('Scenario results') }}</flux:heading>
|
||||||
|
<flux:subheading>
|
||||||
|
@if ($this->band->label)
|
||||||
|
<flux:badge size="xs" color="amber">{{ $this->band->label }}</flux:badge>
|
||||||
|
@endif
|
||||||
|
<span class="text-sm">{{ $this->band->model_id ?? 'unknown model' }}</span>
|
||||||
|
@if ($this->band->model_quant)
|
||||||
|
<flux:badge size="xs" color="zinc">{{ $this->band->model_quant }}</flux:badge>
|
||||||
|
@endif
|
||||||
|
<span class="text-sm text-zinc-500">{{ $this->band->gpu ?? 'unknown GPU' }}</span>
|
||||||
|
</flux:subheading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<flux:button variant="ghost" icon="arrow-left" href="{{ route('home') }}" wire:navigate>{{ __('Sessions') }}</flux:button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<flux:table>
|
||||||
|
<flux:table.columns>
|
||||||
|
<flux:table.column>{{ __('Scenario') }}</flux:table.column>
|
||||||
|
<flux:table.column>{{ __('Level') }}</flux:table.column>
|
||||||
|
<flux:table.column>{{ __('Pass') }}</flux:table.column>
|
||||||
|
<flux:table.column>{{ __('Outcome') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Wall (s)') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Turns') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Commands') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Submissions') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Rejected') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('t first sub') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('t pass') }}</flux:table.column>
|
||||||
|
<flux:table.column class="text-right">{{ __('Gen tokens') }}</flux:table.column>
|
||||||
|
</flux:table.columns>
|
||||||
|
|
||||||
|
<flux:table.rows>
|
||||||
|
@foreach ($this->results as $result)
|
||||||
|
<flux:table.row :key="$result->id">
|
||||||
|
<flux:table.cell>{{ $result->scenario }}</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ $result->level ?? '—' }}</flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
@if ($result->ok)
|
||||||
|
<flux:icon name="check-circle" variant="solid" class="size-4 text-emerald-500" />
|
||||||
|
@elseif ($result->ok === null)
|
||||||
|
<span class="text-zinc-400">—</span>
|
||||||
|
@else
|
||||||
|
<flux:icon name="x-circle" variant="solid" class="size-4 text-red-500" />
|
||||||
|
@endif
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell><flux:badge size="xs" color="{{ $result->outcome === 'passed' ? 'emerald' : 'rose' }}">{{ $result->outcome ?? '—' }}</flux:badge></flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->wall, 1) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->turns) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->commands) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->submissions) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->rejected) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->t_first_sub, 1) }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ $result->t_pass !== null ? number_format($result->t_pass, 1) : '—' }}</flux:table.cell>
|
||||||
|
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->gen_tokens) }}</flux:table.cell>
|
||||||
|
</flux:table.row>
|
||||||
|
@endforeach
|
||||||
|
</flux:table.rows>
|
||||||
|
</flux:table>
|
||||||
|
</div>
|
||||||
|
|
@ -148,22 +148,23 @@ class="w-64"
|
||||||
<flux:badge size="xs" color="zinc">×{{ $session->band->gpu_count }}</flux:badge>
|
<flux:badge size="xs" color="zinc">×{{ $session->band->gpu_count }}</flux:badge>
|
||||||
@endif
|
@endif
|
||||||
<span class="text-sm">{{ $session->band->model_id ?? 'unknown model' }}</span>
|
<span class="text-sm">{{ $session->band->model_id ?? 'unknown model' }}</span>
|
||||||
@if ($session->band->model_quant)
|
@if ($session->band->label)
|
||||||
<flux:badge size="xs" color="zinc">{{ $session->band->model_quant }}</flux:badge>
|
<flux:badge size="xs" color="amber">{{ $session->band->label }}</flux:badge>
|
||||||
@endif
|
@else
|
||||||
@if ($session->band->engine)
|
@if ($session->band->model_quant)
|
||||||
<flux:badge size="xs" color="sky">{{ $session->band->engine }}</flux:badge>
|
<flux:badge size="xs" color="zinc">{{ $session->band->model_quant }}</flux:badge>
|
||||||
@endif
|
@endif
|
||||||
@if ($session->band->config_name)
|
@if ($session->band->engine)
|
||||||
<flux:badge size="xs" color="amber">{{ $session->band->config_name }}</flux:badge>
|
<flux:badge size="xs" color="sky">{{ $session->band->engine }}</flux:badge>
|
||||||
|
@endif
|
||||||
|
@if ($session->band->config_name)
|
||||||
|
<flux:badge size="xs" color="amber">{{ $session->band->config_name }}</flux:badge>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
<flux:spacer />
|
<flux:spacer />
|
||||||
<span class="text-sm text-zinc-500">{{ $session->band->runner_name ?? 'anonymous' }}</span>
|
<span class="text-sm text-zinc-500">{{ $session->band->runner_name ?? 'anonymous' }}</span>
|
||||||
<span class="text-xs text-zinc-400">{{ optional($session->band->ran_at)->format('M j Y') }}</span>
|
<span class="text-xs text-zinc-400">{{ optional($session->band->ran_at)->format('M j Y') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@if ($session->band->label)
|
|
||||||
<div class="px-4 pt-2 text-xs text-zinc-500">{{ $session->band->label }}</div>
|
|
||||||
@endif
|
|
||||||
{{-- Column titles aligned over the suite rows (md+ only) --}}
|
{{-- Column titles aligned over the suite rows (md+ only) --}}
|
||||||
<div class="hidden px-4 pt-2 md:flex flex-wrap items-center gap-x-4 gap-y-1">
|
<div class="hidden px-4 pt-2 md:flex flex-wrap items-center gap-x-4 gap-y-1">
|
||||||
<span class="w-24 text-[10px] font-semibold uppercase tracking-wider text-zinc-400">{{ __('Benchmark') }}</span>
|
<span class="w-24 text-[10px] font-semibold uppercase tracking-wider text-zinc-400">{{ __('Benchmark') }}</span>
|
||||||
|
|
@ -174,7 +175,12 @@ class="w-64"
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||||
@foreach ($session->suites as $row)
|
@foreach ($session->suites as $row)
|
||||||
<a href="{{ route('runs.show', $row->run_id) }}{{ $row->level && $row->suite === 'depthsweep' ? '?level='.$row->level : '' }}" wire:navigate
|
@php
|
||||||
|
$detailUrl = $row->suite === 'scenariobench'
|
||||||
|
? route('sessions.scenarios', $session->band->id)
|
||||||
|
: route('runs.show', $row->run_id) . ($row->level && $row->suite === 'depthsweep' ? '?level=' . $row->level : '');
|
||||||
|
@endphp
|
||||||
|
<a href="{{ $detailUrl }}" wire:navigate
|
||||||
class="flex flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2.5 hover:bg-zinc-50 dark:hover:bg-zinc-800/60">
|
class="flex flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2.5 hover:bg-zinc-50 dark:hover:bg-zinc-800/60">
|
||||||
<flux:badge size="xs">{{ $row->suite }}</flux:badge>
|
<flux:badge size="xs">{{ $row->suite }}</flux:badge>
|
||||||
@if ($row->level)
|
@if ($row->level)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
Route::livewire('results', 'results.index')->name('results');
|
Route::livewire('results', 'results.index')->name('results');
|
||||||
|
|
||||||
Route::livewire('runs/{run}', 'runs.show')->name('runs.show');
|
Route::livewire('runs/{run}', 'runs.show')->name('runs.show');
|
||||||
|
Route::livewire('sessions/{session}/scenarios', 'sessions.scenarios')->name('sessions.scenarios');
|
||||||
|
|
||||||
// Device authorization for the festbench CLI (Geekbench-style login flow).
|
// Device authorization for the festbench CLI (Geekbench-style login flow).
|
||||||
Route::livewire('cli/authorize/{code}', 'cli.authorize')
|
Route::livewire('cli/authorize/{code}', 'cli.authorize')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue