diskformer 0.5.4 → 0.6.6

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/ggml/worker.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // sync access handles, straight into the wasm heap. A GGUF gets into OPFS by download (resumable, from a URL) or by
4
4
  // copying a picked file; with opfs: false a picked File is read in place instead (slower).
5
5
  //
6
- // Messages in: load {opfsName | file, budget | slots, ctx, env} · chat {messages, n} · complete {prompt, n} (the gate)
6
+ // Messages in: load {opfsName | file, budget | slots, ctx, env} · chat {messages, n, thinking} · complete {prompt, n} (the gate)
7
7
  // · abort · download {url, name, size} · list · remove {name}
8
8
  // Messages out: log · progress · plan · loaded · piece · done · downloaded · models · removed · error
9
9
  import createModule from './dist/df-chat.mjs'; // built from ggml/build-wasm, see ggml/dist/BUILD.md
@@ -153,24 +153,42 @@ onmessage = async ({ data }) => {
153
153
  // settings for llama.cpp / ggml-webgpu, e.g. { GGML_WEBGPU_PAGED_STATS: '2' }
154
154
  preRun: [(m) => Object.assign(m.ENV, data.env || {})],
155
155
  });
156
- let file = data.file;
157
- let fh = null;
158
- if (!file) {
159
- fh = await (await ggufDir(false)).getFileHandle(data.opfsName); // a GGUF already in OPFS, by name
156
+ // the GGUF's parts: one, or every part of a split GGUF (name-00001-of-0000n.gguf, as published), which llama.cpp
157
+ // finds by name beside the first and the driver reads part by part
158
+ let fhs = [], files = null;
159
+ if (!data.file) {
160
+ const dir = await ggufDir(false);
161
+ const split = /^(.*)-00001-of-(\d{5})\.gguf$/.exec(data.opfsName);
162
+ const names = split ? Array.from({ length: +split[2] }, (_, k) => `${split[1]}-${String(k + 1).padStart(5, '0')}-of-${split[2]}.gguf`) : [data.opfsName];
163
+ for (const name of names) {
164
+ fhs.push(await dir.getFileHandle(name).catch(() => { throw new Error(`${name} is not in OPFS; download every part of ${data.opfsName}`); }));
165
+ }
160
166
  } else if (data.opfs !== false) {
161
- fh = await opfsCopy(data.file);
167
+ fhs = [await opfsCopy(data.file)];
168
+ } else {
169
+ files = [data.file];
162
170
  }
163
- if (fh) {
164
- file = await fh.getFile();
165
- // read-only, so the reader workers can open the same file
166
- mod.ggufHandle = await fh.createSyncAccessHandle({ mode: 'read-only' });
171
+ mod.ggufParts = [];
172
+ if (fhs.length) {
173
+ files = [];
167
174
  // expert reads in parallel, off this thread (DF_READERS=0: on this thread's handle)
168
175
  const n = +((data.env || {}).DF_READERS ?? 8); // 8: measured 10.66 tok/s vs 9.72 at 4, 10.57 at 16
169
- if (n > 0) mod.ggufReaders = await OpfsReaderPool.open(`gguf/${file.name}`, { workers: n });
176
+ for (const fh of fhs) {
177
+ const f = await fh.getFile();
178
+ files.push(f);
179
+ mod.ggufParts.push({
180
+ file: f,
181
+ // read-only, so the reader workers can open the same file
182
+ handle: await fh.createSyncAccessHandle({ mode: 'read-only' }),
183
+ readers: n > 0 ? await OpfsReaderPool.open(`gguf/${f.name}`, { workers: n }) : null,
184
+ });
185
+ }
186
+ } else {
187
+ mod.ggufParts.push({ file: files[0], handle: null, readers: null });
170
188
  }
171
- mod.ggufFile = file;
189
+ const file = files[0];
172
190
  mod.FS.mkdir('/m');
173
- mod.FS.mount(mod.WORKERFS, { files: [file] }, '/m');
191
+ mod.FS.mount(mod.WORKERFS, { files }, '/m');
174
192
  const t0 = performance.now();
175
193
  // a GPU budget (bytes) becomes slots through df_plan; else data.slots as given
176
194
  let slots = data.slots, plan = null;
@@ -181,14 +199,14 @@ onmessage = async ({ data }) => {
181
199
  slots = plan.slots;
182
200
  }
183
201
  const rc = await mod._df_load(str('/m/' + file.name), slots, data.ctx ?? 1024);
184
- post('loaded', { rc, slots, plan, size: file.size, name: file.name, ms: Math.round(performance.now() - t0) });
202
+ post('loaded', { rc, slots, plan, size: files.reduce((a, f) => a + f.size, 0), name: file.name, parts: files.length, ms: Math.round(performance.now() - t0) });
185
203
  } else if (data.type === 'complete') {
186
204
  const reply = await mod._df_complete(str(data.prompt), data.n ?? 64);
187
205
  post('done', { id: data.id, text: text(reply), stats: JSON.parse(text(mod._df_stats())) });
188
206
  } else if (data.type === 'chat') {
189
207
  // messages: the whole conversation; prompt: one user turn (the older single-turn form)
190
208
  const reply = data.messages
191
- ? await mod._df_chat_messages(str(JSON.stringify(data.messages)), data.n ?? 512)
209
+ ? await mod._df_chat_messages(str(JSON.stringify(data.messages)), data.n ?? 512, data.thinking === false ? 0 : 1)
192
210
  : await mod._df_chat(str(data.prompt), data.n ?? 64);
193
211
  post('done', { text: text(reply), stats: JSON.parse(text(mod._df_stats())) });
194
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diskformer",
3
- "version": "0.5.4",
3
+ "version": "0.6.6",
4
4
  "description": "Run a model larger than your GPU memory in a browser tab: weights stay in OPFS, WebGPU holds only what each step needs. llama.cpp with paged MoE experts (diskformer/llama), a disk tier, and hand-written MoE engines.",
5
5
  "type": "module",
6
6
  "license": "MIT",