@agorapete/wllama 3.5.1-q2.0
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/.gitmodules +3 -0
- package/.prettierignore +38 -0
- package/AGENTS.md +1 -0
- package/CMakeLists.txt +131 -0
- package/LICENCE +21 -0
- package/README-dev.md +178 -0
- package/README.md +225 -0
- package/README_banner.png +0 -0
- package/assets/screenshot_0.png +0 -0
- package/cpp/generate_glue_prototype.js +115 -0
- package/cpp/glue.hpp +664 -0
- package/cpp/test_glue.cpp +80 -0
- package/cpp/wllama-context.h +1172 -0
- package/cpp/wllama-fs.h +148 -0
- package/cpp/wllama.cpp +187 -0
- package/cpp/wllama.h +6 -0
- package/esm/cache-manager.d.ts +130 -0
- package/esm/debug.d.ts +28 -0
- package/esm/glue/glue.d.ts +22 -0
- package/esm/glue/messages.d.ts +146 -0
- package/esm/huggingface.d.ts +31 -0
- package/esm/index.cjs +3406 -0
- package/esm/index.d.ts +8 -0
- package/esm/index.js +3387 -0
- package/esm/index.min.js +1 -0
- package/esm/index.min.js.map +1 -0
- package/esm/model-manager.d.ts +136 -0
- package/esm/storage/cos.d.ts +36 -0
- package/esm/storage/index.d.ts +33 -0
- package/esm/storage/opfs.d.ts +12 -0
- package/esm/types/oai-compat.d.ts +278 -0
- package/esm/types/types.d.ts +112 -0
- package/esm/utils.d.ts +119 -0
- package/esm/wasm/source-map.d.ts +1 -0
- package/esm/wasm/wllama.wasm +0 -0
- package/esm/wasm-from-cdn.d.ts +8 -0
- package/esm/wllama.d.ts +397 -0
- package/esm/worker.d.ts +92 -0
- package/esm/workers-code/generated.d.ts +4 -0
- package/guides/intro-v2.md +132 -0
- package/guides/intro-v3.1.md +40 -0
- package/guides/intro-v3.md +230 -0
- package/index.ts +1 -0
- package/package.json +71 -0
- package/scripts/bisect_test.sh +33 -0
- package/scripts/build_hf_space.sh +26 -0
- package/scripts/build_source_map.js +269 -0
- package/scripts/build_wasm.sh +19 -0
- package/scripts/build_worker.sh +38 -0
- package/scripts/check_debug_build.js +30 -0
- package/scripts/check_package_size.js +25 -0
- package/scripts/docker-compose.yml +76 -0
- package/scripts/generate_wasm_from_cdn.js +24 -0
- package/scripts/http_server.js +44 -0
- package/scripts/post_build.sh +32 -0
- package/src/cache-manager.ts +358 -0
- package/src/debug.ts +111 -0
- package/src/glue/glue.ts +291 -0
- package/src/glue/messages.ts +773 -0
- package/src/huggingface.ts +151 -0
- package/src/index.ts +8 -0
- package/src/mjs.test.ts +44 -0
- package/src/model-manager.test.ts +200 -0
- package/src/model-manager.ts +359 -0
- package/src/storage/cos.test.ts +83 -0
- package/src/storage/cos.ts +171 -0
- package/src/storage/index.ts +40 -0
- package/src/storage/opfs.ts +119 -0
- package/src/types/oai-compat.ts +342 -0
- package/src/types/types.ts +133 -0
- package/src/utils.test.ts +231 -0
- package/src/utils.ts +403 -0
- package/src/wasm/source-map.ts +7 -0
- package/src/wasm/wllama.js +1 -0
- package/src/wasm/wllama.wasm +0 -0
- package/src/wasm-from-cdn.ts +13 -0
- package/src/wllama.test.ts +392 -0
- package/src/wllama.ts +1138 -0
- package/src/wllama.wgpu.test.ts +62 -0
- package/src/worker.ts +443 -0
- package/src/workers-code/generated.ts +11 -0
- package/src/workers-code/llama-cpp.js +511 -0
- package/src/workers-code/opfs-utils.js +150 -0
- package/tsconfig.build.json +34 -0
- package/tsup.config.ts +23 -0
- package/vitest.config.ts +61 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
// Start the main llama.cpp
|
|
2
|
+
let wllamaMalloc;
|
|
3
|
+
let wllamaStart;
|
|
4
|
+
let wllamaAction;
|
|
5
|
+
let wllamaExit;
|
|
6
|
+
let wllamaDebug;
|
|
7
|
+
|
|
8
|
+
let Module = null;
|
|
9
|
+
let isCompat = false;
|
|
10
|
+
let lastStack = '';
|
|
11
|
+
let isAborted = false;
|
|
12
|
+
let hasMultithread = false;
|
|
13
|
+
|
|
14
|
+
//////////////////////////////////////////////////////////////
|
|
15
|
+
// UTILS
|
|
16
|
+
//////////////////////////////////////////////////////////////
|
|
17
|
+
|
|
18
|
+
// send message back to main thread
|
|
19
|
+
const msg = (data, transfer) => postMessage(data, transfer);
|
|
20
|
+
|
|
21
|
+
// Convert CPP log into JS log
|
|
22
|
+
const cppLogToJSLog = (line) => {
|
|
23
|
+
const matched = line.match(/@@(DEBUG|INFO|WARN|ERROR)@@(.*)/);
|
|
24
|
+
return !!matched
|
|
25
|
+
? {
|
|
26
|
+
level: (matched[1] === 'INFO' ? 'debug' : matched[1]).toLowerCase(),
|
|
27
|
+
text: matched[2],
|
|
28
|
+
}
|
|
29
|
+
: { level: 'log', text: line };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getHeapU8 = () => {
|
|
33
|
+
const buffer = Module.wasmMemory.buffer;
|
|
34
|
+
return new Uint8Array(buffer);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const toSizeT = (num) => {
|
|
38
|
+
return isCompat ? Number(num) : BigInt(num);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Get module config that forwards stdout/err to main thread
|
|
42
|
+
const getWModuleConfig = (_argMainScriptBlob) => {
|
|
43
|
+
var pathConfig = RUN_OPTIONS.pathConfig;
|
|
44
|
+
var pthreadPoolSize = RUN_OPTIONS.nbThread;
|
|
45
|
+
var argMainScriptBlob = _argMainScriptBlob;
|
|
46
|
+
|
|
47
|
+
isCompat = RUN_OPTIONS.compat;
|
|
48
|
+
hasMultithread = pthreadPoolSize > 1;
|
|
49
|
+
|
|
50
|
+
msg({
|
|
51
|
+
verb: 'console.debug',
|
|
52
|
+
args: [
|
|
53
|
+
`Multithread enabled: ${hasMultithread}, pthreadPoolSize: ${pthreadPoolSize}`,
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!pathConfig['wllama.wasm']) {
|
|
58
|
+
throw new Error('"wllama.wasm" is missing in pathConfig');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
noInitialRun: true,
|
|
62
|
+
print: function (text) {
|
|
63
|
+
if (arguments.length > 1)
|
|
64
|
+
text = Array.prototype.slice.call(arguments).join(' ');
|
|
65
|
+
msg({ verb: 'console.log', args: [text] });
|
|
66
|
+
},
|
|
67
|
+
printErr: function (text) {
|
|
68
|
+
if (arguments.length > 1)
|
|
69
|
+
text = Array.prototype.slice.call(arguments).join(' ');
|
|
70
|
+
if (text.startsWith('@@STACK@@')) {
|
|
71
|
+
lastStack = text.slice('@@STACK@@'.length);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const logLine = cppLogToJSLog(text);
|
|
75
|
+
msg({ verb: 'console.' + logLine.level, args: [logLine.text] });
|
|
76
|
+
},
|
|
77
|
+
locateFile: function (filename, basePath) {
|
|
78
|
+
const p = pathConfig[filename];
|
|
79
|
+
const truncate = (str) =>
|
|
80
|
+
str.length > 128 ? `${str.substr(0, 128)}...` : str;
|
|
81
|
+
if (filename.match(/wllama\.worker\.js/)) {
|
|
82
|
+
msg({
|
|
83
|
+
verb: 'console.error',
|
|
84
|
+
args: [
|
|
85
|
+
'"wllama.worker.js" is removed from v2.2.1. Hint: make sure to clear browser\'s cache.',
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
msg({
|
|
90
|
+
verb: 'console.debug',
|
|
91
|
+
args: [`Loading "${filename}" from "${truncate(p)}"`],
|
|
92
|
+
});
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
mainScriptUrlOrBlob: hasMultithread
|
|
97
|
+
? argMainScriptBlob
|
|
98
|
+
: 'throw new Error("Multithreading is not enabled")',
|
|
99
|
+
pthreadPoolSize: hasMultithread ? pthreadPoolSize : 0,
|
|
100
|
+
wasmMemory: hasMultithread ? getWasmMemory() : null,
|
|
101
|
+
onAbort: function (message) {
|
|
102
|
+
isAborted = true;
|
|
103
|
+
msg({ verb: 'signal.abort', args: ['abort', message, lastStack, null] });
|
|
104
|
+
},
|
|
105
|
+
onExit: function (code) {
|
|
106
|
+
isAborted = true;
|
|
107
|
+
const callstack = new Error().stack.toString();
|
|
108
|
+
msg({
|
|
109
|
+
verb: 'signal.abort',
|
|
110
|
+
args: ['abort', 'exit(' + code + ')', callstack, null],
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Get the memory to be used by wasm. (Only used in multi-thread mode)
|
|
117
|
+
// Because we have a weird OOM issue on iOS, we need to try some values
|
|
118
|
+
// See: https://github.com/emscripten-core/emscripten/issues/19144
|
|
119
|
+
// https://github.com/godotengine/godot/issues/70621
|
|
120
|
+
const getWasmMemory = () => {
|
|
121
|
+
let minBytes = 128 * 1024 * 1024;
|
|
122
|
+
let maxBytes = 4096 * 1024 * 1024;
|
|
123
|
+
let stepBytes = 128 * 1024 * 1024;
|
|
124
|
+
while (maxBytes > minBytes) {
|
|
125
|
+
try {
|
|
126
|
+
const wasmMemory = new WebAssembly.Memory({
|
|
127
|
+
initial: toSizeT(minBytes / 65536),
|
|
128
|
+
maximum: toSizeT(maxBytes / 65536),
|
|
129
|
+
shared: true,
|
|
130
|
+
address: isCompat ? undefined : 'i64',
|
|
131
|
+
});
|
|
132
|
+
return wasmMemory;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
maxBytes -= stepBytes;
|
|
135
|
+
continue; // retry
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error('Cannot allocate WebAssembly.Memory');
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
//////////////////////////////////////////////////////////////
|
|
142
|
+
// HEAPFS PATCH
|
|
143
|
+
//////////////////////////////////////////////////////////////
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* By default, emscripten uses memfs. The way it works is by
|
|
147
|
+
* allocating new Uint8Array in javascript heap. This is not good
|
|
148
|
+
* because it requires files to be copied to wasm heap each time
|
|
149
|
+
* a file is read.
|
|
150
|
+
*
|
|
151
|
+
* HeapFS is an alternative, which resolves this problem by
|
|
152
|
+
* allocating space for file directly inside wasm heap. This
|
|
153
|
+
* allows us to mmap without doing any copy.
|
|
154
|
+
*
|
|
155
|
+
* For llama.cpp, this is great because we use MAP_SHARED
|
|
156
|
+
*
|
|
157
|
+
* Ref: https://github.com/ngxson/wllama/pull/39
|
|
158
|
+
* Ref: https://github.com/emscripten-core/emscripten/blob/main/src/library_memfs.js
|
|
159
|
+
*
|
|
160
|
+
* Note 29/05/2024 @ngxson
|
|
161
|
+
* Due to ftell() being limited to MAX_LONG, we cannot load files bigger than 2^31 bytes (or 2GB)
|
|
162
|
+
* Ref: https://github.com/emscripten-core/emscripten/blob/main/system/lib/libc/musl/src/stdio/ftell.c
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
const fsNameToFile = {}; // map Name => File
|
|
166
|
+
const fsIdToFile = {}; // map ID => File
|
|
167
|
+
let currFileId = 0;
|
|
168
|
+
|
|
169
|
+
// Patch and redirect memfs calls to wllama
|
|
170
|
+
const patchHeapFS = () => {
|
|
171
|
+
const m = Module;
|
|
172
|
+
// save functions
|
|
173
|
+
m.MEMFS.stream_ops._read = m.MEMFS.stream_ops.read;
|
|
174
|
+
m.MEMFS.stream_ops._write = m.MEMFS.stream_ops.write;
|
|
175
|
+
m.MEMFS.stream_ops._llseek = m.MEMFS.stream_ops.llseek;
|
|
176
|
+
m.MEMFS.stream_ops._allocate = m.MEMFS.stream_ops.allocate;
|
|
177
|
+
m.MEMFS.stream_ops._mmap = m.MEMFS.stream_ops.mmap;
|
|
178
|
+
m.MEMFS.stream_ops._msync = m.MEMFS.stream_ops.msync;
|
|
179
|
+
|
|
180
|
+
const patchStream = (stream) => {
|
|
181
|
+
const name = stream.node.name;
|
|
182
|
+
if (fsNameToFile[name]) {
|
|
183
|
+
const f = fsNameToFile[name];
|
|
184
|
+
const ptr = Number(f.ptr);
|
|
185
|
+
stream.node.contents = getHeapU8().subarray(ptr, ptr + f.size);
|
|
186
|
+
stream.node.usedBytes = f.size;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// replace "read" functions
|
|
191
|
+
m.MEMFS.stream_ops.read = function (
|
|
192
|
+
stream,
|
|
193
|
+
buffer,
|
|
194
|
+
offset,
|
|
195
|
+
length,
|
|
196
|
+
position
|
|
197
|
+
) {
|
|
198
|
+
patchStream(stream);
|
|
199
|
+
return m.MEMFS.stream_ops._read(stream, buffer, offset, length, position);
|
|
200
|
+
};
|
|
201
|
+
m.MEMFS.ops_table.file.stream.read = m.MEMFS.stream_ops.read;
|
|
202
|
+
|
|
203
|
+
// replace "llseek" functions
|
|
204
|
+
m.MEMFS.stream_ops.llseek = function (stream, offset, whence) {
|
|
205
|
+
patchStream(stream);
|
|
206
|
+
return m.MEMFS.stream_ops._llseek(stream, offset, whence);
|
|
207
|
+
};
|
|
208
|
+
m.MEMFS.ops_table.file.stream.llseek = m.MEMFS.stream_ops.llseek;
|
|
209
|
+
|
|
210
|
+
// replace "mmap" functions
|
|
211
|
+
m.MEMFS.stream_ops.mmap = function (stream, length, position, prot, flags) {
|
|
212
|
+
patchStream(stream);
|
|
213
|
+
const name = stream.node.name;
|
|
214
|
+
if (fsNameToFile[name]) {
|
|
215
|
+
const f = fsNameToFile[name];
|
|
216
|
+
const mmapPtr = f.ptr + toSizeT(position);
|
|
217
|
+
return {
|
|
218
|
+
ptr: mmapPtr,
|
|
219
|
+
allocated: false,
|
|
220
|
+
};
|
|
221
|
+
} else {
|
|
222
|
+
return m.MEMFS.stream_ops._mmap(stream, length, position, prot, flags);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
m.MEMFS.ops_table.file.stream.mmap = m.MEMFS.stream_ops.mmap;
|
|
226
|
+
|
|
227
|
+
// mount FS
|
|
228
|
+
m.FS.mkdir('/models');
|
|
229
|
+
m.FS.mount(m.MEMFS, { root: '.' }, '/models');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Allocate a new file in wllama heapfs, returns file ID
|
|
233
|
+
const heapfsAlloc = (name, size, allocBuffer) => {
|
|
234
|
+
if (size < 1) {
|
|
235
|
+
throw new Error('File size must be bigger than 0');
|
|
236
|
+
}
|
|
237
|
+
const m = Module;
|
|
238
|
+
const ptr = toSizeT(allocBuffer ? m.mmapAlloc(size) : 0);
|
|
239
|
+
const file = {
|
|
240
|
+
ptr: ptr,
|
|
241
|
+
size: size,
|
|
242
|
+
id: currFileId++,
|
|
243
|
+
};
|
|
244
|
+
fsIdToFile[file.id] = file;
|
|
245
|
+
fsNameToFile[name] = file;
|
|
246
|
+
return file.id;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Add new file to wllama heapfs, return number of written bytes
|
|
250
|
+
const heapfsWrite = (id, buffer, offset) => {
|
|
251
|
+
if (fsIdToFile[id]) {
|
|
252
|
+
const { ptr, size } = fsIdToFile[id];
|
|
253
|
+
const afterWriteByte = offset + buffer.byteLength;
|
|
254
|
+
if (afterWriteByte > size) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`File ID ${id} write out of bound, afterWriteByte = ${afterWriteByte} while size = ${size}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
getHeapU8().set(buffer, Number(ptr) + offset);
|
|
260
|
+
return buffer.byteLength;
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error(`File ID ${id} not found in heapfs`);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
//////////////////////////////////////////////////////////////
|
|
267
|
+
// ASYNC FILE READ
|
|
268
|
+
//////////////////////////////////////////////////////////////
|
|
269
|
+
|
|
270
|
+
let isAwaitReading = false;
|
|
271
|
+
let pendingReadPromise = null;
|
|
272
|
+
let pendingReadResolve = null;
|
|
273
|
+
let pendingReadReject = null;
|
|
274
|
+
|
|
275
|
+
const _stripModelsPrefix = (path) => path.replace(/^\/?models\//, '');
|
|
276
|
+
|
|
277
|
+
// Called from EM_ASYNC_JS stub in wllama-fs.h (path is already a JS string)
|
|
278
|
+
const _wllama_js_file_read = async (path, offset, req_size, out_ptr) => {
|
|
279
|
+
const name = _stripModelsPrefix(path);
|
|
280
|
+
|
|
281
|
+
pendingReadPromise = new Promise((res, rej) => {
|
|
282
|
+
pendingReadResolve = res;
|
|
283
|
+
pendingReadReject = rej;
|
|
284
|
+
});
|
|
285
|
+
isAwaitReading = true;
|
|
286
|
+
|
|
287
|
+
postMessage({ verb: 'fs.read_req', args: [name, offset, req_size] });
|
|
288
|
+
|
|
289
|
+
let data;
|
|
290
|
+
try {
|
|
291
|
+
data = await pendingReadPromise;
|
|
292
|
+
} finally {
|
|
293
|
+
isAwaitReading = false;
|
|
294
|
+
pendingReadResolve = null;
|
|
295
|
+
pendingReadReject = null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const bytes = new Uint8Array(data);
|
|
299
|
+
getHeapU8().set(bytes, out_ptr);
|
|
300
|
+
return toSizeT(bytes.length);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
//////////////////////////////////////////////////////////////
|
|
304
|
+
// MAIN CODE
|
|
305
|
+
//////////////////////////////////////////////////////////////
|
|
306
|
+
|
|
307
|
+
const callWrapper = (name, ret, args, isAsync) => {
|
|
308
|
+
const fn = Module.cwrap(
|
|
309
|
+
name,
|
|
310
|
+
ret,
|
|
311
|
+
args,
|
|
312
|
+
isAsync ? { async: true } : undefined
|
|
313
|
+
);
|
|
314
|
+
return async (action, req) => {
|
|
315
|
+
// console.log(`Calling ${name} with action:`, action, 'and req:', req);
|
|
316
|
+
let result;
|
|
317
|
+
try {
|
|
318
|
+
if (args.length === 2) {
|
|
319
|
+
result = isAsync ? await fn(action, req) : fn(action, req);
|
|
320
|
+
} else {
|
|
321
|
+
result = fn();
|
|
322
|
+
}
|
|
323
|
+
} catch (ex) {
|
|
324
|
+
console.error(ex);
|
|
325
|
+
throw ex;
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
function handleError(err) {
|
|
332
|
+
// If WASM already aborted, onAbort already sent signal.abort; skip to avoid
|
|
333
|
+
// re-reporting the resulting WebAssembly.RuntimeError as a JS exception.
|
|
334
|
+
if (isAborted) return;
|
|
335
|
+
|
|
336
|
+
const message = err ? err.message || String(err) : 'Unknown error';
|
|
337
|
+
const stack = err ? err.stack || String(err) : '';
|
|
338
|
+
msg({
|
|
339
|
+
verb: 'signal.abort',
|
|
340
|
+
args: ['exception', message, stack, err],
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
onmessage = async (e) => {
|
|
345
|
+
if (!e.data) return;
|
|
346
|
+
const { verb, args, callbackId } = e.data;
|
|
347
|
+
|
|
348
|
+
// fs.read_res arrives while wasm is JSPI-suspended; resolve the pending promise.
|
|
349
|
+
if (verb === 'fs.read_res') {
|
|
350
|
+
if (pendingReadResolve) {
|
|
351
|
+
pendingReadResolve(args[0]);
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Guard: while awaiting a file read, reject any other incoming task.
|
|
357
|
+
if (isAwaitReading) {
|
|
358
|
+
if (callbackId) {
|
|
359
|
+
msg({
|
|
360
|
+
callbackId,
|
|
361
|
+
err: 'Worker is suspended waiting for file data (JSPI)',
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!callbackId) {
|
|
368
|
+
msg({ verb: 'console.error', args: ['callbackId is required', e.data] });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (verb === 'module.init') {
|
|
373
|
+
const argMainScriptBlob = args[0];
|
|
374
|
+
const argUseAsyncFile = args[1];
|
|
375
|
+
try {
|
|
376
|
+
Module = getWModuleConfig(argMainScriptBlob);
|
|
377
|
+
Module.preRun = () => {
|
|
378
|
+
if (argUseAsyncFile) {
|
|
379
|
+
Module.ENV['USE_ASYNC_FILE'] = '1';
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
Module.onRuntimeInitialized = () => {
|
|
383
|
+
// async call once module is ready
|
|
384
|
+
// init FS
|
|
385
|
+
patchHeapFS();
|
|
386
|
+
// init cwrap
|
|
387
|
+
const pointer = isCompat ? 'number' : 'bigint';
|
|
388
|
+
// TODO: note sure why emscripten cannot bind if there is only 1 argument
|
|
389
|
+
wllamaMalloc = callWrapper('wllama_malloc', pointer, [
|
|
390
|
+
'number',
|
|
391
|
+
pointer,
|
|
392
|
+
]);
|
|
393
|
+
wllamaStart = callWrapper('wllama_start', 'string', [], true);
|
|
394
|
+
wllamaAction = callWrapper(
|
|
395
|
+
'wllama_action',
|
|
396
|
+
pointer,
|
|
397
|
+
['string', pointer],
|
|
398
|
+
true
|
|
399
|
+
);
|
|
400
|
+
wllamaExit = callWrapper('wllama_exit', 'string', []);
|
|
401
|
+
wllamaDebug = callWrapper('wllama_debug', 'string', []);
|
|
402
|
+
msg({ callbackId, result: null });
|
|
403
|
+
};
|
|
404
|
+
wModuleInit();
|
|
405
|
+
} catch (err) {
|
|
406
|
+
handleError(err);
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (verb === 'fs.alloc') {
|
|
412
|
+
const argFilename = args[0];
|
|
413
|
+
const argSize = args[1];
|
|
414
|
+
const argAllocBuffer = args[2];
|
|
415
|
+
try {
|
|
416
|
+
// create blank file
|
|
417
|
+
const emptyBuffer = new ArrayBuffer(0);
|
|
418
|
+
Module['FS_createDataFile'](
|
|
419
|
+
'/models',
|
|
420
|
+
argFilename,
|
|
421
|
+
emptyBuffer,
|
|
422
|
+
true,
|
|
423
|
+
true,
|
|
424
|
+
true
|
|
425
|
+
);
|
|
426
|
+
// alloc data on heap
|
|
427
|
+
const fileId = heapfsAlloc(argFilename, argSize, argAllocBuffer);
|
|
428
|
+
msg({ callbackId, result: { fileId } });
|
|
429
|
+
} catch (err) {
|
|
430
|
+
handleError(err);
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (verb === 'fs.write') {
|
|
436
|
+
const argFileId = args[0];
|
|
437
|
+
const argBuffer = args[1];
|
|
438
|
+
const argOffset = args[2];
|
|
439
|
+
try {
|
|
440
|
+
const writtenBytes = heapfsWrite(argFileId, argBuffer, argOffset);
|
|
441
|
+
msg({ callbackId, result: { writtenBytes } });
|
|
442
|
+
} catch (err) {
|
|
443
|
+
handleError(err);
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (verb === 'wllama.start') {
|
|
449
|
+
try {
|
|
450
|
+
const result = await wllamaStart();
|
|
451
|
+
msg({ callbackId, result });
|
|
452
|
+
} catch (err) {
|
|
453
|
+
handleError(err);
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (verb === 'wllama.action') {
|
|
459
|
+
const argAction = args[0];
|
|
460
|
+
const argEncodedMsg = args[1];
|
|
461
|
+
try {
|
|
462
|
+
const inputPtr = await wllamaMalloc(toSizeT(argEncodedMsg.byteLength), 0);
|
|
463
|
+
// copy data to wasm heap
|
|
464
|
+
const inputBuffer = new Uint8Array(
|
|
465
|
+
getHeapU8().buffer,
|
|
466
|
+
Number(inputPtr),
|
|
467
|
+
argEncodedMsg.byteLength
|
|
468
|
+
);
|
|
469
|
+
inputBuffer.set(argEncodedMsg, 0);
|
|
470
|
+
const outputPtr = await wllamaAction(argAction, inputPtr);
|
|
471
|
+
// length of output buffer is written at the first 4 bytes of input buffer
|
|
472
|
+
const outputLen = new Uint32Array(
|
|
473
|
+
getHeapU8().buffer,
|
|
474
|
+
Number(inputPtr),
|
|
475
|
+
1
|
|
476
|
+
)[0];
|
|
477
|
+
// copy the output buffer to JS heap
|
|
478
|
+
const outputBuffer = new Uint8Array(outputLen);
|
|
479
|
+
const outputSrcView = new Uint8Array(
|
|
480
|
+
getHeapU8().buffer,
|
|
481
|
+
Number(outputPtr),
|
|
482
|
+
outputLen
|
|
483
|
+
);
|
|
484
|
+
outputBuffer.set(outputSrcView, 0); // copy it
|
|
485
|
+
msg({ callbackId, result: outputBuffer }, [outputBuffer.buffer]);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
handleError(err);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (verb === 'wllama.exit') {
|
|
493
|
+
try {
|
|
494
|
+
const result = await wllamaExit();
|
|
495
|
+
msg({ callbackId, result });
|
|
496
|
+
} catch (err) {
|
|
497
|
+
handleError(err);
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (verb === 'wllama.debug') {
|
|
503
|
+
try {
|
|
504
|
+
const result = await wllamaDebug();
|
|
505
|
+
msg({ callbackId, result });
|
|
506
|
+
} catch (err) {
|
|
507
|
+
handleError(err);
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
let accessHandle;
|
|
2
|
+
let abortController = new AbortController();
|
|
3
|
+
|
|
4
|
+
async function openFile(filename) {
|
|
5
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
6
|
+
const cacheDir = await opfsRoot.getDirectoryHandle('cache', { create: true });
|
|
7
|
+
const fileHandler = await cacheDir.getFileHandle(filename, { create: true });
|
|
8
|
+
accessHandle = await fileHandler.createSyncAccessHandle();
|
|
9
|
+
accessHandle.truncate(0); // clear file content
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function writeFile(buf) {
|
|
13
|
+
accessHandle.write(buf);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function closeFile() {
|
|
17
|
+
accessHandle.flush();
|
|
18
|
+
accessHandle.close();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function writeTextFile(filename, str) {
|
|
22
|
+
await openFile(filename);
|
|
23
|
+
await writeFile(new TextEncoder().encode(str));
|
|
24
|
+
await closeFile();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const throttled = (func, delay) => {
|
|
28
|
+
let lastRun = 0;
|
|
29
|
+
return (...args) => {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (now - lastRun > delay) {
|
|
32
|
+
lastRun = now;
|
|
33
|
+
func.apply(null, args);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const assertNonNull = (val) => {
|
|
39
|
+
if (val === null || val === undefined) {
|
|
40
|
+
throw new Error('OPFS Worker: Assertion failed');
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// respond to main thread
|
|
45
|
+
const resOK = () => postMessage({ ok: true });
|
|
46
|
+
const resProgress = (loaded, total) =>
|
|
47
|
+
postMessage({ progress: { loaded, total } });
|
|
48
|
+
const resErr = (err) => postMessage({ err });
|
|
49
|
+
|
|
50
|
+
onmessage = async (e) => {
|
|
51
|
+
try {
|
|
52
|
+
if (!e.data) return;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {Object} e.data
|
|
56
|
+
*
|
|
57
|
+
* Fine-control FS actions:
|
|
58
|
+
* - { action: 'open', filename: 'string' }
|
|
59
|
+
* - { action: 'write', buf: ArrayBuffer }
|
|
60
|
+
* - { action: 'close' }
|
|
61
|
+
*
|
|
62
|
+
* Simple write API:
|
|
63
|
+
* - { action: 'write-simple', filename: 'string', buf: ArrayBuffer }
|
|
64
|
+
*
|
|
65
|
+
* Download API:
|
|
66
|
+
* - { action: 'download', url: 'string', filename: 'string', options: Object, metadataFileName: 'string' }
|
|
67
|
+
* - { action: 'download-abort' }
|
|
68
|
+
*/
|
|
69
|
+
const {
|
|
70
|
+
action,
|
|
71
|
+
filename,
|
|
72
|
+
buf,
|
|
73
|
+
url,
|
|
74
|
+
options,
|
|
75
|
+
metadataFileName,
|
|
76
|
+
metadataAdditional,
|
|
77
|
+
} = e.data;
|
|
78
|
+
|
|
79
|
+
if (action === 'open') {
|
|
80
|
+
assertNonNull(filename);
|
|
81
|
+
await openFile(filename);
|
|
82
|
+
return resOK();
|
|
83
|
+
} else if (action === 'write') {
|
|
84
|
+
assertNonNull(buf);
|
|
85
|
+
await writeFile(buf);
|
|
86
|
+
return resOK();
|
|
87
|
+
} else if (action === 'close') {
|
|
88
|
+
await closeFile();
|
|
89
|
+
return resOK();
|
|
90
|
+
} else if (action === 'write-simple') {
|
|
91
|
+
assertNonNull(filename);
|
|
92
|
+
assertNonNull(buf);
|
|
93
|
+
await openFile(filename);
|
|
94
|
+
await writeFile(buf);
|
|
95
|
+
await closeFile();
|
|
96
|
+
return resOK();
|
|
97
|
+
} else if (action === 'download') {
|
|
98
|
+
assertNonNull(url);
|
|
99
|
+
assertNonNull(filename);
|
|
100
|
+
assertNonNull(metadataFileName);
|
|
101
|
+
assertNonNull(options);
|
|
102
|
+
assertNonNull(options.aborted);
|
|
103
|
+
abortController = new AbortController();
|
|
104
|
+
if (options.aborted) abortController.abort();
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
...options,
|
|
107
|
+
signal: abortController.signal,
|
|
108
|
+
});
|
|
109
|
+
const contentLength = response.headers.get('content-length');
|
|
110
|
+
const etag = (response.headers.get('etag') || '').replace(
|
|
111
|
+
/[^A-Za-z0-9]/g,
|
|
112
|
+
''
|
|
113
|
+
);
|
|
114
|
+
const total = parseInt(contentLength, 10);
|
|
115
|
+
const reader = response.body.getReader();
|
|
116
|
+
await openFile(filename);
|
|
117
|
+
let loaded = 0;
|
|
118
|
+
const throttledProgress = throttled(resProgress, 100);
|
|
119
|
+
while (true) {
|
|
120
|
+
const { done, value } = await reader.read();
|
|
121
|
+
if (done) break;
|
|
122
|
+
loaded += value.byteLength;
|
|
123
|
+
await writeFile(value);
|
|
124
|
+
throttledProgress(loaded, total);
|
|
125
|
+
}
|
|
126
|
+
resProgress(total, total); // 100% done
|
|
127
|
+
await closeFile();
|
|
128
|
+
// make sure this is in-sync with CacheEntryMetadata
|
|
129
|
+
await writeTextFile(
|
|
130
|
+
metadataFileName,
|
|
131
|
+
JSON.stringify({
|
|
132
|
+
originalURL: url,
|
|
133
|
+
originalSize: total,
|
|
134
|
+
etag,
|
|
135
|
+
...metadataAdditional,
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
return resOK();
|
|
139
|
+
} else if (action === 'download-abort') {
|
|
140
|
+
if (abortController) {
|
|
141
|
+
abortController.abort();
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throw new Error('OPFS Worker: Invalid action', e.data);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return resErr(err);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"useDefineForClassFields": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"lib": ["esnext", "dom"],
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noImplicitThis": true,
|
|
12
|
+
"strictNullChecks": true,
|
|
13
|
+
"strictFunctionTypes": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"preserveConstEnums": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"downlevelIteration": true,
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"experimentalDecorators": true,
|
|
21
|
+
"exactOptionalPropertyTypes": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
"verbatimModuleSyntax": true,
|
|
24
|
+
|
|
25
|
+
"noEmit": false,
|
|
26
|
+
"module": "es2015",
|
|
27
|
+
"outDir": "esm",
|
|
28
|
+
"rootDir": "./src",
|
|
29
|
+
"baseUrl": ".",
|
|
30
|
+
"allowSyntheticDefaultImports": true
|
|
31
|
+
},
|
|
32
|
+
"include": [ "./src/**/*.ts" ],
|
|
33
|
+
"exclude": [ "./src/**/*.test.ts" ]
|
|
34
|
+
}
|