Files
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

225 lines
7.9 KiB
Swift

// PR 13 — Benchmark DTOs (throughput + accuracy).
//
// Throughput mirrors omlx/admin/routes.py:/api/bench/* (start @4619,
// stream @4679, cancel @4724, results @4748). Accuracy mirrors
// /api/bench/accuracy/* (queue/add @4460, queue/status @4507, results
// @4531, reset @4547, cancel @4558, stream @4569).
//
// Both feature areas use *polling* in the Swift screens (the HTML uses
// SSE). The shape of `GET /api/bench/{id}/results` and
// `GET /api/bench/accuracy/results` matches what each respective SSE
// stream emits once aggregated, so polling produces functionally
// equivalent state — just without per-token granularity.
import Foundation
// =============================================================================
// MARK: - Device info (shared by both screens)
// =============================================================================
/// Response from `GET /admin/api/device-info`. Surfaced as a small chip
/// at the top of the Throughput screen so the user can sanity-check
/// which hardware they're benchmarking.
struct DeviceInfoDTO: Codable, Sendable {
let chipName: String?
let chipVariant: String?
let memoryGb: Int?
let gpuCores: Int?
let ownerHash: String?
}
// =============================================================================
// MARK: - Throughput bench
// =============================================================================
/// Body for `POST /admin/api/bench/start`. `prompt_lengths` and
/// `batch_sizes` are server-validated against a known whitelist
/// (1024…200000 / 2…8). `generation_length` is free-form int.
///
/// The server always publishes results to the public omlx.ai
/// leaderboard after the bench completes (matching the browser admin
/// panel). Submission is anonymous — the payload carries an
/// owner_hash derived from hardware fingerprint, not user identity.
struct BenchStartRequest: Encodable, Sendable {
let modelId: String
let promptLengths: [Int]
let generationLength: Int
let batchSizes: [Int]
}
struct BenchStartResponse: Codable, Sendable {
let benchId: String
let status: String
let totalTests: Int
}
/// Single + batch results share one envelope; `testType` is the
/// discriminator. We use optional fields so a single struct can decode
/// both variants without resorting to an enum-with-associated-values
/// (which would have to be hand-coded for Codable).
struct BenchResultDTO: Codable, Equatable, Sendable {
let testType: String
// Single-only
let pp: Int?
let tg: Int?
let ttftMs: Double?
let tpotMs: Double?
let processingTps: Double?
let genTps: Double?
let e2eLatencyS: Double?
let totalThroughput: Double?
let peakMemoryBytes: Int64?
// Batch-only
let batchSize: Int?
let tgTps: Double?
let ppTps: Double?
let avgTtftMs: Double?
}
struct BenchResultsResponse: Codable, Sendable {
let benchId: String
let status: String
let results: [BenchResultDTO]
let error: String?
/// Mirror of the SSE `upload` / `upload_done` / `upload_skipped` events
/// (omlx/admin/benchmark.py:_upload_to_omlx_ai). Populated server-side
/// as the upload progresses so polling clients render the same state
/// the HTML admin panel sees over its event stream.
let uploadState: BenchUploadStateDTO?
}
/// Per-run upload state. Lives on `BenchmarkRun.upload_state` server-side.
struct BenchUploadStateDTO: Codable, Equatable, Sendable {
/// "idle" | "uploading" | "done" | "skipped"
let phase: String
let results: [BenchUploadResultDTO]
let total: Int
let successCount: Int
let failedCount: Int
/// Display owner hash (verify char stripped). Populated on phase=done.
let ownerHash: String?
/// Set when phase=skipped. Today the only reason is
/// "experimental_features".
let skippedReason: String?
let skippedFeatures: [String]
}
/// One context-length's upload outcome. Exactly one of `url` / `error`
/// should be populated; `duplicate=true` flags rows the server already had.
///
/// The JSON `id` field from the server (a submission UUID, nullable on
/// errors) is renamed to `submissionId` so the SwiftUI `Identifiable`
/// conformance can use `contextLength` as a stable, non-optional key.
struct BenchUploadResultDTO: Codable, Equatable, Sendable, Identifiable {
let contextLength: Int
let submissionId: String?
let url: String?
let duplicate: Bool?
let error: String?
var id: Int { contextLength }
// The client's JSONDecoder uses convertFromSnakeCase, which transforms
// JSON keys *before* matching against CodingKey raw values. So we only
// declare a custom raw value for `submissionId`, whose JSON name (`id`)
// can't be derived from snake_case conversion. The rest use synthesized
// raw values (`contextLength`, etc.) that the strategy produces from
// the server's snake_case keys.
enum CodingKeys: String, CodingKey {
case contextLength
case submissionId = "id"
case url
case duplicate
case error
}
}
struct BenchCancelResponse: Codable, Sendable {
let status: String
let benchId: String?
}
// =============================================================================
// MARK: - Accuracy bench
// =============================================================================
/// Body for `POST /admin/api/bench/accuracy/queue/add`. `benchmarks` is
/// a dict of benchmark-key → sample-size; the catalog of valid keys
/// lives in AccuracyBenchScreen.swift to keep the DTO server-agnostic.
struct AccuracyQueueAddRequest: Encodable, Sendable {
let modelId: String
let benchmarks: [String: Int]
let batchSize: Int
let enableThinking: Bool
}
struct AccuracyQueueItem: Codable, Equatable, Sendable {
let modelId: String
let benchmarks: [String]
}
/// Mirrors the queue-status snapshot returned by every accuracy
/// queue/add, queue/status, queue/remove endpoint. `lastProgress` is
/// the most recent progress event the SSE stream emitted; polling
/// callers use it to drive the in-flight progress message.
///
/// `phase` is finer-grained than `running` — it distinguishes
/// "evaluating" (still scoring) from "unloading" (post-result cleanup),
/// so the UI can hide the running row once the result card has
/// appeared. Values: "pending" | "loading" | "evaluating" |
/// "unloading" | "completed" | "cancelled" | "error". Nullable when no
/// run is in flight.
struct AccuracyQueueStatus: Codable, Sendable {
let running: Bool
let currentModel: String
let currentBenchId: String
let lastProgress: AccuracyProgressDTO?
let phase: String?
let queue: [AccuracyQueueItem]
/// True only while the run is actively producing results. Hides the
/// running row during post-result unload and after the bench task
/// finishes but before `_queue_running` flips back.
var isActivelyEvaluating: Bool {
guard running else { return false }
switch phase {
case "evaluating", "loading", "pending", nil: return true
default: return false
}
}
}
struct AccuracyProgressDTO: Codable, Equatable, Sendable {
let modelId: String?
let message: String?
let current: Int?
let total: Int?
let benchCurrent: Int?
let benchTotal: Int?
let benchmark: String?
}
struct AccuracyResultDTO: Codable, Equatable, Sendable, Identifiable {
let benchmark: String
let modelId: String
let accuracy: Double
let correct: Int
let total: Int
let timeS: Double
let thinkingUsed: Bool
let categoryScores: [String: Double]?
/// Synthetic ID — the server doesn't emit one and `(benchmark,
/// model)` is unique within an accAllResults array.
var id: String { "\(benchmark)::\(modelId)" }
}
struct AccuracyResultsResponse: Codable, Sendable {
let results: [AccuracyResultDTO]
let running: Bool
let currentModel: String
let currentBenchId: String
}