@qvac/tts-ggml 0.14.0 → 0.15.0

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/README.md CHANGED
@@ -29,8 +29,27 @@ The LM, flow frontend/decoder, F0 predictor and HiFT vocoder use resident
29
29
  sidecars when present and fall back to ggml when unavailable. HiFT exports
30
30
  use FP32 for waveform parity. Core ML chooses operation placement, so a
31
31
  sidecar does not guarantee ANE execution or a speed improvement.
32
- When a sidecar is present, `assessFit` returns an error with reason
33
- `coreml-memory-estimate-unavailable`; its memory usage is not projected.
32
+ When a sidecar is present, `assessFit` answers `unknown`, since the sidecar's
33
+ memory usage is not projected.
34
+
35
+
36
+ ### Chatterbox Core ML sidecars
37
+
38
+ Apple builds support optional Chatterbox Turbo and Multilingual Core ML
39
+ sidecars for the S3Gen encoder, CFM estimator and HiFT vocoder through the
40
+ native `speech-cpp` 2026-10-30 dependency. Export and placement details are in
41
+ the [Chatterbox Core ML guide](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/9ed06a668923b1ea3f68ad28614228aa1156223e/engines/tts/docs/chatterbox.md#core-ml-sidecars).
42
+
43
+ The three stages run on the Neural Engine when their sidecars sit next to the
44
+ S3Gen GGUF and fall back to ggml when unavailable; T3 keeps the GPU. Set
45
+ `maxSentenceChars` (the reference CLI uses 180) so S3Gen vocodes each sentence
46
+ on the Neural Engine while T3 decodes the next one: on an Apple M2 a
47
+ three-sentence prompt then synthesizes in 31% (Turbo) and 33% (Multilingual)
48
+ less time than on Metal. Without a split there is nothing to overlap, so a call
49
+ gains only the S3Gen time. The first use of each sidecar function by an
50
+ executable compiles it for the Neural Engine (3 to 26 s per function on an M2;
51
+ the OS caches the result). When a sidecar is present, `assessFit` answers
52
+ `unknown`, since the sidecar's memory usage is not projected.
34
53
 
35
54
 
36
55
  Runs in-process with a persistent native engine — the GGUFs, the S3Gen
@@ -54,7 +73,7 @@ and registry prerequisites described under [MOSS](#moss).
54
73
 
55
74
  - Batch synthesis (`run({ input })` → single PCM buffer).
56
75
  - **Sentence-granularity streaming** — `runStreaming(asyncIterable)`:
57
- yields one audio chunk per input sentence.
76
+ yields audio for each input sentence; Audio8 emits PCM as codec windows finish.
58
77
  - **Native per-chunk streaming** — set `streamChunkTokens` and audio
59
78
  flows out of the C++ engine chunk-by-chunk as T3 tokens produce
60
79
  S3Gen+HiFT output; sub-second first-audio-out inside a single
@@ -67,6 +86,7 @@ and registry prerequisites described under [MOSS](#moss).
67
86
  `tts-cpp` selects the GPU backend per its per-vendor allowlist (see
68
87
  [Backends & GPU acceleration](#backends--gpu-acceleration)).
69
88
  - **Apple Core ML sidecars** for the Supertonic vocoder, the Audio8 codec,
89
+ the Chatterbox S3Gen encoder, CFM and HiFT,
70
90
  and one stage each of MOSS, MOSS-SoundEffect, and MOSS-Speech on macOS /
71
91
  iOS, picked up when staged next to the model (see
72
92
  [Core ML sidecars on Apple](#core-ml-sidecars-on-apple)).
@@ -204,10 +224,13 @@ under `models/`:
204
224
  # Chatterbox turbo (English)
205
225
  chatterbox-t3-turbo.gguf (~742 MB) — T3 GPT-2 Medium + BPE + VoiceEncoder
206
226
  chatterbox-s3gen.gguf (~1.0 GB) — S3Gen encoder/CFM + HiFT + CAMPPlus + S3TokenizerV2
227
+ chatterbox-s3gen-{encoder,cfm,hift}.mlmodelc/ (optional, macOS / iOS: Apple Core ML
228
+ sidecars for the S3Gen stages; see Core ML sidecars on Apple)
207
229
 
208
230
  # Chatterbox multilingual (en/es/fr/de/pt/it/zh/ja/ko/...)
209
231
  chatterbox-t3-mtl.gguf (~1.0 GB)
210
232
  chatterbox-s3gen-mtl.gguf (~1.0 GB)
233
+ chatterbox-s3gen-mtl-{encoder,cfm,hift}.mlmodelc/ (optional, macOS / iOS)
211
234
 
212
235
  # Supertonic 3 (Supertone/supertonic-3; 31 languages) — preferred Supertonic
213
236
  # checkpoint; published per quant tier (auto-detected from modelDir)
@@ -577,7 +600,7 @@ accepting a new one, so changing voices means constructing a new instance.
577
600
 
578
601
  Opt-in neural post-processing that bandwidth-extends the synthesized audio to
579
602
  **48 kHz** with a synthesised high band, using the LavaSR Vocos enhancer
580
- (ConvNeXt backbone + ISTFT spec head) converted to a single GGUF. It follows the
603
+ (ConvNeXt backbone + ISTFT spec head) converted to a single GGUF. Without an explicit Core ML selection, it follows the
581
604
  engine's GPU intent: with `config.useGPU: true` (or `nGpuLayers`) the enhancer
582
605
  runs on the GPU (Vulkan on Linux/Windows, Metal on macOS/iOS) and falls back to
583
606
  CPU otherwise. It is fully backward compatible — provide no enhancer GGUF and
@@ -698,8 +721,59 @@ Notes:
698
721
  (scalar CPU port, validated bit-close to the ONNX reference); it requires a
699
722
  `tts-cpp` build that includes that port — see the pinned version in `vcpkg.json`.
700
723
 
724
+
725
+ ### LavaSR Core ML
726
+
727
+ On Apple platforms, either stage can use a compiled Core ML model explicitly.
728
+ Provide `coremlModelPath` and `coremlComputeUnits` together inside `enhancer`
729
+ or `denoiser`, alongside the corresponding GGUF path (or its `files` alias).
730
+
731
+ ```js
732
+ const model = new TTSGgml({
733
+ engine: TTSGgml.ENGINE_SUPERTONIC,
734
+ files: { supertonicModel: 'models/supertonic.gguf' },
735
+ enhancer: {
736
+ type: 'lavasr',
737
+ enhancerPath: 'models/lavasr-enhancer.gguf',
738
+ coremlModelPath: 'models/enhancer.mlmodelc',
739
+ coremlComputeUnits: 'CPU_AND_NE'
740
+ },
741
+ denoiser: {
742
+ type: 'lavasr',
743
+ denoiserPath: 'models/lavasr-denoiser.gguf',
744
+ coremlModelPath: 'models/denoiser.mlmodelc',
745
+ coremlComputeUnits: 'CPU_AND_NE'
746
+ }
747
+ })
748
+ ```
749
+
750
+ The accepted policies are `CPU_ONLY`, `CPU_AND_GPU`, `CPU_AND_NE`, and `ALL`.
751
+ Each stage has its own policy; it does not change the synthesis engine's
752
+ `config.useGPU`. Models stay loaded with the instance. Omitting both options
753
+ keeps the existing ggml/scalar selection. An invalid, missing, incompatible,
754
+ or unsupported explicitly selected Core ML model fails loading; it does not
755
+ fall back to ggml. Existing streaming restrictions still apply.
756
+
757
+ Core ML policies select eligible devices; they do not guarantee that every
758
+ operation runs on ANE or GPU. Stage stats report device `2` for Core ML-managed
759
+ placement and backend id `99` (other), rather than claiming CPU or GPU.
760
+
761
+ Export matching models with the native Fabric
762
+ [`export-lavasr-coreml.py`](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/8d9c6247ac18ff912bb3e1269da5f3fd679e77aa/engines/tts/scripts/export-lavasr-coreml.py)
763
+ script; precision is explicit (`float32` or `float16`). See the
764
+ [native LavaSR guide](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/8d9c6247ac18ff912bb3e1269da5f3fd679e77aa/engines/tts/docs/lavasr.md#core-ml)
765
+ for export and compilation commands. A runnable addon example is
766
+ [`examples/lavasr-coreml.js`](examples/lavasr-coreml.js).
767
+
701
768
  ## Backends & GPU acceleration
702
769
 
770
+ `config.backend` selects the native backend explicitly. Accepted selectors
771
+ include `auto`, `cpu`, `opencl`, `webgpu`, and exact registered
772
+ device names such as `MTL0`, `CUDA0`, `Vulkan0`. Exact device names
773
+ depend on the host. Native selection reports an unavailable explicit backend
774
+ as an error; omitting this option preserves the existing `useGPU` policy.
775
+ The same selector is accepted as top-level `backend` by `assessFit()`.
776
+
703
777
  The addon delegates backend selection to `tts-cpp`'s registry-only
704
778
  init path. At `load()` time the engine walks the ggml-backend registry
705
779
  once and picks the first available accelerator that matches the
@@ -719,11 +793,67 @@ On hosts where more than one backend is usable, `TTS_CPP_GPU_BACKEND`
719
793
  and fails loudly when that backend cannot be resolved; unset (or empty)
720
794
  keeps the automatic preference above.
721
795
 
796
+ ### Optional WebGPU builds
797
+
798
+ Build with `bare-make generate -D TTS_WEBGPU=ON`, followed by
799
+ `bare-make build`, `bare-make install`, and `npm run link:platform` to include
800
+ and load the local Dawn backend.
801
+ WebGPU is exposed only for the native library's established model families:
802
+ Supertonic **3**, Parler, CosyVoice3 and Audio8. Explicit WebGPU requests
803
+ for other families, including Supertonic 1/2, fail before loading weights.
804
+ The manifest pins the compatible Dawn version; default builds stay unchanged.
805
+
806
+ Hexagon is not included in JavaScript package builds. Stats add `backendId: 5`
807
+ for WebGPU. LavaSR's existing `backendDevice: 2` continues to mean Core ML-managed
808
+ placement.
809
+
810
+ ## Native engine controls
811
+
812
+ These options are available at construction and through `reload()`. Leaving
813
+ them unset retains the native engine defaults.
814
+
815
+ | Engine | Options | Meaning |
816
+ | --- | --- | --- |
817
+ | MOSS / TTSD | `nCtx`, `maxNewTokens` | Context and generation budgets; positive integers. Duration validation uses the configured token budget, reserving the native 33 delay/EOS rows. |
818
+ | MOSS / TTSD | `textTemperature`, `textTopP`, `textTopK` | Text-channel sampling. |
819
+ | MOSS / TTSD | `audioTemperature`, `audioTopP`, `audioTopK`, `audioRepetitionPenalty` | Independent audio-channel sampling. |
820
+ | Parler | `coremlDecoder` | Explicit decoder-LM Core ML opt-in. The separate DAC sidecar remains presence-driven. |
821
+ | Parler | `coremlComputeUnits`, `coremlStrict` | Compute policy (`all`, `cpu_and_ane`, `cpu_and_gpu`, `cpu_only`) and optional strict/fallback selection. Omission preserves native/environment policy. |
822
+ | Supertonic | `precision` | `auto`, `f32`, `f16`, `q8_0`. |
823
+ | Supertonic | `f16Attn`, `f16Weights`, `kvAttnType` | Native attention/weight precision overrides; `-1` leaves automatic selection. |
824
+ | Supertonic | `f16WeightsDenyList` | Tensor-name patterns retained at their original precision. |
825
+ | Supertonic | `vulkanEnvOverrides` | String-valued Vulkan environment overrides passed to the engine. Explicit entries take precedence over the `vulkanCacheDir` convenience setting. |
826
+ | Supertonic | `voiceStyleTtl`, `voiceStyleDp` | Paired flattened `number[]` or `Float32Array` voice tensors; the engine validates model-specific sizes. They take precedence over JSON and baked voices. |
827
+ | CosyVoice3 | `greedy`, `forceSpeechTokens` | Greedy LM decoding or an explicit integer speech-token trajectory for replay. An empty trajectory restores generation. |
828
+
829
+ After loading Supertonic, `await model.warmUp(text)` invokes the engine's
830
+ warm-up directly, completes without returning PCM, and leaves the loaded
831
+ instance available for normal synthesis. It shares the instance's exclusive
832
+ execution queue. Other engines reject the method.
833
+
834
+ CosyVoice3 responses expose owned `speechTokenIds` arrays. Batch output
835
+ includes them with PCM; native streaming delivers them on a final
836
+ `isLast: true` empty-PCM chunk. Retain those IDs and pass them to
837
+ `reload({ forceSpeechTokens: ids })` to replay the trajectory. The existing
838
+ numeric `response.stats.speechTokens` remains a count.
839
+
840
+ [`examples/fabric-controls.js`](examples/fabric-controls.js) accepts a JSON
841
+ constructor configuration through `QVAC_TTS_OPTIONS`, optional preflight
842
+ through `QVAC_TTS_FIT`, and `QVAC_TTS_WARM_UP=1` for Supertonic. For example:
843
+
844
+ ```bash
845
+ QVAC_TTS_OPTIONS='{"engine":"pocket","files":{"modelDir":"/models/pocket"},"config":{"useGPU":true}}' \
846
+ npm run example:fabric-controls -- "Speech runs on the selected backend." pocket.wav
847
+ ```
848
+
849
+ The example saves a WAV and prints stats. For CosyVoice3 it also saves
850
+ `<output>.tokens.json` containing the returned speech token IDs.
851
+
722
852
  ### Core ML sidecars on Apple
723
853
 
724
854
  The macOS / iOS prebuilds carry the Apple Core ML (Neural Engine) sidecars
725
- for the Supertonic vocoder, the Audio8 codec, and the MOSS models' fixed-shape
726
- stages. They are presence-driven:
855
+ for the Supertonic vocoder, the Audio8 codec, the Chatterbox S3Gen stages, and
856
+ the MOSS models' fixed-shape stages. They are presence-driven:
727
857
  each stage runs on a compiled `.mlmodelc` found next to its model file
728
858
  (`supertonic3-q8_0.gguf` -> `supertonic3-vocoder.mlmodelc`) and falls back to
729
859
  the ggml graph when it is absent, so a model directory without sidecars
@@ -738,13 +868,20 @@ published model set yet; supply your own to opt in.
738
868
  | MOSS, MOSS-TTSD | `moss-codec-decoder.mlmodelc`, beside the codec decoder GGUF | codec decoder, 25 frames (2 s) per call, keeping its attention cache between calls (macOS 15 / iOS 18) | non-streaming synthesis and streams with `streamChunkTokens` above 25, which ggml decodes faster in one pass | `MOSS_COREML_DISABLE=1` |
739
869
  | MOSS-SoundEffect | `moss-sfx-v2-dit.mlmodelc` and `moss-sfx-v2-vae.mlmodelc` | diffusion transformer, one velocity per call; VAE decoder in 320-frame windows | a call that fails on the sidecar, which retires it | `MOSS_COREML_DISABLE=1` |
740
870
  | MOSS-Speech | `moss-speech-codec-tokenizer.mlmodelc`, beside the codec GGUF | speech tokenizer (Whisper-VQ encoder), one full 30 s segment per call | segments shorter than 30 s, which ggml encodes faster; so most spoken questions | `MOSS_COREML_DISABLE=1` |
741
- | Chatterbox, Parler, CosyVoice3, LavaSR | none | — | always | — |
871
+ | Parler | `<model>-dac.mlmodelc` and optional `<model>-decoder.mlmodelc` (drop the GGUF quant suffix) | DAC; decoder steps when `coremlDecoder: true` (macOS 15 / iOS 18); T5 and prefill stay on ggml | a failed sidecar, unless strict mode rejects it; see [Parler Core ML sidecars](#parler-core-ml-sidecars) | `PARLER_COREML_DISABLE=1` |
872
+ | LavaSR | explicit `enhancer.coremlModelPath` / `denoiser.coremlModelPath` | denoiser and enhancer neural cores | only when Core ML options are absent; explicit load errors fail | omit both Core ML options |
873
+ | Chatterbox Turbo / Multilingual | `<s3gen>-encoder.mlmodelc`, `<s3gen>-cfm.mlmodelc` and `<s3gen>-hift.mlmodelc`, beside the S3Gen GGUF (`chatterbox-s3gen-q8_0.gguf` -> `chatterbox-s3gen-{encoder,cfm,hift}.mlmodelc`) | S3Gen encoder and CFM estimator, one function per width up to 1536 mel frames (voice prompt included); HiFT in 128-frame windows. With `maxSentenceChars` set, each sentence is vocoded while T3 decodes the next; T3 stays on the ggml backend | inputs past 1536 mel frames (encoder and CFM), and a call that fails on a sidecar, which also retires it | `CHATTERBOX_COREML_DISABLE=1` |
874
+ | CosyVoice3 | none | — | always | — |
742
875
 
743
876
  Set the force-ggml variables in the process environment before `load()`.
744
877
  Audio8 reports its codec path in `response.stats`: `codecSidecarLoaded` is 1
745
878
  while the sidecar is attached and `codecOnCoreml` is 1 when that synthesis
746
879
  ran its codec on it (see [Response shape](#response-shape)). The Supertonic
747
- vocoder path is not reported in the stats, and neither are the MOSS paths.
880
+ vocoder path is not reported in the stats, and neither are the Chatterbox and
881
+ MOSS paths. The Chatterbox stages run on the CPU and Neural Engine, keeping the
882
+ GPU for T3, unless `CHATTERBOX_COREML_COMPUTE_UNITS` (`all`, `cpu_and_gpu`,
883
+ `cpu_only`) says otherwise; `CHATTERBOX_COREML_STRICT=1` turns a fallback into
884
+ an error.
748
885
  The MOSS sidecars run on the GPU unless `MOSS_COREML_COMPUTE_UNITS` (`all`,
749
886
  `cpu_and_ane`, `cpu_only`) says otherwise: none of their stages ran faster on
750
887
  the Neural Engine, and the speech tokenizer loses code accuracy there.
@@ -767,12 +904,14 @@ The MOSS sidecars, per stage against Metal with f16 models:
767
904
 
768
905
  Export the sidecars from the model GGUFs with
769
906
  `engines/tts/scripts/export-supertonic-coreml.py`,
770
- `engines/tts/scripts/export-audio8-codec-coreml.py`, and
907
+ `engines/tts/scripts/export-audio8-codec-coreml.py`,
908
+ `engines/tts/scripts/export-chatterbox-coreml.py`, and
771
909
  `engines/tts/scripts/export-moss-{codec,sfx,speech-tokenizer}-coreml.py` from the
772
910
  [`qvac-fabric-speech.cpp`](https://github.com/tetherto/qvac-fabric-speech.cpp)
773
911
  tree at the ref `speech-cpp` pins. Its
774
912
  [Supertonic](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/supertonic.md#core-ml-vocoder-sidecar),
775
913
  [Audio8](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/audio8.md#core-ml-codec-sidecar),
914
+ [Chatterbox](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/chatterbox.md#core-ml-sidecars),
776
915
  and
777
916
  [MOSS](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/moss.md#core-ml-codec-decoder)
778
917
  guides cover export, placement, and measurements.
@@ -929,6 +1068,55 @@ Android GPU backend (`useGPU: true` / `nGpuLayers`), including Vulkan on ARM
929
1068
  Mali. Unsupported or unavailable backends fall back to CPU. It emits native
930
1069
  44.1 kHz.
931
1070
 
1071
+ ### Parler Core ML sidecars
1072
+
1073
+ On Apple platforms, Parler can use optional Core ML decoder and DAC sidecars.
1074
+ Set these **top-level constructor options**, or pass them to `reload()`:
1075
+
1076
+ ```js
1077
+ const model = new TTSGgml({
1078
+ files: { parlerModel: './models/parler-mini-v1-q8_0.gguf' },
1079
+ coremlDecoder: true,
1080
+ coremlComputeUnits: 'cpu_and_ane',
1081
+ coremlStrict: false,
1082
+ opts: { stats: true }
1083
+ })
1084
+ await model.load()
1085
+ await model.reload({ coremlDecoder: false })
1086
+ ```
1087
+
1088
+ `coremlDecoder` is a boolean; omitting it keeps the engine's default.
1089
+ `coremlComputeUnits` accepts `all`, `cpu_and_ane`, `cpu_and_gpu`, or `cpu_only`;
1090
+ omitting it inherits the engine's environment/default policy. `coremlStrict`
1091
+ is a boolean: omit it to inherit strict mode, or pass `false` to explicitly
1092
+ permit fallback. These options are Parler-only load settings and are rejected
1093
+ on per-call APIs. Reload options that fail validation are rejected before
1094
+ replacing the current addon.
1095
+ `assessFit()` accepts `coremlDecoder` to match the load's decoder policy.
1096
+
1097
+ Strict mode requires a usable DAC sidecar even when `coremlDecoder` is false;
1098
+ enabling the decoder also requires its sidecar. A build without Core ML fails
1099
+ in strict mode. Combining strict mode with `PARLER_COREML_DISABLE` remains a
1100
+ fatal configuration under the engine's existing policy. On reload, omitted or
1101
+ `undefined` settings retain their current values; an empty `coremlComputeUnits`
1102
+ string is invalid. To return to inherited compute-unit or strict policy,
1103
+ construct a new instance with the corresponding options omitted.
1104
+
1105
+ If the replacement model fails to load, `reload()` rejects and leaves the
1106
+ instance unloaded with the requested settings retained. Recover by reloading
1107
+ with a usable configuration or constructing a new instance.
1108
+
1109
+ With stats enabled, `decoderSidecarLoaded` and `dacSidecarLoaded` report `1`
1110
+ while the corresponding sidecar remains attached. `decoderOnCoreml` and
1111
+ `dacOnCoreml` report `1` only when that synthesis used the corresponding Core ML
1112
+ stage; ggml fallback and mixed Core ML/ggml execution report `0`. Sentence
1113
+ streams report Core ML execution only if every reported sentence used it;
1114
+ sidecar availability retains the last report. These numeric flags describe
1115
+ execution paths; they do not
1116
+ identify whether Core ML scheduled work on the CPU, GPU, or Neural Engine.
1117
+ `backendDevice` / `backendId` continue to identify the ggml backend, so a mixed
1118
+ pipeline does not report all stages as Core ML.
1119
+
932
1120
  ## CosyVoice3 instruct
933
1121
 
934
1122
  Beyond `emotion` / `pace` above, CosyVoice3 accepts a natural-language
@@ -982,6 +1170,19 @@ await model.load()
982
1170
  await model.run({ input: 'Hello from a fully on-device pipeline.' })
983
1171
  ```
984
1172
 
1173
+ `runStream(text)`, `runStreaming(asyncIterable)` and
1174
+ `run({ input: text, streamOutput: true })` emit PCM as codec windows finish,
1175
+ before the current text chunk's complete waveform is ready. Ordinary `run()`
1176
+ retains batch synthesis. Native end markers have an empty `outputArray`. Use `response.await()`
1177
+ for completion and aggregate statistics, including for asynchronous text input. PCM window sizes are fixed by the codec, so
1178
+ `streamChunkTokens` and `streamFirstChunkTokens` remain unsupported.
1179
+
1180
+ The native streaming API is included in the pinned `speech-cpp` dependency.
1181
+ Rebuild the addon to use it; an older published prebuild does not gain native
1182
+ streaming from a JavaScript update alone.
1183
+ Use [audio8-streaming.js](examples/audio8-streaming.js) to measure first PCM
1184
+ and completion through the wrapper, excluding model loading.
1185
+
985
1186
  Sampling is **repetition-aware**: a semantic token that repeats one from the
986
1187
  recent window is re-drawn under a narrower nucleus at a higher temperature,
987
1188
  which is what keeps the model out of the babble attractor that pure top-p
@@ -1005,6 +1206,15 @@ synthesis, the analysis half only to clone a voice or run a dialogue, so a
1005
1206
  text-only deployment can omit `files.mossCodecEncoder`. The backbones have 8B
1006
1207
  parameters, so MOSS targets desktop hosts; mobile is not supported.
1007
1208
 
1209
+ On Metal, the backbone projects only the text-control logits needed during
1210
+ audio generation and omits text projection during the forced drain. Embedding
1211
+ tables stay on the GPU, and the model weight type is preserved.
1212
+
1213
+ When `dialogueReferences` contains one recording, its encoded codes also serve
1214
+ as continuation, avoiding a duplicate encoder pass during engine creation.
1215
+ Multiple references retain their joined-audio continuation encoding. Prepared
1216
+ reference codes stay cached per instance for batch and streaming synthesis.
1217
+
1008
1218
  ```js
1009
1219
  const model = new TTSGgml({
1010
1220
  engine: TTSGgml.ENGINE_MOSS,
@@ -1182,6 +1392,15 @@ to 30 seconds. It is not a speech model, and it ships as one GGUF. Select it
1182
1392
  with `engine: TTSGgml.ENGINE_MOSS_SFX` or by staging `moss-sfx-*.gguf` in the
1183
1393
  `modelDir`.
1184
1394
 
1395
+ Native generation reuses fixed cross-attention K/V across diffusion steps and
1396
+ uses Metal DiT FlashAttention when the GPU supports both attention shapes.
1397
+ Memory preflight includes the request cache (360 MiB for guided generation
1398
+ with the released models) alongside the scheduler's retained peak across text
1399
+ encoding, diffusion and VAE decoding, including repeated requests.
1400
+ FlashAttention can slightly change waveform samples. See the
1401
+ [native SoundEffect guide](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/33174cc8693b1c8cdb44b5ff57b0f120bc89e454/engines/tts/docs/moss.md#moss-soundeffect)
1402
+ for precision details and the M1 Ultra measurements.
1403
+
1185
1404
  ```js
1186
1405
  const sfx = new TTSGgml({
1187
1406
  engine: TTSGgml.ENGINE_MOSS_SFX,
@@ -1317,7 +1536,7 @@ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1317
1536
  | `cfmSteps` | number | 2 | Chatterbox native-streaming CFM steps: 1 = faster (halved CFM cost) |
1318
1537
  | `batchCfmSteps` | number | engine default | Chatterbox-only batch CFM steps (`0` = 2-step meanflow on Turbo, the model's full schedule on multilingual). The multilingual engine does not floor it, so values well below its schedule under-integrate |
1319
1538
  | `nPredict` | number | 1000 | Chatterbox-only cap on speech tokens per synthesis (25 ≈ 1 s, so ~40 s by default). Longer single calls also need `nCtx` to fit, or use `maxSentenceChars` |
1320
- | `maxSentenceChars` | number | 0 | Chatterbox-only sentence auto-split: T3 + S3Gen run per segment of at most this many bytes (the reference CLI uses 180), so `nPredict` applies per segment |
1539
+ | `maxSentenceChars` | number | 0 | Chatterbox-only sentence auto-split: T3 + S3Gen run per segment of at most this many bytes (the reference CLI uses 180), so `nPredict` applies per segment; with [Chatterbox Core ML sidecars](#chatterbox-core-ml-sidecars), S3Gen vocodes a segment while T3 decodes the next |
1321
1540
  | `crossfadeMs` | number | 30 | Chatterbox-only raised-cosine crossfade between auto-split batch segments; native streaming stays gapless |
1322
1541
  | `repeatPenalty` | number | 1.2 | Chatterbox-only T3 repetition penalty (`1` = off) |
1323
1542
  | `exaggeration` / `cfgWeight` / `minP` | number | 0.5 / 0.5 / 0 | Chatterbox multilingual only (Turbo ignores them): prosody intensity (> 0 more expressive, < 0 flatter), T3 classifier-free guidance scale (`0` = off; `cfgRate` is the separate S3Gen rate), and the min-p sampling threshold |
@@ -1334,7 +1553,7 @@ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1334
1553
  | `pitch`, `expressivity`, `noise`, `reverb`, `quality` | string | — | Parler-only template fields (see [Parler descriptions](#parler-descriptions)); invalid values error listing the valid set |
1335
1554
  | `temperature` / `topK` / `topP` | number | engine defaults | Parler, Audio8 and Chatterbox sampling knobs (omit for the engine's own defaults — Parler temp 1.0 / top-k 50, Audio8 temp 0.7 / top-k 50 / top-p 0.9, Chatterbox temp 0.8 / top-k 1000 / top-p 0.95 where temp `0` is greedy and top-k `0` disables the cutoff; `topP` in `(0, 1]`) |
1336
1555
  | `maxFrames` | number | engine max | Parler + Audio8 generation cap in frames (Parler ~86/s of audio, Audio8 ~21.5/s); `0` = model default, Parler rejects 1–9 |
1337
- | `greedy` | boolean | `false` | Audio8-only: take the argmax instead of sampling; ignores `temperature` / `topK` / `topP` |
1556
+ | `greedy` | boolean | `false` | Audio8 / CosyVoice3: take the argmax instead of sampling; Audio8 ignores `temperature` / `topK` / `topP` |
1338
1557
  | `minNewTokens` | number | GGUF default | Parler-only minimum tokens before EOS (`-1` = model default) |
1339
1558
  | `normalizeNumbers` | boolean | `true` | Parler-only: expand digits before tokenization (English words; script-native digits on indic) — parler voices raw digits badly |
1340
1559
  | `instruct` | object \| string | — | CosyVoice3-only: instruction controls (`dialect` / `volume` / `style`, resolved by precedence in that order) or a raw instruction string; an unknown key or invalid value throws, and it counts toward the one-instruction rule (see [CosyVoice3 instruct](#cosyvoice3-instruct)). Also accepted per call, where it replaces the configured conditioning like a per-call `emotion` / `pace` |
@@ -1345,9 +1564,10 @@ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1345
1564
  | `openclCacheDir` | string | unset | Android-only: directory where the OpenCL backend persists its compiled program-binary cache. Setting it across runs avoids re-JITing the kernels on every fresh process |
1346
1565
  | `vulkanCacheDir` | string | unset | Supertonic + `useGPU: true` only: writable directory where the Vulkan backend persists its compiled pipeline cache (`GGML_VK_PIPELINE_CACHE_DIR`). Moves the one-time first-dispatch pipeline-compile cost (seconds on Mali) off the first `run()` — paid once per install instead of once per process — and enables a load-time pre-warm. Fully opt-in: unset -> no cross-process cache, no pre-warm, behaviour unchanged |
1347
1566
  | `config.language` | string | `"en"` | Chatterbox MTL accepts `es/fr/de/pt/it/zh/ja/ko/...`; turbo & Supertonic are English; MOSS takes it as a prompt hint (`en`, `zh`, ...) |
1567
+ | `config.backend` | string | unset | Native backend selector or exact registered device name; see [Backends](#backends--gpu-acceleration) |
1348
1568
  | `config.useGPU` | boolean | `false` | Set to `true` to route through Metal / CUDA / Vulkan / OpenCL if available. Honored for Chatterbox/Supertonic on GPU-capable hosts (including Android, per `tts-cpp`'s per-vendor allowlist); Parler is validated on Apple/Metal, linux CUDA, and Android/ARM Mali Vulkan; CosyVoice3 and Audio8 offload on Apple/Metal, desktop linux CUDA/Vulkan, Windows Vulkan, and Android OpenCL/Adreno; MOSS, MOSS-SoundEffect and MOSS-Speech GPU offload is validated on Apple/Metal. Unsupported backends fall back to CPU. See [Backends & GPU acceleration](#backends--gpu-acceleration) |
1349
1569
  | `config.outputSampleRate` | number | — (engine-native) | Resample the output to this rate (8000–192000 Hz). Omit to keep the engine-native rate (Chatterbox 24 kHz, Supertonic / Parler / Audio8 44.1 kHz, CosyVoice3 24 kHz, MOSS 24 kHz, MOSS-SoundEffect 48 kHz, MOSS-Speech 24 kHz, enhancer 48 kHz). Parler native chunk streaming accepts a non-native rate only with the enhancer active; MOSS, MOSS-SoundEffect and MOSS-Speech accept only their native rate |
1350
- | `opts.stats` | boolean | `false` | Populate `response.stats` with RTF, `backendDevice` (0=CPU, 1=GPU), `backendId` (0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 99=other), `enhancerBackendDevice` / `enhancerBackendId` and `denoiserBackendDevice` / `denoiserBackendId` (`-1` when that LavaSR stage is off), and the engine's own stage stats: Chatterbox `t3Ms` / `s3genMs` / `t3Tokens`; CosyVoice3 per-stage ms (`lmPrefillMs` … `hiftDecodeMs`, `stageTotalMs`) and work counters (`speechTokens`, `decodeSteps`, …); Audio8 per-stage ms (`prefillMs`, `fastDecodeMs`, `codecSynthMs`, …, `stageTotalMs`) |
1570
+ | `opts.stats` | boolean | `false` | Populate `response.stats` with RTF, `backendDevice` (0=CPU, 1=GPU), `backendId` (0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 5=WebGPU, 99=other), `enhancerBackendDevice` / `enhancerBackendId` and `denoiserBackendDevice` / `denoiserBackendId` (`-1` when that LavaSR stage is off; device `2` and id `99` for Core ML-managed placement), and the engine's own stage stats: Chatterbox `t3Ms` / `s3genMs` / `t3Tokens`; CosyVoice3 per-stage ms (`lmPrefillMs` … `hiftDecodeMs`, `stageTotalMs`) and work counters (`speechTokens`, `decodeSteps`, …); Audio8 per-stage ms (`prefillMs`, `fastDecodeMs`, `codecSynthMs`, …, `stageTotalMs`) |
1351
1571
  | `exclusiveRun` | boolean | `false` | **Top-level** option (not under `opts`): serialize overlapping streaming runs |
1352
1572
 
1353
1573
  ### Methods
@@ -1360,6 +1580,7 @@ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1360
1580
  config (`language`, `useGPU`, `outputSampleRate`, …).
1361
1581
  - `await model.destroy()` — `unload()` + mark this instance dead.
1362
1582
  - `await model.cancel()` — best-effort cancel of any in-flight run.
1583
+ - `await model.warmUp(text)` — Supertonic-only native warm-up without a PCM response.
1363
1584
  - `TTSGgml.getVoiceControls()` — static: tts-cpp's emotion / pace vocabulary
1364
1585
  and each engine's supported subset (see [Emotion & pace](#emotion--pace-cross-engine)).
1365
1586
  - `model.run({ input, type: 'text' })` → `QvacResponse`.
@@ -1413,14 +1634,16 @@ response.stats.generatedFrames // Audio8 and MOSS only: codec frames (Audio8 e
1413
1634
  response.stats.codecSidecarLoaded // Audio8 only, macOS / iOS: 1 while the Apple Core ML codec sidecar is attached
1414
1635
  response.stats.codecOnCoreml // Audio8 only: 1 when this synthesis ran the codec on that sidecar, 0 when on the ggml backend
1415
1636
  response.stats.backendDevice // 0=CPU, 1=GPU
1416
- response.stats.backendId // 0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 99=other
1637
+ response.stats.backendId // 0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 5=WebGPU, 99=other
1417
1638
  // present when a LavaSR enhancer is active:
1418
1639
  response.stats.enhancerBackendDevice // -1 none, 0 CPU, 1 GPU
1419
1640
  response.stats.enhancerBackendId
1420
1641
  ```
1421
1642
 
1422
- For `runStream()` and `runStreaming()`, `codecSidecarLoaded` and
1423
- `codecOnCoreml` retain the last chunk that reported each field, including zero
1643
+ For `runStream()` and `runStreaming()`, the backend fields (`backendDevice`,
1644
+ `backendId`, `gpuUnsupported`, and the LavaSR `enhancerBackend*` and
1645
+ `denoiserBackend*` pairs) and the Audio8 codec fields (`codecSidecarLoaded`,
1646
+ `codecOnCoreml`) retain the last chunk that reported each field, including zero
1424
1647
  when a sidecar is retired or synthesis falls back to ggml. They are not summed;
1425
1648
  if no chunk reports a field, it remains absent.
1426
1649
 
@@ -1476,20 +1699,17 @@ const fit = await TTSGgml.assessFit({
1476
1699
  audioSeconds: 30
1477
1700
  })
1478
1701
 
1479
- fit.status // 'fits' | 'does-not-fit' | 'error'
1480
- fit.reason // the engine's own wording, e.g. 'model-unreadable', 'workload-too-large'
1481
- fit.modelVariant // which pipeline was projected, e.g. 'chatterbox-t3-turbo'
1482
- fit.deviceName
1483
- fit.deviceBytes
1484
- fit.weightsBytes
1485
- fit.stateBytes
1486
- fit.lmComputeBytes // 0 for a pipeline with no language-model stage, such as supertonic
1487
- fit.codecComputeBytes // 0 for a pipeline with no separate codec stage
1488
- fit.hostBytes
1489
- fit.lavasrFileBytes // the LavaSR stages' size on disk, 0 when none was named
1490
- fit.report
1702
+ fit.status // 'fits' | 'does-not-fit' | 'unknown'
1703
+ fit.devices // [{ id, name, unified, usedBytes, freeBytes, totalBytes }]
1704
+ fit.stats // {}
1491
1705
  ```
1492
1706
 
1707
+ `devices` holds the device the load resolves to, then the host CPU, which holds the host-side buffers. A load on the CPU is one row. `id` is the PCI bus ID when the backend reports one, so the same GPU matches across addons built on different backends, and the device name otherwise. `unified` is true for memory the host owns: the CPU, an integrated GPU, a GPU that shares the host's memory, Android's OpenCL GPU, and a device that reports no memory of its own, which takes the host's figures. `usedBytes` is what the load takes on that device.
1708
+
1709
+ On an iOS device every unified row shares the app's own memory limit, `os_proc_available_memory()`. Each such row's free memory is capped at that limit, and a load whose unified rows together take more than the limit less the margin does not fit. A limit of 0 means the app has none left.
1710
+
1711
+ `unknown` is a load the fitter cannot size, and it carries no rows: supertonic outside the fused graph path it models, which is a validated GPU, or a CPU without the Accelerate pointwise kernels; a CosyVoice3 or Parler load with Core ML sidecars; or a load whose memory the engine fails to measure.
1712
+
1493
1713
  `engineType` picks the fitter, the same key a load uses, and is required: a fit request carries none of the file keys a load is inferred from. Each engine takes the load's own file keys, plus a workload the fit adds:
1494
1714
 
1495
1715
  | `engineType` | Files | Workload |
@@ -1499,16 +1719,15 @@ fit.report
1499
1719
  | `chatterbox` | `t3ModelPath`, `s3genModelPath` | `textTokens`, `predictTokens` |
1500
1720
  | `audio8` | `audio8LmPath`, `audio8CodecDecoderPath`, `audio8CodecEncoderPath` | `promptTokens`, `maxFrames`, `referenceSeconds` |
1501
1721
  | `cosyvoice3` | `cosyvoiceLlmModelPath`, `cosyvoiceFlowModelPath`, `cosyvoiceHiftModelPath`, `cosyvoiceVoiceModelPath` | `textTokens`, `speechTokens` |
1722
+ | `pocket` | `pocketFlowModelPath`, `pocketMimiModelPath`, `pocketFrontendPath`, optional `pocketVoicePath` / `referenceAudio` | Required `text`; optional `memoryBudgetBytes`, `marginBytes`, and native load controls |
1502
1723
  | `moss` | `mossBackbonePath`, `mossCodecDecoderPath`, optional `mossCodecEncoderPath` | Required `promptRows`, `referenceSamples`, `streaming`; load controls `durationTokens`, `streamChunkTokens`, `threads` |
1503
1724
  | `moss-sfx` | Required `mossSoundEffectPath` | Required `prompt`, `seconds`; optional `negativePrompt`, `steps`, `guidance`, `shift`, `threads` |
1504
1725
 
1505
1726
  Everything else comes from the load config: `nGpuLayers` and `useGPU` carry the offload intent, `nCtx` and `kvCacheType` size the chatterbox cache, `steps` takes the GGUF's own default at 0, and `vulkanDevice` and `backendsDir` place the backend. Supplying `audio8CodecEncoderPath` projects voice cloning, which the decoder alone cannot do. `marginBytes` sets the free memory that must remain for the projection to count as fitting.
1506
1727
 
1507
- `lavasrEnhancerPath` and `lavasrDenoiserPath` are reported as `lavasrFileBytes`, their size on disk. Those stages have no fitter, so the figure is a size on disk. It is counted in neither `deviceBytes` nor `hostBytes`.
1508
-
1509
- `deviceSharesHostMemory` reports that the device pool is system RAM, so host bytes compete with device bytes.
1728
+ The LavaSR enhancer and denoiser have no fitter, and the request takes no LavaSR paths. The estimate for a load with LavaSR stages leaves those stages out.
1510
1729
 
1511
- A model the engine cannot read resolves to `status: "error"`, as does a voice with no fitter, which reports `reason: "unsupported-engine"`. MOSS-TTS, MOSS-TTSD and MOSS-SoundEffect are supported. MOSS-Speech has no SDK fit projection. A broken request, or a host with no native binding, rejects.
1730
+ A model the engine cannot read, a workload it cannot project, a voice with no fitter, or a broken request rejects with an `InvalidArgument` error, and a host with no native binding rejects as well. MOSS-TTS, MOSS-TTSD and MOSS-SoundEffect are supported. MOSS-Speech has no fitter.
1512
1731
 
1513
1732
  MOSS-TTS and MOSS-TTSD share the `moss` fitter. `promptRows` is the complete
1514
1733
  native prompt length, including special tokens, encoded speaker references and
@@ -1536,8 +1755,6 @@ as a conservative upper bound, including the reference encoder when requested;
1536
1755
  this may reject a workload whose actual peak is smaller. The fit follows the
1537
1756
  load's generation settings and does not change synthesis behavior.
1538
1757
 
1539
- The supertonic fitter covers the fused graph path: a validated GPU, or a CPU without the Accelerate pointwise kernels. Elsewhere it answers `compute-path-not-supported` and projects nothing.
1540
-
1541
1758
  ### MOSS-SoundEffect fit
1542
1759
 
1543
1760
  ```js
@@ -1548,7 +1765,7 @@ const fit = await TTSGgml.assessFit({
1548
1765
  seconds: 8,
1549
1766
  useGPU: false
1550
1767
  })
1551
- console.log(fit.report)
1768
+ console.log(fit.devices)
1552
1769
  ```
1553
1770
 
1554
1771
  The fitter uses generation's tokenizer, request validation and graph builders,
@@ -1556,8 +1773,8 @@ without loading weights or generating audio. It supports full and metadata-only
1556
1773
  GGUF files. `negativePrompt`, `steps`, `guidance` and `shift` keep their generation
1557
1774
  semantics; `marginBytes` keeps the shared 256 MiB default. DiT processes the model's
1558
1775
  full latent duration even for a short output. Requested seconds size decoder
1559
- windows and audio buffers. The shared compute arena is counted at its peak:
1560
- `lmComputeBytes` covers text/DiT, `codecComputeBytes` any additional VAE demand.
1776
+ windows and audio buffers. The shared compute arena for text/DiT and any
1777
+ additional VAE demand is counted at its peak.
1561
1778
  Host memory includes conditioning, temporary arrays and CPU fallback buffers.
1562
1779
 
1563
1780
  Run `bare examples/moss-sfx-fit.js ./moss-sfx-v2-q8_0.gguf "Rain on a roof." 8`.
@@ -1580,11 +1797,13 @@ Runnable demos under `examples/`:
1580
1797
  | `supertonic-mtl-sweep-tts.js` | Multilingual Supertonic sweep across languages |
1581
1798
  | `supertonic-sentence-stream-tts.js` | Supertonic sentence-level streaming |
1582
1799
  | `supertonic-chunk-stream-tts.js` | Supertonic native chunk streaming via `streamChunkTokens`, with streaming playback |
1800
+ | `lavasr-coreml.js` | Supertonic + explicit Core ML denoiser/enhancer. Run without arguments for usage. |
1583
1801
  | `supertonic-enhanced.js` | Supertonic + LavaSR 48 kHz enhancement. `bare examples/supertonic-enhanced.js "Hello"` |
1584
1802
  | `parler-tts.js` | Parler batch synth with voice/emotion templates. `bare examples/parler-tts.js "Hello" Laura happy` |
1585
1803
  | `parler-enhanced.js` | Parler + LavaSR 48 kHz enhancement. `bare examples/parler-enhanced.js "Hello" Laura happy` |
1586
1804
  | `cosyvoice-tts.js` | CosyVoice3 batch synth with the cross-engine emotion option (24 kHz; CPU by default, `--gpu` opts into Metal / desktop Vulkan / Android GPU). `bare examples/cosyvoice-tts.js --gpu "Hello" happy` |
1587
1805
  | `cosyvoice-enhanced.js` | CosyVoice3 + LavaSR 48 kHz enhancement (add `--denoise` for the denoiser). `bare examples/cosyvoice-enhanced.js "Hello"` |
1806
+ | `audio8-streaming.js` | Measure Audio8 wrapper batch, streaming, or duplex latency: `bare examples/audio8-streaming.js ./models stream "Hello"`. Modes: `batch`, `stream`, `duplex`. |
1588
1807
  | `audio8-tts.js` | Audio8 batch synth, optionally cloning a reference. Set `QVAC_TTS_AUDIO8_GPU=1` for the desktop GPU backend (CUDA or Vulkan on linux, Vulkan on Windows). `bare examples/audio8-tts.js "Hello" voice.wav "What it says."` |
1589
1808
  | `moss-tts.js` | MOSS batch synth, optionally cloning a reference. Set `QVAC_TTS_MOSS_STREAM_FRAMES=25` for native chunk streaming, `QVAC_TTS_MOSS_DURATION=38` for a target length and `QVAC_TTS_MOSS_GPU=1` for the GPU backend. `bare examples/moss-tts.js "Hello" voice.wav` |
1590
1809
  | `moss-sound-effect.js` | MOSS-SoundEffect text-to-sound-effects at 48 kHz. Set `QVAC_TTS_MOSS_SFX_GPU=1` for the GPU backend and `QVAC_TTS_MOSS_SFX_STEPS` to trade quality for time. `bare examples/moss-sound-effect.js "Rain on a tin roof." 5` |
@@ -1756,9 +1975,10 @@ Apache-2.0. See [LICENSE](./LICENSE).
1756
1975
 
1757
1976
  ## Pocket TTS
1758
1977
 
1759
- The native `speech-cpp` dependency includes Pocket Metal, CUDA, Vulkan and
1760
- OpenCL support in the native library. The JavaScript Pocket API continues
1761
- to require `config.useGPU: false`; updating the native dependency does not
1762
- remove that addon validation.
1978
+ Pocket supports CPU, Metal, CUDA, Vulkan and OpenCL through the addon.
1979
+ CPU remains the default. Set `config.useGPU: true`, `nGpuLayers`, or
1980
+ `config.backend` to select an accelerator; runtime stats identify the
1981
+ backend actually selected. Metadata-only `assessFit()` uses the same
1982
+ native configuration and accepts an optional application memory budget.
1763
1983
 
1764
1984
  See [Pocket TTS](docs/pocket-tts.md) for model conversion, addon/SDK usage, supported controls and validation commands.
@@ -1,8 +1,10 @@
1
1
  # Pocket TTS
2
2
 
3
3
  Pocket is an English TTS engine implemented in the Fabric speech library.
4
- The native library supports CPU, Metal, CUDA, Vulkan and OpenCL; the
5
- `@qvac/tts-ggml` JavaScript API currently accepts CPU inference only.
4
+ The native library and `@qvac/tts-ggml` addon support CPU, Metal, CUDA,
5
+ Vulkan and OpenCL. CPU remains the default; set `config.useGPU: true`
6
+ or a nonzero `nGpuLayers` to request GPU inference. `config.backend`
7
+ can select an exact registered device such as `MTL0`.
6
8
  It streams PCM16 audio through the addon. Python
7
9
  is used only to convert weights and benchmark the upstream reference; it is
8
10
  not needed for native synthesis. Inference plugin and public SDK integration
@@ -71,13 +73,39 @@ chunk with `isLast: true`; other engines may omit this metadata.
71
73
 
72
74
  ## Supported controls
73
75
 
74
- English only; CPU only; `threads` defaults to one per worker (two workers).
76
+ English only; `threads` defaults to one per worker (two workers).
75
77
  `seed` accepts unsigned 32-bit integers. `temperature`, `steps`, `nCtx`,
76
78
  `maxTokens`, `noiseClamp`, `eosThreshold`, `framesAfterEos` and
77
79
  `outputSampleRate` are validated before native loading. Output resampling
78
- supports 8–192 kHz. Other engines' sampling, emotional, speed, GPU and
80
+ supports 8–192 kHz. Other engines' sampling, emotional, speed and
79
81
  LavaSR settings are rejected instead of silently ignored.
80
82
 
83
+ `config.useGPU` and `nGpuLayers` must agree when supplied together.
84
+ Reloading preserves the selected backend unless it is explicitly changed.
85
+
86
+ ### Memory preflight
87
+
88
+ `assessFit()` reads GGUF metadata and frontend/voice metadata without loading
89
+ weights. Pass the same backend and workload that synthesis will use:
90
+
91
+ ```js
92
+ const fit = await TTSGgml.assessFit({
93
+ engineType: 'pocket',
94
+ pocketFlowModelPath: '/models/pocket/flow-lm.gguf',
95
+ pocketMimiModelPath: '/models/pocket/mimi.gguf',
96
+ pocketFrontendPath: '/models/pocket/frontend.json',
97
+ pocketVoicePath: '/models/pocket/voice.gguf',
98
+ text: 'Hello from Pocket.',
99
+ useGPU: true
100
+ })
101
+ ```
102
+
103
+ `marginBytes` leaves the requested memory headroom. Optional
104
+ `memoryBudgetBytes` applies an application ceiling to each memory pool;
105
+ zero or omission retains the native telemetry-based budget. Results use
106
+ the common `fits`, `does-not-fit` and `error` statuses. The projection
107
+ does not load a model or change a synthesis setting.
108
+
81
109
  Fabric defaults to one sampling step, matching the native CLI and upstream
82
110
  reference. An explicit `steps: 4` (or
83
111
  addon `numInferenceSteps: 4`) offers a quality option at additional compute
@@ -0,0 +1,74 @@
1
+ 'use strict'
2
+
3
+ const TTSGgml = require('..')
4
+ const process = require('bare-process')
5
+
6
+ const DEFAULT_TEXT = 'The first audio should arrive while the rest of this response is generated.'
7
+ const MODES = ['batch', 'stream', 'duplex']
8
+
9
+ async function* textSource(text) {
10
+ yield text
11
+ }
12
+
13
+ function start(model, mode, text) {
14
+ if (mode === 'batch') return model.run({ input: text })
15
+ if (mode === 'stream') return model.runStream(text, { maxChunkScalars: Array.from(text).length })
16
+ return model.runStreaming(textSource(text), { accumulateSentences: false })
17
+ }
18
+
19
+ async function measure(model, mode, text) {
20
+ const started = Date.now()
21
+ let firstAudioMs = null
22
+ let samples = 0
23
+ let sampleRate = 0
24
+ let chunks = 0
25
+ const response = await start(model, mode, text)
26
+ response.onUpdate((data) => {
27
+ if (!data.outputArray?.length) return
28
+ firstAudioMs ??= Date.now() - started
29
+ samples += data.outputArray.length
30
+ sampleRate = data.sampleRate
31
+ chunks++
32
+ })
33
+ await response.await()
34
+ if (firstAudioMs === null || sampleRate <= 0) throw new Error('Synthesis returned no audio')
35
+ console.log(
36
+ JSON.stringify({
37
+ mode,
38
+ firstAudioMs,
39
+ totalMs: Date.now() - started,
40
+ audioSeconds: samples / sampleRate,
41
+ chunks,
42
+ stats: response.stats
43
+ })
44
+ )
45
+ }
46
+
47
+ async function main() {
48
+ const [, , modelDir, mode = 'stream', text = DEFAULT_TEXT] = process.argv
49
+ if (!modelDir || !MODES.includes(mode)) {
50
+ throw new Error(
51
+ 'Usage: bare examples/audio8-streaming.js MODEL_DIR [batch|stream|duplex] [TEXT]'
52
+ )
53
+ }
54
+ const model = new TTSGgml({
55
+ engine: TTSGgml.ENGINE_AUDIO8,
56
+ files: { modelDir },
57
+ greedy: true,
58
+ seed: 42,
59
+ threads: 4,
60
+ config: { useGPU: process.env.QVAC_TTS_AUDIO8_GPU === '1' },
61
+ opts: { stats: true }
62
+ })
63
+ try {
64
+ await model.load()
65
+ await measure(model, mode, text)
66
+ } finally {
67
+ await model.unload()
68
+ }
69
+ }
70
+
71
+ main().catch((error) => {
72
+ console.error(error)
73
+ process.exitCode = 1
74
+ })