@qvac/harness 0.8.5 → 0.8.7
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 +1278 -472
- package/README.md +49 -0
- package/dist-lib/index.d.ts +42 -2
- package/dist-lib/index.js +447 -58
- 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 +6 -3
- package/dist-lib/lib/capabilities/completion.js +96 -49
- 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/tool-selection.d.ts +1 -3
- package/dist-lib/lib/capabilities/tool-selection.js +2 -4
- package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
- package/dist-lib/lib/capabilities/tool-turns.js +0 -6
- 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 +16 -1
- package/dist-lib/lib/contract-types.d.ts +12 -0
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
- package/dist-lib/lib/models/context-choice.d.ts +15 -0
- package/dist-lib/lib/models/context-choice.js +55 -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 +35 -5
- package/dist-lib/lib/models/model-pool.js +97 -32
- package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
- package/dist-lib/lib/models/sdk-faults.js +6 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
- package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
- package/dist-lib/lib/serve.js +23 -3
- package/dist-lib/lib/spawn.js +4 -10
- package/dist-lib/lib/tools/exec.d.ts +4 -1
- package/dist-lib/lib/tools/exec.js +55 -11
- 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/office/ops.js +1 -1
- package/dist-lib/lib/tools/office.js +29 -6
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +2 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
- package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
- package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
- package/dist-lib/lib/tools/skills/mention.js +0 -3
- 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 +43 -4
- 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 +101 -13
- package/dist-lib/spec/hyperschema/index.js +101 -13
- package/dist-lib/spec/hyperschema/schema.json +62 -1
- package/dist-lib/spec/hyperschema/types.d.ts +20 -2
- package/package.json +8 -10
- package/worker/harness-sidecar.mjs +2 -1
package/dist-lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import ScopeLock from 'scope-lock';
|
|
2
3
|
import { platform, totalmem } from 'os';
|
|
3
4
|
import process from 'process';
|
|
4
5
|
import ReadyResource from 'ready-resource';
|
|
@@ -17,15 +18,18 @@ import { download } from './lib/capabilities/download.js';
|
|
|
17
18
|
import { createEmbedLane } from './lib/capabilities/embed.js';
|
|
18
19
|
import { deleteAllKvCaches, deleteKvCacheKey } from './lib/capabilities/kv-cache.js';
|
|
19
20
|
import { pcmForTranscription } from './lib/capabilities/audio-resample.js';
|
|
20
|
-
import {
|
|
21
|
-
import { createVoiceLane, packSamples, speakOnce, speakStreamOnce, transcribeOnce, transcribeStreaming, SUPERTONIC_SAMPLE_RATE } from './lib/capabilities/voice.js';
|
|
21
|
+
import { createVoiceLane, packSamples, speakOnce, speakStreamOnce, transcribeAudio, SUPERTONIC_SAMPLE_RATE } from './lib/capabilities/voice.js';
|
|
22
22
|
import { pushQueue } from './lib/queue.js';
|
|
23
23
|
import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
24
|
-
import { audioGenDownloadMetadata, categoryModelConfig, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, curatedModelAsset, infraModelsFor, modelAddonOf, modelNameToCapabilityProfile, musicModelSet, residentSrc, setCustomModels } from './lib/models/model-catalog.js';
|
|
25
|
-
import { createModelStatusCache, customModelRows, invalidateModelStatusCache, mergeCustomRows, listModels } from './lib/models/model-list.js';
|
|
24
|
+
import { audioGenDownloadMetadata, categoryModelConfig, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, assetBytes, curatedModelAsset, customContextLength, customModelSrc, infraModelsFor, modelAddonOf, modelNameToCapabilityProfile, musicModelSet, catalogModelName, residentSrc, setCustomModels } from './lib/models/model-catalog.js';
|
|
25
|
+
import { createModelStatusCache, customModelRows, invalidateModelStatusCache, isModelSourceCached, mergeCustomRows, listModels } from './lib/models/model-list.js';
|
|
26
|
+
import { createDownloadGate } from './lib/models/download-gate.js';
|
|
26
27
|
import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
|
|
27
|
-
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
28
|
+
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictIdleWithin, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledEntries, settledModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
29
|
+
import { memoryCapBytes, nextVictim, VictimRank } from './lib/models/memory-cap.js';
|
|
28
30
|
import { defaultContextSize } from './lib/models/context.js';
|
|
31
|
+
import { ContextChoice } from './lib/models/context-choice.js';
|
|
32
|
+
import { parseGgufHeader, readGgufHeaderBytes } from './lib/models/gguf-header.js';
|
|
29
33
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
30
34
|
import { createIdleReaper } from './lib/models/idle-unload.js';
|
|
31
35
|
import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
@@ -41,7 +45,7 @@ import { ensureBundledSkillsDir } from './lib/tools/skills/bundled-dir.js';
|
|
|
41
45
|
import { ensureSkillsOverlayDir } from './lib/tools/skills/overlay-dir.js';
|
|
42
46
|
import { copyBaseForEdit, deleteOverlaySkill, listManagedSkills, readSkillFile, writeSkillFile } from './lib/tools/skills/overlay-crud.js';
|
|
43
47
|
import { watchSkillsOverlay } from './lib/tools/skills/overlay-watch.js';
|
|
44
|
-
import { errorMessage } from './lib/guards.js';
|
|
48
|
+
import { errorMessage, isRecord } from './lib/guards.js';
|
|
45
49
|
import { hlog } from './lib/log.js';
|
|
46
50
|
import { harness } from './spec/index.js';
|
|
47
51
|
import { installResolutionDetector, resolutionDetail } from './lib/resolution-detector.js';
|
|
@@ -75,7 +79,6 @@ export { harnessHooks } from './lib/hooks/registry.js';
|
|
|
75
79
|
export { EgressGuardHook, SkillCatalogGuardHook } from './lib/hooks/built-in/index.js';
|
|
76
80
|
const HarnessState = harness.getEnum('@harness/state');
|
|
77
81
|
const BIN_VERSION_PROBE_TTL_MS = 45_000;
|
|
78
|
-
const TRANSCRIBE_STREAM_MIN_BYTES = 16_000 * 2 * 60;
|
|
79
82
|
const TRANSCRIPTION_MODEL_LABEL = 'transcription model';
|
|
80
83
|
const DIFFUSION_EVICT_WAIT_MS = 15_000;
|
|
81
84
|
const MUSIC_EVICT_WAIT_MS = 15_000;
|
|
@@ -91,6 +94,23 @@ const TTS_MODEL_CONFIG = {
|
|
|
91
94
|
ttsSpeed: 1.175,
|
|
92
95
|
ttsNumInferenceSteps: 5
|
|
93
96
|
};
|
|
97
|
+
function fitSrc(modelSrc) {
|
|
98
|
+
if (typeof modelSrc === 'string')
|
|
99
|
+
return modelSrc;
|
|
100
|
+
const { src, name, sha256Checksum } = modelSrc;
|
|
101
|
+
return { src, name, sha256Checksum };
|
|
102
|
+
}
|
|
103
|
+
function downloadBytes(modelSrc) {
|
|
104
|
+
if (typeof modelSrc === 'string') {
|
|
105
|
+
return assetBytes(modelSrc);
|
|
106
|
+
}
|
|
107
|
+
return isRecord(modelSrc) && typeof modelSrc.expectedSize === 'number'
|
|
108
|
+
? modelSrc.expectedSize
|
|
109
|
+
: undefined;
|
|
110
|
+
}
|
|
111
|
+
function stagesBytes(srcs) {
|
|
112
|
+
return srcs.reduce((sum, src) => sum + (assetBytes(src) ?? 0), 0);
|
|
113
|
+
}
|
|
94
114
|
export class Harness extends ReadyResource {
|
|
95
115
|
_tools;
|
|
96
116
|
_sdk;
|
|
@@ -98,6 +118,8 @@ export class Harness extends ReadyResource {
|
|
|
98
118
|
_pinned = new Set();
|
|
99
119
|
_replying = [];
|
|
100
120
|
_modelStatus = createModelStatusCache();
|
|
121
|
+
_downloads;
|
|
122
|
+
_hold;
|
|
101
123
|
_models;
|
|
102
124
|
_embedModels;
|
|
103
125
|
_retiredEmbedModels;
|
|
@@ -140,6 +162,12 @@ export class Harness extends ReadyResource {
|
|
|
140
162
|
_capabilityInflight = null;
|
|
141
163
|
_modelFaults = new Map();
|
|
142
164
|
_idle;
|
|
165
|
+
_sizeLoads = false;
|
|
166
|
+
_capBytes = null;
|
|
167
|
+
_reserved = new Map();
|
|
168
|
+
_contexts = new ContextChoice(() => this._contextProfile());
|
|
169
|
+
_admission = new ScopeLock();
|
|
170
|
+
_byHand = new Map();
|
|
143
171
|
constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
|
|
144
172
|
super();
|
|
145
173
|
this._sdk = sdkLoader ?? createSdkLoader();
|
|
@@ -164,11 +192,19 @@ export class Harness extends ReadyResource {
|
|
|
164
192
|
device: embedConfig.device,
|
|
165
193
|
gpuLayers: embedConfig.gpuLayers
|
|
166
194
|
});
|
|
195
|
+
this._downloads = createDownloadGate({
|
|
196
|
+
isCached: (model, addon) => this._isModelCached(model, addon),
|
|
197
|
+
onHeld: ({ model, addon }) => this._notifyModel({ status: 'held', model, addon }),
|
|
198
|
+
onDropped: ({ model, addon }) => this._notifyModel({ status: 'error', model, addon, message: 'aborted' })
|
|
199
|
+
});
|
|
200
|
+
const hold = (model, addon, signal) => this._downloads.hold(catalogModelName(model, addon), addon, signal);
|
|
201
|
+
this._hold = hold;
|
|
167
202
|
const imageTransfers = new Map();
|
|
168
203
|
const image = typeof imageModel === 'string' ? { model: imageModel } : imageModel;
|
|
169
204
|
this._imageRuntime = image
|
|
170
205
|
? createDiffusionRuntime({
|
|
171
206
|
...image,
|
|
207
|
+
hold,
|
|
172
208
|
loads: this._loads,
|
|
173
209
|
loadSdk: () => this._loadDiffusionSdk(),
|
|
174
210
|
onProgress: (model, progress) => {
|
|
@@ -191,9 +227,13 @@ export class Harness extends ReadyResource {
|
|
|
191
227
|
invalidateModelStatusCache(this._modelStatus);
|
|
192
228
|
this.emit('model', { ...transfer, status: 'downloaded' });
|
|
193
229
|
},
|
|
194
|
-
onBeforeLoad: async (signal) => {
|
|
230
|
+
onBeforeLoad: async (signal, srcs) => {
|
|
195
231
|
if (this._models.destroyed)
|
|
196
232
|
return;
|
|
233
|
+
if (this._capBytes !== null) {
|
|
234
|
+
await this._makeRoomForGenerator(srcs, signal);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
197
237
|
const evicted = await awaitModelCapacity(this._models, {
|
|
198
238
|
cap: 1,
|
|
199
239
|
deadlineMs: DIFFUSION_EVICT_WAIT_MS,
|
|
@@ -212,12 +252,17 @@ export class Harness extends ReadyResource {
|
|
|
212
252
|
this._musicRuntime = music
|
|
213
253
|
? createMusicRuntime({
|
|
214
254
|
...music,
|
|
255
|
+
hold,
|
|
215
256
|
loads: this._loads,
|
|
216
257
|
loadSdk: () => this._loadMusicSdk(),
|
|
217
258
|
onProgress: (model, progress) => this.emit('model', progressState(model, progress)),
|
|
218
|
-
onBeforeLoad: async (signal) => {
|
|
259
|
+
onBeforeLoad: async (signal, srcs) => {
|
|
219
260
|
if (this._models.destroyed)
|
|
220
261
|
return;
|
|
262
|
+
if (this._capBytes !== null) {
|
|
263
|
+
await this._makeRoomForGenerator(srcs, signal);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
221
266
|
const evicted = await awaitModelCapacity(this._models, {
|
|
222
267
|
cap: 1,
|
|
223
268
|
deadlineMs: MUSIC_EVICT_WAIT_MS,
|
|
@@ -233,6 +278,7 @@ export class Harness extends ReadyResource {
|
|
|
233
278
|
})
|
|
234
279
|
: null;
|
|
235
280
|
this._translationRuntime = createTranslationRuntime({
|
|
281
|
+
hold,
|
|
236
282
|
loads: this._loads,
|
|
237
283
|
loadSdk: () => this._loadTranslationSdk(),
|
|
238
284
|
onProgress: (model, progress) => this.emit('model', progressState(model, progress))
|
|
@@ -523,13 +569,45 @@ export class Harness extends ReadyResource {
|
|
|
523
569
|
setModelIdleTimeout(ms) {
|
|
524
570
|
this._idle.configure(ms);
|
|
525
571
|
}
|
|
572
|
+
async setModelMemoryCap(percent) {
|
|
573
|
+
this._sizeLoads = true;
|
|
574
|
+
const { mobile, memoryBudget } = await this.capability();
|
|
575
|
+
this._capBytes =
|
|
576
|
+
percent > 0 && !mobile && memoryBudget
|
|
577
|
+
? memoryCapBytes(percent, memoryBudget.availableBytes)
|
|
578
|
+
: null;
|
|
579
|
+
hlog('memory cap', { percent, capBytes: this._capBytes });
|
|
580
|
+
if (this._capBytes !== null) {
|
|
581
|
+
await this._busy(() => this._admit(() => this._makeRoom(0, { generators: true })));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
async memoryUsage() {
|
|
585
|
+
return { usedBytes: this._settledBytes() };
|
|
586
|
+
}
|
|
587
|
+
setDownloadsAllowed({ allowed }) {
|
|
588
|
+
this._downloads.setAllowed(allowed);
|
|
589
|
+
}
|
|
590
|
+
async _isModelCached(model, addon) {
|
|
591
|
+
const row = (await this.list({ type: addon })).find((entry) => entry.name === model);
|
|
592
|
+
if (row) {
|
|
593
|
+
return row.isCached;
|
|
594
|
+
}
|
|
595
|
+
const sdk = await this._sdk.load();
|
|
596
|
+
return isModelSourceCached(sdk, { model, addon, modelsDir: this._modelsDir });
|
|
597
|
+
}
|
|
526
598
|
previewModelSource(context) {
|
|
527
599
|
return previewModelSource(context, { assessFit: (query) => this._assessFit(query) });
|
|
528
600
|
}
|
|
529
|
-
async _assessFit({ sha256Checksum, name, contextTokens }) {
|
|
601
|
+
async _assessFit({ src, sha256Checksum, name, contextTokens }) {
|
|
530
602
|
const sdk = await this._sdk.load();
|
|
531
603
|
const result = await sdk.assessModelFit({
|
|
532
|
-
models: [
|
|
604
|
+
models: [
|
|
605
|
+
{
|
|
606
|
+
modelSrc: { src, sha256Checksum, name },
|
|
607
|
+
modelType: 'llm',
|
|
608
|
+
modelConfig: { ctx_size: contextTokens }
|
|
609
|
+
}
|
|
610
|
+
]
|
|
533
611
|
});
|
|
534
612
|
const model = result.models[0];
|
|
535
613
|
return {
|
|
@@ -607,7 +685,7 @@ export class Harness extends ReadyResource {
|
|
|
607
685
|
const key = modelKey(model, loadOpts);
|
|
608
686
|
const pool = this._models;
|
|
609
687
|
if (!isResident(pool, key)) {
|
|
610
|
-
await this._makeRoomForCompletion(model, { ctxSize:
|
|
688
|
+
await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
|
|
611
689
|
}
|
|
612
690
|
try {
|
|
613
691
|
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
@@ -644,7 +722,10 @@ export class Harness extends ReadyResource {
|
|
|
644
722
|
const loadSdk = this._sdk.load;
|
|
645
723
|
return createModelPool(loadSdk, (state) => this._notifyModel(state), {
|
|
646
724
|
deviceConfig: this._deviceConfig(),
|
|
647
|
-
loads: this._loads
|
|
725
|
+
loads: this._loads,
|
|
726
|
+
beforeLoad: (load) => this._beforeLoad(load),
|
|
727
|
+
chooseContext: (load, tier) => this._chooseContext(load, tier),
|
|
728
|
+
hold: this._hold
|
|
648
729
|
});
|
|
649
730
|
}
|
|
650
731
|
_createEmbedPool() {
|
|
@@ -653,7 +734,9 @@ export class Harness extends ReadyResource {
|
|
|
653
734
|
faults: this._embedFaults,
|
|
654
735
|
batchSize: this._embedBatchSize,
|
|
655
736
|
openclCacheDir,
|
|
656
|
-
loads: this._loads
|
|
737
|
+
loads: this._loads,
|
|
738
|
+
beforeLoad: (load) => this._beforeLoad(load),
|
|
739
|
+
hold: this._hold
|
|
657
740
|
});
|
|
658
741
|
}
|
|
659
742
|
_createTtsPool() {
|
|
@@ -663,7 +746,9 @@ export class Harness extends ReadyResource {
|
|
|
663
746
|
addon: 'tts',
|
|
664
747
|
modelConfig: TTS_MODEL_CONFIG,
|
|
665
748
|
modelConfigFor: categoryModelConfig,
|
|
666
|
-
loads: this._loads
|
|
749
|
+
loads: this._loads,
|
|
750
|
+
beforeLoad: (load) => this._beforeLoad(load),
|
|
751
|
+
hold: this._hold
|
|
667
752
|
});
|
|
668
753
|
}
|
|
669
754
|
_createTranscriptionPool() {
|
|
@@ -673,7 +758,9 @@ export class Harness extends ReadyResource {
|
|
|
673
758
|
addon: 'parakeet',
|
|
674
759
|
modelConfig: { useGPU: this._transcriptionDevice !== 'cpu' },
|
|
675
760
|
openclCacheDir,
|
|
676
|
-
loads: this._loads
|
|
761
|
+
loads: this._loads,
|
|
762
|
+
beforeLoad: (load) => this._beforeLoad(load),
|
|
763
|
+
hold: this._hold
|
|
677
764
|
});
|
|
678
765
|
}
|
|
679
766
|
_notifyModel(state) {
|
|
@@ -703,6 +790,10 @@ export class Harness extends ReadyResource {
|
|
|
703
790
|
releaseModel(pool, key);
|
|
704
791
|
await this._collectRetiredEmbedPool(pool);
|
|
705
792
|
}
|
|
793
|
+
_collectRetiredEmbedPoolAfter(settled, pool) {
|
|
794
|
+
const collect = () => this._collectRetiredEmbedPool(pool);
|
|
795
|
+
settled.then(collect, collect).catch(noop);
|
|
796
|
+
}
|
|
706
797
|
_releaseEmbedResourceAfter(settled, pool, key) {
|
|
707
798
|
settled
|
|
708
799
|
.then(() => this._releaseEmbedResource(pool, key), () => this._releaseEmbedResource(pool, key))
|
|
@@ -758,11 +849,14 @@ export class Harness extends ReadyResource {
|
|
|
758
849
|
}
|
|
759
850
|
this._state = HarnessState.SUSPEND;
|
|
760
851
|
hlog('suspending', { models: loadedModels(this._models).map((entry) => entry.model) });
|
|
852
|
+
this._downloads.abandon();
|
|
761
853
|
const pending = this._sdk.peek();
|
|
762
854
|
if (pending) {
|
|
763
855
|
this._loads.drain(await pending);
|
|
764
856
|
}
|
|
765
857
|
await this._releaseRuntimes();
|
|
858
|
+
this._reserved.clear();
|
|
859
|
+
this._byHand.clear();
|
|
766
860
|
await this._sdkLifecycle('suspend');
|
|
767
861
|
invalidateModelStatusCache(this._modelStatus);
|
|
768
862
|
hlog('suspended');
|
|
@@ -856,6 +950,7 @@ export class Harness extends ReadyResource {
|
|
|
856
950
|
async _close() {
|
|
857
951
|
hlog('closing');
|
|
858
952
|
this._idle.close();
|
|
953
|
+
this._downloads.abandon();
|
|
859
954
|
this._overlayWatcher?.stop();
|
|
860
955
|
this._overlayWatcher = null;
|
|
861
956
|
const pending = this._sdk.peek();
|
|
@@ -881,7 +976,12 @@ export class Harness extends ReadyResource {
|
|
|
881
976
|
projection,
|
|
882
977
|
companions: multiFile?.companions,
|
|
883
978
|
assetSizes: multiFile?.assetSizes
|
|
884
|
-
}, () =>
|
|
979
|
+
}, (event) => {
|
|
980
|
+
invalidateModelStatusCache(this._modelStatus);
|
|
981
|
+
if (event.type === 'complete') {
|
|
982
|
+
this._downloads.downloaded();
|
|
983
|
+
}
|
|
984
|
+
});
|
|
885
985
|
}
|
|
886
986
|
embed(context) {
|
|
887
987
|
return this._busy(() => this._embed(context));
|
|
@@ -902,6 +1002,7 @@ export class Harness extends ReadyResource {
|
|
|
902
1002
|
let loaded = null;
|
|
903
1003
|
let job = null;
|
|
904
1004
|
let releaseAfter = null;
|
|
1005
|
+
let collectAfter = null;
|
|
905
1006
|
try {
|
|
906
1007
|
loaded = await raceAbort(handle.load(), signal);
|
|
907
1008
|
const sdk = await this._sdk.loadEmbed();
|
|
@@ -912,7 +1013,8 @@ export class Harness extends ReadyResource {
|
|
|
912
1013
|
}
|
|
913
1014
|
catch (err) {
|
|
914
1015
|
if (err instanceof AbortedError) {
|
|
915
|
-
releaseAfter = job
|
|
1016
|
+
releaseAfter = job;
|
|
1017
|
+
collectAfter = job === null && this._retiredEmbedModels.has(pool) ? handle.peek() : null;
|
|
916
1018
|
}
|
|
917
1019
|
if (isSdkGpuDeviceLostError(err)) {
|
|
918
1020
|
faults.recordFault(generation);
|
|
@@ -930,6 +1032,10 @@ export class Harness extends ReadyResource {
|
|
|
930
1032
|
if (releaseAfter) {
|
|
931
1033
|
this._releaseEmbedResourceAfter(releaseAfter, pool, key);
|
|
932
1034
|
}
|
|
1035
|
+
else if (collectAfter) {
|
|
1036
|
+
releaseModel(pool, key);
|
|
1037
|
+
this._collectRetiredEmbedPoolAfter(collectAfter, pool);
|
|
1038
|
+
}
|
|
933
1039
|
else {
|
|
934
1040
|
await this._releaseEmbedResource(pool, key);
|
|
935
1041
|
}
|
|
@@ -992,16 +1098,7 @@ export class Harness extends ReadyResource {
|
|
|
992
1098
|
return this._busy(() => this._transcribe(context));
|
|
993
1099
|
}
|
|
994
1100
|
async _transcribe({ model, audio, signal }) {
|
|
995
|
-
return this._voiceJob(this._transcriptionModels, { category: 'transcription', model, signal },
|
|
996
|
-
const pcm = await pcmForTranscription(audio);
|
|
997
|
-
if (signal?.aborted) {
|
|
998
|
-
throw new AbortedError();
|
|
999
|
-
}
|
|
1000
|
-
if (pcm && pcm.length >= TRANSCRIBE_STREAM_MIN_BYTES) {
|
|
1001
|
-
return transcribeStreaming(sdk, pcm, { modelId: id, signal });
|
|
1002
|
-
}
|
|
1003
|
-
return transcribeOnce(sdk, id, pcm ? buildWavBuffer(pcm) : audio);
|
|
1004
|
-
});
|
|
1101
|
+
return this._voiceJob(this._transcriptionModels, { category: 'transcription', model, signal }, (sdk, id) => transcribeAudio(sdk, pcmForTranscription(audio), { modelId: id, signal, audio }));
|
|
1005
1102
|
}
|
|
1006
1103
|
detectLanguage(context) {
|
|
1007
1104
|
return detectLanguage(context);
|
|
@@ -1033,21 +1130,25 @@ export class Harness extends ReadyResource {
|
|
|
1033
1130
|
}
|
|
1034
1131
|
};
|
|
1035
1132
|
signal?.addEventListener('abort', cancel, { once: true });
|
|
1036
|
-
const job =
|
|
1133
|
+
const job = (async () => {
|
|
1037
1134
|
try {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1135
|
+
const loadedId = (await raceAbort(handle.load(), signal)).modelId;
|
|
1136
|
+
modelId = loadedId;
|
|
1137
|
+
const voiceSdk = (await this._sdk.loadVoice());
|
|
1138
|
+
sdk = voiceSdk;
|
|
1139
|
+
await this._voiceLane(async () => {
|
|
1140
|
+
if (signal?.aborted) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
for await (const chunk of speakStreamOnce(voiceSdk, loadedId, text)) {
|
|
1144
|
+
chunks.push(chunk);
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1046
1147
|
}
|
|
1047
1148
|
finally {
|
|
1048
1149
|
chunks.end();
|
|
1049
1150
|
}
|
|
1050
|
-
});
|
|
1151
|
+
})();
|
|
1051
1152
|
let settled = false;
|
|
1052
1153
|
try {
|
|
1053
1154
|
yield* chunks;
|
|
@@ -1075,9 +1176,11 @@ export class Harness extends ReadyResource {
|
|
|
1075
1176
|
const loaded = this._settledModels();
|
|
1076
1177
|
const loading = this._loadingModels();
|
|
1077
1178
|
const custom = await customModelRows(options, { modelsDir: this._modelsDir, loaded });
|
|
1179
|
+
const memory = this._memoryByModel();
|
|
1078
1180
|
return mergeCustomRows(rows, custom).map((row) => ({
|
|
1079
1181
|
...row,
|
|
1080
|
-
isLoading: loading.has(row.name)
|
|
1182
|
+
isLoading: loading.has(row.name),
|
|
1183
|
+
memoryBytes: memory.get(row.name) ?? memory.get(residentSrc(row.name, row.type))
|
|
1081
1184
|
}));
|
|
1082
1185
|
}
|
|
1083
1186
|
async deleteModel(name) {
|
|
@@ -1159,7 +1262,7 @@ export class Harness extends ReadyResource {
|
|
|
1159
1262
|
if (!roomed) {
|
|
1160
1263
|
return;
|
|
1161
1264
|
}
|
|
1162
|
-
await this.
|
|
1265
|
+
await this._yieldGenerators();
|
|
1163
1266
|
}
|
|
1164
1267
|
await this._loadAndRelease(target, signal);
|
|
1165
1268
|
}
|
|
@@ -1175,20 +1278,20 @@ export class Harness extends ReadyResource {
|
|
|
1175
1278
|
const target = this._residency(model, agent);
|
|
1176
1279
|
const resident = isResident(target.pool, target.key);
|
|
1177
1280
|
hlog('load model', { model, resident });
|
|
1281
|
+
if (!resident && !(await this._admitByHand(model, target))) {
|
|
1282
|
+
return { loaded: false, reason: 'memory' };
|
|
1283
|
+
}
|
|
1178
1284
|
if (target.pool !== this._models) {
|
|
1179
|
-
await this.
|
|
1285
|
+
await this._loadByHand(target, signal);
|
|
1180
1286
|
return { loaded: true };
|
|
1181
1287
|
}
|
|
1182
1288
|
if (!resident) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
await evictIdleWhere(this._models, (key) => modelOf(key) === model && !this._replying.includes(key));
|
|
1187
|
-
await this._unloadIdleGenerators();
|
|
1289
|
+
await evictIdleWhere(this._models, this._replacedBy(model));
|
|
1290
|
+
await this._yieldGenerators();
|
|
1188
1291
|
}
|
|
1189
1292
|
this._pinned.add(target.key);
|
|
1190
1293
|
try {
|
|
1191
|
-
await this.
|
|
1294
|
+
await this._loadByHand(target, signal);
|
|
1192
1295
|
}
|
|
1193
1296
|
catch (err) {
|
|
1194
1297
|
this._pinned.delete(target.key);
|
|
@@ -1228,11 +1331,15 @@ export class Harness extends ReadyResource {
|
|
|
1228
1331
|
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
1229
1332
|
}
|
|
1230
1333
|
async _fits(model, ctxSize) {
|
|
1334
|
+
return (await this._curatedFit(model, ctxSize))?.fit !== 'likely-too-large';
|
|
1335
|
+
}
|
|
1336
|
+
async _curatedFit(model, ctxSize) {
|
|
1231
1337
|
const asset = curatedModelAsset(model, 'llm');
|
|
1232
1338
|
if (!asset) {
|
|
1233
|
-
return
|
|
1339
|
+
return null;
|
|
1234
1340
|
}
|
|
1235
|
-
|
|
1341
|
+
return this._assessFit({
|
|
1342
|
+
src: asset.src,
|
|
1236
1343
|
sha256Checksum: asset.sha256Checksum,
|
|
1237
1344
|
name: asset.name,
|
|
1238
1345
|
contextTokens: ctxSize || defaultContextSize(asset)
|
|
@@ -1240,7 +1347,273 @@ export class Harness extends ReadyResource {
|
|
|
1240
1347
|
hlog('fit estimate failed', { model, err: errorMessage(err) });
|
|
1241
1348
|
return null;
|
|
1242
1349
|
});
|
|
1243
|
-
|
|
1350
|
+
}
|
|
1351
|
+
async _admitByHand(model, target) {
|
|
1352
|
+
if (this._capBytes === null) {
|
|
1353
|
+
return (target.pool !== this._models ||
|
|
1354
|
+
this._alone(model, target) ||
|
|
1355
|
+
this._fits(model, target.ctxSize));
|
|
1356
|
+
}
|
|
1357
|
+
return this._admit(async () => {
|
|
1358
|
+
if (isResident(target.pool, target.key) || this._byHand.has(target.key)) {
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
const sized = await this._sizeByHand(model, target);
|
|
1362
|
+
const capBytes = this._capBytes;
|
|
1363
|
+
const replaced = this._replacedEntries(model, target);
|
|
1364
|
+
const used = this._usedBytes() - replaced.reduce((sum, { bytes }) => sum + (bytes ?? 0), 0);
|
|
1365
|
+
const alone = this._alone(model, target);
|
|
1366
|
+
const fits = capBytes === null || alone || sized.bytes === undefined || used + sized.bytes <= capBytes;
|
|
1367
|
+
if ((sized.tooLarge && !alone) || !fits) {
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
this._byHand.set(target.key, sized.ctxSize);
|
|
1371
|
+
if (sized.bytes !== undefined) {
|
|
1372
|
+
this._reserved.set(target.key, sized.bytes);
|
|
1373
|
+
}
|
|
1374
|
+
return true;
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
_replacedBy(model) {
|
|
1378
|
+
return (key) => modelOf(key) === model && !this._replying.includes(key);
|
|
1379
|
+
}
|
|
1380
|
+
_replacedEntries(model, target) {
|
|
1381
|
+
const replacedBy = this._replacedBy(model);
|
|
1382
|
+
return target.pool === this._models
|
|
1383
|
+
? settledEntries(this._models).filter(({ key, refs }) => refs === 0 && replacedBy(key))
|
|
1384
|
+
: [];
|
|
1385
|
+
}
|
|
1386
|
+
_alone(model, target) {
|
|
1387
|
+
const settled = this._pools().flatMap((pool) => settledEntries(pool));
|
|
1388
|
+
const loading = this._pools().some((pool) => loadingModels(pool).length > 0);
|
|
1389
|
+
return (!loading &&
|
|
1390
|
+
this._reserved.size === 0 &&
|
|
1391
|
+
settled.length === this._replacedEntries(model, target).length);
|
|
1392
|
+
}
|
|
1393
|
+
async _sizeByHand(model, target) {
|
|
1394
|
+
const addon = modelAddonOf(model);
|
|
1395
|
+
const modelSrc = curatedModelAsset(model, addon) ?? customModelSrc(model, addon) ?? model;
|
|
1396
|
+
const ctxSize = target.pool === this._models
|
|
1397
|
+
? target.ctxSize || defaultContextSize(modelSrc, customContextLength(model))
|
|
1398
|
+
: undefined;
|
|
1399
|
+
const sized = await this._sizeLoad({
|
|
1400
|
+
key: target.key,
|
|
1401
|
+
model,
|
|
1402
|
+
modelSrc,
|
|
1403
|
+
modelType: target.modelType,
|
|
1404
|
+
modelConfig: ctxSize === undefined
|
|
1405
|
+
? undefined
|
|
1406
|
+
: { ctx_size: ctxSize, projectionModelSrc: this._projectionFor(model) }
|
|
1407
|
+
});
|
|
1408
|
+
return { ...sized, ctxSize };
|
|
1409
|
+
}
|
|
1410
|
+
async _loadByHand(target, signal) {
|
|
1411
|
+
const loading = target.open().load();
|
|
1412
|
+
void loading.then(noop, noop).then(() => {
|
|
1413
|
+
if (this._byHand.delete(target.key)) {
|
|
1414
|
+
this._reserved.delete(target.key);
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
try {
|
|
1418
|
+
await raceAbort(loading, signal);
|
|
1419
|
+
}
|
|
1420
|
+
finally {
|
|
1421
|
+
releaseModel(target.pool, target.key);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
async _admit(run) {
|
|
1425
|
+
await this._admission.lock();
|
|
1426
|
+
try {
|
|
1427
|
+
return await run();
|
|
1428
|
+
}
|
|
1429
|
+
finally {
|
|
1430
|
+
this._admission.unlock();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
async _makeRoomForGenerator(srcs, signal) {
|
|
1434
|
+
if (signal?.aborted) {
|
|
1435
|
+
throw new AbortedError();
|
|
1436
|
+
}
|
|
1437
|
+
await this._makeRoom(stagesBytes(srcs), { replying: true });
|
|
1438
|
+
if (signal?.aborted) {
|
|
1439
|
+
throw new AbortedError();
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
async _yieldGenerators() {
|
|
1443
|
+
if (this._capBytes === null) {
|
|
1444
|
+
await this._unloadIdleGenerators();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
async _beforeLoad(load) {
|
|
1448
|
+
if (!this._sizeLoads)
|
|
1449
|
+
return undefined;
|
|
1450
|
+
const bytes = this._byHand.has(load.key)
|
|
1451
|
+
? await this._admit(() => this._reserveByHand(load))
|
|
1452
|
+
: await this._admit(() => this._reserve(load));
|
|
1453
|
+
return { bytes, release: () => this._reserved.delete(load.key) };
|
|
1454
|
+
}
|
|
1455
|
+
async _reserveByHand(load) {
|
|
1456
|
+
const admittedAt = this._byHand.get(load.key);
|
|
1457
|
+
this._byHand.delete(load.key);
|
|
1458
|
+
if (load.modelConfig?.['ctx_size'] !== admittedAt) {
|
|
1459
|
+
const { bytes } = await this._sizeLoad(load);
|
|
1460
|
+
if (bytes !== undefined) {
|
|
1461
|
+
this._reserved.set(load.key, bytes);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return this._reserved.get(load.key);
|
|
1465
|
+
}
|
|
1466
|
+
async _reserve(load) {
|
|
1467
|
+
const sized = await this._sizeLoad(load);
|
|
1468
|
+
if (this._capBytes !== null) {
|
|
1469
|
+
await this._makeRoom(sized.bytes ?? 0, {
|
|
1470
|
+
generators: true,
|
|
1471
|
+
tooLarge: sized.tooLarge,
|
|
1472
|
+
recheck: async () => (await this._sizeLoad(load)).tooLarge
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
if (sized.bytes !== undefined) {
|
|
1476
|
+
this._reserved.set(load.key, sized.bytes);
|
|
1477
|
+
}
|
|
1478
|
+
return sized.bytes;
|
|
1479
|
+
}
|
|
1480
|
+
async _sizeLoad({ model, modelSrc, modelType, modelConfig }) {
|
|
1481
|
+
const downloaded = downloadBytes(modelSrc);
|
|
1482
|
+
const { projectionModelSrc, ...config } = modelConfig ?? {};
|
|
1483
|
+
const projection = downloadBytes(projectionModelSrc) ?? 0;
|
|
1484
|
+
try {
|
|
1485
|
+
const sdk = await this._sdk.load();
|
|
1486
|
+
const { models } = await sdk.assessModelFit({
|
|
1487
|
+
models: [{ modelSrc: fitSrc(modelSrc), modelType, modelConfig: config }]
|
|
1488
|
+
});
|
|
1489
|
+
const [fit] = models;
|
|
1490
|
+
const bytes = fit?.estimate?.upperBoundBytes ?? fit?.floorBytes ?? downloaded;
|
|
1491
|
+
return {
|
|
1492
|
+
bytes: bytes === undefined ? undefined : bytes + projection,
|
|
1493
|
+
tooLarge: fit?.verdict === 'likely-too-large',
|
|
1494
|
+
confirmed: fit?.verdict === 'likely-fits' &&
|
|
1495
|
+
(fit.evidence === 'calibration' || fit.evidence === 'native-fit')
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
catch (err) {
|
|
1499
|
+
hlog('fit estimate failed', { model, err: errorMessage(err) });
|
|
1500
|
+
return {
|
|
1501
|
+
bytes: downloaded === undefined ? undefined : downloaded + projection,
|
|
1502
|
+
tooLarge: false,
|
|
1503
|
+
confirmed: false
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
async _chooseContext(load, tier) {
|
|
1508
|
+
if (isTranslationModel(load.model)) {
|
|
1509
|
+
return tier;
|
|
1510
|
+
}
|
|
1511
|
+
const ctxSize = await this._contexts.choose(load.key, tier, {
|
|
1512
|
+
trained: () => this._trainedContext(load),
|
|
1513
|
+
fits: async (rung) => {
|
|
1514
|
+
const sized = await this._sizeLoad({
|
|
1515
|
+
...load,
|
|
1516
|
+
modelConfig: { ...load.modelConfig, ctx_size: rung }
|
|
1517
|
+
});
|
|
1518
|
+
const room = this._capRoom(load.key);
|
|
1519
|
+
return sized.confirmed && (room === null || (sized.bytes ?? Infinity) <= room);
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
hlog('context chosen', { model: load.model, tier, ctxSize });
|
|
1523
|
+
return ctxSize;
|
|
1524
|
+
}
|
|
1525
|
+
_capRoom(key) {
|
|
1526
|
+
const cap = this._capBytes;
|
|
1527
|
+
return cap === null ? null : cap - (this._usedBytes() - (this._reserved.get(key) ?? 0));
|
|
1528
|
+
}
|
|
1529
|
+
async _contextProfile() {
|
|
1530
|
+
const { capabilityProfile, memoryBudget } = await this.capability();
|
|
1531
|
+
return memoryBudget ? capabilityProfile : undefined;
|
|
1532
|
+
}
|
|
1533
|
+
async _trainedContext({ model, modelSrc }) {
|
|
1534
|
+
const custom = customContextLength(model);
|
|
1535
|
+
if (custom || typeof modelSrc === 'string') {
|
|
1536
|
+
return custom;
|
|
1537
|
+
}
|
|
1538
|
+
const sdk = await this._sdk.load();
|
|
1539
|
+
const info = await sdk.getModelInfo({ name: modelSrc.name }).catch(() => null);
|
|
1540
|
+
const file = info?.cacheFiles[0]?.path;
|
|
1541
|
+
const bytes = file ? await readGgufHeaderBytes(file) : null;
|
|
1542
|
+
return (bytes && parseGgufHeader(bytes)?.contextLength) || undefined;
|
|
1543
|
+
}
|
|
1544
|
+
_ctxSizeOf(key, loadOpts) {
|
|
1545
|
+
return loadOpts.ctxSize || this._contexts.chosen(key);
|
|
1546
|
+
}
|
|
1547
|
+
async _makeRoom(need, room) {
|
|
1548
|
+
const capBytes = this._capBytes;
|
|
1549
|
+
if (capBytes === null) {
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
const tried = new Set();
|
|
1553
|
+
let tooLarge = room.tooLarge ?? false;
|
|
1554
|
+
while (this._usedBytes() + need > capBytes || tooLarge) {
|
|
1555
|
+
const victim = nextVictim(this._capVictims(room).filter(({ id }) => !tried.has(id)));
|
|
1556
|
+
if (!victim) {
|
|
1557
|
+
hlog('model over memory cap', { need, used: this._usedBytes(), capBytes, tooLarge });
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
tried.add(victim.id);
|
|
1561
|
+
await victim.evict();
|
|
1562
|
+
if (tooLarge && room.recheck) {
|
|
1563
|
+
tooLarge = await room.recheck();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
_capVictims({ replying = false, generators = false }) {
|
|
1568
|
+
const victims = [];
|
|
1569
|
+
if (generators) {
|
|
1570
|
+
victims.push({
|
|
1571
|
+
id: 'generators',
|
|
1572
|
+
rank: VictimRank.generator,
|
|
1573
|
+
lastUsed: 0,
|
|
1574
|
+
evict: () => this._unloadIdleGenerators()
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
for (const [index, pool] of this._pools().entries()) {
|
|
1578
|
+
for (const { key, refs, lastUsed } of settledEntries(pool)) {
|
|
1579
|
+
const inReply = this._replying.includes(key);
|
|
1580
|
+
if (refs > 0 || (inReply && !replying)) {
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
victims.push({
|
|
1584
|
+
id: `${index}:${key}`,
|
|
1585
|
+
rank: this._victimRank(pool, key, inReply),
|
|
1586
|
+
lastUsed,
|
|
1587
|
+
evict: () => evictIdleWithin(pool, key, { deadlineMs: WEDGED_EVICT_WAIT_MS })
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return victims;
|
|
1592
|
+
}
|
|
1593
|
+
_victimRank(pool, key, inReply) {
|
|
1594
|
+
if (pool !== this._models)
|
|
1595
|
+
return VictimRank.companion;
|
|
1596
|
+
if (inReply)
|
|
1597
|
+
return VictimRank.replying;
|
|
1598
|
+
return this._pinned.has(key) ? VictimRank.pinned : VictimRank.requested;
|
|
1599
|
+
}
|
|
1600
|
+
_usedBytes() {
|
|
1601
|
+
return (this._settledBytes() + [...this._reserved.values()].reduce((sum, bytes) => sum + bytes, 0));
|
|
1602
|
+
}
|
|
1603
|
+
_settledBytes() {
|
|
1604
|
+
return this._pools()
|
|
1605
|
+
.flatMap((pool) => settledEntries(pool))
|
|
1606
|
+
.reduce((sum, { bytes }) => sum + (bytes ?? 0), 0);
|
|
1607
|
+
}
|
|
1608
|
+
_memoryByModel() {
|
|
1609
|
+
const memory = new Map();
|
|
1610
|
+
for (const { key, bytes } of this._pools().flatMap((pool) => settledEntries(pool))) {
|
|
1611
|
+
if (bytes === undefined)
|
|
1612
|
+
continue;
|
|
1613
|
+
const model = modelOf(key);
|
|
1614
|
+
memory.set(model, (memory.get(model) ?? 0) + bytes);
|
|
1615
|
+
}
|
|
1616
|
+
return memory;
|
|
1244
1617
|
}
|
|
1245
1618
|
async _yieldPins(model, ctxSize) {
|
|
1246
1619
|
for (const key of this._pinned) {
|
|
@@ -1261,18 +1634,25 @@ export class Harness extends ReadyResource {
|
|
|
1261
1634
|
return {
|
|
1262
1635
|
pool: this._models,
|
|
1263
1636
|
key,
|
|
1264
|
-
|
|
1637
|
+
modelType: 'llamacpp-completion',
|
|
1638
|
+
ctxSize: this._ctxSizeOf(key, loadOpts),
|
|
1265
1639
|
open: () => this._models.open(key, model, loadOpts)
|
|
1266
1640
|
};
|
|
1267
1641
|
}
|
|
1268
1642
|
if (addon === 'embeddings') {
|
|
1269
1643
|
const key = this._embedFaults.key(residentSrc(model, addon));
|
|
1270
|
-
return {
|
|
1644
|
+
return {
|
|
1645
|
+
pool: this._embedModels,
|
|
1646
|
+
key,
|
|
1647
|
+
modelType: 'llamacpp-embedding',
|
|
1648
|
+
open: () => this._embedModels.open(key, model)
|
|
1649
|
+
};
|
|
1271
1650
|
}
|
|
1272
1651
|
if (addon === 'tts' || addon === 'parakeet') {
|
|
1273
1652
|
const pool = addon === 'tts' ? this._ttsModels : this._transcriptionModels;
|
|
1274
1653
|
const key = residentSrc(model, addon);
|
|
1275
|
-
|
|
1654
|
+
const modelType = addon === 'tts' ? 'tts-ggml' : 'parakeet-transcription';
|
|
1655
|
+
return { pool, key, modelType, open: () => pool.open(key, key) };
|
|
1276
1656
|
}
|
|
1277
1657
|
throw new Error(`${model} loads for each generation and is not kept in memory`);
|
|
1278
1658
|
}
|
|
@@ -1317,7 +1697,7 @@ export class Harness extends ReadyResource {
|
|
|
1317
1697
|
completion(context) {
|
|
1318
1698
|
return this._busyStream(() => this._completion(context));
|
|
1319
1699
|
}
|
|
1320
|
-
async *_completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
1700
|
+
async *_completion({ model, agent, messages, typedText, cacheKey, session, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
1321
1701
|
if (!this.opened)
|
|
1322
1702
|
await this.ready();
|
|
1323
1703
|
if (this.suspended)
|
|
@@ -1329,13 +1709,16 @@ export class Harness extends ReadyResource {
|
|
|
1329
1709
|
const resident = isResident(pool, key);
|
|
1330
1710
|
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
1331
1711
|
if (!resident) {
|
|
1332
|
-
await this._makeRoomForCompletion(model, { ctxSize:
|
|
1333
|
-
await this.
|
|
1712
|
+
await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
|
|
1713
|
+
await this._yieldGenerators();
|
|
1334
1714
|
}
|
|
1335
1715
|
const acquireRound = async () => {
|
|
1336
1716
|
if (!isResident(pool, key)) {
|
|
1337
|
-
await this._makeRoomForCompletion(model, {
|
|
1338
|
-
|
|
1717
|
+
await this._makeRoomForCompletion(model, {
|
|
1718
|
+
ctxSize: this._ctxSizeOf(key, loadOpts),
|
|
1719
|
+
signal
|
|
1720
|
+
});
|
|
1721
|
+
await this._yieldGenerators();
|
|
1339
1722
|
}
|
|
1340
1723
|
try {
|
|
1341
1724
|
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
@@ -1382,11 +1765,13 @@ export class Harness extends ReadyResource {
|
|
|
1382
1765
|
agent,
|
|
1383
1766
|
ctxSize,
|
|
1384
1767
|
cacheKey,
|
|
1768
|
+
session,
|
|
1385
1769
|
thinkingDefault,
|
|
1386
1770
|
generationParams,
|
|
1387
1771
|
responseFormat,
|
|
1388
1772
|
captureInferenceCalls,
|
|
1389
1773
|
messages: turns,
|
|
1774
|
+
typedText,
|
|
1390
1775
|
tools: this._tools,
|
|
1391
1776
|
skillCatalog: await this._completionSkills(agent?.id ?? null, model),
|
|
1392
1777
|
toolConfigs,
|
|
@@ -1449,6 +1834,10 @@ export class Harness extends ReadyResource {
|
|
|
1449
1834
|
};
|
|
1450
1835
|
}
|
|
1451
1836
|
async _makeRoomForCompletion(model, { ctxSize, signal, deadlineMs = MODEL_ROOM_WAIT_MS } = {}) {
|
|
1837
|
+
if (this._capBytes !== null) {
|
|
1838
|
+
await evictIdleWhere(this._models, (key) => modelOf(key) === model && !this._replying.includes(key));
|
|
1839
|
+
return true;
|
|
1840
|
+
}
|
|
1452
1841
|
const roomed = await awaitModelCapacity(this._models, {
|
|
1453
1842
|
cap: this._completionModelCap,
|
|
1454
1843
|
deadlineMs,
|