Lighter model.
Browse files- package.json +1 -1
- src/app/worker-connection.js +12 -7
- src/worker/load-model-core.js +7 -6
- src/worker/model-cache.js +49 -12
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "localm",
|
| 3 |
-
"version": "1.1.
|
| 4 |
"description": "",
|
| 5 |
"main": "chat-full.js",
|
| 6 |
"scripts": {
|
|
|
|
| 1 |
{
|
| 2 |
"name": "localm",
|
| 3 |
+
"version": "1.1.2",
|
| 4 |
"description": "",
|
| 5 |
"main": "chat-full.js",
|
| 6 |
"scripts": {
|
src/app/worker-connection.js
CHANGED
|
@@ -33,7 +33,7 @@ export function workerConnection() {
|
|
| 33 |
const msg = ev.data || {};
|
| 34 |
if (msg && msg.type === 'ready') {
|
| 35 |
ready = true;
|
| 36 |
-
resolve({ worker
|
| 37 |
return;
|
| 38 |
}
|
| 39 |
|
|
@@ -41,10 +41,14 @@ export function workerConnection() {
|
|
| 41 |
const id = msg.id;
|
| 42 |
const entry = pending.get(id);
|
| 43 |
if (!entry) return;
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
else
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
});
|
| 50 |
|
|
@@ -80,11 +84,12 @@ export function workerConnection() {
|
|
| 80 |
|
| 81 |
/**
|
| 82 |
* @param {string} promptText
|
| 83 |
-
* @param {string} modelName
|
| 84 |
*/
|
| 85 |
async function runPrompt(promptText, modelName) {
|
| 86 |
await workerLoaded;
|
| 87 |
const { send } = await workerLoaded;
|
| 88 |
-
|
|
|
|
| 89 |
}
|
| 90 |
}
|
|
|
|
| 33 |
const msg = ev.data || {};
|
| 34 |
if (msg && msg.type === 'ready') {
|
| 35 |
ready = true;
|
| 36 |
+
resolve({ worker, pending, send });
|
| 37 |
return;
|
| 38 |
}
|
| 39 |
|
|
|
|
| 41 |
const id = msg.id;
|
| 42 |
const entry = pending.get(id);
|
| 43 |
if (!entry) return;
|
| 44 |
+
if (msg.type === 'response') {
|
| 45 |
+
pending.delete(id);
|
| 46 |
+
entry.resolve(msg.result);
|
| 47 |
+
} else if (msg.type === 'error') {
|
| 48 |
+
pending.delete(id);
|
| 49 |
+
entry.reject(new Error(msg.error));
|
| 50 |
+
}
|
| 51 |
+
//else entry.resolve(msg);
|
| 52 |
}
|
| 53 |
});
|
| 54 |
|
|
|
|
| 84 |
|
| 85 |
/**
|
| 86 |
* @param {string} promptText
|
| 87 |
+
* @param {string} [modelName]
|
| 88 |
*/
|
| 89 |
async function runPrompt(promptText, modelName) {
|
| 90 |
await workerLoaded;
|
| 91 |
const { send } = await workerLoaded;
|
| 92 |
+
const sendPromise = send({ type: 'runPrompt', prompt: promptText, modelName });
|
| 93 |
+
return sendPromise;
|
| 94 |
}
|
| 95 |
}
|
src/worker/load-model-core.js
CHANGED
|
@@ -19,12 +19,13 @@ export async function loadModelCore({
|
|
| 19 |
// via its own callbacks if available.
|
| 20 |
const pipe = await pipeline(
|
| 21 |
'text-generation',
|
| 22 |
-
modelName,
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
return pipe;
|
| 30 |
}
|
|
|
|
| 19 |
// via its own callbacks if available.
|
| 20 |
const pipe = await pipeline(
|
| 21 |
'text-generation',
|
| 22 |
+
modelName,
|
| 23 |
+
{
|
| 24 |
+
device,
|
| 25 |
+
progress_callback: (progress) => {
|
| 26 |
+
if (onProgress) onProgress(progress);
|
| 27 |
+
}
|
| 28 |
+
});
|
| 29 |
|
| 30 |
return pipe;
|
| 31 |
}
|
src/worker/model-cache.js
CHANGED
|
@@ -9,9 +9,14 @@ export class ModelCache {
|
|
| 9 |
backend = undefined;
|
| 10 |
|
| 11 |
knownModels = [
|
| 12 |
-
'Xenova/
|
| 13 |
'Xenova/phi-3-mini-4k-instruct',
|
| 14 |
-
'Xenova/all-MiniLM-L6-v2'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
];
|
| 16 |
|
| 17 |
/**
|
|
@@ -30,20 +35,52 @@ export class ModelCache {
|
|
| 30 |
*/
|
| 31 |
_loadModelAndStore({ modelName }) {
|
| 32 |
if (!this.backend) this.backend = detectTransformersBackend();
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
this.cache.set(modelName, model);
|
| 41 |
},
|
| 42 |
() => {
|
| 43 |
this.cache.delete(modelName);
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
|
|
|
|
| 47 |
}
|
| 48 |
|
| 49 |
}
|
|
|
|
| 9 |
backend = undefined;
|
| 10 |
|
| 11 |
knownModels = [
|
| 12 |
+
'Xenova/llama2.c-stories15M', // nonsense
|
| 13 |
'Xenova/phi-3-mini-4k-instruct',
|
| 14 |
+
'Xenova/all-MiniLM-L6-v2', // unsupported model type: bert
|
| 15 |
+
'Xenova/phi-1.5', // gated
|
| 16 |
+
'Qwen/Qwen2.5-3B', // cannot be loaded
|
| 17 |
+
'microsoft/phi-1_5', // cannot be loaded
|
| 18 |
+
'FlofloB/100k_fineweb_continued_pretraining_Qwen2.5-0.5B-Instruct_Unsloth_merged_16bit', // cannot be loaded
|
| 19 |
+
'ehristoforu/coolqwen-3b-it' // cannot be loaded
|
| 20 |
];
|
| 21 |
|
| 22 |
/**
|
|
|
|
| 35 |
*/
|
| 36 |
_loadModelAndStore({ modelName }) {
|
| 37 |
if (!this.backend) this.backend = detectTransformersBackend();
|
| 38 |
+
// Create a loader promise that will try multiple backends in order.
|
| 39 |
+
const loader = (async () => {
|
| 40 |
+
const tried = [];
|
| 41 |
+
// candidate order: detected backend first, then common fallbacks
|
| 42 |
+
let candidates = ['webgpu', 'gpu', 'wasm'];
|
| 43 |
+
candidates = ['gpu', 'wasm'];
|
| 44 |
+
candidates = candidates.slice(candidates.indexOf(this.backend || 'wasm'));
|
| 45 |
+
|
| 46 |
+
let lastErr = null;
|
| 47 |
+
console.log('Trying candidates ', candidates);
|
| 48 |
+
for (const device of candidates) {
|
| 49 |
+
try {
|
| 50 |
+
const model = await loadModelCore({
|
| 51 |
+
modelName,
|
| 52 |
+
device: /** @type {import('@huggingface/transformers').DeviceType} */ (device)
|
| 53 |
+
});
|
| 54 |
+
// on success, update backend to the working device and store model
|
| 55 |
+
this.backend = /** @type {import('@huggingface/transformers').DeviceType} */ (device);
|
| 56 |
+
this.cache.set(modelName, model);
|
| 57 |
+
return model;
|
| 58 |
+
} catch (err) {
|
| 59 |
+
console.log('Failed ', device, ' ', err);
|
| 60 |
+
tried.push({ device, error: err.stack || String(err) });
|
| 61 |
+
lastErr = err;
|
| 62 |
+
// continue to next candidate
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// none succeeded
|
| 67 |
+
const err = new Error(`no available backend found. attempts=${JSON.stringify(tried)}; last=${String(lastErr)}`);
|
| 68 |
+
throw err;
|
| 69 |
+
})();
|
| 70 |
+
|
| 71 |
+
// store the in-progress promise so concurrent requests reuse it
|
| 72 |
+
this.cache.set(modelName, loader);
|
| 73 |
+
loader.then(
|
| 74 |
+
(model) => {
|
| 75 |
+
// on success, loader already stored the model
|
| 76 |
this.cache.set(modelName, model);
|
| 77 |
},
|
| 78 |
() => {
|
| 79 |
this.cache.delete(modelName);
|
| 80 |
+
}
|
| 81 |
+
);
|
| 82 |
+
|
| 83 |
+
return loader;
|
| 84 |
}
|
| 85 |
|
| 86 |
}
|