unbekannte Hardware/Modelle werden * einem VORHANDENEN Katalog-Eintrag ZUGEORDNET (Alternativname/Matching). * (b) Dein Token darf anlegen (create) -> unbekannte Hardware/Modelle werden * SELBST im Katalog angelegt. * Welcher Weg genommen wird, entscheidet das Script automatisch anhand der * Rechte, die /api/v1/me zurueckliefert. Du musst nichts umstellen. * * BENUTZUNG: * 1. $API und $TOKEN unten setzen. * 2. Die drei ensure_*()-Aufrufe + den $result-Block an deine Werte anpassen. * 3. Ausfuehren: php upload-benchmark.php * * Voraussetzung: PHP >= 7.4 mit cURL-Extension. * ============================================================================ */ // =========================================================================== // 1) KONFIGURATION // =========================================================================== $API = 'https://llm-benchmark.de/api/v1'; // Basis-URL der API (ohne Slash am Ende) $TOKEN = 'DEIN_BEARER_TOKEN_HIER'; // Persoenlicher Token (Profil -> API) // Sicherheitsschwelle fuer automatisches Zuordnen (Weg a): nur Kandidaten mit // mindestens dieser Aehnlichkeit (%) werden automatisch als Alternativname // verknuepft. Darunter bricht das Script ab, statt falsch zuzuordnen. $MATCH_MIN_SCORE = 82; // =========================================================================== // 2) HTTP-HELFER (gibt ['code'=>int, 'json'=>array|null, 'raw'=>string] zurueck) // =========================================================================== function api(string $method, string $path, ?array $body = null): array { global $API, $TOKEN; $ch = curl_init($API . $path); $headers = ['Accept: application/json', 'Authorization: Bearer ' . $TOKEN]; $payload = null; if ($body !== null) { $payload = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $headers[] = 'Content-Type: application/json'; } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $payload, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 12, ]); $raw = curl_exec($ch); $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); if ($raw === false) { throw new RuntimeException("Netzwerkfehler: $err"); } return ['code' => $code, 'json' => json_decode((string) $raw, true), 'raw' => (string) $raw]; } function fail(string $msg): void { fwrite(STDERR, "ABBRUCH: $msg\n"); exit(1); } // =========================================================================== // 3) RECHTE PRUEFEN (GET /me) // Liefert u.a. capabilities.can_create -> darf dieser Token anlegen? // =========================================================================== $me = api('GET', '/me'); if ($me['code'] !== 200) { fail("Token ungueltig oder API nicht erreichbar (HTTP {$me['code']}). Antwort: {$me['raw']}"); } $meData = $me['json']['data'] ?? []; $caps = $meData['capabilities'] ?? []; $canCreate = !empty($caps['can_create']); // darf Katalog-Eintraege NEU anlegen? $canWrite = !empty($caps['can_write']); // darf Ergebnisse senden + zuordnen? printf("Angemeldet als %s (Rolle: %s) | anlegen: %s | schreiben: %s\n", $meData['display_name'] ?? '?', $meData['role'] ?? '?', $canCreate ? 'JA' : 'NEIN', $canWrite ? 'JA' : 'NEIN'); if (!$canWrite) { fail("Dieser Token darf keine Ergebnisse senden (kein write-Recht)."); } // =========================================================================== // 4) KERNSTUECK: Hardware/Modell AUFLOESEN oder ANLEGEN // --------------------------------------------------------------------------- // ensure_entity() garantiert, dass am Ende ein GUELTIGER Slug existiert, auf // den ein Ergebnis verweisen kann. Der Ablauf: // // 1. GET /match -> exakter Treffer (per Name/Slug/Alternativname)? -> fertig. // 2. Kein Treffer: // (b) create-Recht -> POST / (selbst anlegen) // (a) kein create -> besten Kandidaten als Alternativname // verknuepfen (POST /aliases) -> "zuordnen" // Nur wenn Aehnlichkeit >= $MATCH_MIN_SCORE, // sonst Abbruch mit Hinweis. // // $resource : 'models' | 'manufacturers' | 'gpus' | 'cpus' | 'mainboards' // $name : der lokale Anzeigename, so wie DEIN System ihn kennt // $createBody: Felder fuer den Fall, dass NEU angelegt werden darf // =========================================================================== function ensure_entity(string $resource, string $name, array $createBody): string { global $canCreate, $MATCH_MIN_SCORE; // --- Schritt 1: Auflösen (exakt oder Fuzzy-Kandidaten) --- $m = api('GET', "/match?resource=$resource&name=" . rawurlencode($name)); $data = $m['json']['data'] ?? []; $exact = $data['exact'] ?? null; // {slug,name} bei exaktem Treffer $candidates = $data['candidates'] ?? []; // [{slug,name,score}, ...] (score 0..100) if ($exact && !empty($exact['slug'])) { echo " [$resource] '$name' -> bereits vorhanden: {$exact['slug']}\n"; return (string) $exact['slug']; } // --- Schritt 2b: SELBST ANLEGEN (nur mit create-Recht) --- if ($canCreate) { $r = api('POST', "/$resource", $createBody); if (in_array($r['code'], [200, 201], true)) { $slug = (string) ($r['json']['data']['slug'] ?? $createBody['slug'] ?? ''); echo " [$resource] '$name' -> NEU angelegt: $slug\n"; return $slug; } // Moegliche Dublette -> vorhandenen Eintrag verwenden if ($r['code'] === 409 && !empty($r['json']['possible_duplicate']['slug'])) { $slug = (string) $r['json']['possible_duplicate']['slug']; echo " [$resource] '$name' -> existierte bereits (Dublette): $slug\n"; return $slug; } fail("[$resource] Anlegen von '$name' fehlgeschlagen (HTTP {$r['code']}): {$r['raw']}"); } // --- Schritt 2a: ZUORDNEN (kein create-Recht) --- // Besten Kandidaten waehlen und den lokalen Namen als Alternativname // hinterlegen. Kuenftige Uploads matchen dann automatisch. $best = $candidates[0] ?? null; if (!$best || (float) ($best['score'] ?? 0) < $MATCH_MIN_SCORE) { $hint = $best ? "bester Kandidat '{$best['slug']}' nur {$best['score']}%" : 'keine Kandidaten'; fail("[$resource] '$name' hat kein sicheres Gegenstueck ($hint). " . "Dein Token darf nichts anlegen. Bitte einen Eintrag manuell zuordnen " . "(POST /aliases mit dem passenden slug) oder einen Token mit create-Recht nutzen."); } $target = (string) $best['slug']; $a = api('POST', '/aliases', ['resource' => $resource, 'slug' => $target, 'alias' => $name]); if (!in_array($a['code'], [200, 201], true)) { fail("[$resource] Zuordnung '$name' -> '$target' fehlgeschlagen (HTTP {$a['code']}): {$a['raw']}"); } echo " [$resource] '$name' -> zugeordnet an: $target (Alternativname gesetzt, Score {$best['score']}%)\n"; return $target; } // =========================================================================== // 5) HARDWARE + MODELL SICHERSTELLEN // (Namen + createBody an deine Umgebung anpassen) // =========================================================================== echo "Zuordnung/Anlage laeuft ...\n"; // Hersteller zuerst (Modelle brauchen einen gueltigen manufacturer_slug) $manufacturerSlug = ensure_entity('manufacturers', 'Qwen (Alibaba)', [ 'slug' => 'qwen', 'name' => 'Qwen (Alibaba)', 'huggingface_url' => 'https://huggingface.co/Qwen', 'website_url' => 'https://qwen.ai', ]); $modelSlug = ensure_entity('models', 'Qwen3 32B', [ 'slug' => 'qwen3-32b', 'name' => 'Qwen3 32B', 'manufacturer_slug' => $manufacturerSlug, 'parameter_count' => '32B', 'architecture' => 'Dense', 'model_url' => 'https://huggingface.co/Qwen/Qwen3-32B', ]); $gpuSlug = ensure_entity('gpus', 'GeForce RTX 5090', [ 'slug' => 'geforce-rtx-5090', 'name' => 'GeForce RTX 5090', 'vendor' => 'NVIDIA', 'memory_gb' => 32, ]); $cpuSlug = ensure_entity('cpus', 'AMD EPYC 9654', [ 'slug' => 'epyc-9654', 'name' => 'AMD EPYC 9654', 'vendor' => 'AMD', 'cores' => 96, 'threads' => 192, ]); // Optional: Mainboard (weglassbar) // $boardSlug = ensure_entity('mainboards', 'ASUS ROG X670E', [ // 'slug' => 'asus-rog-x670e', 'name' => 'ASUS ROG X670E', 'vendor' => 'ASUS', // ]); // =========================================================================== // 6) ERGEBNIS SENDEN (POST /results) // --------------------------------------------------------------------------- // benchmark_type = 'performance' -> Durchsatz/Latenz (tok/s, TTFT) // benchmark_type = 'harness' -> Aufgaben-Score (points_achieved/max + tasks[]) // gpu_count/cpu_count/ram_gb bilden serverseitig ein Hardwareprofil. // measured_at (optional) = Messzeitpunkt; sonst 'jetzt'. Doppelte Laeufe mit // gleichem Modell+HW+Typ+measured_at werden serverseitig dedupliziert. // =========================================================================== $result = [ // --- Zuordnung (Slugs aus Schritt 5) --- 'model_slug' => $modelSlug, 'gpu_slug' => $gpuSlug, 'gpu_count' => 2, 'cpu_slug' => $cpuSlug, 'cpu_count' => 1, 'ram_gb' => 256, // 'board_slug' => $boardSlug, // optional // --- Typ + Laufzeitumgebung --- 'benchmark_type' => 'performance', 'model_server_type' => 'vllm', // Alias 'runtime' ist kompatibel 'runtime_version' => '0.10.0', 'engine' => 'vllm', 'quantization' => 'FP8', 'start_path' => '/opt/bin/start-vllm-qwen.sh', 'context_length' => 32768, 'configuration_parameters' => [ 'model' => 'Qwen/Qwen3-32B', 'tensor_parallel_size' => 2, 'gpu_memory_utilization' => 0.92, 'max_model_len' => 32768, ], // --- Performance-Messwerte --- 'concurrency' => 8, 'prefill_tokens_s' => 2450.7, 'generation_tokens_s' => 184.3, 'ttft_ms' => 126.4, 'duration_ms' => 60000, // 'measured_at' => '2026-07-31 10:00:00', // --- Fuer benchmark_type = 'harness' stattdessen: --- // 'points_achieved' => 87, // 'points_max' => 100, // 'duration_ms' => 325000, // 'tasks' => [ // ['name'=>'Recherche','success'=>true,'points'=>20,'points_max'=>20,'duration_ms'=>42000], // ['name'=>'Codeaenderung','success'=>true,'points'=>30,'points_max'=>30,'duration_ms'=>81000], // ], ]; $res = api('POST', '/results', $result); if (in_array($res['code'], [200, 201], true)) { $d = $res['json']['data'] ?? []; $dedup = !empty($d['deduped']) ? ' (bereits vorhanden, dedupliziert)' : ''; echo "OK: Ergebnis gespeichert -> ID {$d['ID']}, slug {$d['slug']}, Status {$d['status']}{$dedup}\n"; exit(0); } // Typische Fehler: // 422 "unbekannt" -> ein Slug wurde nicht gefunden (Modell/GPU/CPU); zuordnen/anlegen // 403 need:create -> Anlegen versucht ohne Recht; stattdessen zuordnen // 401 -> Token fehlt/ungueltig fail("Ergebnis-Upload fehlgeschlagen (HTTP {$res['code']}): {$res['raw']}");