anyformer 0.4.10 → 0.4.11
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 +26 -2
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/backends/diskformer-llama.js +30 -10
- package/src/models.js +6 -0
- package/src/sources.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.11 — 2026-10-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `MODELS.chat.DEEPSEEK_V4_1_FLASH`: DeepSeek V4.1 Flash (763B MoE, ISTA-DASLab GSQ-RCO IQ1_M) on `diskformer-llama`,
|
|
7
|
+
budget 8 GB, `experimental: true`, so LocalMind shows it as an opt-in tech demo (Chirag's approval, relayed by the
|
|
8
|
+
LocalMind session). 231,316,411,072 bytes on disk: 4 parts plus the 110.6 GB Engram file. Very slow: diskformer
|
|
9
|
+
measured about 0.4 tok/s natively from a USB SSD.
|
|
10
|
+
- `diskformer-llama`: side files (`SIDE_FILES`). A model's side file downloads with its parts under its own name,
|
|
11
|
+
counts toward the disk bytes, goes to llama.cpp as `load({ extra })`, and is never picked as the model. The
|
|
12
|
+
`deepseek41-engram` architecture is accepted for the demo file only; another quant in the repo is refused.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- diskformer pinned to v0.6.9 (53c976e), which adds `load({ extra })`. diskformer reports Granite and Bolt 9/9 on it.
|
|
16
|
+
|
|
17
|
+
### Verified
|
|
18
|
+
- npm test 92/92 (fixture of the repo at cb02422c: the IQ1_M set with its side file, no Engram file alone, a missing
|
|
19
|
+
side file refuses, `extra` reaches the fake llama.js). `plan()` against the live Hub picks `diskformer-llama` at 8 GB
|
|
20
|
+
with 231,316,411,072 disk bytes.
|
|
21
|
+
- Not run, on purpose (Chirag: no live runs or benchmarks of this model): any browser load through anyformer.
|
|
22
|
+
diskformer checked one hand-written prompt through `complete()` in Chrome; the chat-template path that `generate()`
|
|
23
|
+
uses, and so LocalMind's chat, has not run in any browser.
|
|
24
|
+
|
|
3
25
|
## 0.4.10 — 2026-10-24
|
|
4
26
|
|
|
5
27
|
### Changed
|
|
@@ -9,8 +31,10 @@
|
|
|
9
31
|
|
|
10
32
|
### Verified
|
|
11
33
|
- npm test 90/90. kohra's own Chrome check on v0.3.1 (kohra session): the first Nemotron load cached all 3 files with
|
|
12
|
-
their content-length; a reload was ready in 7.4 s with no graph fetches; output unchanged. anyformer
|
|
13
|
-
|
|
34
|
+
their content-length; a reload was ready in 7.4 s with no graph fetches; output unchanged. Through anyformer
|
|
35
|
+
(`npm run test:browser -- kohra-cache`, Chrome, GPU shared, after the npm publish): the first load sent 35,416
|
|
36
|
+
progress events to `onProgress` and answered on topic in 200 s; `stored()` listed 2,735,424,112 bytes under kohra;
|
|
37
|
+
the second load took 5.1 s with the same reply; `remove()` freed 2,752,678,776 bytes (graph and tokenizer).
|
|
14
38
|
|
|
15
39
|
## 0.4.9 — 2026-10-24
|
|
16
40
|
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ browser storage; no account, no server, no telemetry.
|
|
|
14
14
|
| How | Command |
|
|
15
15
|
|---|---|
|
|
16
16
|
| npm | `npm install anyformer` |
|
|
17
|
-
| CDN | `import { load } from 'https://cdn.jsdelivr.net/npm/anyformer@0.4.
|
|
17
|
+
| CDN | `import { load } from 'https://cdn.jsdelivr.net/npm/anyformer@0.4.11/+esm';` |
|
|
18
18
|
| Copy | `index.js` and `src/`; nothing else is needed |
|
|
19
19
|
|
|
20
20
|
```js
|
|
@@ -59,7 +59,7 @@ back end's answer: yes with an estimate (GPU bytes, disk bytes, tok/s when measu
|
|
|
59
59
|
| `localmind` | LocalMind's hand-written WebGPU kernels (shipped in diskformer), the whole model on the GPU | any LFM2 / LFM2.5 GGUF; Gemma 4 E2B / E4B QAT-mobile; Ternary Bonsai 2 |
|
|
60
60
|
| `wllama` | llama.cpp in WebAssembly: WebGPU when the budget holds the model, the CPU otherwise (no WebGPU needed) | any GGUF up to about 3.6 GB that no engine above covers; files over 2 GB are split into OPFS shards once |
|
|
61
61
|
| `diskformer` | diskformer engines: weights in OPFS, only what a step needs on the GPU | Gemma 4 26B-A4B (from 2.5 GB), Qwen3.6 35B-A3B (from 4 GB), Ling 3.0 Flash 124B (from 6.8 GB), MiniCPM-V 4.7 with images, Flint 40B, LFM2.5 24B (from 4 GB) |
|
|
62
|
-
| `diskformer-llama` | diskformer's llama.cpp build with WebGPU, mixture-of-experts layers paged in from OPFS | GGUFs of the 17 MoE architectures diskformer has checked (Granite 4.0 H, gpt-oss, GLM-4.7-Flash, LFM2 MoE…), split sets included; a hand engine above wins where it covers the model |
|
|
62
|
+
| `diskformer-llama` | diskformer's llama.cpp build with WebGPU, mixture-of-experts layers paged in from OPFS | GGUFs of the 17 MoE architectures diskformer has checked (Granite 4.0 H, gpt-oss, GLM-4.7-Flash, LFM2 MoE…), split sets included; a hand engine above wins where it covers the model; DeepSeek V4.1 Flash as an experimental tech demo (231 GB, side file included) |
|
|
63
63
|
|
|
64
64
|
Each runtime loads from an exact version or commit in [src/sources.js](src/sources.js); set a back end's `.source`
|
|
65
65
|
(for example `kohraBackend.source`) to load another copy. `BACKENDS` lists the back ends `load()` asks by default: each joins after its browser test passes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anyformer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "One call to run a model in a browser tab: anyformer reads the Hub repo, probes the browser, and picks the back end (transformers.js, LocalMind's WebGPU kernels, diskformer, kohra) that fits the model's format, architecture and your GPU budget.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -35,11 +35,29 @@ export const CHECKED_FILES = [
|
|
|
35
35
|
|
|
36
36
|
const SPLIT = /-(\d{5})-of-(\d{5})\.gguf$/i;
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
38
|
+
// Side files a model opens beside its GGUF (llama.load({ extra }), diskformer v0.6.9+), keyed by the GGUF's file name:
|
|
39
|
+
// they download with its parts, count toward its disk bytes, and are never picked as the model itself. DeepSeek V4.1
|
|
40
|
+
// Flash is a tech demo in diskformer (docs/coverage.md: one prompt identical to native in Chrome, about 0.4 tok/s, the
|
|
41
|
+
// chat template not checked), so its architecture is accepted for this file only, not added to LLAMA_ARCHITECTURES.
|
|
42
|
+
export const SIDE_FILES = {
|
|
43
|
+
'DeepSeek-V4.1-Flash-1017-GSQ-RCO-IQ1_M-00001-of-00004.gguf': { architecture: 'deepseek41-engram', files: ['DeepSeek-V4.1-Flash-Engram-IQ4_NL.gguf'] },
|
|
44
|
+
};
|
|
45
|
+
const SIDE_NAMES = new Set(Object.values(SIDE_FILES).flatMap((s) => s.files));
|
|
46
|
+
|
|
47
|
+
// The GGUF to load, as { name, size, parts: [{ name, size }], extra: [{ name, size }] }: the language model (no mmproj
|
|
48
|
+
// vision tower), one file or every part of a split set (name-00001-of-0000n.gguf, named by its first part), and its
|
|
49
|
+
// side files (SIDE_FILES); size is the sum of all of them. The caller's file when given; else the file diskformer
|
|
50
|
+
// checked; else the preferred quant, a single file before a split set. A model missing a side file cannot load.
|
|
41
51
|
export function pickGguf(model, file = null) {
|
|
42
|
-
const lms = model.files.gguf.filter((f) => !/mmproj/i.test(f.name));
|
|
52
|
+
const lms = model.files.gguf.filter((f) => !/mmproj/i.test(f.name) && !SIDE_NAMES.has(opfsName(f)));
|
|
53
|
+
const f = pickModel(lms, file);
|
|
54
|
+
if (!f) return null;
|
|
55
|
+
const extra = (SIDE_FILES[opfsName(f)]?.files || []).map((n) => model.files.gguf.find((x) => opfsName(x) === n));
|
|
56
|
+
if (extra.some((x) => !x)) return null;
|
|
57
|
+
return { ...f, size: f.size + extra.reduce((n, x) => n + x.size, 0), extra };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pickModel(lms, file) {
|
|
43
61
|
const sets = new Map();
|
|
44
62
|
for (const f of lms.filter((x) => SPLIT.test(x.name))) {
|
|
45
63
|
const base = f.name.replace(SPLIT, '');
|
|
@@ -54,7 +72,7 @@ export function pickGguf(model, file = null) {
|
|
|
54
72
|
}),
|
|
55
73
|
];
|
|
56
74
|
if (file) return candidates.find((f) => f.name === file) || null;
|
|
57
|
-
const checked = candidates.find((f) => CHECKED_FILES.includes(opfsName(f)));
|
|
75
|
+
const checked = candidates.find((f) => CHECKED_FILES.includes(opfsName(f)) || SIDE_FILES[opfsName(f)]);
|
|
58
76
|
if (checked) return checked;
|
|
59
77
|
for (const q of QUANTS) {
|
|
60
78
|
const f = candidates.find((x) => new RegExp(`(^|[-_.])${q}([-_.]|$)`, 'i').test(x.name.replace(SPLIT, '').replace(/\.gguf$/i, '')));
|
|
@@ -101,8 +119,9 @@ export const diskformerLlamaBackend = {
|
|
|
101
119
|
canRun(model, caps, { budget, task, file = null }) {
|
|
102
120
|
if (!model.formats.includes('gguf')) return { ok: false, code: 'format', reason: 'no GGUF in the repo' };
|
|
103
121
|
if (task && !diskformerLlamaBackend.tasks.includes(task)) return { ok: false, code: 'task', reason: `task ${task}; diskformer's llama.cpp path runs text generation only` };
|
|
104
|
-
if (!LLAMA_ARCHITECTURES.includes(model.architecture)) return { ok: false, code: 'model', reason: `architecture ${model.architecture || 'unknown'} is not one diskformer's llama.cpp path has checked (${LLAMA_ARCHITECTURES.join(', ')})` };
|
|
105
122
|
const f = pickGguf(model, file);
|
|
123
|
+
const demo = f && SIDE_FILES[opfsName(f)]?.architecture === model.architecture;
|
|
124
|
+
if (!LLAMA_ARCHITECTURES.includes(model.architecture) && !demo) return { ok: false, code: 'model', reason: `architecture ${model.architecture || 'unknown'} is not one diskformer's llama.cpp path has checked (${LLAMA_ARCHITECTURES.join(', ')})` };
|
|
106
125
|
if (!f) return { ok: false, code: 'model', reason: file ? `${file} is not a language-model GGUF in the repo (a split set is named by its first part, with every part present)` : `no GGUF at ${QUANTS.join(', ')} in the repo (a split set needs every part)` };
|
|
107
126
|
if (!caps.webgpu) return { ok: false, code: 'webgpu', reason: 'no WebGPU adapter' };
|
|
108
127
|
if (!caps.memory64 || !caps.jspi) return { ok: false, code: 'webgpu', reason: 'the llama.cpp build needs wasm memory64 and JSPI (Chrome or Edge 137+)' };
|
|
@@ -121,9 +140,10 @@ export const diskformerLlamaBackend = {
|
|
|
121
140
|
signal?.addEventListener('abort', stop, { once: true });
|
|
122
141
|
let info;
|
|
123
142
|
try {
|
|
124
|
-
// Each part downloads under its own name; progress counts bytes over
|
|
143
|
+
// Each part and side file downloads under its own name; progress counts bytes over all of them.
|
|
144
|
+
const files = [...f.parts, ...f.extra];
|
|
125
145
|
let before = 0;
|
|
126
|
-
for (const part of
|
|
146
|
+
for (const part of files) {
|
|
127
147
|
const name = opfsName(part);
|
|
128
148
|
const have = (await llama.list()).find((m) => m.name === name && !m.partial);
|
|
129
149
|
if (!have || have.size !== part.size) {
|
|
@@ -134,9 +154,9 @@ export const diskformerLlamaBackend = {
|
|
|
134
154
|
}
|
|
135
155
|
before += part.size;
|
|
136
156
|
}
|
|
137
|
-
await writeIndex({ ...(await readIndex()), ...Object.fromEntries(
|
|
157
|
+
await writeIndex({ ...(await readIndex()), ...Object.fromEntries(files.map((p) => [opfsName(p), model.repo])) });
|
|
138
158
|
say({ status: 'load' });
|
|
139
|
-
info = await llama.load({ name: opfsName(f), budget, ctx: maxCtx }, (p) => say({ status: 'plan', plan: p }));
|
|
159
|
+
info = await llama.load({ name: opfsName(f), budget, ctx: maxCtx, extra: f.extra.map(opfsName) }, (p) => say({ status: 'plan', plan: p }));
|
|
140
160
|
signal?.throwIfAborted();
|
|
141
161
|
} catch (e) {
|
|
142
162
|
llama.terminate();
|
package/src/models.js
CHANGED
|
@@ -21,6 +21,10 @@ const QWEN3_0_6B_MDLM = M('naklitechie/Qwen3-0.6B-diffusion-mdlm-ONNX', 'kohra',
|
|
|
21
21
|
const NEMOTRON_DIFFUSION_3B = M('naklitechie/Nemotron-Labs-Diffusion-3B-ONNX', 'kohra', 'text-generation', 'kohra', { downloadBytes: 2.77e9, diffusion: true, experimental: true });
|
|
22
22
|
const GEMMA4_26B_A4B = M('google/gemma-4-26B-A4B-it-qat-q4_0-gguf', 'diskformer', 'text-generation', 'diskformer-chat', { budget: 2.5e9, downloadBytes: 14.44e9 });
|
|
23
23
|
const NEMOTRON_3_5_BOLT_E4B = M('danyalmalik/NVIDIA-Nemotron-3.5-Bolt-E4B-ONNX-WebGPU', 'transformers', 'text-generation', 'transformers', { budget: 5e9, downloadBytes: 4.94e9 });
|
|
24
|
+
// A tech demo, not a gated model: 231 GB on disk (4 IQ1_M parts plus the 110.6 GB Engram side file), about 0.4 tok/s.
|
|
25
|
+
// diskformer checked one hand-written prompt through complete(); generate() renders the GGUF's chat template, which no
|
|
26
|
+
// browser run has checked.
|
|
27
|
+
const DEEPSEEK_V4_1_FLASH = M('ISTA-DASLab/DeepSeek-V4.1-Flash-1017-GSQ-RCO-GGUF', 'diskformer-llama', 'text-generation', 'llama-gate', { budget: 8e9, downloadBytes: 231316411072, experimental: true });
|
|
24
28
|
const MINILM_L6 = M('Xenova/all-MiniLM-L6-v2', 'transformers', 'feature-extraction', 'embed', { downloadBytes: 90e6 });
|
|
25
29
|
const DETR_RESNET_50 = M('Xenova/detr-resnet-50', 'transformers', 'object-detection', 'detect', { downloadBytes: 167e6 });
|
|
26
30
|
|
|
@@ -50,6 +54,8 @@ export const MODELS = {
|
|
|
50
54
|
GEMMA4_26B_A4B,
|
|
51
55
|
/** NVIDIA's Nemotron 3.5 Bolt E4B (dense, text only), a community q4f16 ONNX export in Gemma 4 E4B's graph. */
|
|
52
56
|
NEMOTRON_3_5_BOLT_E4B,
|
|
57
|
+
/** DeepSeek V4.1 Flash (763B MoE), a tech demo on diskformer's llama.cpp path: 231 GB on disk, very slow. */
|
|
58
|
+
DEEPSEEK_V4_1_FLASH,
|
|
53
59
|
},
|
|
54
60
|
/** Chat with images in the messages: content [{ type: 'image', image }, { type: 'text', text }]. Gemma 4 takes audio too. */
|
|
55
61
|
vision: { DEFAULT: QWEN3_5_4B, QWEN3_5_4B, GEMMA4_E2B_ONNX, GEMMA4_E4B_ONNX },
|
package/src/sources.js
CHANGED
|
@@ -9,11 +9,12 @@ export const PINS = {
|
|
|
9
9
|
// v0.3.1: seeded block-KV graphs (Nemotron-Labs-Diffusion-3B), chatFormat, split external data; download progress
|
|
10
10
|
// (progress_callback) and the graph files cached in Cache Storage 'kohra-models'.
|
|
11
11
|
kohra: 'bf95d44c3e3cfc001201baa03fa121c2bf9c7d73',
|
|
12
|
+
// v0.6.9: llama.load({ extra }) mounts side files beside the GGUF (DeepSeek V4.1 Flash's Engram tables).
|
|
12
13
|
// v0.6.8: every engine (the disk-tier MoE engines, Ling 3.0 Flash, and LocalMind's whole-model engines LFM2.5,
|
|
13
14
|
// Gemma 4 E2B/E4B and Ternary Bonsai 2) under engines/, and the llama.cpp path under ggml/, which loads split GGUFs
|
|
14
15
|
// and drafters, and reads past 2 GiB (Nemotron 3.5 Bolt E4B needs v0.6.8 or later). The hand engines' ingest
|
|
15
16
|
// streams the GGUF header with progress and resumes a dropped range.
|
|
16
|
-
diskformer: '
|
|
17
|
+
diskformer: '53c976e43d34ac2d2601a731124de81cb07fdab4',
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const gh = (repo, sha, path) => `https://cdn.jsdelivr.net/gh/NakliTechie/${repo}@${sha}/${path}`;
|