@qvac/harness 0.8.5 → 0.8.6
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 +55 -1
- package/README.md +49 -0
- package/dist-lib/index.d.ts +31 -2
- package/dist-lib/index.js +365 -53
- package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-bare.js +22 -25
- package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-node.js +1 -3
- package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
- package/dist-lib/lib/capabilities/audio-resample.js +7 -8
- package/dist-lib/lib/capabilities/completion.d.ts +5 -2
- package/dist-lib/lib/capabilities/completion.js +22 -4
- package/dist-lib/lib/capabilities/download.d.ts +3 -1
- package/dist-lib/lib/capabilities/download.js +1 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
- package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +94 -26
- package/dist-lib/lib/connect.d.ts +4 -1
- package/dist-lib/lib/connect.js +14 -1
- package/dist-lib/lib/contract-types.d.ts +10 -0
- package/dist-lib/lib/models/download-gate.d.ts +20 -0
- package/dist-lib/lib/models/download-gate.js +73 -0
- package/dist-lib/lib/models/memory-cap.d.ts +13 -0
- package/dist-lib/lib/models/memory-cap.js +21 -0
- package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
- package/dist-lib/lib/models/model-catalog-constants.js +9 -1
- package/dist-lib/lib/models/model-catalog.d.ts +6 -2
- package/dist-lib/lib/models/model-catalog.js +35 -3
- package/dist-lib/lib/models/model-handle.d.ts +8 -1
- package/dist-lib/lib/models/model-handle.js +20 -2
- package/dist-lib/lib/models/model-list.d.ts +8 -1
- package/dist-lib/lib/models/model-list.js +23 -0
- package/dist-lib/lib/models/model-pool.d.ts +33 -5
- package/dist-lib/lib/models/model-pool.js +97 -29
- package/dist-lib/lib/serve.js +18 -2
- package/dist-lib/lib/tools/generate-image.d.ts +4 -2
- package/dist-lib/lib/tools/generate-image.js +5 -2
- package/dist-lib/lib/tools/generate-music.d.ts +4 -2
- package/dist-lib/lib/tools/generate-music.js +5 -2
- package/dist-lib/lib/tools/translate.d.ts +3 -1
- package/dist-lib/lib/tools/translate.js +6 -1
- package/dist-lib/schema/harness.js +41 -3
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +93 -10
- package/dist-lib/spec/hyperschema/index.js +93 -10
- package/dist-lib/spec/hyperschema/schema.json +57 -1
- package/dist-lib/spec/hyperschema/types.d.ts +19 -2
- package/package.json +4 -4
|
@@ -11,6 +11,7 @@ import { RUNAWAY_STOP_SEQUENCES } from '../helpers/control-tokens.js';
|
|
|
11
11
|
import { raceTimeout } from '../helpers/race-timeout.js';
|
|
12
12
|
import { resolveModelSrc } from './model-source.js';
|
|
13
13
|
import { errorMessage } from '../guards.js';
|
|
14
|
+
import { AbortedError } from '../helpers/race-abort.js';
|
|
14
15
|
export function createLoadTracker() {
|
|
15
16
|
const inflight = new Set();
|
|
16
17
|
let draining = null;
|
|
@@ -73,10 +74,14 @@ function entryOf(pool, key) {
|
|
|
73
74
|
export function modelOf(key) {
|
|
74
75
|
return key.split('\0')[0];
|
|
75
76
|
}
|
|
77
|
+
let uses = 0;
|
|
76
78
|
export function releaseModel(pool, key) {
|
|
77
79
|
if (pool.destroyed)
|
|
78
80
|
return;
|
|
79
81
|
const handle = entryOf(pool, key)?.value;
|
|
82
|
+
if (handle) {
|
|
83
|
+
handle.lastUsed = ++uses;
|
|
84
|
+
}
|
|
80
85
|
if (handle === undefined || !handle.loading) {
|
|
81
86
|
pool.release(key);
|
|
82
87
|
return;
|
|
@@ -86,8 +91,18 @@ export function releaseModel(pool, key) {
|
|
|
86
91
|
pool.release(key);
|
|
87
92
|
return;
|
|
88
93
|
}
|
|
89
|
-
|
|
94
|
+
const deferred = (deferredReleases.get(handle) ?? 0) + 1;
|
|
95
|
+
deferredReleases.set(handle, deferred);
|
|
96
|
+
if (deferred >= (entryOf(pool, key)?.refs ?? 0)) {
|
|
97
|
+
handle.abandon();
|
|
98
|
+
}
|
|
99
|
+
const settle = () => {
|
|
100
|
+
deferredReleases.set(handle, (deferredReleases.get(handle) ?? 1) - 1);
|
|
101
|
+
releaseSettled(pool, key);
|
|
102
|
+
};
|
|
103
|
+
void pending.then(settle, settle);
|
|
90
104
|
}
|
|
105
|
+
const deferredReleases = new WeakMap();
|
|
91
106
|
function releaseSettled(pool, key) {
|
|
92
107
|
if (!pool.destroyed) {
|
|
93
108
|
pool.release(key);
|
|
@@ -111,6 +126,21 @@ export function settledModels(pool) {
|
|
|
111
126
|
}
|
|
112
127
|
return out;
|
|
113
128
|
}
|
|
129
|
+
export function settledEntries(pool) {
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const entry of pool.resources.values()) {
|
|
132
|
+
const resource = entry.value.resource;
|
|
133
|
+
if (resource !== null) {
|
|
134
|
+
out.push({
|
|
135
|
+
key: entry.id,
|
|
136
|
+
refs: entry.refs,
|
|
137
|
+
bytes: resource.bytes,
|
|
138
|
+
lastUsed: entry.value.lastUsed
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
114
144
|
export function loadingModels(pool) {
|
|
115
145
|
const out = [];
|
|
116
146
|
for (const entry of pool.resources.values()) {
|
|
@@ -120,14 +150,19 @@ export function loadingModels(pool) {
|
|
|
120
150
|
}
|
|
121
151
|
return out;
|
|
122
152
|
}
|
|
123
|
-
function reportedHandle(notify, model, load) {
|
|
153
|
+
function reportedHandle({ notify, model, addon, hold, load }) {
|
|
124
154
|
return new ModelHandle({
|
|
125
|
-
load: () => {
|
|
155
|
+
load: async (signal) => {
|
|
156
|
+
await hold?.(model, addon, signal);
|
|
126
157
|
notify({ status: 'loading', model, catalogChanged: true });
|
|
127
158
|
return load();
|
|
128
159
|
},
|
|
129
160
|
onLoaded: () => notify({ status: 'ready', model, catalogChanged: true }),
|
|
130
|
-
onFailed: (err) =>
|
|
161
|
+
onFailed: (err) => {
|
|
162
|
+
if (err instanceof AbortedError)
|
|
163
|
+
return;
|
|
164
|
+
notify({ status: 'error', model, message: errorMessage(err), catalogChanged: true });
|
|
165
|
+
}
|
|
131
166
|
});
|
|
132
167
|
}
|
|
133
168
|
export async function leaseResidentModel(pool, wanted = () => true) {
|
|
@@ -226,6 +261,10 @@ export async function awaitModelCapacity(pool, { cap, deadlineMs, tickMs = 250,
|
|
|
226
261
|
export function evictModelWithin(pool, key, options) {
|
|
227
262
|
return evictWhereWithin(pool, (candidate) => candidate === key, options);
|
|
228
263
|
}
|
|
264
|
+
export function evictIdleWithin(pool, key, { deadlineMs }) {
|
|
265
|
+
const evicting = evictIdleWhere(pool, (candidate) => candidate === key).then(() => !isResident(pool, key));
|
|
266
|
+
return raceTimeout(evicting, { ms: deadlineMs, fallback: false });
|
|
267
|
+
}
|
|
229
268
|
export function evictModel(pool, model, options) {
|
|
230
269
|
return evictWhereWithin(pool, (key) => modelOf(key) === model, options);
|
|
231
270
|
}
|
|
@@ -318,8 +357,8 @@ export function loadClockGuard(model, sdk, { now = () => Date.now(), monoMs = mo
|
|
|
318
357
|
};
|
|
319
358
|
return { race };
|
|
320
359
|
}
|
|
321
|
-
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker() } = {}) {
|
|
322
|
-
const load = async (model, opts) => {
|
|
360
|
+
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker(), beforeLoad, hold } = {}) {
|
|
361
|
+
const load = async (key, model, opts) => {
|
|
323
362
|
const sdk = await loadSdk();
|
|
324
363
|
const modelSrc = await resolveModelSrc(sdk, model);
|
|
325
364
|
const resolved = resolveLoadOptions(opts, deviceConfig);
|
|
@@ -352,54 +391,80 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
352
391
|
});
|
|
353
392
|
const { projectionModelSrc: projection, ...loggedConfig } = modelConfig;
|
|
354
393
|
hlog('llm load config', { model, projection: Boolean(projection), ...loggedConfig });
|
|
394
|
+
const admission = await beforeLoad?.({
|
|
395
|
+
key,
|
|
396
|
+
model,
|
|
397
|
+
modelSrc,
|
|
398
|
+
modelType: 'llamacpp-completion',
|
|
399
|
+
modelConfig
|
|
400
|
+
});
|
|
355
401
|
const loadStarted = Date.now();
|
|
356
|
-
const modelId = await loadClockGuard(model, sdk)
|
|
402
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
403
|
+
.race(loads.track(sdk.loadModel({
|
|
357
404
|
modelSrc,
|
|
358
405
|
modelType: 'llamacpp-completion',
|
|
359
406
|
modelConfig,
|
|
360
407
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
361
|
-
})))
|
|
408
|
+
})))
|
|
409
|
+
.finally(() => admission?.release());
|
|
362
410
|
hlog('llm loaded', { model, loadMs: Date.now() - loadStarted });
|
|
363
|
-
return { model, modelId, ctxSize };
|
|
411
|
+
return { model, modelId, ctxSize, bytes: admission?.bytes };
|
|
364
412
|
};
|
|
365
413
|
return new ModelPuddle({
|
|
366
|
-
open: (key, model = key, opts = {}) => reportedHandle(notify, model, () => load(model, opts)),
|
|
414
|
+
open: (key, model = key, opts = {}) => reportedHandle({ notify, model, addon: 'llm', hold, load: () => load(key, model, opts) }),
|
|
367
415
|
gc: unloadOnGc(loadSdk, notify),
|
|
368
416
|
...NO_IDLE_GC
|
|
369
417
|
});
|
|
370
418
|
}
|
|
371
|
-
export function createEmbedPool(loadSdk, notify, { faults, batchSize = DEFAULT_EMBED_BATCH_SIZE, openclCacheDir, loads = createLoadTracker() }) {
|
|
372
|
-
const load = async (model) => {
|
|
419
|
+
export function createEmbedPool(loadSdk, notify, { faults, batchSize = DEFAULT_EMBED_BATCH_SIZE, openclCacheDir, loads = createLoadTracker(), beforeLoad, hold }) {
|
|
420
|
+
const load = async (key, model) => {
|
|
373
421
|
const sdk = await loadSdk();
|
|
374
422
|
const modelSrc = await resolveModelSrc(sdk, model, 'embeddings');
|
|
375
423
|
const openclCache = faults.device !== 'cpu' && openclCacheDir ? openclCacheDir() : undefined;
|
|
376
|
-
const
|
|
424
|
+
const modelConfig = {
|
|
425
|
+
batchSize,
|
|
426
|
+
gpuLayers: faults.gpuLayers(),
|
|
427
|
+
device: faults.device,
|
|
428
|
+
openclCacheDir: openclCache || undefined
|
|
429
|
+
};
|
|
430
|
+
const admission = await beforeLoad?.({
|
|
431
|
+
key,
|
|
432
|
+
model,
|
|
377
433
|
modelSrc,
|
|
378
434
|
modelType: 'llamacpp-embedding',
|
|
379
|
-
modelConfig
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
435
|
+
modelConfig
|
|
436
|
+
});
|
|
437
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
438
|
+
.race(loads.track(sdk.loadModel({
|
|
439
|
+
modelSrc,
|
|
440
|
+
modelType: 'llamacpp-embedding',
|
|
441
|
+
modelConfig,
|
|
385
442
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
386
|
-
})))
|
|
387
|
-
|
|
443
|
+
})))
|
|
444
|
+
.finally(() => admission?.release());
|
|
445
|
+
return { model, modelId, bytes: admission?.bytes };
|
|
388
446
|
};
|
|
389
447
|
return new ModelPuddle({
|
|
390
|
-
open: (key, model = key) => reportedHandle(notify, model, () => load(model)),
|
|
448
|
+
open: (key, model = key) => reportedHandle({ notify, model, addon: 'embeddings', hold, load: () => load(key, model) }),
|
|
391
449
|
gc: unloadOnGc(loadSdk, notify),
|
|
392
450
|
...NO_IDLE_GC
|
|
393
451
|
});
|
|
394
452
|
}
|
|
395
|
-
export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads = createLoadTracker() }) {
|
|
453
|
+
export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads = createLoadTracker(), beforeLoad, hold }) {
|
|
396
454
|
const wantsGpu = modelConfig?.['useGPU'] === true;
|
|
397
455
|
let gpuRefused = false;
|
|
398
|
-
const load = async (model) => {
|
|
456
|
+
const load = async (key, model) => {
|
|
399
457
|
const sdk = await loadSdk();
|
|
400
458
|
const modelSrc = await resolveModelSrc(sdk, model, addon);
|
|
401
459
|
const tuned = modelConfigFor?.(modelSrc);
|
|
402
460
|
const loadConfig = tuned ? { ...modelConfig, ...tuned } : modelConfig;
|
|
461
|
+
const admission = await beforeLoad?.({
|
|
462
|
+
key,
|
|
463
|
+
model,
|
|
464
|
+
modelSrc,
|
|
465
|
+
modelType,
|
|
466
|
+
modelConfig: loadConfig
|
|
467
|
+
});
|
|
403
468
|
const attempt = () => {
|
|
404
469
|
const config = gpuRefused ? { ...loadConfig, useGPU: false } : loadConfig;
|
|
405
470
|
const openclCache = gpuRefused ? undefined : openclCacheDir?.();
|
|
@@ -410,17 +475,19 @@ export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig
|
|
|
410
475
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
411
476
|
}));
|
|
412
477
|
};
|
|
413
|
-
const modelId = await loadClockGuard(model, sdk)
|
|
478
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
479
|
+
.race(attempt().catch((err) => {
|
|
414
480
|
if (!wantsGpu || gpuRefused || loads.draining)
|
|
415
481
|
throw err;
|
|
416
482
|
hlog('voice gpu load refused, retrying on cpu', { model, modelType, error: String(err) });
|
|
417
483
|
gpuRefused = true;
|
|
418
484
|
return attempt();
|
|
419
|
-
}))
|
|
420
|
-
|
|
485
|
+
}))
|
|
486
|
+
.finally(() => admission?.release());
|
|
487
|
+
return { model, modelId, bytes: admission?.bytes };
|
|
421
488
|
};
|
|
422
489
|
return new ModelPuddle({
|
|
423
|
-
open: (key, model = key) => reportedHandle(notify, model, () => load(model)),
|
|
490
|
+
open: (key, model = key) => reportedHandle({ notify, model, addon, hold, load: () => load(key, model) }),
|
|
424
491
|
gc: unloadOnGc(loadSdk, notify),
|
|
425
492
|
...NO_IDLE_GC
|
|
426
493
|
});
|
|
@@ -445,7 +512,8 @@ function unloadOnGc(loadSdk, notify) {
|
|
|
445
512
|
try {
|
|
446
513
|
await sdk.unloadModel({ modelId });
|
|
447
514
|
}
|
|
448
|
-
catch {
|
|
515
|
+
catch (err) {
|
|
516
|
+
hlog('unload failed', { model, err: errorMessage(err) });
|
|
449
517
|
continue;
|
|
450
518
|
}
|
|
451
519
|
notify({ status: 'unloaded', model, catalogChanged: true });
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -23,7 +23,8 @@ const MODEL_STATUSES = new Set([
|
|
|
23
23
|
'ready',
|
|
24
24
|
'error',
|
|
25
25
|
'unloading',
|
|
26
|
-
'unloaded'
|
|
26
|
+
'unloaded',
|
|
27
|
+
'held'
|
|
27
28
|
]);
|
|
28
29
|
const DOWNLOAD_PROGRESS_INTERVAL_MS = 500;
|
|
29
30
|
const WIRE_HIGH_WATER_MARK = 64;
|
|
@@ -50,6 +51,20 @@ export function serveHarness(stream, harness) {
|
|
|
50
51
|
await harness.setModelIdleTimeout?.(ms);
|
|
51
52
|
return { ok: true };
|
|
52
53
|
},
|
|
54
|
+
setModelMemoryCap: async ({ percent }) => {
|
|
55
|
+
await harness.setModelMemoryCap?.(percent);
|
|
56
|
+
return { ok: true };
|
|
57
|
+
},
|
|
58
|
+
memoryUsage: async () => {
|
|
59
|
+
if (!harness.memoryUsage) {
|
|
60
|
+
throw new Error('harness does not support memoryUsage');
|
|
61
|
+
}
|
|
62
|
+
return harness.memoryUsage();
|
|
63
|
+
},
|
|
64
|
+
setDownloadsAllowed: async ({ allowed }) => {
|
|
65
|
+
await harness.setDownloadsAllowed?.({ allowed: allowed === true });
|
|
66
|
+
return { ok: true };
|
|
67
|
+
},
|
|
53
68
|
listManagedSkills: async () => ({
|
|
54
69
|
skills: (await harness.listManagedSkills?.()) ?? []
|
|
55
70
|
}),
|
|
@@ -282,7 +297,8 @@ function modelEvents(harness) {
|
|
|
282
297
|
total,
|
|
283
298
|
message: state.message,
|
|
284
299
|
catalogChanged,
|
|
285
|
-
loaded
|
|
300
|
+
loaded,
|
|
301
|
+
addon: state.addon
|
|
286
302
|
};
|
|
287
303
|
const now = Date.now();
|
|
288
304
|
if (state.status === 'downloading') {
|
|
@@ -3,6 +3,7 @@ import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
|
3
3
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
4
|
import { type ImageClamps } from '../models/model-catalog.ts';
|
|
5
5
|
import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
|
|
6
|
+
import type { HoldFn } from '../models/download-gate.ts';
|
|
6
7
|
import { type ModelRegistrySdk } from '../models/model-source.ts';
|
|
7
8
|
export declare const MAX_DIM = 1024;
|
|
8
9
|
export declare const DIM_STEP = 64;
|
|
@@ -68,7 +69,8 @@ export interface DiffusionRuntimeOptions extends ImageModelSet {
|
|
|
68
69
|
loadSdk: () => Promise<DiffusionSdk>;
|
|
69
70
|
onProgress?: (model: string, progress: Record<string, unknown>) => void;
|
|
70
71
|
onLoaded?: (model: string) => void;
|
|
71
|
-
onBeforeLoad?: (signal
|
|
72
|
+
onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
|
|
73
|
+
hold?: HoldFn;
|
|
72
74
|
loads?: LoadTracker;
|
|
73
75
|
stallMs?: number;
|
|
74
76
|
}
|
|
@@ -87,7 +89,7 @@ export interface DiffusionRuntime {
|
|
|
87
89
|
}
|
|
88
90
|
export declare function generationPercentTick(onPercent: (percentage: number) => void, encodePhases?: number): (tick: DiffusionTick) => void;
|
|
89
91
|
export declare const IMAGE_LOAD_STALL_ERROR = "image model download stalled";
|
|
90
|
-
export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
|
|
92
|
+
export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
|
|
91
93
|
export declare const BUSY_ERROR = "an image generation is already running; try again when it finishes";
|
|
92
94
|
export interface ImageToolError {
|
|
93
95
|
error: string;
|
|
@@ -39,7 +39,7 @@ function diffusionProgressName(set) {
|
|
|
39
39
|
return entry?.name ?? set.model;
|
|
40
40
|
}
|
|
41
41
|
export const IMAGE_LOAD_STALL_ERROR = 'image model download stalled';
|
|
42
|
-
export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
|
|
42
|
+
export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
|
|
43
43
|
let loading = null;
|
|
44
44
|
let residentFingerprint = null;
|
|
45
45
|
let busy = false;
|
|
@@ -76,13 +76,16 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
|
|
|
76
76
|
ensureLoaded: async (sdk, signal, modelChoice) => {
|
|
77
77
|
const set = (modelChoice && imageModelFor(modelChoice)) || base;
|
|
78
78
|
const fingerprint = fingerprintOf(set);
|
|
79
|
+
if (hold && !(loading && residentFingerprint === fingerprint)) {
|
|
80
|
+
await hold(diffusionProgressName(set), 'diffusion', signal);
|
|
81
|
+
}
|
|
79
82
|
await lock.lock();
|
|
80
83
|
try {
|
|
81
84
|
if (loading && residentFingerprint !== fingerprint) {
|
|
82
85
|
await doUnload();
|
|
83
86
|
}
|
|
84
87
|
if (!loading && onBeforeLoad) {
|
|
85
|
-
await onBeforeLoad(signal);
|
|
88
|
+
await onBeforeLoad(signal, [set.model, set.llmModel, set.vaeModel].filter((src) => src !== undefined));
|
|
86
89
|
}
|
|
87
90
|
residentFingerprint = fingerprint;
|
|
88
91
|
loading ??= (async () => {
|
|
@@ -2,6 +2,7 @@ import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
|
2
2
|
import type { MusicSettings, ToolDefinition, ToolExecuteContext } from '../capabilities/completion.ts';
|
|
3
3
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
4
|
import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
|
|
5
|
+
import type { HoldFn } from '../models/download-gate.ts';
|
|
5
6
|
import { type ModelRegistrySdk } from '../models/model-source.ts';
|
|
6
7
|
export declare const MAX_DURATION_SECONDS = 300;
|
|
7
8
|
export declare const MIN_DURATION_SECONDS = 1;
|
|
@@ -75,7 +76,8 @@ export interface MusicRuntimeOptions extends MusicModelStages {
|
|
|
75
76
|
inferenceSteps?: number;
|
|
76
77
|
useGpu?: boolean;
|
|
77
78
|
onProgress?: (model: string, progress: Record<string, unknown>) => void;
|
|
78
|
-
onBeforeLoad?: (signal
|
|
79
|
+
onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
|
|
80
|
+
hold?: HoldFn;
|
|
79
81
|
loads?: LoadTracker;
|
|
80
82
|
}
|
|
81
83
|
export interface LoadedMusicModel {
|
|
@@ -97,7 +99,7 @@ export interface MusicRuntime {
|
|
|
97
99
|
release(): void;
|
|
98
100
|
}
|
|
99
101
|
export declare function musicPercentTick(onPercent: (percentage: number) => void): (tick: MusicTick) => void;
|
|
100
|
-
export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
|
|
102
|
+
export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
|
|
101
103
|
export declare const BUSY_ERROR = "a music generation is already running; try again when it finishes";
|
|
102
104
|
export interface MusicToolError {
|
|
103
105
|
error: string;
|
|
@@ -49,7 +49,7 @@ function musicProgressName(dit) {
|
|
|
49
49
|
const entry = AUDIOGEN_CATALOG.find((candidate) => candidate.dit.src === dit || candidate.dit.name === dit);
|
|
50
50
|
return entry?.name ?? dit;
|
|
51
51
|
}
|
|
52
|
-
export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
|
|
52
|
+
export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
|
|
53
53
|
let loading = null;
|
|
54
54
|
let residentFingerprint = null;
|
|
55
55
|
let busy = false;
|
|
@@ -89,13 +89,16 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
|
|
|
89
89
|
const stages = stagesFor(modelChoice, base);
|
|
90
90
|
const effectiveSteps = steps ?? baseSteps;
|
|
91
91
|
const fingerprint = fingerprintOf(stages, effectiveSteps);
|
|
92
|
+
if (hold && !(loading && residentFingerprint === fingerprint)) {
|
|
93
|
+
await hold(musicProgressName(stages.dit), 'audiogen', signal);
|
|
94
|
+
}
|
|
92
95
|
await lock.lock();
|
|
93
96
|
try {
|
|
94
97
|
if (loading && residentFingerprint !== fingerprint) {
|
|
95
98
|
await doUnload();
|
|
96
99
|
}
|
|
97
100
|
if (!loading && onBeforeLoad) {
|
|
98
|
-
await onBeforeLoad(signal);
|
|
101
|
+
await onBeforeLoad(signal, [stages.dit, stages.textEnc, stages.lm, stages.vae]);
|
|
99
102
|
}
|
|
100
103
|
residentFingerprint = fingerprint;
|
|
101
104
|
loading ??= (async () => {
|
|
@@ -3,6 +3,7 @@ import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
|
3
3
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
4
|
import { type NmtPlan } from '../models/nmt-catalog.ts';
|
|
5
5
|
import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
|
|
6
|
+
import type { HoldFn } from '../models/download-gate.ts';
|
|
6
7
|
import { type ModelRegistrySdk } from '../models/model-source.ts';
|
|
7
8
|
interface BergamotConfig {
|
|
8
9
|
engine: 'Bergamot';
|
|
@@ -42,6 +43,7 @@ export interface TranslationRuntimeOptions {
|
|
|
42
43
|
loadSdk: () => Promise<TranslationSdk>;
|
|
43
44
|
loads?: LoadTracker;
|
|
44
45
|
onProgress?: (model: string, progress: Record<string, unknown>) => void;
|
|
46
|
+
hold?: HoldFn;
|
|
45
47
|
stallMs?: number;
|
|
46
48
|
}
|
|
47
49
|
export interface TranslationRuntime {
|
|
@@ -53,7 +55,7 @@ export interface TranslationRuntime {
|
|
|
53
55
|
enter: (signal?: BareAbortSignal) => Promise<void>;
|
|
54
56
|
exit: () => void;
|
|
55
57
|
}
|
|
56
|
-
export declare function createTranslationRuntime({ loadSdk, loads, onProgress, stallMs }: TranslationRuntimeOptions): TranslationRuntime;
|
|
58
|
+
export declare function createTranslationRuntime({ loadSdk, loads, onProgress, hold, stallMs }: TranslationRuntimeOptions): TranslationRuntime;
|
|
57
59
|
export type SlmTranslateFn = (model: string, params: {
|
|
58
60
|
text: string;
|
|
59
61
|
from: string;
|
|
@@ -26,7 +26,7 @@ function bergamotConfig(plan) {
|
|
|
26
26
|
}
|
|
27
27
|
return config;
|
|
28
28
|
}
|
|
29
|
-
export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(), onProgress, stallMs = TRANSLATION_LOAD_STALL_MS }) {
|
|
29
|
+
export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(), onProgress, hold, stallMs = TRANSLATION_LOAD_STALL_MS }) {
|
|
30
30
|
let loading = null;
|
|
31
31
|
let residentFingerprint = null;
|
|
32
32
|
const lock = new ScopeLock();
|
|
@@ -95,6 +95,11 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
95
95
|
loadSdk,
|
|
96
96
|
ensureLoaded: async (sdk, plan, signal) => {
|
|
97
97
|
const fingerprint = fingerprintOf(plan);
|
|
98
|
+
if (hold && !(loading && residentFingerprint === fingerprint)) {
|
|
99
|
+
for (const model of plan.pivot ? [plan.model, plan.pivot] : [plan.model]) {
|
|
100
|
+
await hold(model, 'nmt', signal);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
98
103
|
await lock.lock();
|
|
99
104
|
try {
|
|
100
105
|
if (loading && residentFingerprint !== fingerprint) {
|
|
@@ -175,7 +175,16 @@ export function registerHarnessTypes(ns) {
|
|
|
175
175
|
});
|
|
176
176
|
ns.register({
|
|
177
177
|
name: 'model-event-status',
|
|
178
|
-
enum: [
|
|
178
|
+
enum: [
|
|
179
|
+
'loading',
|
|
180
|
+
'downloading',
|
|
181
|
+
'ready',
|
|
182
|
+
'error',
|
|
183
|
+
'unloading',
|
|
184
|
+
'unloaded',
|
|
185
|
+
'downloaded',
|
|
186
|
+
'held'
|
|
187
|
+
],
|
|
179
188
|
strings: true
|
|
180
189
|
});
|
|
181
190
|
ns.register({
|
|
@@ -195,7 +204,8 @@ export function registerHarnessTypes(ns) {
|
|
|
195
204
|
{ name: 'loaded', type: '@harness/loaded-model', array: true },
|
|
196
205
|
{ name: 'catalogChanged', type: 'bool' },
|
|
197
206
|
{ name: 'downloaded', type: 'uint' },
|
|
198
|
-
{ name: 'total', type: 'uint' }
|
|
207
|
+
{ name: 'total', type: 'uint' },
|
|
208
|
+
{ name: 'addon', type: 'string' }
|
|
199
209
|
]
|
|
200
210
|
});
|
|
201
211
|
ns.register({
|
|
@@ -340,7 +350,8 @@ export function registerHarnessTypes(ns) {
|
|
|
340
350
|
{ name: 'expectedSize', type: 'uint', required: true },
|
|
341
351
|
{ name: 'actualSize', type: 'uint' },
|
|
342
352
|
{ name: 'label', type: 'string' },
|
|
343
|
-
{ name: 'isLoading', type: 'bool' }
|
|
353
|
+
{ name: 'isLoading', type: 'bool' },
|
|
354
|
+
{ name: 'memoryBytes', type: 'uint' }
|
|
344
355
|
]
|
|
345
356
|
});
|
|
346
357
|
ns.register({
|
|
@@ -738,6 +749,18 @@ export function registerHarnessTypes(ns) {
|
|
|
738
749
|
name: 'unload-model-response',
|
|
739
750
|
fields: [{ name: 'unloaded', type: 'bool', required: true }]
|
|
740
751
|
});
|
|
752
|
+
ns.register({
|
|
753
|
+
name: 'set-model-memory-cap-request',
|
|
754
|
+
fields: [{ name: 'percent', type: 'uint', required: true }]
|
|
755
|
+
});
|
|
756
|
+
ns.register({
|
|
757
|
+
name: 'memory-usage',
|
|
758
|
+
fields: [{ name: 'usedBytes', type: 'uint', required: true }]
|
|
759
|
+
});
|
|
760
|
+
ns.register({
|
|
761
|
+
name: 'set-downloads-allowed-request',
|
|
762
|
+
fields: [{ name: 'allowed', type: 'bool', required: true }]
|
|
763
|
+
});
|
|
741
764
|
}
|
|
742
765
|
export function registerHarnessApi(api) {
|
|
743
766
|
api.register({
|
|
@@ -905,4 +928,19 @@ export function registerHarnessApi(api) {
|
|
|
905
928
|
request: { name: '@harness/unload-model-request', stream: false },
|
|
906
929
|
response: { name: '@harness/unload-model-response', stream: false }
|
|
907
930
|
});
|
|
931
|
+
api.register({
|
|
932
|
+
name: 'set-model-memory-cap',
|
|
933
|
+
request: { name: '@harness/set-model-memory-cap-request', stream: false },
|
|
934
|
+
response: { name: '@harness/ok-response', stream: false }
|
|
935
|
+
});
|
|
936
|
+
api.register({
|
|
937
|
+
name: 'memory-usage',
|
|
938
|
+
request: { name: '@harness/empty', stream: false },
|
|
939
|
+
response: { name: '@harness/memory-usage', stream: false }
|
|
940
|
+
});
|
|
941
|
+
api.register({
|
|
942
|
+
name: 'set-downloads-allowed',
|
|
943
|
+
request: { name: '@harness/set-downloads-allowed-request', stream: false },
|
|
944
|
+
response: { name: '@harness/ok-response', stream: false }
|
|
945
|
+
});
|
|
908
946
|
}
|
package/dist-lib/spec/bind.js
CHANGED
|
@@ -139,4 +139,13 @@ export function bindApi(rpc, api) {
|
|
|
139
139
|
if (api.unloadModel) {
|
|
140
140
|
rpc.onUnloadModel((req) => api.unloadModel(req));
|
|
141
141
|
}
|
|
142
|
+
if (api.setModelMemoryCap) {
|
|
143
|
+
rpc.onSetModelMemoryCap((req) => api.setModelMemoryCap(req));
|
|
144
|
+
}
|
|
145
|
+
if (api.memoryUsage) {
|
|
146
|
+
rpc.onMemoryUsage((req) => api.memoryUsage(req));
|
|
147
|
+
}
|
|
148
|
+
if (api.setDownloadsAllowed) {
|
|
149
|
+
rpc.onSetDownloadsAllowed((req) => api.setDownloadsAllowed(req));
|
|
150
|
+
}
|
|
142
151
|
}
|
package/dist-lib/spec/calls.js
CHANGED
|
@@ -43,5 +43,8 @@ export function harnessClient(rpc) {
|
|
|
43
43
|
setModelIdleTimeout: (input) => rpc.setModelIdleTimeout(input),
|
|
44
44
|
loadModel: (input) => rpc.loadModel(input),
|
|
45
45
|
unloadModel: (input) => rpc.unloadModel(input),
|
|
46
|
+
setModelMemoryCap: (input) => rpc.setModelMemoryCap(input),
|
|
47
|
+
memoryUsage: (input) => rpc.memoryUsage(input),
|
|
48
|
+
setDownloadsAllowed: (input) => rpc.setDownloadsAllowed(input),
|
|
46
49
|
};
|
|
47
50
|
}
|
|
@@ -429,6 +429,45 @@
|
|
|
429
429
|
"stream": false
|
|
430
430
|
},
|
|
431
431
|
"version": 1
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"id": 33,
|
|
435
|
+
"name": "@harness/set-model-memory-cap",
|
|
436
|
+
"request": {
|
|
437
|
+
"name": "@harness/set-model-memory-cap-request",
|
|
438
|
+
"stream": false
|
|
439
|
+
},
|
|
440
|
+
"response": {
|
|
441
|
+
"name": "@harness/ok-response",
|
|
442
|
+
"stream": false
|
|
443
|
+
},
|
|
444
|
+
"version": 1
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
"id": 34,
|
|
448
|
+
"name": "@harness/memory-usage",
|
|
449
|
+
"request": {
|
|
450
|
+
"name": "@harness/empty",
|
|
451
|
+
"stream": false
|
|
452
|
+
},
|
|
453
|
+
"response": {
|
|
454
|
+
"name": "@harness/memory-usage",
|
|
455
|
+
"stream": false
|
|
456
|
+
},
|
|
457
|
+
"version": 1
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"id": 35,
|
|
461
|
+
"name": "@harness/set-downloads-allowed",
|
|
462
|
+
"request": {
|
|
463
|
+
"name": "@harness/set-downloads-allowed-request",
|
|
464
|
+
"stream": false
|
|
465
|
+
},
|
|
466
|
+
"response": {
|
|
467
|
+
"name": "@harness/ok-response",
|
|
468
|
+
"stream": false
|
|
469
|
+
},
|
|
470
|
+
"version": 1
|
|
432
471
|
}
|
|
433
472
|
]
|
|
434
473
|
}
|
|
@@ -71,7 +71,13 @@ const methods = new Map([
|
|
|
71
71
|
['@harness/load-model', 31],
|
|
72
72
|
[31, '@harness/load-model'],
|
|
73
73
|
['@harness/unload-model', 32],
|
|
74
|
-
[32, '@harness/unload-model']
|
|
74
|
+
[32, '@harness/unload-model'],
|
|
75
|
+
['@harness/set-model-memory-cap', 33],
|
|
76
|
+
[33, '@harness/set-model-memory-cap'],
|
|
77
|
+
['@harness/memory-usage', 34],
|
|
78
|
+
[34, '@harness/memory-usage'],
|
|
79
|
+
['@harness/set-downloads-allowed', 35],
|
|
80
|
+
[35, '@harness/set-downloads-allowed']
|
|
75
81
|
])
|
|
76
82
|
|
|
77
83
|
class HRPC {
|
|
@@ -111,7 +117,10 @@ class HRPC {
|
|
|
111
117
|
['@harness/preview-model-source', getEncoding('@harness/preview-model-source-request')],
|
|
112
118
|
['@harness/set-model-idle-timeout', getEncoding('@harness/set-model-idle-timeout-request')],
|
|
113
119
|
['@harness/load-model', getEncoding('@harness/warm-model-request')],
|
|
114
|
-
['@harness/unload-model', getEncoding('@harness/unload-model-request')]
|
|
120
|
+
['@harness/unload-model', getEncoding('@harness/unload-model-request')],
|
|
121
|
+
['@harness/set-model-memory-cap', getEncoding('@harness/set-model-memory-cap-request')],
|
|
122
|
+
['@harness/memory-usage', getEncoding('@harness/empty')],
|
|
123
|
+
['@harness/set-downloads-allowed', getEncoding('@harness/set-downloads-allowed-request')]
|
|
115
124
|
])
|
|
116
125
|
this._responseEncodings = new Map([
|
|
117
126
|
['@harness/completion', getEncoding('@harness/completion-event')],
|
|
@@ -146,7 +155,10 @@ class HRPC {
|
|
|
146
155
|
['@harness/preview-model-source', getEncoding('@harness/preview-model-source-response')],
|
|
147
156
|
['@harness/set-model-idle-timeout', getEncoding('@harness/ok-response')],
|
|
148
157
|
['@harness/load-model', getEncoding('@harness/load-model-response')],
|
|
149
|
-
['@harness/unload-model', getEncoding('@harness/unload-model-response')]
|
|
158
|
+
['@harness/unload-model', getEncoding('@harness/unload-model-response')],
|
|
159
|
+
['@harness/set-model-memory-cap', getEncoding('@harness/ok-response')],
|
|
160
|
+
['@harness/memory-usage', getEncoding('@harness/memory-usage')],
|
|
161
|
+
['@harness/set-downloads-allowed', getEncoding('@harness/ok-response')]
|
|
150
162
|
])
|
|
151
163
|
this._rpc = new RPC(stream, async (req) => {
|
|
152
164
|
const command = methods.get(req.command)
|
|
@@ -376,6 +388,18 @@ class HRPC {
|
|
|
376
388
|
return this._call('@harness/unload-model', args)
|
|
377
389
|
}
|
|
378
390
|
|
|
391
|
+
async setModelMemoryCap(args) {
|
|
392
|
+
return this._call('@harness/set-model-memory-cap', args)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async memoryUsage(args) {
|
|
396
|
+
return this._call('@harness/memory-usage', args)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async setDownloadsAllowed(args) {
|
|
400
|
+
return this._call('@harness/set-downloads-allowed', args)
|
|
401
|
+
}
|
|
402
|
+
|
|
379
403
|
onCompletion(responseFn) {
|
|
380
404
|
this._handlers['@harness/completion'] = responseFn
|
|
381
405
|
}
|
|
@@ -508,6 +532,18 @@ class HRPC {
|
|
|
508
532
|
this._handlers['@harness/unload-model'] = responseFn
|
|
509
533
|
}
|
|
510
534
|
|
|
535
|
+
onSetModelMemoryCap(responseFn) {
|
|
536
|
+
this._handlers['@harness/set-model-memory-cap'] = responseFn
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
onMemoryUsage(responseFn) {
|
|
540
|
+
this._handlers['@harness/memory-usage'] = responseFn
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
onSetDownloadsAllowed(responseFn) {
|
|
544
|
+
this._handlers['@harness/set-downloads-allowed'] = responseFn
|
|
545
|
+
}
|
|
546
|
+
|
|
511
547
|
_requestIsStream(command) {
|
|
512
548
|
return [
|
|
513
549
|
'@harness/completion',
|