llama-cpp-pro 0.2.2
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/CHANGELOG.md +295 -0
- package/LICENSE +21 -0
- package/LlamaCpp.podspec +33 -0
- package/LlamaCppCapacitor.podspec +33 -0
- package/Package.swift +29 -0
- package/README.md +93 -0
- package/android/build.gradle +88 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/CMakeLists-arm64.txt +138 -0
- package/android/src/main/CMakeLists-x86_64.txt +141 -0
- package/android/src/main/CMakeLists.txt +138 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCpp.java +1340 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCppPlugin.java +814 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/ModelAdmissionController.java +214 -0
- package/android/src/main/jni-chat-session.cpp +261 -0
- package/android/src/main/jni-lora.cpp +197 -0
- package/android/src/main/jni-multimodal.cpp +167 -0
- package/android/src/main/jni-tts.cpp +290 -0
- package/android/src/main/jni-utils.h +148 -0
- package/android/src/main/jni.cpp +1808 -0
- package/android/src/main/jniLibs/arm64-v8a/libllama-cpp-arm64.so +0 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/build-native.sh +280 -0
- package/cmake/desktop-metal-embed.cmake +30 -0
- package/cmake/desktop-sources.cmake +100 -0
- package/cmake/ggml-backends.cmake +44 -0
- package/cpp/LICENSE +21 -0
- package/cpp/README.md +15 -0
- package/cpp/anyascii.c +22223 -0
- package/cpp/anyascii.h +42 -0
- package/cpp/cap-completion.cpp +942 -0
- package/cpp/cap-completion.h +127 -0
- package/cpp/cap-embedding.cpp +193 -0
- package/cpp/cap-embedding.h +35 -0
- package/cpp/cap-ios-bridge.cpp +1810 -0
- package/cpp/cap-ios-bridge.h +61 -0
- package/cpp/cap-llama.cpp +412 -0
- package/cpp/cap-llama.h +161 -0
- package/cpp/cap-mtmd.hpp +602 -0
- package/cpp/cap-native-server.cpp +1095 -0
- package/cpp/cap-native-server.h +40 -0
- package/cpp/cap-tts.cpp +591 -0
- package/cpp/cap-tts.h +59 -0
- package/cpp/cap-wasm-fs.cpp +156 -0
- package/cpp/cap-wasm-jspi.cpp +19 -0
- package/cpp/cap-wasm-jspi.h +30 -0
- package/cpp/cap-wasm-vfs.cpp +22 -0
- package/cpp/chat-parser.cpp +393 -0
- package/cpp/chat-parser.h +120 -0
- package/cpp/chat.cpp +2315 -0
- package/cpp/chat.h +221 -0
- package/cpp/common.cpp +1664 -0
- package/cpp/common.h +744 -0
- package/cpp/ggml-alloc.c +1028 -0
- package/cpp/ggml-alloc.h +76 -0
- package/cpp/ggml-backend-impl.h +255 -0
- package/cpp/ggml-backend-reg.cpp +600 -0
- package/cpp/ggml-backend.cpp +2121 -0
- package/cpp/ggml-backend.h +354 -0
- package/cpp/ggml-common.h +1878 -0
- package/cpp/ggml-cpp.h +39 -0
- package/cpp/ggml-cpu/amx/amx.cpp +221 -0
- package/cpp/ggml-cpu/amx/amx.h +8 -0
- package/cpp/ggml-cpu/amx/common.h +91 -0
- package/cpp/ggml-cpu/amx/mmq.cpp +2512 -0
- package/cpp/ggml-cpu/amx/mmq.h +10 -0
- package/cpp/ggml-cpu/arch/arm/cpu-feats.cpp +94 -0
- package/cpp/ggml-cpu/arch/arm/quants.c +3650 -0
- package/cpp/ggml-cpu/arch/arm/repack.cpp +1891 -0
- package/cpp/ggml-cpu/arch/x86/cpu-feats.cpp +327 -0
- package/cpp/ggml-cpu/arch/x86/quants.c +3820 -0
- package/cpp/ggml-cpu/arch/x86/repack.cpp +6307 -0
- package/cpp/ggml-cpu/arch-fallback.h +215 -0
- package/cpp/ggml-cpu/binary-ops.cpp +158 -0
- package/cpp/ggml-cpu/binary-ops.h +16 -0
- package/cpp/ggml-cpu/common.h +73 -0
- package/cpp/ggml-cpu/ggml-cpu-impl.h +559 -0
- package/cpp/ggml-cpu/ggml-cpu.c +3578 -0
- package/cpp/ggml-cpu/ggml-cpu.cpp +672 -0
- package/cpp/ggml-cpu/ops.cpp +10587 -0
- package/cpp/ggml-cpu/ops.h +114 -0
- package/cpp/ggml-cpu/quants.c +1193 -0
- package/cpp/ggml-cpu/quants.h +97 -0
- package/cpp/ggml-cpu/repack.cpp +1982 -0
- package/cpp/ggml-cpu/repack.h +120 -0
- package/cpp/ggml-cpu/simd-mappings.h +1184 -0
- package/cpp/ggml-cpu/traits.cpp +36 -0
- package/cpp/ggml-cpu/traits.h +38 -0
- package/cpp/ggml-cpu/unary-ops.cpp +186 -0
- package/cpp/ggml-cpu/unary-ops.h +28 -0
- package/cpp/ggml-cpu/vec.cpp +348 -0
- package/cpp/ggml-cpu/vec.h +1121 -0
- package/cpp/ggml-cpu.h +145 -0
- package/cpp/ggml-impl.h +622 -0
- package/cpp/ggml-metal-impl.h +688 -0
- package/cpp/ggml-metal.h +66 -0
- package/cpp/ggml-metal.m +6833 -0
- package/cpp/ggml-metal.metal +10754 -0
- package/cpp/ggml-opt.cpp +1093 -0
- package/cpp/ggml-opt.h +256 -0
- package/cpp/ggml-quants.c +5324 -0
- package/cpp/ggml-quants.h +106 -0
- package/cpp/ggml-threading.cpp +12 -0
- package/cpp/ggml-threading.h +14 -0
- package/cpp/ggml.c +7108 -0
- package/cpp/ggml.h +2492 -0
- package/cpp/gguf.cpp +1358 -0
- package/cpp/gguf.h +202 -0
- package/cpp/json-partial.cpp +256 -0
- package/cpp/json-partial.h +38 -0
- package/cpp/json-schema-to-grammar.cpp +985 -0
- package/cpp/json-schema-to-grammar.h +21 -0
- package/cpp/llama-adapter.cpp +388 -0
- package/cpp/llama-adapter.h +76 -0
- package/cpp/llama-arch.cpp +2355 -0
- package/cpp/llama-arch.h +499 -0
- package/cpp/llama-batch.cpp +875 -0
- package/cpp/llama-batch.h +160 -0
- package/cpp/llama-chat.cpp +783 -0
- package/cpp/llama-chat.h +65 -0
- package/cpp/llama-context.cpp +2788 -0
- package/cpp/llama-context.h +306 -0
- package/cpp/llama-cparams.cpp +5 -0
- package/cpp/llama-cparams.h +41 -0
- package/cpp/llama-cpp.h +30 -0
- package/cpp/llama-grammar.cpp +1229 -0
- package/cpp/llama-grammar.h +173 -0
- package/cpp/llama-graph.cpp +1891 -0
- package/cpp/llama-graph.h +810 -0
- package/cpp/llama-hparams.cpp +180 -0
- package/cpp/llama-hparams.h +233 -0
- package/cpp/llama-impl.cpp +167 -0
- package/cpp/llama-impl.h +61 -0
- package/cpp/llama-io.cpp +15 -0
- package/cpp/llama-io.h +35 -0
- package/cpp/llama-kv-cache-iswa.cpp +318 -0
- package/cpp/llama-kv-cache-iswa.h +135 -0
- package/cpp/llama-kv-cache.cpp +2059 -0
- package/cpp/llama-kv-cache.h +374 -0
- package/cpp/llama-kv-cells.h +491 -0
- package/cpp/llama-memory-hybrid.cpp +258 -0
- package/cpp/llama-memory-hybrid.h +137 -0
- package/cpp/llama-memory-recurrent.cpp +1146 -0
- package/cpp/llama-memory-recurrent.h +179 -0
- package/cpp/llama-memory.cpp +59 -0
- package/cpp/llama-memory.h +119 -0
- package/cpp/llama-mmap.cpp +609 -0
- package/cpp/llama-mmap.h +68 -0
- package/cpp/llama-model-loader.cpp +1166 -0
- package/cpp/llama-model-loader.h +170 -0
- package/cpp/llama-model-saver.cpp +282 -0
- package/cpp/llama-model-saver.h +37 -0
- package/cpp/llama-model.cpp +19061 -0
- package/cpp/llama-model.h +491 -0
- package/cpp/llama-sampling.cpp +2575 -0
- package/cpp/llama-sampling.h +32 -0
- package/cpp/llama-vocab.cpp +3792 -0
- package/cpp/llama-vocab.h +176 -0
- package/cpp/llama.cpp +358 -0
- package/cpp/llama.h +1373 -0
- package/cpp/log.cpp +428 -0
- package/cpp/log.h +103 -0
- package/cpp/minja/chat-template.hpp +550 -0
- package/cpp/minja/minja.hpp +3009 -0
- package/cpp/nlohmann/json.hpp +25526 -0
- package/cpp/nlohmann/json_fwd.hpp +187 -0
- package/cpp/regex-partial.cpp +204 -0
- package/cpp/regex-partial.h +56 -0
- package/cpp/sampling.cpp +579 -0
- package/cpp/sampling.h +107 -0
- package/cpp/tools/mtmd/clip-impl.h +473 -0
- package/cpp/tools/mtmd/clip.cpp +4322 -0
- package/cpp/tools/mtmd/clip.h +106 -0
- package/cpp/tools/mtmd/miniaudio/miniaudio.h +93468 -0
- package/cpp/tools/mtmd/mtmd-audio.cpp +769 -0
- package/cpp/tools/mtmd/mtmd-audio.h +47 -0
- package/cpp/tools/mtmd/mtmd-helper.cpp +460 -0
- package/cpp/tools/mtmd/mtmd-helper.h +95 -0
- package/cpp/tools/mtmd/mtmd.cpp +1066 -0
- package/cpp/tools/mtmd/mtmd.h +302 -0
- package/cpp/tools/mtmd/stb/stb_image.h +7988 -0
- package/cpp/unicode-data.cpp +7034 -0
- package/cpp/unicode-data.h +20 -0
- package/cpp/unicode.cpp +1061 -0
- package/cpp/unicode.h +68 -0
- package/cpp/vendor/cpp-httplib/httplib.cpp +16509 -0
- package/cpp/vendor/cpp-httplib/httplib.h +3883 -0
- package/desktop/electron-builder.config.cjs +157 -0
- package/desktop/entitlements.mac.plist +12 -0
- package/desktop/package.json +11 -0
- package/desktop/resolve-package-root.cjs +88 -0
- package/desktop/src/main/backend-selector.cjs +148 -0
- package/desktop/src/main/gpu-probe.cjs +142 -0
- package/desktop/src/main/index.cjs +58 -0
- package/desktop/src/main/ipc-handlers.cjs +212 -0
- package/desktop/src/main/model-store.cjs +50 -0
- package/desktop/src/main/preload.cjs +39 -0
- package/desktop/src/main/sidecar-client.cjs +76 -0
- package/desktop/src/main/sidecar-manager.cjs +364 -0
- package/dist/docs.json +14357 -0
- package/dist/esm/definitions.d.ts +731 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/desktop.d.ts +37 -0
- package/dist/esm/desktop.js +133 -0
- package/dist/esm/desktop.js.map +1 -0
- package/dist/esm/index.d.ts +200 -0
- package/dist/esm/index.js +612 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/isomorphic/desktop.runtime.d.ts +37 -0
- package/dist/esm/isomorphic/desktop.runtime.js +36 -0
- package/dist/esm/isomorphic/desktop.runtime.js.map +1 -0
- package/dist/esm/isomorphic/errors.d.ts +6 -0
- package/dist/esm/isomorphic/errors.js +9 -0
- package/dist/esm/isomorphic/errors.js.map +1 -0
- package/dist/esm/isomorphic/model.admission.d.ts +17 -0
- package/dist/esm/isomorphic/model.admission.js +27 -0
- package/dist/esm/isomorphic/model.admission.js.map +1 -0
- package/dist/esm/isomorphic/model.scheduler.d.ts +31 -0
- package/dist/esm/isomorphic/model.scheduler.js +95 -0
- package/dist/esm/isomorphic/model.scheduler.js.map +1 -0
- package/dist/esm/isomorphic/provider.desktop.d.ts +40 -0
- package/dist/esm/isomorphic/provider.desktop.js +411 -0
- package/dist/esm/isomorphic/provider.desktop.js.map +1 -0
- package/dist/esm/isomorphic/provider.factory.d.ts +2 -0
- package/dist/esm/isomorphic/provider.factory.js +16 -0
- package/dist/esm/isomorphic/provider.factory.js.map +1 -0
- package/dist/esm/isomorphic/provider.interface.d.ts +76 -0
- package/dist/esm/isomorphic/provider.interface.js +2 -0
- package/dist/esm/isomorphic/provider.interface.js.map +1 -0
- package/dist/esm/isomorphic/provider.native.d.ts +18 -0
- package/dist/esm/isomorphic/provider.native.js +173 -0
- package/dist/esm/isomorphic/provider.native.js.map +1 -0
- package/dist/esm/isomorphic/provider.web.d.ts +88 -0
- package/dist/esm/isomorphic/provider.web.js +573 -0
- package/dist/esm/isomorphic/provider.web.js.map +1 -0
- package/dist/esm/isomorphic/sidecar-sse.d.ts +14 -0
- package/dist/esm/isomorphic/sidecar-sse.js +76 -0
- package/dist/esm/isomorphic/sidecar-sse.js.map +1 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.d.ts +26 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.js +60 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.js.map +1 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.d.ts +55 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.js +93 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.js.map +1 -0
- package/dist/esm/storage/manifest.d.ts +13 -0
- package/dist/esm/storage/manifest.js +87 -0
- package/dist/esm/storage/manifest.js.map +1 -0
- package/dist/esm/storage/opfs.store.d.ts +31 -0
- package/dist/esm/storage/opfs.store.js +241 -0
- package/dist/esm/storage/opfs.store.js.map +1 -0
- package/dist/esm/web.d.ts +206 -0
- package/dist/esm/web.js +521 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/esm/workers/async-file.d.ts +13 -0
- package/dist/esm/workers/async-file.js +12 -0
- package/dist/esm/workers/async-file.js.map +1 -0
- package/dist/esm/workers/heapfs.d.ts +55 -0
- package/dist/esm/workers/heapfs.js +115 -0
- package/dist/esm/workers/heapfs.js.map +1 -0
- package/dist/esm/workers/wasm.engine.d.ts +86 -0
- package/dist/esm/workers/wasm.engine.js +504 -0
- package/dist/esm/workers/wasm.engine.js.map +1 -0
- package/dist/esm/workers/worker.protocol.d.ts +160 -0
- package/dist/esm/workers/worker.protocol.js +2 -0
- package/dist/esm/workers/worker.protocol.js.map +1 -0
- package/dist/plugin.cjs +3179 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.js +3181 -0
- package/dist/plugin.js.map +1 -0
- package/dist/wasm/llama_engine.d.ts +74 -0
- package/dist/wasm/llama_engine.js +1829 -0
- package/dist/wasm/llama_engine.wasm +0 -0
- package/dist/wasm/llama_engine_emscripten.mjs +2 -0
- package/dist/wasm/package.json +16 -0
- package/dist/workers/llm.worker.js +1226 -0
- package/dist/workers/llm.worker.js.map +7 -0
- package/extraResources/llama-wasm/llama_engine.d.ts +74 -0
- package/extraResources/llama-wasm/llama_engine.js +1829 -0
- package/extraResources/llama-wasm/llama_engine.wasm +0 -0
- package/extraResources/llama-wasm/llama_engine_emscripten.mjs +2 -0
- package/extraResources/llama-wasm/package.json +16 -0
- package/extraResources/sidecar/README.md +11 -0
- package/extraResources/sidecar/darwin-arm64 +0 -0
- package/extraResources/sidecar/darwin-x64 +0 -0
- package/ios/CMakeLists-arm64.txt +161 -0
- package/ios/CMakeLists-x86_64.txt +188 -0
- package/ios/CMakeLists.txt +161 -0
- package/ios/Frameworks/llama-cpp.framework/Info.plist +28 -0
- package/ios/Frameworks/llama-cpp.framework/llama-cpp +0 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaCpp.swift +1365 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaCppPlugin.swift +694 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaNativeBridge.swift +349 -0
- package/ios/Sources/LlamaCppCapacitor/ModelAdmissionController.swift +177 -0
- package/ios/embed-metal-shaders.sh +24 -0
- package/ios/metal-embed.cmake +29 -0
- package/package.json +281 -0
- package/scripts/build-js-preserve-wasm.cjs +53 -0
- package/scripts/build-sidecar-linux.sh +6 -0
- package/scripts/build-sidecar-win.bat +19 -0
- package/scripts/build-sidecar.sh +184 -0
- package/scripts/embed-llama-ios-app-framework.sh +63 -0
- package/scripts/ensure-desktop-sidecar-bundle.cjs +103 -0
- package/scripts/ensure-llama-ios-xcframework.sh +108 -0
- package/scripts/fix-esm-extensions.cjs +45 -0
- package/scripts/prepare-js-dist.cjs +28 -0
- package/scripts/stage-desktop-resources.cjs +116 -0
- package/sidecar/CMakeLists.txt +192 -0
- package/sidecar/cap-sidecar-main.cpp +68 -0
- package/types/llama-cpp-pro.d.ts +441 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum LlamaNativeBridge {
|
|
4
|
+
enum Failure: LocalizedError {
|
|
5
|
+
case missingSymbol(String)
|
|
6
|
+
case modelNotFound(String)
|
|
7
|
+
case initializationFailed(String)
|
|
8
|
+
case completionFailed(String)
|
|
9
|
+
case embeddingFailed(String)
|
|
10
|
+
case operationFailed(String)
|
|
11
|
+
|
|
12
|
+
var errorDescription: String? {
|
|
13
|
+
switch self {
|
|
14
|
+
case .missingSymbol(let symbol):
|
|
15
|
+
return "Native symbol \(symbol) is not linked. Rebuild llama-cpp.framework."
|
|
16
|
+
case .modelNotFound(let path):
|
|
17
|
+
return "Model file not found at \(path)"
|
|
18
|
+
case .initializationFailed(let details):
|
|
19
|
+
return "Failed to initialize llama context: \(details)"
|
|
20
|
+
case .completionFailed(let details):
|
|
21
|
+
return "Failed to run local completion: \(details)"
|
|
22
|
+
case .embeddingFailed(let details):
|
|
23
|
+
return "Failed to run local embedding: \(details)"
|
|
24
|
+
case .operationFailed(let details):
|
|
25
|
+
return "Operation failed: \(details)"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private typealias InitContextFn = @convention(c) (UnsafePointer<CChar>, UnsafePointer<CChar>) -> Int64
|
|
31
|
+
private typealias ReleaseContextFn = @convention(c) (Int64) -> Void
|
|
32
|
+
private typealias RunCompletionFn = @convention(c) (Int64, UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>?
|
|
33
|
+
private typealias FreeResultFn = @convention(c) (UnsafeMutablePointer<CChar>) -> Void
|
|
34
|
+
private typealias StopCompletionFn = @convention(c) (Int64) -> Void
|
|
35
|
+
private typealias RunEmbeddingJsonFn = @convention(c) (Int64, UnsafePointer<CChar>, UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>?
|
|
36
|
+
private typealias JsonOpFn = @convention(c) (Int64, UnsafePointer<CChar>, UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>?
|
|
37
|
+
private typealias SingleArgJsonFn = @convention(c) (Int64, UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>?
|
|
38
|
+
private typealias BoolFn = @convention(c) (Int64) -> Int32
|
|
39
|
+
private typealias BoolBoolFn = @convention(c) (Int64, Int32) -> Int32
|
|
40
|
+
private typealias SessionSaveFn = @convention(c) (Int64, UnsafePointer<CChar>, Int32) -> Int32
|
|
41
|
+
private typealias BenchFn = @convention(c) (Int64, Int32, Int32, Int32, Int32) -> UnsafeMutablePointer<CChar>?
|
|
42
|
+
private typealias LoraApplyFn = @convention(c) (Int64, UnsafePointer<CChar>) -> Int32
|
|
43
|
+
private typealias VoidFn = @convention(c) (Int64) -> Void
|
|
44
|
+
private typealias MultimodalInitFn = @convention(c) (Int64, UnsafePointer<CChar>, Int32) -> Int32
|
|
45
|
+
private typealias VocoderInitFn = @convention(c) (Int64, UnsafePointer<CChar>, Int32) -> Int32
|
|
46
|
+
|
|
47
|
+
private static var library: UnsafeMutableRawPointer? = {
|
|
48
|
+
let fm = FileManager.default
|
|
49
|
+
var candidates: [String] = []
|
|
50
|
+
if let fw = Bundle.main.path(forResource: "llama-cpp", ofType: "framework") {
|
|
51
|
+
candidates.append((fw as NSString).appendingPathComponent("llama-cpp"))
|
|
52
|
+
}
|
|
53
|
+
if let exec = Bundle.main.executablePath {
|
|
54
|
+
candidates.append((exec as NSString).deletingLastPathComponent + "/Frameworks/llama-cpp.framework/llama-cpp")
|
|
55
|
+
}
|
|
56
|
+
for path in candidates where fm.fileExists(atPath: path) {
|
|
57
|
+
if let handle = dlopen(path, RTLD_NOW) {
|
|
58
|
+
return handle
|
|
59
|
+
}
|
|
60
|
+
print("[LlamaNativeBridge] dlopen failed for \(path): \(String(cString: dlerror()))")
|
|
61
|
+
}
|
|
62
|
+
print("[LlamaNativeBridge] llama-cpp framework binary not found; tried: \(candidates)")
|
|
63
|
+
return nil
|
|
64
|
+
}()
|
|
65
|
+
|
|
66
|
+
static func sym<T>(_ name: String, _ type: T.Type) throws -> T {
|
|
67
|
+
guard let library else {
|
|
68
|
+
throw Failure.missingSymbol(name)
|
|
69
|
+
}
|
|
70
|
+
guard let pointer = dlsym(library, name) else {
|
|
71
|
+
throw Failure.missingSymbol(name)
|
|
72
|
+
}
|
|
73
|
+
return unsafeBitCast(pointer, to: T.self)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static func trySymOpt<T>(_ name: String, _ type: T.Type) -> T? {
|
|
77
|
+
guard let library, let pointer = dlsym(library, name) else { return nil }
|
|
78
|
+
return unsafeBitCast(pointer, to: T.self)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - Core
|
|
82
|
+
|
|
83
|
+
static func initContext(modelPath: String, paramsJson: String) throws -> Int64 {
|
|
84
|
+
let normalizedPath = normalizeModelPath(modelPath)
|
|
85
|
+
guard FileManager.default.fileExists(atPath: normalizedPath) else {
|
|
86
|
+
throw Failure.modelNotFound(normalizedPath)
|
|
87
|
+
}
|
|
88
|
+
let fn = try sym("llama_init_context", InitContextFn.self)
|
|
89
|
+
let id = normalizedPath.withCString { mp in
|
|
90
|
+
paramsJson.withCString { pj in fn(mp, pj) }
|
|
91
|
+
}
|
|
92
|
+
guard id > 0 else {
|
|
93
|
+
throw Failure.initializationFailed("native loader returned \(id) for \(normalizedPath)")
|
|
94
|
+
}
|
|
95
|
+
return id
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static func releaseContext(_ contextId: Int64) {
|
|
99
|
+
guard let fn = try? sym("llama_release_context", ReleaseContextFn.self) else { return }
|
|
100
|
+
fn(contextId)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static func runCompletion(contextId: Int64, paramsJson: String) throws -> [String: Any] {
|
|
104
|
+
let fn = try sym("llama_run_completion", RunCompletionFn.self)
|
|
105
|
+
let free = try sym("llama_free_completion_result", FreeResultFn.self)
|
|
106
|
+
let ptr = paramsJson.withCString { fn(contextId, $0) }
|
|
107
|
+
guard let ptr else { throw Failure.completionFailed("nil result") }
|
|
108
|
+
defer { free(ptr) }
|
|
109
|
+
return try decodeJsonDictionary(from: ptr, failure: .completionFailed("invalid JSON"))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static func runEmbedding(contextId: Int64, text: String, paramsJson: String) throws -> [String: Any] {
|
|
113
|
+
let fn = try sym("llama_run_embedding_json", RunEmbeddingJsonFn.self)
|
|
114
|
+
let free = try sym("llama_free_completion_result", FreeResultFn.self)
|
|
115
|
+
let ptr = text.withCString { t in paramsJson.withCString { p in fn(contextId, t, p) } }
|
|
116
|
+
guard let ptr else { throw Failure.embeddingFailed("nil result") }
|
|
117
|
+
defer { free(ptr) }
|
|
118
|
+
return try decodeJsonDictionary(from: ptr, failure: .embeddingFailed("invalid JSON"))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static func stopCompletion(_ contextId: Int64) {
|
|
122
|
+
guard let fn = try? sym("llama_stop_completion", StopCompletionFn.self) else { return }
|
|
123
|
+
fn(contextId)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: - Rerank
|
|
127
|
+
|
|
128
|
+
/// Returns JSON: [{"score": float, "index": int}, ...]
|
|
129
|
+
static func runRerank(contextId: Int64, query: String, documentsJson: String) throws -> [[String: Any]] {
|
|
130
|
+
// llama_rerank_json(context_id, query_cstr, docs_json_cstr) -> char*
|
|
131
|
+
guard let fn: JsonOpFn = trySymOpt("llama_rerank_json", JsonOpFn.self),
|
|
132
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
133
|
+
throw Failure.missingSymbol("llama_rerank_json")
|
|
134
|
+
}
|
|
135
|
+
let ptr = query.withCString { q in documentsJson.withCString { d in fn(contextId, q, d) } }
|
|
136
|
+
guard let ptr else { throw Failure.operationFailed("rerank returned nil") }
|
|
137
|
+
defer { free(ptr) }
|
|
138
|
+
let json = String(cString: ptr)
|
|
139
|
+
guard let data = json.data(using: .utf8),
|
|
140
|
+
let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
141
|
+
throw Failure.operationFailed("rerank returned invalid JSON")
|
|
142
|
+
}
|
|
143
|
+
return arr
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// MARK: - Bench
|
|
147
|
+
|
|
148
|
+
/// Returns the raw bench result string "[modelDesc, size, nParams, ppAvg, ppStd, tgAvg, tgStd]"
|
|
149
|
+
static func runBench(contextId: Int64, pp: Int, tg: Int, pl: Int, nr: Int) throws -> String {
|
|
150
|
+
guard let fn: BenchFn = trySymOpt("llama_bench", BenchFn.self),
|
|
151
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
152
|
+
throw Failure.missingSymbol("llama_bench")
|
|
153
|
+
}
|
|
154
|
+
let ptr = fn(contextId, Int32(pp), Int32(tg), Int32(pl), Int32(nr))
|
|
155
|
+
guard let ptr else { return "[]" }
|
|
156
|
+
defer { free(ptr) }
|
|
157
|
+
return String(cString: ptr)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// MARK: - Session management
|
|
161
|
+
|
|
162
|
+
static func loadSession(contextId: Int64, filepath: String) throws -> [String: Any] {
|
|
163
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_cap_load_session_file", SingleArgJsonFn.self),
|
|
164
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
165
|
+
throw Failure.missingSymbol("llama_cap_load_session_file")
|
|
166
|
+
}
|
|
167
|
+
let ptr = filepath.withCString { fn(contextId, $0) }
|
|
168
|
+
guard let ptr else { throw Failure.operationFailed("loadSession returned nil for \(filepath)") }
|
|
169
|
+
defer { free(ptr) }
|
|
170
|
+
return try decodeJsonDictionary(from: ptr, failure: .operationFailed("loadSession invalid JSON"))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static func saveSession(contextId: Int64, filepath: String, size: Int) throws -> Int {
|
|
174
|
+
guard let fn: SessionSaveFn = trySymOpt("llama_cap_save_session_file", SessionSaveFn.self) else {
|
|
175
|
+
throw Failure.missingSymbol("llama_cap_save_session_file")
|
|
176
|
+
}
|
|
177
|
+
let saved = filepath.withCString { fn(contextId, $0, Int32(size)) }
|
|
178
|
+
return Int(saved)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// MARK: - LoRA
|
|
182
|
+
|
|
183
|
+
/// loraAdaptersJson: JSON array [{path:string, scale:float}, ...]
|
|
184
|
+
static func applyLoraAdapters(contextId: Int64, loraAdaptersJson: String) throws -> Int {
|
|
185
|
+
guard let fn: LoraApplyFn = trySymOpt("llama_apply_lora_adapters", LoraApplyFn.self) else {
|
|
186
|
+
throw Failure.missingSymbol("llama_apply_lora_adapters")
|
|
187
|
+
}
|
|
188
|
+
let result = loraAdaptersJson.withCString { fn(contextId, $0) }
|
|
189
|
+
if result < 0 {
|
|
190
|
+
throw Failure.operationFailed("applyLoraAdapters returned \(result)")
|
|
191
|
+
}
|
|
192
|
+
return Int(result)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static func removeLoraAdapters(contextId: Int64) throws {
|
|
196
|
+
guard let fn: VoidFn = trySymOpt("llama_remove_lora_adapters", VoidFn.self) else {
|
|
197
|
+
throw Failure.missingSymbol("llama_remove_lora_adapters")
|
|
198
|
+
}
|
|
199
|
+
fn(contextId)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static func getLoadedLoraAdapters(contextId: Int64) throws -> [[String: Any]] {
|
|
203
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_get_loaded_lora_adapters", SingleArgJsonFn.self),
|
|
204
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
205
|
+
throw Failure.missingSymbol("llama_get_loaded_lora_adapters")
|
|
206
|
+
}
|
|
207
|
+
// fn takes (contextId, empty_cstr) — use "" as placeholder
|
|
208
|
+
let ptr = "".withCString { fn(contextId, $0) }
|
|
209
|
+
guard let ptr else { return [] }
|
|
210
|
+
defer { free(ptr) }
|
|
211
|
+
let json = String(cString: ptr)
|
|
212
|
+
guard let data = json.data(using: .utf8),
|
|
213
|
+
let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
214
|
+
return []
|
|
215
|
+
}
|
|
216
|
+
return arr
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// MARK: - Multimodal
|
|
220
|
+
|
|
221
|
+
static func initMultimodal(contextId: Int64, mmProjPath: String, useGpu: Bool) throws -> Bool {
|
|
222
|
+
guard let fn: MultimodalInitFn = trySymOpt("llama_init_multimodal", MultimodalInitFn.self) else {
|
|
223
|
+
throw Failure.missingSymbol("llama_init_multimodal")
|
|
224
|
+
}
|
|
225
|
+
let result = mmProjPath.withCString { fn(contextId, $0, useGpu ? 1 : 0) }
|
|
226
|
+
return result != 0
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static func isMultimodalEnabled(contextId: Int64) -> Bool {
|
|
230
|
+
guard let fn: BoolFn = trySymOpt("llama_is_multimodal_enabled", BoolFn.self) else { return false }
|
|
231
|
+
return fn(contextId) != 0
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static func getMultimodalSupport(contextId: Int64) throws -> [String: Any] {
|
|
235
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_get_multimodal_support", SingleArgJsonFn.self),
|
|
236
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
237
|
+
throw Failure.missingSymbol("llama_get_multimodal_support")
|
|
238
|
+
}
|
|
239
|
+
let ptr = "".withCString { fn(contextId, $0) }
|
|
240
|
+
guard let ptr else { return ["vision": false, "audio": false] }
|
|
241
|
+
defer { free(ptr) }
|
|
242
|
+
return (try? decodeJsonDictionary(from: ptr, failure: .operationFailed("bad JSON"))) ?? ["vision": false, "audio": false]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static func releaseMultimodal(contextId: Int64) {
|
|
246
|
+
guard let fn: VoidFn = trySymOpt("llama_release_multimodal", VoidFn.self) else { return }
|
|
247
|
+
fn(contextId)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// MARK: - TTS / Vocoder
|
|
251
|
+
|
|
252
|
+
static func initVocoder(contextId: Int64, path: String, nBatch: Int) throws -> Bool {
|
|
253
|
+
guard let fn: VocoderInitFn = trySymOpt("llama_init_vocoder", VocoderInitFn.self) else {
|
|
254
|
+
throw Failure.missingSymbol("llama_init_vocoder")
|
|
255
|
+
}
|
|
256
|
+
let result = path.withCString { fn(contextId, $0, Int32(nBatch)) }
|
|
257
|
+
return result != 0
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static func isVocoderEnabled(contextId: Int64) -> Bool {
|
|
261
|
+
guard let fn: BoolFn = trySymOpt("llama_is_vocoder_enabled", BoolFn.self) else { return false }
|
|
262
|
+
return fn(contextId) != 0
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
static func getFormattedAudioCompletion(contextId: Int64, speakerJson: String, text: String) throws -> [String: Any] {
|
|
266
|
+
guard let fn: JsonOpFn = trySymOpt("llama_get_formatted_audio_completion", JsonOpFn.self),
|
|
267
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
268
|
+
throw Failure.missingSymbol("llama_get_formatted_audio_completion")
|
|
269
|
+
}
|
|
270
|
+
let ptr = speakerJson.withCString { sp in text.withCString { tx in fn(contextId, sp, tx) } }
|
|
271
|
+
guard let ptr else { throw Failure.operationFailed("getFormattedAudioCompletion returned nil") }
|
|
272
|
+
defer { free(ptr) }
|
|
273
|
+
return try decodeJsonDictionary(from: ptr, failure: .operationFailed("audio completion invalid JSON"))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static func getAudioCompletionGuideTokens(contextId: Int64, text: String) throws -> [Int] {
|
|
277
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_get_audio_completion_guide_tokens", SingleArgJsonFn.self),
|
|
278
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
279
|
+
throw Failure.missingSymbol("llama_get_audio_completion_guide_tokens")
|
|
280
|
+
}
|
|
281
|
+
let ptr = text.withCString { fn(contextId, $0) }
|
|
282
|
+
guard let ptr else { return [] }
|
|
283
|
+
defer { free(ptr) }
|
|
284
|
+
let json = String(cString: ptr)
|
|
285
|
+
guard let data = json.data(using: .utf8),
|
|
286
|
+
let arr = try? JSONSerialization.jsonObject(with: data) as? [Int] else { return [] }
|
|
287
|
+
return arr
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static func decodeAudioTokens(contextId: Int64, tokensJson: String) throws -> [Float] {
|
|
291
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_decode_audio_tokens", SingleArgJsonFn.self),
|
|
292
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
293
|
+
throw Failure.missingSymbol("llama_decode_audio_tokens")
|
|
294
|
+
}
|
|
295
|
+
let ptr = tokensJson.withCString { fn(contextId, $0) }
|
|
296
|
+
guard let ptr else { return [] }
|
|
297
|
+
defer { free(ptr) }
|
|
298
|
+
let json = String(cString: ptr)
|
|
299
|
+
guard let data = json.data(using: .utf8),
|
|
300
|
+
let arr = try? JSONSerialization.jsonObject(with: data) as? [Double] else { return [] }
|
|
301
|
+
return arr.map { Float($0) }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static func releaseVocoder(contextId: Int64) {
|
|
305
|
+
guard let fn: VoidFn = trySymOpt("llama_release_vocoder", VoidFn.self) else { return }
|
|
306
|
+
fn(contextId)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// MARK: - GPU info
|
|
310
|
+
|
|
311
|
+
/// Returns {"gpu": bool, "reasonNoGPU": string} from native context model JSON
|
|
312
|
+
static func queryGpuInfo(contextId: Int64) -> (gpu: Bool, reason: String) {
|
|
313
|
+
guard let fn: SingleArgJsonFn = trySymOpt("llama_get_context_gpu_info", SingleArgJsonFn.self),
|
|
314
|
+
let free: FreeResultFn = trySymOpt("llama_free_completion_result", FreeResultFn.self) else {
|
|
315
|
+
return (false, "llama_get_context_gpu_info not available")
|
|
316
|
+
}
|
|
317
|
+
let ptr = "".withCString { fn(contextId, $0) }
|
|
318
|
+
guard let ptr else { return (false, "no GPU info returned") }
|
|
319
|
+
defer { free(ptr) }
|
|
320
|
+
let json = String(cString: ptr)
|
|
321
|
+
guard let data = json.data(using: .utf8),
|
|
322
|
+
let d = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
323
|
+
return (false, "GPU info JSON parse error")
|
|
324
|
+
}
|
|
325
|
+
let gpu = (d["gpu"] as? Bool) ?? false
|
|
326
|
+
let reason = (d["reasonNoGPU"] as? String) ?? (gpu ? "" : "Metal/GPU not available")
|
|
327
|
+
return (gpu, reason)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// MARK: - Helpers
|
|
331
|
+
|
|
332
|
+
static func decodeJsonDictionary(
|
|
333
|
+
from resultPointer: UnsafeMutablePointer<CChar>,
|
|
334
|
+
failure: Failure
|
|
335
|
+
) throws -> [String: Any] {
|
|
336
|
+
let jsonString = String(cString: resultPointer)
|
|
337
|
+
guard let data = jsonString.data(using: .utf8) else { throw failure }
|
|
338
|
+
let object = try JSONSerialization.jsonObject(with: data, options: [])
|
|
339
|
+
guard let dictionary = object as? [String: Any] else { throw failure }
|
|
340
|
+
return dictionary
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static func normalizeModelPath(_ modelPath: String) -> String {
|
|
344
|
+
if modelPath.hasPrefix("file://") {
|
|
345
|
+
return String(modelPath.dropFirst("file://".count))
|
|
346
|
+
}
|
|
347
|
+
return modelPath
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Memory admission control for native model loading on iOS.
|
|
4
|
+
///
|
|
5
|
+
/// This mirrors the Web/WASM `DefaultModelScheduler` + `wasmMemoryPolicy` so that all
|
|
6
|
+
/// three platforms share the same isomorphic admission semantics:
|
|
7
|
+
/// - A hard cap on concurrent loaded models (parity with `WASM_MAX_CONCURRENT_MODELS = 5`).
|
|
8
|
+
/// - Per-model footprint estimation based on GGUF file size.
|
|
9
|
+
/// - A device-memory guard that keeps a reserve free after each load.
|
|
10
|
+
///
|
|
11
|
+
/// Unlike the Web path (bounded by a single 2 GB WASM linear-memory pool), the iOS
|
|
12
|
+
/// ceiling is the per-process memory budget reported by `os_proc_available_memory()`
|
|
13
|
+
/// (the headroom before jetsam terminates the app). The controller rejects a load when
|
|
14
|
+
/// either the slot limit is reached or the projected available memory after loading
|
|
15
|
+
/// would fall below the reserve threshold.
|
|
16
|
+
final class ModelAdmissionController {
|
|
17
|
+
|
|
18
|
+
/// Max concurrent native contexts. Parity with `WASM_MAX_CONCURRENT_MODELS`.
|
|
19
|
+
static let maxConcurrentModels = 5
|
|
20
|
+
|
|
21
|
+
/// Keep this much process memory free after a model load (headroom for app + OS).
|
|
22
|
+
static let defaultReserveBytes: UInt64 = 512 * 1024 * 1024 // 512 MB
|
|
23
|
+
|
|
24
|
+
/// Fallback footprint when the GGUF size cannot be determined.
|
|
25
|
+
private static let unknownModelFootprintBytes: UInt64 = 512 * 1024 * 1024
|
|
26
|
+
|
|
27
|
+
enum DeniedBy {
|
|
28
|
+
case limit
|
|
29
|
+
case memory
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Result of an admission decision.
|
|
33
|
+
struct Decision {
|
|
34
|
+
let allow: Bool
|
|
35
|
+
let deniedBy: DeniedBy? // nil when allowed
|
|
36
|
+
let reason: String? // nil when allowed
|
|
37
|
+
let estimatedBytes: UInt64
|
|
38
|
+
|
|
39
|
+
static func allowed(_ estimatedBytes: UInt64) -> Decision {
|
|
40
|
+
Decision(allow: true, deniedBy: nil, reason: nil, estimatedBytes: estimatedBytes)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static func denied(_ deniedBy: DeniedBy, _ reason: String, _ estimatedBytes: UInt64) -> Decision {
|
|
44
|
+
Decision(allow: false, deniedBy: deniedBy, reason: reason, estimatedBytes: estimatedBytes)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Lightweight snapshot of process/device memory (mirrors the Web MemorySnapshot).
|
|
49
|
+
struct MemorySnapshot {
|
|
50
|
+
let totalBytes: UInt64 // device physical memory
|
|
51
|
+
let freeBytes: UInt64 // per-process available memory (headroom before jetsam)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private let maxModels: Int
|
|
55
|
+
private let reserveBytes: UInt64
|
|
56
|
+
|
|
57
|
+
init(maxModels: Int = ModelAdmissionController.maxConcurrentModels,
|
|
58
|
+
reserveBytes: UInt64 = ModelAdmissionController.defaultReserveBytes) {
|
|
59
|
+
self.maxModels = maxModels
|
|
60
|
+
self.reserveBytes = reserveBytes
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var maxModelCount: Int { maxModels }
|
|
64
|
+
|
|
65
|
+
/// Estimate the resident footprint of a model once loaded.
|
|
66
|
+
///
|
|
67
|
+
/// Native loading (mmap or full read) does not duplicate the GGUF the way a naive
|
|
68
|
+
/// heap copy would, but the KV cache and compute buffers add headroom on top of the
|
|
69
|
+
/// mapped weights. The multipliers below match the WASM policy's intent, scaled for
|
|
70
|
+
/// native memory behaviour.
|
|
71
|
+
///
|
|
72
|
+
/// - Parameters:
|
|
73
|
+
/// - fileBytes: GGUF file size in bytes (0 when unknown)
|
|
74
|
+
/// - embedding: whether the context is an embedding-only model (lighter headroom)
|
|
75
|
+
static func estimateModelFootprint(fileBytes: UInt64, embedding: Bool) -> UInt64 {
|
|
76
|
+
guard fileBytes > 0 else {
|
|
77
|
+
return unknownModelFootprintBytes
|
|
78
|
+
}
|
|
79
|
+
let isLarge = fileBytes > 200 * 1024 * 1024
|
|
80
|
+
let weightMultiplier: Double = embedding ? 1.15 : (isLarge ? 1.30 : 1.20)
|
|
81
|
+
let proportionalHeadroom = UInt64((Double(fileBytes) * 0.12).rounded(.up))
|
|
82
|
+
let minHeadroom: UInt64 = embedding ? 48 * 1024 * 1024 : 96 * 1024 * 1024
|
|
83
|
+
let headroom = max(minHeadroom, proportionalHeadroom)
|
|
84
|
+
let weights = UInt64((Double(fileBytes) * weightMultiplier).rounded(.up))
|
|
85
|
+
return weights + headroom
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Read the GGUF file size, returning 0 when the path is missing or unreadable.
|
|
89
|
+
static func fileSizeBytes(_ modelPath: String) -> UInt64 {
|
|
90
|
+
guard !modelPath.isEmpty else { return 0 }
|
|
91
|
+
// Accept both plain paths and file:// URLs.
|
|
92
|
+
let path: String
|
|
93
|
+
if let url = URL(string: modelPath), url.isFileURL {
|
|
94
|
+
path = url.path
|
|
95
|
+
} else {
|
|
96
|
+
path = modelPath
|
|
97
|
+
}
|
|
98
|
+
do {
|
|
99
|
+
let attrs = try FileManager.default.attributesOfItem(atPath: path)
|
|
100
|
+
if let size = attrs[.size] as? NSNumber {
|
|
101
|
+
return size.uint64Value
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
return 0
|
|
105
|
+
}
|
|
106
|
+
return 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Capture a process/device memory snapshot.
|
|
110
|
+
///
|
|
111
|
+
/// `os_proc_available_memory()` (iOS 13+) reports the memory the app can still use
|
|
112
|
+
/// before jetsam terminates it — a more meaningful guard than device-wide free RAM.
|
|
113
|
+
func memorySnapshot() -> MemorySnapshot {
|
|
114
|
+
let total = ProcessInfo.processInfo.physicalMemory
|
|
115
|
+
var free: UInt64 = 0
|
|
116
|
+
if #available(iOS 13.0, *) {
|
|
117
|
+
let available = os_proc_available_memory()
|
|
118
|
+
if available > 0 {
|
|
119
|
+
free = UInt64(available)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return MemorySnapshot(totalBytes: total, freeBytes: free)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Decide whether a new model may be admitted.
|
|
126
|
+
///
|
|
127
|
+
/// - Parameters:
|
|
128
|
+
/// - currentlyLoaded: number of contexts already loaded
|
|
129
|
+
/// - modelPath: path to the GGUF being loaded (used to size the footprint)
|
|
130
|
+
/// - embedding: whether this is an embedding-only context
|
|
131
|
+
func canAdmit(currentlyLoaded: Int, modelPath: String, embedding: Bool) -> Decision {
|
|
132
|
+
let fileBytes = Self.fileSizeBytes(modelPath)
|
|
133
|
+
let estimated = Self.estimateModelFootprint(fileBytes: fileBytes, embedding: embedding)
|
|
134
|
+
|
|
135
|
+
// 1. Slot limit (parity with Web).
|
|
136
|
+
if currentlyLoaded >= maxModels {
|
|
137
|
+
return .denied(
|
|
138
|
+
.limit,
|
|
139
|
+
"Model slot limit reached (\(maxModels) concurrent contexts)",
|
|
140
|
+
estimated
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 2. Process-memory guard: keep `reserveBytes` free after the load.
|
|
145
|
+
let mem = memorySnapshot()
|
|
146
|
+
if mem.freeBytes > 0 {
|
|
147
|
+
// Guard against unsigned underflow.
|
|
148
|
+
if mem.freeBytes <= estimated || (mem.freeBytes - estimated) < reserveBytes {
|
|
149
|
+
return .denied(
|
|
150
|
+
.memory,
|
|
151
|
+
"Insufficient memory: need ~\(toMb(estimated)) MB, "
|
|
152
|
+
+ "\(toMb(mem.freeBytes)) MB available, "
|
|
153
|
+
+ "\(toMb(reserveBytes)) MB reserve required",
|
|
154
|
+
estimated
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return .allowed(estimated)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Snapshot of admission state for diagnostics / health reporting.
|
|
163
|
+
func status(currentlyLoaded: Int) -> [String: Any] {
|
|
164
|
+
let mem = memorySnapshot()
|
|
165
|
+
return [
|
|
166
|
+
"loadedModels": currentlyLoaded,
|
|
167
|
+
"maxModels": maxModels,
|
|
168
|
+
"deviceTotalBytes": mem.totalBytes,
|
|
169
|
+
"processFreeBytes": mem.freeBytes,
|
|
170
|
+
"reserveBytes": reserveBytes
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private func toMb(_ bytes: UInt64) -> UInt64 {
|
|
175
|
+
bytes / (1024 * 1024)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Merge ggml headers into ggml-metal.metal and emit assembly that embeds the source
|
|
3
|
+
# for LM_GGML_METAL_EMBED_LIBRARY (runtime Metal shader compilation).
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
COMMON="$1"
|
|
7
|
+
SOURCE="$2"
|
|
8
|
+
IMPL="$3"
|
|
9
|
+
OUT_ASM="$4"
|
|
10
|
+
OUT_METAL="$5"
|
|
11
|
+
TMP_METAL="${OUT_METAL}.tmp"
|
|
12
|
+
|
|
13
|
+
sed -e "/__embed_ggml-common.h__/r ${COMMON}" -e "/__embed_ggml-common.h__/d" < "${SOURCE}" > "${TMP_METAL}"
|
|
14
|
+
sed -e "/#include \"ggml-metal-impl.h\"/r ${IMPL}" -e "/#include \"ggml-metal-impl.h\"/d" < "${TMP_METAL}" > "${OUT_METAL}"
|
|
15
|
+
rm -f "${TMP_METAL}"
|
|
16
|
+
|
|
17
|
+
cat > "${OUT_ASM}" <<EOF
|
|
18
|
+
.section __DATA,__ggml_metallib
|
|
19
|
+
.globl _lm_ggml_metallib_start
|
|
20
|
+
_lm_ggml_metallib_start:
|
|
21
|
+
.incbin "${OUT_METAL}"
|
|
22
|
+
.globl _lm_ggml_metallib_end
|
|
23
|
+
_lm_ggml_metallib_end:
|
|
24
|
+
EOF
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Embed ggml-metal.metal (with merged headers) for LM_GGML_METAL_EMBED_LIBRARY.
|
|
2
|
+
# Runtime compiles shaders from the embedded source — no default.metallib bundle needed.
|
|
3
|
+
|
|
4
|
+
set(METALLIB_COMMON "${SOURCE_DIR}/ggml-common.h")
|
|
5
|
+
set(METALLIB_SOURCE "${SOURCE_DIR}/ggml-metal.metal")
|
|
6
|
+
set(METALLIB_IMPL "${SOURCE_DIR}/ggml-metal-impl.h")
|
|
7
|
+
set(METALLIB_EMBED_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/embed-metal-shaders.sh")
|
|
8
|
+
|
|
9
|
+
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/autogenerated")
|
|
10
|
+
|
|
11
|
+
set(METALLIB_EMBED_ASM "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/ggml-metal-embed.s")
|
|
12
|
+
set(METALLIB_SOURCE_EMBED "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/ggml-metal-embed.metal")
|
|
13
|
+
|
|
14
|
+
add_custom_command(
|
|
15
|
+
OUTPUT "${METALLIB_EMBED_ASM}"
|
|
16
|
+
COMMAND "${METALLIB_EMBED_SCRIPT}"
|
|
17
|
+
"${METALLIB_COMMON}"
|
|
18
|
+
"${METALLIB_SOURCE}"
|
|
19
|
+
"${METALLIB_IMPL}"
|
|
20
|
+
"${METALLIB_EMBED_ASM}"
|
|
21
|
+
"${METALLIB_SOURCE_EMBED}"
|
|
22
|
+
DEPENDS
|
|
23
|
+
"${METALLIB_EMBED_SCRIPT}"
|
|
24
|
+
"${METALLIB_COMMON}"
|
|
25
|
+
"${METALLIB_SOURCE}"
|
|
26
|
+
"${METALLIB_IMPL}"
|
|
27
|
+
COMMENT "Generate assembly for embedded Metal shader source"
|
|
28
|
+
VERBATIM
|
|
29
|
+
)
|