@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 +260 -40
- package/docs/pocket-tts.md +32 -4
- package/examples/audio8-streaming.js +74 -0
- package/examples/fabric-controls.js +71 -0
- package/examples/lavasr-coreml.js +76 -0
- package/examples/moss-sfx-fit.js +1 -1
- package/index.d.ts +6 -0
- package/index.js +27 -2
- package/lib/engineParams.js +14 -0
- package/lib/engineSetup.d.ts +8 -1
- package/lib/engines/audio8.js +1 -1
- package/lib/engines/moss.d.ts +1 -1
- package/lib/engines/moss.js +8 -7
- package/lib/engines/parler.d.ts +13 -0
- package/lib/engines/parler.js +54 -1
- package/lib/fabricOptions.d.ts +29 -0
- package/lib/fabricOptions.js +133 -0
- package/lib/fit.d.ts +46 -36
- package/lib/fit.js +5 -3
- package/lib/jobFields.js +1 -0
- package/lib/nativeParams.js +12 -0
- package/lib/pocketConfig.js +27 -4
- package/lib/pocketLifecycle.js +9 -1
- package/lib/reload.js +19 -0
- package/lib/sentenceStream.d.ts +15 -3
- package/lib/sentenceStream.js +49 -5
- package/lib/setup.js +38 -2
- package/lib/streamingSupport.js +2 -2
- package/lib/types.d.ts +34 -13
- package/package.json +12 -11
- package/test/integration/audio8-streaming.test.js +66 -0
- package/test/integration/fabric-controls.test.js +124 -0
- package/test/integration/fit.test.js +67 -77
- package/test/integration/moss-fit.test.js +7 -7
- package/test/integration/moss-sfx-fit.test.js +7 -20
- package/test/integration/native-options.test.js +41 -0
- package/tts.d.ts +2 -1
package/lib/fit.d.ts
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
* the same keys and applies the same defaults as the load it describes.
|
|
5
5
|
*/
|
|
6
6
|
interface TtsFitCommon {
|
|
7
|
+
backend?: string;
|
|
7
8
|
/** Layers to offload. Unset defers to `useGPU`. */
|
|
8
9
|
nGpuLayers?: number;
|
|
9
10
|
/** All layers or none, when `nGpuLayers` is unset. */
|
|
10
11
|
useGPU?: boolean;
|
|
11
12
|
vulkanDevice?: number;
|
|
12
13
|
backendsDir?: string;
|
|
13
|
-
/** LavaSR enhancer; a non-empty path turns enhancement on. */
|
|
14
|
-
lavasrEnhancerPath?: string;
|
|
15
|
-
/** LavaSR denoiser, applied before the enhancer. */
|
|
16
|
-
lavasrDenoiserPath?: string;
|
|
17
14
|
/** Free memory that must remain for the projection to count as fitting. Defaults to 256 MiB. */
|
|
18
15
|
marginBytes?: number;
|
|
19
16
|
}
|
|
@@ -32,6 +29,8 @@ export interface SupertonicFitRequest extends TtsFitCommon {
|
|
|
32
29
|
export interface ParlerFitRequest extends TtsFitCommon {
|
|
33
30
|
engineType: 'parler';
|
|
34
31
|
parlerModelPath: string;
|
|
32
|
+
/** Match the Parler decoder-sidecar load policy. */
|
|
33
|
+
coremlDecoder?: boolean;
|
|
35
34
|
descriptionTokens?: number;
|
|
36
35
|
promptTokens?: number;
|
|
37
36
|
/** 0 takes the checkpoint's default generation length. */
|
|
@@ -67,6 +66,8 @@ export interface CosyvoiceFitRequest extends TtsFitCommon {
|
|
|
67
66
|
speechTokens?: number;
|
|
68
67
|
}
|
|
69
68
|
export interface MossFitRequest extends TtsFitCommon {
|
|
69
|
+
nCtx?: number;
|
|
70
|
+
maxNewTokens?: number;
|
|
70
71
|
engineType: 'moss';
|
|
71
72
|
mossBackbonePath: string;
|
|
72
73
|
mossCodecDecoderPath: string;
|
|
@@ -92,41 +93,50 @@ export interface MossSoundEffectFitRequest extends TtsFitCommon {
|
|
|
92
93
|
shift?: number;
|
|
93
94
|
threads?: number;
|
|
94
95
|
}
|
|
95
|
-
export
|
|
96
|
+
export interface PocketFitRequest extends TtsFitCommon {
|
|
97
|
+
engineType: 'pocket';
|
|
98
|
+
pocketFlowModelPath: string;
|
|
99
|
+
pocketMimiModelPath: string;
|
|
100
|
+
pocketFrontendPath: string;
|
|
101
|
+
pocketVoicePath?: string;
|
|
102
|
+
referenceAudio?: string;
|
|
103
|
+
text: string;
|
|
104
|
+
threads?: number;
|
|
105
|
+
nCtx?: number;
|
|
106
|
+
maxTokens?: number;
|
|
107
|
+
steps?: number;
|
|
108
|
+
framesAfterEos?: number;
|
|
109
|
+
memoryBudgetBytes?: number;
|
|
110
|
+
}
|
|
111
|
+
export type TtsFitRequest = PocketFitRequest | SupertonicFitRequest | ParlerFitRequest | ChatterboxFitRequest | Audio8FitRequest | CosyvoiceFitRequest | MossFitRequest | MossSoundEffectFitRequest;
|
|
96
112
|
/** The voice engines a fit can be asked for, read off the request union. */
|
|
97
113
|
export type TtsFitEngine = TtsFitRequest['engineType'];
|
|
98
|
-
|
|
114
|
+
/**
|
|
115
|
+
* `unknown` is a load the fitter cannot size: supertonic outside the fused
|
|
116
|
+
* graph path it models, a CosyVoice3 or Parler load with Core ML sidecars, or a failed
|
|
117
|
+
* measurement.
|
|
118
|
+
*/
|
|
119
|
+
export type TtsFitStatus = 'fits' | 'does-not-fit' | 'unknown';
|
|
120
|
+
export interface TtsFitDevice {
|
|
121
|
+
/** The PCI bus ID when the backend reports one, the device name otherwise. */
|
|
122
|
+
id: string;
|
|
123
|
+
name: string;
|
|
124
|
+
/** Memory the host owns: the host CPU, and a GPU that shares the host's memory. */
|
|
125
|
+
unified: boolean;
|
|
126
|
+
/** Memory the load takes on this device. */
|
|
127
|
+
usedBytes: number;
|
|
128
|
+
freeBytes: number;
|
|
129
|
+
totalBytes: number;
|
|
130
|
+
}
|
|
131
|
+
export type TtsFitStats = Record<string, never>;
|
|
99
132
|
export interface TtsFitResult {
|
|
100
133
|
status: TtsFitStatus;
|
|
101
134
|
/**
|
|
102
|
-
* The
|
|
103
|
-
*
|
|
104
|
-
* not cover.
|
|
105
|
-
*/
|
|
106
|
-
reason: string;
|
|
107
|
-
/** Which pipeline was projected, e.g. `chatterbox-t3-turbo`. */
|
|
108
|
-
modelVariant: string;
|
|
109
|
-
deviceName: string;
|
|
110
|
-
deviceIsCpu: boolean;
|
|
111
|
-
/** The device pool is system RAM, so host bytes compete with device bytes. */
|
|
112
|
-
deviceSharesHostMemory: boolean;
|
|
113
|
-
deviceFreeBytes: number;
|
|
114
|
-
deviceTotalBytes: number;
|
|
115
|
-
deviceBytes: number;
|
|
116
|
-
weightsBytes: number;
|
|
117
|
-
stateBytes: number;
|
|
118
|
-
/** 0 for a pipeline with no language-model stage, such as supertonic. */
|
|
119
|
-
lmComputeBytes: number;
|
|
120
|
-
/** 0 for a pipeline with no separate codec stage. */
|
|
121
|
-
codecComputeBytes: number;
|
|
122
|
-
hostBytes: number;
|
|
123
|
-
/**
|
|
124
|
-
* The LavaSR stages' size on disk, 0 when none was named. They have no
|
|
125
|
-
* fitter, so this is a size on disk. It is counted in neither `deviceBytes`
|
|
126
|
-
* nor `hostBytes`.
|
|
135
|
+
* The device the load resolves to, then the host CPU. One CPU row for a load
|
|
136
|
+
* on the CPU, and none for `unknown`.
|
|
127
137
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
devices: TtsFitDevice[];
|
|
139
|
+
stats: TtsFitStats;
|
|
130
140
|
}
|
|
131
141
|
/**
|
|
132
142
|
* Projects one voice against the memory free right now, reading GGUF metadata
|
|
@@ -139,9 +149,9 @@ export interface TtsFitResult {
|
|
|
139
149
|
*
|
|
140
150
|
* `engineType` picks the fitter, the same key a load uses, and is required
|
|
141
151
|
* here: a fit request carries none of the file keys a load is inferred from.
|
|
142
|
-
* A model the engine cannot read
|
|
143
|
-
*
|
|
144
|
-
*
|
|
152
|
+
* A model the engine cannot read, a voice with no fitter, an invalid request,
|
|
153
|
+
* or a host with no native binding rejects. Fits run one at a time within this
|
|
154
|
+
* addon.
|
|
145
155
|
*/
|
|
146
156
|
export declare function assessFit(request: TtsFitRequest): Promise<TtsFitResult>;
|
|
147
157
|
export {};
|
package/lib/fit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.assessFit = assessFit;
|
|
4
|
+
const fabricOptions_1 = require("./fabricOptions");
|
|
4
5
|
/**
|
|
5
6
|
* Projects one voice against the memory free right now, reading GGUF metadata
|
|
6
7
|
* and never weight data. The registry's weightless copy of each file answers
|
|
@@ -12,11 +13,12 @@ exports.assessFit = assessFit;
|
|
|
12
13
|
*
|
|
13
14
|
* `engineType` picks the fitter, the same key a load uses, and is required
|
|
14
15
|
* here: a fit request carries none of the file keys a load is inferred from.
|
|
15
|
-
* A model the engine cannot read
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* A model the engine cannot read, a voice with no fitter, an invalid request,
|
|
17
|
+
* or a host with no native binding rejects. Fits run one at a time within this
|
|
18
|
+
* addon.
|
|
18
19
|
*/
|
|
19
20
|
async function assessFit(request) {
|
|
21
|
+
(0, fabricOptions_1.validateBackendOption)(request.engineType, request.backend);
|
|
20
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- the native binding loads on first use.
|
|
21
23
|
const binding = require('../binding.js');
|
|
22
24
|
return await binding.assessFit(request);
|
package/lib/jobFields.js
CHANGED
|
@@ -50,6 +50,7 @@ function assertNoOtherEngineFields(setup, source, where) {
|
|
|
50
50
|
* and every engine the cross-engine conditioning it supports.
|
|
51
51
|
*/
|
|
52
52
|
function resolveJobFields(setup, source, where) {
|
|
53
|
+
(0, parler_1.assertNoPerCallParlerCoreml)(source, where);
|
|
53
54
|
if (setup.engineType === engineIds_1.ENGINE_MOSS_SPEECH) {
|
|
54
55
|
assertNoOtherEngineFields(setup, source, where);
|
|
55
56
|
return (0, mossSpeech_1.resolveSpeechJobFields)(source, where);
|
package/lib/nativeParams.js
CHANGED
|
@@ -70,4 +70,16 @@ function assignLavasrParams(setup, parameters) {
|
|
|
70
70
|
if (setup.backend.denoiserGgufPath) {
|
|
71
71
|
parameters.lavasrDenoiserPath = setup.backend.denoiserGgufPath;
|
|
72
72
|
}
|
|
73
|
+
if (setup.backend.enhancerCoremlModelPath !== undefined) {
|
|
74
|
+
parameters.lavasrEnhancerCoremlModelPath =
|
|
75
|
+
setup.backend.enhancerCoremlModelPath;
|
|
76
|
+
parameters.lavasrEnhancerCoremlComputeUnits =
|
|
77
|
+
setup.backend.enhancerCoremlComputeUnits;
|
|
78
|
+
}
|
|
79
|
+
if (setup.backend.denoiserCoremlModelPath !== undefined) {
|
|
80
|
+
parameters.lavasrDenoiserCoremlModelPath =
|
|
81
|
+
setup.backend.denoiserCoremlModelPath;
|
|
82
|
+
parameters.lavasrDenoiserCoremlComputeUnits =
|
|
83
|
+
setup.backend.denoiserCoremlComputeUnits;
|
|
84
|
+
}
|
|
73
85
|
}
|
package/lib/pocketConfig.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.buildPocketParams = buildPocketParams;
|
|
7
7
|
const bare_path_1 = __importDefault(require("bare-path"));
|
|
8
|
+
const fabricOptions_1 = require("./fabricOptions");
|
|
8
9
|
const DEFAULT_SAMPLING_STEPS = 1;
|
|
9
10
|
const UNSUPPORTED_OPTIONS = [
|
|
10
11
|
'voice',
|
|
@@ -36,6 +37,9 @@ const UNSUPPORTED_OPTIONS = [
|
|
|
36
37
|
'maxFrames',
|
|
37
38
|
'minNewTokens',
|
|
38
39
|
'normalizeNumbers',
|
|
40
|
+
'coremlDecoder',
|
|
41
|
+
'coremlComputeUnits',
|
|
42
|
+
'coremlStrict',
|
|
39
43
|
'enhancer',
|
|
40
44
|
'denoiser'
|
|
41
45
|
];
|
|
@@ -69,14 +73,21 @@ function resolveModelFiles(files, options) {
|
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
function validateEngineOptions(options, config) {
|
|
76
|
+
(0, fabricOptions_1.resolveFabricOptions)('pocket', options);
|
|
77
|
+
(0, fabricOptions_1.validateBackendOption)('pocket', config.backend);
|
|
78
|
+
if (config.useGPU !== undefined && typeof config.useGPU !== 'boolean')
|
|
79
|
+
throw new Error('Pocket useGPU must be a boolean');
|
|
80
|
+
if (options.nGpuLayers !== undefined &&
|
|
81
|
+
(typeof options.nGpuLayers !== 'number' || !Number.isInteger(options.nGpuLayers) ||
|
|
82
|
+
options.nGpuLayers < -2147483648 || options.nGpuLayers > 2147483647))
|
|
83
|
+
throw new Error('Pocket nGpuLayers must be a 32-bit integer');
|
|
84
|
+
if (typeof config.useGPU === 'boolean' && typeof options.nGpuLayers === 'number' &&
|
|
85
|
+
config.useGPU !== (options.nGpuLayers !== 0))
|
|
86
|
+
throw new Error('Pocket useGPU conflicts with nGpuLayers');
|
|
72
87
|
if (options.referenceAudio !== undefined && typeof options.referenceAudio !== 'string')
|
|
73
88
|
throw new Error('Pocket referenceAudio must be a path string');
|
|
74
89
|
if (config.language !== undefined && config.language !== 'en')
|
|
75
90
|
throw new Error('Pocket currently supports English (en) only');
|
|
76
|
-
if (config.useGPU !== undefined && config.useGPU !== false)
|
|
77
|
-
throw new Error('Pocket currently supports CPU only (useGPU: false)');
|
|
78
|
-
if (options.nGpuLayers !== undefined && options.nGpuLayers !== 0)
|
|
79
|
-
throw new Error('Pocket requires nGpuLayers: 0');
|
|
80
91
|
if (options.steps !== undefined &&
|
|
81
92
|
options.numInferenceSteps !== undefined &&
|
|
82
93
|
options.steps !== options.numInferenceSteps)
|
|
@@ -138,6 +149,18 @@ function buildPocketParams(files, optionInput, configInput) {
|
|
|
138
149
|
validateEngineOptions(options, config);
|
|
139
150
|
rejectUnsupportedOptions(options);
|
|
140
151
|
const params = resolveModelFiles(files, options);
|
|
152
|
+
if (config.useGPU !== undefined)
|
|
153
|
+
params.useGPU = config.useGPU;
|
|
154
|
+
if (options.nGpuLayers !== undefined)
|
|
155
|
+
params.nGpuLayers = options.nGpuLayers;
|
|
156
|
+
if (options.nGpuLayers !== undefined && config.useGPU === undefined)
|
|
157
|
+
params.useGPU = options.nGpuLayers !== 0;
|
|
158
|
+
if (config.backendsDir !== undefined)
|
|
159
|
+
params.backendsDir = config.backendsDir;
|
|
160
|
+
if (options.backendsDir !== undefined)
|
|
161
|
+
params.backendsDir = options.backendsDir;
|
|
162
|
+
if (config.backend !== undefined)
|
|
163
|
+
params.backend = config.backend;
|
|
141
164
|
applyNumericOptions(params, options, config);
|
|
142
165
|
validateRequiredFiles(params);
|
|
143
166
|
validateVoiceConditioning(params);
|
package/lib/pocketLifecycle.js
CHANGED
|
@@ -138,6 +138,12 @@ class PocketLifecycle {
|
|
|
138
138
|
throw new Error("Pocket engine is not configured");
|
|
139
139
|
const options = { ...pocket.options, ...newConfig };
|
|
140
140
|
const config = { ...setup.config, ...newConfig };
|
|
141
|
+
if (newConfig.nGpuLayers !== undefined && newConfig.useGPU === undefined) {
|
|
142
|
+
config.useGPU = undefined;
|
|
143
|
+
}
|
|
144
|
+
if (newConfig.useGPU !== undefined && newConfig.nGpuLayers === undefined) {
|
|
145
|
+
options.nGpuLayers = undefined;
|
|
146
|
+
}
|
|
141
147
|
const params = (0, pocketConfig_1.buildPocketParams)(pocket.files, options, config);
|
|
142
148
|
this.inProgress = true;
|
|
143
149
|
let replacement = null;
|
|
@@ -149,7 +155,9 @@ class PocketLifecycle {
|
|
|
149
155
|
replacement = null;
|
|
150
156
|
setup.pocket = { files: pocket.files, options, params };
|
|
151
157
|
setup.config.language = "en";
|
|
152
|
-
setup.config.useGPU =
|
|
158
|
+
setup.config.useGPU = params.useGPU;
|
|
159
|
+
setup.config.backend = params.backend;
|
|
160
|
+
setup.settings.nGpuLayers = params.nGpuLayers;
|
|
153
161
|
setup.outputSampleRate =
|
|
154
162
|
typeof params.outputSampleRate === "number" ? params.outputSampleRate : null;
|
|
155
163
|
setup.config.outputSampleRate = setup.outputSampleRate ?? undefined;
|
package/lib/reload.js
CHANGED
|
@@ -5,12 +5,15 @@ const conditioning_1 = require("./conditioning");
|
|
|
5
5
|
const engineParams_1 = require("./engineParams");
|
|
6
6
|
const audio8_1 = require("./engines/audio8");
|
|
7
7
|
const moss_1 = require("./engines/moss");
|
|
8
|
+
const fabricOptions_1 = require("./fabricOptions");
|
|
9
|
+
const engineIds_1 = require("./engineIds");
|
|
8
10
|
const parler_1 = require("./engines/parler");
|
|
9
11
|
const setup_1 = require("./setup");
|
|
10
12
|
function captureReloadableState(setup) {
|
|
11
13
|
return {
|
|
12
14
|
language: setup.config.language,
|
|
13
15
|
useGPU: setup.config.useGPU,
|
|
16
|
+
backend: setup.config.backend,
|
|
14
17
|
outputSampleRate: setup.outputSampleRate,
|
|
15
18
|
settings: { ...setup.settings },
|
|
16
19
|
};
|
|
@@ -18,6 +21,7 @@ function captureReloadableState(setup) {
|
|
|
18
21
|
function restoreReloadableState(setup, state) {
|
|
19
22
|
setup.config.language = state.language;
|
|
20
23
|
setup.config.useGPU = state.useGPU;
|
|
24
|
+
setup.config.backend = state.backend;
|
|
21
25
|
setup.outputSampleRate = state.outputSampleRate;
|
|
22
26
|
const live = setup.settings;
|
|
23
27
|
const snapshot = state.settings;
|
|
@@ -28,6 +32,18 @@ function restoreReloadableState(setup, state) {
|
|
|
28
32
|
function applyReloadableRuntimeConfig(setup, newConfig) {
|
|
29
33
|
(0, moss_1.assertMossReloadKeepsVoice)(setup, newConfig);
|
|
30
34
|
const runtimeConfig = newConfig;
|
|
35
|
+
(0, fabricOptions_1.validateBackendOption)(setup.engineType, runtimeConfig.backend);
|
|
36
|
+
if (runtimeConfig.backend !== undefined)
|
|
37
|
+
setup.config.backend = runtimeConfig.backend;
|
|
38
|
+
Object.assign(setup.settings, (0, fabricOptions_1.resolveFabricOptions)(setup.engineType, { ...setup.settings, ...newConfig }));
|
|
39
|
+
if (setup.engineType === engineIds_1.ENGINE_MOSS && newConfig.nCtx !== undefined) {
|
|
40
|
+
setup.settings.nCtx = newConfig.nCtx;
|
|
41
|
+
}
|
|
42
|
+
if (setup.engineType === engineIds_1.ENGINE_COSYVOICE3 && newConfig.greedy !== undefined) {
|
|
43
|
+
if (typeof newConfig.greedy !== "boolean")
|
|
44
|
+
throw new Error("greedy must be a boolean");
|
|
45
|
+
setup.settings.greedy = newConfig.greedy;
|
|
46
|
+
}
|
|
31
47
|
if (runtimeConfig.language !== undefined) {
|
|
32
48
|
setup.config.language = runtimeConfig.language;
|
|
33
49
|
}
|
|
@@ -39,6 +55,9 @@ function applyReloadableRuntimeConfig(setup, newConfig) {
|
|
|
39
55
|
}
|
|
40
56
|
(0, audio8_1.applyAudio8Reload)(setup, newConfig);
|
|
41
57
|
(0, moss_1.applyMossReload)(setup, newConfig);
|
|
58
|
+
if (setup.engineType === engineIds_1.ENGINE_MOSS) {
|
|
59
|
+
(0, moss_1.assertMossDurationTokens)(setup.settings.durationTokens, "reload", setup.settings.maxNewTokens);
|
|
60
|
+
}
|
|
42
61
|
}
|
|
43
62
|
// Cross-engine conditioning is reloadable on every engine that supports it,
|
|
44
63
|
// so both families change emotion the same way.
|
package/lib/sentenceStream.d.ts
CHANGED
|
@@ -7,14 +7,26 @@ export interface ChunkResolver {
|
|
|
7
7
|
resolve(): void;
|
|
8
8
|
reject(error: unknown): void;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Stats a stream keeps from the last chunk that reported them instead of
|
|
12
|
+
* summing: where the synthesis ran, whether the Audio8 codec used its Core ML
|
|
13
|
+
* sidecar, and which Parler sidecars were still loaded.
|
|
14
|
+
*/
|
|
15
|
+
export declare const LAST_REPORTED_STREAM_STATS: readonly ["backendDevice", "backendId", "gpuUnsupported", "enhancerBackendDevice", "enhancerBackendId", "denoiserBackendDevice", "denoiserBackendId", "codecSidecarLoaded", "codecOnCoreml", "decoderSidecarLoaded", "dacSidecarLoaded"];
|
|
16
|
+
export type LastReportedStreamStat = (typeof LAST_REPORTED_STREAM_STATS)[number];
|
|
17
|
+
/**
|
|
18
|
+
* Stats a stream keeps at the lowest value any chunk reported. Complete
|
|
19
|
+
* Core ML execution requires every sentence to have used it: a capacity
|
|
20
|
+
* fallback can leave the sidecar attached for the next sentence.
|
|
21
|
+
*/
|
|
22
|
+
export declare const EVERY_CHUNK_STREAM_STATS: readonly ["decoderOnCoreml", "dacOnCoreml"];
|
|
23
|
+
export type EveryChunkStreamStat = (typeof EVERY_CHUNK_STREAM_STATS)[number];
|
|
24
|
+
export interface StreamAccumulator extends Partial<Pick<RuntimeStats, LastReportedStreamStat | EveryChunkStreamStat>> {
|
|
11
25
|
firstAudioMs?: number;
|
|
12
26
|
totalTime: number;
|
|
13
27
|
audioDurationMs: number;
|
|
14
28
|
totalSamples: number;
|
|
15
29
|
generatedFrames: number;
|
|
16
|
-
codecSidecarLoaded?: number;
|
|
17
|
-
codecOnCoreml?: number;
|
|
18
30
|
}
|
|
19
31
|
export interface SentenceStreamContext {
|
|
20
32
|
textStreamMode?: boolean;
|
package/lib/sentenceStream.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SentenceStreamer = void 0;
|
|
3
|
+
exports.SentenceStreamer = exports.EVERY_CHUNK_STREAM_STATS = exports.LAST_REPORTED_STREAM_STATS = void 0;
|
|
4
4
|
exports.normalizeError = normalizeError;
|
|
5
5
|
exports.ttsOutputDebugString = ttsOutputDebugString;
|
|
6
6
|
exports.isAudioOutputEvent = isAudioOutputEvent;
|
|
@@ -14,6 +14,33 @@ exports.splitSentenceChunks = splitSentenceChunks;
|
|
|
14
14
|
const error_1 = require("./error");
|
|
15
15
|
const textChunker_1 = require("./textChunker");
|
|
16
16
|
const textStreamAccumulator_1 = require("./textStreamAccumulator");
|
|
17
|
+
/**
|
|
18
|
+
* Stats a stream keeps from the last chunk that reported them instead of
|
|
19
|
+
* summing: where the synthesis ran, whether the Audio8 codec used its Core ML
|
|
20
|
+
* sidecar, and which Parler sidecars were still loaded.
|
|
21
|
+
*/
|
|
22
|
+
exports.LAST_REPORTED_STREAM_STATS = [
|
|
23
|
+
"backendDevice",
|
|
24
|
+
"backendId",
|
|
25
|
+
"gpuUnsupported",
|
|
26
|
+
"enhancerBackendDevice",
|
|
27
|
+
"enhancerBackendId",
|
|
28
|
+
"denoiserBackendDevice",
|
|
29
|
+
"denoiserBackendId",
|
|
30
|
+
"codecSidecarLoaded",
|
|
31
|
+
"codecOnCoreml",
|
|
32
|
+
"decoderSidecarLoaded",
|
|
33
|
+
"dacSidecarLoaded",
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Stats a stream keeps at the lowest value any chunk reported. Complete
|
|
37
|
+
* Core ML execution requires every sentence to have used it: a capacity
|
|
38
|
+
* fallback can leave the sidecar attached for the next sentence.
|
|
39
|
+
*/
|
|
40
|
+
exports.EVERY_CHUNK_STREAM_STATS = [
|
|
41
|
+
"decoderOnCoreml",
|
|
42
|
+
"dacOnCoreml",
|
|
43
|
+
];
|
|
17
44
|
function normalizeError(error) {
|
|
18
45
|
return typeof error === "string"
|
|
19
46
|
? error
|
|
@@ -209,13 +236,27 @@ function mergeSentenceStreamStats(accumulator, data) {
|
|
|
209
236
|
typeof data.totalSamples === "number" ? data.totalSamples : 0;
|
|
210
237
|
accumulator.generatedFrames +=
|
|
211
238
|
typeof data.generatedFrames === "number" ? data.generatedFrames : 0;
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
retainLastReportedStats(accumulator, data);
|
|
240
|
+
retainLowestReportedStats(accumulator, data);
|
|
241
|
+
}
|
|
242
|
+
function retainLastReportedStats(accumulator, data) {
|
|
243
|
+
for (const field of exports.LAST_REPORTED_STREAM_STATS) {
|
|
244
|
+
const value = data[field];
|
|
245
|
+
if (typeof value === "number")
|
|
246
|
+
accumulator[field] = value;
|
|
214
247
|
}
|
|
215
|
-
|
|
216
|
-
|
|
248
|
+
}
|
|
249
|
+
function retainLowestReportedStats(accumulator, data) {
|
|
250
|
+
for (const field of exports.EVERY_CHUNK_STREAM_STATS) {
|
|
251
|
+
const value = data[field];
|
|
252
|
+
if (typeof value === "number") {
|
|
253
|
+
accumulator[field] = lowestReported(accumulator[field], value);
|
|
254
|
+
}
|
|
217
255
|
}
|
|
218
256
|
}
|
|
257
|
+
function lowestReported(previous, value) {
|
|
258
|
+
return previous === undefined ? value : Math.min(previous, value);
|
|
259
|
+
}
|
|
219
260
|
/**
|
|
220
261
|
* Drives one sentence stream at a time. The wrapper routes native output and
|
|
221
262
|
* stats events here while a context is active; a newer stream, a cancel or a
|
|
@@ -283,6 +324,9 @@ class SentenceStreamer {
|
|
|
283
324
|
if (data.sampleRate != null) {
|
|
284
325
|
enriched.sampleRate = data.sampleRate;
|
|
285
326
|
}
|
|
327
|
+
if (data.speechTokenIds !== undefined) {
|
|
328
|
+
enriched.speechTokenIds = data.speechTokenIds;
|
|
329
|
+
}
|
|
286
330
|
if (!context.textStreamMode) {
|
|
287
331
|
enriched.isLast = index >= context.chunks.length - 1 && data.isLast !== false;
|
|
288
332
|
}
|
package/lib/setup.js
CHANGED
|
@@ -17,12 +17,35 @@ const engineDetection_1 = require("./engineDetection");
|
|
|
17
17
|
const engineIds_1 = require("./engineIds");
|
|
18
18
|
const cosyvoice_1 = require("./engines/cosyvoice");
|
|
19
19
|
const moss_1 = require("./engines/moss");
|
|
20
|
+
const fabricOptions_1 = require("./fabricOptions");
|
|
20
21
|
const modelFiles_1 = require("./modelFiles");
|
|
21
22
|
const optionValidation_1 = require("./optionValidation");
|
|
22
23
|
const pocketConfig_1 = require("./pocketConfig");
|
|
23
24
|
const { platform } = bareOs;
|
|
24
25
|
const MIN_OUTPUT_SAMPLE_RATE = 8000;
|
|
25
26
|
const MAX_OUTPUT_SAMPLE_RATE = 192000;
|
|
27
|
+
const LAVASR_COREML_COMPUTE_UNITS = [
|
|
28
|
+
"CPU_ONLY",
|
|
29
|
+
"CPU_AND_GPU",
|
|
30
|
+
"CPU_AND_NE",
|
|
31
|
+
"ALL",
|
|
32
|
+
];
|
|
33
|
+
function validateLavaSRCoreML(options, ggufPath, stage) {
|
|
34
|
+
const modelPath = options?.coremlModelPath;
|
|
35
|
+
const computeUnits = options?.coremlComputeUnits;
|
|
36
|
+
if (modelPath === undefined && computeUnits === undefined)
|
|
37
|
+
return;
|
|
38
|
+
if (typeof modelPath !== "string" || modelPath.trim().length === 0) {
|
|
39
|
+
throw new Error(`${stage}.coremlModelPath must be a non-empty string`);
|
|
40
|
+
}
|
|
41
|
+
if (typeof computeUnits !== "string" ||
|
|
42
|
+
!LAVASR_COREML_COMPUTE_UNITS.includes(computeUnits)) {
|
|
43
|
+
throw new Error(`${stage}.coremlComputeUnits must be CPU_ONLY, CPU_AND_GPU, CPU_AND_NE or ALL`);
|
|
44
|
+
}
|
|
45
|
+
if (!ggufPath) {
|
|
46
|
+
throw new Error(`${stage} Core ML requires the corresponding LavaSR GGUF path`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
26
49
|
function resolveDefaultLazySessionLoading(lazySessionLoading) {
|
|
27
50
|
if (lazySessionLoading != null)
|
|
28
51
|
return lazySessionLoading;
|
|
@@ -111,13 +134,24 @@ function settingsFromOptions(options) {
|
|
|
111
134
|
maxFrames: options.maxFrames,
|
|
112
135
|
minNewTokens: options.minNewTokens,
|
|
113
136
|
normalizeNumbers: options.normalizeNumbers,
|
|
137
|
+
coremlDecoder: options.coremlDecoder,
|
|
138
|
+
coremlComputeUnits: options.coremlComputeUnits,
|
|
139
|
+
coremlStrict: options.coremlStrict,
|
|
114
140
|
...(0, moss_1.mossVoiceSettings)(options),
|
|
115
141
|
};
|
|
116
142
|
}
|
|
117
143
|
function resolveBackendPaths(files, options, config) {
|
|
144
|
+
const enhancerGgufPath = resolveEnhancerGgufPath(files, options.enhancer);
|
|
145
|
+
const denoiserGgufPath = resolveDenoiserGgufPath(files, options.denoiser);
|
|
146
|
+
validateLavaSRCoreML(options.enhancer, enhancerGgufPath, "enhancer");
|
|
147
|
+
validateLavaSRCoreML(options.denoiser, denoiserGgufPath, "denoiser");
|
|
118
148
|
return {
|
|
119
|
-
enhancerGgufPath
|
|
120
|
-
denoiserGgufPath
|
|
149
|
+
enhancerGgufPath,
|
|
150
|
+
denoiserGgufPath,
|
|
151
|
+
enhancerCoremlModelPath: options.enhancer?.coremlModelPath,
|
|
152
|
+
enhancerCoremlComputeUnits: options.enhancer?.coremlComputeUnits,
|
|
153
|
+
denoiserCoremlModelPath: options.denoiser?.coremlModelPath,
|
|
154
|
+
denoiserCoremlComputeUnits: options.denoiser?.coremlComputeUnits,
|
|
121
155
|
backendsDir: (0, modelFiles_1.firstNonEmpty)(options.backendsDir, config.backendsDir),
|
|
122
156
|
openclCacheDir: (0, modelFiles_1.firstNonEmpty)(options.openclCacheDir, config.openclCacheDir),
|
|
123
157
|
vulkanCacheDir: (0, modelFiles_1.firstNonEmpty)(options.vulkanCacheDir, config.vulkanCacheDir),
|
|
@@ -139,6 +173,7 @@ function resolveEngineSetup(options) {
|
|
|
139
173
|
const config = { ...(options.config || {}) };
|
|
140
174
|
const outputSampleRate = validateOutputSampleRate(config.outputSampleRate);
|
|
141
175
|
const engineType = (0, engineDetection_1.detectEngineType)(options.engine, files);
|
|
176
|
+
(0, fabricOptions_1.validateBackendOption)(engineType, config.backend);
|
|
142
177
|
const mossVoice = (0, moss_1.mossVoiceSettings)(options);
|
|
143
178
|
const pocket = engineType === engineIds_1.ENGINE_POCKET
|
|
144
179
|
? resolvePocketSetup(files, options, config)
|
|
@@ -150,6 +185,7 @@ function resolveEngineSetup(options) {
|
|
|
150
185
|
dialogue: mossVoice.dialogueReferences !== undefined,
|
|
151
186
|
});
|
|
152
187
|
const settings = settingsFromOptions(options);
|
|
188
|
+
Object.assign(settings, (0, fabricOptions_1.resolveFabricOptions)(engineType, options));
|
|
153
189
|
const backend = resolveBackendPaths(files, options, config);
|
|
154
190
|
const setup = {
|
|
155
191
|
engineType,
|
package/lib/streamingSupport.js
CHANGED
|
@@ -31,8 +31,8 @@ function assertChunkStreamingSupport(setup) {
|
|
|
31
31
|
(settings.streamChunkTokens != null ||
|
|
32
32
|
settings.streamFirstChunkTokens != null)) {
|
|
33
33
|
throw new Error("tts-ggml: streamChunkTokens / streamFirstChunkTokens are not " +
|
|
34
|
-
"supported by the audio8 engine, which
|
|
35
|
-
"
|
|
34
|
+
"supported by the audio8 engine, which uses fixed codec windows. " +
|
|
35
|
+
"Use incremental PCM streaming via " +
|
|
36
36
|
"runStream() / runStreaming() / run({ streamOutput: true }).");
|
|
37
37
|
}
|
|
38
38
|
if ((engineType === engineIds_1.ENGINE_MOSS_SFX || engineType === engineIds_1.ENGINE_MOSS_SPEECH) &&
|
package/lib/types.d.ts
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import type { TTSConditioningFields } from "./conditioning";
|
|
2
2
|
import type { EngineType } from "./engineIds";
|
|
3
|
+
import type { FabricOptions } from "./fabricOptions";
|
|
3
4
|
import type { TTSGgmlRuntimeConfig } from "./engineSetup";
|
|
4
5
|
import type { Audio8VoiceFields } from "./engines/audio8";
|
|
5
6
|
import type { CosyvoiceInstruct, CosyvoiceJobFields } from "./engines/cosyvoice";
|
|
6
7
|
import type { MossSoundEffectFields } from "./engines/mossSoundEffect";
|
|
7
8
|
import type { MossSpeechFields } from "./engines/mossSpeech";
|
|
8
|
-
import type { ParlerDescriptionFields } from "./engines/parler";
|
|
9
|
+
import type { ParlerCoremlFields, ParlerDescriptionFields } from "./engines/parler";
|
|
9
10
|
import type { TTSGgmlFiles } from "./modelFiles";
|
|
10
11
|
import type { SentenceDelimiterPreset } from "./textStreamAccumulator";
|
|
11
|
-
export type { Audio8VoiceFields, CosyvoiceInstruct, CosyvoiceJobFields, MossSoundEffectFields, MossSpeechFields, ParlerDescriptionFields, TTSConditioningFields, TTSGgmlFiles, TTSGgmlRuntimeConfig, };
|
|
12
|
+
export type { Audio8VoiceFields, CosyvoiceInstruct, CosyvoiceJobFields, MossSoundEffectFields, MossSpeechFields, ParlerDescriptionFields, ParlerCoremlFields, TTSConditioningFields, TTSGgmlFiles, TTSGgmlRuntimeConfig, };
|
|
12
13
|
export type { MossSpeechJobFields, MossSpeechMessage, MossSpeechPcm, } from "./engines/mossSpeech";
|
|
13
14
|
export type { Audio8SamplingFields } from "./engines/audio8";
|
|
15
|
+
export type LavaSRCoreMLComputeUnits = "CPU_ONLY" | "CPU_AND_GPU" | "CPU_AND_NE" | "ALL";
|
|
16
|
+
export interface LavaSRCoreMLOptions {
|
|
17
|
+
coremlModelPath?: string;
|
|
18
|
+
coremlComputeUnits?: LavaSRCoreMLComputeUnits;
|
|
19
|
+
}
|
|
14
20
|
/**
|
|
15
21
|
* LavaSR enhancer config. The discriminated `type` leaves room for future
|
|
16
22
|
* enhancer kinds; v1 ships `lavasr`. Enhancement is enabled by providing a
|
|
17
23
|
* GGUF path here or via `files.lavasrEnhancer`.
|
|
18
24
|
*/
|
|
19
|
-
export interface LavaSREnhancerOptions {
|
|
25
|
+
export interface LavaSREnhancerOptions extends LavaSRCoreMLOptions {
|
|
20
26
|
type: "lavasr";
|
|
21
27
|
/** Enhancer GGUF path (alternative to `files.lavasrEnhancer`). */
|
|
22
28
|
enhancerPath?: string;
|
|
@@ -26,12 +32,12 @@ export interface LavaSREnhancerOptions {
|
|
|
26
32
|
* is enabled by providing a GGUF path here or via `files.lavasrDenoiser`.
|
|
27
33
|
* It runs before the enhancer and preserves the sample rate.
|
|
28
34
|
*/
|
|
29
|
-
export interface LavaSRDenoiserOptions {
|
|
35
|
+
export interface LavaSRDenoiserOptions extends LavaSRCoreMLOptions {
|
|
30
36
|
type: "lavasr";
|
|
31
37
|
/** Denoiser GGUF path (alternative to `files.lavasrDenoiser`). */
|
|
32
38
|
denoiserPath?: string;
|
|
33
39
|
}
|
|
34
|
-
export interface TTSGgmlOptions extends ParlerDescriptionFields, Audio8VoiceFields, TTSConditioningFields {
|
|
40
|
+
export interface TTSGgmlOptions extends FabricOptions, ParlerDescriptionFields, ParlerCoremlFields, Audio8VoiceFields, TTSConditioningFields {
|
|
35
41
|
/** Pocket: generation/context controls; native sampling uses a portable RNG. */
|
|
36
42
|
maxTokens?: number;
|
|
37
43
|
noiseClamp?: number;
|
|
@@ -68,6 +74,7 @@ export interface TTSGgmlOptions extends ParlerDescriptionFields, Audio8VoiceFiel
|
|
|
68
74
|
* front at this length, so the cap directly bounds memory. Pass 0 to use
|
|
69
75
|
* the GGUF's full context; negative values are rejected.
|
|
70
76
|
* Pocket: FlowLM context capacity; accepts integers from 1 to 8192.
|
|
77
|
+
* MOSS / TTSD: context capacity; omission retains the native value.
|
|
71
78
|
*/
|
|
72
79
|
nCtx?: number;
|
|
73
80
|
/**
|
|
@@ -274,12 +281,13 @@ export interface TTSGgmlOptions extends ParlerDescriptionFields, Audio8VoiceFiel
|
|
|
274
281
|
* ~86 frames/s, Audio8 ~21.5.
|
|
275
282
|
*/
|
|
276
283
|
maxFrames?: number;
|
|
277
|
-
/** Audio8: take the argmax instead of sampling. */
|
|
284
|
+
/** Audio8 / CosyVoice3: take the argmax instead of sampling. */
|
|
278
285
|
greedy?: boolean;
|
|
279
286
|
/**
|
|
280
|
-
* MOSS: target length in codec frames (12.5 per second),
|
|
281
|
-
*
|
|
282
|
-
*
|
|
287
|
+
* MOSS: target length in codec frames (12.5 per second), up to
|
|
288
|
+
* maxNewTokens minus 33 native delay/EOS rows. The native token budget is
|
|
289
|
+
* 2048 when omitted. Zero keeps the length free. Set at construction or
|
|
290
|
+
* with reload(), not per call.
|
|
283
291
|
*/
|
|
284
292
|
durationTokens?: number;
|
|
285
293
|
/**
|
|
@@ -304,6 +312,7 @@ export interface InferenceState {
|
|
|
304
312
|
destroyed: boolean;
|
|
305
313
|
}
|
|
306
314
|
export interface TTSOutputChunk {
|
|
315
|
+
speechTokenIds?: number[];
|
|
307
316
|
chunkIndex?: number;
|
|
308
317
|
isLast?: boolean;
|
|
309
318
|
/** Signed 16-bit mono PCM audio payload. */
|
|
@@ -318,6 +327,7 @@ export interface TTSOutputChunk {
|
|
|
318
327
|
sampleRate?: number;
|
|
319
328
|
}
|
|
320
329
|
export interface NativeOutputChunk {
|
|
330
|
+
speechTokenIds?: number[];
|
|
321
331
|
outputArray: Int16Array;
|
|
322
332
|
text?: string;
|
|
323
333
|
isLast?: boolean;
|
|
@@ -330,15 +340,15 @@ export interface RuntimeStats {
|
|
|
330
340
|
realTimeFactor: number;
|
|
331
341
|
audioDurationMs: number;
|
|
332
342
|
totalSamples: number;
|
|
333
|
-
/** Active compute device
|
|
343
|
+
/** Active compute device: 0 = CPU, 1 = GPU, 3 = NPU, 99 = other. */
|
|
334
344
|
backendDevice?: number;
|
|
335
|
-
/**
|
|
345
|
+
/** Backend: 0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 5=WebGPU, 99=other. */
|
|
336
346
|
backendId?: number;
|
|
337
|
-
/** LavaSR enhancer
|
|
347
|
+
/** LavaSR enhancer device. -1 = off, 0 = CPU, 1 = GPU, 2 = Core ML-managed. */
|
|
338
348
|
enhancerBackendDevice?: number;
|
|
339
349
|
/** LavaSR enhancer backend code, using the same values as `backendId`. */
|
|
340
350
|
enhancerBackendId?: number;
|
|
341
|
-
/** LavaSR denoiser
|
|
351
|
+
/** LavaSR denoiser device. -1 = off, 0 = CPU, 1 = GPU, 2 = Core ML-managed. */
|
|
342
352
|
denoiserBackendDevice?: number;
|
|
343
353
|
/** LavaSR denoiser backend code, using the same values as `backendId`. */
|
|
344
354
|
denoiserBackendId?: number;
|
|
@@ -366,6 +376,17 @@ export interface RuntimeStats {
|
|
|
366
376
|
* Streams report the last chunk that supplied this field, not a sum.
|
|
367
377
|
*/
|
|
368
378
|
codecOnCoreml?: number;
|
|
379
|
+
/** Parler only: 1 while the decoder sidecar is attached; 0 after fallback retires it. */
|
|
380
|
+
decoderSidecarLoaded?: number;
|
|
381
|
+
/** Parler only: 1 while the DAC sidecar is attached; 0 after fallback retires it. */
|
|
382
|
+
dacSidecarLoaded?: number;
|
|
383
|
+
/** Parler only: 1 when this synthesis used the Core ML decoder; 0 for ggml or mixed fallback. */
|
|
384
|
+
decoderOnCoreml?: number;
|
|
385
|
+
/** Parler only: 1 when this synthesis used the Core ML DAC; 0 for ggml or mixed fallback.
|
|
386
|
+
* Sentence streams retain the latest availability flags and require all reported sentences to use Core ML for a use flag of 1.
|
|
387
|
+
* These flags do not identify the hardware Core ML scheduled work on.
|
|
388
|
+
*/
|
|
389
|
+
dacOnCoreml?: number;
|
|
369
390
|
/** Chatterbox only: T3 decode wall time of the last synthesis, in ms. */
|
|
370
391
|
t3Ms?: number;
|
|
371
392
|
/** Chatterbox only: S3Gen + HiFT wall time of the last synthesis, in ms. */
|