@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,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Usage: node scripts/build_source_map.js [--input name:buildDir] [--output out.ts]
|
|
3
|
+
// All flags are optional — defaults point to build/ and src/wasm/source-map.ts.
|
|
4
|
+
// Reads *.js.symbols from each build dir and produces cleaned function names.
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
7
|
+
import { resolve, join } from 'path';
|
|
8
|
+
import { gzipSync } from 'zlib';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_INPUTS = [
|
|
11
|
+
{ name: 'default', symbolsPath: 'build' },
|
|
12
|
+
{ name: 'compat', symbolsPath: 'build-compat' },
|
|
13
|
+
];
|
|
14
|
+
const DEFAULT_OUTPUT = 'src/wasm/source-map.ts';
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const inputs = [];
|
|
18
|
+
let outputFile = null;
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === '--input') {
|
|
22
|
+
const next = args[++i];
|
|
23
|
+
if (!next) { console.error('--input must be name:buildDir'); process.exit(1); }
|
|
24
|
+
const [name, symbolsPath] = next.split(':');
|
|
25
|
+
if (!name || !symbolsPath) { console.error('--input must be name:buildDir'); process.exit(1); }
|
|
26
|
+
inputs.push({ name, symbolsPath: resolve(symbolsPath) });
|
|
27
|
+
} else if (args[i] === '--output') {
|
|
28
|
+
const next = args[++i];
|
|
29
|
+
if (!next) { console.error('--output requires a path'); process.exit(1); }
|
|
30
|
+
outputFile = resolve(next);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!inputs.length) {
|
|
35
|
+
for (const { name, symbolsPath } of DEFAULT_INPUTS) {
|
|
36
|
+
const resolved = resolve(symbolsPath);
|
|
37
|
+
try { readdirSync(resolved); inputs.push({ name, symbolsPath: resolved }); } catch { /* skip */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!outputFile) outputFile = resolve(DEFAULT_OUTPUT);
|
|
41
|
+
if (!inputs.length) { console.error('No build directories found.'); process.exit(1); }
|
|
42
|
+
|
|
43
|
+
// -- wasm binary: extract [firstFuncId, funcCount] ----------------------------
|
|
44
|
+
|
|
45
|
+
function readUleb(buf, pos) {
|
|
46
|
+
let result = 0, shift = 0;
|
|
47
|
+
while (true) {
|
|
48
|
+
const b = buf[pos++];
|
|
49
|
+
result |= (b & 0x7F) << shift;
|
|
50
|
+
if (!(b & 0x80)) break;
|
|
51
|
+
shift += 7;
|
|
52
|
+
}
|
|
53
|
+
return [result, pos];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function skipLimits(buf, pos) {
|
|
57
|
+
const kind = buf[pos++];
|
|
58
|
+
[, pos] = readUleb(buf, pos);
|
|
59
|
+
if (kind === 0x01 || kind === 0x03 || kind === 0x05 || kind === 0x07)
|
|
60
|
+
[, pos] = readUleb(buf, pos);
|
|
61
|
+
return pos;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseFuncIds(wasmBuf) {
|
|
65
|
+
let pos = 8, importCount = 0;
|
|
66
|
+
while (pos < wasmBuf.length) {
|
|
67
|
+
const sectionId = wasmBuf[pos++];
|
|
68
|
+
let sectionSize;
|
|
69
|
+
[sectionSize, pos] = readUleb(wasmBuf, pos);
|
|
70
|
+
const sectionEnd = pos + sectionSize;
|
|
71
|
+
if (sectionId === 2) {
|
|
72
|
+
let count; [count, pos] = readUleb(wasmBuf, pos);
|
|
73
|
+
for (let i = 0; i < count; i++) {
|
|
74
|
+
let nl;
|
|
75
|
+
[nl, pos] = readUleb(wasmBuf, pos); pos += nl;
|
|
76
|
+
[nl, pos] = readUleb(wasmBuf, pos); pos += nl;
|
|
77
|
+
const kind = wasmBuf[pos++];
|
|
78
|
+
if (kind === 0) { [, pos] = readUleb(wasmBuf, pos); importCount++; }
|
|
79
|
+
else if (kind === 1) { pos++; pos = skipLimits(wasmBuf, pos); }
|
|
80
|
+
else if (kind === 2) { pos = skipLimits(wasmBuf, pos); }
|
|
81
|
+
else if (kind === 3) { pos += 2; }
|
|
82
|
+
}
|
|
83
|
+
} else if (sectionId === 10) {
|
|
84
|
+
let funcCount; [funcCount, pos] = readUleb(wasmBuf, pos);
|
|
85
|
+
return [importCount, funcCount];
|
|
86
|
+
} else {
|
|
87
|
+
pos = sectionEnd;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new Error('No code section found');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// -- name simplification ------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
function truncateTemplates(name, maxLen) {
|
|
96
|
+
let result = '', i = 0;
|
|
97
|
+
while (i < name.length) {
|
|
98
|
+
if (name[i] !== '<') { result += name[i++]; continue; }
|
|
99
|
+
// find matching >
|
|
100
|
+
let depth = 1, j = i + 1;
|
|
101
|
+
while (j < name.length && depth > 0) {
|
|
102
|
+
if (name[j] === '<') depth++;
|
|
103
|
+
else if (name[j] === '>') depth--;
|
|
104
|
+
j++;
|
|
105
|
+
}
|
|
106
|
+
const content = name.slice(i + 1, j - 1);
|
|
107
|
+
result += content.length > maxLen
|
|
108
|
+
? '<' + content.slice(0, maxLen) + '...>'
|
|
109
|
+
: '<' + content + '>';
|
|
110
|
+
i = j;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function stripParams(name) {
|
|
116
|
+
// Find first top-level '(' (not inside <>) and strip from there.
|
|
117
|
+
// If params are empty "()", keep them; otherwise drop entirely.
|
|
118
|
+
let depth = 0;
|
|
119
|
+
for (let i = 0; i < name.length; i++) {
|
|
120
|
+
const c = name[i];
|
|
121
|
+
if (c === '<') { depth++; continue; }
|
|
122
|
+
if (c === '>') { depth--; continue; }
|
|
123
|
+
if (c === '(' && depth === 0) {
|
|
124
|
+
const base = name.slice(0, i);
|
|
125
|
+
return (name[i + 1] === ')') ? base + '()' : base;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return name;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const STD_HINT = 'std::...';
|
|
132
|
+
|
|
133
|
+
function simplifyName(raw) {
|
|
134
|
+
if (!raw) return null;
|
|
135
|
+
let name = raw;
|
|
136
|
+
|
|
137
|
+
// Rule 0: collapse all std:: into a hint
|
|
138
|
+
if (/^std::/.test(name)) return STD_HINT;
|
|
139
|
+
|
|
140
|
+
// Rule 1: lambda / closure types
|
|
141
|
+
// Handles ::$_N, ::'lambda'(), 'lambda'()
|
|
142
|
+
// Strategy: find the marker, take everything inside the nearest enclosing <..> before it
|
|
143
|
+
const lambdaRe = /::[$']_?\d*|::'lambda'/;
|
|
144
|
+
const lambdaMatch = lambdaRe.exec(name);
|
|
145
|
+
if (lambdaMatch) {
|
|
146
|
+
const before = name.slice(0, lambdaMatch.index);
|
|
147
|
+
// Extract the innermost meaningful context: look for the last '<' before the marker
|
|
148
|
+
const lastAngle = before.lastIndexOf('<');
|
|
149
|
+
let parent = lastAngle >= 0 ? before.slice(lastAngle + 1) : before;
|
|
150
|
+
// Strip trailing qualifiers
|
|
151
|
+
parent = parent.replace(/\s+(const|volatile|noexcept|&&?)\s*$/, '').trim();
|
|
152
|
+
name = parent;
|
|
153
|
+
// fall through to further cleanup below
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Rule 2: strip parameter list
|
|
157
|
+
name = stripParams(name);
|
|
158
|
+
|
|
159
|
+
// Rule 3: remove libc++ internal sub-namespaces (::__2::, ::__1::, etc.)
|
|
160
|
+
name = name.replace(/::__\d+::/g, '::');
|
|
161
|
+
|
|
162
|
+
// Rule 4: remove ABI tags
|
|
163
|
+
name = name.replace(/\[abi:[^\]]+\]/g, '');
|
|
164
|
+
|
|
165
|
+
// Rule 5: truncate template args to 10 chars
|
|
166
|
+
name = truncateTemplates(name, 10);
|
|
167
|
+
|
|
168
|
+
// Rule 6: final cleanup
|
|
169
|
+
name = name.replace(/::::/g, '::').replace(/\s+/g, ' ').trim();
|
|
170
|
+
|
|
171
|
+
return name || null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// -- binary encoder -----------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
function encodeNames(funcCount, firstId, symbols) {
|
|
177
|
+
// Build deduplicated name table
|
|
178
|
+
const nameToIdx = new Map(); // string -> u16 index
|
|
179
|
+
const nameTable = []; // array of Buffer
|
|
180
|
+
const indices = new Uint16Array(funcCount); // 0xFFFF = unknown
|
|
181
|
+
indices.fill(0xFFFF);
|
|
182
|
+
|
|
183
|
+
let mapped = 0;
|
|
184
|
+
for (let i = 0; i < funcCount; i++) {
|
|
185
|
+
const raw = symbols.get(firstId + i) ?? null;
|
|
186
|
+
const cleaned = raw ? simplifyName(raw) : null;
|
|
187
|
+
if (!cleaned) continue;
|
|
188
|
+
let idx = nameToIdx.get(cleaned);
|
|
189
|
+
if (idx === undefined) {
|
|
190
|
+
idx = nameTable.length;
|
|
191
|
+
const b = Buffer.from(cleaned.slice(0, 254));
|
|
192
|
+
nameTable.push(Buffer.concat([Buffer.from([b.length]), b]));
|
|
193
|
+
nameToIdx.set(cleaned, idx);
|
|
194
|
+
}
|
|
195
|
+
indices[i] = idx;
|
|
196
|
+
mapped++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const numNames = nameTable.length;
|
|
200
|
+
process.stderr.write(` ${mapped}/${funcCount} named, ${numNames} unique names\n`);
|
|
201
|
+
|
|
202
|
+
// u32 numNames + name table + u16 index array
|
|
203
|
+
const header = Buffer.alloc(4);
|
|
204
|
+
header.writeUInt32LE(numNames, 0);
|
|
205
|
+
const indexBuf = Buffer.from(indices.buffer);
|
|
206
|
+
return Buffer.concat([header, ...nameTable, indexBuf]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// -- resolve symbolsPath (dir or file) ----------------------------------------
|
|
210
|
+
|
|
211
|
+
function resolveSymbolsFile(symbolsPath) {
|
|
212
|
+
if (symbolsPath.endsWith('.js.symbols')) return symbolsPath;
|
|
213
|
+
for (const entry of readdirSync(symbolsPath))
|
|
214
|
+
if (entry.endsWith('.js.symbols')) return join(symbolsPath, entry);
|
|
215
|
+
throw new Error(`No .js.symbols file in ${symbolsPath}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// -- per-build processing -----------------------------------------------------
|
|
219
|
+
|
|
220
|
+
function processBuild(symbolsPath) {
|
|
221
|
+
const resolvedSymbols = resolveSymbolsFile(symbolsPath);
|
|
222
|
+
const wasmPath = resolvedSymbols.replace(/\.js\.symbols$/, '.wasm');
|
|
223
|
+
|
|
224
|
+
process.stderr.write(` Parsing wasm binary...\n`);
|
|
225
|
+
const [firstId, funcCount] = parseFuncIds(readFileSync(wasmPath));
|
|
226
|
+
process.stderr.write(` ${funcCount} functions starting at index ${firstId}\n`);
|
|
227
|
+
|
|
228
|
+
process.stderr.write(` Loading symbols...\n`);
|
|
229
|
+
const symbols = new Map();
|
|
230
|
+
for (const line of readFileSync(resolvedSymbols, 'utf8').split('\n')) {
|
|
231
|
+
const colon = line.indexOf(':');
|
|
232
|
+
if (colon < 0) continue;
|
|
233
|
+
const id = parseInt(line.slice(0, colon));
|
|
234
|
+
if (!isNaN(id)) symbols.set(id, line.slice(colon + 1).trim());
|
|
235
|
+
}
|
|
236
|
+
process.stderr.write(` ${symbols.size} raw symbols\n`);
|
|
237
|
+
|
|
238
|
+
const header = Buffer.alloc(8);
|
|
239
|
+
header.writeUInt32LE(firstId, 0);
|
|
240
|
+
header.writeUInt32LE(funcCount, 4);
|
|
241
|
+
|
|
242
|
+
const nameData = encodeNames(funcCount, firstId, symbols);
|
|
243
|
+
const binary = Buffer.concat([header, nameData]);
|
|
244
|
+
const compressed = gzipSync(binary);
|
|
245
|
+
process.stderr.write(` ${binary.length.toLocaleString()} bytes -> ${compressed.length.toLocaleString()} bytes gzipped\n`);
|
|
246
|
+
|
|
247
|
+
return compressed.toString('base64');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// -- main ---------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
const entries_ts = [];
|
|
253
|
+
for (const { name, symbolsPath } of inputs) {
|
|
254
|
+
process.stderr.write(`\n[${name}] ${symbolsPath}\n`);
|
|
255
|
+
entries_ts.push(` "${name}": "${processBuild(symbolsPath)}"`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const tsContent = [
|
|
259
|
+
`// Auto-generated by scripts/build_source_map.js — do not edit`,
|
|
260
|
+
`// Format: gzip-compressed binary name table, base64-encoded`,
|
|
261
|
+
`// Structure: u32 firstId, u32 funcCount, u32 numNames, then name table (u8 len + bytes each), then u16 index array (0xFFFF = unknown)`,
|
|
262
|
+
`export const WASM_SOURCE_MAP: Record<string, string> = {`,
|
|
263
|
+
entries_ts.join(',\n'),
|
|
264
|
+
`};`,
|
|
265
|
+
``,
|
|
266
|
+
].join('\n');
|
|
267
|
+
|
|
268
|
+
writeFileSync(outputFile, tsContent);
|
|
269
|
+
process.stderr.write(`\nWrote ${outputFile}\n`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
#set -e
|
|
4
|
+
|
|
5
|
+
export EMSDK_IMAGE_TAG="4.0.20"
|
|
6
|
+
|
|
7
|
+
CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
8
|
+
cd $CURRENT_PATH
|
|
9
|
+
|
|
10
|
+
export D_UID=$UID
|
|
11
|
+
export D_GID=$GID
|
|
12
|
+
|
|
13
|
+
if [[ $(uname -m) == "arm64" ]]; then
|
|
14
|
+
echo "Running on ARM64 processor"
|
|
15
|
+
export DOCKER_DEFAULT_PLATFORM="linux/arm64"
|
|
16
|
+
export EMSDK_IMAGE_TAG="${EMSDK_IMAGE_TAG}-arm64"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
docker compose up llamacpp-wasm-builder --exit-code-from llamacpp-wasm-builder
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
6
|
+
|
|
7
|
+
# change to the llama.cpp directory
|
|
8
|
+
cd $CURRENT_PATH
|
|
9
|
+
cd ../llama.cpp
|
|
10
|
+
BUILD_NUMBER="$(git rev-list --count HEAD)"
|
|
11
|
+
SHORT_HASH="$(git rev-parse --short=7 HEAD)"
|
|
12
|
+
|
|
13
|
+
# change to the root of the project
|
|
14
|
+
cd $CURRENT_PATH
|
|
15
|
+
cd ..
|
|
16
|
+
|
|
17
|
+
echo "// This file is auto-generated" > ./src/workers-code/generated.ts
|
|
18
|
+
echo "// To re-generate it, run: npm run build:worker" >> ./src/workers-code/generated.ts
|
|
19
|
+
echo "" >> ./src/workers-code/generated.ts
|
|
20
|
+
echo "export const LIBLLAMA_VERSION = 'b${BUILD_NUMBER}-${SHORT_HASH}';" >> ./src/workers-code/generated.ts
|
|
21
|
+
echo "" >> ./src/workers-code/generated.ts
|
|
22
|
+
|
|
23
|
+
process_file() {
|
|
24
|
+
local file="$1"
|
|
25
|
+
local content
|
|
26
|
+
content=$(node -e "console.log(JSON.stringify(require('fs').readFileSync('$file', 'utf8').toString()))")
|
|
27
|
+
echo "export const $2 = $content;" >> ./src/workers-code/generated.ts
|
|
28
|
+
echo "" >> ./src/workers-code/generated.ts
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
process_file ./src/workers-code/llama-cpp.js LLAMA_CPP_WORKER_CODE
|
|
32
|
+
process_file ./src/workers-code/opfs-utils.js OPFS_UTILS_WORKER_CODE
|
|
33
|
+
|
|
34
|
+
# emscripten
|
|
35
|
+
process_file ./src/wasm/wllama.js WLLAMA_EMSCRIPTEN_CODE
|
|
36
|
+
|
|
37
|
+
# build CDN paths
|
|
38
|
+
node ./scripts/generate_wasm_from_cdn.js
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { resolve, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const ROOT = resolve(__dirname, '..');
|
|
9
|
+
const WASM_PATHS = [
|
|
10
|
+
'src/wasm/wllama.wasm',
|
|
11
|
+
'compat/src/wasm/wllama.wasm',
|
|
12
|
+
];
|
|
13
|
+
const DEBUG_MARKER = 'test-backend-ops.cpp';
|
|
14
|
+
|
|
15
|
+
export function checkDebugBuild() {
|
|
16
|
+
for (const relPath of WASM_PATHS) {
|
|
17
|
+
const absPath = resolve(ROOT, relPath);
|
|
18
|
+
if (!existsSync(absPath)) continue;
|
|
19
|
+
const contents = readFileSync(absPath);
|
|
20
|
+
if (contents.includes(DEBUG_MARKER)) {
|
|
21
|
+
console.error(`ERROR: ${relPath} contains "${DEBUG_MARKER}" - this is a debug build and cannot be merged to master or be published`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Run directly when invoked as a script
|
|
28
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
29
|
+
checkDebugBuild();
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { checkDebugBuild } from './check_debug_build.js';
|
|
5
|
+
|
|
6
|
+
checkDebugBuild();
|
|
7
|
+
|
|
8
|
+
const MAX_SIZE = 20 * 1024 * 1024; // 20 MB
|
|
9
|
+
const MAX_FILES = 90;
|
|
10
|
+
|
|
11
|
+
const result = JSON.parse(execSync('npm pack --dry-run --json 2>/dev/null'));
|
|
12
|
+
const { unpackedSize, entryCount } = result[0];
|
|
13
|
+
|
|
14
|
+
console.log(`Unpacked size: ${(unpackedSize / 1024 / 1024).toFixed(2)} MB`);
|
|
15
|
+
console.log(`Total files: ${entryCount}`);
|
|
16
|
+
|
|
17
|
+
if (unpackedSize > MAX_SIZE) {
|
|
18
|
+
console.error(`ERROR: Unpacked size exceeds 20 MB limit`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (entryCount > MAX_FILES) {
|
|
23
|
+
console.error(`ERROR: Total files (${entryCount}) exceeds limit of ${MAX_FILES}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
services:
|
|
2
|
+
llamacpp-wasm-builder:
|
|
3
|
+
container_name: llamacpp-wasm-builder
|
|
4
|
+
build:
|
|
5
|
+
context: .
|
|
6
|
+
dockerfile_inline: |
|
|
7
|
+
ARG EMSDK_IMAGE_TAG=none # modify it in build_wasm.sh
|
|
8
|
+
FROM emscripten/emsdk:$${EMSDK_IMAGE_TAG}
|
|
9
|
+
RUN apt update && apt install -y git
|
|
10
|
+
args:
|
|
11
|
+
EMSDK_IMAGE_TAG: ${EMSDK_IMAGE_TAG}
|
|
12
|
+
user: "${D_UID}:${D_GID}"
|
|
13
|
+
environment:
|
|
14
|
+
- WLLAMA_TEST_BACKEND=${WLLAMA_TEST_BACKEND:-}
|
|
15
|
+
- SKIP_COMPAT=${SKIP_COMPAT:-}
|
|
16
|
+
volumes:
|
|
17
|
+
- ..:/source:Z
|
|
18
|
+
entrypoint: /bin/bash
|
|
19
|
+
command:
|
|
20
|
+
- -c
|
|
21
|
+
- |
|
|
22
|
+
set -e
|
|
23
|
+
cd /source
|
|
24
|
+
|
|
25
|
+
mkdir -p build
|
|
26
|
+
cd build
|
|
27
|
+
mkdir -p emdawn
|
|
28
|
+
|
|
29
|
+
DAWN_TAG=v20260317.182325
|
|
30
|
+
EMDAWN_PKG="emdawnwebgpu_pkg-$${DAWN_TAG}.zip"
|
|
31
|
+
EMDAWNWEBGPU_DIR="/source/build/emdawn/emdawnwebgpu_pkg"
|
|
32
|
+
echo "Downloading $${EMDAWN_PKG}"
|
|
33
|
+
curl -L -o emdawn.zip \
|
|
34
|
+
"https://github.com/google/dawn/releases/download/$${DAWN_TAG}/$${EMDAWN_PKG}"
|
|
35
|
+
python3 -c "import zipfile; zf=zipfile.ZipFile('emdawn.zip','r'); zf.extractall('/source/build/emdawn'); zf.close()"
|
|
36
|
+
|
|
37
|
+
CMAKE_EXTRA_FLAGS="-DWLLAMA_TEST_BACKEND=OFF"
|
|
38
|
+
if [ -n "${WLLAMA_TEST_BACKEND}" ]; then
|
|
39
|
+
CMAKE_EXTRA_FLAGS="-DWLLAMA_TEST_BACKEND=ON";
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
emcmake cmake .. -DGGML_WEBGPU=ON -DGGML_WEBGPU_JSPI=ON -DEMDAWNWEBGPU_DIR="$${EMDAWNWEBGPU_DIR}" $${CMAKE_EXTRA_FLAGS}
|
|
43
|
+
emmake make wllama -j
|
|
44
|
+
|
|
45
|
+
# go back to root
|
|
46
|
+
cd ..
|
|
47
|
+
|
|
48
|
+
# copy output files
|
|
49
|
+
mkdir -p src/wasm
|
|
50
|
+
cp build/wllama.js src/wasm
|
|
51
|
+
cp build/wllama.wasm src/wasm
|
|
52
|
+
|
|
53
|
+
# build compat package (skip if SKIP_COMPAT=1)
|
|
54
|
+
if [ -z "${SKIP_COMPAT}" ]; then
|
|
55
|
+
mkdir -p build-compat
|
|
56
|
+
cd build-compat
|
|
57
|
+
emcmake cmake .. -DWLLAMA_COMPAT=ON -DLLAMA_WASM_MEM64=OFF -DGGML_WEBGPU=ON -DGGML_WEBGPU_JSPI=OFF -DEMDAWNWEBGPU_DIR="$${EMDAWNWEBGPU_DIR}" $${CMAKE_EXTRA_FLAGS}
|
|
58
|
+
emmake make wllama -j
|
|
59
|
+
|
|
60
|
+
# go back to root
|
|
61
|
+
cd ..
|
|
62
|
+
|
|
63
|
+
# copy output files (compat build)
|
|
64
|
+
mkdir -p compat/wasm
|
|
65
|
+
cp build-compat/wllama.js compat/wasm
|
|
66
|
+
cp build-compat/wllama.wasm compat/wasm
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# generate source map
|
|
70
|
+
node scripts/build_source_map.js
|
|
71
|
+
|
|
72
|
+
# list files (optional, for verification)
|
|
73
|
+
ls -lh build
|
|
74
|
+
if [ -d build-compat ]; then
|
|
75
|
+
ls -lh build-compat
|
|
76
|
+
fi
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
5
|
+
const outputPath = './src/wasm-from-cdn.ts';
|
|
6
|
+
|
|
7
|
+
const version = packageJson.version;
|
|
8
|
+
const outputContent = `
|
|
9
|
+
// This file is generated by scripts/generate_wasm_from_cdn.js
|
|
10
|
+
// Do not edit this file directly
|
|
11
|
+
|
|
12
|
+
const WasmFromCDN = {
|
|
13
|
+
default: 'https://cdn.jsdelivr.net/npm/@wllama/wllama@${version}/src/wasm/wllama.wasm',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const WasmCompatFromCDN = {
|
|
17
|
+
worker: 'https://cdn.jsdelivr.net/npm/@wllama/wllama-compat@${version}/wasm/wllama.js',
|
|
18
|
+
wasm: 'https://cdn.jsdelivr.net/npm/@wllama/wllama-compat@${version}/wasm/wllama.wasm',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default WasmFromCDN;
|
|
22
|
+
`.trim();
|
|
23
|
+
|
|
24
|
+
fs.writeFileSync(outputPath, outputContent);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import mime from 'mime-types';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
|
|
11
|
+
// Serve static files
|
|
12
|
+
app.use(express.static(path.join(__dirname, '..'), {
|
|
13
|
+
setHeaders: (res) => {
|
|
14
|
+
if (process.env.MULTITHREAD) {
|
|
15
|
+
// add required security header to enable SharedArrayBuffer, needed to run multithread
|
|
16
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements
|
|
17
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
18
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
19
|
+
}
|
|
20
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
21
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
22
|
+
res.setHeader('Pragma', 'no-cache');
|
|
23
|
+
res.setHeader('Expires', '0');
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
if (!process.env.MULTITHREAD) {
|
|
28
|
+
console.log('WARN: Running server without MULTITHREAD=1, this will effectively disable multithreading');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
app.get("/", (req, res) => {
|
|
32
|
+
res.send(`
|
|
33
|
+
Examples:<br/>
|
|
34
|
+
<a href="/examples/basic">Basic</a><br/>
|
|
35
|
+
<a href="/examples/embeddings">Embeddings</a><br/>
|
|
36
|
+
<a href="/examples/multimodal">Multimodal</a><br/>
|
|
37
|
+
<a href="/examples/tools">Tools</a><br/>
|
|
38
|
+
<a href="/examples/test-backend-ops">Test Backend Ops (FOR DEBUGGING ONLY)</a><br/>
|
|
39
|
+
`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Start the server
|
|
43
|
+
const PORT = 8080;
|
|
44
|
+
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
6
|
+
cd $CURRENT_PATH/..
|
|
7
|
+
|
|
8
|
+
mkdir -p esm/wasm
|
|
9
|
+
cp src/wasm/wllama.wasm esm/wasm
|
|
10
|
+
|
|
11
|
+
# https://stackoverflow.com/questions/62619058/appending-js-extension-on-relative-import-statements-during-typescript-compilat
|
|
12
|
+
|
|
13
|
+
function patch_esm_import_js {
|
|
14
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
15
|
+
find $1 -name "*.js" -exec sed -i '' -E "s#export (.*) from '\.(.*)';#export \1 from '.\2\.js';#g" {} +;
|
|
16
|
+
find $1 -name "*.js" -exec sed -i '' -E "s#import (.*) from '\.(.*)';#import \1 from '.\2\.js';#g" {} +;
|
|
17
|
+
else
|
|
18
|
+
find $1 -name "*.js" -exec sed -i -E "s#export (.*) from '\.(.*)';#export \1 from '.\2\.js';#g" {} +;
|
|
19
|
+
find $1 -name "*.js" -exec sed -i -E "s#import (.*) from '\.(.*)';#import \1 from '.\2\.js';#g" {} +;
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
patch_esm_import_js "./esm"
|
|
24
|
+
|
|
25
|
+
# sync compat/package.json version with root package.json
|
|
26
|
+
node -e "
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
|
|
29
|
+
const compat = JSON.parse(fs.readFileSync('compat/package.json', 'utf8'));
|
|
30
|
+
compat.version = version;
|
|
31
|
+
fs.writeFileSync('compat/package.json', JSON.stringify(compat, null, 2) + '\n');
|
|
32
|
+
"
|