anyformer 0.4.16 → 0.4.18

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,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.18 — 2026-10-31
4
+
5
+ ### Added
6
+ - LoRA adapters: `load(repo, { adapter: { repo, file, revision, size, scale } })` (or an entry carrying `adapter`)
7
+ downloads the adapter GGUF into OPFS beside the model and applies it at `scale` on `diskformer-llama`, the only back
8
+ end with `adapters: true`; the others drop out of the plan. Needs diskformer v0.6.17.
9
+ - `MODELS.chat.LFM2_5_24B_PI`: LFM2.5-24B-A2B (QAD-Q4_0) with harness-hub's Pi coding-agent LoRA
10
+ (`harness-hub/LFM2.5-24B-A2B-pi-GGUF` at a994cf7, 37.4 MB), 4 GB budget, `experimental`, `license: 'lfm1.0'`.
11
+ diskformer's gate (v0.6.17): 8/9 replies identical to llama-server --lora (7/9 without the adapter, the path's own
12
+ baseline). The adapter was trained inside the Pi agent; elsewhere expect base-model behaviour or worse. Hub fixture
13
+ at 0912d33.
14
+
15
+ ### Changed
16
+ - diskformer pin f17009c → 314362e (v0.6.17, which includes v0.6.16). v0.6.16: Chrome 156 rejects mixed f16/f32 subgroup-matrix kernels on Apple GPUs,
17
+ so Ternary Bonsai 2 27B, Gemma 4 E2B, LFM2.5 and the IQ-quant dense path failed to load ("Mixed precision subgroup
18
+ matrix multiply is not supported by the device"). diskformer's new `engines/subgroup-matrix-guard.js` hides the
19
+ feature when a tiny mixed kernel fails to compile, and those engines take their non-matrix prefill (slower prefill).
20
+ - Browser tests: `chat` takes `?turns=N` (up to 4 follow-up turns, each checked on topic); `vision` takes `?backend=`
21
+ and `?local=1` (MiniCPM-V 4.7 on diskformer, image as packed RGB after `ensureVision()`); `run.mjs` prints each
22
+ failed network request with its reason. `vision?local=1` serves `cats.jpg` from `--models` as well. `chat` also takes
23
+ `?entry=<family>.<KEY>` (a MODELS entry, adapter included) and `?tokens=N`; its `?local=1` serves diskformer-llama
24
+ GGUFs, adapters and (through the engines' fetch) localmind GGUFs by file name.
25
+
26
+ ### Known limitation
27
+ - LFM2.5-24B-A2B's chat template always opens a `<think>` block and ignores `enable_thinking`, so on
28
+ `diskformer-llama` a reply with thinking off still starts with visible reasoning, then `</think>`, then the answer.
29
+ The fix belongs in diskformer's chat driver (close the block when thinking is off).
30
+
31
+ ### Verified
32
+ - `LFM2_5_24B_PI` through anyformer (browser `chat?entry=chat.LFM2_5_24B_PI&local=1&tokens=400`, Chrome 156, this
33
+ Mac, 11:56 IST, base GGUF and adapter from local files, adapter sha256 5e96d059…, diskformer 314362e): loaded with
34
+ the adapter in 208.9 s; reasoning, `</think>`, then a correct two-sentence answer about the CPU; 162 tokens at
35
+ 2.73 tok/s. The page reports FAIL because its check rejects `</think>` text (the limitation above). A 4-turn run at
36
+ 64 tokens per reply spent each reply on reasoning (1.5–3.0 tok/s).
37
+ - MiniCPM-V 4.7 image round trip through anyformer (browser `vision`, Chrome 156, this Mac, 11:12 IST, GGUF, vision
38
+ encoder and image from local files, 6.5 GB budget, diskformer f17009c): "There are **two cats** in this picture",
39
+ then a correct description, 40 tokens at 18.3 tok/s.
40
+
41
+ ## 0.4.17 — 2026-10-30
42
+
43
+ ### Added
44
+ - `MODELS.chat.CLOVER_35B_A3B`: Diffuspace's Clover-35B-A3B-Preview (Apache-2.0), block diffusion on Qwen3.6-35B-A3B,
45
+ text only, on diskformer's `clover_moe_ssd.js` (Q4_0 GGUF, 19.6 GB, from OPFS; 4 GB budget). `diffusion` and
46
+ `experimental`; speed unmeasured. generate() yields once per committed 32-token block. Hub fixture at revision
47
+ 32e22a0. Browser test `chat` passed (Chrome, this Mac, 23:17 IST, GGUF from Models2, 4 GB budget): load 55.6 s
48
+ including the copy into OPFS, an on-topic 25-token reply in 25.4 s.
49
+ - Browser test `chat` takes `?local=1` for a diskformer GGUF served from `--models`.
50
+
51
+ ### Changed
52
+ - diskformer pin 93193d0 → f17009c (v0.6.15: the Clover engine; `qwen35_moe_ssd.js` and `qwen3_moe_ssd.js` only learn
53
+ to accept the `clover_moe` architecture).
54
+
3
55
  ## 0.4.16 — 2026-10-30
4
56
 
5
57
  ### Changed
package/README.md CHANGED
@@ -76,8 +76,8 @@ and `threw`. Every other failure is an `AnyformerError` too, with a code from `E
76
76
  ## Commands
77
77
 
78
78
  ```js
79
- plan(repo, { budget, task }) // what would run it, and why the others would not; loads nothing
80
- load(repo, { budget, task, file, maxCtx, poolBytes, localFile, fetch, ple, speculative, onProgress, signal }) // → { backend, task, plan, run(input), generate?, think?, prime?, reset?, speculative?, dispose() }
79
+ plan(repo, { budget, task }) // what would run it (adapter: a LoRA GGUF; only diskformer-llama applies one), and why the others would not; loads nothing
80
+ load(repo, { budget, task, file, adapter, maxCtx, poolBytes, localFile, fetch, ple, speculative, onProgress, signal }) // → { backend, task, plan, run(input), generate?, think?, prime?, reset?, speculative?, dispose() }
81
81
  pipeline(task, repo, { budget }) // transformers.js's call shape: gen(messages) → [{ generated_text }]
82
82
  probe() // WebGPU, shader-f16, subgroups, limits, OPFS quota, memory64
83
83
  describe(repo) // Hub repo → formats, architecture, task, tags, files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyformer",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
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",
@@ -112,6 +112,8 @@ function textOnly(messages) {
112
112
  export const diskformerLlamaBackend = {
113
113
  id: 'diskformer-llama',
114
114
  tasks: ['text-generation', 'image-text-to-text'],
115
+ // Applies a LoRA GGUF over the model (llama.load({ lora, loraScale }), diskformer v0.6.17+), as llama-server --lora.
116
+ adapters: true,
115
117
  source: SOURCES.diskformerLlama,
116
118
  // Where a GGUF downloads from; a test points it at a local server.
117
119
  url: (model, file) => `${model.endpoint || 'https://huggingface.co'}/${model.repo}/resolve/${model.revision || 'main'}/${file.name.split('/').map(encodeURIComponent).join('/')}`,
@@ -131,7 +133,8 @@ export const diskformerLlamaBackend = {
131
133
  },
132
134
 
133
135
  // maxCtx sizes the KV cache; 1024 is the context diskformer's gates run at.
134
- async load(model, { budget, file = null, maxCtx = 1024, onProgress, signal }) {
136
+ // adapter: { repo, file, revision, size, scale } downloads into OPFS beside the model and applies at scale (default 1).
137
+ async load(model, { budget, file = null, adapter = null, maxCtx = 1024, onProgress, signal }) {
135
138
  const f = pickGguf(model, file);
136
139
  const { Llama } = await import(diskformerLlamaBackend.source);
137
140
  const llama = new Llama();
@@ -140,23 +143,27 @@ export const diskformerLlamaBackend = {
140
143
  signal?.addEventListener('abort', stop, { once: true });
141
144
  let info;
142
145
  try {
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];
146
+ // Each part, side file and adapter downloads under its own name; progress counts bytes over all of them.
147
+ const lora = adapter && { name: adapter.file.split('/').pop(), size: adapter.size, repo: adapter.repo,
148
+ url: diskformerLlamaBackend.url({ repo: adapter.repo, revision: adapter.revision, endpoint: model.endpoint }, { name: adapter.file }) };
149
+ const files = [...f.parts.map((p) => ({ name: opfsName(p), size: p.size, repo: model.repo, url: diskformerLlamaBackend.url(model, p) })),
150
+ ...f.extra.map((p) => ({ name: opfsName(p), size: p.size, repo: model.repo, url: diskformerLlamaBackend.url(model, p) })),
151
+ ...(lora ? [lora] : [])];
152
+ const total = files.reduce((n, x) => n + x.size, 0);
145
153
  let before = 0;
146
- for (const part of files) {
147
- const name = opfsName(part);
154
+ for (const { name, size, url } of files) {
148
155
  const have = (await llama.list()).find((m) => m.name === name && !m.partial);
149
- if (!have || have.size !== part.size) {
150
- say({ status: 'download', loaded: before, total: f.size });
151
- await llama.download({ url: diskformerLlamaBackend.url(model, part), name, size: part.size },
152
- (p) => say({ status: 'download', loaded: before + p.copied, total: f.size }));
156
+ if (!have || have.size !== size) {
157
+ say({ status: 'download', loaded: before, total });
158
+ await llama.download({ url, name, size }, (p) => say({ status: 'download', loaded: before + p.copied, total }));
153
159
  signal?.throwIfAborted();
154
160
  }
155
- before += part.size;
161
+ before += size;
156
162
  }
157
- await writeIndex({ ...(await readIndex()), ...Object.fromEntries(files.map((p) => [opfsName(p), model.repo])) });
163
+ await writeIndex({ ...(await readIndex()), ...Object.fromEntries(files.map((p) => [p.name, p.repo])) });
158
164
  say({ status: 'load' });
159
- info = await llama.load({ name: opfsName(f), budget, ctx: maxCtx, extra: f.extra.map(opfsName) }, (p) => say({ status: 'plan', plan: p }));
165
+ info = await llama.load({ name: opfsName(f), budget, ctx: maxCtx, extra: f.extra.map(opfsName),
166
+ ...(lora && { lora: lora.name, loraScale: adapter.scale ?? 1 }) }, (p) => say({ status: 'plan', plan: p }));
160
167
  signal?.throwIfAborted();
161
168
  } catch (e) {
162
169
  llama.terminate();
@@ -27,6 +27,17 @@ export const DISKFORMER_MODELS = [
27
27
  engine: 'qwen35_moe_ssd.js', Engine: 'MiniCpmV47Ssd', minBudget: 4e9,
28
28
  speeds: [[6.1e9, 14.1]],
29
29
  },
30
+ // Clover-35B-A3B-Preview (Diffuspace, Apache-2.0): block diffusion on Qwen3.6-35B-A3B, text only. The engine
31
+ // (engines/clover_moe_ssd.js CLOVER_35B_A3B, rev 32e22a0) fills a 32-token block over several denoising passes
32
+ // (kohra's denoise module), so generate() yields once per committed block. Gated by diskformer (v0.6.15: oracle
33
+ // and browser chat) at a 1 GB expert pool and maxCtx 1024; speed unmeasured. At maxCtx 1024 the engine fixes about
34
+ // 2.0 GB (dense 1.46, KV and DeltaNet state 0.09, working buffers) and refuses under about 2.05 GB (diskformer's
35
+ // reading of its budget formula); 4 GB, Qwen3.6's floor, leaves about 1.9 GB of experts.
36
+ {
37
+ repo: 'Diffuspace/Clover-35B-A3B-Preview-GGUF', file: 'Clover-35B-A3B-Preview-Q4_0.gguf', size: 19586909600,
38
+ engine: 'clover_moe_ssd.js', Engine: 'CloverMoeSsd', minBudget: 4e9,
39
+ speeds: [],
40
+ },
30
41
  // From LocalMind's gates (M4 Pro, 4 GiB pool; plan/2026-10-22-flint-harness): Flint ~7.3 GB on the GPU at maxCtx
31
42
  // 8192 (dense 2.33, pool 4.29, KV 0.41, batch 0.20), 8.66 tok/s at lookahead 1. LFM2.5 24B ~16 tok/s at a 4 GiB
32
43
  // pool; its total GPU use was not recorded, so it carries no speed estimate. The smallest working budget of either
package/src/load.js CHANGED
@@ -29,16 +29,19 @@ function noBackend(plan) {
29
29
  // What load() would do, without loading: the descriptor, the caps, and every back end's answer.
30
30
  // task: a Hugging Face pipeline task ('text-generation', 'feature-extraction', …); null runs the repo's own.
31
31
  // file: a weights file in the repo to load (a GGUF name); null lets each back end pick.
32
- // repo: a Hub id, or an entry from MODELS (its task and budget apply unless opts set them).
32
+ // repo: a Hub id, or an entry from MODELS (its task, budget and adapter apply unless opts set them).
33
+ // adapter: a LoRA GGUF applied over the model, { repo, file, revision, size, scale }; only a back end with
34
+ // `adapters: true` (diskformer-llama) can apply one, so the others drop out of the plan.
33
35
  const entry = (repo, opts) => (typeof repo === 'object' && repo?.repo
34
- ? [repo.repo, { ...(repo.task && { task: repo.task }), ...(repo.budget && { budget: repo.budget }), ...opts }]
36
+ ? [repo.repo, { ...(repo.task && { task: repo.task }), ...(repo.budget && { budget: repo.budget }), ...(repo.adapter && { adapter: repo.adapter }), ...opts }]
35
37
  : [repo, opts]);
36
38
 
37
39
  export async function plan(repoOrEntry, opts = {}) {
38
- let [repo, { budget = DEFAULT_BUDGET, task = null, file = null, backends = BACKENDS, caps, model, fetch }] = entry(repoOrEntry, opts);
40
+ let [repo, { budget = DEFAULT_BUDGET, task = null, file = null, backends = BACKENDS, caps, model, fetch, adapter = null }] = entry(repoOrEntry, opts);
39
41
  caps ??= await probe();
40
42
  model ??= await describe(repo, fetch ? { fetch } : {});
41
- return { model, caps, budget, task, file, ...(await resolve(model, caps, backends, { budget, task, file })) };
43
+ if (adapter) backends = backends.filter((b) => b.adapters);
44
+ return { model, caps, budget, task, file, ...(adapter && { adapter }), ...(await resolve(model, caps, backends, { budget, task, file })) };
42
45
  }
43
46
 
44
47
  export async function load(repoOrEntry, options = {}) {
@@ -50,7 +53,7 @@ export async function load(repoOrEntry, options = {}) {
50
53
  const backend = backends.find((b) => b.id === p.chosen);
51
54
  let session;
52
55
  try {
53
- session = await backend.load(p.model, { budget: p.budget, task: p.task, file: p.file, caps: p.caps, maxCtx, dtype, poolBytes, localFile, fetch: engineFetch, ple, speculative, speech, onProgress, signal });
56
+ session = await backend.load(p.model, { budget: p.budget, task: p.task, file: p.file, adapter: p.adapter, caps: p.caps, maxCtx, dtype, poolBytes, localFile, fetch: engineFetch, ple, speculative, speech, onProgress, signal });
54
57
  } catch (e) { throw wrap(e, 'LOAD_FAILED', signal, `${backend.id}: `); }
55
58
  // Added onto serialize()'s object, not spread: a spread would read its getters once and freeze them.
56
59
  return Object.assign(serialize(session), { backend: backend.id, task: session.task || p.task || p.model.task || null, ...(session.device && { device: session.device }), plan: p });
package/src/models.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Models that pass a browser test through anyformer, grouped by what they do. Pass an entry to load() in place of a
2
2
  // repo id; it carries the task and, where the back end needs one, the budget. DEFAULT is the smallest download that
3
3
  // passed. `test` names the page under test/browser/ that proves the entry; test/models.test.js checks it exists.
4
- // `diffusion: true` marks a text-diffusion model (the reply denoises in blocks); `experimental: true` marks an entry its
4
+ // `diffusion: true` marks a text-diffusion model (the reply denoises in blocks, so generate() chunks arrive a block at a time); `experimental: true` marks an entry its
5
5
  // app shows as opt-in until that app's own checks pass; `audioOut: true` marks a model that can also answer in speech
6
- // (generate({ speech: true })).
6
+ // (generate({ speech: true })); `adapter` names a LoRA GGUF applied over the model (diskformer-llama only).
7
7
 
8
8
  const M = (repo, backend, task, test, extra = {}) => ({ repo, backend, task, test, ...extra });
9
9
 
@@ -21,6 +21,8 @@ const MINICPM5_2B = M('openbmb/MiniCPM5-2B-GGUF', 'wllama', 'text-generation', '
21
21
  const QWEN3_0_6B_MDLM = M('naklitechie/Qwen3-0.6B-diffusion-mdlm-ONNX', 'kohra', 'text-generation', 'kohra', { downloadBytes: 1.54e9, diffusion: true });
22
22
  const NEMOTRON_DIFFUSION_3B = M('naklitechie/Nemotron-Labs-Diffusion-3B-ONNX', 'kohra', 'text-generation', 'kohra', { downloadBytes: 2.77e9, diffusion: true, experimental: true });
23
23
  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 });
24
+ // Block diffusion on Qwen3.6-35B-A3B: replies arrive one 32-token block at a time. Speed unmeasured.
25
+ const CLOVER_35B_A3B = M('Diffuspace/Clover-35B-A3B-Preview-GGUF', 'diskformer', 'text-generation', 'chat', { budget: 4e9, downloadBytes: 19.59e9, diffusion: true, experimental: true });
24
26
  const LFM3_1_7B = M('LiquidAI/LFM3-1.7B-ONNX', 'transformers', 'text-generation', 'chat', { downloadBytes: 1.63e9 });
25
27
  // transformers.js 4.3.2 has no lfm3_omni class, so diskformer's hand engine runs it; swap the entry to transformers.js
26
28
  // when it adds the model.
@@ -30,6 +32,17 @@ const NEMOTRON_3_5_BOLT_E4B = M('danyalmalik/NVIDIA-Nemotron-3.5-Bolt-E4B-ONNX-W
30
32
  // diskformer checked one hand-written prompt through complete(); generate() renders the GGUF's chat template, which no
31
33
  // browser run has checked.
32
34
  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 });
35
+ // LFM2.5-24B-A2B with harness-hub's Pi LoRA (rank 32, alpha 64; asynchronous GRPO inside the Pi coding agent), on
36
+ // diskformer's llama.cpp path, the only back end that applies an adapter. The adapter touches the conv, attention and
37
+ // two dense FFN layers only, so expert paging is unchanged. diskformer v0.6.17's gate: 8/9 replies identical to
38
+ // llama-server --lora (the path's own baseline without the adapter is 7/9). Authors' numbers on 100 data-analysis tasks:
39
+ // 39% fewer tool calls on tasks both solve, pass@1 −4.2 points (noise about ±5); expect base-model behaviour or worse in
40
+ // other agents. Licence lfm1.0 (model and adapter): royalty-free commercial use only below US$10M annual revenue.
41
+ const LFM2_5_24B_PI = M('LiquidAI/LFM2.5-24B-A2B-GGUF', 'diskformer-llama', 'text-generation', 'chat', {
42
+ budget: 4e9, downloadBytes: 13578190880 + 37437888, experimental: true, license: 'lfm1.0',
43
+ adapter: { repo: 'harness-hub/LFM2.5-24B-A2B-pi-GGUF', file: 'LFM2.5-24B-A2B-harbor-pi-lora-F16.gguf',
44
+ revision: 'a994cf7f03ab0e572832d5829f42cbe2ef28d472', size: 37437888, scale: 1, trainedFor: 'pi' },
45
+ });
33
46
  const MINILM_L6 = M('Xenova/all-MiniLM-L6-v2', 'transformers', 'feature-extraction', 'embed', { downloadBytes: 90e6 });
34
47
  const DETR_RESNET_50 = M('Xenova/detr-resnet-50', 'transformers', 'object-detection', 'detect', { downloadBytes: 167e6 });
35
48
 
@@ -57,6 +70,8 @@ export const MODELS = {
57
70
  NEMOTRON_DIFFUSION_3B,
58
71
  /** 26B parameters in 2.5 GB of GPU memory; 14.4 GB on disk (diskformer). */
59
72
  GEMMA4_26B_A4B,
73
+ /** Diffuspace's Clover-35B-A3B-Preview: block diffusion on Qwen3.6-35B-A3B, 19.6 GB on disk; replies arrive in 32-token blocks (diskformer). */
74
+ CLOVER_35B_A3B,
60
75
  /** NVIDIA's Nemotron 3.5 Bolt E4B (dense, text only), a community q4f16 ONNX export in Gemma 4 E4B's graph. */
61
76
  NEMOTRON_3_5_BOLT_E4B,
62
77
  /** LiquidAI's LFM3 1.7B (dense), LiquidAI's own ONNX export; thinking off unless a request asks for it. */
@@ -65,6 +80,8 @@ export const MODELS = {
65
80
  LFM3_OMNI_2B,
66
81
  /** DeepSeek V4.1 Flash (763B MoE), a tech demo on diskformer's llama.cpp path: 231 GB on disk, very slow. */
67
82
  DEEPSEEK_V4_1_FLASH,
83
+ /** LFM2.5-24B-A2B with harness-hub's Pi coding-agent LoRA (diskformer's llama.cpp path); tuned for the Pi agent; licence lfm1.0. */
84
+ LFM2_5_24B_PI,
68
85
  },
69
86
  /** Chat with images in the messages: content [{ type: 'image', image }, { type: 'text', text }]. Gemma 4 takes audio too. */
70
87
  vision: { DEFAULT: QWEN3_5_4B, QWEN3_5_4B, GEMMA4_E2B_ONNX, GEMMA4_E4B_ONNX },
package/src/sources.js CHANGED
@@ -9,6 +9,13 @@ 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.17 (314362e): LoRA adapters on the llama.cpp path (llama.load({ lora, loraScale })), and lfm2_moe_ssd.js runs
13
+ // a LoRA-merged LFM2.5-24B-A2B with Q8_0 dense matrices.
14
+ // v0.6.16 (467052f): engines/subgroup-matrix-guard.js. Chrome 156 rejects mixed f16/f32 subgroup-matrix kernels
15
+ // on Apple GPUs; the guard hides the feature when one tiny mixed kernel fails to compile, so Ternary Bonsai 2 27B,
16
+ // Gemma 4 E2B, LFM2.5 and qwen3_moe_ssd.js take their non-matrix prefill (slower prefill, same output path).
17
+ // v0.6.15 (f17009c): engines/clover_moe_ssd.js, Clover-35B-A3B-Preview (block diffusion on Qwen3.6-35B-A3B; kohra's
18
+ // denoise loop vendored in engines/vendor/). qwen35_moe_ssd.js and qwen3_moe_ssd.js only learn to accept clover_moe.
12
19
  // v0.6.14 (93193d0): LFM3-Omni speech out's inverse STFT uses an FFT (same samples), 0.54x → 1.5–1.8x real time on
13
20
  // an M4 Pro. Changes only engines/lfm3_omni.js.
14
21
  // v0.6.13 (7ffbea5): LFM3-Omni serves one reply at a time; overlapping runs broke onnxruntime-web WebGPU.
@@ -21,7 +28,7 @@ export const PINS = {
21
28
  // Gemma 4 E2B/E4B and Ternary Bonsai 2) under engines/, and the llama.cpp path under ggml/, which loads split GGUFs
22
29
  // and drafters, and reads past 2 GiB (Nemotron 3.5 Bolt E4B needs v0.6.8 or later). The hand engines' ingest
23
30
  // streams the GGUF header with progress and resumes a dropped range.
24
- diskformer: '93193d0913f5e76059fa45db509dc670e7527430',
31
+ diskformer: '314362e907787ab28408a4e3e737fcf4b661e5c6',
25
32
  };
26
33
 
27
34
  const gh = (repo, sha, path) => `https://cdn.jsdelivr.net/gh/NakliTechie/${repo}@${sha}/${path}`;