@dvai-bridge/ios 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DVAIBridge.podspec +120 -0
- package/LICENSE +51 -0
- package/Package.swift +104 -0
- package/README.md +199 -0
- package/ios/Sources/DVAIBridge/BackendKind.swift +23 -0
- package/ios/Sources/DVAIBridge/BoundServer.swift +46 -0
- package/ios/Sources/DVAIBridge/Capability/CapabilityCache.swift +85 -0
- package/ios/Sources/DVAIBridge/Capability/CapabilityPrecheck.swift +193 -0
- package/ios/Sources/DVAIBridge/Capability/CapabilityScore.swift +51 -0
- package/ios/Sources/DVAIBridge/Capability/DeviceID.swift +70 -0
- package/ios/Sources/DVAIBridge/Capability/HardwareAssessment.swift +41 -0
- package/ios/Sources/DVAIBridge/DVAIBridge.swift +658 -0
- package/ios/Sources/DVAIBridge/DVAIBridgeConfig.swift +86 -0
- package/ios/Sources/DVAIBridge/DVAIBridgeError.swift +33 -0
- package/ios/Sources/DVAIBridge/Discovery/MDNSPeer.swift +64 -0
- package/ios/Sources/DVAIBridge/Discovery/NWAdvertiser.swift +103 -0
- package/ios/Sources/DVAIBridge/Discovery/NWBrowserDiscovery.swift +212 -0
- package/ios/Sources/DVAIBridge/Internal/BackendSelector.swift +59 -0
- package/ios/Sources/DVAIBridge/Internal/ProgressBroadcaster.swift +84 -0
- package/ios/Sources/DVAIBridge/License/Audience.swift +133 -0
- package/ios/Sources/DVAIBridge/License/Discovery.swift +164 -0
- package/ios/Sources/DVAIBridge/License/LicenseValidator.swift +392 -0
- package/ios/Sources/DVAIBridge/License/PublicKeys.swift +114 -0
- package/ios/Sources/DVAIBridge/License/Types.swift +195 -0
- package/ios/Sources/DVAIBridge/Offload/OffloadConfig.swift +118 -0
- package/ios/Sources/DVAIBridge/Offload/OffloadProxy.swift +604 -0
- package/ios/Sources/DVAIBridge/Offload/OffloadRuntime.swift +98 -0
- package/ios/Sources/DVAIBridge/Pairing/Pairing.swift +125 -0
- package/ios/Sources/DVAIBridge/Pairing/PairingHandshake.swift +141 -0
- package/ios/Sources/DVAIBridge/Pairing/PairingPolicy.swift +162 -0
- package/ios/Sources/DVAIBridge/Pairing/PairingStore.swift +65 -0
- package/ios/Sources/DVAIBridge/ProgressEvent.swift +34 -0
- package/ios/Sources/DVAIBridge/ReactiveState.swift +149 -0
- package/ios/Sources/DVAICoreMLCore/.gitkeep +0 -0
- package/ios/Sources/DVAICoreMLCore/CoreMLBackendError.swift +19 -0
- package/ios/Sources/DVAICoreMLCore/CoreMLHandlers.swift +123 -0
- package/ios/Sources/DVAICoreMLCore/CoreMLPluginState.swift +130 -0
- package/ios/Sources/DVAICoreMLCore/Internal/CoreMLEngine.swift +137 -0
- package/ios/Sources/DVAICoreMLCore/Internal/CoreMLGenerator.swift +108 -0
- package/ios/Sources/DVAICoreMLCore/Internal/CoreMLSampler.swift +96 -0
- package/ios/Sources/DVAICoreMLCore/Internal/CoreMLTokenizer.swift +69 -0
- package/ios/Tests/DVAIBridgeTests/BackendSelectorTests.swift +53 -0
- package/ios/Tests/DVAIBridgeTests/CapabilityPrecheckTests.swift +108 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLEngineTests.swift +18 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLGeneratorShapeTests.swift +11 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLHandlersTests.swift +32 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLPluginStateTests.swift +41 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLSamplerTests.swift +40 -0
- package/ios/Tests/DVAIBridgeTests/CoreMLTokenizerTests.swift +19 -0
- package/ios/Tests/DVAIBridgeTests/DVAIBridgeAPIShapeTests.swift +37 -0
- package/ios/Tests/DVAIBridgeTests/DVAIBridgeConfigTests.swift +52 -0
- package/ios/Tests/DVAIBridgeTests/DVAIBridgeErrorTests.swift +33 -0
- package/ios/Tests/DVAIBridgeTests/LicenseValidatorTests.swift +658 -0
- package/ios/Tests/DVAIBridgeTests/OffloadProxyDecisionTests.swift +156 -0
- package/ios/Tests/DVAIBridgeTests/OffloadTests.swift +339 -0
- package/ios/Tests/DVAIBridgeTests/ProgressBroadcasterTests.swift +69 -0
- package/ios/Tests/DVAIBridgeTests/ProgressEventTests.swift +25 -0
- package/ios/Tests/DVAIBridgeTests/ReactiveStateTests.swift +45 -0
- package/ios/Tests/DVAIBridgeTests/RealModelIntegrationTest.swift +359 -0
- package/package.json +19 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
#if canImport(UIKit)
|
|
3
|
+
import UIKit
|
|
4
|
+
#endif
|
|
5
|
+
|
|
6
|
+
/// v3.2 — pre-init capability gate (Swift port of the Kotlin
|
|
7
|
+
/// `CapabilityPrecheck` and the TS `assessCapability`).
|
|
8
|
+
///
|
|
9
|
+
/// Mirrors the Android + TS heuristic 1:1 — same hint shapes, same
|
|
10
|
+
/// threshold defaults (3 tok/s hardware floor, 10 tok/s comfort
|
|
11
|
+
/// threshold), same three modes. Guarantees that a given device
|
|
12
|
+
/// classifies the same way regardless of which platform's SDK is
|
|
13
|
+
/// asking.
|
|
14
|
+
///
|
|
15
|
+
/// Heuristic-only: no model is loaded, no probe runs. The cold-run
|
|
16
|
+
/// probe (`probeCapability`) refines the estimate AFTER the model is
|
|
17
|
+
/// loaded and a request has actually completed; that path is unchanged
|
|
18
|
+
/// from v3.0.
|
|
19
|
+
public enum CapabilityPrecheck {
|
|
20
|
+
|
|
21
|
+
/// Result of `assess(...)`. `Codable` so it can serialize cleanly
|
|
22
|
+
/// to JSON for cross-language Pigeon / Capacitor bridges.
|
|
23
|
+
public struct Result: Sendable, Codable, Equatable {
|
|
24
|
+
public let mode: PrecheckMode
|
|
25
|
+
/// Estimated decode tok/s for any 1–3B-class model.
|
|
26
|
+
public let tokPerSec: Double
|
|
27
|
+
public let hints: DeviceCapabilityHints
|
|
28
|
+
/// Human-readable explanation; safe to log + display.
|
|
29
|
+
public let reason: String
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Tunable thresholds. Defaults match `OffloadConfig` / the Kotlin
|
|
33
|
+
/// `CapabilityPrecheck.Thresholds` / the TS defaults.
|
|
34
|
+
public struct Thresholds: Sendable {
|
|
35
|
+
/// Below this, the device is too weak to run inference at all.
|
|
36
|
+
/// Default 3.0 tok/s.
|
|
37
|
+
public var hardwareMinimum: Double
|
|
38
|
+
/// Below this, run in offload-only mode. Default 10.0.
|
|
39
|
+
public var minLocalCapability: Double
|
|
40
|
+
|
|
41
|
+
public init(hardwareMinimum: Double = 3.0, minLocalCapability: Double = 10.0) {
|
|
42
|
+
self.hardwareMinimum = hardwareMinimum
|
|
43
|
+
self.minLocalCapability = minLocalCapability
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Run the precheck. Pass `hints` to override the auto-detect (used
|
|
48
|
+
/// by tests + cross-platform parity checks).
|
|
49
|
+
public static func assess(
|
|
50
|
+
thresholds: Thresholds = Thresholds(),
|
|
51
|
+
hints: DeviceCapabilityHints? = nil
|
|
52
|
+
) -> Result {
|
|
53
|
+
let resolvedHints = hints ?? detectDeviceHints()
|
|
54
|
+
let tokPerSec = heuristicTokPerSec(hints: resolvedHints)
|
|
55
|
+
|
|
56
|
+
if tokPerSec < thresholds.hardwareMinimum {
|
|
57
|
+
return Result(
|
|
58
|
+
mode: .tooWeak,
|
|
59
|
+
tokPerSec: tokPerSec,
|
|
60
|
+
hints: resolvedHints,
|
|
61
|
+
reason: "estimated \(tokPerSec) tok/s, below the " +
|
|
62
|
+
"\(thresholds.hardwareMinimum) tok/s hardware floor — " +
|
|
63
|
+
"local inference would be unusable."
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if tokPerSec < thresholds.minLocalCapability {
|
|
68
|
+
return Result(
|
|
69
|
+
mode: .offloadOnly,
|
|
70
|
+
tokPerSec: tokPerSec,
|
|
71
|
+
hints: resolvedHints,
|
|
72
|
+
reason: "estimated \(tokPerSec) tok/s, below the " +
|
|
73
|
+
"\(thresholds.minLocalCapability) tok/s comfort threshold — " +
|
|
74
|
+
"model will not be loaded locally; every request will be " +
|
|
75
|
+
"forwarded to a paired peer."
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Result(
|
|
80
|
+
mode: .ok,
|
|
81
|
+
tokPerSec: tokPerSec,
|
|
82
|
+
hints: resolvedHints,
|
|
83
|
+
reason: "estimated \(tokPerSec) tok/s, above the " +
|
|
84
|
+
"\(thresholds.minLocalCapability) tok/s threshold — running normally."
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Pure heuristic — mirrors the TS `heuristicTokPerSec` and
|
|
89
|
+
/// Kotlin's `CapabilityPrecheck.heuristicTokPerSec`.
|
|
90
|
+
public static func heuristicTokPerSec(hints: DeviceCapabilityHints) -> Double {
|
|
91
|
+
// Base score by GPU class — observed floors for 1–3B q4 GGUFs.
|
|
92
|
+
let gpuBase: Double
|
|
93
|
+
switch hints.gpuClass {
|
|
94
|
+
case .none: gpuBase = 3.0
|
|
95
|
+
case .integrated: gpuBase = 8.0
|
|
96
|
+
case .discrete: gpuBase = 35.0
|
|
97
|
+
case .appleSilicon: gpuBase = 40.0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let cpuMul: Double
|
|
101
|
+
switch hints.cpuClass {
|
|
102
|
+
case .low: cpuMul = 0.6
|
|
103
|
+
case .mid: cpuMul = 1.0
|
|
104
|
+
case .high: cpuMul = 1.3
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let ramMul: Double
|
|
108
|
+
if hints.ramGb < 4 { ramMul = 0.3 }
|
|
109
|
+
else if hints.ramGb < 8 { ramMul = 0.7 }
|
|
110
|
+
else { ramMul = 1.0 }
|
|
111
|
+
|
|
112
|
+
let npuBonus: Double = hints.hasNpu ? 1.4 : 1.0
|
|
113
|
+
|
|
114
|
+
let raw = gpuBase * cpuMul * ramMul * npuBonus
|
|
115
|
+
return (raw * 10).rounded() / 10
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Best-effort introspection on iOS / macOS via sysctl + ProcessInfo.
|
|
119
|
+
public static func detectDeviceHints() -> DeviceCapabilityHints {
|
|
120
|
+
let physicalMemoryBytes = ProcessInfo.processInfo.physicalMemory
|
|
121
|
+
let ramGb = Int(Double(physicalMemoryBytes) / (1024.0 * 1024.0 * 1024.0))
|
|
122
|
+
let cores = ProcessInfo.processInfo.activeProcessorCount
|
|
123
|
+
|
|
124
|
+
let cpuClass: CpuClass
|
|
125
|
+
if cores >= 8 { cpuClass = .high }
|
|
126
|
+
else if cores >= 4 { cpuClass = .mid }
|
|
127
|
+
else { cpuClass = .low }
|
|
128
|
+
|
|
129
|
+
// Apple Silicon Macs + iPhones with M-series / A-series chips
|
|
130
|
+
// have unified-memory architectures. We treat them as the
|
|
131
|
+
// `appleSilicon` class — the heuristic uses gpuBase = 40.
|
|
132
|
+
// Older Intel Macs report `appleSilicon` if the runtime is on
|
|
133
|
+
// arm64; we rely on `arch == arm64` as the signal.
|
|
134
|
+
let gpuClass: GpuClass = isAppleSilicon() ? .appleSilicon : .integrated
|
|
135
|
+
|
|
136
|
+
// NPU detection: every Apple Silicon device since A11 has the
|
|
137
|
+
// Neural Engine, and every Intel Mac since 2020 has none. Use
|
|
138
|
+
// arch as a proxy.
|
|
139
|
+
let hasNpu = isAppleSilicon()
|
|
140
|
+
|
|
141
|
+
return DeviceCapabilityHints(
|
|
142
|
+
hasNpu: hasNpu,
|
|
143
|
+
ramGb: ramGb,
|
|
144
|
+
gpuClass: gpuClass,
|
|
145
|
+
cpuClass: cpuClass
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private static func isAppleSilicon() -> Bool {
|
|
150
|
+
#if arch(arm64)
|
|
151
|
+
return true
|
|
152
|
+
#else
|
|
153
|
+
return false
|
|
154
|
+
#endif
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Lifecycle mode the SDK enters on `start()`. JSON-serialized as a
|
|
159
|
+
/// lower-cased dash string for cross-platform parity (`ok` /
|
|
160
|
+
/// `offload-only` / `too-weak`).
|
|
161
|
+
public enum PrecheckMode: String, Sendable, Codable, Equatable {
|
|
162
|
+
case ok
|
|
163
|
+
case offloadOnly = "offload-only"
|
|
164
|
+
case tooWeak = "too-weak"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Mirrors the Kotlin `DeviceCapabilityHints` and TS interface.
|
|
168
|
+
public struct DeviceCapabilityHints: Sendable, Codable, Equatable {
|
|
169
|
+
public let hasNpu: Bool
|
|
170
|
+
public let ramGb: Int
|
|
171
|
+
public let gpuClass: GpuClass
|
|
172
|
+
public let cpuClass: CpuClass
|
|
173
|
+
|
|
174
|
+
public init(hasNpu: Bool, ramGb: Int, gpuClass: GpuClass, cpuClass: CpuClass) {
|
|
175
|
+
self.hasNpu = hasNpu
|
|
176
|
+
self.ramGb = ramGb
|
|
177
|
+
self.gpuClass = gpuClass
|
|
178
|
+
self.cpuClass = cpuClass
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public enum GpuClass: String, Sendable, Codable, Equatable {
|
|
183
|
+
case none
|
|
184
|
+
case integrated
|
|
185
|
+
case discrete
|
|
186
|
+
case appleSilicon = "apple-silicon"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public enum CpuClass: String, Sendable, Codable, Equatable {
|
|
190
|
+
case low
|
|
191
|
+
case mid
|
|
192
|
+
case high
|
|
193
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Swift-side `CapabilityScore` mirroring the TypeScript shape in
|
|
4
|
+
/// `packages/dvai-bridge-core/src/capability/types.ts`. Persisted by
|
|
5
|
+
/// `CapabilityCache`.
|
|
6
|
+
public struct CapabilityScore: Sendable, Equatable, Codable, Hashable {
|
|
7
|
+
/// Source of the estimate.
|
|
8
|
+
public enum Source: String, Sendable, Codable, Hashable {
|
|
9
|
+
case probe
|
|
10
|
+
case heuristic
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Model identifier this score applies to.
|
|
14
|
+
public let modelId: String
|
|
15
|
+
/// Stable per-install device identifier.
|
|
16
|
+
public let deviceId: String
|
|
17
|
+
/// Library SemVer at the time the score was measured.
|
|
18
|
+
public let libraryVersion: String
|
|
19
|
+
/// Estimated decode rate, tokens-per-second.
|
|
20
|
+
public let tokPerSec: Double
|
|
21
|
+
/// Source of the estimate.
|
|
22
|
+
public let source: Source
|
|
23
|
+
/// Unix milliseconds the score was measured / computed.
|
|
24
|
+
public let measuredAt: Int64
|
|
25
|
+
|
|
26
|
+
public init(
|
|
27
|
+
modelId: String,
|
|
28
|
+
deviceId: String,
|
|
29
|
+
libraryVersion: String,
|
|
30
|
+
tokPerSec: Double,
|
|
31
|
+
source: Source = .heuristic,
|
|
32
|
+
measuredAt: Int64 = Int64(Date().timeIntervalSince1970 * 1000)
|
|
33
|
+
) {
|
|
34
|
+
self.modelId = modelId
|
|
35
|
+
self.deviceId = deviceId
|
|
36
|
+
self.libraryVersion = libraryVersion
|
|
37
|
+
self.tokPerSec = tokPerSec
|
|
38
|
+
self.source = source
|
|
39
|
+
self.measuredAt = measuredAt
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public struct CapabilityCacheKey: Sendable, Hashable {
|
|
44
|
+
public let modelId: String
|
|
45
|
+
public let libraryVersion: String
|
|
46
|
+
|
|
47
|
+
public init(modelId: String, libraryVersion: String) {
|
|
48
|
+
self.modelId = modelId
|
|
49
|
+
self.libraryVersion = libraryVersion
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Stable per-install device identifier. Generated once on first call,
|
|
4
|
+
/// persisted alongside the capability cache under the same Application
|
|
5
|
+
/// Support directory. Used for:
|
|
6
|
+
/// - identifying THIS device in mDNS TXT records (LAN discovery).
|
|
7
|
+
/// - identifying THIS device in rendezvous-server pairing payloads.
|
|
8
|
+
/// - keying the capability cache.
|
|
9
|
+
///
|
|
10
|
+
/// NOT a privacy hazard: the ID is per-install and per-device-storage,
|
|
11
|
+
/// never tied to user identity. Reinstalling the app or wiping app
|
|
12
|
+
/// storage produces a fresh ID — that's the right behaviour and matches
|
|
13
|
+
/// the TS-side `generateDeviceId` semantics in
|
|
14
|
+
/// `packages/dvai-bridge-core/src/capability/deviceId.ts`.
|
|
15
|
+
public final class DeviceIDStore: @unchecked Sendable {
|
|
16
|
+
private let fileURL: URL
|
|
17
|
+
private let lock = NSLock()
|
|
18
|
+
private var cached: String?
|
|
19
|
+
|
|
20
|
+
public init(directory: URL) {
|
|
21
|
+
self.fileURL = directory.appendingPathComponent("device-id.txt", isDirectory: false)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Return the device ID, generating + persisting it on first call.
|
|
25
|
+
public func get() throws -> String {
|
|
26
|
+
lock.lock()
|
|
27
|
+
defer { lock.unlock() }
|
|
28
|
+
if let cached = cached { return cached }
|
|
29
|
+
|
|
30
|
+
// Try to load existing
|
|
31
|
+
if let data = try? Data(contentsOf: fileURL),
|
|
32
|
+
let raw = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
33
|
+
!raw.isEmpty {
|
|
34
|
+
cached = raw
|
|
35
|
+
return raw
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate fresh
|
|
39
|
+
let id = Self.generate()
|
|
40
|
+
try FileManager.default.createDirectory(
|
|
41
|
+
at: fileURL.deletingLastPathComponent(),
|
|
42
|
+
withIntermediateDirectories: true
|
|
43
|
+
)
|
|
44
|
+
try id.write(to: fileURL, atomically: true, encoding: .utf8)
|
|
45
|
+
cached = id
|
|
46
|
+
return id
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Generate a 22-char URL-safe base64 random ID. Mirrors the TS
|
|
50
|
+
/// `generateDeviceId()` shape (16 random bytes, base64url, no
|
|
51
|
+
/// padding) so device IDs round-trip across platforms.
|
|
52
|
+
public static func generate() -> String {
|
|
53
|
+
var bytes = [UInt8](repeating: 0, count: 16)
|
|
54
|
+
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
|
55
|
+
if status != errSecSuccess {
|
|
56
|
+
// Fall back to UUID — still random, just not from SecRandom.
|
|
57
|
+
return UUID().uuidString.replacingOccurrences(of: "-", with: "")
|
|
58
|
+
.lowercased().prefix(22).description
|
|
59
|
+
}
|
|
60
|
+
return base64UrlEncode(Data(bytes))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private static func base64UrlEncode(_ data: Data) -> String {
|
|
64
|
+
let b64 = data.base64EncodedString()
|
|
65
|
+
return b64
|
|
66
|
+
.replacingOccurrences(of: "+", with: "-")
|
|
67
|
+
.replacingOccurrences(of: "/", with: "_")
|
|
68
|
+
.replacingOccurrences(of: "=", with: "")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// v3.2 — JSON-serializable result of `DVAIBridge.shared.assessHardware()`.
|
|
4
|
+
///
|
|
5
|
+
/// Returned to consumer code so the app developer can decide whether
|
|
6
|
+
/// to call `DVAIBridge.shared.start(...)` and what (if anything) to
|
|
7
|
+
/// surface in the UI. The SDK itself never shows UI for hardware
|
|
8
|
+
/// decisions — consumer apps make those choices.
|
|
9
|
+
///
|
|
10
|
+
/// `Codable` so it round-trips cleanly through Capacitor / React
|
|
11
|
+
/// Native / Pigeon bridges as JSON without any custom converter.
|
|
12
|
+
public struct HardwareAssessment: Sendable, Codable, Equatable {
|
|
13
|
+
/// Lifecycle mode the SDK would enter on `start()`. See
|
|
14
|
+
/// `PrecheckMode` for the three values.
|
|
15
|
+
public let mode: PrecheckMode
|
|
16
|
+
/// Estimated decode tok/s for any 1–3B-class model on this device.
|
|
17
|
+
public let tokPerSec: Double
|
|
18
|
+
/// Human-readable explanation; safe to log + display.
|
|
19
|
+
public let reason: String
|
|
20
|
+
/// Underlying hints used to compute the estimate.
|
|
21
|
+
public let hints: DeviceCapabilityHints
|
|
22
|
+
|
|
23
|
+
public init(
|
|
24
|
+
mode: PrecheckMode,
|
|
25
|
+
tokPerSec: Double,
|
|
26
|
+
reason: String,
|
|
27
|
+
hints: DeviceCapabilityHints
|
|
28
|
+
) {
|
|
29
|
+
self.mode = mode
|
|
30
|
+
self.tokPerSec = tokPerSec
|
|
31
|
+
self.reason = reason
|
|
32
|
+
self.hints = hints
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
init(from result: CapabilityPrecheck.Result) {
|
|
36
|
+
self.mode = result.mode
|
|
37
|
+
self.tokPerSec = result.tokPerSec
|
|
38
|
+
self.reason = result.reason
|
|
39
|
+
self.hints = result.hints
|
|
40
|
+
}
|
|
41
|
+
}
|