mihailik commited on
Commit
b46ef31
·
1 Parent(s): f332e70

Wiring some of the models to be used.

Browse files
Files changed (2) hide show
  1. package.json +1 -1
  2. src/worker/boot-worker.js +123 -16
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "localm",
3
- "version": "1.0.7",
4
  "description": "",
5
  "main": "chat-full.js",
6
  "scripts": {
 
1
  {
2
  "name": "localm",
3
+ "version": "1.0.8",
4
  "description": "",
5
  "main": "chat-full.js",
6
  "scripts": {
src/worker/boot-worker.js CHANGED
@@ -1,8 +1,6 @@
1
  // @ts-check
2
 
3
- // Use a normal static import from the official package. This keeps import semantics
4
- // predictable and lets your bundler (esbuild) resolve the package during build.
5
- import * as hf from '@huggingface/transformers';
6
 
7
  export function bootWorker() {
8
  // Report starting
@@ -13,26 +11,123 @@ export function bootWorker() {
13
  }
14
 
15
  (async () => {
16
- // Use the imported @huggingface/transformers module
17
- let transformersLib = hf;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  try {
19
- if (!transformersLib) throw new Error('transformers module not available');
20
  self.postMessage({ type: 'status', status: 'transformers-loaded', source: '@huggingface/transformers' });
21
  } catch (err) {
22
  self.postMessage({ type: 'status', status: 'transformers-load-failed', error: String(err) });
23
  }
24
 
25
- // Minimal in-worker state for later model operations
 
 
 
26
  const availableModels = [
27
  'Xenova/phi-3-mini-4k-instruct',
28
  'Xenova/phi-1.5',
29
  'Xenova/all-MiniLM-L6-v2'
30
  ];
31
- let currentModel = null;
32
 
33
- // signal ready to main thread
34
  self.postMessage({ type: 'ready' });
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  // handle incoming requests from the UI thread
37
  self.addEventListener('message', async (ev) => {
38
  const msg = ev.data || {};
@@ -42,15 +137,27 @@ export function bootWorker() {
42
  self.postMessage({ id, type: 'response', result: availableModels });
43
  } else if (msg.type === 'loadModel') {
44
  const modelName = msg.model;
45
- // TODO: replace with real model loading code using transformersLib
46
- await new Promise((r) => setTimeout(r, 150));
47
- currentModel = modelName;
48
- self.postMessage({ id, type: 'response', result: { model: modelName, status: 'loaded' } });
 
 
49
  } else if (msg.type === 'runPrompt') {
50
  const prompt = msg.prompt || '';
51
- // TODO: run the prompt with transformersLib pipeline once integrated
52
- const text = `Worker(${currentModel || 'no-model'}): ${prompt}`;
53
- self.postMessage({ id, type: 'response', result: text });
 
 
 
 
 
 
 
 
 
 
54
  } else {
55
  if (id) self.postMessage({ id, type: 'error', error: 'unknown-message-type' });
56
  }
 
1
  // @ts-check
2
 
3
+ import { pipeline } from '@huggingface/transformers';
 
 
4
 
5
  export function bootWorker() {
6
  // Report starting
 
11
  }
12
 
13
  (async () => {
14
+ // named import `pipeline` is available from the bundled runtime
15
+
16
+ // Detect available acceleration backends
17
+ let backend = 'wasm';
18
+ try {
19
+ const hasWebGPU = typeof navigator !== 'undefined' && !!navigator.gpu;
20
+ let hasWebGL2 = false;
21
+ try {
22
+ // In a worker environment prefer OffscreenCanvas to test webgl2
23
+ if (typeof OffscreenCanvas !== 'undefined') {
24
+ const c = new OffscreenCanvas(1, 1);
25
+ const gl = c.getContext('webgl2') || c.getContext('webgl');
26
+ hasWebGL2 = !!gl;
27
+ } else if (typeof document !== 'undefined') {
28
+ const canvas = document.createElement('canvas');
29
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
30
+ hasWebGL2 = !!gl;
31
+ }
32
+ } catch (e) {
33
+ hasWebGL2 = false;
34
+ }
35
+
36
+ if (hasWebGPU) backend = 'webgpu';
37
+ else if (hasWebGL2) backend = 'webgl';
38
+ } catch (e) {
39
+ backend = 'wasm';
40
+ }
41
+
42
+ self.postMessage({ type: 'status', status: 'backend-detected', backend });
43
+
44
+ // verify the named import is present
45
  try {
46
+ if (!pipeline) throw new Error('transformers pipeline import not available');
47
  self.postMessage({ type: 'status', status: 'transformers-loaded', source: '@huggingface/transformers' });
48
  } catch (err) {
49
  self.postMessage({ type: 'status', status: 'transformers-load-failed', error: String(err) });
50
  }
51
 
52
+ // Model cache to avoid loading the same model multiple times.
53
+ // value = { promise, pipeline }
54
+ const modelCache = new Map();
55
+
56
  const availableModels = [
57
  'Xenova/phi-3-mini-4k-instruct',
58
  'Xenova/phi-1.5',
59
  'Xenova/all-MiniLM-L6-v2'
60
  ];
 
61
 
62
+ // signal ready to main thread (worker script loaded; model runtime may still be pending)
63
  self.postMessage({ type: 'ready' });
64
 
65
+ // helper: create or return existing pipeline promise
66
+ async function ensureModel(modelName, id) {
67
+ if (modelCache.has(modelName)) {
68
+ const entry = modelCache.get(modelName);
69
+ // If pipeline already resolved, return it, otherwise await the promise
70
+ if (entry.pipeline) return entry.pipeline;
71
+ return entry.promise;
72
+ }
73
+
74
+ // create loader promise
75
+ const loader = (async () => {
76
+ if (!pipeline) {
77
+ throw new Error('transformers runtime not available');
78
+ }
79
+
80
+ // Post progress and status
81
+ if (id) self.postMessage({ id, type: 'status', status: 'model-loading', model: modelName });
82
+
83
+ // Choose device hint as a literal union. Cast only at the call site if TypeScript
84
+ // needs help narrowing.
85
+ const deviceOption = backend === 'webgpu' ? 'webgpu' : (backend === 'webgl' ? 'gpu' : 'wasm');
86
+
87
+ // Create a text-generation pipeline. Depending on the model this may
88
+ // perform downloads of model weights; the library should report progress
89
+ // via its own callbacks if available.
90
+ const pipe = await pipeline('text-generation', modelName, /** @type {any} */ ({
91
+ device: deviceOption,
92
+ progress_callback: (progress) => {
93
+ if (id) self.postMessage({ id, type: 'model-progress', progress, model: modelName });
94
+ }
95
+ }));
96
+
97
+ // store pipeline for reuse
98
+ const entry = modelCache.get(modelName) || {};
99
+ entry.pipeline = pipe;
100
+ modelCache.set(modelName, entry);
101
+
102
+ if (id) self.postMessage({ id, type: 'status', status: 'model-loaded', model: modelName });
103
+ return pipe;
104
+ })();
105
+
106
+ // temporarly store the in-progress promise so concurrent requests reuse it
107
+ modelCache.set(modelName, { promise: loader });
108
+ return loader;
109
+ }
110
+
111
+ // helper to extract generated text from various runtime outputs
112
+ function extractText(output) {
113
+ // typical shapes: [{ generated_text: '...' }] or [{ text: '...' }] or string
114
+ try {
115
+ if (!output) return '';
116
+ if (typeof output === 'string') return output;
117
+ if (Array.isArray(output) && output.length > 0) {
118
+ const el = output[0];
119
+ if (el.generated_text) return el.generated_text;
120
+ if (el.text) return el.text;
121
+ // Some runtimes return an array of strings
122
+ if (typeof el === 'string') return el;
123
+ }
124
+ // Fallback: try JSON stringify
125
+ return String(output);
126
+ } catch (e) {
127
+ return '';
128
+ }
129
+ }
130
+
131
  // handle incoming requests from the UI thread
132
  self.addEventListener('message', async (ev) => {
133
  const msg = ev.data || {};
 
137
  self.postMessage({ id, type: 'response', result: availableModels });
138
  } else if (msg.type === 'loadModel') {
139
  const modelName = msg.model;
140
+ try {
141
+ await ensureModel(modelName, id);
142
+ self.postMessage({ id, type: 'response', result: { model: modelName, status: 'loaded' } });
143
+ } catch (err) {
144
+ self.postMessage({ id, type: 'error', error: String(err) });
145
+ }
146
  } else if (msg.type === 'runPrompt') {
147
  const prompt = msg.prompt || '';
148
+ const modelName = msg.model;
149
+ try {
150
+ const pipe = await ensureModel(modelName, id);
151
+ // run the pipeline
152
+ if (!pipe) throw new Error('pipeline not available');
153
+ self.postMessage({ id, type: 'status', status: 'inference-start', model: modelName });
154
+ const out = await pipe(prompt, msg.options || {});
155
+ const text = extractText(out);
156
+ self.postMessage({ id, type: 'status', status: 'inference-done', model: modelName });
157
+ self.postMessage({ id, type: 'response', result: text });
158
+ } catch (err) {
159
+ self.postMessage({ id, type: 'error', error: String(err) });
160
+ }
161
  } else {
162
  if (id) self.postMessage({ id, type: 'error', error: 'unknown-message-type' });
163
  }