feat(Me): 性能自检卡 — 后端标识 + prefill/decode 实测 + 引擎对比存档
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
67
康康/Services/BenchmarkService.swift
Normal file
67
康康/Services/BenchmarkService.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
import Foundation
|
||||
|
||||
/// 单次性能自检结果。按后端标签归档,供「MNN·SME2 vs MLX·GPU」对比展示(§12 卖点 2/6)。
|
||||
struct BenchmarkResult: Codable, Equatable {
|
||||
var backendLabel: String
|
||||
var promptTokens: Int
|
||||
var genTokens: Int
|
||||
var prefillTokensPerSecond: Double
|
||||
var decodeTokensPerSecond: Double
|
||||
var totalSeconds: Double
|
||||
var date: Date
|
||||
}
|
||||
|
||||
/// 性能自检服务:跑固定 prompt,取 AIRuntime 的归一统计,按后端标签存 UserDefaults。
|
||||
/// UI(ModelSelfTestView)只经本服务调 AIRuntime(§3.1)。
|
||||
@MainActor
|
||||
struct BenchmarkService {
|
||||
static let shared = BenchmarkService()
|
||||
private init() {}
|
||||
|
||||
nonisolated static let storeKey = "kk.benchmark.results"
|
||||
|
||||
/// 固定测试 prompt:跨设备/引擎可比的前提。
|
||||
static let fixedPrompt = "用中文一句话介绍肝功能里 ALT 这个指标。"
|
||||
|
||||
/// 跑一次自检。onToken 把流式输出交给 UI 展示。
|
||||
func run(onToken: @escaping @MainActor (String, Double) -> Void) async throws -> BenchmarkResult {
|
||||
try await AIRuntime.shared.prepare()
|
||||
let start = Date()
|
||||
let stream = await AIRuntime.shared.generate(prompt: Self.fixedPrompt, maxTokens: 128)
|
||||
for try await chunk in stream {
|
||||
onToken(chunk.text, chunk.decodeRate)
|
||||
}
|
||||
let total = Date().timeIntervalSince(start)
|
||||
let label = await AIRuntime.shared.activeBackendLabel
|
||||
let stats = await AIRuntime.shared.lastGenerateStats
|
||||
let result = BenchmarkResult(
|
||||
backendLabel: label,
|
||||
promptTokens: stats?.promptTokens ?? 0,
|
||||
genTokens: stats?.genTokens ?? 0,
|
||||
prefillTokensPerSecond: stats?.prefillTokensPerSecond ?? 0,
|
||||
decodeTokensPerSecond: stats?.decodeTokensPerSecond ?? 0,
|
||||
totalSeconds: total,
|
||||
date: .now
|
||||
)
|
||||
Self.save(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - 存档(静态纯函数,单测覆盖;nonisolated:纯 UserDefaults 操作,无需主线程)
|
||||
|
||||
nonisolated static func save(_ result: BenchmarkResult, defaults: UserDefaults = .standard) {
|
||||
var all = load(defaults: defaults)
|
||||
all[result.backendLabel] = result
|
||||
if let data = try? JSONEncoder().encode(all) {
|
||||
defaults.set(data, forKey: storeKey)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated static func load(defaults: UserDefaults = .standard) -> [String: BenchmarkResult] {
|
||||
guard let data = defaults.data(forKey: storeKey),
|
||||
let all = try? JSONDecoder().decode([String: BenchmarkResult].self, from: data) else {
|
||||
return [:]
|
||||
}
|
||||
return all
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user