@glassly/engine 0.4.0-dev.1064 → 0.4.0-dev.1066
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/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/LocalLlmModelManager.d.ts.map +1 -1
- package/build/services/LocalLlmModelManager.js +4 -3
- package/build/services/LocalLlmModelManager.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +5 -6
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/package.json +15 -15
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/LocalLlmModelManager.ts +4 -3
- package/src/services/LocalLlmProvider.ts +6 -7
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const ENGINE_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "0.4.0",
|
|
4
|
-
"releaseIdentity": "0.4.0-dev.
|
|
5
|
-
"releaseSetId": "glassly-0.4.0-dev.
|
|
6
|
-
"sourceCommit": "
|
|
4
|
+
"releaseIdentity": "0.4.0-dev.1066",
|
|
5
|
+
"releaseSetId": "glassly-0.4.0-dev.1066",
|
|
6
|
+
"sourceCommit": "1df55b8e89eeec2cb01fa011748fed92c1f5e920",
|
|
7
7
|
"otaManifestUrl": null,
|
|
8
8
|
"otaManifestSha256": null
|
|
9
9
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,gBAAgB;IACnC,cAAc,EAAE,wBAAwB;IACxC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.4.0\",\n \"releaseIdentity\": \"0.4.0-dev.
|
|
1
|
+
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,gBAAgB;IACnC,cAAc,EAAE,wBAAwB;IACxC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.4.0\",\n \"releaseIdentity\": \"0.4.0-dev.1066\",\n \"releaseSetId\": \"glassly-0.4.0-dev.1066\",\n \"sourceCommit\": \"1df55b8e89eeec2cb01fa011748fed92c1f5e920\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLlmModelManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,CAAA;AAEtE,eAAO,MAAM,uBAAuB,EAAE,eAA8B,CAAA;AAEpE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,eAAe,CAAA;IACnB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAA;IAClB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAED,qGAAqG;AACrG,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAA;AAElF,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,eAAe,CAAA;IACxB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;
|
|
1
|
+
{"version":3,"file":"LocalLlmModelManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,9 +17,10 @@ import { SETTINGS, useSettingsStore } from "../stores/settings";
|
|
|
17
17
|
import { storage } from "../utils/storage";
|
|
18
18
|
export const DEFAULT_LOCAL_LLM_MODEL = "qwen3-1.7b";
|
|
19
19
|
// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
|
|
20
|
-
// pinned to the v0.10.0 release tag
|
|
21
|
-
//
|
|
22
|
-
//
|
|
20
|
+
// pinned to the v0.10.0 release tag. They export every method the 0.11 text
|
|
21
|
+
// runner validates; moving to the v0.11.0 re-exports changes the file sizes and
|
|
22
|
+
// makes every existing download re-fetch. modelBytes are the .pte sizes on the
|
|
23
|
+
// Hub; sizeBytes adds the tokenizer files (~11 MB).
|
|
23
24
|
const QWEN3_BASE = "https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0";
|
|
24
25
|
const TOKENIZER_BYTES = 11_431_080;
|
|
25
26
|
const MB = 1024 * 1024;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLlmModelManager.js","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmModelManager — the on-device LLM model files: a catalog of\n * ExecuTorch exports (Software Mansion's Qwen3 .pte builds on Hugging Face),\n * their download into Documents/llm_models/<id>/, availability checks, and a\n * status stream the Settings > AI Models screen renders. Modelled on\n * STTModelManager; the download itself is RNFS so the Settings screen can\n * list, cancel, and delete models with the same calls the STT rows use.\n *\n * Which model is \"the\" on-device model is the `local_llm_model` setting\n * (Settings > AI Models). Downloads are on demand: the first request that\n * routes on-device without a model present starts one (ensureSelectedModel),\n * and the Settings screen can start one explicitly.\n */\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {storage} from \"../utils/storage\"\n\nexport type LocalLlmModelId = \"qwen3-0.6b\" | \"qwen3-1.7b\" | \"qwen3-4b\"\n\nexport const DEFAULT_LOCAL_LLM_MODEL: LocalLlmModelId = \"qwen3-1.7b\"\n\nexport interface LocalLlmModelConfig {\n id: LocalLlmModelId\n /** Human label for the Settings dropdown. */\n label: string\n /** Total download size in bytes (the .pte dominates). */\n sizeBytes: number\n /** Exact size of the published .pte; the availability check compares against it. */\n modelBytes: number\n /** Rough resident memory while loaded; `fitsDevice` holds it against the phone's RAM. */\n ramBytes: number\n urls: {\n model: string\n tokenizer: string\n tokenizerConfig: string\n }\n}\n\n/** The three files every model directory holds; names are fixed so paths never depend on the URL. */\nexport interface LocalLlmModelPaths {\n model: string\n tokenizer: string\n tokenizerConfig: string\n}\n\nexport type LocalLlmDownloadStage = \"idle\" | \"downloading\" | \"complete\" | \"failed\"\n\nexport interface LocalLlmDownloadStatus {\n modelId: LocalLlmModelId\n stage: LocalLlmDownloadStage\n /** 0–100, meaningful while stage is \"downloading\". */\n percent: number\n error?: string\n}\n\n// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,\n// pinned to the v0.10.0 release tag that react-native-executorch 0.10.x\n// expects. modelBytes are the .pte sizes on the Hub; sizeBytes adds the\n// tokenizer files (~11 MB).\nconst QWEN3_BASE = \"https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0\"\nconst TOKENIZER_BYTES = 11_431_080\nconst MB = 1024 * 1024\nconst GB = 1024 * MB\n\nconst MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {\n \"qwen3-0.6b\": {\n id: \"qwen3-0.6b\",\n label: \"Qwen3 0.6B\",\n sizeBytes: 505_686_400 + TOKENIZER_BYTES,\n modelBytes: 505_686_400,\n ramBytes: 0.9 * GB,\n urls: {\n model: `${QWEN3_BASE}/0_6b/xnnpack/qwen_3_0_6b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-1.7b\": {\n id: \"qwen3-1.7b\",\n label: \"Qwen3 1.7B\",\n sizeBytes: 1_303_738_496 + TOKENIZER_BYTES,\n modelBytes: 1_303_738_496,\n ramBytes: 1.8 * GB,\n urls: {\n model: `${QWEN3_BASE}/1_7b/xnnpack/qwen_3_1_7b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-4b\": {\n id: \"qwen3-4b\",\n label: \"Qwen3 4B\",\n sizeBytes: 2_681_646_976 + TOKENIZER_BYTES,\n modelBytes: 2_681_646_976,\n ramBytes: 3.4 * GB,\n urls: {\n model: `${QWEN3_BASE}/4b/xnnpack/qwen_3_4b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n}\n\nexport function isLocalLlmModelId(value: unknown): value is LocalLlmModelId {\n return typeof value === \"string\" && value in MODELS\n}\n\n// Without the increased-memory-limit entitlement iOS kills a foreground app\n// at roughly 60% of physical RAM (Android's low-memory killer is no kinder),\n// and the host holds about 1 GB besides the model: JS runtimes, WebViews,\n// the speech model. A 4 GB phone fits the 0.6B model and nothing larger.\nconst APP_MEMORY_SHARE = 0.6\nconst HOST_BASELINE_BYTES = 1 * GB\n\nexport function modelFitsMemory(model: LocalLlmModelConfig, deviceMemoryBytes: number | null): boolean {\n if (!deviceMemoryBytes) return true\n return model.ramBytes + HOST_BASELINE_BYTES <= deviceMemoryBytes * APP_MEMORY_SHARE\n}\n\nlet deviceMemory: number | null | undefined\nfunction deviceMemoryBytes(): number | null {\n if (deviceMemory === undefined) {\n try {\n deviceMemory = (require(\"expo-device\") as {totalMemory?: number | null}).totalMemory ?? null\n } catch {\n deviceMemory = null\n }\n }\n return deviceMemory\n}\n\n// Written before a model's first load and generation, cleared after either\n// ends. A marker still there at the next launch means the OS killed the app\n// mid-load (out of memory), so that model stays off until picked again.\nconst COLD_LOAD_KEY = \"local_llm_cold_load\"\nconst BLOCKED_MODELS_KEY = \"local_llm_blocked_models\"\n\ntype StatusListener = (status: LocalLlmDownloadStatus | null) => void\n\nclass LocalLlmModelManager {\n private static instance: LocalLlmModelManager\n\n private downloadJobId?: number\n private inFlight: {modelId: LocalLlmModelId; promise: Promise<void>} | null = null\n private cancelled = false\n private coldLoadRecovered = false\n private currentStatus: LocalLlmDownloadStatus | null = null\n private listeners = new Set<StatusListener>()\n private modelReadyListeners = new Set<(modelId: LocalLlmModelId) => void>()\n\n private constructor() {}\n\n static getInstance(): LocalLlmModelManager {\n if (!LocalLlmModelManager.instance) {\n LocalLlmModelManager.instance = new LocalLlmModelManager()\n }\n return LocalLlmModelManager.instance\n }\n\n models(): LocalLlmModelConfig[] {\n return Object.values(MODELS)\n }\n\n model(id: LocalLlmModelId): LocalLlmModelConfig {\n return MODELS[id]\n }\n\n /**\n * The model on-device requests use: the Settings > Offline Models pick\n * (garbage resolves to the default) while it is usable, else the largest\n * model that is. With nothing usable it stays the pick, and loading it\n * fails with `unusableReason`.\n */\n selectedModelId(): LocalLlmModelId {\n const raw = useSettingsStore.getState().getSetting(SETTINGS.local_llm_model.key)\n const picked = isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL\n if (this.isUsable(picked)) return picked\n const fallback = this.models()\n .filter((m) => this.isUsable(m.id))\n .sort((a, b) => b.ramBytes - a.ramBytes)[0]\n return fallback?.id ?? picked\n }\n\n /** Picking a model in Settings is also the way to retry one that crashed the app. */\n async select(id: LocalLlmModelId): Promise<void> {\n this.unblock(id)\n await useSettingsStore.getState().setSetting(SETTINGS.local_llm_model.key, id)\n }\n\n fitsDevice(id: LocalLlmModelId): boolean {\n return modelFitsMemory(MODELS[id], deviceMemoryBytes())\n }\n\n /** The OS killed the app while this model loaded; it stays off until picked again. */\n isBlocked(id: LocalLlmModelId): boolean {\n return this.blockedModelIds().includes(id)\n }\n\n isUsable(id: LocalLlmModelId): boolean {\n return this.fitsDevice(id) && !this.isBlocked(id)\n }\n\n /** Why `id` will not load on this phone, or null when it can. */\n unusableReason(id: LocalLlmModelId): string | null {\n const label = MODELS[id].label\n if (!this.fitsDevice(id)) {\n return `${label} needs more memory than this phone has. Pick a smaller model in Settings > Offline Models.`\n }\n if (this.isBlocked(id)) {\n return `${label} closed the app the last time it loaded. Pick it again in Settings > Offline Models to retry, or pick a smaller model.`\n }\n return null\n }\n\n /** Mark the start of a model's first load and generation in this process. */\n beginColdLoad(id: LocalLlmModelId): void {\n this.recoverColdLoad()\n storage.save(COLD_LOAD_KEY, id)\n }\n\n endColdLoad(): void {\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n private blockedModelIds(): LocalLlmModelId[] {\n this.recoverColdLoad()\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const ids = stored.is_ok() ? stored.value : []\n return Array.isArray(ids) ? ids.filter(isLocalLlmModelId) : []\n }\n\n private unblock(id: LocalLlmModelId): void {\n const remaining = this.blockedModelIds().filter((blocked) => blocked !== id)\n storage.save(BLOCKED_MODELS_KEY, remaining)\n }\n\n // A cold-load marker left by an earlier process: that load never finished.\n private recoverColdLoad(): void {\n if (this.coldLoadRecovered) return\n this.coldLoadRecovered = true\n const marker = storage.load<unknown>(COLD_LOAD_KEY)\n if (!marker.is_ok() || !isLocalLlmModelId(marker.value)) return\n const id = marker.value\n console.warn(`LOCAL_LLM: the app was killed while ${id} loaded; keeping it off until it is picked again`)\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const blocked = stored.is_ok() && Array.isArray(stored.value) ? stored.value.filter(isLocalLlmModelId) : []\n if (!blocked.includes(id)) storage.save(BLOCKED_MODELS_KEY, [...blocked, id])\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/llm_models`\n }\n\n paths(id: LocalLlmModelId = this.selectedModelId()): LocalLlmModelPaths {\n const dir = `${this.getModelDirectory()}/${id}`\n return {\n model: `${dir}/model.pte`,\n tokenizer: `${dir}/tokenizer.json`,\n tokenizerConfig: `${dir}/tokenizer_config.json`,\n }\n }\n\n async isModelAvailable(id: LocalLlmModelId = this.selectedModelId()): Promise<boolean> {\n const paths = this.paths(id)\n try {\n for (const path of [paths.tokenizerConfig, paths.tokenizer, paths.model]) {\n if (!(await RNFS.exists(path))) return false\n }\n // A truncated .pte loads as garbage or crashes the runtime: insist on\n // most of the published size. Downloads land in a .part file and are\n // renamed only once whole, so this only catches external tampering.\n const stat = await RNFS.stat(paths.model)\n return Number(stat.size) >= MODELS[id].modelBytes * 0.95\n } catch (error) {\n console.warn(\"LOCAL_LLM: availability check failed:\", error)\n return false\n }\n }\n\n async downloadedModelIds(): Promise<LocalLlmModelId[]> {\n const ids: LocalLlmModelId[] = []\n for (const id of Object.keys(MODELS) as LocalLlmModelId[]) {\n if (await this.isModelAvailable(id)) ids.push(id)\n }\n return ids\n }\n\n /** Subscribe to download status changes. Fires immediately with the current status. */\n subscribe(listener: StatusListener): () => void {\n this.listeners.add(listener)\n listener(this.currentStatus)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getStatus(): LocalLlmDownloadStatus | null {\n return this.currentStatus\n }\n\n /** Fired once a model's files are complete on disk. */\n onModelReady(listener: (modelId: LocalLlmModelId) => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n /**\n * True when the selected model is on disk. Otherwise starts (or joins) its\n * download in the background and returns false — the caller reports\n * \"downloading\" rather than waiting out a multi-GB transfer.\n */\n async ensureSelectedModel(): Promise<boolean> {\n const id = this.selectedModelId()\n if (await this.isModelAvailable(id)) return true\n if (!this.fitsDevice(id)) return false\n void this.downloadModel(id).catch((error) => {\n console.warn(`LOCAL_LLM: background download of ${id} failed:`, error)\n })\n return false\n }\n\n /**\n * Download one model (three files, model last). Concurrent calls for the\n * same model share the transfer; a call for a different model waits for\n * the running one — the RNFS job slot is singular.\n */\n async downloadModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n if (!this.fitsDevice(id)) throw new Error(this.unusableReason(id) ?? `${MODELS[id].label} does not fit this phone.`)\n if (this.inFlight) {\n if (this.inFlight.modelId === id) return this.inFlight.promise\n await this.inFlight.promise.catch(() => undefined)\n }\n const promise = this.downloadInner(id).finally(() => {\n this.inFlight = null\n })\n this.inFlight = {modelId: id, promise}\n return promise\n }\n\n private async downloadInner(id: LocalLlmModelId): Promise<void> {\n const config = MODELS[id]\n const paths = this.paths(id)\n const dir = `${this.getModelDirectory()}/${id}`\n this.cancelled = false\n this.setStatus({modelId: id, stage: \"downloading\", percent: 0})\n\n try {\n await RNFS.mkdir(dir, {NSURLIsExcludedFromBackupKey: true})\n // Tokenizer files first (small), the weights last: a model directory\n // is only \"available\" once model.pte exists, so an interrupted run\n // never leaves a complete-looking directory behind.\n await this.fetchFile(config.urls.tokenizerConfig, paths.tokenizerConfig)\n await this.fetchFile(config.urls.tokenizer, paths.tokenizer)\n await this.fetchFile(config.urls.model, paths.model, (percent) =>\n this.setStatus({modelId: id, stage: \"downloading\", percent}),\n )\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`downloaded model is smaller than the expected ${config.modelBytes} bytes`)\n }\n this.setStatus({modelId: id, stage: \"complete\", percent: 100})\n for (const listener of this.modelReadyListeners) {\n try {\n listener(id)\n } catch (error) {\n console.warn(\"LOCAL_LLM: model-ready listener failed:\", error)\n }\n }\n } catch (error) {\n const message = this.cancelled ? \"download cancelled\" : error instanceof Error ? error.message : String(error)\n this.setStatus({modelId: id, stage: this.cancelled ? \"idle\" : \"failed\", percent: 0, error: message})\n try {\n if (await RNFS.exists(`${paths.model}.part`)) await RNFS.unlink(`${paths.model}.part`)\n } catch (cleanupError) {\n console.warn(\"LOCAL_LLM: partial cleanup failed:\", cleanupError)\n }\n throw error\n } finally {\n this.downloadJobId = undefined\n }\n }\n\n private async fetchFile(url: string, dest: string, onPercent?: (percent: number) => void): Promise<void> {\n if (this.cancelled) throw new Error(\"download cancelled\")\n const partial = `${dest}.part`\n const download = RNFS.downloadFile({\n fromUrl: url,\n toFile: partial,\n // iOS keeps a background URLSession transfer going while the app is\n // suspended; the files here are large enough to need it.\n background: Platform.OS === \"ios\",\n discretionary: false,\n progressDivider: 1,\n connectionTimeout: 30_000,\n readTimeout: 60_000,\n progress: (res) => {\n if (!onPercent || res.contentLength <= 0) return\n onPercent(Math.min(99, Math.floor((res.bytesWritten / res.contentLength) * 100)))\n },\n })\n this.downloadJobId = download.jobId\n const result = await download.promise\n if (result.statusCode !== 200) {\n throw new Error(`download failed with status ${result.statusCode}`)\n }\n if (await RNFS.exists(dest)) await RNFS.unlink(dest)\n await RNFS.moveFile(partial, dest)\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId === undefined) return\n this.cancelled = true\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n\n async deleteModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n const dir = `${this.getModelDirectory()}/${id}`\n if (await RNFS.exists(dir)) await RNFS.unlink(dir)\n if (this.currentStatus?.modelId === id) this.setStatus(null)\n }\n\n private setStatus(status: LocalLlmDownloadStatus | null): void {\n this.currentStatus = status\n for (const listener of this.listeners) {\n try {\n listener(status)\n } catch (error) {\n console.warn(\"LOCAL_LLM: status listener threw:\", error)\n }\n }\n }\n\n static formatBytes(bytes: number): string {\n if (bytes >= GB) return `${(bytes / GB).toFixed(1)} GB`\n return `${Math.round(bytes / MB)} MB`\n }\n}\n\nconst instance = LocalLlmModelManager.getInstance()\nexport {LocalLlmModelManager}\nexport default instance\n"]}
|
|
1
|
+
{"version":3,"file":"LocalLlmModelManager.js","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmModelManager — the on-device LLM model files: a catalog of\n * ExecuTorch exports (Software Mansion's Qwen3 .pte builds on Hugging Face),\n * their download into Documents/llm_models/<id>/, availability checks, and a\n * status stream the Settings > AI Models screen renders. Modelled on\n * STTModelManager; the download itself is RNFS so the Settings screen can\n * list, cancel, and delete models with the same calls the STT rows use.\n *\n * Which model is \"the\" on-device model is the `local_llm_model` setting\n * (Settings > AI Models). Downloads are on demand: the first request that\n * routes on-device without a model present starts one (ensureSelectedModel),\n * and the Settings screen can start one explicitly.\n */\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {storage} from \"../utils/storage\"\n\nexport type LocalLlmModelId = \"qwen3-0.6b\" | \"qwen3-1.7b\" | \"qwen3-4b\"\n\nexport const DEFAULT_LOCAL_LLM_MODEL: LocalLlmModelId = \"qwen3-1.7b\"\n\nexport interface LocalLlmModelConfig {\n id: LocalLlmModelId\n /** Human label for the Settings dropdown. */\n label: string\n /** Total download size in bytes (the .pte dominates). */\n sizeBytes: number\n /** Exact size of the published .pte; the availability check compares against it. */\n modelBytes: number\n /** Rough resident memory while loaded; `fitsDevice` holds it against the phone's RAM. */\n ramBytes: number\n urls: {\n model: string\n tokenizer: string\n tokenizerConfig: string\n }\n}\n\n/** The three files every model directory holds; names are fixed so paths never depend on the URL. */\nexport interface LocalLlmModelPaths {\n model: string\n tokenizer: string\n tokenizerConfig: string\n}\n\nexport type LocalLlmDownloadStage = \"idle\" | \"downloading\" | \"complete\" | \"failed\"\n\nexport interface LocalLlmDownloadStatus {\n modelId: LocalLlmModelId\n stage: LocalLlmDownloadStage\n /** 0–100, meaningful while stage is \"downloading\". */\n percent: number\n error?: string\n}\n\n// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,\n// pinned to the v0.10.0 release tag. They export every method the 0.11 text\n// runner validates; moving to the v0.11.0 re-exports changes the file sizes and\n// makes every existing download re-fetch. modelBytes are the .pte sizes on the\n// Hub; sizeBytes adds the tokenizer files (~11 MB).\nconst QWEN3_BASE = \"https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0\"\nconst TOKENIZER_BYTES = 11_431_080\nconst MB = 1024 * 1024\nconst GB = 1024 * MB\n\nconst MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {\n \"qwen3-0.6b\": {\n id: \"qwen3-0.6b\",\n label: \"Qwen3 0.6B\",\n sizeBytes: 505_686_400 + TOKENIZER_BYTES,\n modelBytes: 505_686_400,\n ramBytes: 0.9 * GB,\n urls: {\n model: `${QWEN3_BASE}/0_6b/xnnpack/qwen_3_0_6b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-1.7b\": {\n id: \"qwen3-1.7b\",\n label: \"Qwen3 1.7B\",\n sizeBytes: 1_303_738_496 + TOKENIZER_BYTES,\n modelBytes: 1_303_738_496,\n ramBytes: 1.8 * GB,\n urls: {\n model: `${QWEN3_BASE}/1_7b/xnnpack/qwen_3_1_7b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-4b\": {\n id: \"qwen3-4b\",\n label: \"Qwen3 4B\",\n sizeBytes: 2_681_646_976 + TOKENIZER_BYTES,\n modelBytes: 2_681_646_976,\n ramBytes: 3.4 * GB,\n urls: {\n model: `${QWEN3_BASE}/4b/xnnpack/qwen_3_4b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n}\n\nexport function isLocalLlmModelId(value: unknown): value is LocalLlmModelId {\n return typeof value === \"string\" && value in MODELS\n}\n\n// Without the increased-memory-limit entitlement iOS kills a foreground app\n// at roughly 60% of physical RAM (Android's low-memory killer is no kinder),\n// and the host holds about 1 GB besides the model: JS runtimes, WebViews,\n// the speech model. A 4 GB phone fits the 0.6B model and nothing larger.\nconst APP_MEMORY_SHARE = 0.6\nconst HOST_BASELINE_BYTES = 1 * GB\n\nexport function modelFitsMemory(model: LocalLlmModelConfig, deviceMemoryBytes: number | null): boolean {\n if (!deviceMemoryBytes) return true\n return model.ramBytes + HOST_BASELINE_BYTES <= deviceMemoryBytes * APP_MEMORY_SHARE\n}\n\nlet deviceMemory: number | null | undefined\nfunction deviceMemoryBytes(): number | null {\n if (deviceMemory === undefined) {\n try {\n deviceMemory = (require(\"expo-device\") as {totalMemory?: number | null}).totalMemory ?? null\n } catch {\n deviceMemory = null\n }\n }\n return deviceMemory\n}\n\n// Written before a model's first load and generation, cleared after either\n// ends. A marker still there at the next launch means the OS killed the app\n// mid-load (out of memory), so that model stays off until picked again.\nconst COLD_LOAD_KEY = \"local_llm_cold_load\"\nconst BLOCKED_MODELS_KEY = \"local_llm_blocked_models\"\n\ntype StatusListener = (status: LocalLlmDownloadStatus | null) => void\n\nclass LocalLlmModelManager {\n private static instance: LocalLlmModelManager\n\n private downloadJobId?: number\n private inFlight: {modelId: LocalLlmModelId; promise: Promise<void>} | null = null\n private cancelled = false\n private coldLoadRecovered = false\n private currentStatus: LocalLlmDownloadStatus | null = null\n private listeners = new Set<StatusListener>()\n private modelReadyListeners = new Set<(modelId: LocalLlmModelId) => void>()\n\n private constructor() {}\n\n static getInstance(): LocalLlmModelManager {\n if (!LocalLlmModelManager.instance) {\n LocalLlmModelManager.instance = new LocalLlmModelManager()\n }\n return LocalLlmModelManager.instance\n }\n\n models(): LocalLlmModelConfig[] {\n return Object.values(MODELS)\n }\n\n model(id: LocalLlmModelId): LocalLlmModelConfig {\n return MODELS[id]\n }\n\n /**\n * The model on-device requests use: the Settings > Offline Models pick\n * (garbage resolves to the default) while it is usable, else the largest\n * model that is. With nothing usable it stays the pick, and loading it\n * fails with `unusableReason`.\n */\n selectedModelId(): LocalLlmModelId {\n const raw = useSettingsStore.getState().getSetting(SETTINGS.local_llm_model.key)\n const picked = isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL\n if (this.isUsable(picked)) return picked\n const fallback = this.models()\n .filter((m) => this.isUsable(m.id))\n .sort((a, b) => b.ramBytes - a.ramBytes)[0]\n return fallback?.id ?? picked\n }\n\n /** Picking a model in Settings is also the way to retry one that crashed the app. */\n async select(id: LocalLlmModelId): Promise<void> {\n this.unblock(id)\n await useSettingsStore.getState().setSetting(SETTINGS.local_llm_model.key, id)\n }\n\n fitsDevice(id: LocalLlmModelId): boolean {\n return modelFitsMemory(MODELS[id], deviceMemoryBytes())\n }\n\n /** The OS killed the app while this model loaded; it stays off until picked again. */\n isBlocked(id: LocalLlmModelId): boolean {\n return this.blockedModelIds().includes(id)\n }\n\n isUsable(id: LocalLlmModelId): boolean {\n return this.fitsDevice(id) && !this.isBlocked(id)\n }\n\n /** Why `id` will not load on this phone, or null when it can. */\n unusableReason(id: LocalLlmModelId): string | null {\n const label = MODELS[id].label\n if (!this.fitsDevice(id)) {\n return `${label} needs more memory than this phone has. Pick a smaller model in Settings > Offline Models.`\n }\n if (this.isBlocked(id)) {\n return `${label} closed the app the last time it loaded. Pick it again in Settings > Offline Models to retry, or pick a smaller model.`\n }\n return null\n }\n\n /** Mark the start of a model's first load and generation in this process. */\n beginColdLoad(id: LocalLlmModelId): void {\n this.recoverColdLoad()\n storage.save(COLD_LOAD_KEY, id)\n }\n\n endColdLoad(): void {\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n private blockedModelIds(): LocalLlmModelId[] {\n this.recoverColdLoad()\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const ids = stored.is_ok() ? stored.value : []\n return Array.isArray(ids) ? ids.filter(isLocalLlmModelId) : []\n }\n\n private unblock(id: LocalLlmModelId): void {\n const remaining = this.blockedModelIds().filter((blocked) => blocked !== id)\n storage.save(BLOCKED_MODELS_KEY, remaining)\n }\n\n // A cold-load marker left by an earlier process: that load never finished.\n private recoverColdLoad(): void {\n if (this.coldLoadRecovered) return\n this.coldLoadRecovered = true\n const marker = storage.load<unknown>(COLD_LOAD_KEY)\n if (!marker.is_ok() || !isLocalLlmModelId(marker.value)) return\n const id = marker.value\n console.warn(`LOCAL_LLM: the app was killed while ${id} loaded; keeping it off until it is picked again`)\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const blocked = stored.is_ok() && Array.isArray(stored.value) ? stored.value.filter(isLocalLlmModelId) : []\n if (!blocked.includes(id)) storage.save(BLOCKED_MODELS_KEY, [...blocked, id])\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/llm_models`\n }\n\n paths(id: LocalLlmModelId = this.selectedModelId()): LocalLlmModelPaths {\n const dir = `${this.getModelDirectory()}/${id}`\n return {\n model: `${dir}/model.pte`,\n tokenizer: `${dir}/tokenizer.json`,\n tokenizerConfig: `${dir}/tokenizer_config.json`,\n }\n }\n\n async isModelAvailable(id: LocalLlmModelId = this.selectedModelId()): Promise<boolean> {\n const paths = this.paths(id)\n try {\n for (const path of [paths.tokenizerConfig, paths.tokenizer, paths.model]) {\n if (!(await RNFS.exists(path))) return false\n }\n // A truncated .pte loads as garbage or crashes the runtime: insist on\n // most of the published size. Downloads land in a .part file and are\n // renamed only once whole, so this only catches external tampering.\n const stat = await RNFS.stat(paths.model)\n return Number(stat.size) >= MODELS[id].modelBytes * 0.95\n } catch (error) {\n console.warn(\"LOCAL_LLM: availability check failed:\", error)\n return false\n }\n }\n\n async downloadedModelIds(): Promise<LocalLlmModelId[]> {\n const ids: LocalLlmModelId[] = []\n for (const id of Object.keys(MODELS) as LocalLlmModelId[]) {\n if (await this.isModelAvailable(id)) ids.push(id)\n }\n return ids\n }\n\n /** Subscribe to download status changes. Fires immediately with the current status. */\n subscribe(listener: StatusListener): () => void {\n this.listeners.add(listener)\n listener(this.currentStatus)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getStatus(): LocalLlmDownloadStatus | null {\n return this.currentStatus\n }\n\n /** Fired once a model's files are complete on disk. */\n onModelReady(listener: (modelId: LocalLlmModelId) => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n /**\n * True when the selected model is on disk. Otherwise starts (or joins) its\n * download in the background and returns false — the caller reports\n * \"downloading\" rather than waiting out a multi-GB transfer.\n */\n async ensureSelectedModel(): Promise<boolean> {\n const id = this.selectedModelId()\n if (await this.isModelAvailable(id)) return true\n if (!this.fitsDevice(id)) return false\n void this.downloadModel(id).catch((error) => {\n console.warn(`LOCAL_LLM: background download of ${id} failed:`, error)\n })\n return false\n }\n\n /**\n * Download one model (three files, model last). Concurrent calls for the\n * same model share the transfer; a call for a different model waits for\n * the running one — the RNFS job slot is singular.\n */\n async downloadModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n if (!this.fitsDevice(id)) throw new Error(this.unusableReason(id) ?? `${MODELS[id].label} does not fit this phone.`)\n if (this.inFlight) {\n if (this.inFlight.modelId === id) return this.inFlight.promise\n await this.inFlight.promise.catch(() => undefined)\n }\n const promise = this.downloadInner(id).finally(() => {\n this.inFlight = null\n })\n this.inFlight = {modelId: id, promise}\n return promise\n }\n\n private async downloadInner(id: LocalLlmModelId): Promise<void> {\n const config = MODELS[id]\n const paths = this.paths(id)\n const dir = `${this.getModelDirectory()}/${id}`\n this.cancelled = false\n this.setStatus({modelId: id, stage: \"downloading\", percent: 0})\n\n try {\n await RNFS.mkdir(dir, {NSURLIsExcludedFromBackupKey: true})\n // Tokenizer files first (small), the weights last: a model directory\n // is only \"available\" once model.pte exists, so an interrupted run\n // never leaves a complete-looking directory behind.\n await this.fetchFile(config.urls.tokenizerConfig, paths.tokenizerConfig)\n await this.fetchFile(config.urls.tokenizer, paths.tokenizer)\n await this.fetchFile(config.urls.model, paths.model, (percent) =>\n this.setStatus({modelId: id, stage: \"downloading\", percent}),\n )\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`downloaded model is smaller than the expected ${config.modelBytes} bytes`)\n }\n this.setStatus({modelId: id, stage: \"complete\", percent: 100})\n for (const listener of this.modelReadyListeners) {\n try {\n listener(id)\n } catch (error) {\n console.warn(\"LOCAL_LLM: model-ready listener failed:\", error)\n }\n }\n } catch (error) {\n const message = this.cancelled ? \"download cancelled\" : error instanceof Error ? error.message : String(error)\n this.setStatus({modelId: id, stage: this.cancelled ? \"idle\" : \"failed\", percent: 0, error: message})\n try {\n if (await RNFS.exists(`${paths.model}.part`)) await RNFS.unlink(`${paths.model}.part`)\n } catch (cleanupError) {\n console.warn(\"LOCAL_LLM: partial cleanup failed:\", cleanupError)\n }\n throw error\n } finally {\n this.downloadJobId = undefined\n }\n }\n\n private async fetchFile(url: string, dest: string, onPercent?: (percent: number) => void): Promise<void> {\n if (this.cancelled) throw new Error(\"download cancelled\")\n const partial = `${dest}.part`\n const download = RNFS.downloadFile({\n fromUrl: url,\n toFile: partial,\n // iOS keeps a background URLSession transfer going while the app is\n // suspended; the files here are large enough to need it.\n background: Platform.OS === \"ios\",\n discretionary: false,\n progressDivider: 1,\n connectionTimeout: 30_000,\n readTimeout: 60_000,\n progress: (res) => {\n if (!onPercent || res.contentLength <= 0) return\n onPercent(Math.min(99, Math.floor((res.bytesWritten / res.contentLength) * 100)))\n },\n })\n this.downloadJobId = download.jobId\n const result = await download.promise\n if (result.statusCode !== 200) {\n throw new Error(`download failed with status ${result.statusCode}`)\n }\n if (await RNFS.exists(dest)) await RNFS.unlink(dest)\n await RNFS.moveFile(partial, dest)\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId === undefined) return\n this.cancelled = true\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n\n async deleteModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n const dir = `${this.getModelDirectory()}/${id}`\n if (await RNFS.exists(dir)) await RNFS.unlink(dir)\n if (this.currentStatus?.modelId === id) this.setStatus(null)\n }\n\n private setStatus(status: LocalLlmDownloadStatus | null): void {\n this.currentStatus = status\n for (const listener of this.listeners) {\n try {\n listener(status)\n } catch (error) {\n console.warn(\"LOCAL_LLM: status listener threw:\", error)\n }\n }\n }\n\n static formatBytes(bytes: number): string {\n if (bytes >= GB) return `${(bytes / GB).toFixed(1)} GB`\n return `${Math.round(bytes / MB)} MB`\n }\n}\n\nconst instance = LocalLlmModelManager.getInstance()\nexport {LocalLlmModelManager}\nexport default instance\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLlmProvider.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,6BAA6B,CAAA;AAEnE,OAA6B,EAAC,KAAK,eAAe,EAAC,MAAM,wBAAwB,CAAA;AACjF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAA;AA6CzB,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,eAAe,CAAA;CACzB;AA0BD,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkB;IAEzC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,GAAG,CAAmB;IAE9B,OAAO;IAUP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAKtC,mFAAmF;IACnF,QAAQ,IAAI,OAAO;IAInB,aAAa,IAAI,eAAe,GAAG,IAAI;IAIvC,oGAAoG;IACpG,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAqBtE,kGAAkG;IAC5F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAaP,aAAa;YAyEb,YAAY;YAgBZ,IAAI;
|
|
1
|
+
{"version":3,"file":"LocalLlmProvider.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,6BAA6B,CAAA;AAEnE,OAA6B,EAAC,KAAK,eAAe,EAAC,MAAM,wBAAwB,CAAA;AACjF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAA;AA6CzB,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,eAAe,CAAA;CACzB;AA0BD,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkB;IAEzC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,GAAG,CAAmB;IAE9B,OAAO;IAUP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAKtC,mFAAmF;IACnF,QAAQ,IAAI,OAAO;IAInB,aAAa,IAAI,eAAe,GAAG,IAAI;IAIvC,oGAAoG;IACpG,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAqBtE,kGAAkG;IAC5F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAaP,aAAa;YAyEb,YAAY;YAgBZ,IAAI;YA0CJ,OAAO;IASrB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,cAAc;CAMvB;AAED,QAAA,MAAM,gBAAgB,kBAAiC,CAAA;AACvD,OAAO,EAAC,gBAAgB,EAAC,CAAA;AACzB,eAAe,gBAAgB,CAAA"}
|
|
@@ -165,12 +165,12 @@ class LocalLlmProvider {
|
|
|
165
165
|
BgTimer.clearTimeout(deadline);
|
|
166
166
|
}
|
|
167
167
|
const elapsedMs = Date.now() - startedAt;
|
|
168
|
-
console.log(`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.
|
|
168
|
+
console.log(`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.prefill.numTokens} out=${generated.stats.numTokens} tokens in ${elapsedMs} ms`);
|
|
169
169
|
const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
170
170
|
const completion = finalizeLocalResult({
|
|
171
171
|
text: generated.text,
|
|
172
|
-
promptTokens: generated.stats.
|
|
173
|
-
generatedTokens: generated.stats.
|
|
172
|
+
promptTokens: generated.stats.prefill.numTokens,
|
|
173
|
+
generatedTokens: generated.stats.numTokens,
|
|
174
174
|
maxNewTokens,
|
|
175
175
|
}, { jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}` });
|
|
176
176
|
return { ...completion, modelId: model.modelId };
|
|
@@ -212,11 +212,10 @@ class LocalLlmProvider {
|
|
|
212
212
|
const tokenizerConfig = rne.llm.parseTokenizerConfig(JSON.parse(await RNFS.readFile(paths.tokenizerConfig, "utf8")));
|
|
213
213
|
const template = new Template(tokenizerConfig.chatTemplate);
|
|
214
214
|
const startedAt = Date.now();
|
|
215
|
-
const
|
|
216
|
-
const runner = await createRunner(paths.model, paths.tokenizer, []);
|
|
215
|
+
const runner = await rne.llm.createLLMRunner(paths.model, paths.tokenizer, []);
|
|
217
216
|
let tokenizer;
|
|
218
217
|
try {
|
|
219
|
-
const maxSeqLen = runner.getKVCacheState().
|
|
218
|
+
const maxSeqLen = runner.getKVCacheState().maxContextLen;
|
|
220
219
|
if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {
|
|
221
220
|
throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`);
|
|
222
221
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLlmProvider.js","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmProvider — session.ai.complete served by the phone itself: an\n * ExecuTorch runner (react-native-executorch) over the Qwen3 export that\n * LocalLlmModelManager downloaded. Stateless per request like the vendor\n * providers: every call renders the whole conversation through the model's\n * chat template, resets the KV cache, and generates on ExecuTorch's worklet\n * thread so the JS thread stays free.\n *\n * The runner is loaded on the first request and kept resident while calls\n * keep coming (loading a 1–3 GB model is seconds, not milliseconds); it is\n * dropped after an idle period and whenever the selected model changes.\n * Requests are serialized — one model, one KV cache.\n *\n * react-native-executorch is imported lazily so the engine module graph\n * loads without the native runtime (bun tests, hosts without the dependency).\n */\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport {Template} from \"@huggingface/jinja\"\n\nimport type {LlmCompleteRequest} from \"@glassly/cloud-protocol/llm\"\nimport {BgTimer} from \"../utils/timers\"\nimport localLlmModelManager, {type LocalLlmModelId} from \"./LocalLlmModelManager\"\nimport {\n buildTemplateMessages,\n finalizeLocalResult,\n fitPromptToContext,\n toTemplateTools,\n type LocalCompletion,\n} from \"./LocalLlmPrompt\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\ntype Rne = typeof import(\"react-native-executorch\")\n// The runner API lives on the `llm` namespace; derive its types from there so\n// the import stays a type-only reference (no native module at load time).\ntype Tokenizer = ReturnType<Rne[\"nlp\"][\"loadTokenizer\"]>\ntype LLMRunner = ReturnType<Rne[\"llm\"][\"createLLMRunner\"]>\ntype LLMGenerationStats = ReturnType<LLMRunner[\"generate\"]>\ntype LLMGenerationConfig = NonNullable<Parameters<LLMRunner[\"generate\"]>[1]>\n\nconst LOG_TAG = \"LOCAL_LLM\"\n\n/** Drop the resident model after this long without a request. */\nconst IDLE_UNLOAD_MS = 5 * 60_000\n/** Hard stop on one generation — the SDK request times out at 120 s anyway. */\nconst GENERATION_DEADLINE_MS = 100_000\n// One generating, one waiting. Assist's proactive insights re-request every\n// time the SDK's 60 s timeout fires while a slow on-device run is still going,\n// and an unbounded queue kept the phone generating for callers that had\n// already given up (one reporter saw 472 s runs and 15 s touch latency).\nconst MAX_PENDING_REQUESTS = 2\n// Under the SDK's 60 s request timeout: a request that waited longer has no caller.\nconst QUEUE_WAIT_LIMIT_MS = 55_000\nconst DEFAULT_MAX_NEW_TOKENS = 512\nconst MAX_NEW_TOKENS_CEILING = 1_024\n/** Reserve space for runner-added BOS/EOS tokens and context boundary bookkeeping. */\nconst CONTEXT_MARGIN_TOKENS = 96\n\ninterface LoadedModel {\n modelId: LocalLlmModelId\n runner: LLMRunner\n tokenizer: Tokenizer\n countTokens: (tokenizer: Tokenizer, prompt: string) => Promise<number>\n template: Template\n stopTokens: string[]\n maxSeqLen: number\n generate: (\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n ) => Promise<{text: string; stats: LLMGenerationStats}>\n}\n\nexport interface LocalLlmCompleteResult extends LocalCompletion {\n modelId: LocalLlmModelId\n}\n\n/**\n * Runs on ExecuTorch's worklet runtime: `runner.generate` blocks its thread\n * until EOS, `maxNewTokens`, or `runner.stop()` from the JS thread.\n */\nfunction generateWorklet(\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n): {text: string; stats: LLMGenerationStats} {\n \"worklet\"\n let text = \"\"\n const stats = runner.generate(prompt, config, (token: string) => {\n if (stopTokens.includes(token)) return\n text += token\n })\n return {text, stats}\n}\n\nfunction countTokensWorklet(tokenizer: Tokenizer, prompt: string): number {\n \"worklet\"\n return tokenizer.encode(prompt).length\n}\n\nclass LocalLlmProvider {\n private static instance: LocalLlmProvider\n\n private loaded: LoadedModel | null = null\n private loading: Promise<LoadedModel> | null = null\n private queue: Promise<unknown> = Promise.resolve()\n private pendingCount = 0\n private idleTimer: number | null = null\n private rne: Rne | null = null\n\n private constructor() {\n // A different model picked in Settings invalidates the resident runner.\n useSettingsStore.subscribe(\n (state) => state.settings[SETTINGS.local_llm_model.key],\n () => {\n void this.unload()\n },\n )\n }\n\n static getInstance(): LocalLlmProvider {\n if (!LocalLlmProvider.instance) LocalLlmProvider.instance = new LocalLlmProvider()\n return LocalLlmProvider.instance\n }\n\n /** Whether a runner is resident right now (diagnostics; not a readiness check). */\n isLoaded(): boolean {\n return this.loaded !== null\n }\n\n loadedModelId(): LocalLlmModelId | null {\n return this.loaded?.modelId ?? null\n }\n\n /** Serve one neutral completion on-device. Throws when the model is missing or generation fails. */\n complete(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n if (this.pendingCount >= MAX_PENDING_REQUESTS) {\n return Promise.reject(\n new Error(`The on-device model is busy (${this.pendingCount} requests pending); try again shortly.`),\n )\n }\n this.pendingCount += 1\n const queuedAt = Date.now()\n const run = this.queue.then(() => {\n const waitedMs = Date.now() - queuedAt\n if (waitedMs > QUEUE_WAIT_LIMIT_MS) {\n throw new Error(`The on-device model was busy for ${Math.round(waitedMs / 1000)} s; request dropped.`)\n }\n return this.completeInner(request)\n })\n this.queue = run.catch(() => undefined)\n return run.finally(() => {\n this.pendingCount -= 1\n })\n }\n\n /** Release the resident runner (idle, model switch, memory pressure). Safe to call repeatedly. */\n async unload(): Promise<void> {\n this.clearIdleTimer()\n // Never dispose under a running generation: let the queue drain first.\n await this.queue\n const pending = this.loading\n if (pending) {\n try {\n await pending\n } catch {\n // A failed load left nothing to release.\n }\n }\n this.releaseRunner()\n }\n\n /** Synchronous dispose; callers guarantee no generation is in flight. */\n private releaseRunner(): void {\n const loaded = this.loaded\n this.loaded = null\n if (!loaded) return\n try {\n loaded.runner.dispose()\n } catch (error) {\n console.warn(`${LOG_TAG}: runner dispose failed:`, error)\n } finally {\n loaded.tokenizer.dispose()\n }\n }\n\n private async completeInner(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n this.clearIdleTimer()\n // Loading and the first generation are where memory peaks; if the OS kills\n // the app in between, the marker keeps the next launch from looping on it.\n const selectedId = localLlmModelManager.selectedModelId()\n const coldModelId = this.loaded?.modelId === selectedId ? null : selectedId\n if (coldModelId) localLlmModelManager.beginColdLoad(coldModelId)\n try {\n const model = await this.ensureLoaded()\n const jsonMode = request.jsonMode === true\n const tools = toTemplateTools(request.tools)\n\n const maxNewTokens = Math.max(\n 16,\n Math.min(request.maxTokens ?? DEFAULT_MAX_NEW_TOKENS, MAX_NEW_TOKENS_CEILING, Math.floor(model.maxSeqLen / 4)),\n )\n const promptBudget = model.maxSeqLen - maxNewTokens - CONTEXT_MARGIN_TOKENS\n const {prompt, promptTokens, truncated} = await fitPromptToContext(\n buildTemplateMessages({...request, jsonMode}),\n promptBudget,\n (messages) => model.template.render({messages, tools, add_generation_prompt: true, enable_thinking: false}),\n (text) => model.countTokens(model.tokenizer, text),\n )\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${promptTokens} budget=${promptBudget} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens} truncated=${truncated}`,\n )\n\n model.runner.reset(0)\n const deadline = BgTimer.setTimeout(() => {\n console.warn(`${LOG_TAG}: generation deadline hit, stopping`)\n model.runner.stop()\n }, GENERATION_DEADLINE_MS)\n let generated: {text: string; stats: LLMGenerationStats}\n const startedAt = Date.now()\n try {\n generated = await model.generate(\n model.runner,\n prompt,\n {maxNewTokens, temperature: request.temperature ?? 0.7},\n model.stopTokens,\n )\n } catch (error) {\n console.warn(\n `${LOG_TAG}: generation failed: model=${model.modelId} prompt=${promptTokens} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens}`,\n error,\n )\n throw error\n } finally {\n BgTimer.clearTimeout(deadline)\n }\n\n const elapsedMs = Date.now() - startedAt\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.numPromptTokens} out=${generated.stats.numGeneratedTokens} tokens in ${elapsedMs} ms`,\n )\n\n const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`\n const completion = finalizeLocalResult(\n {\n text: generated.text,\n promptTokens: generated.stats.numPromptTokens,\n generatedTokens: generated.stats.numGeneratedTokens,\n maxNewTokens,\n },\n {jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},\n )\n return {...completion, modelId: model.modelId}\n } finally {\n if (coldModelId) localLlmModelManager.endColdLoad()\n this.armIdleTimer()\n }\n }\n\n private async ensureLoaded(): Promise<LoadedModel> {\n const modelId = localLlmModelManager.selectedModelId()\n if (this.loaded?.modelId === modelId) return this.loaded\n if (this.loading) return this.loading\n\n this.loading = this.load(modelId)\n .then((model) => {\n this.loaded = model\n return model\n })\n .finally(() => {\n this.loading = null\n })\n return this.loading\n }\n\n private async load(modelId: LocalLlmModelId): Promise<LoadedModel> {\n // Runs inside the request queue, so nothing is generating; unload() would\n // wait on that same queue.\n this.releaseRunner()\n const unusable = localLlmModelManager.unusableReason(modelId)\n if (unusable) throw new Error(unusable)\n if (!(await localLlmModelManager.isModelAvailable(modelId))) {\n throw new Error(`on-device model ${modelId} is not downloaded`)\n }\n const rne = await this.runtime()\n const paths = localLlmModelManager.paths(modelId)\n\n const tokenizerConfig = rne.llm.parseTokenizerConfig(JSON.parse(await RNFS.readFile(paths.tokenizerConfig, \"utf8\")))\n const template = new Template(tokenizerConfig.chatTemplate)\n\n const startedAt = Date.now()\n const createRunner = rne.wrapAsync(rne.llm.createLLMRunner)\n const runner = await createRunner(paths.model, paths.tokenizer, [])\n let tokenizer: Tokenizer | undefined\n try {\n const maxSeqLen = runner.getKVCacheState().maxSeqLen\n if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {\n throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)\n }\n tokenizer = await rne.wrapAsync(rne.nlp.loadTokenizer)(paths.tokenizer)\n console.log(`${LOG_TAG}: loaded ${modelId} (context ${maxSeqLen}) in ${Date.now() - startedAt} ms`)\n return {\n modelId,\n runner,\n tokenizer,\n countTokens: rne.wrapAsync(countTokensWorklet),\n template,\n stopTokens: [...tokenizerConfig.stopTokens],\n maxSeqLen,\n generate: rne.wrapAsync(generateWorklet),\n }\n } catch (error) {\n tokenizer?.dispose()\n runner.dispose()\n throw error\n }\n }\n\n private async runtime(): Promise<Rne> {\n if (this.rne) return this.rne\n const rne: Rne = await import(\"react-native-executorch\")\n // Software Mansion's anonymous download analytics; the host has its own telemetry.\n rne.setTelemetryEnabled(false)\n this.rne = rne\n return rne\n }\n\n private armIdleTimer(): void {\n this.clearIdleTimer()\n this.idleTimer = BgTimer.setTimeout(() => {\n this.idleTimer = null\n console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? \"nothing\"}`)\n void this.unload()\n }, IDLE_UNLOAD_MS)\n }\n\n private clearIdleTimer(): void {\n if (this.idleTimer) {\n BgTimer.clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n}\n\nconst localLlmProvider = LocalLlmProvider.getInstance()\nexport {LocalLlmProvider}\nexport default localLlmProvider\n"]}
|
|
1
|
+
{"version":3,"file":"LocalLlmProvider.js","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmProvider — session.ai.complete served by the phone itself: an\n * ExecuTorch runner (react-native-executorch) over the Qwen3 export that\n * LocalLlmModelManager downloaded. Stateless per request like the vendor\n * providers: every call renders the whole conversation through the model's\n * chat template, resets the KV cache, and generates on ExecuTorch's worklet\n * thread so the JS thread stays free.\n *\n * The runner is loaded on the first request and kept resident while calls\n * keep coming (loading a 1–3 GB model is seconds, not milliseconds); it is\n * dropped after an idle period and whenever the selected model changes.\n * Requests are serialized — one model, one KV cache.\n *\n * react-native-executorch is imported lazily so the engine module graph\n * loads without the native runtime (bun tests, hosts without the dependency).\n */\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport {Template} from \"@huggingface/jinja\"\n\nimport type {LlmCompleteRequest} from \"@glassly/cloud-protocol/llm\"\nimport {BgTimer} from \"../utils/timers\"\nimport localLlmModelManager, {type LocalLlmModelId} from \"./LocalLlmModelManager\"\nimport {\n buildTemplateMessages,\n finalizeLocalResult,\n fitPromptToContext,\n toTemplateTools,\n type LocalCompletion,\n} from \"./LocalLlmPrompt\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\ntype Rne = typeof import(\"react-native-executorch\")\n// The runner API lives on the `llm` namespace; derive its types from there so\n// the import stays a type-only reference (no native module at load time).\ntype Tokenizer = ReturnType<Rne[\"nlp\"][\"loadTokenizer\"]>\ntype LLMRunner = Awaited<ReturnType<Rne[\"llm\"][\"createLLMRunner\"]>>\ntype LLMGenerationStats = ReturnType<LLMRunner[\"generate\"]>\ntype LLMGenerationConfig = NonNullable<Parameters<LLMRunner[\"generate\"]>[1]>\n\nconst LOG_TAG = \"LOCAL_LLM\"\n\n/** Drop the resident model after this long without a request. */\nconst IDLE_UNLOAD_MS = 5 * 60_000\n/** Hard stop on one generation — the SDK request times out at 120 s anyway. */\nconst GENERATION_DEADLINE_MS = 100_000\n// One generating, one waiting. Assist's proactive insights re-request every\n// time the SDK's 60 s timeout fires while a slow on-device run is still going,\n// and an unbounded queue kept the phone generating for callers that had\n// already given up (one reporter saw 472 s runs and 15 s touch latency).\nconst MAX_PENDING_REQUESTS = 2\n// Under the SDK's 60 s request timeout: a request that waited longer has no caller.\nconst QUEUE_WAIT_LIMIT_MS = 55_000\nconst DEFAULT_MAX_NEW_TOKENS = 512\nconst MAX_NEW_TOKENS_CEILING = 1_024\n/** Reserve space for runner-added BOS/EOS tokens and context boundary bookkeeping. */\nconst CONTEXT_MARGIN_TOKENS = 96\n\ninterface LoadedModel {\n modelId: LocalLlmModelId\n runner: LLMRunner\n tokenizer: Tokenizer\n countTokens: (tokenizer: Tokenizer, prompt: string) => Promise<number>\n template: Template\n stopTokens: string[]\n maxSeqLen: number\n generate: (\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n ) => Promise<{text: string; stats: LLMGenerationStats}>\n}\n\nexport interface LocalLlmCompleteResult extends LocalCompletion {\n modelId: LocalLlmModelId\n}\n\n/**\n * Runs on ExecuTorch's worklet runtime: `runner.generate` blocks its thread\n * until EOS, `maxNewTokens`, or `runner.stop()` from the JS thread.\n */\nfunction generateWorklet(\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n): {text: string; stats: LLMGenerationStats} {\n \"worklet\"\n let text = \"\"\n const stats = runner.generate(prompt, config, (token: string) => {\n if (stopTokens.includes(token)) return\n text += token\n })\n return {text, stats}\n}\n\nfunction countTokensWorklet(tokenizer: Tokenizer, prompt: string): number {\n \"worklet\"\n return tokenizer.encode(prompt).length\n}\n\nclass LocalLlmProvider {\n private static instance: LocalLlmProvider\n\n private loaded: LoadedModel | null = null\n private loading: Promise<LoadedModel> | null = null\n private queue: Promise<unknown> = Promise.resolve()\n private pendingCount = 0\n private idleTimer: number | null = null\n private rne: Rne | null = null\n\n private constructor() {\n // A different model picked in Settings invalidates the resident runner.\n useSettingsStore.subscribe(\n (state) => state.settings[SETTINGS.local_llm_model.key],\n () => {\n void this.unload()\n },\n )\n }\n\n static getInstance(): LocalLlmProvider {\n if (!LocalLlmProvider.instance) LocalLlmProvider.instance = new LocalLlmProvider()\n return LocalLlmProvider.instance\n }\n\n /** Whether a runner is resident right now (diagnostics; not a readiness check). */\n isLoaded(): boolean {\n return this.loaded !== null\n }\n\n loadedModelId(): LocalLlmModelId | null {\n return this.loaded?.modelId ?? null\n }\n\n /** Serve one neutral completion on-device. Throws when the model is missing or generation fails. */\n complete(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n if (this.pendingCount >= MAX_PENDING_REQUESTS) {\n return Promise.reject(\n new Error(`The on-device model is busy (${this.pendingCount} requests pending); try again shortly.`),\n )\n }\n this.pendingCount += 1\n const queuedAt = Date.now()\n const run = this.queue.then(() => {\n const waitedMs = Date.now() - queuedAt\n if (waitedMs > QUEUE_WAIT_LIMIT_MS) {\n throw new Error(`The on-device model was busy for ${Math.round(waitedMs / 1000)} s; request dropped.`)\n }\n return this.completeInner(request)\n })\n this.queue = run.catch(() => undefined)\n return run.finally(() => {\n this.pendingCount -= 1\n })\n }\n\n /** Release the resident runner (idle, model switch, memory pressure). Safe to call repeatedly. */\n async unload(): Promise<void> {\n this.clearIdleTimer()\n // Never dispose under a running generation: let the queue drain first.\n await this.queue\n const pending = this.loading\n if (pending) {\n try {\n await pending\n } catch {\n // A failed load left nothing to release.\n }\n }\n this.releaseRunner()\n }\n\n /** Synchronous dispose; callers guarantee no generation is in flight. */\n private releaseRunner(): void {\n const loaded = this.loaded\n this.loaded = null\n if (!loaded) return\n try {\n loaded.runner.dispose()\n } catch (error) {\n console.warn(`${LOG_TAG}: runner dispose failed:`, error)\n } finally {\n loaded.tokenizer.dispose()\n }\n }\n\n private async completeInner(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n this.clearIdleTimer()\n // Loading and the first generation are where memory peaks; if the OS kills\n // the app in between, the marker keeps the next launch from looping on it.\n const selectedId = localLlmModelManager.selectedModelId()\n const coldModelId = this.loaded?.modelId === selectedId ? null : selectedId\n if (coldModelId) localLlmModelManager.beginColdLoad(coldModelId)\n try {\n const model = await this.ensureLoaded()\n const jsonMode = request.jsonMode === true\n const tools = toTemplateTools(request.tools)\n\n const maxNewTokens = Math.max(\n 16,\n Math.min(request.maxTokens ?? DEFAULT_MAX_NEW_TOKENS, MAX_NEW_TOKENS_CEILING, Math.floor(model.maxSeqLen / 4)),\n )\n const promptBudget = model.maxSeqLen - maxNewTokens - CONTEXT_MARGIN_TOKENS\n const {prompt, promptTokens, truncated} = await fitPromptToContext(\n buildTemplateMessages({...request, jsonMode}),\n promptBudget,\n (messages) => model.template.render({messages, tools, add_generation_prompt: true, enable_thinking: false}),\n (text) => model.countTokens(model.tokenizer, text),\n )\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${promptTokens} budget=${promptBudget} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens} truncated=${truncated}`,\n )\n\n model.runner.reset(0)\n const deadline = BgTimer.setTimeout(() => {\n console.warn(`${LOG_TAG}: generation deadline hit, stopping`)\n model.runner.stop()\n }, GENERATION_DEADLINE_MS)\n let generated: {text: string; stats: LLMGenerationStats}\n const startedAt = Date.now()\n try {\n generated = await model.generate(\n model.runner,\n prompt,\n {maxNewTokens, temperature: request.temperature ?? 0.7},\n model.stopTokens,\n )\n } catch (error) {\n console.warn(\n `${LOG_TAG}: generation failed: model=${model.modelId} prompt=${promptTokens} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens}`,\n error,\n )\n throw error\n } finally {\n BgTimer.clearTimeout(deadline)\n }\n\n const elapsedMs = Date.now() - startedAt\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.prefill.numTokens} out=${generated.stats.numTokens} tokens in ${elapsedMs} ms`,\n )\n\n const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`\n const completion = finalizeLocalResult(\n {\n text: generated.text,\n promptTokens: generated.stats.prefill.numTokens,\n generatedTokens: generated.stats.numTokens,\n maxNewTokens,\n },\n {jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},\n )\n return {...completion, modelId: model.modelId}\n } finally {\n if (coldModelId) localLlmModelManager.endColdLoad()\n this.armIdleTimer()\n }\n }\n\n private async ensureLoaded(): Promise<LoadedModel> {\n const modelId = localLlmModelManager.selectedModelId()\n if (this.loaded?.modelId === modelId) return this.loaded\n if (this.loading) return this.loading\n\n this.loading = this.load(modelId)\n .then((model) => {\n this.loaded = model\n return model\n })\n .finally(() => {\n this.loading = null\n })\n return this.loading\n }\n\n private async load(modelId: LocalLlmModelId): Promise<LoadedModel> {\n // Runs inside the request queue, so nothing is generating; unload() would\n // wait on that same queue.\n this.releaseRunner()\n const unusable = localLlmModelManager.unusableReason(modelId)\n if (unusable) throw new Error(unusable)\n if (!(await localLlmModelManager.isModelAvailable(modelId))) {\n throw new Error(`on-device model ${modelId} is not downloaded`)\n }\n const rne = await this.runtime()\n const paths = localLlmModelManager.paths(modelId)\n\n const tokenizerConfig = rne.llm.parseTokenizerConfig(JSON.parse(await RNFS.readFile(paths.tokenizerConfig, \"utf8\")))\n const template = new Template(tokenizerConfig.chatTemplate)\n\n const startedAt = Date.now()\n const runner = await rne.llm.createLLMRunner(paths.model, paths.tokenizer, [])\n let tokenizer: Tokenizer | undefined\n try {\n const maxSeqLen = runner.getKVCacheState().maxContextLen\n if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {\n throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)\n }\n tokenizer = await rne.wrapAsync(rne.nlp.loadTokenizer)(paths.tokenizer)\n console.log(`${LOG_TAG}: loaded ${modelId} (context ${maxSeqLen}) in ${Date.now() - startedAt} ms`)\n return {\n modelId,\n runner,\n tokenizer,\n countTokens: rne.wrapAsync(countTokensWorklet),\n template,\n stopTokens: [...tokenizerConfig.stopTokens],\n maxSeqLen,\n generate: rne.wrapAsync(generateWorklet),\n }\n } catch (error) {\n tokenizer?.dispose()\n runner.dispose()\n throw error\n }\n }\n\n private async runtime(): Promise<Rne> {\n if (this.rne) return this.rne\n const rne: Rne = await import(\"react-native-executorch\")\n // Software Mansion's anonymous download analytics; the host has its own telemetry.\n rne.setTelemetryEnabled(false)\n this.rne = rne\n return rne\n }\n\n private armIdleTimer(): void {\n this.clearIdleTimer()\n this.idleTimer = BgTimer.setTimeout(() => {\n this.idleTimer = null\n console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? \"nothing\"}`)\n void this.unload()\n }, IDLE_UNLOAD_MS)\n }\n\n private clearIdleTimer(): void {\n if (this.idleTimer) {\n BgTimer.clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n}\n\nconst localLlmProvider = LocalLlmProvider.getInstance()\nexport {LocalLlmProvider}\nexport default localLlmProvider\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.1066",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,26 +97,26 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.4.0-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.4.0-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.4.0-dev.
|
|
103
|
-
"@glassly/crust": "0.4.0-dev.
|
|
104
|
-
"@glassly/miniapp": "0.4.0-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.4.0-dev.1066",
|
|
101
|
+
"@glassly/cloud-client": "0.4.0-dev.1066",
|
|
102
|
+
"@glassly/cloud-protocol": "0.4.0-dev.1066",
|
|
103
|
+
"@glassly/crust": "0.4.0-dev.1066",
|
|
104
|
+
"@glassly/miniapp": "0.4.0-dev.1066",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
|
-
"react-native-marked": "8.3.
|
|
108
|
-
"semver": "^7.
|
|
107
|
+
"react-native-marked": "8.3.4",
|
|
108
|
+
"semver": "^7.8.6",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.4.0-dev.
|
|
111
|
-
"@huggingface/jinja": "^0.5.
|
|
112
|
-
"jsonrepair": "^3.
|
|
110
|
+
"@glassly/llm-providers": "0.4.0-dev.1066",
|
|
111
|
+
"@huggingface/jinja": "^0.5.12",
|
|
112
|
+
"jsonrepair": "^3.15.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
115
|
"@glassly/llm-providers": "workspace:*",
|
|
116
|
-
"@types/semver": "^7.
|
|
116
|
+
"@types/semver": "^7.8.0",
|
|
117
117
|
"expo-module-scripts": "^56.0.3",
|
|
118
|
-
"expo-modules-core": "^58.0.
|
|
119
|
-
"react-native-nitro-modules": "^0.37.
|
|
118
|
+
"expo-modules-core": "^58.0.30",
|
|
119
|
+
"react-native-nitro-modules": "^0.37.2",
|
|
120
120
|
"typescript": "~6.0.3"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"@dr.pogodin/react-native-fs": "*",
|
|
156
156
|
"@craftzdog/react-native-buffer": "*",
|
|
157
157
|
"axios": "*",
|
|
158
|
-
"react-native-executorch": ">=0.
|
|
158
|
+
"react-native-executorch": ">=0.11.0 <0.12",
|
|
159
159
|
"react-native-blob-util": "*"
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.4.0",
|
|
15
|
-
"releaseIdentity": "0.4.0-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.4.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.4.0-dev.1066",
|
|
16
|
+
"releaseSetId": "glassly-0.4.0-dev.1066",
|
|
17
|
+
"sourceCommit": "1df55b8e89eeec2cb01fa011748fed92c1f5e920",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -56,9 +56,10 @@ export interface LocalLlmDownloadStatus {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
|
|
59
|
-
// pinned to the v0.10.0 release tag
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// pinned to the v0.10.0 release tag. They export every method the 0.11 text
|
|
60
|
+
// runner validates; moving to the v0.11.0 re-exports changes the file sizes and
|
|
61
|
+
// makes every existing download re-fetch. modelBytes are the .pte sizes on the
|
|
62
|
+
// Hub; sizeBytes adds the tokenizer files (~11 MB).
|
|
62
63
|
const QWEN3_BASE = "https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0"
|
|
63
64
|
const TOKENIZER_BYTES = 11_431_080
|
|
64
65
|
const MB = 1024 * 1024
|
|
@@ -33,7 +33,7 @@ type Rne = typeof import("react-native-executorch")
|
|
|
33
33
|
// The runner API lives on the `llm` namespace; derive its types from there so
|
|
34
34
|
// the import stays a type-only reference (no native module at load time).
|
|
35
35
|
type Tokenizer = ReturnType<Rne["nlp"]["loadTokenizer"]>
|
|
36
|
-
type LLMRunner = ReturnType<Rne["llm"]["createLLMRunner"]
|
|
36
|
+
type LLMRunner = Awaited<ReturnType<Rne["llm"]["createLLMRunner"]>>
|
|
37
37
|
type LLMGenerationStats = ReturnType<LLMRunner["generate"]>
|
|
38
38
|
type LLMGenerationConfig = NonNullable<Parameters<LLMRunner["generate"]>[1]>
|
|
39
39
|
|
|
@@ -238,15 +238,15 @@ class LocalLlmProvider {
|
|
|
238
238
|
|
|
239
239
|
const elapsedMs = Date.now() - startedAt
|
|
240
240
|
console.log(
|
|
241
|
-
`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.
|
|
241
|
+
`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.prefill.numTokens} out=${generated.stats.numTokens} tokens in ${elapsedMs} ms`,
|
|
242
242
|
)
|
|
243
243
|
|
|
244
244
|
const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
|
|
245
245
|
const completion = finalizeLocalResult(
|
|
246
246
|
{
|
|
247
247
|
text: generated.text,
|
|
248
|
-
promptTokens: generated.stats.
|
|
249
|
-
generatedTokens: generated.stats.
|
|
248
|
+
promptTokens: generated.stats.prefill.numTokens,
|
|
249
|
+
generatedTokens: generated.stats.numTokens,
|
|
250
250
|
maxNewTokens,
|
|
251
251
|
},
|
|
252
252
|
{jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},
|
|
@@ -290,11 +290,10 @@ class LocalLlmProvider {
|
|
|
290
290
|
const template = new Template(tokenizerConfig.chatTemplate)
|
|
291
291
|
|
|
292
292
|
const startedAt = Date.now()
|
|
293
|
-
const
|
|
294
|
-
const runner = await createRunner(paths.model, paths.tokenizer, [])
|
|
293
|
+
const runner = await rne.llm.createLLMRunner(paths.model, paths.tokenizer, [])
|
|
295
294
|
let tokenizer: Tokenizer | undefined
|
|
296
295
|
try {
|
|
297
|
-
const maxSeqLen = runner.getKVCacheState().
|
|
296
|
+
const maxSeqLen = runner.getKVCacheState().maxContextLen
|
|
298
297
|
if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {
|
|
299
298
|
throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)
|
|
300
299
|
}
|