diskformer 0.6.12 → 0.6.13

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.13 (2026-10-30)
4
+ - LFM3-Omni: one reply at a time. `generate()` and `greedyIds()` share the decoder's caches and onnxruntime's sessions;
5
+ two replies overlapping on WebGPU failed ("Failed to download data from buffer") or hung (the NakliOS assistant: a
6
+ text reply with tools left running, then a speech reply). A call made while another runs now waits its turn.
7
+ - Gate `test/browser/lfm3-omni-overlap.html`: text then speech on one engine, and speech started while text runs; both
8
+ stream audio, nothing throws or times out.
9
+
3
10
  ## 0.6.12 (2026-10-30)
4
11
  - LFM3-Omni-2B speaks: `generate(messages, { output_modalities: ['text', 'audio'], onAudio })` runs Liquid's
5
12
  interleaved mode (6 text tokens, 12 speech frames in turn), the depth decoder's MaskGIT (12 codes a frame) and the
@@ -710,7 +710,15 @@ export class Lfm3Omni {
710
710
  // stopReason 'eos' | 'length' | 'aborted'. Streaming: onText(delta) with each new piece of text (held back while
711
711
  // a token ends inside a UTF-8 character), onToken(id), and a transformers.js streamer (put([[id]]), end()). signal
712
712
  // (AbortSignal) is read before every decode step; an abort resolves, it does not throw.
713
- async generate(messages, opts = {}) {
713
+ // One reply at a time: generate() and greedyIds() share the decoder's caches and onnxruntime's sessions, and two runs
714
+ // overlapping on WebGPU fail ("Failed to download data from buffer") or hang (2026-10-30: a caller that stopped
715
+ // reading a text reply without aborting it, then asked for speech). A call made while another runs waits its turn.
716
+ #turn = Promise.resolve();
717
+ #serial(fn) { const run = this.#turn.then(fn); this.#turn = run.catch(() => {}); return run; }
718
+
719
+ generate(messages, opts = {}) { return this.#serial(() => this.#generate(messages, opts)); }
720
+
721
+ async #generate(messages, opts) {
714
722
  const { max_new_tokens = 1024, signal, onText, onToken, streamer, seed, output_modalities = ['text'] } = opts;
715
723
  if (output_modalities.includes('audio')) {
716
724
  if (!output_modalities.includes('text')) throw new Error("LFM3-Omni speaks in interleaved mode: output_modalities ['text', 'audio']");
@@ -818,7 +826,9 @@ export class Lfm3Omni {
818
826
  }
819
827
 
820
828
  // Greedy ids after prompt ids (the gate's comparison): no template, no stop on EOS unless asked.
821
- async greedyIds(ids, side = {}, n = 32, { stopAtEos = true } = {}) {
829
+ greedyIds(ids, side = {}, n = 32, opts = {}) { return this.#serial(() => this.#greedyIds(ids, side, n, opts)); }
830
+
831
+ async #greedyIds(ids, side = {}, n = 32, { stopAtEos = true } = {}) {
822
832
  const T = this.tokens, suppress = [T.audio, T.audio_out, T.image, T.video, T.mixed_start, T.audio_start], out = [];
823
833
  try {
824
834
  let { logits } = await this.prefill(ids, side);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diskformer",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
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",