diskformer 0.5.1 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 (2026-10-14)
4
+ - `diskformer/llama` works when imported from a CDN: a `Llama` on another origin starts its worker from a same-origin
5
+ stub that imports `worker.mjs`, because browsers refuse a cross-origin worker URL. Checked from a localhost page
6
+ importing only from jsDelivr: download, load and a chat reply from Granite 4.0 H Tiny.
7
+ - README: a quick start that runs as pasted; the llama.cpp path leads.
8
+ - docs/coverage.md: diffusion MoE models are not supported. Paging is exact for them, but each denoising step reads
9
+ nearly all experts (about 3.9 GB per step for LLaDA-MoE 7B-A1B); llama.cpp's WebGPU output for it degrades (open).
10
+
3
11
  ## 0.5.1 (2026-10-14)
4
12
  - The browser build (`diskformer/llama`) is rebuilt on the fork after merging upstream llama.cpp (fork `377491df7`, 167
5
13
  upstream commits including the MUL_MAT_ID rewrite #30451); checksums in `ggml/dist/BUILD.md`. Every gated model was
package/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  <h1 align="center">diskformer.js</h1>
2
2
 
3
- **Run a model larger than your GPU memory in a browser tab.** The weights stay on disk in the browser's private
4
- storage (OPFS); WebGPU holds only what each step needs. A disk tier for in-browser inference, with two reference
5
- engines that use it.
3
+ **Run a mixture-of-experts model larger than your GPU memory in a browser tab, from one import.** llama.cpp runs on
4
+ WebGPU in a worker; the GGUF stays on disk in the browser's private storage (OPFS), and only the experts each token
5
+ needs are paged into GPU memory. 16 architectures are checked against llama.cpp, from Qwen3.6 35B to gpt-oss 20B.
6
6
 
7
- Plain ES modules, no dependencies, no build step. Chromium with WebGPU. Weights stay in the user's own browser storage.
7
+ Plain ES modules, no dependencies, no build step. Chrome or Edge with WebGPU, on any origin (CDN or self-hosted).
8
+ Weights stay in the user's own browser storage. The disk tier underneath (`src/`) also runs two hand-written engines.
8
9
 
9
10
  [![npm](https://img.shields.io/npm/v/diskformer?style=flat-square&color=555)](https://www.npmjs.com/package/diskformer)
10
11
  [![license](https://img.shields.io/badge/license-MIT-555?style=flat-square)](LICENSE)
@@ -17,9 +18,27 @@ Plain ES modules, no dependencies, no build step. Chromium with WebGPU. Weights
17
18
  | How | Command |
18
19
  |---|---|
19
20
  | npm | `npm install diskformer` |
20
- | CDN | `import { Llama } from 'https://cdn.jsdelivr.net/npm/diskformer@0.5/ggml/llama.js'` |
21
+ | CDN | `import { Llama } from 'https://cdn.jsdelivr.net/npm/diskformer@0.5.2/ggml/llama.js'` |
21
22
  | Copy | `src/`, `engines/` and `index.js`; nothing else is needed |
22
23
 
24
+ ## Quick start
25
+
26
+ Paste into any page served over http(s), in Chrome or Edge. The first run downloads the 4.2 GB GGUF from Hugging Face into
27
+ OPFS (resumable); later loads take seconds. Any MoE GGUF on a [checked architecture](docs/coverage.md) loads the same way.
28
+ ```html
29
+ <script type="module">
30
+ import { Llama } from 'https://cdn.jsdelivr.net/npm/diskformer@0.5.2/ggml/llama.js';
31
+ const llama = new Llama();
32
+ const name = 'granite-4.0-h-tiny-Q4_K_M.gguf';
33
+ await llama.download({ name, size: 4230976352,
34
+ url: `https://huggingface.co/ibm-granite/granite-4.0-h-tiny-GGUF/resolve/main/${name}` });
35
+ await llama.load({ name, budget: 3e9 }); // GPU memory in bytes; the rest pages in from OPFS
36
+ const { text } = await llama.chat([{ role: 'user', content: 'Name three primary colours.' }]);
37
+ console.log(text);
38
+ </script>
39
+ ```
40
+ `download` takes a progress callback, `chat` an `onPiece` callback for streaming, and `abort()` stops either.
41
+
23
42
  ## Try it: an 80B model (48 GB file) in under 5 GB of GPU memory
24
43
 
25
44
  https://github.com/user-attachments/assets/4a23b43e-995a-4ba8-b1df-5ffae0222b4e
@@ -37,45 +56,29 @@ once. Measured in Chrome on a MacBook Pro M4 Pro, 24 GB, 2026-10-13 (the recordi
37
56
  16 architectures pass both checks (paging exact natively, then Chrome against llama.cpp): Gemma 4, Qwen3, Qwen3-Next, Qwen3.6,
38
57
  Qwen3-VL, GLM-4.7-Flash, Nemotron 3, ERNIE 4.5, gpt-oss, North, LFM2/2.5, Mellum2, Ling 2.0/3.0, Granite 4.0 H, OLMoE. [docs/coverage.md](docs/coverage.md).
39
58
 
40
- The hand-written engines, measured on a MacBook M4 Pro with 24 GB, Chrome 154, on 2026-10-05:
41
-
42
- | Model | On disk | GPU budget | GPU memory used | Decode | Output |
43
- |---|---|---|---|---|---|
44
- | Gemma 4 26B-A4B, QAT Q4_0 | 14.4 GB | 2.5 GB | 2.02 GB | 12.4 tok/s | 9/9 replies identical to llama.cpp |
45
- | Gemma 4 26B-A4B, QAT Q4_0 | 14.4 GB | 4.3 GB | 3.93 GB | 14.2 tok/s | 9/9 |
46
- | Gemma 4 26B-A4B, QAT Q4_0 | 14.4 GB | 7 GB | 6.63 GB | 17.9 tok/s | 9/9 |
47
- | Qwen3.6 35B-A3B, Q8_0 | 36.9 GB | 4 GB | 3.52 GB | 9.2 tok/s | not compared |
48
-
49
- "Identical": the reply matches llama.cpp b9830 Metal character for character on the same GGUF (9 conversations, greedy,
50
- 64 tokens; Qwen3.6 does not fit Metal here). Each row is `node examples/run-demo.mjs chat --budget <GB>`, decode the
51
- median. Only Apple silicon is tested; discrete GPUs and 8–16 GB machines are not.
52
-
53
- In your own page (the GGUF downloads into OPFS once; later loads take seconds). The hand-written engines run at
54
- [/classic/](https://diskformer.naklitechie.com/classic/) and load the same way: `Gemma4MoeSsd.load(null, { gpuBudgetBytes })`.
55
- ```js
56
- import { Llama } from 'diskformer/llama'; // llama.cpp path, any MoE GGUF on a gated architecture
57
- const llama = new Llama();
58
- await llama.download({ url, name: 'model.gguf', size }); // resumable; a GGUF on disk works with a local URL
59
- await llama.load({ name: 'model.gguf', budget: 4e9 }); // GPU memory in bytes; the experts page in from OPFS
60
- const { text } = await llama.chat([{ role: 'user', content: 'Hello' }], { onPiece: show });
61
- ```
62
-
63
59
  ## Why
64
60
 
65
61
  Your model does not fit on the GPU, or fits only by crowding out everything else. Browser engines load every weight
66
- at load time, though a mixture-of-experts model uses under a tenth of its weights per token.
67
-
68
- diskformer keeps those weights in OPFS, read through sync access handles in workers. Only what each step needs
69
- moves into a GPU cache. The core is the store and the residency layer: your engine keeps its own kernels and decides
70
- what a step needs. `engines/` shows two complete engines built on it.
62
+ at load time, though a mixture-of-experts model uses under a tenth of its weights per token. diskformer keeps those
63
+ weights in OPFS, read through sync access handles in workers, and moves only what each step needs into a GPU cache.
71
64
 
72
65
  ## The engines
73
66
 
74
- `engines/gemma4_moe_ssd.js` runs Gemma 4 26B-A4B. `engines/qwen35_moe_ssd.js` runs Qwen3.6 35B-A3B, and
75
- `engines/qwen3_moe_ssd.js` runs Qwen3-30B-A3B as their base. They are from-scratch WebGPU engines. The dense layers and
76
- KV cache live on the GPU. The experts stream from OPFS into a GPU cache, sized by `gpuBudgetBytes`. A budget that
77
- cannot hold the dense part and two tokens' experts is refused with the numbers. Decoding is greedy, one `generate()`
78
- at a time per engine. Kernels: gemma4-webgpu (Apache-2.0, `engines/NOTICE`); files kept in step with LocalMind.
67
+ The disk tier also stands alone: your engine keeps its own kernels and decides what a step needs. `engines/` holds three
68
+ from-scratch WebGPU engines on it: Gemma 4 26B-A4B, Qwen3.6 35B-A3B and Qwen3-30B-A3B (at
69
+ [/classic/](https://diskformer.naklitechie.com/classic/); `Gemma4MoeSsd.load(null, { gpuBudgetBytes })`). A budget that
70
+ cannot hold the dense part and two tokens' experts is refused with the numbers. Kernels: gemma4-webgpu (Apache-2.0,
71
+ `engines/NOTICE`). Measured on a MacBook M4 Pro, 24 GB, Chrome 154, 2026-10-05, decode the median of
72
+ `node examples/run-demo.mjs chat --budget <GB>`:
73
+
74
+ | Model | On disk | GPU memory used | Decode | Output |
75
+ |---|---|---|---|---|
76
+ | Gemma 4 26B-A4B, QAT Q4_0 | 14.4 GB | 2.02 GB (budget 2.5) | 12.4 tok/s | 9/9 replies identical to llama.cpp b9830 Metal |
77
+ | Gemma 4 26B-A4B, QAT Q4_0 | 14.4 GB | 6.63 GB (budget 7) | 17.9 tok/s | 9/9 |
78
+ | Qwen3.6 35B-A3B, Q8_0 | 36.9 GB | 3.52 GB (budget 4) | 9.2 tok/s | not compared (does not fit Metal here) |
79
+
80
+ "Identical": character for character on the same GGUF, 9 conversations, greedy, 64 tokens. Only Apple silicon is
81
+ tested; discrete GPUs and 8–16 GB machines are not.
79
82
 
80
83
  ## A table on disk
81
84
 
@@ -94,9 +97,7 @@ through a mapped staging ring.
94
97
 
95
98
  `listStores()` lists the stored models with size and state; a store with zero-byte or short files (a crash) is
96
99
  `invalid` and is ingested again, never loaded. `removeStore(key)` removes one and keeps the rest. Errors carry a `code`
97
- (`LOAD_ABORTED`, `BUDGET_TOO_SMALL`, … in `src/errors.js`), checked with `isDiskformerError(e, code)`.
98
-
99
- `measure({ path, recordBytes })` reports sequential MB/s and random record reads; OPFS speed varies by machine.
100
+ (`LOAD_ABORTED`, `BUDGET_TOO_SMALL`, … in `src/errors.js`), checked with `isDiskformerError(e, code)`. `measure({ path, recordBytes })` reports OPFS read speed.
100
101
 
101
102
  ## Commands
102
103
 
@@ -105,9 +106,7 @@ npm test # Node unit tests: record pool, row
105
106
  node examples/run-demo.mjs rows # headless Chrome: a 64 MB table on disk, 2,048 rows on the GPU, every byte checked
106
107
  node examples/run-demo.mjs embedding # lookup → slots → a WGSL gather kernel, every output value checked
107
108
  node examples/run-demo.mjs chat --budget 2.5 # downloads Gemma once (14.4 GB) to a fixed profile, replays 9 llama.cpp replies, fails on any difference
108
- node examples/run-demo.mjs test/browser/ingest-resume.html --models <dir> --file <gguf> # abort, resume, compare bytes
109
- node examples/run-demo.mjs test/browser/greedy.html --models <dir> --file <gguf> --ref test/refs/<ref>.json # token by token vs llama.cpp
110
- node examples/run-demo.mjs test/browser/states.html --models <dir> --file g4-6l.gguf # per-layer states; then test/state-check.py
109
+ node examples/run-demo.mjs test/browser/<page>.html --models <dir> --file <gguf> # ingest resume, token-by-token, per-layer checks
111
110
  npm run sync # the files shared with LocalMind match their sources
112
111
  ```
113
112
 
package/ggml/llama.js CHANGED
@@ -9,9 +9,18 @@
9
9
  //
10
10
  // The worker's messages are documented at the top of ggml/worker.mjs; this file only gives them promises.
11
11
 
12
+ // The worker script, started from a blob when it lives on another origin (a CDN): browsers refuse a cross-origin
13
+ // worker URL but let a module worker import a cross-origin module that allows CORS.
14
+ function startWorker() {
15
+ const url = new URL('./worker.mjs', import.meta.url);
16
+ if (url.origin === location.origin) return new Worker(url, { type: 'module' });
17
+ const stub = URL.createObjectURL(new Blob([`import ${JSON.stringify(url.href)};`], { type: 'text/javascript' }));
18
+ return new Worker(stub, { type: 'module' });
19
+ }
20
+
12
21
  export class Llama {
13
22
  constructor({ worker } = {}) {
14
- this.worker = worker || new Worker(new URL('./worker.mjs', import.meta.url), { type: 'module' });
23
+ this.worker = worker || startWorker();
15
24
  this.busy = Promise.resolve();
16
25
  this.onLog = null; // (line) => {}: llama.cpp's log
17
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diskformer",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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",