festbench-web/tests/Feature/SessionSummaryTest.php
PeterChrz ac941fdd27
fix: switch devlogic/sysops totals from geo-mean to token-weighted average
The headline totals were using a per-task geometric mean, which gave
disproportionate weight to short responses with low effective user_tps
(e.g. 2-token SoftBench answers at ~1.2 tok/s). This produced misleadingly
low Devlogic totals (20.5 for asus_210w) when the displayed suite speeds
were all in the 40-50 tok/s range.

Replace geo-mean accumulators (ln_sum/ln_n) with token-weighted
accumulators (rate_token_sum = sum(user_tps × tokens)). The headline total
is now rate_token_sum / total_tokens, matching the methodology already used
by each displayed suite row.

For ScenarioBench, the suite aggregate gen_tps (sum(gen_tokens)/sum(wall))
is weighted by the suite's total generated tokens, keeping the headline
total consistent with the displayed ScenarioBench row.

Results for the 210W bands:
  asus_210w:    devlogic 20.5 → 47.0, sysops 19.5 → 10.6
  sapphire_210w: devlogic 19.6 → 43.7, sysops 8.3 → 8.8
2026-10-05 00:00:19 -04:00

160 lines
7.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Models\BenchmarkRun;
use App\Models\InfrabenchResult;
use App\Models\ScenarioRun;
use App\Models\SoftbenchResult;
use App\Models\User;
use Illuminate\Support\Facades\Artisan;
test('festbench:summary splits runs by label into separate bands', function () {
$me = User::factory()->create(['name' => 'peterZ']);
$a = BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'session A', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-03 12:00:00',
]);
SoftbenchResult::factory()->create(['benchmark_run_id' => $a->id, 'ok' => true, 'user_tps' => 60.0, 'total' => 100, 'level' => 'off']);
SoftbenchResult::factory()->create(['benchmark_run_id' => $a->id, 'ok' => false, 'user_tps' => 20.0, 'total' => 300, 'level' => 'off']);
$b = BenchmarkRun::factory()->create([
'suite' => 'infrabench', 'label' => 'session A', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-03 12:05:00',
]);
InfrabenchResult::factory()->create(['benchmark_run_id' => $b->id, 'ok' => true, 'user_tps' => 40.0, 'total' => 200, 'level' => 'low']);
$c = BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'session B', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-04 12:00:00',
]);
SoftbenchResult::factory()->create(['benchmark_run_id' => $c->id, 'ok' => true, 'user_tps' => 50.0, 'total' => 50, 'level' => 'medium']);
Artisan::call('festbench:summary');
// Different labels → separate bands.
$bands = DB::table('session_summaries')->orderBy('sequence')->get();
expect($bands)->toHaveCount(2);
$bandA = $bands->firstWhere('label', 'session A');
expect($bandA->runner_name)->toBe('peterZ')
->and($bandA->gpu)->toBe('AMD R9700')
->and($bandA->gpu_count)->toBe(1)
->and($bandA->label)->toBe('session A');
$rowsA = DB::table('session_suite_rows')->where('session_summary_id', $bandA->id)->orderBy('sequence')->get();
expect($rowsA)->toHaveCount(2);
$soft = $rowsA->firstWhere('suite', 'softbench');
expect($soft->passed)->toBe(1)
->and($soft->failed)->toBe(1)
->and($soft->avg_user_tps)->toBe(40.0)
->and($soft->weighted_user_tps)->toBe(30.0)
->and($soft->total_tokens)->toBe(400)
->and($soft->level)->toBe('off');
$infra = $rowsA->firstWhere('suite', 'infrabench');
expect($infra->passed)->toBe(1)->and($infra->level)->toBe('low');
});
test('devlogic and sysops totals are split by suite category', function () {
$me = User::factory()->create();
$soft = BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'dual', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'GPU1', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-03 12:00:00',
]);
SoftbenchResult::factory()->create(['benchmark_run_id' => $soft->id, 'ok' => true, 'user_tps' => 50.0, 'total' => 100, 'level' => 'off']);
SoftbenchResult::factory()->create(['benchmark_run_id' => $soft->id, 'ok' => false, 'user_tps' => 10.0, 'total' => 200, 'level' => 'off']);
$infra = BenchmarkRun::factory()->create([
'suite' => 'infrabench', 'label' => 'dual', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'GPU1', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-03 12:05:00',
]);
InfrabenchResult::factory()->create(['benchmark_run_id' => $infra->id, 'ok' => true, 'user_tps' => 30.0, 'total' => 150, 'level' => 'low']);
$scen = BenchmarkRun::factory()->create([
'suite' => 'scenariobench', 'label' => 'dual', 'model_id' => 'm', 'model_quant' => 'Q5_K_M',
'gpu' => 'GPU1', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public',
'ran_at' => '2026-09-03 12:10:00',
]);
ScenarioRun::factory()->create(['benchmark_run_id' => $scen->id, 'ok' => true, 'gen_tokens' => 5000, 'wall' => 100.0]);
ScenarioRun::factory()->create(['benchmark_run_id' => $scen->id, 'ok' => false, 'gen_tokens' => 3000, 'wall' => 200.0]);
Artisan::call('festbench:summary');
$band = DB::table('session_summaries')->where('label', 'dual')->first();
// Devlogic: softbench only — 2 tasks: user_tps=50 (100 tokens),
// user_tps=10 (200 tokens).
// Token-weighted: (50×100 + 10×200) / (100+200) = 7000/300 ≈ 23.3
expect($band->devlogic_passed)->toBe(1)
->and($band->devlogic_failed)->toBe(1)
->and($band->devlogic_tokens)->toBe(300)
->and($band->devlogic_tps)->toBe(23.3);
// Sysops: infrabench (1 task: user_tps=30, tokens=150 → rate_sum=4500)
// + scenariobench (gen_tps = 8000/300 = 26.67, tokens=8000 →
// rate_sum = 26.67×8000 ≈ 213333.33).
// Total sysops tokens = 150 + 8000 = 8150.
// Token-weighted: (4500 + 213333.33) / 8150 ≈ 26.7
expect($band->sysops_passed)->toBe(2)
->and($band->sysops_failed)->toBe(1)
->and($band->sysops_tokens)->toBe(8150);
expect($band->sysops_tps)->not->toBeNull();
expect($band->sysops_tps)->toBeGreaterThan(20.0)->toBeLessThan(30.0);
});
test('private runs form their own band and are hidden from guests', function () {
$me = User::factory()->create();
BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'pub', 'model_id' => 'm', 'visibility' => 'public',
'gpu' => 'g', 'gpu_count' => 1, 'user_id' => $me->id,
]);
SoftbenchResult::factory()->create(['benchmark_run_id' => BenchmarkRun::latest('id')->first()->id]);
BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'pub', 'model_id' => 'm', 'visibility' => 'private',
'gpu' => 'g', 'gpu_count' => 1, 'user_id' => $me->id,
]);
Artisan::call('festbench:summary');
expect(DB::table('session_summaries')->count())->toBe(2)
->and(DB::table('session_summaries')->where('visibility', 'private')->count())->toBe(1);
$this->get(route('home'))->assertOk()->assertSee('pub');
$bands = DB::table('session_summaries')->pluck('visibility');
expect($bands)->toContain('public')->toContain('private');
});
test('home renders the session summary and links into per-task detail', function () {
$run = BenchmarkRun::factory()->create([
'suite' => 'softbench', 'label' => 'rendered session', 'visibility' => 'public',
'model_id' => 'render/model', 'gpu' => 'AMD R9700', 'gpu_count' => 2,
]);
SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'ok' => true, 'level' => 'off']);
Artisan::call('festbench:summary');
$this->get(route('home'))->assertOk()
->assertSee('rendered session')
->assertSee('render/model')
->assertSee('AMD R9700')
->assertSee(route('runs.show', $run->run_id))
->assertSee('All task rows');
});
test('results page still lists flat task rows', function () {
$run = BenchmarkRun::factory()->create(['suite' => 'softbench', 'visibility' => 'public']);
SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'task' => 'flat-task']);
$this->get(route('results'))->assertOk()
->assertSee('All Task Rows')
->assertSee('flat-task');
});