|
3 | 3 | // |
4 | 4 | // Detects system hardware capabilities and recommends optimal whisper model size. |
5 | 5 |
|
| 6 | +import Darwin |
6 | 7 | import Foundation |
7 | 8 |
|
8 | 9 | // MARK: - SystemCapabilities |
@@ -147,4 +148,49 @@ enum SystemInfo { |
147 | 148 | // Use at most half the cores, minimum 2, maximum 8 |
148 | 149 | return max(2, min(cores / 2, 8)) |
149 | 150 | } |
| 151 | + |
| 152 | + /// Approximate reclaimable memory in bytes. Zero means the probe failed. |
| 153 | + /// |
| 154 | + /// Uses free + inactive only. On Darwin, `free_count` already includes |
| 155 | + /// speculative pages, purgeable pages often overlap the inactive queue, |
| 156 | + /// and compressor-resident pages still occupy RAM — so those counters |
| 157 | + /// must not be added on top or the gate can over-approve loads. |
| 158 | + static var availableMemoryBytes: UInt64 { |
| 159 | + var stats = vm_statistics64() |
| 160 | + var count = mach_msg_type_number_t( |
| 161 | + MemoryLayout<vm_statistics64_data_t>.stride / MemoryLayout<integer_t>.stride |
| 162 | + ) |
| 163 | + let host = mach_host_self() |
| 164 | + defer { mach_port_deallocate(mach_task_self_, host) } |
| 165 | + let kr = withUnsafeMutablePointer(to: &stats) { |
| 166 | + $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { |
| 167 | + host_statistics64(host, HOST_VM_INFO64, $0, &count) |
| 168 | + } |
| 169 | + } |
| 170 | + guard kr == KERN_SUCCESS else { return 0 } |
| 171 | + var pageSize: vm_size_t = 0 |
| 172 | + guard host_page_size(host, &pageSize) == KERN_SUCCESS, pageSize > 0 else { return 0 } |
| 173 | + let pages = UInt64(stats.free_count) |
| 174 | + + UInt64(stats.inactive_count) |
| 175 | + return pages * UInt64(pageSize) |
| 176 | + } |
| 177 | + |
| 178 | + /// Whether loading `size` is likely to fit without thrashing. |
| 179 | + /// |
| 180 | + /// Uses the catalog RAM estimate against installed memory and against |
| 181 | + /// reclaimable free memory from host_statistics64. A zero available |
| 182 | + /// reading is treated as unknown so we do not block loads on a failed probe. |
| 183 | + static func canFitModelInMemory( |
| 184 | + _ size: ModelSize, |
| 185 | + physicalMemoryGB: Int = physicalMemoryGB, |
| 186 | + availableBytes: UInt64 = availableMemoryBytes |
| 187 | + ) -> Bool { |
| 188 | + let requiredGB = size.ramRequiredGB |
| 189 | + guard Double(physicalMemoryGB) + 0.001 >= requiredGB else { |
| 190 | + return false |
| 191 | + } |
| 192 | + guard availableBytes > 0 else { return true } |
| 193 | + let requiredBytes = UInt64((requiredGB * 1024 * 1024 * 1024).rounded(.up)) |
| 194 | + return availableBytes >= requiredBytes |
| 195 | + } |
150 | 196 | } |
0 commit comments