@glassly/engine 0.3.5-beta.710 → 0.3.5-beta.760
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/engine.d.ts +4 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +5 -1
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -3
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -3
- package/build/facades/ai.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -1
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +5 -0
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +2 -1
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts +6 -4
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +76 -37
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +12 -4
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +21 -7
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalLlmModelManager.d.ts +3 -1
- package/build/services/LocalLlmModelManager.d.ts.map +1 -1
- package/build/services/LocalLlmModelManager.js +13 -5
- package/build/services/LocalLlmModelManager.js.map +1 -1
- package/build/services/LocalLlmPrompt.d.ts +10 -9
- package/build/services/LocalLlmPrompt.d.ts.map +1 -1
- package/build/services/LocalLlmPrompt.js +43 -32
- package/build/services/LocalLlmPrompt.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +39 -20
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +22 -4
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +108 -134
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +6 -3
- package/build/services/MiniappExitGesture.d.ts.map +1 -1
- package/build/services/MiniappExitGesture.js +4 -4
- package/build/services/MiniappExitGesture.js.map +1 -1
- package/build/services/MiniappWidgetMode.d.ts +6 -4
- package/build/services/MiniappWidgetMode.d.ts.map +1 -1
- package/build/services/MiniappWidgetMode.js +7 -6
- package/build/services/MiniappWidgetMode.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +3 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/actionInvoke.d.ts +37 -0
- package/build/services/actionInvoke.d.ts.map +1 -0
- package/build/services/actionInvoke.js +89 -0
- package/build/services/actionInvoke.js.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.js +259 -0
- package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +16 -0
- package/build/services/controllers.js.map +1 -1
- package/build/services/settingsBridge.d.ts +4 -4
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +9 -4
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +13 -11
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts +2 -0
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +17 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +32 -2
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +2 -0
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +2 -1
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/build/utils/display/scene/index.d.ts +1 -1
- package/build/utils/display/scene/index.d.ts.map +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +20 -1
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +1 -1
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +5 -1
- package/src/facades/ai.ts +3 -3
- package/src/facades/glasses.ts +7 -0
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/GestureDotsService.ts +2 -1
- package/src/services/GlassesTouchInput.ts +81 -34
- package/src/services/LocalDisplayManager.ts +22 -8
- package/src/services/LocalLlmModelManager.ts +16 -6
- package/src/services/LocalLlmPrompt.ts +56 -37
- package/src/services/LocalLlmProvider.ts +49 -22
- package/src/services/LocalMiniappRuntime.ts +140 -134
- package/src/services/MiniappExitGesture.ts +8 -5
- package/src/services/MiniappWidgetMode.ts +9 -7
- package/src/services/SceneRenderer.ts +3 -1
- package/src/services/actionInvoke.ts +105 -0
- package/src/services/appleWatch/AppleWatchLink.ts +297 -0
- package/src/services/controllers.ts +17 -0
- package/src/services/settingsBridge.ts +9 -4
- package/src/stores/apps.ts +12 -10
- package/src/stores/bluetoothSettingKeys.ts +20 -0
- package/src/stores/settings.ts +34 -2
- package/src/types/capabilities/rayneo-io.ts +2 -0
- package/src/types/enums.ts +1 -0
- package/src/utils/display/scene/index.ts +1 -0
- package/src/utils/display/scene/process.ts +17 -1
- package/src/utils/display/scene/types.ts +1 -0
|
@@ -24,8 +24,10 @@ export interface LocalLlmModelConfig {
|
|
|
24
24
|
id: LocalLlmModelId
|
|
25
25
|
/** Human label for the Settings dropdown. */
|
|
26
26
|
label: string
|
|
27
|
-
/**
|
|
27
|
+
/** Total download size in bytes (the .pte dominates). */
|
|
28
28
|
sizeBytes: number
|
|
29
|
+
/** Exact size of the published .pte; the availability check compares against it. */
|
|
30
|
+
modelBytes: number
|
|
29
31
|
/** Rough resident memory while loaded; the Settings copy warns above ~half of device RAM. */
|
|
30
32
|
ramBytes: number
|
|
31
33
|
urls: {
|
|
@@ -54,8 +56,10 @@ export interface LocalLlmDownloadStatus {
|
|
|
54
56
|
|
|
55
57
|
// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
|
|
56
58
|
// pinned to the v0.10.0 release tag that react-native-executorch 0.10.x
|
|
57
|
-
// expects.
|
|
59
|
+
// expects. modelBytes are the .pte sizes on the Hub; sizeBytes adds the
|
|
60
|
+
// tokenizer files (~11 MB).
|
|
58
61
|
const QWEN3_BASE = "https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0"
|
|
62
|
+
const TOKENIZER_BYTES = 11_431_080
|
|
59
63
|
const MB = 1024 * 1024
|
|
60
64
|
const GB = 1024 * MB
|
|
61
65
|
|
|
@@ -63,7 +67,8 @@ const MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {
|
|
|
63
67
|
"qwen3-0.6b": {
|
|
64
68
|
id: "qwen3-0.6b",
|
|
65
69
|
label: "Qwen3 0.6B",
|
|
66
|
-
sizeBytes:
|
|
70
|
+
sizeBytes: 505_686_400 + TOKENIZER_BYTES,
|
|
71
|
+
modelBytes: 505_686_400,
|
|
67
72
|
ramBytes: 0.9 * GB,
|
|
68
73
|
urls: {
|
|
69
74
|
model: `${QWEN3_BASE}/0_6b/xnnpack/qwen_3_0_6b_xnnpack_8da4w.pte`,
|
|
@@ -74,7 +79,8 @@ const MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {
|
|
|
74
79
|
"qwen3-1.7b": {
|
|
75
80
|
id: "qwen3-1.7b",
|
|
76
81
|
label: "Qwen3 1.7B",
|
|
77
|
-
sizeBytes:
|
|
82
|
+
sizeBytes: 1_303_738_496 + TOKENIZER_BYTES,
|
|
83
|
+
modelBytes: 1_303_738_496,
|
|
78
84
|
ramBytes: 1.8 * GB,
|
|
79
85
|
urls: {
|
|
80
86
|
model: `${QWEN3_BASE}/1_7b/xnnpack/qwen_3_1_7b_xnnpack_8da4w.pte`,
|
|
@@ -85,7 +91,8 @@ const MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {
|
|
|
85
91
|
"qwen3-4b": {
|
|
86
92
|
id: "qwen3-4b",
|
|
87
93
|
label: "Qwen3 4B",
|
|
88
|
-
sizeBytes:
|
|
94
|
+
sizeBytes: 2_681_646_976 + TOKENIZER_BYTES,
|
|
95
|
+
modelBytes: 2_681_646_976,
|
|
89
96
|
ramBytes: 3.4 * GB,
|
|
90
97
|
urls: {
|
|
91
98
|
model: `${QWEN3_BASE}/4b/xnnpack/qwen_3_4b_xnnpack_8da4w.pte`,
|
|
@@ -161,7 +168,7 @@ class LocalLlmModelManager {
|
|
|
161
168
|
// most of the published size. Downloads land in a .part file and are
|
|
162
169
|
// renamed only once whole, so this only catches external tampering.
|
|
163
170
|
const stat = await RNFS.stat(paths.model)
|
|
164
|
-
return Number(stat.size) >= MODELS[id].
|
|
171
|
+
return Number(stat.size) >= MODELS[id].modelBytes * 0.95
|
|
165
172
|
} catch (error) {
|
|
166
173
|
console.warn("LOCAL_LLM: availability check failed:", error)
|
|
167
174
|
return false
|
|
@@ -243,6 +250,9 @@ class LocalLlmModelManager {
|
|
|
243
250
|
await this.fetchFile(config.urls.model, paths.model, (percent) =>
|
|
244
251
|
this.setStatus({modelId: id, stage: "downloading", percent}),
|
|
245
252
|
)
|
|
253
|
+
if (!(await this.isModelAvailable(id))) {
|
|
254
|
+
throw new Error(`downloaded model is smaller than the expected ${config.modelBytes} bytes`)
|
|
255
|
+
}
|
|
246
256
|
this.setStatus({modelId: id, stage: "complete", percent: 100})
|
|
247
257
|
for (const listener of this.modelReadyListeners) {
|
|
248
258
|
try {
|
|
@@ -85,55 +85,74 @@ export function toTemplateTools(tools?: LlmTool[]): TemplateTool[] | undefined {
|
|
|
85
85
|
}))
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
export function estimateTokens(text: string): number {
|
|
94
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function estimateMessageTokens(message: TemplateMessage): number {
|
|
98
|
-
let total = TOKENS_PER_MESSAGE + estimateTokens(message.content)
|
|
99
|
-
if (message.role === "assistant" && message.tool_calls) {
|
|
100
|
-
for (const call of message.tool_calls) total += estimateTokens(JSON.stringify(call.function)) + 8
|
|
101
|
-
}
|
|
102
|
-
return total
|
|
88
|
+
export interface FittedLocalPrompt {
|
|
89
|
+
prompt: string
|
|
90
|
+
promptTokens: number
|
|
91
|
+
truncated: boolean
|
|
103
92
|
}
|
|
104
93
|
|
|
105
|
-
const MIN_TOOL_CONTENT_CHARS = 1_200
|
|
106
|
-
|
|
107
94
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* When that last turn alone still does not fit, tool outputs are cut to
|
|
113
|
-
* their head.
|
|
95
|
+
* Measure the complete rendered prompt with the model's tokenizer, including
|
|
96
|
+
* template framing, system instructions and tool schemas. Drop old turns first,
|
|
97
|
+
* then shorten tool results / message text in the final turn. Never cut template
|
|
98
|
+
* delimiters, system instructions, or structured tool calls to make them fit.
|
|
114
99
|
*/
|
|
115
|
-
export function
|
|
116
|
-
|
|
100
|
+
export async function fitPromptToContext(
|
|
101
|
+
messages: TemplateMessage[],
|
|
102
|
+
budgetTokens: number,
|
|
103
|
+
render: (messages: TemplateMessage[]) => string,
|
|
104
|
+
countTokens: (prompt: string) => Promise<number>,
|
|
105
|
+
): Promise<FittedLocalPrompt> {
|
|
106
|
+
if (!Number.isInteger(budgetTokens) || budgetTokens < 1) {
|
|
107
|
+
throw new Error("On-device model has no room for a prompt after reserving output tokens")
|
|
108
|
+
}
|
|
117
109
|
const system = messages[0]?.role === "system" ? [messages[0]] : []
|
|
118
110
|
const turns: TemplateMessage[][] = []
|
|
119
111
|
for (const message of messages.slice(system.length)) {
|
|
120
112
|
if (message.role === "user" || turns.length === 0) turns.push([message])
|
|
121
113
|
else turns[turns.length - 1]!.push(message)
|
|
122
114
|
}
|
|
123
|
-
|
|
124
|
-
while (turns.length > 1 && total([...system, ...turns.flat()]) > budgetTokens) {
|
|
125
|
-
turns.shift()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
115
|
let fitted = [...system, ...turns.flat()]
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
)
|
|
116
|
+
let truncated = false
|
|
117
|
+
const marker = "\n…[truncated]…\n"
|
|
118
|
+
while (true) {
|
|
119
|
+
const prompt = render(fitted)
|
|
120
|
+
const promptTokens = await countTokens(prompt)
|
|
121
|
+
if (!Number.isInteger(promptTokens) || promptTokens < 1) {
|
|
122
|
+
throw new Error("On-device tokenizer returned an invalid prompt token count")
|
|
123
|
+
}
|
|
124
|
+
if (promptTokens <= budgetTokens) return {prompt, promptTokens, truncated}
|
|
125
|
+
truncated = true
|
|
126
|
+
if (turns.length > 1) {
|
|
127
|
+
turns.shift()
|
|
128
|
+
fitted = [...system, ...turns.flat()]
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
// Preserve the user's question while tool output can still be shortened.
|
|
132
|
+
const candidates = fitted
|
|
133
|
+
.map((message, index) => ({message, index}))
|
|
134
|
+
.filter(({message}) => message.role !== "system" && Array.from(message.content).length > marker.length * 2)
|
|
135
|
+
.sort(
|
|
136
|
+
(a, b) =>
|
|
137
|
+
Number(b.message.role === "tool") - Number(a.message.role === "tool") ||
|
|
138
|
+
b.message.content.length - a.message.content.length,
|
|
139
|
+
)
|
|
140
|
+
const candidate = candidates[0]
|
|
141
|
+
if (!candidate) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`On-device prompt instructions and tool definitions exceed the context budget (${promptTokens} > ${budgetTokens} tokens)`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
// Keep both ends of long transcripts and questions, without splitting a
|
|
147
|
+
// Unicode code point. Each pass shrinks strictly, then remeasures the whole
|
|
148
|
+
// rendered prompt; character ratios are never used as a safety check.
|
|
149
|
+
const chars = Array.from(candidate.message.content)
|
|
150
|
+
const keep = Math.floor(chars.length / 4)
|
|
151
|
+
fitted[candidate.index] = {
|
|
152
|
+
...candidate.message,
|
|
153
|
+
content: `${chars.slice(0, keep).join("")}${marker}${chars.slice(-keep).join("")}`,
|
|
154
|
+
}
|
|
135
155
|
}
|
|
136
|
-
return fitted
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
/** Remove a leading `<think>…</think>` block (or an unterminated one). */
|
|
@@ -22,9 +22,8 @@ import {BgTimer} from "../utils/timers"
|
|
|
22
22
|
import localLlmModelManager, {type LocalLlmModelId} from "./LocalLlmModelManager"
|
|
23
23
|
import {
|
|
24
24
|
buildTemplateMessages,
|
|
25
|
-
estimateTokens,
|
|
26
25
|
finalizeLocalResult,
|
|
27
|
-
|
|
26
|
+
fitPromptToContext,
|
|
28
27
|
toTemplateTools,
|
|
29
28
|
type LocalCompletion,
|
|
30
29
|
} from "./LocalLlmPrompt"
|
|
@@ -33,6 +32,7 @@ import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
|
33
32
|
type Rne = typeof import("react-native-executorch")
|
|
34
33
|
// The runner API lives on the `llm` namespace; derive its types from there so
|
|
35
34
|
// the import stays a type-only reference (no native module at load time).
|
|
35
|
+
type Tokenizer = ReturnType<Rne["nlp"]["loadTokenizer"]>
|
|
36
36
|
type LLMRunner = ReturnType<Rne["llm"]["createLLMRunner"]>
|
|
37
37
|
type LLMGenerationStats = ReturnType<LLMRunner["generate"]>
|
|
38
38
|
type LLMGenerationConfig = NonNullable<Parameters<LLMRunner["generate"]>[1]>
|
|
@@ -45,12 +45,14 @@ const IDLE_UNLOAD_MS = 5 * 60_000
|
|
|
45
45
|
const GENERATION_DEADLINE_MS = 100_000
|
|
46
46
|
const DEFAULT_MAX_NEW_TOKENS = 512
|
|
47
47
|
const MAX_NEW_TOKENS_CEILING = 1_024
|
|
48
|
-
/**
|
|
48
|
+
/** Reserve space for runner-added BOS/EOS tokens and context boundary bookkeeping. */
|
|
49
49
|
const CONTEXT_MARGIN_TOKENS = 96
|
|
50
50
|
|
|
51
51
|
interface LoadedModel {
|
|
52
52
|
modelId: LocalLlmModelId
|
|
53
53
|
runner: LLMRunner
|
|
54
|
+
tokenizer: Tokenizer
|
|
55
|
+
countTokens: (tokenizer: Tokenizer, prompt: string) => Promise<number>
|
|
54
56
|
template: Template
|
|
55
57
|
stopTokens: string[]
|
|
56
58
|
maxSeqLen: number
|
|
@@ -85,6 +87,11 @@ function generateWorklet(
|
|
|
85
87
|
return {text, stats}
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
function countTokensWorklet(tokenizer: Tokenizer, prompt: string): number {
|
|
91
|
+
"worklet"
|
|
92
|
+
return tokenizer.encode(prompt).length
|
|
93
|
+
}
|
|
94
|
+
|
|
88
95
|
class LocalLlmProvider {
|
|
89
96
|
private static instance: LocalLlmProvider
|
|
90
97
|
|
|
@@ -150,6 +157,8 @@ class LocalLlmProvider {
|
|
|
150
157
|
loaded.runner.dispose()
|
|
151
158
|
} catch (error) {
|
|
152
159
|
console.warn(`${LOG_TAG}: runner dispose failed:`, error)
|
|
160
|
+
} finally {
|
|
161
|
+
loaded.tokenizer.dispose()
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
164
|
|
|
@@ -159,21 +168,21 @@ class LocalLlmProvider {
|
|
|
159
168
|
const model = await this.ensureLoaded()
|
|
160
169
|
const jsonMode = request.jsonMode === true
|
|
161
170
|
const tools = toTemplateTools(request.tools)
|
|
162
|
-
const toolTokens = tools ? estimateTokens(JSON.stringify(tools)) + 64 : 0
|
|
163
171
|
|
|
164
172
|
const maxNewTokens = Math.max(
|
|
165
173
|
16,
|
|
166
174
|
Math.min(request.maxTokens ?? DEFAULT_MAX_NEW_TOKENS, MAX_NEW_TOKENS_CEILING, Math.floor(model.maxSeqLen / 4)),
|
|
167
175
|
)
|
|
168
176
|
const promptBudget = model.maxSeqLen - maxNewTokens - CONTEXT_MARGIN_TOKENS
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
messages,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
const {prompt, promptTokens, truncated} = await fitPromptToContext(
|
|
178
|
+
buildTemplateMessages({...request, jsonMode}),
|
|
179
|
+
promptBudget,
|
|
180
|
+
(messages) => model.template.render({messages, tools, add_generation_prompt: true, enable_thinking: false}),
|
|
181
|
+
(text) => model.countTokens(model.tokenizer, text),
|
|
182
|
+
)
|
|
183
|
+
console.log(
|
|
184
|
+
`${LOG_TAG}: ${model.modelId} prompt=${promptTokens} budget=${promptBudget} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens} truncated=${truncated}`,
|
|
185
|
+
)
|
|
177
186
|
|
|
178
187
|
model.runner.reset(0)
|
|
179
188
|
const deadline = BgTimer.setTimeout(() => {
|
|
@@ -189,6 +198,12 @@ class LocalLlmProvider {
|
|
|
189
198
|
{maxNewTokens, temperature: request.temperature ?? 0.7},
|
|
190
199
|
model.stopTokens,
|
|
191
200
|
)
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.warn(
|
|
203
|
+
`${LOG_TAG}: generation failed: model=${model.modelId} prompt=${promptTokens} context=${model.maxSeqLen} maxNewTokens=${maxNewTokens}`,
|
|
204
|
+
error,
|
|
205
|
+
)
|
|
206
|
+
throw error
|
|
192
207
|
} finally {
|
|
193
208
|
BgTimer.clearTimeout(deadline)
|
|
194
209
|
}
|
|
@@ -246,16 +261,28 @@ class LocalLlmProvider {
|
|
|
246
261
|
const startedAt = Date.now()
|
|
247
262
|
const createRunner = rne.wrapAsync(rne.llm.createLLMRunner)
|
|
248
263
|
const runner = await createRunner(paths.model, paths.tokenizer, [])
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
264
|
+
let tokenizer: Tokenizer | undefined
|
|
265
|
+
try {
|
|
266
|
+
const maxSeqLen = runner.getKVCacheState().maxSeqLen
|
|
267
|
+
if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {
|
|
268
|
+
throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)
|
|
269
|
+
}
|
|
270
|
+
tokenizer = await rne.wrapAsync(rne.nlp.loadTokenizer)(paths.tokenizer)
|
|
271
|
+
console.log(`${LOG_TAG}: loaded ${modelId} (context ${maxSeqLen}) in ${Date.now() - startedAt} ms`)
|
|
272
|
+
return {
|
|
273
|
+
modelId,
|
|
274
|
+
runner,
|
|
275
|
+
tokenizer,
|
|
276
|
+
countTokens: rne.wrapAsync(countTokensWorklet),
|
|
277
|
+
template,
|
|
278
|
+
stopTokens: [...tokenizerConfig.stopTokens],
|
|
279
|
+
maxSeqLen,
|
|
280
|
+
generate: rne.wrapAsync(generateWorklet),
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
tokenizer?.dispose()
|
|
284
|
+
runner.dispose()
|
|
285
|
+
throw error
|
|
259
286
|
}
|
|
260
287
|
}
|
|
261
288
|
|