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.
Files changed (310) hide show
  1. package/CHANGELOG.md +295 -0
  2. package/LICENSE +21 -0
  3. package/LlamaCpp.podspec +33 -0
  4. package/LlamaCppCapacitor.podspec +33 -0
  5. package/Package.swift +29 -0
  6. package/README.md +93 -0
  7. package/android/build.gradle +88 -0
  8. package/android/src/main/AndroidManifest.xml +4 -0
  9. package/android/src/main/CMakeLists-arm64.txt +138 -0
  10. package/android/src/main/CMakeLists-x86_64.txt +141 -0
  11. package/android/src/main/CMakeLists.txt +138 -0
  12. package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCpp.java +1340 -0
  13. package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCppPlugin.java +814 -0
  14. package/android/src/main/java/ai/annadata/plugin/capacitor/ModelAdmissionController.java +214 -0
  15. package/android/src/main/jni-chat-session.cpp +261 -0
  16. package/android/src/main/jni-lora.cpp +197 -0
  17. package/android/src/main/jni-multimodal.cpp +167 -0
  18. package/android/src/main/jni-tts.cpp +290 -0
  19. package/android/src/main/jni-utils.h +148 -0
  20. package/android/src/main/jni.cpp +1808 -0
  21. package/android/src/main/jniLibs/arm64-v8a/libllama-cpp-arm64.so +0 -0
  22. package/android/src/main/res/.gitkeep +0 -0
  23. package/build-native.sh +280 -0
  24. package/cmake/desktop-metal-embed.cmake +30 -0
  25. package/cmake/desktop-sources.cmake +100 -0
  26. package/cmake/ggml-backends.cmake +44 -0
  27. package/cpp/LICENSE +21 -0
  28. package/cpp/README.md +15 -0
  29. package/cpp/anyascii.c +22223 -0
  30. package/cpp/anyascii.h +42 -0
  31. package/cpp/cap-completion.cpp +942 -0
  32. package/cpp/cap-completion.h +127 -0
  33. package/cpp/cap-embedding.cpp +193 -0
  34. package/cpp/cap-embedding.h +35 -0
  35. package/cpp/cap-ios-bridge.cpp +1810 -0
  36. package/cpp/cap-ios-bridge.h +61 -0
  37. package/cpp/cap-llama.cpp +412 -0
  38. package/cpp/cap-llama.h +161 -0
  39. package/cpp/cap-mtmd.hpp +602 -0
  40. package/cpp/cap-native-server.cpp +1095 -0
  41. package/cpp/cap-native-server.h +40 -0
  42. package/cpp/cap-tts.cpp +591 -0
  43. package/cpp/cap-tts.h +59 -0
  44. package/cpp/cap-wasm-fs.cpp +156 -0
  45. package/cpp/cap-wasm-jspi.cpp +19 -0
  46. package/cpp/cap-wasm-jspi.h +30 -0
  47. package/cpp/cap-wasm-vfs.cpp +22 -0
  48. package/cpp/chat-parser.cpp +393 -0
  49. package/cpp/chat-parser.h +120 -0
  50. package/cpp/chat.cpp +2315 -0
  51. package/cpp/chat.h +221 -0
  52. package/cpp/common.cpp +1664 -0
  53. package/cpp/common.h +744 -0
  54. package/cpp/ggml-alloc.c +1028 -0
  55. package/cpp/ggml-alloc.h +76 -0
  56. package/cpp/ggml-backend-impl.h +255 -0
  57. package/cpp/ggml-backend-reg.cpp +600 -0
  58. package/cpp/ggml-backend.cpp +2121 -0
  59. package/cpp/ggml-backend.h +354 -0
  60. package/cpp/ggml-common.h +1878 -0
  61. package/cpp/ggml-cpp.h +39 -0
  62. package/cpp/ggml-cpu/amx/amx.cpp +221 -0
  63. package/cpp/ggml-cpu/amx/amx.h +8 -0
  64. package/cpp/ggml-cpu/amx/common.h +91 -0
  65. package/cpp/ggml-cpu/amx/mmq.cpp +2512 -0
  66. package/cpp/ggml-cpu/amx/mmq.h +10 -0
  67. package/cpp/ggml-cpu/arch/arm/cpu-feats.cpp +94 -0
  68. package/cpp/ggml-cpu/arch/arm/quants.c +3650 -0
  69. package/cpp/ggml-cpu/arch/arm/repack.cpp +1891 -0
  70. package/cpp/ggml-cpu/arch/x86/cpu-feats.cpp +327 -0
  71. package/cpp/ggml-cpu/arch/x86/quants.c +3820 -0
  72. package/cpp/ggml-cpu/arch/x86/repack.cpp +6307 -0
  73. package/cpp/ggml-cpu/arch-fallback.h +215 -0
  74. package/cpp/ggml-cpu/binary-ops.cpp +158 -0
  75. package/cpp/ggml-cpu/binary-ops.h +16 -0
  76. package/cpp/ggml-cpu/common.h +73 -0
  77. package/cpp/ggml-cpu/ggml-cpu-impl.h +559 -0
  78. package/cpp/ggml-cpu/ggml-cpu.c +3578 -0
  79. package/cpp/ggml-cpu/ggml-cpu.cpp +672 -0
  80. package/cpp/ggml-cpu/ops.cpp +10587 -0
  81. package/cpp/ggml-cpu/ops.h +114 -0
  82. package/cpp/ggml-cpu/quants.c +1193 -0
  83. package/cpp/ggml-cpu/quants.h +97 -0
  84. package/cpp/ggml-cpu/repack.cpp +1982 -0
  85. package/cpp/ggml-cpu/repack.h +120 -0
  86. package/cpp/ggml-cpu/simd-mappings.h +1184 -0
  87. package/cpp/ggml-cpu/traits.cpp +36 -0
  88. package/cpp/ggml-cpu/traits.h +38 -0
  89. package/cpp/ggml-cpu/unary-ops.cpp +186 -0
  90. package/cpp/ggml-cpu/unary-ops.h +28 -0
  91. package/cpp/ggml-cpu/vec.cpp +348 -0
  92. package/cpp/ggml-cpu/vec.h +1121 -0
  93. package/cpp/ggml-cpu.h +145 -0
  94. package/cpp/ggml-impl.h +622 -0
  95. package/cpp/ggml-metal-impl.h +688 -0
  96. package/cpp/ggml-metal.h +66 -0
  97. package/cpp/ggml-metal.m +6833 -0
  98. package/cpp/ggml-metal.metal +10754 -0
  99. package/cpp/ggml-opt.cpp +1093 -0
  100. package/cpp/ggml-opt.h +256 -0
  101. package/cpp/ggml-quants.c +5324 -0
  102. package/cpp/ggml-quants.h +106 -0
  103. package/cpp/ggml-threading.cpp +12 -0
  104. package/cpp/ggml-threading.h +14 -0
  105. package/cpp/ggml.c +7108 -0
  106. package/cpp/ggml.h +2492 -0
  107. package/cpp/gguf.cpp +1358 -0
  108. package/cpp/gguf.h +202 -0
  109. package/cpp/json-partial.cpp +256 -0
  110. package/cpp/json-partial.h +38 -0
  111. package/cpp/json-schema-to-grammar.cpp +985 -0
  112. package/cpp/json-schema-to-grammar.h +21 -0
  113. package/cpp/llama-adapter.cpp +388 -0
  114. package/cpp/llama-adapter.h +76 -0
  115. package/cpp/llama-arch.cpp +2355 -0
  116. package/cpp/llama-arch.h +499 -0
  117. package/cpp/llama-batch.cpp +875 -0
  118. package/cpp/llama-batch.h +160 -0
  119. package/cpp/llama-chat.cpp +783 -0
  120. package/cpp/llama-chat.h +65 -0
  121. package/cpp/llama-context.cpp +2788 -0
  122. package/cpp/llama-context.h +306 -0
  123. package/cpp/llama-cparams.cpp +5 -0
  124. package/cpp/llama-cparams.h +41 -0
  125. package/cpp/llama-cpp.h +30 -0
  126. package/cpp/llama-grammar.cpp +1229 -0
  127. package/cpp/llama-grammar.h +173 -0
  128. package/cpp/llama-graph.cpp +1891 -0
  129. package/cpp/llama-graph.h +810 -0
  130. package/cpp/llama-hparams.cpp +180 -0
  131. package/cpp/llama-hparams.h +233 -0
  132. package/cpp/llama-impl.cpp +167 -0
  133. package/cpp/llama-impl.h +61 -0
  134. package/cpp/llama-io.cpp +15 -0
  135. package/cpp/llama-io.h +35 -0
  136. package/cpp/llama-kv-cache-iswa.cpp +318 -0
  137. package/cpp/llama-kv-cache-iswa.h +135 -0
  138. package/cpp/llama-kv-cache.cpp +2059 -0
  139. package/cpp/llama-kv-cache.h +374 -0
  140. package/cpp/llama-kv-cells.h +491 -0
  141. package/cpp/llama-memory-hybrid.cpp +258 -0
  142. package/cpp/llama-memory-hybrid.h +137 -0
  143. package/cpp/llama-memory-recurrent.cpp +1146 -0
  144. package/cpp/llama-memory-recurrent.h +179 -0
  145. package/cpp/llama-memory.cpp +59 -0
  146. package/cpp/llama-memory.h +119 -0
  147. package/cpp/llama-mmap.cpp +609 -0
  148. package/cpp/llama-mmap.h +68 -0
  149. package/cpp/llama-model-loader.cpp +1166 -0
  150. package/cpp/llama-model-loader.h +170 -0
  151. package/cpp/llama-model-saver.cpp +282 -0
  152. package/cpp/llama-model-saver.h +37 -0
  153. package/cpp/llama-model.cpp +19061 -0
  154. package/cpp/llama-model.h +491 -0
  155. package/cpp/llama-sampling.cpp +2575 -0
  156. package/cpp/llama-sampling.h +32 -0
  157. package/cpp/llama-vocab.cpp +3792 -0
  158. package/cpp/llama-vocab.h +176 -0
  159. package/cpp/llama.cpp +358 -0
  160. package/cpp/llama.h +1373 -0
  161. package/cpp/log.cpp +428 -0
  162. package/cpp/log.h +103 -0
  163. package/cpp/minja/chat-template.hpp +550 -0
  164. package/cpp/minja/minja.hpp +3009 -0
  165. package/cpp/nlohmann/json.hpp +25526 -0
  166. package/cpp/nlohmann/json_fwd.hpp +187 -0
  167. package/cpp/regex-partial.cpp +204 -0
  168. package/cpp/regex-partial.h +56 -0
  169. package/cpp/sampling.cpp +579 -0
  170. package/cpp/sampling.h +107 -0
  171. package/cpp/tools/mtmd/clip-impl.h +473 -0
  172. package/cpp/tools/mtmd/clip.cpp +4322 -0
  173. package/cpp/tools/mtmd/clip.h +106 -0
  174. package/cpp/tools/mtmd/miniaudio/miniaudio.h +93468 -0
  175. package/cpp/tools/mtmd/mtmd-audio.cpp +769 -0
  176. package/cpp/tools/mtmd/mtmd-audio.h +47 -0
  177. package/cpp/tools/mtmd/mtmd-helper.cpp +460 -0
  178. package/cpp/tools/mtmd/mtmd-helper.h +95 -0
  179. package/cpp/tools/mtmd/mtmd.cpp +1066 -0
  180. package/cpp/tools/mtmd/mtmd.h +302 -0
  181. package/cpp/tools/mtmd/stb/stb_image.h +7988 -0
  182. package/cpp/unicode-data.cpp +7034 -0
  183. package/cpp/unicode-data.h +20 -0
  184. package/cpp/unicode.cpp +1061 -0
  185. package/cpp/unicode.h +68 -0
  186. package/cpp/vendor/cpp-httplib/httplib.cpp +16509 -0
  187. package/cpp/vendor/cpp-httplib/httplib.h +3883 -0
  188. package/desktop/electron-builder.config.cjs +157 -0
  189. package/desktop/entitlements.mac.plist +12 -0
  190. package/desktop/package.json +11 -0
  191. package/desktop/resolve-package-root.cjs +88 -0
  192. package/desktop/src/main/backend-selector.cjs +148 -0
  193. package/desktop/src/main/gpu-probe.cjs +142 -0
  194. package/desktop/src/main/index.cjs +58 -0
  195. package/desktop/src/main/ipc-handlers.cjs +212 -0
  196. package/desktop/src/main/model-store.cjs +50 -0
  197. package/desktop/src/main/preload.cjs +39 -0
  198. package/desktop/src/main/sidecar-client.cjs +76 -0
  199. package/desktop/src/main/sidecar-manager.cjs +364 -0
  200. package/dist/docs.json +14357 -0
  201. package/dist/esm/definitions.d.ts +731 -0
  202. package/dist/esm/definitions.js +2 -0
  203. package/dist/esm/definitions.js.map +1 -0
  204. package/dist/esm/desktop.d.ts +37 -0
  205. package/dist/esm/desktop.js +133 -0
  206. package/dist/esm/desktop.js.map +1 -0
  207. package/dist/esm/index.d.ts +200 -0
  208. package/dist/esm/index.js +612 -0
  209. package/dist/esm/index.js.map +1 -0
  210. package/dist/esm/isomorphic/desktop.runtime.d.ts +37 -0
  211. package/dist/esm/isomorphic/desktop.runtime.js +36 -0
  212. package/dist/esm/isomorphic/desktop.runtime.js.map +1 -0
  213. package/dist/esm/isomorphic/errors.d.ts +6 -0
  214. package/dist/esm/isomorphic/errors.js +9 -0
  215. package/dist/esm/isomorphic/errors.js.map +1 -0
  216. package/dist/esm/isomorphic/model.admission.d.ts +17 -0
  217. package/dist/esm/isomorphic/model.admission.js +27 -0
  218. package/dist/esm/isomorphic/model.admission.js.map +1 -0
  219. package/dist/esm/isomorphic/model.scheduler.d.ts +31 -0
  220. package/dist/esm/isomorphic/model.scheduler.js +95 -0
  221. package/dist/esm/isomorphic/model.scheduler.js.map +1 -0
  222. package/dist/esm/isomorphic/provider.desktop.d.ts +40 -0
  223. package/dist/esm/isomorphic/provider.desktop.js +411 -0
  224. package/dist/esm/isomorphic/provider.desktop.js.map +1 -0
  225. package/dist/esm/isomorphic/provider.factory.d.ts +2 -0
  226. package/dist/esm/isomorphic/provider.factory.js +16 -0
  227. package/dist/esm/isomorphic/provider.factory.js.map +1 -0
  228. package/dist/esm/isomorphic/provider.interface.d.ts +76 -0
  229. package/dist/esm/isomorphic/provider.interface.js +2 -0
  230. package/dist/esm/isomorphic/provider.interface.js.map +1 -0
  231. package/dist/esm/isomorphic/provider.native.d.ts +18 -0
  232. package/dist/esm/isomorphic/provider.native.js +173 -0
  233. package/dist/esm/isomorphic/provider.native.js.map +1 -0
  234. package/dist/esm/isomorphic/provider.web.d.ts +88 -0
  235. package/dist/esm/isomorphic/provider.web.js +573 -0
  236. package/dist/esm/isomorphic/provider.web.js.map +1 -0
  237. package/dist/esm/isomorphic/sidecar-sse.d.ts +14 -0
  238. package/dist/esm/isomorphic/sidecar-sse.js +76 -0
  239. package/dist/esm/isomorphic/sidecar-sse.js.map +1 -0
  240. package/dist/esm/isomorphic/wasmMemoryCalibration.d.ts +26 -0
  241. package/dist/esm/isomorphic/wasmMemoryCalibration.js +60 -0
  242. package/dist/esm/isomorphic/wasmMemoryCalibration.js.map +1 -0
  243. package/dist/esm/isomorphic/wasmMemoryPolicy.d.ts +55 -0
  244. package/dist/esm/isomorphic/wasmMemoryPolicy.js +93 -0
  245. package/dist/esm/isomorphic/wasmMemoryPolicy.js.map +1 -0
  246. package/dist/esm/storage/manifest.d.ts +13 -0
  247. package/dist/esm/storage/manifest.js +87 -0
  248. package/dist/esm/storage/manifest.js.map +1 -0
  249. package/dist/esm/storage/opfs.store.d.ts +31 -0
  250. package/dist/esm/storage/opfs.store.js +241 -0
  251. package/dist/esm/storage/opfs.store.js.map +1 -0
  252. package/dist/esm/web.d.ts +206 -0
  253. package/dist/esm/web.js +521 -0
  254. package/dist/esm/web.js.map +1 -0
  255. package/dist/esm/workers/async-file.d.ts +13 -0
  256. package/dist/esm/workers/async-file.js +12 -0
  257. package/dist/esm/workers/async-file.js.map +1 -0
  258. package/dist/esm/workers/heapfs.d.ts +55 -0
  259. package/dist/esm/workers/heapfs.js +115 -0
  260. package/dist/esm/workers/heapfs.js.map +1 -0
  261. package/dist/esm/workers/wasm.engine.d.ts +86 -0
  262. package/dist/esm/workers/wasm.engine.js +504 -0
  263. package/dist/esm/workers/wasm.engine.js.map +1 -0
  264. package/dist/esm/workers/worker.protocol.d.ts +160 -0
  265. package/dist/esm/workers/worker.protocol.js +2 -0
  266. package/dist/esm/workers/worker.protocol.js.map +1 -0
  267. package/dist/plugin.cjs +3179 -0
  268. package/dist/plugin.cjs.map +1 -0
  269. package/dist/plugin.js +3181 -0
  270. package/dist/plugin.js.map +1 -0
  271. package/dist/wasm/llama_engine.d.ts +74 -0
  272. package/dist/wasm/llama_engine.js +1829 -0
  273. package/dist/wasm/llama_engine.wasm +0 -0
  274. package/dist/wasm/llama_engine_emscripten.mjs +2 -0
  275. package/dist/wasm/package.json +16 -0
  276. package/dist/workers/llm.worker.js +1226 -0
  277. package/dist/workers/llm.worker.js.map +7 -0
  278. package/extraResources/llama-wasm/llama_engine.d.ts +74 -0
  279. package/extraResources/llama-wasm/llama_engine.js +1829 -0
  280. package/extraResources/llama-wasm/llama_engine.wasm +0 -0
  281. package/extraResources/llama-wasm/llama_engine_emscripten.mjs +2 -0
  282. package/extraResources/llama-wasm/package.json +16 -0
  283. package/extraResources/sidecar/README.md +11 -0
  284. package/extraResources/sidecar/darwin-arm64 +0 -0
  285. package/extraResources/sidecar/darwin-x64 +0 -0
  286. package/ios/CMakeLists-arm64.txt +161 -0
  287. package/ios/CMakeLists-x86_64.txt +188 -0
  288. package/ios/CMakeLists.txt +161 -0
  289. package/ios/Frameworks/llama-cpp.framework/Info.plist +28 -0
  290. package/ios/Frameworks/llama-cpp.framework/llama-cpp +0 -0
  291. package/ios/Sources/LlamaCppCapacitor/LlamaCpp.swift +1365 -0
  292. package/ios/Sources/LlamaCppCapacitor/LlamaCppPlugin.swift +694 -0
  293. package/ios/Sources/LlamaCppCapacitor/LlamaNativeBridge.swift +349 -0
  294. package/ios/Sources/LlamaCppCapacitor/ModelAdmissionController.swift +177 -0
  295. package/ios/embed-metal-shaders.sh +24 -0
  296. package/ios/metal-embed.cmake +29 -0
  297. package/package.json +281 -0
  298. package/scripts/build-js-preserve-wasm.cjs +53 -0
  299. package/scripts/build-sidecar-linux.sh +6 -0
  300. package/scripts/build-sidecar-win.bat +19 -0
  301. package/scripts/build-sidecar.sh +184 -0
  302. package/scripts/embed-llama-ios-app-framework.sh +63 -0
  303. package/scripts/ensure-desktop-sidecar-bundle.cjs +103 -0
  304. package/scripts/ensure-llama-ios-xcframework.sh +108 -0
  305. package/scripts/fix-esm-extensions.cjs +45 -0
  306. package/scripts/prepare-js-dist.cjs +28 -0
  307. package/scripts/stage-desktop-resources.cjs +116 -0
  308. package/sidecar/CMakeLists.txt +192 -0
  309. package/sidecar/cap-sidecar-main.cpp +68 -0
  310. package/types/llama-cpp-pro.d.ts +441 -0
@@ -0,0 +1,1829 @@
1
+ /* @ts-self-types="./llama_engine.d.ts" */
2
+ /* Auto-generated by package-embed-wasm.mjs — do not edit */
3
+ import createLlamaModule from './llama_engine_emscripten.mjs';
4
+
5
+ let _mod = null;
6
+ let _lastWasmStderr = '';
7
+ const LLAMA_WASM_JSPI = true;
8
+ const LLAMA_WASM_PTHREAD = false;
9
+
10
+ // JSPI polyfill for token streaming only (generate_stream). Model load uses sync EM_JS fread.
11
+ if (LLAMA_WASM_JSPI && typeof WebAssembly !== 'undefined' && !WebAssembly.Suspending) {
12
+ WebAssembly.Suspending = function (fn) { return fn; };
13
+ WebAssembly.promising = function (fn) {
14
+ return function (...args) {
15
+ try { return Promise.resolve(fn(...args)); }
16
+ catch (e) { return Promise.reject(e); }
17
+ };
18
+ };
19
+ }
20
+
21
+ function wasmHasAsyncFileBridge(mod) {
22
+ if (!mod) return false;
23
+ if (typeof mod._cap_wasm_set_use_async_file === 'function') return true;
24
+ try {
25
+ if (typeof mod.cwrap === 'function') {
26
+ return typeof mod.cwrap('cap_wasm_set_use_async_file', null, ['number']) === 'function';
27
+ }
28
+ } catch (_) {}
29
+ return false;
30
+ }
31
+
32
+ /** External file read (OPFS/JS handler → sync fread hook). Does not require native JSPI. */
33
+ export function can_use_async_file() {
34
+ if (_mod && wasmHasAsyncFileBridge(_mod)) return true;
35
+ return LLAMA_WASM_JSPI;
36
+ }
37
+
38
+ /** Most recent llama stderr (error messages must use tail — head is stale load_tensors lines). */
39
+ function tailWasmStderr(maxLen = 1200) {
40
+ if (!_lastWasmStderr) return '';
41
+ return _lastWasmStderr.length > maxLen
42
+ ? _lastWasmStderr.slice(-maxLen)
43
+ : _lastWasmStderr;
44
+ }
45
+
46
+ /** Shared WASM memory for pthread builds (wllama getWasmMemory). Requires COOP/COEP. */
47
+ function trySharedWasmMemory() {
48
+ if (!LLAMA_WASM_PTHREAD) return null;
49
+ if (globalThis.crossOriginIsolated !== true || typeof SharedArrayBuffer === 'undefined') {
50
+ return null;
51
+ }
52
+ // wllama-style: 20 MB initial, step down max (4096 → 20 MB) on iOS OOM
53
+ const minBytes = 20 * 1024 * 1024;
54
+ let maxBytes = 4096 * 1024 * 1024;
55
+ const stepBytes = 128 * 1024 * 1024;
56
+ while (maxBytes >= minBytes) {
57
+ try {
58
+ return new WebAssembly.Memory({
59
+ initial: minBytes / 65536,
60
+ maximum: maxBytes / 65536,
61
+ shared: true,
62
+ });
63
+ } catch {
64
+ maxBytes -= stepBytes;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /** Single-thread: Emscripten owns memory (wllama passes wasmMemory: null). */
71
+ function tryWasmMemory() {
72
+ return null;
73
+ }
74
+
75
+ /** Raw Emscripten Module — used by HeapFS helpers in wasm.engine.ts. */
76
+ export function getEmscriptenModule() {
77
+ return _mod;
78
+ }
79
+
80
+ // ── Default export: loads and instantiates the WebAssembly module ─────────────
81
+ // wasm.engine.ts calls: await mod.default()
82
+ // Named "initWasm" internally so the "init" named export (below) can remain
83
+ // unambiguously the Rust-level engine initialiser (mod.init).
84
+ export default async function initWasm(_pathHint) {
85
+ if (_mod) return _mod;
86
+
87
+ // Emscripten's instantiateWasm hook has no failure callback — if instantiation
88
+ // fails we must reject this outer promise via Promise.race (never throw from
89
+ // the async chain inside instantiateWasm or the module hangs forever).
90
+ let rejectWasmInstantiate;
91
+ const wasmInstantiateFailed = new Promise((_, reject) => {
92
+ rejectWasmInstantiate = reject;
93
+ });
94
+
95
+ const sharedMem = trySharedWasmMemory();
96
+ if (LLAMA_WASM_PTHREAD && !sharedMem) {
97
+ throw new Error(
98
+ 'Cannot allocate shared WebAssembly.Memory for llama pthread build. ' +
99
+ 'Ensure COOP/COEP headers (crossOriginIsolated) or disable pthreads.',
100
+ );
101
+ }
102
+ const importedMem = sharedMem;
103
+ const pthreadPoolSize = sharedMem && navigator.hardwareConcurrency
104
+ ? Math.max(2, Math.floor(navigator.hardwareConcurrency / 2))
105
+ : 4;
106
+
107
+ const modulePromise = createLlamaModule({
108
+ preRun: [() => {
109
+ if (can_use_async_file()) {
110
+ if (typeof Module !== 'undefined') {
111
+ Module.ENV = Module.ENV || {};
112
+ Module.ENV['USE_ASYNC_FILE'] = '1';
113
+ }
114
+ }
115
+ }],
116
+ printErr: (text) => {
117
+ const line = String(text);
118
+ _lastWasmStderr = (_lastWasmStderr ? _lastWasmStderr + '\n' : '') + line;
119
+ if (_lastWasmStderr.length > 4096) _lastWasmStderr = _lastWasmStderr.slice(-4096);
120
+ if (typeof console !== 'undefined') {
121
+ if (line.includes('@@WASM_LOAD@@') || line.includes('@@WASM_GEN@@')) {
122
+ console.error('[llama-wasm-load]', line);
123
+ } else {
124
+ console.error('[llama.cpp]', line);
125
+ }
126
+ }
127
+ },
128
+ onAbort: (reason) => {
129
+ _lastWasmStderr = 'Aborted: ' + String(reason);
130
+ if (typeof console !== 'undefined') console.error('[llama.cpp]', _lastWasmStderr);
131
+ },
132
+ ...(importedMem ? { wasmMemory: importedMem } : {}),
133
+ ...(sharedMem ? {
134
+ pthreadPoolSize,
135
+ mainScriptUrlOrBlob: new URL('./llama_engine_emscripten.mjs', import.meta.url).href,
136
+ } : {}),
137
+ // Resolve assets relative to this JS file so the module works regardless
138
+ // of where the dist/wasm/ directory is served from.
139
+ // Emscripten requests 'llama_engine_emscripten.wasm' but we ship the
140
+ // MAIN_MODULE binary under the stable name 'llama_engine.wasm'.
141
+ locateFile: (filename) => {
142
+ const name = filename === 'llama_engine_emscripten.wasm' ? 'llama_engine.wasm' : filename;
143
+ return new URL(name, import.meta.url).href;
144
+ },
145
+ // The WASM binary imports wasm-bindgen helpers from "__wbindgen_placeholder__"
146
+ // but Emscripten's addToLibrary places them all under the "env" key.
147
+ // Alias the module so WebAssembly.instantiate receives the expected key.
148
+ instantiateWasm: (imports, successCallback) => {
149
+ const wasmUrl = new URL('llama_engine.wasm', import.meta.url).href;
150
+ // wasm-bindgen externref xform: manages a growable JS-side index space
151
+ // for externref table slots. Indices are tracked with a monotonic counter;
152
+ // the Emscripten runtime's heap handles actual JS object storage.
153
+ let _extRefNextIdx = 128; // skip wasm-bindgen's pre-filled reserved slots
154
+ const extRefXform = {
155
+ __wbindgen_externref_table_grow: (delta) => {
156
+ const prev = _extRefNextIdx;
157
+ _extRefNextIdx += delta;
158
+ return prev;
159
+ },
160
+ __wbindgen_externref_table_set_null: (_idx) => {},
161
+ };
162
+
163
+ // The MAIN_MODULE binary was originally a SIDE_MODULE: it imports ggml
164
+ // quantisation functions via GOT.func even though those functions are
165
+ // compiled into the same binary (as "_generic" suffixed exports).
166
+ // Emscripten's reportUndefinedSymbols crashes when GOT entries that are
167
+ // marked "required" cannot be resolved. We fix this in two steps:
168
+ //
169
+ // 1. Wrap GOT.func/GOT.mem so every entry is marked weak (required=false).
170
+ // This stops the GL/AL/console stubs (never called by llama inference)
171
+ // from crashing reportUndefinedSymbols.
172
+ //
173
+ // 2. After instantiation, grow __indirect_function_table by one slot per
174
+ // ggml symbol, store the "_generic" function there, and write that slot
175
+ // index into the GOT global before calling successCallback.
176
+ // Emscripten's updateGOT sees value != -1 and leaves them untouched.
177
+ const gotGlobals = {};
178
+ const weakenGOT = (proxy) => new Proxy({}, {
179
+ get(_, symName) {
180
+ if (typeof symName !== 'string') return undefined;
181
+ const global = proxy[symName];
182
+ if (global instanceof WebAssembly.Global) {
183
+ global.required = false;
184
+ gotGlobals[symName] = global;
185
+ }
186
+ return global;
187
+ },
188
+ });
189
+
190
+ // GOT.func symbol → exported "_generic" implementation present in the binary.
191
+ const FUNC_ALIASES = {
192
+ 'lm_ggml_vec_dot_q4_0_q8_0': 'lm_ggml_vec_dot_q4_0_q8_0_generic',
193
+ 'lm_ggml_vec_dot_q5_0_q8_0': 'lm_ggml_vec_dot_q5_0_q8_0_generic',
194
+ 'lm_ggml_vec_dot_q5_1_q8_1': 'lm_ggml_vec_dot_q5_1_q8_1_generic',
195
+ 'quantize_row_q8_0': 'quantize_row_q8_0_generic',
196
+ 'lm_ggml_vec_dot_q8_0_q8_0': 'lm_ggml_vec_dot_q8_0_q8_0_generic',
197
+ 'quantize_row_q8_1': 'quantize_row_q8_1_generic',
198
+ 'lm_ggml_vec_dot_q2_K_q8_K': 'lm_ggml_vec_dot_q2_K_q8_K_generic',
199
+ 'lm_ggml_vec_dot_q3_K_q8_K': 'lm_ggml_vec_dot_q3_K_q8_K_generic',
200
+ 'lm_ggml_vec_dot_q4_K_q8_K': 'lm_ggml_vec_dot_q4_K_q8_K_generic',
201
+ 'lm_ggml_vec_dot_q5_K_q8_K': 'lm_ggml_vec_dot_q5_K_q8_K_generic',
202
+ 'lm_ggml_vec_dot_q6_K_q8_K': 'lm_ggml_vec_dot_q6_K_q8_K_generic',
203
+ 'quantize_row_q8_K': 'quantize_row_q8_K_generic',
204
+ };
205
+
206
+ const patchedImports = {
207
+ ...imports,
208
+ '__wbindgen_placeholder__': imports['env'] ?? {},
209
+ '__wbindgen_externref_xform__': extRefXform,
210
+ 'GOT.func': weakenGOT(imports['GOT.func']),
211
+ 'GOT.mem': weakenGOT(imports['GOT.mem']),
212
+ };
213
+
214
+ const patchGOTAndCall = (result) => {
215
+ const exports = result.instance.exports;
216
+ const table = exports.__indirect_function_table;
217
+ if (table) {
218
+ for (const [gotSym, exportSym] of Object.entries(FUNC_ALIASES)) {
219
+ const fn = exports[exportSym];
220
+ if (fn && gotGlobals[gotSym]) {
221
+ const idx = table.grow(1);
222
+ table.set(idx, fn);
223
+ gotGlobals[gotSym].value = idx;
224
+ }
225
+ }
226
+ }
227
+
228
+ // Pass the real Instance — updateGOT(origExports) requires authentic WASM
229
+ // export objects. wasm-bindgen shims are injected in llama_engine_emscripten.mjs
230
+ // after Asyncify.instrumentWasmExports (see package-embed-wasm Stage 1b).
231
+ successCallback(result.instance, result.module);
232
+ };
233
+
234
+ const reportInstantiateFailure = (err) => {
235
+ const error = err instanceof Error ? err : new Error(String(err));
236
+ rejectWasmInstantiate(error);
237
+ };
238
+ WebAssembly.instantiateStreaming(fetch(wasmUrl), patchedImports)
239
+ .catch(() =>
240
+ fetch(wasmUrl)
241
+ .then((r) => {
242
+ if (!r.ok) {
243
+ throw new Error(`Failed to fetch wasm: ${r.status} ${r.statusText}`);
244
+ }
245
+ return r.arrayBuffer();
246
+ })
247
+ .then((bytes) => WebAssembly.instantiate(bytes, patchedImports))
248
+ )
249
+ .then(patchGOTAndCall)
250
+ .catch(reportInstantiateFailure);
251
+ return {}; // Emscripten requires a synchronous {} return
252
+ },
253
+ });
254
+
255
+ _mod = await Promise.race([modulePromise, wasmInstantiateFailed]);
256
+ installAsyncFileBridge(_mod);
257
+ ensureMemfsTmp();
258
+ patchHeapFS();
259
+ _mod.__llamaWasmJspi = LLAMA_WASM_JSPI;
260
+ _mod.__llamaWasmAsyncFile = can_use_async_file();
261
+ _mod.__llamaWasmPthread = LLAMA_WASM_PTHREAD && !!sharedMem;
262
+ return _mod;
263
+ }
264
+
265
+ /**
266
+ * wasm-bindgen + JSPI: Rust load_model_from_path reads wasm ptr2 as vfs_path (not ptr1).
267
+ * JS glue still maps arg2→ptr1, arg3→ptr2 — pass (model_id, opts_json, vfs_path).
268
+ */
269
+ function wasmBindgenLoadModelFromPath(model_id, vfs_path, opts_json) {
270
+ const fn = _mod?.load_model_from_path;
271
+ if (!fn) throw new Error('load_model_from_path not on Emscripten module');
272
+ fn(model_id, opts_json, vfs_path);
273
+ }
274
+
275
+ // ── Named exports (synchronous; safe to call after await initWasm()) ─────────
276
+ // These match the wasm-bindgen function names that initBindgen assigns to Module.*
277
+
278
+ /** Rust-level engine init — must be called once after the default export resolves. */
279
+ export function init() {
280
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
281
+ return _mod.init();
282
+ }
283
+
284
+ /** Load a GGUF model from bytes. */
285
+ export function load_model(model_id, bytes, opts_json) {
286
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
287
+ return _mod.load_model(model_id, bytes, opts_json);
288
+ }
289
+
290
+ /** Unload a loaded model and release resources. */
291
+ export function unload_model(model_id) {
292
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
293
+ _modelContextIds.delete(String(model_id));
294
+ _loadedModelBytes.delete(String(model_id));
295
+ _loadedModelOpts.delete(String(model_id));
296
+ _embedGraphWarmed.delete(String(model_id));
297
+ _measuredFootprintBytes.delete(String(model_id));
298
+ _linearAtLoadStart.delete(String(model_id));
299
+ _mod.unload_model(model_id);
300
+ const kept = _loadedHeapfsModels.get(model_id);
301
+ if (kept) {
302
+ heapfsReleaseEntry(kept.path, { mode: 'heapfs', basename: kept.basename, heapId: kept.heapId });
303
+ _loadedHeapfsModels.delete(model_id);
304
+ }
305
+ const asyncPath = _loadedAsyncModels.get(model_id);
306
+ if (asyncPath) {
307
+ asyncFileRelease(asyncPath);
308
+ _loadedAsyncModels.delete(model_id);
309
+ }
310
+ }
311
+
312
+ /** Select which resident model receives inference when model_id is omitted. */
313
+ export function set_active_model(model_id) {
314
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
315
+ return _mod.set_active_model(model_id);
316
+ }
317
+
318
+ /** Return the active model id, or throw if none. */
319
+ export function get_active_model() {
320
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
321
+ return _mod.get_active_model();
322
+ }
323
+
324
+ /** List resident models (registry snapshot JSON). */
325
+ export function list_loaded_models() {
326
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
327
+ return _mod.list_loaded_models();
328
+ }
329
+
330
+ /** model_id → C++ context id (cwrap path — same pattern as llama_load_context_from_path). */
331
+ const _modelContextIds = new Map();
332
+ let _cwrapCompletion = null;
333
+ let _cwrapEmbeddingJson = null;
334
+
335
+ /** Emscripten 3.x / WASM i64 — cwrap and wasm-bindgen expect bigint for int64_t. */
336
+ function wasmI64Arg(value) {
337
+ const n = typeof value === 'bigint' ? value : BigInt(Math.trunc(Number(value)));
338
+ return n;
339
+ }
340
+
341
+ function wasmI64ToNumber(value) {
342
+ if (typeof value === 'bigint') return Number(value);
343
+ return Number(value);
344
+ }
345
+
346
+ function registerModelContext(model_id, context_id) {
347
+ const id = wasmI64ToNumber(context_id);
348
+ if (model_id && id > 0) {
349
+ _modelContextIds.set(String(model_id), id);
350
+ }
351
+ const reg = _mod?.register_model_context;
352
+ if (typeof reg === 'function') {
353
+ try {
354
+ try {
355
+ reg(model_id, wasmI64Arg(id));
356
+ } catch (_) {
357
+ reg(model_id, id);
358
+ }
359
+ } catch (err) {
360
+ if (typeof console !== 'undefined') {
361
+ console.error('[llama-wasm] register_model_context failed (JS context map still set):', err);
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ function buildCompletionParamsJson(req_json) {
368
+ let req = {};
369
+ try {
370
+ req = JSON.parse(req_json || '{}');
371
+ } catch (e) {
372
+ throw new Error('Invalid generate request JSON: ' + (e && e.message ? e.message : String(e)));
373
+ }
374
+ const prompt = req.prompt != null
375
+ ? String(req.prompt)
376
+ : Array.isArray(req.messages)
377
+ ? req.messages.map((m) => m.role + ': ' + m.content).join('\n')
378
+ : '';
379
+ if (!prompt.trim()) {
380
+ throw new Error('No prompt or messages provided');
381
+ }
382
+ const out = {
383
+ prompt,
384
+ n_predict: req.max_tokens ?? req.n_predict ?? 128,
385
+ temperature: req.temperature ?? 0.7,
386
+ top_p: req.top_p ?? 0.95,
387
+ top_k: req.top_k ?? 40,
388
+ stop: req.stop ?? [],
389
+ };
390
+ const repeat =
391
+ req.repeat_penalty ?? req.penalty_repeat ?? req.repeatPenalty;
392
+ if (typeof repeat === 'number' && Number.isFinite(repeat)) {
393
+ out.penalty_repeat = repeat;
394
+ }
395
+ return JSON.stringify(out);
396
+ }
397
+
398
+ function resolveCompletionFn() {
399
+ if (_cwrapCompletion) return _cwrapCompletion;
400
+ const name = 'llama_completion';
401
+ const wrapCall = (raw, useBigintArg) => (ctxId, paramsJson) =>
402
+ raw(useBigintArg ? wasmI64Arg(ctxId) : Number(ctxId), paramsJson);
403
+ if (typeof _mod.cwrap === 'function') {
404
+ for (const argType of ['bigint', 'number']) {
405
+ try {
406
+ const raw = _mod.cwrap(name, 'string', [argType, 'string']);
407
+ if (typeof raw === 'function') {
408
+ _cwrapCompletion = wrapCall(raw, argType === 'bigint');
409
+ return _cwrapCompletion;
410
+ }
411
+ } catch (_) {}
412
+ }
413
+ }
414
+ const rawFn = _mod['_' + name] ?? _mod.wasmExports?.[name];
415
+ if (typeof rawFn === 'function') {
416
+ _cwrapCompletion = (ctxId, paramsJson) => {
417
+ try {
418
+ return rawFn(wasmI64Arg(ctxId), paramsJson);
419
+ } catch (e1) {
420
+ return rawFn(Number(ctxId), paramsJson);
421
+ }
422
+ };
423
+ return _cwrapCompletion;
424
+ }
425
+ throw new Error('llama_completion not exported — rebuild wasm (EXPORTED_FUNCTIONS)');
426
+ }
427
+
428
+ /** Inference via C cwrap (avoids wasm-bindgen trap/undefined throw on llama_decode). */
429
+ function wasmGenerateViaCwrap(model_id, req_json) {
430
+ const ctxId = _modelContextIds.get(String(model_id));
431
+ if (!ctxId || ctxId <= 0) {
432
+ throw new Error('Model not loaded (no WASM context id for ' + model_id + ')');
433
+ }
434
+ const t0 = typeof performance !== 'undefined' ? performance.now() : Date.now();
435
+ if (typeof console !== 'undefined') {
436
+ console.error(
437
+ '[llama-wasm] cwrap generate: model=' + model_id + ' ctxId=' + ctxId +
438
+ ' wasmMB=' + wasmLinearMb(),
439
+ );
440
+ }
441
+ const compJson = buildCompletionParamsJson(req_json);
442
+ const raw = resolveCompletionFn()(ctxId, compJson);
443
+ const elapsedMs = Math.round(
444
+ (typeof performance !== 'undefined' ? performance.now() : Date.now()) - t0,
445
+ );
446
+ if (typeof raw !== 'string') {
447
+ throw new TypeError('llama_completion returned ' + typeof raw + ' (' + String(raw) + ')');
448
+ }
449
+ let parsed;
450
+ try {
451
+ parsed = JSON.parse(raw);
452
+ } catch (_) {
453
+ throw new Error('llama_completion returned non-JSON: ' + raw.slice(0, 160));
454
+ }
455
+ if (parsed && typeof parsed.error === 'string' && parsed.error.length > 0) {
456
+ throw new Error(parsed.error);
457
+ }
458
+ if (typeof console !== 'undefined') {
459
+ console.error(
460
+ '[llama-wasm] cwrap generate ok chars=' + (parsed.text?.length ?? 0) +
461
+ ' ms=' + elapsedMs + ' tokens=' + (parsed.tokens_predicted ?? '?'),
462
+ );
463
+ }
464
+ return raw;
465
+ }
466
+
467
+ function resolveEmbeddingFn() {
468
+ if (_cwrapEmbeddingJson) return _cwrapEmbeddingJson;
469
+ const name = 'llama_embedding_json';
470
+ const wrapCall = (raw, useBigintArg) => (ctxId, text, paramsJson) =>
471
+ raw(useBigintArg ? wasmI64Arg(ctxId) : Number(ctxId), text, paramsJson ?? '{}');
472
+ if (typeof _mod.cwrap === 'function') {
473
+ for (const argType of ['bigint', 'number']) {
474
+ try {
475
+ const raw = _mod.cwrap(name, 'string', [argType, 'string', 'string']);
476
+ if (typeof raw === 'function') {
477
+ _cwrapEmbeddingJson = wrapCall(raw, argType === 'bigint');
478
+ return _cwrapEmbeddingJson;
479
+ }
480
+ } catch (_) {}
481
+ }
482
+ }
483
+ const rawFn = _mod['_' + name] ?? _mod.wasmExports?.[name];
484
+ if (typeof rawFn === 'function') {
485
+ _cwrapEmbeddingJson = (ctxId, text, paramsJson) => {
486
+ try {
487
+ return rawFn(wasmI64Arg(ctxId), text, paramsJson ?? '{}');
488
+ } catch (e1) {
489
+ return rawFn(Number(ctxId), text, paramsJson ?? '{}');
490
+ }
491
+ };
492
+ return _cwrapEmbeddingJson;
493
+ }
494
+ throw new Error('llama_embedding_json not exported — rebuild wasm (EXPORTED_FUNCTIONS)');
495
+ }
496
+
497
+ function parseEmbedRequest(req_json) {
498
+ let req = {};
499
+ try {
500
+ req = JSON.parse(req_json || '{}');
501
+ } catch (e) {
502
+ throw new Error('Invalid embed request JSON: ' + (e && e.message ? e.message : String(e)));
503
+ }
504
+ let inputs;
505
+ if (typeof req.input === 'string') {
506
+ inputs = [req.input];
507
+ } else if (Array.isArray(req.input)) {
508
+ inputs = req.input.map((v) => String(v));
509
+ } else {
510
+ throw new Error('Embed request missing input (string or string[])');
511
+ }
512
+ if (inputs.length === 0 || inputs.every((t) => !String(t).trim())) {
513
+ throw new Error('Embed request input is empty');
514
+ }
515
+ const paramsJson = req.normalize != null
516
+ ? JSON.stringify({ embd_normalize: req.normalize ? 2 : -1 })
517
+ : '{}';
518
+ return { inputs, paramsJson };
519
+ }
520
+
521
+ /** Inference via C cwrap (avoids wasm-bindgen malloc abort after heap grow). */
522
+ function wasmEmbedViaCwrap(model_id, req_json) {
523
+ const ctxId = _modelContextIds.get(String(model_id));
524
+ if (!ctxId || ctxId <= 0) {
525
+ throw new Error('Model not loaded (no WASM context id for ' + model_id + ')');
526
+ }
527
+ const { inputs, paramsJson } = parseEmbedRequest(req_json);
528
+ const embedFn = resolveEmbeddingFn();
529
+ const t0 = typeof performance !== 'undefined' ? performance.now() : Date.now();
530
+ if (typeof console !== 'undefined') {
531
+ console.error(
532
+ '[llama-wasm] cwrap embed: model=' + model_id + ' ctxId=' + ctxId +
533
+ ' n=' + inputs.length + ' wasmMB=' + wasmLinearMb(),
534
+ );
535
+ }
536
+ const vectors = [];
537
+ for (const text of inputs) {
538
+ const raw = embedFn(ctxId, text, paramsJson);
539
+ if (typeof raw !== 'string') {
540
+ throw new TypeError('llama_embedding_json returned ' + typeof raw);
541
+ }
542
+ let parsed;
543
+ try {
544
+ parsed = JSON.parse(raw);
545
+ } catch (_) {
546
+ throw new Error('llama_embedding_json returned non-JSON: ' + raw.slice(0, 160));
547
+ }
548
+ if (parsed && typeof parsed.error === 'string' && parsed.error.length > 0) {
549
+ throw new Error(parsed.error);
550
+ }
551
+ if (!Array.isArray(parsed?.embedding) || parsed.embedding.length === 0) {
552
+ throw new Error('llama_embedding_json returned empty embedding');
553
+ }
554
+ vectors.push(parsed.embedding);
555
+ }
556
+ const elapsedMs = Math.round(
557
+ (typeof performance !== 'undefined' ? performance.now() : Date.now()) - t0,
558
+ );
559
+ if (typeof console !== 'undefined') {
560
+ console.error(
561
+ '[llama-wasm] cwrap embed ok n=' + vectors.length + ' dim=' + (vectors[0]?.length ?? 0) +
562
+ ' ms=' + elapsedMs,
563
+ );
564
+ }
565
+ return JSON.stringify({ vectors });
566
+ }
567
+
568
+ /** Run text generation. Returns JSON GenerateResponse. */
569
+ export function generate(model_id, req_json) {
570
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
571
+ try {
572
+ return wasmGenerateViaCwrap(model_id, req_json);
573
+ } catch (err) {
574
+ throw wasmThrowToError(err, 'generate failed');
575
+ }
576
+ }
577
+
578
+ /** Streaming generation — calls on_token(token, index) per token (JSPI when available). */
579
+ export function generate_stream(model_id, req_json, on_token) {
580
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
581
+ const fn = _mod.generate_stream;
582
+ if (typeof fn !== 'function') {
583
+ throw new Error('generate_stream not exported — rebuild with LLAMA_WASM_JSPI=1');
584
+ }
585
+ try {
586
+ return fn(model_id, req_json, on_token);
587
+ } catch (err) {
588
+ throw wasmThrowToError(err, 'generate_stream failed');
589
+ }
590
+ }
591
+
592
+ function growWasmLinearTo(targetBytes) {
593
+ if (!_mod) return;
594
+ const current = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
595
+ if (current >= targetBytes) return;
596
+ let grown = false;
597
+ if (typeof _mod.growMemory === 'function') {
598
+ grown = _mod.growMemory(targetBytes) === 1;
599
+ } else if (typeof _mod.emscripten_resize_heap === 'function') {
600
+ grown = !!_mod.emscripten_resize_heap(targetBytes);
601
+ }
602
+ if (!grown) {
603
+ throw new Error(
604
+ '[llama-wasm] failed to grow wasm memory to ' + targetBytes + ' bytes (have ' + current + ')',
605
+ );
606
+ }
607
+ heapfsResyncViews();
608
+ const after = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
609
+ if (typeof console !== 'undefined') {
610
+ console.error(
611
+ '[llama-wasm] embed memory grow: ' + (current / 1048576).toFixed(0) + 'MB -> ' +
612
+ (after / 1048576).toFixed(0) + 'MB',
613
+ );
614
+ }
615
+ }
616
+
617
+ /** Reserve slack for lazy encoder-graph alloc on first embed (BERT / pooling models). */
618
+ function ensureEmbedInferenceHeadroom(model_id) {
619
+ if (!_mod) return;
620
+ const id = String(model_id);
621
+ if (_embedGraphWarmed.has(id)) return;
622
+ const opts = _loadedModelOpts.get(id) ?? {};
623
+ if (!opts.embedding) {
624
+ _embedGraphWarmed.add(id);
625
+ return;
626
+ }
627
+ const current = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
628
+ const target = Math.min(current + 64 * 1024 * 1024, WASM_MAX_BYTES);
629
+ if (current < target) {
630
+ growWasmLinearTo(target);
631
+ }
632
+ _embedGraphWarmed.add(id);
633
+ }
634
+
635
+ /** Generate embeddings. Returns JSON EmbedResponse. */
636
+ export function embed(model_id, req_json) {
637
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
638
+ try {
639
+ ensureEmbedInferenceHeadroom(model_id);
640
+ return wasmEmbedViaCwrap(model_id, req_json);
641
+ } catch (err) {
642
+ throw wasmThrowToError(err, 'embed failed');
643
+ }
644
+ }
645
+
646
+ /** Rank documents by relevance (rank-pooling embed models). Returns JSON array. */
647
+ export function rerank(model_id, query, documents_json) {
648
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
649
+ const fn = _mod.rerank;
650
+ if (typeof fn !== 'function') throw new Error('rerank not exported — rebuild WASM');
651
+ return fn(model_id, query, documents_json);
652
+ }
653
+
654
+ /** Benchmark prompt/tg throughput. Returns JSON-ish bench string. */
655
+ export function bench(model_id, pp, tg, pl, nr) {
656
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
657
+ const fn = _mod.bench;
658
+ if (typeof fn !== 'function') throw new Error('bench not exported — rebuild WASM');
659
+ return fn(model_id, pp, tg, pl, nr);
660
+ }
661
+
662
+ export function save_session(model_id, filepath, token_size) {
663
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
664
+ const fn = _mod.save_session;
665
+ if (typeof fn !== 'function') throw new Error('save_session not exported — rebuild WASM');
666
+ return fn(model_id, filepath, token_size);
667
+ }
668
+
669
+ export function load_session(model_id, filepath) {
670
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
671
+ const fn = _mod.load_session;
672
+ if (typeof fn !== 'function') throw new Error('load_session not exported — rebuild WASM');
673
+ return fn(model_id, filepath);
674
+ }
675
+
676
+ export function apply_lora_adapters(model_id, lora_list_json) {
677
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
678
+ const fn = _mod.apply_lora_adapters;
679
+ if (typeof fn !== 'function') throw new Error('apply_lora_adapters not exported — rebuild WASM');
680
+ return fn(model_id, lora_list_json);
681
+ }
682
+
683
+ export function remove_lora_adapters(model_id) {
684
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
685
+ _mod.remove_lora_adapters?.(model_id);
686
+ }
687
+
688
+ export function get_loaded_lora_adapters(model_id) {
689
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
690
+ const fn = _mod.get_loaded_lora_adapters;
691
+ if (typeof fn !== 'function') return '[]';
692
+ return fn(model_id);
693
+ }
694
+
695
+ export function init_multimodal(model_id, path, use_gpu) {
696
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
697
+ const fn = _mod.init_multimodal;
698
+ if (typeof fn !== 'function') throw new Error('init_multimodal not exported — rebuild WASM');
699
+ return fn(model_id, path, !!use_gpu);
700
+ }
701
+
702
+ export function multimodal_status(model_id) {
703
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
704
+ const fn = _mod.multimodal_status;
705
+ if (typeof fn !== 'function') return '{"enabled":false,"vision":false,"audio":false}';
706
+ return fn(model_id);
707
+ }
708
+
709
+ export function release_multimodal(model_id) {
710
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
711
+ _mod.release_multimodal?.(model_id);
712
+ }
713
+
714
+ export function init_vocoder(model_id, path, n_batch) {
715
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
716
+ const fn = _mod.init_vocoder;
717
+ if (typeof fn !== 'function') throw new Error('init_vocoder not exported — rebuild WASM');
718
+ return fn(model_id, path, n_batch ?? 512);
719
+ }
720
+
721
+ export function vocoder_enabled(model_id) {
722
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
723
+ const fn = _mod.vocoder_enabled;
724
+ if (typeof fn !== 'function') return '{"enabled":false}';
725
+ return fn(model_id);
726
+ }
727
+
728
+ export function release_vocoder(model_id) {
729
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
730
+ _mod.release_vocoder?.(model_id);
731
+ }
732
+
733
+ export function formatted_audio_completion(model_id, speaker_json, text_to_speak) {
734
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
735
+ const fn = _mod.formatted_audio_completion;
736
+ if (typeof fn !== 'function') throw new Error('formatted_audio_completion not exported — rebuild WASM');
737
+ return fn(model_id, speaker_json ?? '', text_to_speak ?? '');
738
+ }
739
+
740
+ export function audio_guide_tokens(model_id, text_to_speak) {
741
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
742
+ const fn = _mod.audio_guide_tokens;
743
+ if (typeof fn !== 'function') throw new Error('audio_guide_tokens not exported — rebuild WASM');
744
+ return fn(model_id, text_to_speak ?? '');
745
+ }
746
+
747
+ export function decode_audio_tokens(model_id, tokens_json) {
748
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
749
+ const fn = _mod.decode_audio_tokens;
750
+ if (typeof fn !== 'function') throw new Error('decode_audio_tokens not exported — rebuild WASM');
751
+ return fn(model_id, tokens_json);
752
+ }
753
+
754
+ /** Return engine health as a JSON string. */
755
+ export function health() {
756
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
757
+ return _mod.health();
758
+ }
759
+
760
+ /** Return memory usage as a JSON string (JS-only — never call Rust memory_snapshot; it can abort WASM). */
761
+ export function memory_snapshot() {
762
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
763
+ const linear = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
764
+ const loadedModels = [];
765
+ for (const [modelId, fileBytes] of _loadedModelBytes) {
766
+ const opts = _loadedModelOpts.get(modelId) ?? {};
767
+ const estimatedFootprintBytes = estimateModelWasmFootprint(fileBytes, opts);
768
+ const measuredFootprintBytes = _measuredFootprintBytes.get(modelId);
769
+ loadedModels.push({
770
+ modelId,
771
+ fileBytes,
772
+ estimatedFootprintBytes,
773
+ measuredFootprintBytes: measuredFootprintBytes ?? null,
774
+ footprintBytes: getModelFootprintBytes(modelId),
775
+ calibrated: typeof measuredFootprintBytes === 'number' && measuredFootprintBytes > 0,
776
+ });
777
+ }
778
+ return JSON.stringify({
779
+ pressure: 'unknown',
780
+ wasmLinearBytes: linear,
781
+ wasmLinearMb: linear ? +(linear / 1048576).toFixed(1) : 0,
782
+ wasmPoolCeilingBytes: WASM_POOL_CEILING_BYTES,
783
+ wasmHeadroomBytes: Math.max(0, WASM_POOL_CEILING_BYTES - linear),
784
+ maxModels: WASM_MAX_CONCURRENT_MODELS,
785
+ loadedModelCount: _loadedModelBytes.size,
786
+ loadedModels,
787
+ loadedFootprintBytes: loadedModelsFootprintBytes(null),
788
+ measuredFootprintBytes: loadedModelsFootprintBytes(null),
789
+ });
790
+ }
791
+
792
+ // ── JSPI async file read (wllama USE_ASYNC_FILE / cap-wasm-fs.cpp) ───────────
793
+ // Model bytes stay in JS (Blob / OPFS reader). C++ fread → _cap_js_file_read.
794
+ const _asyncFileHandlers = new Map();
795
+ const _loadedAsyncModels = new Map();
796
+ /** Bytes per loaded model — cumulative sizing when chat + embed share one worker. */
797
+ const _loadedModelBytes = new Map();
798
+ /** Load opts per model — for accurate footprint when summing resident models. */
799
+ const _loadedModelOpts = new Map();
800
+ /** First embed per model may lazily allocate encoder graph — one-time heap bump. */
801
+ const _embedGraphWarmed = new Set();
802
+ /** Post-load measured WASM bytes per model (calibrated from linear heap delta). */
803
+ const _measuredFootprintBytes = new Map();
804
+ /** Linear heap bytes captured immediately before each model load. */
805
+ const _linearAtLoadStart = new Map();
806
+
807
+ function wasmLinearBytesNow() {
808
+ return _mod?.HEAPU8?.length ?? _mod?.wasmMemory?.buffer?.byteLength ?? 0;
809
+ }
810
+
811
+ function getModelFootprintBytes(modelId) {
812
+ const id = String(modelId);
813
+ if (_measuredFootprintBytes.has(id)) {
814
+ return _measuredFootprintBytes.get(id);
815
+ }
816
+ const fileBytes = _loadedModelBytes.get(id) ?? 0;
817
+ const opts = _loadedModelOpts.get(id) ?? {};
818
+ return estimateModelWasmFootprint(fileBytes, opts);
819
+ }
820
+
821
+ function calibrateModelFootprintAfterLoad(model_id) {
822
+ const id = String(model_id);
823
+ const after = wasmLinearBytesNow();
824
+ const before = _linearAtLoadStart.get(id) ?? 0;
825
+ const fileBytes = _loadedModelBytes.get(id) ?? 0;
826
+ const opts = _loadedModelOpts.get(id) ?? {};
827
+ const estimated = estimateModelWasmFootprint(fileBytes, opts);
828
+ const firstModel = _loadedModelBytes.size <= 1;
829
+ let measured = Math.max(0, after - before);
830
+ if (firstModel && after > 64 * 1024 * 1024) {
831
+ measured = after;
832
+ } else if (measured <= 0) {
833
+ measured = estimated;
834
+ }
835
+ _measuredFootprintBytes.set(id, measured);
836
+ _linearAtLoadStart.delete(id);
837
+ if (typeof console !== 'undefined') {
838
+ console.error(
839
+ '[llama-wasm] footprint calibrated: model=' + id +
840
+ ' measuredMB=' + (measured / 1048576).toFixed(1) +
841
+ ' estimatedMB=' + (estimated / 1048576).toFixed(1) +
842
+ ' heapMB=' + (after / 1048576).toFixed(1),
843
+ );
844
+ }
845
+ return measured;
846
+ }
847
+
848
+ function stripModelsPrefix(path) {
849
+ return String(path).replace(/^\/?models\//, '');
850
+ }
851
+
852
+ function enableAsyncFileEnv() {
853
+ if (!_mod) return;
854
+ _mod.ENV = _mod.ENV || {};
855
+ _mod.ENV['USE_ASYNC_FILE'] = '1';
856
+ try {
857
+ if (typeof _mod._cap_wasm_set_use_async_file === 'function') {
858
+ _mod._cap_wasm_set_use_async_file(1);
859
+ } else if (typeof _mod.cwrap === 'function') {
860
+ const setFn = _mod.cwrap('cap_wasm_set_use_async_file', null, ['number']);
861
+ if (typeof setFn === 'function') setFn(1);
862
+ }
863
+ } catch (_) {}
864
+ }
865
+
866
+ function disableAsyncFileEnv() {
867
+ if (!_mod) return;
868
+ if (_mod.ENV) delete _mod.ENV['USE_ASYNC_FILE'];
869
+ try {
870
+ if (typeof _mod._cap_wasm_set_use_async_file === 'function') {
871
+ _mod._cap_wasm_set_use_async_file(0);
872
+ } else if (typeof _mod.cwrap === 'function') {
873
+ const setFn = _mod.cwrap('cap_wasm_set_use_async_file', null, ['number']);
874
+ if (typeof setFn === 'function') setFn(0);
875
+ }
876
+ } catch (_) {}
877
+ }
878
+
879
+ function installAsyncFileBridge(mod) {
880
+ const bridge = (path, offset, req_size, out_ptr) => {
881
+ const name = stripModelsPrefix(path);
882
+ const handler = _asyncFileHandlers.get(name);
883
+ if (!handler) {
884
+ throw new Error('async file handler missing for ' + name);
885
+ }
886
+ const off = Number(offset);
887
+ const req = Number(req_size);
888
+ const raw = handler.readSync(off, req);
889
+ if (raw != null && typeof raw.then === 'function') {
890
+ throw new Error(
891
+ 'async file readFn returned a Promise — use synchronous OPFS SyncAccessHandle reads',
892
+ );
893
+ }
894
+ const bytes = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
895
+ getHeapU8().set(bytes.subarray(0, Math.min(bytes.byteLength, req)), Number(out_ptr));
896
+ return bytes.byteLength;
897
+ };
898
+ globalThis._cap_js_file_read = bridge;
899
+ if (mod) mod._cap_js_file_read = bridge;
900
+ }
901
+
902
+ function asyncFilePlaceholder(vfs_path, sizeBytes) {
903
+ patchHeapFS();
904
+ enableAsyncFileEnv();
905
+ const basename = stripModelsPrefix(vfs_path);
906
+ if (!_mod.FS.analyzePath(vfs_path).exists) {
907
+ _mod.FS.createDataFile('/models', basename, new ArrayBuffer(0), true, true, true);
908
+ }
909
+ try {
910
+ const node = _mod.FS.lookupPath(vfs_path).node;
911
+ node.usedBytes = sizeBytes;
912
+ } catch (_) {}
913
+ }
914
+
915
+ function asyncFileRegister(vfs_path, sizeBytes, readFn) {
916
+ const basename = stripModelsPrefix(vfs_path);
917
+ asyncFilePlaceholder(vfs_path, sizeBytes);
918
+ _asyncFileHandlers.set(basename, {
919
+ size: sizeBytes,
920
+ readSync: (offset, len) => readFn(Number(offset), Number(len)),
921
+ });
922
+ }
923
+
924
+ function asyncFileRelease(vfs_path) {
925
+ const basename = stripModelsPrefix(vfs_path);
926
+ _asyncFileHandlers.delete(basename);
927
+ try { _mod.FS.unlink(vfs_path); } catch (_) {}
928
+ }
929
+
930
+ /**
931
+ * Bind a JS-side reader for an async VFS path (no WASM heap copy of the full GGUF).
932
+ * readFn(offset, length) must return Uint8Array (may be shorter at EOF).
933
+ */
934
+ export function async_model_bind(vfs_path, size_bytes, readFn) {
935
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
936
+ const entry = _jsVfsStreams.get(vfs_path);
937
+ if (entry?.mode === 'heapfs') {
938
+ if (!(size_bytes > 0)) {
939
+ throw new Error('async_model_bind(heapfs): size_bytes must be positive');
940
+ }
941
+ const CHUNK = 1024 * 1024;
942
+ for (let off = 0; off < size_bytes; off += CHUNK) {
943
+ const raw = readFn(off, Math.min(CHUNK, size_bytes - off));
944
+ if (raw != null && typeof raw.then === 'function') {
945
+ throw new Error('async_model_bind readFn must be synchronous (use OPFS SyncAccessHandle)');
946
+ }
947
+ const bytes = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
948
+ heapfsWrite(entry.heapId, bytes, off);
949
+ }
950
+ entry.bound = true;
951
+ if (typeof console !== 'undefined') {
952
+ console.error(
953
+ '[llama-wasm] heapfs_model_bind: copied modelMB=' + (size_bytes / 1048576).toFixed(1) +
954
+ ' wasmMB=' + wasmLinearMb() + ' (use_mmap=true zero-copy tensors)',
955
+ );
956
+ }
957
+ return;
958
+ }
959
+ if (!can_use_async_file()) {
960
+ throw new Error(
961
+ 'async_model_bind requires cap_wasm async file bridge — rebuild with npm run build:wasm',
962
+ );
963
+ }
964
+ asyncFileRegister(vfs_path, size_bytes, readFn);
965
+ try {
966
+ const probe = readFn(0, 4);
967
+ if (probe != null && typeof probe.then === 'function') {
968
+ throw new Error('async_model_bind readFn must be synchronous (use OPFS SyncAccessHandle)');
969
+ }
970
+ const b = probe instanceof Uint8Array ? probe : new Uint8Array(probe);
971
+ const magic = b.byteLength >= 4
972
+ ? String.fromCharCode(b[0], b[1], b[2], b[3])
973
+ : '';
974
+ if (magic !== 'GGUF' && typeof console !== 'undefined') {
975
+ console.error(
976
+ '[llama-wasm] async_model_bind: GGUF magic mismatch at offset 0 for ' +
977
+ vfs_path + ': ' + JSON.stringify(magic) + ' (check OPFS reader)',
978
+ );
979
+ }
980
+ } catch (err) {
981
+ if (typeof console !== 'undefined') {
982
+ console.error('[llama-wasm] async_model_bind: probe read failed for ' + vfs_path, err);
983
+ }
984
+ throw err;
985
+ }
986
+ }
987
+
988
+ // ── VFS streaming API (wllama HeapFS pattern) ─────────────────────────────────
989
+ // Ref: ref-code/wllama/src/workers-code/llama-cpp.js
990
+ // - mmapAlloc places GGUF bytes in WASM linear memory (not JS heap MEMFS)
991
+ // - patchHeapFS redirects MEMFS mmap/read to those bytes (zero-copy for llama)
992
+ // - Targeted mem.grow() before model init: 20 MB at boot, then model size + headroom (BGE and LFM same policy)
993
+
994
+ let _heapfsPatched = false;
995
+ const _heapfsNameToFile = {};
996
+ const _heapfsIdToFile = {};
997
+ let _heapfsFileId = 0;
998
+
999
+ /** Fresh HEAP view after mmapAlloc growth (wllama getHeapU8). */
1000
+ function getHeapU8() {
1001
+ const buf = _mod.wasmMemory?.buffer ?? _mod.HEAPU8?.buffer;
1002
+ if (!buf) throw new Error('WASM heap not ready');
1003
+ return new Uint8Array(buf);
1004
+ }
1005
+
1006
+ function supportsHeapFS() {
1007
+ return typeof _mod?.mmapAlloc === 'function' && _mod?.MEMFS && _mod?.FS;
1008
+ }
1009
+
1010
+ function patchHeapFS() {
1011
+ if (_heapfsPatched || !supportsHeapFS()) return;
1012
+ _heapfsPatched = true;
1013
+ const m = _mod;
1014
+ const ops = m.MEMFS.stream_ops;
1015
+ ops._read = ops._read ?? ops.read;
1016
+ ops._write = ops._write ?? ops.write;
1017
+ ops._llseek = ops._llseek ?? ops.llseek;
1018
+ ops._allocate = ops._allocate ?? ops.allocate;
1019
+ ops._mmap = ops._mmap ?? ops.mmap;
1020
+ ops._msync = ops._msync ?? ops.msync;
1021
+
1022
+ const patchStream = (stream) => {
1023
+ const name = stream.node.name;
1024
+ const f = _heapfsNameToFile[name];
1025
+ if (!f) return;
1026
+ const ptr = Number(f.ptr);
1027
+ stream.node.contents = getHeapU8().subarray(ptr, ptr + f.size);
1028
+ stream.node.usedBytes = f.size;
1029
+ };
1030
+
1031
+ ops.read = function (stream, buffer, offset, length, position) {
1032
+ patchStream(stream);
1033
+ return ops._read(stream, buffer, offset, length, position);
1034
+ };
1035
+ m.MEMFS.ops_table.file.stream.read = ops.read;
1036
+
1037
+ ops.llseek = function (stream, off, whence) {
1038
+ patchStream(stream);
1039
+ return ops._llseek(stream, off, whence);
1040
+ };
1041
+ m.MEMFS.ops_table.file.stream.llseek = ops.llseek;
1042
+
1043
+ ops.mmap = function (stream, length, position, prot, flags) {
1044
+ patchStream(stream);
1045
+ const name = stream.node.name;
1046
+ const f = _heapfsNameToFile[name];
1047
+ if (f) {
1048
+ return { ptr: Number(f.ptr) + Number(position), allocated: false };
1049
+ }
1050
+ return ops._mmap(stream, length, position, prot, flags);
1051
+ };
1052
+ m.MEMFS.ops_table.file.stream.mmap = ops.mmap;
1053
+
1054
+ try {
1055
+ if (!m.FS.analyzePath('/models').exists) {
1056
+ if (m.FS.createPath) {
1057
+ m.FS.createPath('/', 'models', true, true);
1058
+ } else {
1059
+ m.FS.mkdir('/models');
1060
+ }
1061
+ }
1062
+ m.FS.mount(m.MEMFS, { root: '.' }, '/models');
1063
+ } catch (_) {}
1064
+ }
1065
+
1066
+ function heapfsAlloc(name, size) {
1067
+ const ptr = _mod.mmapAlloc(size);
1068
+ const file = { ptr: Number(ptr), size, id: _heapfsFileId++ };
1069
+ _heapfsIdToFile[file.id] = file;
1070
+ _heapfsNameToFile[name] = file;
1071
+ return file.id;
1072
+ }
1073
+
1074
+ function heapfsWrite(id, buffer, offset) {
1075
+ const f = _heapfsIdToFile[id];
1076
+ if (!f) throw new Error('HeapFS file id ' + id + ' not found');
1077
+ const after = offset + buffer.byteLength;
1078
+ if (after > f.size) {
1079
+ throw new Error('HeapFS write out of bounds: ' + after + ' > ' + f.size);
1080
+ }
1081
+ getHeapU8().set(buffer, Number(f.ptr) + offset);
1082
+ return buffer.byteLength;
1083
+ }
1084
+
1085
+ /** Ensure MEMFS /tmp exists (legacy fallback path). */
1086
+ function ensureMemfsTmp() {
1087
+ const fs = _mod?.FS;
1088
+ if (!fs) {
1089
+ throw new Error('Emscripten FS not ready — cannot create /tmp for model streaming');
1090
+ }
1091
+ if (!fs.analyzePath('/tmp').exists) {
1092
+ if (typeof fs.createPath === 'function') {
1093
+ fs.createPath('/', 'tmp', true, true);
1094
+ } else {
1095
+ fs.mkdir('/tmp');
1096
+ }
1097
+ }
1098
+ if (!fs.analyzePath('/tmp').exists) {
1099
+ throw new Error('Failed to create MEMFS /tmp for model VFS streaming');
1100
+ }
1101
+ }
1102
+
1103
+ const _jsVfsStreams = new Map();
1104
+ /** Keep HeapFS mmap alive for loaded models (wllama never unlinks until exit). */
1105
+ const _loadedHeapfsModels = new Map();
1106
+ let _jsVfsCounter = 0;
1107
+
1108
+ function effectiveNctx(modelBytes, opts_json) {
1109
+ let n_ctx = 1024;
1110
+ try {
1111
+ const opts = JSON.parse(opts_json || '{}');
1112
+ if (typeof opts.n_ctx === 'number' && opts.n_ctx > 0) n_ctx = opts.n_ctx;
1113
+ } catch (_) {}
1114
+ if (modelBytes > 500 * 1024 * 1024) {
1115
+ n_ctx = Math.min(n_ctx, 512);
1116
+ }
1117
+ if (modelBytes > 600 * 1024 * 1024) {
1118
+ // Recurrent/hybrid models (e.g. LFM2) use tiny KV — keep n_ctx high enough for chat templates.
1119
+ n_ctx = Math.min(n_ctx, 512);
1120
+ } else if (modelBytes > 500 * 1024 * 1024) {
1121
+ n_ctx = Math.min(n_ctx, 256);
1122
+ }
1123
+ return n_ctx;
1124
+ }
1125
+
1126
+ function heapfsReleaseEntry(vfs_path, entry) {
1127
+ if (!entry || entry.mode !== 'heapfs') return;
1128
+ if (entry.basename) {
1129
+ delete _heapfsNameToFile[entry.basename];
1130
+ if (entry.heapId != null) delete _heapfsIdToFile[entry.heapId];
1131
+ }
1132
+ try {
1133
+ _mod.FS.unlink(vfs_path);
1134
+ } catch (_) {}
1135
+ }
1136
+
1137
+ /** Re-attach MEMFS node views after wasmMemory.grow() (old TypedArrays are detached). */
1138
+ function heapfsResyncViews() {
1139
+ if (!supportsHeapFS()) return;
1140
+ patchHeapFS();
1141
+ const heap = getHeapU8();
1142
+ for (const name in _heapfsNameToFile) {
1143
+ const f = _heapfsNameToFile[name];
1144
+ if (!f) continue;
1145
+ const ptr = Number(f.ptr);
1146
+ const slice = heap.subarray(ptr, ptr + f.size);
1147
+ try {
1148
+ const node = _mod.FS.lookupPath('/models/' + name).node;
1149
+ node.contents = slice;
1150
+ node.usedBytes = f.size;
1151
+ } catch (_) {}
1152
+ }
1153
+ }
1154
+
1155
+ /** Sync MEMFS node size + verify GGUF magic before C++ fopen/mmap (wllama pattern). */
1156
+ function heapfsFinalizeForLoad(vfs_path, basename, expectedBytes) {
1157
+ const f = _heapfsNameToFile[basename];
1158
+ if (!f) {
1159
+ throw new Error('heapfs finalize: missing entry for ' + basename);
1160
+ }
1161
+ patchHeapFS();
1162
+ const ptr = Number(f.ptr);
1163
+ const heap = getHeapU8();
1164
+ if (heap.byteLength < ptr + 4) {
1165
+ throw new Error('heapfs finalize: ptr out of heap bounds ' + ptr);
1166
+ }
1167
+ const magic = String.fromCharCode(heap[ptr], heap[ptr + 1], heap[ptr + 2], heap[ptr + 3]);
1168
+ if (magic !== 'GGUF') {
1169
+ throw new Error('heapfs GGUF magic mismatch at ' + vfs_path + ': ' + JSON.stringify(magic));
1170
+ }
1171
+ const node = _mod.FS.lookupPath(vfs_path).node;
1172
+ node.contents = heap.subarray(ptr, ptr + f.size);
1173
+ node.usedBytes = f.size;
1174
+ const st = _mod.FS.stat(vfs_path);
1175
+ const fd = _mod.FS.open(vfs_path, 'r');
1176
+ const endPos = _mod.FS.llseek(fd, 0, 2);
1177
+ _mod.FS.close(fd);
1178
+ if (typeof console !== 'undefined') {
1179
+ console.error(
1180
+ '[llama-wasm] heapfs finalize: path=' + vfs_path +
1181
+ ' stat=' + st.size + ' llseek=' + endPos +
1182
+ ' expected=' + expectedBytes,
1183
+ );
1184
+ }
1185
+ if (endPos !== expectedBytes) {
1186
+ throw new Error(
1187
+ 'heapfs file size mismatch at ' + vfs_path + ': llseek=' + endPos + ' expected=' + expectedBytes,
1188
+ );
1189
+ }
1190
+ }
1191
+
1192
+ let _cwrapLoadContextFromPath = null;
1193
+
1194
+ function resolveLoadContextFromPathFn() {
1195
+ if (_cwrapLoadContextFromPath) return _cwrapLoadContextFromPath;
1196
+ const name = 'llama_load_context_from_path';
1197
+ const tryFns = [];
1198
+ if (typeof _mod.cwrap === 'function') {
1199
+ tryFns.push(() => _mod.cwrap(name, 'bigint', ['string', 'string']));
1200
+ tryFns.push(() => _mod.cwrap(name, 'number', ['string', 'string']));
1201
+ }
1202
+ if (_mod.wasmExports?.[name]) {
1203
+ tryFns.push(() => _mod.wasmExports[name]);
1204
+ }
1205
+ if (typeof _mod['_' + name] === 'function') {
1206
+ tryFns.push(() => _mod['_' + name]);
1207
+ }
1208
+ for (const get of tryFns) {
1209
+ try {
1210
+ const raw = get();
1211
+ if (typeof raw === 'function') {
1212
+ _cwrapLoadContextFromPath = (path, opts) => wasmI64ToNumber(raw(path, opts));
1213
+ return _cwrapLoadContextFromPath;
1214
+ }
1215
+ } catch (_) {}
1216
+ }
1217
+ throw new Error('llama_load_context_from_path not exported (cwrap/wasmExports)');
1218
+ }
1219
+
1220
+ /** Load via C bridge (avoids bindgen throw on trap) + register context in Rust. */
1221
+ function wasmLoadContextFromPath(model_id, vfs_path, opts_json, mode) {
1222
+ if (mode === 'async') {
1223
+ enableAsyncFileEnv();
1224
+ } else {
1225
+ disableAsyncFileEnv();
1226
+ }
1227
+ const loadFn = resolveLoadContextFromPathFn();
1228
+ if (typeof console !== 'undefined') {
1229
+ console.error('[llama-wasm] cwrap load: path=' + vfs_path + ' wasmMB=' + wasmLinearMb());
1230
+ }
1231
+ const ctxId = loadFn(vfs_path, opts_json);
1232
+ if (typeof console !== 'undefined') {
1233
+ console.error('[llama-wasm] cwrap load result: ctxId=' + ctxId + ' wasmMB=' + wasmLinearMb());
1234
+ }
1235
+ if (ctxId <= 0) {
1236
+ throw new Error(
1237
+ 'llama_load_context_from_path returned ' + ctxId + ' for ' + vfs_path +
1238
+ ' wasmMB=' + wasmLinearMb() +
1239
+ (tailWasmStderr() ? ' | llama: ' + tailWasmStderr() : ''),
1240
+ );
1241
+ }
1242
+ registerModelContext(model_id, ctxId);
1243
+ return;
1244
+ }
1245
+
1246
+ function wasmThrowToError(err, context) {
1247
+ const WasmException = typeof WebAssembly !== 'undefined' && WebAssembly.Exception;
1248
+ const stderrTail = tailWasmStderr();
1249
+ const memNote = ' wasmMB=' + wasmLinearMb();
1250
+ if (WasmException && err instanceof WasmException) {
1251
+ let msg = 'WebAssembly.Exception (likely OOM during model/context init — try closing other tabs)';
1252
+ try {
1253
+ if (err.message) msg = String(err.message);
1254
+ } catch (_) {}
1255
+ if (stderrTail) msg += ' | llama: ' + stderrTail;
1256
+ return new Error(context + ': ' + msg + memNote);
1257
+ }
1258
+ if (err instanceof WebAssembly.RuntimeError) {
1259
+ const msg = err.message || 'WebAssembly runtime error (likely OOM during model load)';
1260
+ let suffix = msg;
1261
+ if (stderrTail) suffix += ' | llama: ' + stderrTail;
1262
+ return new Error(context + ': ' + suffix + memNote);
1263
+ }
1264
+ if (err instanceof Error && err.message && err.message !== 'undefined') {
1265
+ if (stderrTail) err.message += ' | llama: ' + stderrTail;
1266
+ err.message += memNote;
1267
+ return err;
1268
+ }
1269
+ const detail =
1270
+ typeof err === 'string' ? err :
1271
+ err instanceof Error ? err.message :
1272
+ err == null ? '' : String(err);
1273
+ let suffix = detail && detail !== 'undefined' ? detail :
1274
+ 'WASM trap during inference (OOM/stack in llama_decode — close other tabs and reload model)';
1275
+ if (stderrTail) suffix += ' | llama: ' + stderrTail;
1276
+ return new Error(context + ': ' + suffix + memNote);
1277
+ }
1278
+
1279
+ function vfsStatBytes(path) {
1280
+ try {
1281
+ const st = _mod.FS.stat(path);
1282
+ return typeof st.size === 'number' ? st.size : 0;
1283
+ } catch (_) {
1284
+ return -1;
1285
+ }
1286
+ }
1287
+
1288
+ function wasmLinearMb() {
1289
+ try {
1290
+ const buf = _mod.wasmMemory?.buffer ?? _mod.HEAPU8?.buffer;
1291
+ return buf ? +(buf.byteLength / 1024 / 1024).toFixed(1) : 0;
1292
+ } catch (_) {
1293
+ return 0;
1294
+ }
1295
+ }
1296
+
1297
+ const WASM_INITIAL_BYTES = 20 * 1024 * 1024;
1298
+ const WASM_MAX_BYTES = 2147483648;
1299
+ /** WASM pool cap — aligned with Emscripten MAXIMUM_MEMORY (2 GiB). */
1300
+ const WASM_POOL_CEILING_BYTES = WASM_MAX_BYTES;
1301
+ const WASM_POOL_RESERVE_BYTES = 64 * 1024 * 1024;
1302
+ const WASM_MAX_CONCURRENT_MODELS = 5;
1303
+ const WASM_MIN_HEADROOM_BYTES = 128 * 1024 * 1024;
1304
+
1305
+ function totalLoadedModelBytes() {
1306
+ let sum = 0;
1307
+ for (const b of _loadedModelBytes.values()) sum += b;
1308
+ return sum;
1309
+ }
1310
+
1311
+ function loadOptsForMemory(opts_json, mode, modelBytes) {
1312
+ let opts = {};
1313
+ try {
1314
+ opts = JSON.parse(opts_json || '{}');
1315
+ } catch (_) {}
1316
+ const embedding = opts.embedding === true || opts.embedding === 'true';
1317
+ const n_ctx = typeof opts.n_ctx === 'number' && opts.n_ctx > 0
1318
+ ? opts.n_ctx
1319
+ : embedding ? 128 : effectiveNctx(modelBytes, opts_json);
1320
+ // BERT/encoder: llama_encode() asserts n_ubatch >= n_tokens — always match n_batch to n_ctx.
1321
+ const n_batch = embedding
1322
+ ? n_ctx
1323
+ : (typeof opts.n_batch === 'number' && opts.n_batch > 0 ? opts.n_batch : 16);
1324
+ return {
1325
+ mode,
1326
+ n_ctx,
1327
+ n_batch,
1328
+ embedding,
1329
+ };
1330
+ }
1331
+
1332
+ /** Estimate WASM bytes for one model (weights + COPY path + KV/context headroom). */
1333
+ function estimateModelWasmFootprint(fileBytes, memOpts = {}, mode) {
1334
+ if (!(fileBytes > 0)) return 20 * 1024 * 1024;
1335
+ const embedding = memOpts.embedding === true;
1336
+ const heapfsMmap = mode === 'heapfs' && embedding;
1337
+ const heapfsCopy = (mode === 'heapfs' && !embedding) || (mode !== 'async' && embedding && mode !== 'heapfs');
1338
+ const n_ctx = typeof memOpts.n_ctx === 'number' && memOpts.n_ctx > 0
1339
+ ? memOpts.n_ctx
1340
+ : embedding ? 128 : 512;
1341
+ const n_batch = typeof memOpts.n_batch === 'number' && memOpts.n_batch > 0
1342
+ ? memOpts.n_batch
1343
+ : embedding ? n_ctx : 16;
1344
+ // Async OPFS (use_mmap=false) and HeapFS COPY duplicate weights during tensor init (~2× file size).
1345
+ const weightMultiplier = embedding
1346
+ ? (heapfsMmap ? 1.15 : heapfsCopy ? 2.2 : 2.0)
1347
+ : fileBytes > 200 * 1024 * 1024 ? 1.32 : 1.2;
1348
+ // Encoder-only (BERT): n_ubatch = min(n_batch, …) and must cover the full prompt.
1349
+ const effectiveBatch = embedding ? Math.max(n_batch, n_ctx) : n_batch;
1350
+ const ctxBytes = n_ctx * effectiveBatch * 4096;
1351
+ const graphReserve = embedding
1352
+ ? (heapfsMmap ? 64 * 1024 * 1024 : heapfsCopy ? 48 * 1024 * 1024 : (mode === 'async' ? 96 * 1024 * 1024 : 32 * 1024 * 1024))
1353
+ : 0;
1354
+ const proportional = Math.ceil(fileBytes * (embedding ? 0.25 : 0.12));
1355
+ const minHeadroom = embedding ? 72 * 1024 * 1024 : WASM_MIN_HEADROOM_BYTES;
1356
+ const headroom = Math.max(minHeadroom, proportional, ctxBytes) + graphReserve;
1357
+ return Math.ceil(fileBytes * weightMultiplier + headroom);
1358
+ }
1359
+
1360
+ function loadedModelsFootprintBytes(excludeModelId) {
1361
+ let sum = 0;
1362
+ for (const [id] of _loadedModelBytes) {
1363
+ if (excludeModelId != null && String(id) === String(excludeModelId)) continue;
1364
+ sum += getModelFootprintBytes(id);
1365
+ }
1366
+ return sum;
1367
+ }
1368
+
1369
+ function isModelResidentInWasm(model_id) {
1370
+ const id = String(model_id);
1371
+ return _loadedModelBytes.has(id) || _modelContextIds.has(id);
1372
+ }
1373
+
1374
+ /** Projected pool usage after loading one more model (footprint-based — no double-count). */
1375
+ function projectedWasmAfterLoad(model_id, fileBytes, memOpts) {
1376
+ const id = String(model_id);
1377
+ const linear = _mod?.HEAPU8?.length ?? _mod?.wasmMemory?.buffer?.byteLength ?? 0;
1378
+ if (isModelResidentInWasm(id)) {
1379
+ return linear;
1380
+ }
1381
+ const estimated = estimateModelWasmFootprint(fileBytes, memOpts);
1382
+ const residentCount = _loadedModelBytes.size;
1383
+ const residentFootprint = loadedModelsFootprintBytes(id);
1384
+ // Prefer calibrated footprints over linear heap size. Linear may already include
1385
+ // unused pre-grown headroom (or a prior failed grow to MAXIMUM_MEMORY), so
1386
+ // linear+estimated falsely rejects BGE+LFM2 co-residence inside 2 GiB.
1387
+ if (residentCount > 0) {
1388
+ return residentFootprint + estimated;
1389
+ }
1390
+ if (linear > 64 * 1024 * 1024) {
1391
+ return estimated;
1392
+ }
1393
+ return Math.max(linear, estimated);
1394
+ }
1395
+
1396
+ /** Reject load before grow when pool ceiling or slot limit would be exceeded. */
1397
+ function checkWasmLoadAdmission(model_id, fileBytes, opts_json, mode) {
1398
+ const id = String(model_id);
1399
+ if (isModelResidentInWasm(id)) {
1400
+ return;
1401
+ }
1402
+
1403
+ if (_loadedModelBytes.size >= WASM_MAX_CONCURRENT_MODELS) {
1404
+ throw new Error(
1405
+ '[llama-wasm] model slot limit reached (' + WASM_MAX_CONCURRENT_MODELS +
1406
+ ' concurrent contexts) — unload a model first',
1407
+ );
1408
+ }
1409
+ const memOpts = loadOptsForMemory(opts_json, mode, fileBytes);
1410
+ const estimated = estimateModelWasmFootprint(fileBytes, memOpts);
1411
+ const projected = projectedWasmAfterLoad(id, fileBytes, memOpts);
1412
+ const linear = _mod?.HEAPU8?.length ?? _mod?.wasmMemory?.buffer?.byteLength ?? 0;
1413
+ const admitLimit = WASM_MAX_BYTES - WASM_POOL_RESERVE_BYTES;
1414
+ if (projected > admitLimit) {
1415
+ const ggufMb = (fileBytes / 1048576).toFixed(0);
1416
+ const linearMb = (linear / 1048576).toFixed(0);
1417
+ throw new Error(
1418
+ '[llama-wasm] WASM pool would exceed ' + (WASM_MAX_BYTES / 1048576).toFixed(0) +
1419
+ ' MB (projected ' + (projected / 1048576).toFixed(0) +
1420
+ ' MB, heap now ' + linearMb + ' MB, GGUF ' + ggufMb + ' MB → est. +~' +
1421
+ (estimated / 1048576).toFixed(0) + ' MB for ' + id + ')',
1422
+ );
1423
+ }
1424
+ }
1425
+
1426
+ /** Same growth policy for BGE and LFM: 20 MB at init, then model size + headroom (max 2 GB). */
1427
+ function wasmMemoryTarget(modelBytes, memOpts = {}) {
1428
+ if (!(modelBytes > 0)) return WASM_INITIAL_BYTES;
1429
+ const n_ctx = memOpts.n_ctx ?? 512;
1430
+ const n_batch = memOpts.n_batch ?? 16;
1431
+ const ctxBytes = n_ctx * n_batch * 4096;
1432
+ const proportional = Math.ceil(modelBytes * 0.35);
1433
+ const headroom = Math.max(WASM_MIN_HEADROOM_BYTES, proportional, ctxBytes);
1434
+ const target = modelBytes + headroom;
1435
+ return Math.min(Math.max(WASM_INITIAL_BYTES, target), WASM_MAX_BYTES);
1436
+ }
1437
+
1438
+ /** Reserve headroom for vocab, BPE ranks, and KV cache before model init. */
1439
+ function ensureWasmMemoryHeadroom(modelBytes, opts_json, mode, model_id) {
1440
+ if (!_mod || !(modelBytes > 0)) return;
1441
+ if (model_id != null && isModelResidentInWasm(model_id)) return;
1442
+
1443
+ const current = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
1444
+ const memOpts = loadOptsForMemory(opts_json, mode, modelBytes);
1445
+ const modelsBytes = totalLoadedModelBytes() + modelBytes;
1446
+ const estimate = estimateModelWasmFootprint(modelBytes, memOpts, mode);
1447
+ const warmHeap = current > 64 * 1024 * 1024;
1448
+ let target;
1449
+ if (memOpts.embedding) {
1450
+ // Embed graph + sched reserve — budget extra headroom; heapfs mmap still needs graph slack.
1451
+ const graphSlack = mode === 'heapfs'
1452
+ ? 128 * 1024 * 1024
1453
+ : mode === 'async'
1454
+ ? 128 * 1024 * 1024
1455
+ : 48 * 1024 * 1024;
1456
+ const embedFloor = mode === 'heapfs' ? 256 * 1024 * 1024 : 224 * 1024 * 1024;
1457
+ target = Math.min(Math.max(current, estimate + graphSlack, embedFloor), WASM_MAX_BYTES);
1458
+ } else if (warmHeap || _loadedModelBytes.size > 0) {
1459
+ if (_loadedModelBytes.size > 0) {
1460
+ // Second+ resident model — incremental footprint plus COPY tensor headroom (use_mmap=0).
1461
+ const memOpts = loadOptsForMemory(opts_json, mode, modelBytes);
1462
+ const embedCopy = memOpts.embedding
1463
+ ? Math.max(96 * 1024 * 1024, Math.ceil(modelBytes * 3))
1464
+ : Math.max(32 * 1024 * 1024, Math.ceil(modelBytes * 1.5));
1465
+ const copyHeadroom = embedCopy;
1466
+ target = Math.min(current + estimate + copyHeadroom, WASM_MAX_BYTES);
1467
+ } else {
1468
+ // Async stream begin may have pre-grown; do not add estimate again.
1469
+ target = Math.min(Math.max(current, estimate), WASM_MAX_BYTES);
1470
+ }
1471
+ } else {
1472
+ target = wasmMemoryTarget(modelsBytes, memOpts);
1473
+ }
1474
+ if (target > WASM_MAX_BYTES) {
1475
+ throw new Error(
1476
+ '[llama-wasm] WASM grow target exceeds Emscripten max ' + (WASM_MAX_BYTES / 1048576).toFixed(0) +
1477
+ ' MB (requested ' + (target / 1048576).toFixed(0) + ' MB, heap now ' +
1478
+ (current / 1048576).toFixed(0) + ' MB)',
1479
+ );
1480
+ }
1481
+ if (current >= target) return;
1482
+
1483
+ let grown = false;
1484
+ if (typeof _mod.growMemory === 'function') {
1485
+ grown = _mod.growMemory(target) === 1;
1486
+ } else if (typeof _mod.emscripten_resize_heap === 'function') {
1487
+ grown = !!_mod.emscripten_resize_heap(target);
1488
+ }
1489
+ if (!grown) {
1490
+ throw new Error('[llama-wasm] failed to grow wasm memory to ' + target + ' bytes (have ' + current + ')');
1491
+ }
1492
+ heapfsResyncViews();
1493
+ const after = _mod.HEAPU8?.length ?? _mod.wasmMemory?.buffer?.byteLength ?? 0;
1494
+ if (typeof console !== 'undefined') {
1495
+ console.error(
1496
+ '[llama-wasm] memory grow: ' + (current / 1048576).toFixed(0) + 'MB -> ' +
1497
+ (after / 1048576).toFixed(0) + 'MB (models=' + (modelsBytes / 1048576).toFixed(0) +
1498
+ 'MB target=' + (target / 1048576).toFixed(0) + 'MB mode=' + (mode || 'async') + ')',
1499
+ );
1500
+ }
1501
+ }
1502
+
1503
+ /** Begin HeapFS stream — mmapAlloc grows Emscripten memory (wllama fs.alloc). */
1504
+ function heapfsStreamBegin(totalBytes, opts_json) {
1505
+ patchHeapFS();
1506
+ if (typeof console !== 'undefined') {
1507
+ console.error(
1508
+ '[llama-wasm] HeapFS begin: modelMB=' + (totalBytes / 1048576).toFixed(1) +
1509
+ ' wasmMB=' + wasmLinearMb() + ' (mmapAlloc will grow as needed)',
1510
+ );
1511
+ }
1512
+ const basename = 'wasm_stream_' + (_jsVfsCounter++) + '.gguf';
1513
+ _mod.FS.createDataFile('/models', basename, new ArrayBuffer(0), true, true, true);
1514
+ const heapId = heapfsAlloc(basename, totalBytes);
1515
+ const path = '/models/' + basename;
1516
+ _jsVfsStreams.set(path, { mode: 'heapfs', heapId, basename, offset: 0, size: totalBytes });
1517
+ return path;
1518
+ }
1519
+
1520
+ /** Begin streaming a model into WASM VFS. Pass total_bytes + opts_json for HeapFS pre-grow. */
1521
+ function asyncFileStreamBegin(totalBytes, opts_json) {
1522
+ patchHeapFS();
1523
+ enableAsyncFileEnv();
1524
+ // Cold load only: pre-grow here. When another model is resident, admission + grow
1525
+ // run in load_model_from_vfs (after checkWasmLoadAdmission) so we do not inflate
1526
+ // linear to MAXIMUM_MEMORY and then reject with linear+estimated.
1527
+ if (typeof totalBytes === 'number' && totalBytes > 0 && _loadedModelBytes.size === 0) {
1528
+ const loadOpts = vfsLoadOptsJson(opts_json ?? '{}', 'async', totalBytes);
1529
+ ensureWasmMemoryHeadroom(totalBytes, loadOpts, 'async');
1530
+ }
1531
+ const basename = 'wasm_async_' + (_jsVfsCounter++) + '.gguf';
1532
+ const path = '/models/' + basename;
1533
+ asyncFilePlaceholder(path, totalBytes);
1534
+ _jsVfsStreams.set(path, { mode: 'async', basename, size: totalBytes, bound: false });
1535
+ if (typeof console !== 'undefined') {
1536
+ console.error(
1537
+ '[llama-wasm] JSPI async begin: modelMB=' + (totalBytes / 1048576).toFixed(1) +
1538
+ ' wasmMB=' + wasmLinearMb() + ' (no full-model heap copy)',
1539
+ );
1540
+ }
1541
+ return path;
1542
+ }
1543
+
1544
+ function vfsOptsEmbedding(opts_json) {
1545
+ try {
1546
+ const o = JSON.parse(opts_json || '{}');
1547
+ return o.embedding === true || o.embedding === 'true';
1548
+ } catch (_) {
1549
+ return false;
1550
+ }
1551
+ }
1552
+
1553
+ /** Small embed (≤32MB): HeapFS + use_mmap zero-copy; larger embed falls back to async OPFS fread. */
1554
+ const EMBED_HEAPFS_MAX_BYTES = 32 * 1024 * 1024;
1555
+
1556
+ function preferHeapFsVfs(totalBytes, opts_json) {
1557
+ if (!supportsHeapFS() || !(totalBytes > 0)) return false;
1558
+ if (vfsOptsEmbedding(opts_json)) {
1559
+ if (totalBytes <= EMBED_HEAPFS_MAX_BYTES) return true;
1560
+ if (can_use_async_file()) return false;
1561
+ return true;
1562
+ }
1563
+ if (totalBytes < 100 * 1024 * 1024) return true;
1564
+ return false;
1565
+ }
1566
+
1567
+ function jsModelVfsBegin(totalBytes, opts_json) {
1568
+ if (preferHeapFsVfs(totalBytes, opts_json)) {
1569
+ return heapfsStreamBegin(totalBytes, opts_json);
1570
+ }
1571
+ if (can_use_async_file() && typeof totalBytes === 'number' && totalBytes > 0) {
1572
+ return asyncFileStreamBegin(totalBytes, opts_json);
1573
+ }
1574
+ if (typeof totalBytes === 'number' && totalBytes > 0 && supportsHeapFS()) {
1575
+ return heapfsStreamBegin(totalBytes, opts_json);
1576
+ }
1577
+ ensureMemfsTmp();
1578
+ const path = '/tmp/wasm_stream_' + (_jsVfsCounter++) + '.gguf';
1579
+ const stream = _mod.FS.open(path, 'w');
1580
+ _jsVfsStreams.set(path, { mode: 'memfs', stream, offset: 0 });
1581
+ return path;
1582
+ }
1583
+
1584
+ function jsModelVfsWrite(path, chunk) {
1585
+ const entry = _jsVfsStreams.get(path);
1586
+ if (!entry) {
1587
+ throw new Error('model_vfs_write: no open stream for ' + path);
1588
+ }
1589
+ if (!chunk || chunk.byteLength === 0) return;
1590
+ const buf = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
1591
+ if (entry.mode === 'async') {
1592
+ throw new Error(
1593
+ 'model_vfs_write on async path: use async_model_bind() instead of streaming into WASM heap',
1594
+ );
1595
+ }
1596
+ try {
1597
+ if (entry.mode === 'heapfs') {
1598
+ heapfsWrite(entry.heapId, buf, entry.offset);
1599
+ } else {
1600
+ _mod.FS.write(entry.stream, buf, 0, buf.byteLength, entry.offset);
1601
+ }
1602
+ entry.offset += buf.byteLength;
1603
+ } catch (err) {
1604
+ throw wasmThrowToError(err, 'VFS write failed at offset ' + entry.offset + ' for ' + path);
1605
+ }
1606
+ }
1607
+
1608
+ function jsModelVfsClose(path) {
1609
+ const entry = _jsVfsStreams.get(path);
1610
+ if (!entry) return;
1611
+ if (entry.mode === 'memfs' && entry.stream) {
1612
+ _mod.FS.close(entry.stream);
1613
+ }
1614
+ }
1615
+
1616
+ function jsModelVfsAbort(path) {
1617
+ const entry = _jsVfsStreams.get(path);
1618
+ jsModelVfsClose(path);
1619
+ _jsVfsStreams.delete(path);
1620
+ if (entry?.mode === 'heapfs' && entry.basename) {
1621
+ delete _heapfsNameToFile[entry.basename];
1622
+ if (entry.heapId != null) delete _heapfsIdToFile[entry.heapId];
1623
+ } else if (entry?.mode === 'async') {
1624
+ asyncFileRelease(path);
1625
+ }
1626
+ try {
1627
+ _mod.FS.unlink(path);
1628
+ } catch (_) {}
1629
+ }
1630
+
1631
+ function vfsLoadOptsJson(opts_json, mode, modelBytes) {
1632
+ let opts = {};
1633
+ try {
1634
+ opts = JSON.parse(opts_json || '{}');
1635
+ } catch (_) {}
1636
+ opts.n_threads = 1;
1637
+ const embedding = opts.embedding === true || opts.embedding === 'true';
1638
+ if (embedding) {
1639
+ if (opts.n_ctx == null || opts.n_ctx > 128) {
1640
+ opts.n_ctx = 128;
1641
+ }
1642
+ // BERT encode: n_ubatch must be >= token count; match n_batch to n_ctx.
1643
+ const encBatch = opts.n_ctx;
1644
+ if (opts.n_batch == null || opts.n_batch < encBatch) {
1645
+ opts.n_batch = encBatch;
1646
+ } else if (opts.n_batch > encBatch) {
1647
+ opts.n_batch = encBatch;
1648
+ }
1649
+ } else if (modelBytes < 100 * 1024 * 1024) {
1650
+ if (opts.n_ctx == null || opts.n_ctx > 256) {
1651
+ opts.n_ctx = 256;
1652
+ }
1653
+ if (opts.n_batch == null || opts.n_batch > 32) {
1654
+ opts.n_batch = 32;
1655
+ }
1656
+ }
1657
+ if (mode === 'heapfs') {
1658
+ // Small embed: GGUF lives in mmapAlloc heap — buffer_from_host_ptr avoids a second COPY.
1659
+ opts.use_mmap = embedding;
1660
+ if (!embedding) {
1661
+ opts.n_ctx = effectiveNctx(modelBytes, opts_json);
1662
+ if (opts.n_batch == null || opts.n_batch > 128) {
1663
+ opts.n_batch = 128;
1664
+ }
1665
+ }
1666
+ if (modelBytes > 600 * 1024 * 1024 && (opts.n_batch == null || opts.n_batch > 64)) {
1667
+ opts.n_batch = 64;
1668
+ }
1669
+ } else if (mode === 'async') {
1670
+ opts.use_mmap = false;
1671
+ if (!embedding) {
1672
+ opts.n_ctx = effectiveNctx(modelBytes, opts_json);
1673
+ }
1674
+ if (modelBytes > 600 * 1024 * 1024) {
1675
+ // 65536 vocab × n_batch logits buffer — keep small at 2GB heap after full weight copy.
1676
+ if (opts.n_batch == null || opts.n_batch > 16) {
1677
+ opts.n_batch = 16;
1678
+ }
1679
+ } else if (!embedding && (opts.n_batch == null || opts.n_batch > 128)) {
1680
+ opts.n_batch = 128;
1681
+ }
1682
+ if (!embedding && modelBytes > 500 * 1024 * 1024 && modelBytes <= 600 * 1024 * 1024 &&
1683
+ (opts.n_batch == null || opts.n_batch > 64)) {
1684
+ opts.n_batch = 64;
1685
+ }
1686
+ } else if (opts.use_mmap == null) {
1687
+ opts.use_mmap = false;
1688
+ }
1689
+ return JSON.stringify(opts);
1690
+ }
1691
+
1692
+ /** Begin streaming a model into WASM VFS. Pass total_bytes + opts_json for HeapFS pre-grow. */
1693
+ export function model_vfs_begin(total_bytes, opts_json) {
1694
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
1695
+ return jsModelVfsBegin(total_bytes, opts_json);
1696
+ }
1697
+
1698
+ /** Append a chunk to an in-progress VFS model write. */
1699
+ export function model_vfs_write(vfs_path, chunk) {
1700
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
1701
+ jsModelVfsWrite(vfs_path, chunk);
1702
+ }
1703
+
1704
+ /** Abort a partial VFS write and remove the temp file. */
1705
+ export function model_vfs_abort(vfs_path) {
1706
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
1707
+ jsModelVfsAbort(vfs_path);
1708
+ }
1709
+
1710
+ /** Finish a streamed VFS write and load the model. HeapFS mmap stays alive until unload. */
1711
+ export function load_model_from_vfs(model_id, vfs_path, opts_json) {
1712
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
1713
+ const entry = _jsVfsStreams.get(vfs_path);
1714
+ const mode = entry?.mode ?? 'memfs';
1715
+ jsModelVfsClose(vfs_path);
1716
+ const bytes = mode === 'heapfs' || mode === 'async'
1717
+ ? (entry?.size ?? 0)
1718
+ : vfsStatBytes(vfs_path);
1719
+ if (bytes <= 0) {
1720
+ throw new Error('VFS model file missing or empty after stream: ' + vfs_path + ' (bytes=' + bytes + ')');
1721
+ }
1722
+ if (mode === 'async') {
1723
+ const basename = entry?.basename ?? stripModelsPrefix(vfs_path);
1724
+ if (!_asyncFileHandlers.has(basename)) {
1725
+ throw new Error(
1726
+ 'async VFS path not bound — call async_model_bind() before load_model_from_vfs: ' + vfs_path,
1727
+ );
1728
+ }
1729
+ }
1730
+ const loadOpts = vfsLoadOptsJson(opts_json, mode, bytes);
1731
+ if (typeof console !== 'undefined') {
1732
+ console.error(
1733
+ '[llama-wasm] load_model_from_vfs: mode=' + mode +
1734
+ ' wasmMB=' + wasmLinearMb() + ' opts=' + loadOpts,
1735
+ );
1736
+ }
1737
+ _jsVfsStreams.delete(vfs_path);
1738
+ if (isModelResidentInWasm(model_id)) {
1739
+ if (typeof console !== 'undefined') {
1740
+ console.error(
1741
+ '[llama-wasm] load_model_from_vfs: skip — already loaded model_id=' + model_id +
1742
+ ' wasmMB=' + wasmLinearMb(),
1743
+ );
1744
+ }
1745
+ return;
1746
+ }
1747
+ try {
1748
+ checkWasmLoadAdmission(model_id, bytes, loadOpts, mode);
1749
+ if (mode === 'heapfs') {
1750
+ ensureWasmMemoryHeadroom(bytes, loadOpts, 'heapfs', model_id);
1751
+ heapfsFinalizeForLoad(vfs_path, entry.basename, bytes);
1752
+ } else if (mode === 'async') {
1753
+ enableAsyncFileEnv();
1754
+ // asyncFileStreamBegin grows for cold load; re-run when another model is already resident.
1755
+ if (_loadedModelBytes.size > 0) {
1756
+ ensureWasmMemoryHeadroom(bytes, loadOpts, 'async', model_id);
1757
+ }
1758
+ } else {
1759
+ ensureWasmMemoryHeadroom(bytes, loadOpts, 'memfs', model_id);
1760
+ }
1761
+ _linearAtLoadStart.set(String(model_id), wasmLinearBytesNow());
1762
+ // COPY tensor path + llm_graph_result reserve can exceed pre-stream grow — top up before cwrap.
1763
+ const embedLoad = vfsOptsEmbedding(loadOpts);
1764
+ if (mode === 'heapfs' || mode === 'memfs' || (mode === 'async' && embedLoad)) {
1765
+ ensureWasmMemoryHeadroom(bytes, loadOpts, mode, model_id);
1766
+ }
1767
+ // P2 diagnostics: Log memory state before and after load
1768
+ if (embedLoad && (mode === 'async' || mode === 'heapfs')) {
1769
+ console.error('[llama-wasm] P2-diag: pre-tensor wasmMB=' + wasmLinearMb() + ' model=' + model_id + ' mode=' + mode);
1770
+ }
1771
+
1772
+ wasmLoadContextFromPath(model_id, vfs_path, loadOpts, mode);
1773
+
1774
+ if (embedLoad && (mode === 'async' || mode === 'heapfs')) {
1775
+ console.error('[llama-wasm] P2-diag: post-load wasmMB=' + wasmLinearMb() + ' model=' + model_id + ' mode=' + mode);
1776
+ }
1777
+
1778
+ _loadedModelBytes.set(String(model_id), bytes);
1779
+ try {
1780
+ _loadedModelOpts.set(String(model_id), JSON.parse(loadOpts || '{}'));
1781
+ } catch (_) {
1782
+ _loadedModelOpts.set(String(model_id), {});
1783
+ }
1784
+ calibrateModelFootprintAfterLoad(model_id);
1785
+ if (mode === 'heapfs' && entry?.basename) {
1786
+ _loadedHeapfsModels.set(model_id, {
1787
+ path: vfs_path,
1788
+ basename: entry.basename,
1789
+ heapId: entry.heapId,
1790
+ });
1791
+ } else if (mode === 'async') {
1792
+ _loadedAsyncModels.set(model_id, vfs_path);
1793
+ }
1794
+ } catch (err) {
1795
+ // P2: Capture comprehensive diagnostics on error
1796
+ const wasmMbAfterFail = wasmLinearMb();
1797
+ const stderrTail = tailWasmStderr(1200); // capture last 1200 chars of stderr
1798
+ const errorMsg = '[llama-wasm] load_model_from_vfs FAILED: model=' + model_id +
1799
+ ' mode=' + mode + ' wasmMbAfterFail=' + wasmMbAfterFail +
1800
+ ' err=' + (err instanceof Error ? err.message : String(err));
1801
+
1802
+ if (typeof console !== 'undefined') {
1803
+ console.error(errorMsg);
1804
+ if (stderrTail) {
1805
+ console.error('[llama-wasm] stderr tail (last 1200 chars):\n' + stderrTail);
1806
+ }
1807
+ }
1808
+
1809
+ if (mode === 'heapfs') {
1810
+ heapfsReleaseEntry(vfs_path, entry);
1811
+ } else if (mode === 'async') {
1812
+ asyncFileRelease(vfs_path);
1813
+ } else {
1814
+ try {
1815
+ _mod.FS.unlink(vfs_path);
1816
+ } catch (_) {}
1817
+ }
1818
+ throw wasmThrowToError(
1819
+ err,
1820
+ 'load_model_from_vfs failed for ' + vfs_path + ' (' + bytes + ' bytes, mode=' + mode + ', wasmMb=' + wasmLinearMb() + ')',
1821
+ );
1822
+ }
1823
+ }
1824
+
1825
+ /** Load from an existing VFS path (HeapFS — zero-copy mmap). */
1826
+ export function load_model_from_path(model_id, vfs_path, opts_json) {
1827
+ if (!_mod) throw new Error('llama_engine not ready — await init() first');
1828
+ wasmBindgenLoadModelFromPath(model_id, vfs_path, opts_json);
1829
+ }