anyformer 0.4.14 → 0.4.16

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,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.16 — 2026-10-30
4
+
5
+ ### Changed
6
+ - diskformer pin 7ffbea5 → 93193d0 (v0.6.14: LFM3-Omni speech out runs at 1.5–1.8x real time, up from 0.54x, on an
7
+ M4 Pro; the inverse STFT uses an FFT with the same output; changes only `engines/lfm3_omni.js`).
8
+ - Browser test `omni-speech` on the new pin (Chrome, this Mac, local files, about 22:50 IST): 13.52 s of speech in
9
+ 7.1 s (1.9x real time), first sound after 0.98 s; abort at the first speech chunk; text reply after it equal.
10
+
11
+ ## 0.4.15 — 2026-10-30
12
+
13
+ ### Fixed
14
+ - A caller that stops reading a reply early (a `break` out of `for await`, as after a tool call) without aborting
15
+ now stops the reply on `diskformer-onnx` and `transformers`: the back end aborts the engine call (a linked
16
+ AbortController for diskformer-onnx, the stopping criteria's interrupt for transformers.js) and waits for it before
17
+ the next generate() starts. Before, the engine kept generating, and the next reply ran on the same onnxruntime
18
+ sessions beside it: "failed to call OrtRun() … Failed to download data from buffer" or a hang of over 10 minutes in
19
+ Chrome (reported by NakliOS, root-caused by LocalMind). `diskformer-llama` already did this.
20
+
21
+ ### Changed
22
+ - diskformer pin 1fe19f8 → 7ffbea5 (v0.6.13: LFM3-Omni serves one reply at a time; changes only
23
+ `engines/lfm3_omni.js`).
24
+ - Browser tests `omni` and `omni-speech` break out of a reply (text, and spoken at its first speech chunk) without
25
+ aborting, then require the next reply to equal the text reference within 10 s. Passed 2026-10-30 (Chrome, this Mac,
26
+ local files), with `transformers` and the LFM3 1.7B `chat` check.
27
+
3
28
  ## 0.4.14 — 2026-10-30
4
29
 
5
30
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyformer",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
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",
@@ -87,8 +87,12 @@ export const diskformerOnnxBackend = {
87
87
  let wake = () => {}, done = false, failed = null, text = '', tokens = 0;
88
88
  const msgs = await media(messages);
89
89
  const sampleRate = engine.speech?.sampling_rate ?? 24000;
90
- engine.generate(msgs, {
91
- max_new_tokens: maxNewTokens, do_sample: false, enable_thinking: thinking === true, signal,
90
+ // The engine reads this signal, linked to the caller's, so a caller that stops reading can end the reply too.
91
+ const stop = new AbortController();
92
+ const abort = () => stop.abort();
93
+ if (signal?.aborted) abort(); else signal?.addEventListener('abort', abort, { once: true });
94
+ const job = engine.generate(msgs, {
95
+ max_new_tokens: maxNewTokens, do_sample: false, enable_thinking: thinking === true, signal: stop.signal,
92
96
  ...(tools && { tools }), ...(repetitionPenalty && { repetition_penalty: repetitionPenalty }), ...(seed !== undefined && { seed }),
93
97
  ...(speech && {
94
98
  output_modalities: ['text', 'audio'],
@@ -100,12 +104,19 @@ export const diskformerOnnxBackend = {
100
104
  onToken: () => { tokens++; },
101
105
  onText: (delta) => { text += delta; queue.push({ text, tokens }); wake(); },
102
106
  }).catch((err) => { failed = err; }).finally(() => { done = true; wake(); });
103
- while (true) {
104
- if (queue.length) { yield queue.shift(); continue; }
105
- if (done) break;
106
- await new Promise((r) => { wake = r; });
107
+ try {
108
+ while (true) {
109
+ if (queue.length) { yield queue.shift(); continue; }
110
+ if (done) break;
111
+ await new Promise((r) => { wake = r; });
112
+ }
113
+ if (failed) throw failed;
114
+ } finally {
115
+ signal?.removeEventListener('abort', abort);
116
+ // The caller stopped reading early (a break after a tool call): stop the reply, so the next call on this
117
+ // engine's sessions does not run beside it or queue behind it.
118
+ if (!done) { stop.abort(); await job; }
107
119
  }
108
- if (failed) throw failed;
109
120
  },
110
121
  dispose: () => engine.dispose(),
111
122
  };
@@ -177,14 +177,21 @@ async function* stream(lib, tokenizer, signal, run) {
177
177
  token_callback_function: () => { tokens++; },
178
178
  });
179
179
  const stop = new lib.InterruptableStoppingCriteria();
180
- signal?.addEventListener('abort', () => stop.interrupt(), { once: true });
181
- run(streamer, stop).catch((e) => { failed = e; }).finally(() => { done = true; wake(); });
182
- while (true) {
183
- if (queue.length) { yield queue.shift(); continue; }
184
- if (done) break;
185
- await new Promise((r) => { wake = r; });
180
+ const interrupt = () => stop.interrupt();
181
+ signal?.addEventListener('abort', interrupt, { once: true });
182
+ const job = run(streamer, stop).catch((e) => { failed = e; }).finally(() => { done = true; wake(); });
183
+ try {
184
+ while (true) {
185
+ if (queue.length) { yield queue.shift(); continue; }
186
+ if (done) break;
187
+ await new Promise((r) => { wake = r; });
188
+ }
189
+ if (failed) throw failed;
190
+ } finally {
191
+ signal?.removeEventListener('abort', interrupt);
192
+ // The caller stopped reading early: stop the reply so the next call on this model does not run beside it.
193
+ if (!done) { stop.interrupt(); await job; }
186
194
  }
187
- if (failed) throw failed;
188
195
  }
189
196
 
190
197
  // A text-only model in the multimodal layout: the causal-LM class over embed_tokens + decoder_model_merged.
package/src/sources.js CHANGED
@@ -9,6 +9,9 @@ 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.14 (93193d0): LFM3-Omni speech out's inverse STFT uses an FFT (same samples), 0.54x → 1.5–1.8x real time on
13
+ // an M4 Pro. Changes only engines/lfm3_omni.js.
14
+ // v0.6.13 (7ffbea5): LFM3-Omni serves one reply at a time; overlapping runs broke onnxruntime-web WebGPU.
12
15
  // v0.6.12 (1fe19f8): LFM3-Omni speech out (interleaved text and 24 kHz speech; depth and codec decoders). Changes
13
16
  // only engines/lfm3_omni.js; its text, image and speech-in gate (3/3) is unchanged.
14
17
  // 48f2c2a (main after v0.6.9): engines/lfm3_omni.js, LFM3-Omni-2B on onnxruntime-web 1.30.0 (text, image and speech
@@ -18,7 +21,7 @@ export const PINS = {
18
21
  // Gemma 4 E2B/E4B and Ternary Bonsai 2) under engines/, and the llama.cpp path under ggml/, which loads split GGUFs
19
22
  // and drafters, and reads past 2 GiB (Nemotron 3.5 Bolt E4B needs v0.6.8 or later). The hand engines' ingest
20
23
  // streams the GGUF header with progress and resumes a dropped range.
21
- diskformer: '1fe19f8f430f3af2c78e5d2534fdd7938b36b307',
24
+ diskformer: '93193d0913f5e76059fa45db509dc670e7527430',
22
25
  };
23
26
 
24
27
  const gh = (repo, sha, path) => `https://cdn.jsdelivr.net/gh/NakliTechie/${repo}@${sha}/${path}`;