anyformer 0.4.6 → 0.4.8

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,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.8 — 2026-10-24
4
+
5
+ Skips 0.4.7 on npm: this release carries its Nemotron-Labs-Diffusion-3B entry too.
6
+
7
+ ### Changed
8
+ - kohra pinned to v0.3.0 (85e01f0); kohra.js is identical to 0.4.7's pin, 14e430c.
9
+ - Nemotron-Labs-Diffusion-3B decodes with threshold 0.9 by default (Fast-dLLM: unmask every position at that
10
+ confidence per step); `generate(messages, { threshold: null })` runs fixed steps. kohra measured 34 forward passes
11
+ against 64 with the same answer.
12
+
13
+ ### Verified
14
+ - Browser (Chrome, M4 Pro, GPU shared with another session): one page, the CPU prompt, 32 tokens, threshold
15
+ null / 0.9 / null / 0.9: identical text in every run; 7.1 s and 5.8 s at fixed steps, 4.9 s at 0.9. npm test 89/89.
16
+
17
+ ## 0.4.7 — 2026-10-24
18
+
19
+ ### Added
20
+ - Nemotron-Labs-Diffusion-3B (`MODELS.chat.NEMOTRON_DIFFUSION_3B`, naklitechie/Nemotron-Labs-Diffusion-3B-ONNX) on
21
+ `kohra`: the q4f16 seeded block-KV graph (onnx/model_kv_q4f16.onnx, 2.73 GB with its two data files), mask id 100,
22
+ EOS 11, and Nemotron's chat template over the whole conversation (a system turn, an empty think block before each
23
+ assistant reply). Content blocks are joined as text.
24
+
25
+ ### Changed
26
+ - kohra pinned to main 14e430c (after v0.2.1; kohra.js changed in afee5f4): seeded KV graphs, `chatFormat`, graphs
27
+ whose data is split into `_data`, `_data_1`.
28
+
29
+ ### Verified
30
+ - Browser (Chrome, M4 Pro): `npm run test:browser -- kohra --query "repo=naklitechie/Nemotron-Labs-Diffusion-3B-ONNX"`
31
+ answered on topic (32 tokens, 5.0 tok/s, load 156 s with the download); `npm run test:browser -- kohra` (Qwen3-0.6B
32
+ MDLM) on the new pin answered on topic. npm test 89/89.
33
+
3
34
  ## 0.4.6 — 2026-10-24
4
35
 
5
36
  Skips 0.4.5 on npm: this release carries its diskformer v0.6.8 pin too.
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.6/+esm';` |
17
+ | CDN | `import { load } from 'https://cdn.jsdelivr.net/npm/anyformer@0.4.8/+esm';` |
18
18
  | Copy | `index.js` and `src/`; nothing else is needed |
19
19
 
20
20
  ```js
@@ -55,7 +55,7 @@ back end's answer: yes with an estimate (GPU bytes, disk bytes, tok/s when measu
55
55
  | Back end | Runs | Models |
56
56
  |---|---|---|
57
57
  | `transformers` | transformers.js 4.3.1: text generation (Nemotron 3.5 Bolt E4B among them), image and audio chat (Gemma 4, Qwen3.5), embeddings, speech to text, detection, classification, depth, segmentation | ONNX repos in the onnx-community / Xenova layout |
58
- | `kohra` | kohra, text-diffusion over onnxruntime-web | MDLM and BD3LM repos tagged `text-diffusion` |
58
+ | `kohra` | kohra, text-diffusion over onnxruntime-web | MDLM and BD3LM repos tagged `text-diffusion`; Nemotron-Labs-Diffusion-3B |
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) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyformer",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
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",
@@ -9,8 +9,29 @@ const FP16 = 'onnx/model_fp16_fused.onnx';
9
9
  const Q4 = 'onnx/model_q4f16_rtn_sym.onnx';
10
10
  const Q4_ORT = '1.26.0-dev.20260416-b7804b056c';
11
11
 
12
+ // Models with their own graph and tokens, keyed by config.model_type (kohra index.html REGISTRY at the pinned commit).
13
+ // Nemotron-Labs-Diffusion-3B: a seeded block-KV graph (kohra.js finds its attention_mask input and denoises blocks
14
+ // from the prompt end), mask id 100, EOS 11, and Nemotron's template: a system turn, always, and an empty think block
15
+ // before each assistant reply.
16
+ const SPECIAL = {
17
+ nemotron_labs_diffusion: {
18
+ graph: 'onnx/model_kv_q4f16.onnx', opts: { kvCache: true, maskId: 100n, eosId: 11n }, blockCausal: false,
19
+ // Fast-dLLM decoding: unmask every position at confidence 0.9 or more per step. Same answer in 34 forward passes
20
+ // where fixed steps took 64 (kohra, primes prompt).
21
+ threshold: 0.9,
22
+ template: (messages) => {
23
+ const [system, rest] = messages[0]?.role === 'system' ? [messages[0].content, messages.slice(1)] : ['', messages];
24
+ return `<|im_start|>system\n${system}<|im_end|>\n` + rest.map((m) => (m.role === 'assistant'
25
+ ? `<|im_start|>assistant\n<think></think>${m.content.trim()}<|im_end|>\n`
26
+ : `<|im_start|>${m.role}\n${m.content}<|im_end|>\n`)).join('') + '<|im_start|>assistant\n<think></think>';
27
+ },
28
+ },
29
+ };
30
+
12
31
  const pickGraph = (model, caps) => {
13
32
  const have = new Set(model.files.onnx.map((f) => f.name));
33
+ const special = SPECIAL[model.architecture];
34
+ if (special) return have.has(special.graph) ? special.graph : null;
14
35
  return caps.f16 && have.has(FP16) ? FP16 : have.has(Q4) ? Q4 : null;
15
36
  };
16
37
 
@@ -18,6 +39,9 @@ const pickGraph = (model, caps) => {
18
39
  const chatml = (messages) =>
19
40
  messages.map((m) => `<|im_start|>${m.role}\n${m.content}<|im_end|>\n`).join('') + '<|im_start|>assistant\n';
20
41
 
42
+ // Text blocks joined: kohra reads text only.
43
+ const text = (messages) => messages.map((m) => (typeof m.content === 'string' ? m : { ...m, content: m.content.map((b) => b.text ?? '').join('\n') }));
44
+
21
45
  export const kohraBackend = {
22
46
  id: 'kohra',
23
47
  tasks: ['text-generation'],
@@ -28,7 +52,7 @@ export const kohraBackend = {
28
52
  if (task && task !== 'text-generation') return { ok: false, code: 'task', reason: `task ${task}; kohra runs text-generation only` };
29
53
  if (!caps.webgpu) return { ok: false, code: 'webgpu', reason: 'no WebGPU adapter' };
30
54
  const graph = pickGraph(model, caps);
31
- if (!graph) return { ok: false, code: 'format', reason: `no kohra graph (${FP16} or ${Q4}) in the repo` };
55
+ if (!graph) return { ok: false, code: 'format', reason: `no kohra graph (${SPECIAL[model.architecture]?.graph || `${FP16} or ${Q4}`}) in the repo` };
32
56
  const bytes = graphBytes(model, graph);
33
57
  return { ok: true, estimate: { gpuBytes: bytes, diskBytes: bytes, tokPerSec: null } };
34
58
  },
@@ -38,18 +62,21 @@ export const kohraBackend = {
38
62
  const { DiffusionLM } = await import(kohraBackend.source);
39
63
  onProgress?.({ backend: 'kohra', status: 'loading', file: graph });
40
64
  const base = `https://huggingface.co/${model.repo}/resolve/${model.revision || 'main'}`;
65
+ const special = SPECIAL[model.architecture];
41
66
  const lm = await DiffusionLM.from_pretrained({
42
- model: `${base}/${graph}`, tokenizer: model.repo, ...(graph === Q4 && { ortVersion: Q4_ORT }),
67
+ model: `${base}/${graph}`, tokenizer: model.repo, ...(graph === Q4 && { ortVersion: Q4_ORT }), ...special?.opts,
43
68
  });
44
69
  signal?.throwIfAborted();
45
- const blockCausal = model.tags.includes('block-diffusion');
70
+ const blockCausal = special ? special.blockCausal : model.tags.includes('block-diffusion');
71
+ const prompt = special?.template || chatml;
46
72
  return {
47
73
  task: 'text-generation',
48
74
  // Diffusion fills the whole block at once, so the reply arrives as one chunk.
49
- async *generate(messages, { maxNewTokens = 128, steps, signal } = {}) {
75
+ // threshold: unmask every position at that confidence per step (Fast-dLLM); null runs fixed steps.
76
+ async *generate(messages, { maxNewTokens = 128, steps, threshold = special?.threshold ?? null, signal } = {}) {
50
77
  signal?.throwIfAborted();
51
- const out = await lm.generate(chatml(messages), {
52
- maxNewTokens, steps: steps ?? maxNewTokens, blockCausal, chatTemplate: false, stripThink: true,
78
+ const out = await lm.generate(prompt(text(messages)), {
79
+ maxNewTokens, steps: steps ?? maxNewTokens, threshold, blockCausal, chatTemplate: false, stripThink: true,
53
80
  });
54
81
  yield { text: out.text, tokens: out.tokens, tokPerSec: out.tokensPerSecond };
55
82
  },
package/src/models.js CHANGED
@@ -16,6 +16,7 @@ const GEMMA4_E2B_ONNX = M('onnx-community/gemma-4-E2B-it-ONNX', 'transformers',
16
16
  const GEMMA4_E4B_ONNX = M('onnx-community/gemma-4-E4B-it-ONNX', 'transformers', 'text-generation', 'transformers', { budget: 5.3e9, downloadBytes: 5.18e9 });
17
17
  const MINICPM5_2B = M('openbmb/MiniCPM5-2B-GGUF', 'wllama', 'text-generation', 'chat', { downloadBytes: 1.56e9 });
18
18
  const QWEN3_0_6B_MDLM = M('naklitechie/Qwen3-0.6B-diffusion-mdlm-ONNX', 'kohra', 'text-generation', 'kohra', { downloadBytes: 1.54e9 });
19
+ const NEMOTRON_DIFFUSION_3B = M('naklitechie/Nemotron-Labs-Diffusion-3B-ONNX', 'kohra', 'text-generation', 'kohra', { downloadBytes: 2.77e9 });
19
20
  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 });
20
21
  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 });
21
22
  const MINILM_L6 = M('Xenova/all-MiniLM-L6-v2', 'transformers', 'feature-extraction', 'embed', { downloadBytes: 90e6 });
@@ -41,6 +42,8 @@ export const MODELS = {
41
42
  MINICPM5_2B,
42
43
  /** Text diffusion: the whole reply denoises at once (kohra). */
43
44
  QWEN3_0_6B_MDLM,
45
+ /** NVIDIA's Nemotron-Labs-Diffusion-3B, block diffusion with a KV cache, q4 (kohra). */
46
+ NEMOTRON_DIFFUSION_3B,
44
47
  /** 26B parameters in 2.5 GB of GPU memory; 14.4 GB on disk (diskformer). */
45
48
  GEMMA4_26B_A4B,
46
49
  /** NVIDIA's Nemotron 3.5 Bolt E4B (dense, text only), a community q4f16 ONNX export in Gemma 4 E4B's graph. */
package/src/sources.js CHANGED
@@ -6,7 +6,8 @@
6
6
  export const PINS = {
7
7
  transformers: '4.3.1',
8
8
  wllama: '3.4.1', // LocalMind's pin; its stream-drain workaround (src/backends/wllama.js) is for this version
9
- kohra: 'f678fac7342c79b58b640540b8b3a7d078b7ab1a',
9
+ // v0.3.0: seeded block-KV graphs (Nemotron-Labs-Diffusion-3B), chatFormat, split external data.
10
+ kohra: '85e01f0193163b0f2e9696d4f6f5e12b68a277e9',
10
11
  // v0.6.8: every engine (the disk-tier MoE engines, Ling 3.0 Flash, and LocalMind's whole-model engines LFM2.5,
11
12
  // Gemma 4 E2B/E4B and Ternary Bonsai 2) under engines/, and the llama.cpp path under ggml/, which loads split GGUFs
12
13
  // and drafters, and reads past 2 GiB (Nemotron 3.5 Bolt E4B needs v0.6.8 or later). The hand engines' ingest