@bendyline/gezel-inference 1.0.7 → 1.0.9
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/dist/catalog/chat-models/ge/gemma4-12b-q4/manifest.json +46 -3
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.2.1/manifest.json +141 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/manifest.json +5 -0
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/or/ornith1.5-9b-q4/manifest.json +5 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q8/manifest.json +5 -0
- package/dist/catalog/chat-models/qw/qwen3.5-9b-q4/manifest.json +5 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/manifest.json +5 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/manifest.json +24 -7
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/versions/1.0.3/manifest.json +47 -0
- package/dist/catalog/chat-models/raw-index.json +1 -1
- package/dist/catalog/provenance.json +1 -1
- package/dist/engines/electron-native-reuse.d.ts +5 -4
- package/dist/engines/electron-native-reuse.js +1 -1
- package/dist/engines/native-file-manifest.json +12344 -698
- package/dist/engines/native-manifest.d.ts +4 -4
- package/dist/engines/native-manifest.js +40 -36
- package/dist/engines/resolver.d.ts +18 -17
- package/dist/engines/resolver.js +36 -1
- package/dist/http/model-inventory-cache.d.ts +1 -1
- package/dist/http/routes/engines.js +3 -3
- package/dist/http/routes/v1-images.d.ts +2 -1
- package/dist/http/routes/v1-images.js +36 -9
- package/dist/knowledge/manager.d.ts +3 -27
- package/dist/knowledge/manager.js +8 -364
- package/dist/knowledge/unified-search.d.ts +61 -0
- package/dist/knowledge/unified-search.js +373 -0
- package/dist/models/catalog-drift.d.ts +2 -0
- package/dist/models/catalog-drift.js +1 -1
- package/dist/models/catalog-payload-identity.d.ts +2 -2
- package/dist/models/catalog-payload-identity.js +10 -5
- package/dist/providers/active-install-message.js +1 -1
- package/dist/providers/catalog-model-config.d.ts +28 -0
- package/dist/providers/ds4/build-provider.js +35 -13
- package/dist/providers/ds4/residency.d.ts +77 -0
- package/dist/providers/ds4/residency.js +87 -6
- package/dist/providers/image/sd-cpp.js +19 -11
- package/dist/providers/llama-cpp/backend-health.d.ts +3 -2
- package/dist/providers/llama-cpp/backend-health.js +6 -3
- package/dist/providers/llama-cpp/backend-quarantine.js +1 -1
- package/dist/providers/llama-cpp/build-provider.js +168 -29
- package/dist/providers/llama-cpp/devices.js +4 -1
- package/dist/providers/llama-cpp/engine-flags.d.ts +8 -1
- package/dist/providers/llama-cpp/engine-flags.js +13 -8
- package/dist/providers/llama-cpp/gpu-startup-watch.d.ts +4 -4
- package/dist/providers/llama-cpp/gpu-startup-watch.js +7 -5
- package/dist/providers/llama-cpp/models.d.ts +10 -0
- package/dist/providers/llama-cpp/models.js +64 -10
- package/dist/providers/llama-cpp/offload-planner.d.ts +6 -0
- package/dist/providers/llama-cpp/offload-planner.js +1 -1
- package/dist/providers/llama-cpp/spec-draft.d.ts +45 -23
- package/dist/providers/llama-cpp/spec-draft.js +113 -24
- package/dist/providers/mlx/async-file-handoff.d.ts +9 -1
- package/dist/providers/mlx/async-file-handoff.js +32 -4
- package/dist/providers/mlx/provider.js +28 -16
- package/dist/providers/mlx/python/spec_decode.py +23 -2
- package/dist/providers/mlx/python/tool_args_json.py +59 -21
- package/dist/providers/mlx/runtime-diagnostics.d.ts +6 -0
- package/dist/providers/mlx/runtime-diagnostics.js +18 -1
- package/dist/providers/mlx/tool-call-protocol.d.ts +4 -3
- package/dist/providers/mlx/tool-call-protocol.js +4 -3
- package/dist/providers/mlx/venv.d.ts +19 -1
- package/dist/providers/mlx/venv.js +21 -2
- package/dist/providers/native/device-capacity-ledger.d.ts +4 -2
- package/dist/providers/native/device-capacity-ledger.js +35 -20
- package/dist/providers/native/device-capacity.d.ts +4 -0
- package/dist/providers/native/device-capacity.js +5 -0
- package/dist/providers/native/local-engine-runtime.d.ts +16 -1
- package/dist/providers/native/local-engine-runtime.js +142 -39
- package/dist/providers/native/process-identity.d.ts +38 -0
- package/dist/providers/native/process-identity.js +86 -0
- package/dist/providers/native/supervisor.d.ts +8 -1
- package/dist/providers/native/supervisor.js +16 -7
- package/dist/providers/types.d.ts +4 -0
- package/dist/remotes/types.d.ts +1 -1
- package/dist/runtime-lock.d.ts +3 -27
- package/dist/runtime-lock.js +7 -80
- package/package.json +4 -4
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { createLogger } from '@bendyline/gezel';
|
|
4
|
-
import { estimateLlamaCppResidentBytes } from '@bendyline/gezel';
|
|
3
|
+
import { createLogger, ds4SsdStreamingSupported } from '@bendyline/gezel';
|
|
4
|
+
import { estimateLlamaCppResidentBytes, llamaCppDraftResidentBytes, } from '@bendyline/gezel';
|
|
5
5
|
import { resolveCatalogLlamaCppEngineConfig } from '../catalog-model-config.js';
|
|
6
|
-
import { buildDs4Provider, resolveDs4LaunchCtx, resolveDs4VisionLaunch, } from '../ds4/build-provider.js';
|
|
6
|
+
import { buildDs4Provider, ds4RamTieredContext, resolveDs4LaunchCtx, resolveDs4VisionLaunch, } from '../ds4/build-provider.js';
|
|
7
7
|
import { ds4VisionResidentBytes } from '../ds4/residency.js';
|
|
8
|
+
import { currentLlamaCppBackend } from '../llama-cpp/backend-health.js';
|
|
8
9
|
import { buildLlamaCppProvider, ensureLlamaEngineStatus } from '../llama-cpp/build-provider.js';
|
|
9
10
|
import { readGgufSummaryAsync } from '../llama-cpp/gguf-metadata-async.js';
|
|
10
11
|
import { effectiveLazyTensorBytes } from '../llama-cpp/gguf-metadata.js';
|
|
11
12
|
import { isGemmaModel, planLlamaCppKv, resolveLlamaCppKvCacheType, } from '../llama-cpp/kv-cache-type.js';
|
|
12
13
|
import { estimateExactPerSlotKvBytesF16, estimateKvReserveBytes, estimateWindowedKvLinearization, } from '../llama-cpp/offload-planner.js';
|
|
14
|
+
import { drafterCostsContext, llamaCppSidecarLoads, resolveSpecDraft, } from '../llama-cpp/spec-draft.js';
|
|
13
15
|
import { buildMlxProvider, resolveMlxEffectiveNumCtx } from '../mlx/build-provider.js';
|
|
14
16
|
import { readMlxModelGeometry } from '../mlx/model-geometry.js';
|
|
15
17
|
import { ModelNotInstalledError } from '../types.js';
|
|
@@ -594,7 +596,8 @@ export class LocalEngineRuntime {
|
|
|
594
596
|
const config = await this.store.readConfig();
|
|
595
597
|
const externalBaseUrl = process.env.GEZEL_DS4_SERVER_URL ?? config.ds4BaseUrl;
|
|
596
598
|
if (!externalBaseUrl) {
|
|
597
|
-
const { ds4ProjectedResidentBytes, ds4ResidentBytesForMode, ds4ResidentLine, shouldUseDs4SsdStreaming, } = await import('../ds4/residency.js');
|
|
599
|
+
const { ds4ProjectedResidentBytes, ds4ResidentBytesForMode, ds4ResidentLine, planDs4MeasuredLaunch, shouldUseDs4SsdStreaming, } = await import('../ds4/residency.js');
|
|
600
|
+
const { totalMemoryBytes } = await import('../../system/host-memory.js');
|
|
598
601
|
// Bill the window this model will actually launch with. The
|
|
599
602
|
// authored footprint is a measurement at `residentCtxTokens`, so
|
|
600
603
|
// without this a raised context reserves the old number and the
|
|
@@ -603,29 +606,51 @@ export class LocalEngineRuntime {
|
|
|
603
606
|
const ds4Block = cm.ds4;
|
|
604
607
|
const installed = await this.ds4Models?.resolveModel(modelId).catch(() => null);
|
|
605
608
|
const visionResidentBytes = await this.resolveDs4VisionResidentBytes(config, modelId, installed, ds4Block);
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
609
|
+
const totalRamBytes = totalMemoryBytes();
|
|
610
|
+
const ds4Override = config.modelContextOverrides?.[`ds4:${modelId}`];
|
|
611
|
+
const contextFloor = minViableLocalContextTokens();
|
|
612
|
+
const numCtx = resolveDs4LaunchCtx({
|
|
613
|
+
configured: ds4Override ?? config.ds4NumCtx,
|
|
614
|
+
ramTieredCtx: ds4RamTieredContext(totalRamBytes),
|
|
615
|
+
catalogMaxCtx: ds4Block?.maxLaunchCtx,
|
|
616
|
+
minViableContextTokens: ds4Override !== undefined ? Math.min(contextFloor, ds4Override) : contextFloor,
|
|
610
617
|
});
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
bytes = ds4ProjectedResidentBytes(line, resolveDs4LaunchCtx({
|
|
614
|
-
configured: config.modelContextOverrides?.[`ds4:${modelId}`] ?? config.ds4NumCtx,
|
|
615
|
-
ramTieredCtx: totalMemoryBytes() / 1024 ** 3 >= 192 ? 262_144 : 131_072,
|
|
616
|
-
catalogMaxCtx: ds4Block?.maxLaunchCtx,
|
|
617
|
-
minViableContextTokens: minViableLocalContextTokens(),
|
|
618
|
-
}));
|
|
619
|
-
}
|
|
620
|
-
bytes = ds4ResidentBytesForMode(bytes, shouldUseDs4SsdStreaming({
|
|
618
|
+
const streamingSupported = ds4SsdStreamingSupported(ds4Block, process.platform);
|
|
619
|
+
const ssdStreaming = shouldUseDs4SsdStreaming({
|
|
621
620
|
configured: config.ds4SsdStreaming,
|
|
622
|
-
...(
|
|
623
|
-
? { ssdStreamingSupported:
|
|
621
|
+
...(streamingSupported !== undefined
|
|
622
|
+
? { ssdStreamingSupported: streamingSupported }
|
|
624
623
|
: {}),
|
|
625
624
|
modelSizeBytes: ds4Block?.residentWeightBytes ?? block.approxSizeBytes,
|
|
625
|
+
totalRamBytes,
|
|
626
626
|
...(visionResidentBytes > 0 ? { companionBytes: visionResidentBytes } : {}),
|
|
627
|
-
})
|
|
628
|
-
|
|
627
|
+
});
|
|
628
|
+
const measured = planDs4MeasuredLaunch({
|
|
629
|
+
source: ds4Block,
|
|
630
|
+
numCtx,
|
|
631
|
+
ssdStreaming,
|
|
632
|
+
totalRamBytes,
|
|
633
|
+
budgetBytes: ssdStreaming && ds4Block?.streamingMemory
|
|
634
|
+
? await (await import('./device-capacity.js')).deviceModelBudgetBytes(config)
|
|
635
|
+
: undefined,
|
|
636
|
+
sidecarBytes: visionResidentBytes,
|
|
637
|
+
configuredCacheGb: config.ds4CacheExpertsGb,
|
|
638
|
+
});
|
|
639
|
+
if (measured) {
|
|
640
|
+
bytes = measured.workingSetBytes;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
const line = ds4ResidentLine({
|
|
644
|
+
residentBytes: bytes,
|
|
645
|
+
kvBytesPerToken: ds4Block?.kvBytesPerToken,
|
|
646
|
+
residentCtxTokens: ds4Block?.residentCtxTokens,
|
|
647
|
+
});
|
|
648
|
+
if (line)
|
|
649
|
+
bytes = ds4ProjectedResidentBytes(line, numCtx);
|
|
650
|
+
bytes =
|
|
651
|
+
ds4ResidentBytesForMode(bytes, ssdStreaming, streamingSupported) +
|
|
652
|
+
visionResidentBytes;
|
|
653
|
+
}
|
|
629
654
|
}
|
|
630
655
|
}
|
|
631
656
|
}
|
|
@@ -1047,7 +1072,9 @@ export class LocalEngineRuntime {
|
|
|
1047
1072
|
throw new ModelNotInstalledError(name, modelId);
|
|
1048
1073
|
}
|
|
1049
1074
|
const { totalMemoryBytes } = await import('../../system/host-memory.js');
|
|
1050
|
-
|
|
1075
|
+
// The launcher's own tier: 64K below 96 GiB, so a smaller Mac is quoted
|
|
1076
|
+
// the window and footprint it will actually launch with.
|
|
1077
|
+
const ramTieredCtx = ds4RamTieredContext(totalMemoryBytes());
|
|
1051
1078
|
const ds4Override = config.modelContextOverrides?.[`ds4:${modelId}`];
|
|
1052
1079
|
const ds4Floor = ds4Override !== undefined ? Math.min(contextFloor, ds4Override) : contextFloor;
|
|
1053
1080
|
const effective = resolveDs4LaunchCtx({
|
|
@@ -1086,36 +1113,69 @@ export class LocalEngineRuntime {
|
|
|
1086
1113
|
// `fixed + slope × ctx` line llama.cpp/MLX rows carry, so the slider
|
|
1087
1114
|
// prices a drag instead of claiming the footprint never moves. Entries
|
|
1088
1115
|
// without a measured slope keep the flat number and say so.
|
|
1089
|
-
const { ds4ProjectedResidentBytes, ds4ResidentBytesForMode, ds4ResidentLine, shouldUseDs4SsdStreaming, } = await import('../ds4/residency.js');
|
|
1116
|
+
const { ds4ProjectedResidentBytes, ds4ResidentBytesForMode, ds4ResidentLine, planDs4MeasuredLaunch, shouldUseDs4SsdStreaming, } = await import('../ds4/residency.js');
|
|
1090
1117
|
const ds4Line = ds4ResidentLine({
|
|
1091
1118
|
residentBytes: ds4Source?.residentBytes,
|
|
1092
1119
|
kvBytesPerToken: ds4Source?.kvBytesPerToken,
|
|
1093
1120
|
residentCtxTokens: ds4Source?.residentCtxTokens,
|
|
1094
1121
|
});
|
|
1095
1122
|
const visionResidentBytes = await this.resolveDs4VisionResidentBytes(config, modelId, installed, ds4Source);
|
|
1123
|
+
const streamingSupported = ds4SsdStreamingSupported(ds4Source, process.platform);
|
|
1096
1124
|
const ssdStreaming = shouldUseDs4SsdStreaming({
|
|
1097
1125
|
configured: config.ds4SsdStreaming,
|
|
1098
|
-
...(
|
|
1099
|
-
? { ssdStreamingSupported: ds4Source.ssdStreamingSupported }
|
|
1100
|
-
: {}),
|
|
1126
|
+
...(streamingSupported !== undefined ? { ssdStreamingSupported: streamingSupported } : {}),
|
|
1101
1127
|
modelSizeBytes: ds4Source?.residentWeightBytes ??
|
|
1102
1128
|
ds4Source?.approxSizeBytes ??
|
|
1103
1129
|
installed?.approxSizeBytes,
|
|
1104
1130
|
...(visionResidentBytes > 0 ? { companionBytes: visionResidentBytes } : {}),
|
|
1105
1131
|
});
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1132
|
+
// A measurement for this launch's mode prices it exactly — the window
|
|
1133
|
+
// moves only that mode's slope, and a streamed launch carries the cache
|
|
1134
|
+
// the launcher would choose. Unmeasured entries keep the authored line.
|
|
1135
|
+
const measured = planDs4MeasuredLaunch({
|
|
1136
|
+
source: ds4Source,
|
|
1137
|
+
numCtx: effective,
|
|
1138
|
+
ssdStreaming,
|
|
1139
|
+
totalRamBytes: totalMemoryBytes(),
|
|
1140
|
+
budgetBytes: ssdStreaming && ds4Source?.streamingMemory
|
|
1141
|
+
? await (await import('./device-capacity.js')).deviceModelBudgetBytes(config)
|
|
1142
|
+
: undefined,
|
|
1143
|
+
sidecarBytes: visionResidentBytes,
|
|
1144
|
+
configuredCacheGb: config.ds4CacheExpertsGb,
|
|
1145
|
+
});
|
|
1146
|
+
const plannedResidentBytes = measured
|
|
1147
|
+
? measured.workingSetBytes
|
|
1148
|
+
: ds4Line
|
|
1149
|
+
? ds4ResidentBytesForMode(ds4ProjectedResidentBytes(ds4Line, effective), ssdStreaming, streamingSupported) + visionResidentBytes
|
|
1150
|
+
: await this.resolveResidentBytes('ds4', modelId);
|
|
1109
1151
|
return {
|
|
1110
1152
|
contextWindow: useResidentOr(effective, Math.min(effective, ds4Floor)),
|
|
1111
1153
|
plannedResidentBytes,
|
|
1112
|
-
|
|
1154
|
+
ssdStreaming,
|
|
1155
|
+
...(measured
|
|
1113
1156
|
? {
|
|
1114
|
-
|
|
1157
|
+
memoryMeasured: true,
|
|
1158
|
+
weightsResidentBytes: measured.contextFreeBytes,
|
|
1115
1159
|
kvFixedBytesPerSlot: 0,
|
|
1116
|
-
kvBytesPerTokenPerSlot:
|
|
1160
|
+
kvBytesPerTokenPerSlot: measured.bytesPerToken,
|
|
1161
|
+
...(measured.cache
|
|
1162
|
+
? {
|
|
1163
|
+
expertCache: {
|
|
1164
|
+
cacheBytes: measured.cache.cacheGb * 1024 ** 3,
|
|
1165
|
+
requestedBytes: measured.cache.requestedGb * 1024 ** 3,
|
|
1166
|
+
clamped: measured.cache.clamped,
|
|
1167
|
+
safe: measured.cache.safe,
|
|
1168
|
+
},
|
|
1169
|
+
}
|
|
1170
|
+
: {}),
|
|
1117
1171
|
}
|
|
1118
|
-
:
|
|
1172
|
+
: ds4Line
|
|
1173
|
+
? {
|
|
1174
|
+
weightsResidentBytes: ds4ResidentBytesForMode(ds4Line.contextFreeBytes, ssdStreaming, streamingSupported) + visionResidentBytes,
|
|
1175
|
+
kvFixedBytesPerSlot: 0,
|
|
1176
|
+
kvBytesPerTokenPerSlot: ds4Line.kvBytesPerToken,
|
|
1177
|
+
}
|
|
1178
|
+
: {}),
|
|
1119
1179
|
...(nativeWindow ? { nativeContextWindow: nativeWindow } : {}),
|
|
1120
1180
|
...(ds4Override !== undefined ? { overrideContextTokens: ds4Override } : {}),
|
|
1121
1181
|
...(ds4AutoContextWindow !== undefined ? { autoContextWindow: ds4AutoContextWindow } : {}),
|
|
@@ -1190,9 +1250,32 @@ export class LocalEngineRuntime {
|
|
|
1190
1250
|
? effectiveLazyTensorBytes(summary, config.llamaCppLazyMode ?? manifestEngineConfig?.lazyMode ?? 'auto')
|
|
1191
1251
|
: 0;
|
|
1192
1252
|
const residentWeightFileBytes = Math.max(0, installed.approxSizeBytes - lazyTensorBytes);
|
|
1193
|
-
const
|
|
1253
|
+
const { perModel: previewEngineConfig } = await resolveSpecDraft({
|
|
1254
|
+
perModel: manifestEngineConfig,
|
|
1255
|
+
backend: currentLlamaCppBackend(),
|
|
1256
|
+
configSpecType: config.llamaCppSpecType,
|
|
1257
|
+
configDraftModelPath: config.llamaCppDraftModelPath,
|
|
1258
|
+
installedDraftModelPath: installed.draftModelPath,
|
|
1259
|
+
resolveDraftPath: async (id) => (await this.llamaCppModels?.resolveModelPath(id)) ?? null,
|
|
1260
|
+
});
|
|
1261
|
+
const previewSpecType = config.llamaCppSpecType ?? previewEngineConfig?.spec?.type;
|
|
1262
|
+
const draftBudgetBytes = llamaCppSidecarLoads({
|
|
1263
|
+
specType: previewSpecType,
|
|
1264
|
+
targetHasMtp: (summary?.nextnPredictLayers ?? 0) > 0,
|
|
1265
|
+
perBackend: !!manifestEngineConfig?.spec?.byBackend,
|
|
1266
|
+
}) && installed.draftModelPath
|
|
1267
|
+
? (installed.draftModelSizeBytes ?? 0)
|
|
1268
|
+
: 0;
|
|
1269
|
+
const catalogDetail = draftBudgetBytes > 0 ? await this.catalog.get('chat-model', modelId).catch(() => null) : null;
|
|
1270
|
+
const draftResidentBytes = llamaCppDraftResidentBytes(catalogDetail?.manifest.kind === 'chat-model'
|
|
1271
|
+
? catalogDetail.manifest.llamaCpp?.draftModel
|
|
1272
|
+
: undefined, installed.draftModelPath);
|
|
1273
|
+
const residentBytesWith = (draftBytes) => estimateLlamaCppResidentBytes(residentWeightFileBytes, {
|
|
1194
1274
|
mmprojBytes: visionBudgetBytes,
|
|
1275
|
+
draftBytes,
|
|
1276
|
+
draftResidentBytes,
|
|
1195
1277
|
});
|
|
1278
|
+
let residentWeightsBytes = residentBytesWith(draftBudgetBytes);
|
|
1196
1279
|
const exactPerSlotKvF16 = summary
|
|
1197
1280
|
? estimateExactPerSlotKvBytesF16({
|
|
1198
1281
|
blockCount: summary.blockCount,
|
|
@@ -1452,6 +1535,7 @@ export class LocalEngineRuntime {
|
|
|
1452
1535
|
maxSlots: defaultLocalEngineSlots(fastBudgetBytes),
|
|
1453
1536
|
});
|
|
1454
1537
|
kv = kvPlan.kvCacheType;
|
|
1538
|
+
const draftFits = planFitsAt(kv, kvPlan.ctxCapTokens ?? grantedCtx, 1);
|
|
1455
1539
|
// Mirror the launch: the f16-by-context-cap trade shrinks the granted
|
|
1456
1540
|
// window here too, so the settings preview shows what will really run.
|
|
1457
1541
|
if (kvPlan.ctxCapTokens !== undefined && kvPlan.ctxCapTokens < grantedCtx) {
|
|
@@ -1462,7 +1546,7 @@ export class LocalEngineRuntime {
|
|
|
1462
1546
|
ceiling: passCeiling(kv, grantedCtx),
|
|
1463
1547
|
tierDefault: defaultLocalEngineSlots(fastBudgetBytes),
|
|
1464
1548
|
});
|
|
1465
|
-
if (
|
|
1549
|
+
if (previewSpecType === 'draft-mtp')
|
|
1466
1550
|
slots = 1;
|
|
1467
1551
|
try {
|
|
1468
1552
|
if (!summary)
|
|
@@ -1544,7 +1628,14 @@ export class LocalEngineRuntime {
|
|
|
1544
1628
|
// SWA model without a readable layout, over budget on full
|
|
1545
1629
|
// math: the launch path leaves such a launch untouched — preview
|
|
1546
1630
|
// the requested window.
|
|
1547
|
-
return {
|
|
1631
|
+
return {
|
|
1632
|
+
grantedCtx,
|
|
1633
|
+
slots,
|
|
1634
|
+
kvCacheType: kv,
|
|
1635
|
+
plannedFieldsOk: false,
|
|
1636
|
+
draftFits,
|
|
1637
|
+
kvPlan,
|
|
1638
|
+
};
|
|
1548
1639
|
}
|
|
1549
1640
|
}
|
|
1550
1641
|
if (!admission.minimumSatisfied) {
|
|
@@ -1588,9 +1679,21 @@ export class LocalEngineRuntime {
|
|
|
1588
1679
|
throw error;
|
|
1589
1680
|
log.warn(`[llama-cpp] could not inspect ${modelId} while previewing admission: ${error instanceof Error ? error.message : String(error)}`);
|
|
1590
1681
|
}
|
|
1591
|
-
return { grantedCtx, slots, kvCacheType: kv, plannedFieldsOk: true };
|
|
1682
|
+
return { grantedCtx, slots, kvCacheType: kv, plannedFieldsOk: true, draftFits, kvPlan };
|
|
1592
1683
|
};
|
|
1593
|
-
|
|
1684
|
+
let plan = llamaPlanPass(explicitContextWindow, contextFloor);
|
|
1685
|
+
// The launch leaves out a catalog drafter that would cap the target window
|
|
1686
|
+
// or cost its KV precision; the preview has to land on the same plan.
|
|
1687
|
+
if (draftBudgetBytes > 0 && !config.llamaCppSpecType && !config.llamaCppDraftModelPath) {
|
|
1688
|
+
residentWeightsBytes = residentBytesWith(0);
|
|
1689
|
+
const withoutDrafter = llamaPlanPass(explicitContextWindow, contextFloor);
|
|
1690
|
+
if (drafterCostsContext(plan.kvPlan, withoutDrafter.kvPlan, plan.draftFits)) {
|
|
1691
|
+
plan = withoutDrafter;
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
residentWeightsBytes = residentBytesWith(draftBudgetBytes);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1594
1697
|
let autoContextWindow;
|
|
1595
1698
|
if (overrideActive) {
|
|
1596
1699
|
try {
|
|
@@ -1,2 +1,40 @@
|
|
|
1
|
+
export declare function processAlive(pid: number): boolean;
|
|
1
2
|
/** Birth stamps survive coordinator restarts and distinguish a reused PID. */
|
|
2
3
|
export declare function readProcessIdentities(pids: readonly number[]): Promise<ReadonlyMap<number, string>>;
|
|
4
|
+
/**
|
|
5
|
+
* How long a stamp read for a still-live PID is trusted. A PID can die and be
|
|
6
|
+
* reused between two liveness checks; this bounds how long such a dead
|
|
7
|
+
* engine's reservation outlives it. That is the safe direction: memory looks
|
|
8
|
+
* taken for a few seconds longer, never free twice.
|
|
9
|
+
*/
|
|
10
|
+
export declare const VERIFIED_IDENTITY_TTL_MS = 10000;
|
|
11
|
+
export interface ProcessIdentityCacheOptions {
|
|
12
|
+
read?: (pids: readonly number[]) => Promise<ReadonlyMap<number, string>>;
|
|
13
|
+
alive?: (pid: number) => boolean;
|
|
14
|
+
now?: () => number;
|
|
15
|
+
ttlMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reading a birth stamp starts `ps`, or PowerShell on Windows. The capacity
|
|
19
|
+
* ledger used to read every PID it holds on each command, which ran before
|
|
20
|
+
* every engine request. A stamp read for a PID that is still alive is reused
|
|
21
|
+
* for a short while, and the PIDs that do need reading go in one batch.
|
|
22
|
+
*
|
|
23
|
+
* A cached stamp may only confirm the stamp a reservation already recorded.
|
|
24
|
+
* A stamp about to be recorded, or one that disagrees, is read again: another
|
|
25
|
+
* coordinator may have recorded a newer process at that PID, and trusting an
|
|
26
|
+
* older stamp there would free a live engine's memory.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ProcessIdentityCache {
|
|
29
|
+
private readonly options;
|
|
30
|
+
private readonly verified;
|
|
31
|
+
constructor(options?: ProcessIdentityCacheOptions);
|
|
32
|
+
/**
|
|
33
|
+
* `record`: PIDs whose stamp is about to be written onto a reservation.
|
|
34
|
+
* `confirm`: each PID's stamps as reservations recorded them.
|
|
35
|
+
* PIDs that are dead, or whose stamp cannot be read, are left out.
|
|
36
|
+
*/
|
|
37
|
+
lookup(record: Iterable<number>, confirm: ReadonlyMap<number, readonly string[]>): Promise<ReadonlyMap<number, string>>;
|
|
38
|
+
}
|
|
39
|
+
/** One cache per process: every ledger in it asks about the same OS processes. */
|
|
40
|
+
export declare const sharedProcessIdentities: ProcessIdentityCache;
|
|
@@ -11,6 +11,15 @@ const PROBE_PID_LIMIT = {
|
|
|
11
11
|
darwin: 99_999,
|
|
12
12
|
win32: 2_147_483_647,
|
|
13
13
|
};
|
|
14
|
+
export function processAlive(pid) {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return error.code !== 'ESRCH';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
/** Birth stamps survive coordinator restarts and distinguish a reused PID. */
|
|
15
24
|
export async function readProcessIdentities(pids) {
|
|
16
25
|
const limit = PROBE_PID_LIMIT[process.platform] ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -68,3 +77,80 @@ export async function readProcessIdentities(pids) {
|
|
|
68
77
|
}
|
|
69
78
|
return identities;
|
|
70
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* How long a stamp read for a still-live PID is trusted. A PID can die and be
|
|
82
|
+
* reused between two liveness checks; this bounds how long such a dead
|
|
83
|
+
* engine's reservation outlives it. That is the safe direction: memory looks
|
|
84
|
+
* taken for a few seconds longer, never free twice.
|
|
85
|
+
*/
|
|
86
|
+
export const VERIFIED_IDENTITY_TTL_MS = 10_000;
|
|
87
|
+
/**
|
|
88
|
+
* Reading a birth stamp starts `ps`, or PowerShell on Windows. The capacity
|
|
89
|
+
* ledger used to read every PID it holds on each command, which ran before
|
|
90
|
+
* every engine request. A stamp read for a PID that is still alive is reused
|
|
91
|
+
* for a short while, and the PIDs that do need reading go in one batch.
|
|
92
|
+
*
|
|
93
|
+
* A cached stamp may only confirm the stamp a reservation already recorded.
|
|
94
|
+
* A stamp about to be recorded, or one that disagrees, is read again: another
|
|
95
|
+
* coordinator may have recorded a newer process at that PID, and trusting an
|
|
96
|
+
* older stamp there would free a live engine's memory.
|
|
97
|
+
*/
|
|
98
|
+
export class ProcessIdentityCache {
|
|
99
|
+
options;
|
|
100
|
+
verified = new Map();
|
|
101
|
+
constructor(options = {}) {
|
|
102
|
+
this.options = options;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* `record`: PIDs whose stamp is about to be written onto a reservation.
|
|
106
|
+
* `confirm`: each PID's stamps as reservations recorded them.
|
|
107
|
+
* PIDs that are dead, or whose stamp cannot be read, are left out.
|
|
108
|
+
*/
|
|
109
|
+
async lookup(record, confirm) {
|
|
110
|
+
const alive = this.options.alive ?? processAlive;
|
|
111
|
+
const ttl = this.options.ttlMs ?? VERIFIED_IDENTITY_TTL_MS;
|
|
112
|
+
const now = (this.options.now ?? (() => performance.now()))();
|
|
113
|
+
for (const [pid, entry] of this.verified) {
|
|
114
|
+
if (now - entry.at >= ttl)
|
|
115
|
+
this.verified.delete(pid);
|
|
116
|
+
}
|
|
117
|
+
const live = (pid) => {
|
|
118
|
+
if (alive(pid))
|
|
119
|
+
return true;
|
|
120
|
+
// Forget a PID seen dead, so whatever process takes it next is read.
|
|
121
|
+
this.verified.delete(pid);
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
const identities = new Map();
|
|
125
|
+
const unread = new Set();
|
|
126
|
+
for (const pid of record)
|
|
127
|
+
if (live(pid))
|
|
128
|
+
unread.add(pid);
|
|
129
|
+
for (const [pid, stamps] of confirm) {
|
|
130
|
+
if (unread.has(pid) || !live(pid))
|
|
131
|
+
continue;
|
|
132
|
+
const cached = this.verified.get(pid)?.identity;
|
|
133
|
+
if (cached !== undefined && stamps.every((stamp) => stamp === cached)) {
|
|
134
|
+
identities.set(pid, cached);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
unread.add(pid);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (unread.size === 0)
|
|
141
|
+
return identities;
|
|
142
|
+
const read = await (this.options.read ?? readProcessIdentities)([...unread]).catch(() => new Map());
|
|
143
|
+
for (const pid of unread) {
|
|
144
|
+
const identity = read.get(pid);
|
|
145
|
+
if (identity === undefined) {
|
|
146
|
+
this.verified.delete(pid);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
this.verified.set(pid, { identity, at: now });
|
|
150
|
+
identities.set(pid, identity);
|
|
151
|
+
}
|
|
152
|
+
return identities;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** One cache per process: every ledger in it asks about the same OS processes. */
|
|
156
|
+
export const sharedProcessIdentities = new ProcessIdentityCache();
|
|
@@ -516,7 +516,14 @@ export declare class NativeEngineSupervisor {
|
|
|
516
516
|
private clearFreezeTimer;
|
|
517
517
|
private clearHealthTimer;
|
|
518
518
|
}
|
|
519
|
-
/**
|
|
519
|
+
/**
|
|
520
|
+
* High-precision fatal signatures; attribution happens only if the child exits.
|
|
521
|
+
*
|
|
522
|
+
* The `cuda-*` kinds also cover ROCm builds: ggml's ROCm backend is its CUDA
|
|
523
|
+
* code compiled with HIP, and it prints the same errors under the name `ROCm`
|
|
524
|
+
* (`ROCm error: out of memory`). The kinds are wire values, so they keep their
|
|
525
|
+
* names rather than growing a ROCm twin each.
|
|
526
|
+
*/
|
|
520
527
|
export declare function classifyNativeEnginePanic(line: string): {
|
|
521
528
|
kind: NativeEnginePanicKind;
|
|
522
529
|
line: string;
|
|
@@ -1415,13 +1415,20 @@ export class NativeEngineSupervisor {
|
|
|
1415
1415
|
}
|
|
1416
1416
|
}
|
|
1417
1417
|
}
|
|
1418
|
-
/**
|
|
1418
|
+
/**
|
|
1419
|
+
* High-precision fatal signatures; attribution happens only if the child exits.
|
|
1420
|
+
*
|
|
1421
|
+
* The `cuda-*` kinds also cover ROCm builds: ggml's ROCm backend is its CUDA
|
|
1422
|
+
* code compiled with HIP, and it prints the same errors under the name `ROCm`
|
|
1423
|
+
* (`ROCm error: out of memory`). The kinds are wire values, so they keep their
|
|
1424
|
+
* names rather than growing a ROCm twin each.
|
|
1425
|
+
*/
|
|
1419
1426
|
export function classifyNativeEnginePanic(line) {
|
|
1420
1427
|
const clipped = line.trim().slice(0, 1_000);
|
|
1421
|
-
if (/CUDA error:\s*invalid (?:configuration )?argument/i.test(line)) {
|
|
1428
|
+
if (/(?:CUDA|ROCm) error:\s*invalid (?:configuration )?argument/i.test(line)) {
|
|
1422
1429
|
return { kind: 'cuda-invalid-argument', line: clipped };
|
|
1423
1430
|
}
|
|
1424
|
-
if (/CUDA error:\s*(?:out of memory|memory allocation)/i.test(line)) {
|
|
1431
|
+
if (/(?:CUDA|ROCm) error:\s*(?:out of memory|memory allocation)/i.test(line)) {
|
|
1425
1432
|
return { kind: 'cuda-out-of-memory', line: clipped };
|
|
1426
1433
|
}
|
|
1427
1434
|
// Vulkan's spellings of the same condition — the backend a non-CUDA
|
|
@@ -1429,16 +1436,18 @@ export function classifyNativeEnginePanic(line) {
|
|
|
1429
1436
|
if (/ErrorOutOfDeviceMemory|OutOfDeviceMemoryError|ggml_vulkan:.*(?:allocation|memory).*failed/i.test(line)) {
|
|
1430
1437
|
return { kind: 'vulkan-out-of-memory', line: clipped };
|
|
1431
1438
|
}
|
|
1432
|
-
if (/CUDA error:\s*(?:an )?illegal memory access/i.test(line)) {
|
|
1439
|
+
if (/(?:CUDA|ROCm) error:\s*(?:an )?illegal memory access/i.test(line)) {
|
|
1433
1440
|
return { kind: 'cuda-illegal-memory-access', line: clipped };
|
|
1434
1441
|
}
|
|
1435
|
-
if (/CUDA error:.*device-side assert/i.test(line)) {
|
|
1442
|
+
if (/(?:CUDA|ROCm) error:.*device-side assert/i.test(line)) {
|
|
1436
1443
|
return { kind: 'cuda-device-assert', line: clipped };
|
|
1437
1444
|
}
|
|
1438
|
-
if (/CUDA error:\s*(?:no kernel image is available|the provided PTX was compiled with an unsupported toolchain)/i.test(line)
|
|
1445
|
+
if (/(?:CUDA|ROCm) error:\s*(?:no kernel image is available|invalid device function|the provided PTX was compiled with an unsupported toolchain)/i.test(line) ||
|
|
1446
|
+
// rocBLAS names the architecture it has no kernel library for, then aborts.
|
|
1447
|
+
/rocBLAS error: Cannot read .*TensileLibrary.*for GPU arch/i.test(line)) {
|
|
1439
1448
|
return { kind: 'cuda-unsupported-device', line: clipped };
|
|
1440
1449
|
}
|
|
1441
|
-
if (/CUDA error:/i.test(line))
|
|
1450
|
+
if (/(?:CUDA|ROCm) error:/i.test(line))
|
|
1442
1451
|
return { kind: 'cuda-error', line: clipped };
|
|
1443
1452
|
if (/assert(?:ion)? failed|GGML_ASSERT/i.test(line)) {
|
|
1444
1453
|
return { kind: 'assertion-failed', line: clipped };
|
|
@@ -600,6 +600,10 @@ export interface EngineLaunchSnapshot {
|
|
|
600
600
|
export interface ModelInfo {
|
|
601
601
|
id: string;
|
|
602
602
|
name: string;
|
|
603
|
+
/** One short line a picker shows beside the name. */
|
|
604
|
+
description?: string;
|
|
605
|
+
/** Heading a picker lists this model under; see core's `ModelInfoSchema`. */
|
|
606
|
+
group?: string;
|
|
603
607
|
supportsReasoning?: boolean;
|
|
604
608
|
reasoningEfforts?: string[];
|
|
605
609
|
defaultReasoningEffort?: string;
|
package/dist/remotes/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface PairedRemote {
|
|
|
11
11
|
remoteId: string;
|
|
12
12
|
/** Base URL A reaches the server at, e.g. `https://192.168.1.50:6229`. */
|
|
13
13
|
baseUrl: string;
|
|
14
|
-
/** Human label shown in A's model picker / Remote
|
|
14
|
+
/** Human label shown in A's model picker / Remote servers panel. */
|
|
15
15
|
displayName: string;
|
|
16
16
|
/** Per-device bearer token the server issued A (scope `remote-inference`). */
|
|
17
17
|
token: string;
|
package/dist/runtime-lock.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { type LockIdentity, currentLockIdentity, parseLockFile, procStartTicks, sharesPidNamespace } from '@bendyline/gezel/native';
|
|
2
|
+
export type { LockIdentity };
|
|
3
|
+
export { currentLockIdentity, parseLockFile, procStartTicks, sharesPidNamespace };
|
|
1
4
|
/**
|
|
2
5
|
* Thrown when a gezel home already has a live daemon. The CLI's `gezel
|
|
3
6
|
* start` / the supervisor surface this as an actionable message rather
|
|
@@ -12,33 +15,6 @@ export declare class SingleInstanceError extends Error {
|
|
|
12
15
|
export interface RuntimeLock {
|
|
13
16
|
release(): Promise<void>;
|
|
14
17
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Who holds a lock. A pid alone identifies a process only inside one pid
|
|
17
|
-
* namespace on one boot: two containers sharing a volume each see the
|
|
18
|
-
* other's pid as dead (or as an unrelated process), which let a one-shot
|
|
19
|
-
* `docker run … gezel do` reclaim a serving container's live lock. The
|
|
20
|
-
* namespace, boot and host fields say when a pid check is meaningful; a
|
|
21
|
-
* holder outside our namespace is judged by the lock file's heartbeat.
|
|
22
|
-
*/
|
|
23
|
-
export interface LockIdentity {
|
|
24
|
-
pid: number;
|
|
25
|
-
hostname?: string;
|
|
26
|
-
bootId?: string;
|
|
27
|
-
pidNs?: string;
|
|
28
|
-
/** Process start time in clock ticks since boot (`/proc/<pid>/stat` field 22). */
|
|
29
|
-
startTicks?: string;
|
|
30
|
-
/** Random per-process id; tells our own heartbeat whether the lock is still ours. */
|
|
31
|
-
instance?: string;
|
|
32
|
-
}
|
|
33
|
-
export declare function procStartTicks(pid: number | 'self'): string | undefined;
|
|
34
|
-
export declare function currentLockIdentity(): LockIdentity;
|
|
35
|
-
export declare function parseLockFile(text: string): LockIdentity | null;
|
|
36
|
-
/**
|
|
37
|
-
* Whether a pid check against `holder` means anything here. Any field the
|
|
38
|
-
* holder recorded must match ours; a legacy pid-only record is assumed to be
|
|
39
|
-
* in our namespace, which is how it was judged before.
|
|
40
|
-
*/
|
|
41
|
-
export declare function sharesPidNamespace(holder: LockIdentity, me: LockIdentity): boolean;
|
|
42
18
|
/**
|
|
43
19
|
* Acquire an exclusive single-instance lock for a gezel home so two daemons
|
|
44
20
|
* can't share one `~/.gezel/` and race its on-disk state (config, sessions,
|