@bendyline/gezel-inference 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -8
- package/dist/cache/adapter.d.ts +7 -0
- package/dist/cache/controller.d.ts +12 -0
- package/dist/cache/controller.js +24 -0
- package/dist/catalog/chat-models/bt/btl4-35b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/gl/glm-5.2-754b-q2/manifest.json +1 -0
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/manifest.json +122 -0
- package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/manifest.json +185 -0
- package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/versions/1.0.0/manifest.json +94 -0
- package/dist/catalog/chat-models/mi/minicpm5-1b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/mi/minicpm5-2b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/qw/qwen3.5-0.8b-q4/manifest.json +1 -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 +9 -0
- package/dist/engines/electron-native-reuse.js +146 -45
- package/dist/engines/native-file-manifest.json +404 -404
- package/dist/engines/native-manifest.d.ts +11 -3
- package/dist/engines/native-manifest.js +48 -18
- package/dist/http/openai-compat/apple-model.d.ts +2 -0
- package/dist/http/openai-compat/apple-model.js +21 -1
- package/dist/http/openai-compat/chat-target.d.ts +12 -0
- package/dist/http/openai-compat/chat-target.js +27 -1
- package/dist/http/openai-compat/engine-failure.d.ts +25 -0
- package/dist/http/openai-compat/engine-failure.js +76 -0
- package/dist/http/openai-compat/responses-streaming.js +2 -1
- package/dist/http/openai-compat/responses-translate.d.ts +1 -1
- package/dist/http/openai-compat/translate.d.ts +1 -1
- package/dist/http/routes/engines.js +44 -3
- package/dist/http/routes/v1-chat.js +30 -8
- package/dist/http/routes/v1-models.d.ts +6 -0
- package/dist/http/routes/v1-models.js +137 -30
- package/dist/http/routes/v1-responses.js +16 -3
- package/dist/in-flight-requests.d.ts +19 -0
- package/dist/in-flight-requests.js +34 -0
- package/dist/inference-service.js +3 -3
- package/dist/knowledge/catalog-host.d.ts +9 -1
- package/dist/knowledge/catalog-host.js +2 -0
- package/dist/knowledge/manager.d.ts +14 -0
- package/dist/knowledge/manager.js +53 -6
- package/dist/knowledge/search-worker.d.ts +1 -1
- package/dist/knowledge/spatial-query.d.ts +2 -3
- package/dist/knowledge/spatial-query.js +12 -6
- package/dist/knowledge/worker-host.js +2 -0
- package/dist/memory/embed-core.d.ts +16 -0
- package/dist/memory/embed-core.js +31 -0
- package/dist/memory/embed-worker.d.ts +7 -3
- package/dist/memory/embed-worker.js +13 -4
- package/dist/memory/embeddings.d.ts +17 -2
- package/dist/memory/embeddings.js +36 -7
- package/dist/models/ensure.d.ts +2 -0
- package/dist/models/ensure.js +9 -0
- package/dist/perf/responsiveness.d.ts +14 -1
- package/dist/perf/responsiveness.js +77 -7
- package/dist/providers/llama-cpp/backend-health.d.ts +30 -0
- package/dist/providers/llama-cpp/backend-health.js +64 -0
- package/dist/providers/llama-cpp/backend-quarantine.d.ts +40 -0
- package/dist/providers/llama-cpp/backend-quarantine.js +100 -0
- package/dist/providers/llama-cpp/build-provider.js +140 -118
- package/dist/providers/llama-cpp/cache-adapter.d.ts +12 -0
- package/dist/providers/llama-cpp/cache-adapter.js +36 -1
- package/dist/providers/llama-cpp/devices.d.ts +11 -2
- package/dist/providers/llama-cpp/devices.js +10 -4
- package/dist/providers/llama-cpp/engine-flags.d.ts +0 -18
- package/dist/providers/llama-cpp/engine-flags.js +12 -18
- package/dist/providers/llama-cpp/gguf-metadata-async.js +7 -0
- package/dist/providers/llama-cpp/gguf-metadata.js +20 -3
- package/dist/providers/llama-cpp/gpu-startup-watch.d.ts +41 -0
- package/dist/providers/llama-cpp/gpu-startup-watch.js +58 -0
- package/dist/providers/llama-cpp/models.d.ts +6 -0
- package/dist/providers/llama-cpp/models.js +19 -1
- package/dist/providers/llama-cpp/offload-planner.d.ts +3 -18
- package/dist/providers/llama-cpp/offload-planner.js +3 -14
- package/dist/providers/llama-cpp/provider.d.ts +8 -0
- package/dist/providers/llama-cpp/provider.js +4 -1
- package/dist/providers/mlx/provider.js +14 -11
- package/dist/providers/mlx/python/batch_processors.py +26 -0
- package/dist/providers/mlx/python/gezel_mlx_server.py +88 -20
- package/dist/providers/mlx/python/qwen4_ple.py +9 -2
- package/dist/providers/mlx/python/response_format.py +374 -0
- package/dist/providers/mlx/python/spec_decode.py +5 -0
- package/dist/providers/mlx/python/think_budget.py +35 -1
- package/dist/providers/mlx/python/tool_grammar.py +62 -3
- package/dist/providers/mlx/reasoning-stream.d.ts +20 -2
- package/dist/providers/mlx/reasoning-stream.js +77 -16
- package/dist/providers/mlx/runtime-diagnostics.d.ts +16 -0
- package/dist/providers/mlx/runtime-diagnostics.js +29 -2
- package/dist/providers/native/capacity-broker.d.ts +51 -10
- package/dist/providers/native/capacity-broker.js +80 -9
- package/dist/providers/native/capacity-budget.d.ts +35 -3
- package/dist/providers/native/capacity-budget.js +49 -5
- package/dist/providers/native/device-capacity.d.ts +1 -1
- package/dist/providers/native/device-capacity.js +23 -5
- package/dist/providers/native/local-engine-runtime.js +15 -13
- package/dist/providers/native/measured-budget.js +2 -0
- package/dist/providers/native/supervisor.d.ts +25 -0
- package/dist/providers/native/supervisor.js +59 -7
- package/dist/remotes/identity.d.ts +1 -0
- package/dist/remotes/identity.js +6 -0
- package/dist/remotes/pinned-fetch.d.ts +2 -0
- package/dist/remotes/pinned-fetch.js +30 -1
- package/dist/remotes/types.d.ts +14 -0
- package/dist/runtime-lock.js +1 -1
- package/dist/system/amdgpu-adapters.d.ts +18 -0
- package/dist/system/amdgpu-adapters.js +55 -0
- package/dist/system/gpu-carve-out.d.ts +84 -0
- package/dist/system/gpu-carve-out.js +111 -0
- package/dist/system/memory.d.ts +17 -1
- package/dist/system/memory.js +105 -14
- package/dist/transformers-cache.d.ts +2 -0
- package/dist/transformers-cache.js +6 -3
- package/dist/utils/service-worker-entry.d.ts +4 -0
- package/dist/utils/service-worker-entry.js +4 -0
- package/package.json +14 -10
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine process's side of the llama-server quarantine
|
|
3
|
+
* (`llama-quarantine.ts` in `@bendyline/gezel/native`): which build this
|
|
4
|
+
* process launches, kept in the env discovery stamps at startup
|
|
5
|
+
* (`GEZEL_LLAMA_SERVER_BIN`, `GEZEL_LLAMA_SERVER_BACKEND`,
|
|
6
|
+
* `GEZEL_LLAMA_QUARANTINED`), and the view and undo behind the engine's
|
|
7
|
+
* Settings tab.
|
|
8
|
+
*
|
|
9
|
+
* Discovery reads the quarantine only at startup. These move the env inside
|
|
10
|
+
* a running process, so a quarantine and its undo both take effect on the
|
|
11
|
+
* next engine launch instead of the next app start.
|
|
12
|
+
*/
|
|
13
|
+
import { LLAMA_QUARANTINE_STRIKES, clearLlamaQuarantine, createLlamaBuildCheck, isBinaryQuarantined, llamaVariantBinaryPath, publishLlamaCudaBlocker, readLlamaQuarantine, resolveLlamaFallbackBinary, resolveLlamaVariantBinary, } from '@bendyline/gezel/native';
|
|
14
|
+
const BACKENDS = ['cuda', 'vulkan', 'metal', 'cpu'];
|
|
15
|
+
export function isLlamaBackend(value) {
|
|
16
|
+
return BACKENDS.some((backend) => backend === value);
|
|
17
|
+
}
|
|
18
|
+
function envBackend(name) {
|
|
19
|
+
const raw = process.env[name];
|
|
20
|
+
return raw !== undefined && isLlamaBackend(raw) ? raw : undefined;
|
|
21
|
+
}
|
|
22
|
+
function updateQuarantinedEnv(update) {
|
|
23
|
+
const backends = new Set(process.env.GEZEL_LLAMA_QUARANTINED?.split(',').filter(Boolean));
|
|
24
|
+
update(backends);
|
|
25
|
+
if (backends.size > 0)
|
|
26
|
+
process.env.GEZEL_LLAMA_QUARANTINED = [...backends].join(',');
|
|
27
|
+
else
|
|
28
|
+
delete process.env.GEZEL_LLAMA_QUARANTINED;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Point this process at the build that replaces `backend`'s, just
|
|
32
|
+
* quarantined, so the pool's rebuild of the failed engine launches it. Null,
|
|
33
|
+
* env untouched, when nothing usable is bundled beside `binary`.
|
|
34
|
+
*/
|
|
35
|
+
export function adoptLlamaFallback(home, binary, backend) {
|
|
36
|
+
const quarantine = readLlamaQuarantine(home);
|
|
37
|
+
const fallback = resolveLlamaFallbackBinary(binary, backend, (candidate, path) => !isBinaryQuarantined(quarantine, candidate, path));
|
|
38
|
+
if (!fallback)
|
|
39
|
+
return null;
|
|
40
|
+
process.env.GEZEL_LLAMA_SERVER_BIN = fallback.path;
|
|
41
|
+
process.env.GEZEL_LLAMA_SERVER_BACKEND = fallback.backend;
|
|
42
|
+
updateQuarantinedEnv((backends) => backends.add(backend));
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The quarantined builds that still apply. An entry pins its binary's bytes,
|
|
47
|
+
* so one whose build an update replaced is left out — discovery ignores it
|
|
48
|
+
* too. Without a resolved build to measure from, entries are shown as written.
|
|
49
|
+
*/
|
|
50
|
+
export function llamaQuarantineView(home) {
|
|
51
|
+
const binary = process.env.GEZEL_LLAMA_SERVER_BIN;
|
|
52
|
+
const running = envBackend('GEZEL_LLAMA_SERVER_BACKEND');
|
|
53
|
+
const entries = readLlamaQuarantine(home).filter((entry) => {
|
|
54
|
+
if (!isLlamaBackend(entry.backend))
|
|
55
|
+
return false;
|
|
56
|
+
if (!binary || !running)
|
|
57
|
+
return true;
|
|
58
|
+
const path = llamaVariantBinaryPath(binary, running, entry.backend);
|
|
59
|
+
return path === null || isBinaryQuarantined([entry], entry.backend, path);
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
entries: entries.map(({ backend, signal, reason, at, crashes }) => ({
|
|
63
|
+
backend,
|
|
64
|
+
signal,
|
|
65
|
+
reason,
|
|
66
|
+
at,
|
|
67
|
+
...(crashes !== undefined ? { crashes } : {}),
|
|
68
|
+
})),
|
|
69
|
+
strikeThreshold: LLAMA_QUARANTINE_STRIKES,
|
|
70
|
+
...(running ? { running } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Lift `backend`'s quarantine at a person's request. Unless they pinned a
|
|
75
|
+
* backend, also point this process back at the best build the hardware
|
|
76
|
+
* supports, so the next engine launch tries it; `restored` is that build when
|
|
77
|
+
* the env moved. A pinned backend is launched as pinned and was never routed
|
|
78
|
+
* around, so there is nothing to move back.
|
|
79
|
+
*/
|
|
80
|
+
export function liftLlamaQuarantine(home, backend, opts) {
|
|
81
|
+
const cleared = clearLlamaQuarantine(home, backend);
|
|
82
|
+
updateQuarantinedEnv((backends) => backends.delete(backend));
|
|
83
|
+
const binary = process.env.GEZEL_LLAMA_SERVER_BIN;
|
|
84
|
+
const running = envBackend('GEZEL_LLAMA_SERVER_BACKEND');
|
|
85
|
+
if (opts.pinned || !binary || !running)
|
|
86
|
+
return { cleared, restored: null };
|
|
87
|
+
// Discovery's own check: a CUDA build the driver cannot run stays passed
|
|
88
|
+
// over, whatever else was lifted.
|
|
89
|
+
const check = createLlamaBuildCheck({
|
|
90
|
+
quarantine: readLlamaQuarantine(home),
|
|
91
|
+
platform: process.platform,
|
|
92
|
+
});
|
|
93
|
+
const best = resolveLlamaVariantBinary(binary, running, envBackend('GEZEL_LLAMA_DETECTED_BACKEND') ?? backend, check.isUsable);
|
|
94
|
+
publishLlamaCudaBlocker(check.cudaBlocker());
|
|
95
|
+
if (!best || best.backend === running)
|
|
96
|
+
return { cleared, restored: null };
|
|
97
|
+
process.env.GEZEL_LLAMA_SERVER_BIN = best.path;
|
|
98
|
+
process.env.GEZEL_LLAMA_SERVER_BACKEND = best.backend;
|
|
99
|
+
return { cleared, restored: best };
|
|
100
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os';
|
|
2
2
|
import { delimiter, dirname, join } from 'node:path';
|
|
3
|
-
import { DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS, createLogger, estimateLlamaCppResidentBytes, } from '@bendyline/gezel';
|
|
4
|
-
import {
|
|
3
|
+
import { DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS, createLogger, estimateLlamaCppResidentBytes, llamaCppBackendExclusion, llamaCppBackendLabel, } from '@bendyline/gezel';
|
|
4
|
+
import { LLAMA_QUARANTINE_STRIKES, clearLlamaStartupStrikes, recordLlamaStartupCrash, unrunnableBuildExit, } from '@bendyline/gezel/native';
|
|
5
5
|
import { gezelPaths } from '@bendyline/gezel/paths';
|
|
6
6
|
import { mmprojBudgetBytes, nativeVisionEnabledFor } from '../../chat/vision-capability.js';
|
|
7
7
|
import { effectiveEngineRelease, isEnginePinned } from '../../engines/native-manifest.js';
|
|
@@ -15,13 +15,16 @@ import { makeEngineKey } from '../native/engine-key.js';
|
|
|
15
15
|
import { pickFreePort } from '../native/port.js';
|
|
16
16
|
import { NativeEngineSupervisor } from '../native/supervisor.js';
|
|
17
17
|
import { patientFetch } from '../patient-fetch.js';
|
|
18
|
+
import { currentLlamaCppBackend, noteGpuStartup, noteHostOutOfMemory } from './backend-health.js';
|
|
19
|
+
import { adoptLlamaFallback } from './backend-quarantine.js';
|
|
18
20
|
import { lastArgValue, readLlamaCppBuildMetadata } from './build-metadata.js';
|
|
19
21
|
import { matchNvidiaRuntimeDevice, maxGpuVramBytes, pickBestGpuDevice, probeLlamaDevices, probeNvidiaRuntimeDevices, } from './devices.js';
|
|
20
22
|
import { buildLlamaCppEngineArgs } from './engine-flags.js';
|
|
21
23
|
import { readGgufSummaryAsync } from './gguf-metadata-async.js';
|
|
22
24
|
import { effectiveLazyTensorBytes } from './gguf-metadata.js';
|
|
25
|
+
import { GpuStartupWatch } from './gpu-startup-watch.js';
|
|
23
26
|
import { isGemmaModel, planLlamaCppKv, resolveLlamaCppKvCacheType, } from './kv-cache-type.js';
|
|
24
|
-
import { degradeDenseFfnOffloadDecision, degradeMoeOffloadDecision, estimateExactPerSlotKvBytesF16, estimateKvReserveBytes, estimateWindowedKvLinearization,
|
|
27
|
+
import { degradeDenseFfnOffloadDecision, degradeMoeOffloadDecision, estimateExactPerSlotKvBytesF16, estimateKvReserveBytes, estimateWindowedKvLinearization, planDenseFfnOffload, planMoeOffload, } from './offload-planner.js';
|
|
25
28
|
import { LlamaCppProvider } from './provider.js';
|
|
26
29
|
import { reasoningLaunchOverridesFromEnv } from './reasoning-launch.js';
|
|
27
30
|
import { resolveSpecDraft } from './spec-draft.js';
|
|
@@ -69,6 +72,15 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
69
72
|
? undefined
|
|
70
73
|
: (process.env.GEZEL_LLAMA_SERVER_URL ?? config.llamaCppBaseUrl);
|
|
71
74
|
const binary = process.env.GEZEL_LLAMA_SERVER_BIN;
|
|
75
|
+
// Read together with the binary: after a quarantine the env points at the
|
|
76
|
+
// fallback build, and a crash of THIS build must be charged to its own
|
|
77
|
+
// backend, not to whatever the env names by then.
|
|
78
|
+
const binaryBackend = process.env.GEZEL_LLAMA_SERVER_BACKEND;
|
|
79
|
+
// What the latest startup crash did to this build's standing, for the error
|
|
80
|
+
// the failed start raises. `onExit` runs before `ensureRunning` rejects.
|
|
81
|
+
let lastStartupCrash = null;
|
|
82
|
+
// Fresh for each launch: whether this start of a GPU build got its GPU.
|
|
83
|
+
let gpuWatch = null;
|
|
72
84
|
// Preservation is a two-sided contract: launch llama-server with the
|
|
73
85
|
// explicit preserve/no-preserve flag AND replay captured
|
|
74
86
|
// `reasoning_content` only in the preserve case. v0.4.0 changed the server
|
|
@@ -269,6 +281,18 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
269
281
|
}
|
|
270
282
|
throw notReadyError(noModelYetMessage('Local model', activeLlamaInstall(opts.llamaCppModels)), 'model-not-ready');
|
|
271
283
|
}
|
|
284
|
+
// Install refuses an excluded backend; this catches a model installed
|
|
285
|
+
// before the exclusion was authored, or copied in by hand.
|
|
286
|
+
if (modelCatalogInfo && opts.catalog) {
|
|
287
|
+
const engineBackend = binaryBackend ?? currentLlamaCppBackend();
|
|
288
|
+
const detail = await opts.catalog.get('chat-model', modelCatalogInfo.id).catch(() => null);
|
|
289
|
+
const excluded = llamaCppBackendExclusion(detail?.manifest.kind === 'chat-model' ? detail.manifest.llamaCpp : undefined, engineBackend);
|
|
290
|
+
if (excluded && engineBackend) {
|
|
291
|
+
const err = new Error(`Local model: ${modelCatalogInfo.name} cannot run on the ${llamaCppBackendLabel(engineBackend)} llama.cpp engine. ${excluded} Pick another local model in Settings → ${localDeviceSettingsLabel()}.`);
|
|
292
|
+
err.isActionable = true;
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
272
296
|
// Per Phase 0 measurements on a cold Mac: Metal library compile is
|
|
273
297
|
// ~14s and model load adds model-size-dependent time. Default
|
|
274
298
|
// startupTimeoutMs is 60s which squeaks through for a 7B on a warm
|
|
@@ -621,13 +645,6 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
621
645
|
// back to the engine's own `--fit` / `-ngl auto`.
|
|
622
646
|
let offloadDecision;
|
|
623
647
|
let offloadBlockCount;
|
|
624
|
-
// Whether weights + full-attention KV at the requested context fit the
|
|
625
|
-
// fast-memory pool — VRAM on a discrete GPU. Gates the Gemma
|
|
626
|
-
// `--swa-full` auto-default (see `EngineFlagInput.swaFullAutoFits`). Set
|
|
627
|
-
// false to keep the full context on the windowed cache when Full would
|
|
628
|
-
// spill or require a slot/context reduction; undefined means it fits or
|
|
629
|
-
// the exact decision could not be computed.
|
|
630
|
-
let swaFullAutoFits;
|
|
631
648
|
// Broker-ledger reservation for this launch: resident weights + the KV
|
|
632
649
|
// the engine will actually allocate at the granted window and cache
|
|
633
650
|
// mode. Undefined when the GGUF is unreadable — the pool builder then
|
|
@@ -709,21 +726,16 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
709
726
|
// under GEZEL_LLAMA_NUM_CTX — eval runs lift ceilings deliberately
|
|
710
727
|
// and accept the memory consequences.
|
|
711
728
|
//
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
//
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
// ungated: admission re-plans with the windowed-KV linearization
|
|
723
|
-
// (SWA layers = fixed window-capped bytes, global layers = the only
|
|
724
|
-
// per-token cost), so a host too small even for the windowed cache
|
|
725
|
-
// still denies or clamps honestly instead of OOMing one machine
|
|
726
|
-
// class below the 2026-08-05 incident.
|
|
729
|
+
// A sliding-window model runs llama.cpp's windowed cache unless
|
|
730
|
+
// `swaFull` is pinned, and admission prices what runs: it re-plans
|
|
731
|
+
// with the windowed-KV linearization (SWA layers = fixed window-capped
|
|
732
|
+
// bytes, global layers = the only per-token cost), so a host too small
|
|
733
|
+
// even for the windowed cache still denies or clamps honestly instead
|
|
734
|
+
// of OOMing one machine class below the 2026-08-05 incident.
|
|
735
|
+
// Shared-KV layers and SWA-specific head dimensions are load-bearing
|
|
736
|
+
// in that math — E4B owns only 24 KV caches for 42 logical layers, and
|
|
737
|
+
// pricing all 42 at global dimensions overstated its two-slot 64K KV
|
|
738
|
+
// allocation as 21 GiB instead of llama.cpp's actual 7 GiB.
|
|
727
739
|
{
|
|
728
740
|
const REFERENCE_CTX = 4096;
|
|
729
741
|
const exactKvAtReference = estimateKvReserveBytes({
|
|
@@ -769,23 +781,14 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
769
781
|
? {
|
|
770
782
|
unifiedMemory: liveMemory.gpuMemoryKind === 'integrated' ||
|
|
771
783
|
liveMemory.gpuMemoryKind === 'unified',
|
|
784
|
+
reservedGpuBytes: liveMemory.gpuReservedBytes ?? null,
|
|
785
|
+
gpuAddressableBytes: liveMemory.gpuAddressableBytes ?? null,
|
|
772
786
|
}
|
|
773
787
|
: {}),
|
|
774
788
|
});
|
|
775
789
|
const fastBudgetBytes = brokerSnap?.enforced
|
|
776
790
|
? (opts.broker?.fastBudgetBytes() ?? liveBudget.fastBytes)
|
|
777
791
|
: liveBudget.fastBytes;
|
|
778
|
-
const fullKvBytesAtRequested = exactKvAtReference !== undefined
|
|
779
|
-
? (exactKvAtReference / REFERENCE_CTX) * effectiveNumCtx * slots
|
|
780
|
-
: undefined;
|
|
781
|
-
const fullCacheFitsFastMemory = fullKvBytesAtRequested !== undefined
|
|
782
|
-
? fitsSwaFullInFastMemory({
|
|
783
|
-
residentWeightsBytes: residentBytes,
|
|
784
|
-
fullKvBytes: fullKvBytesAtRequested,
|
|
785
|
-
fastBudgetBytes,
|
|
786
|
-
committedOtherBytes,
|
|
787
|
-
})
|
|
788
|
-
: undefined;
|
|
789
792
|
const admission = planCtxTokensForMemory({
|
|
790
793
|
requestedPerTurnCtxTokens: effectiveNumCtx,
|
|
791
794
|
slots,
|
|
@@ -797,19 +800,13 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
797
800
|
freeSystemRamBytes: availableSystemRamBytes(),
|
|
798
801
|
vramBytes: liveBudget.vramBytes,
|
|
799
802
|
});
|
|
803
|
+
// `--swa-full` runs only when pinned; every other launch of a
|
|
804
|
+
// sliding-window model gets llama.cpp's windowed cache.
|
|
800
805
|
const explicitSwaFull = config.llamaCppSwaFull ?? manifestEngineConfig?.swaFull;
|
|
801
|
-
const
|
|
802
|
-
isGemmaModel({
|
|
803
|
-
architecture: modelCatalogInfo?.architecture,
|
|
804
|
-
modelId: defaultModelId ?? undefined,
|
|
805
|
-
});
|
|
806
|
-
const fullKvOverBudget = fullCacheFitsFastMemory === false ||
|
|
807
|
-
!admission.minimumSatisfied ||
|
|
808
|
-
admission.slots < slots ||
|
|
809
|
-
admission.clamped;
|
|
806
|
+
const fullKvOverBudget = !admission.minimumSatisfied || admission.slots < slots || admission.clamped;
|
|
810
807
|
// The plan whose ladder (deny → shed slots → clamp) is enforced
|
|
811
808
|
// below. Defaults to the full-attention plan; the windowed-cache
|
|
812
|
-
//
|
|
809
|
+
// branch replaces it with windowed math or — only when the GGUF
|
|
813
810
|
// hides its SWA layout — null (no safe estimate; launch untouched,
|
|
814
811
|
// the pre-windowed-admission behavior).
|
|
815
812
|
let ladderPlan = admission;
|
|
@@ -827,17 +824,17 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
827
824
|
// full-attention), and the strict minimum rides inside
|
|
828
825
|
// `contextRequirement.minimumPerTurnCtxTokens`, so the windowed
|
|
829
826
|
// re-plan below sheds slots or denies but never shortens the window.
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
// context
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
//
|
|
827
|
+
if (explicitSwaFull !== true) {
|
|
828
|
+
// The admission above is full-attention KV math, which overstates
|
|
829
|
+
// the windowed cache's real allocation (Gemma 4: global layers are
|
|
830
|
+
// both fewer AND cheaper per token than SWA layers — ~11× on 31b
|
|
831
|
+
// at 64K). Re-plan with the windowed linearization: SWA layers
|
|
832
|
+
// become a fixed, context-independent reservation; only global
|
|
833
|
+
// layers scale. Without it a 31b-class Gemma on a ~32 GB host
|
|
834
|
+
// would launch with NO context admission at all — the same
|
|
835
|
+
// lazy-Metal-wiring OOM this gate exists to prevent, one machine
|
|
836
|
+
// class down — and growth would price every token at the full
|
|
837
|
+
// slope.
|
|
841
838
|
const windowed = estimateWindowedKvLinearization({
|
|
842
839
|
blockCount: summary.blockCount,
|
|
843
840
|
embeddingLength: summary.embeddingLength,
|
|
@@ -858,23 +855,6 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
858
855
|
ssmConvKernel: summary.ssmConvKernel,
|
|
859
856
|
kvCacheType,
|
|
860
857
|
});
|
|
861
|
-
if (swaFullAutoDefault) {
|
|
862
|
-
// Declining `--swa-full` dominates every full-math remedy:
|
|
863
|
-
// the context window is capability, the full cache is a
|
|
864
|
-
// session-switch performance trade. Weights-level admission
|
|
865
|
-
// stays with the capacity broker.
|
|
866
|
-
swaFullAutoFits = false;
|
|
867
|
-
const fastFitDetail = fullCacheFitsFastMemory === false && fullKvBytesAtRequested !== undefined
|
|
868
|
-
? `projected weights + full KV ${((residentBytes + fullKvBytesAtRequested) / 1024 ** 3).toFixed(1)} GiB exceeds available fast memory ${(Math.max(0, fastBudgetBytes - committedOtherBytes) / 1024 ** 3).toFixed(1)} GiB`
|
|
869
|
-
: undefined;
|
|
870
|
-
log.warn([
|
|
871
|
-
`[llama-cpp] ${modelCatalogInfo?.id ?? 'model'}: the full SWA working set at the requested `,
|
|
872
|
-
`${effectiveNumCtx * slots}-token total context does not fit fast memory — keeping the full `,
|
|
873
|
-
'context and declining the --swa-full auto-default instead; the windowed KV cache ',
|
|
874
|
-
'is what actually fits (cross-request prefix reuse unavailable). ',
|
|
875
|
-
`Fit detail: ${fastFitDetail ?? admission.reason ?? 'over budget at the requested slot count'}`,
|
|
876
|
-
].join(''));
|
|
877
|
-
}
|
|
878
858
|
if (windowed) {
|
|
879
859
|
ladderPlan = planCtxTokensForMemory({
|
|
880
860
|
requestedPerTurnCtxTokens: effectiveNumCtx,
|
|
@@ -892,16 +872,23 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
892
872
|
fixedPerSlotBytes: windowed.fixedBytes,
|
|
893
873
|
};
|
|
894
874
|
}
|
|
895
|
-
else if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
875
|
+
else if (fullKvOverBudget &&
|
|
876
|
+
(explicitSwaFull === false
|
|
877
|
+
? (summary.slidingWindow ?? 0) > 0
|
|
878
|
+
: isGemmaModel({
|
|
879
|
+
architecture: modelCatalogInfo?.architecture,
|
|
880
|
+
modelId: defaultModelId ?? undefined,
|
|
881
|
+
}))) {
|
|
882
|
+
// A sliding-window model whose GGUF hides the layer layout,
|
|
883
|
+
// over budget on full math: that math over-states the real cache
|
|
884
|
+
// and there is no exact substitute — leave the launch alone
|
|
885
|
+
// rather than over-clamp.
|
|
899
886
|
ladderPlan = null;
|
|
900
887
|
ladderKvLinearization = null;
|
|
901
888
|
}
|
|
902
|
-
// else:
|
|
903
|
-
//
|
|
904
|
-
//
|
|
889
|
+
// else: no SWA layers at all, or a layout-less model that fits on
|
|
890
|
+
// full math — the full-attention plan stands (it IS the real
|
|
891
|
+
// allocation, or safely above it).
|
|
905
892
|
}
|
|
906
893
|
if (ladderPlan !== null && envNumCtx === undefined) {
|
|
907
894
|
const windowedNote = ladderPlan === admission ? '' : ' (windowed KV admission)';
|
|
@@ -964,19 +951,12 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
964
951
|
}
|
|
965
952
|
}
|
|
966
953
|
// Price the KV the engine will ACTUALLY allocate. Mirrors the
|
|
967
|
-
// `--swa-full`
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
const effectiveSwaFull = config.llamaCppSwaFull ??
|
|
974
|
-
manifestEngineConfig?.swaFull ??
|
|
975
|
-
(swaFullAutoFits !== false &&
|
|
976
|
-
isGemmaModel({
|
|
977
|
-
architecture: modelCatalogInfo?.architecture,
|
|
978
|
-
modelId: defaultModelId ?? undefined,
|
|
979
|
-
}));
|
|
954
|
+
// opt-in `--swa-full` in buildLlamaCppEngineArgs: an SWA model on the
|
|
955
|
+
// default windowed cache carries only its global layers per token
|
|
956
|
+
// plus a fixed window block per slot. Feeding the offload planner
|
|
957
|
+
// full-attention math for a windowed launch over-biased Gemma experts
|
|
958
|
+
// into system RAM for a cache that never materializes.
|
|
959
|
+
const effectiveSwaFull = config.llamaCppSwaFull ?? manifestEngineConfig?.swaFull ?? false;
|
|
980
960
|
const windowedKvPlan = effectiveSwaFull
|
|
981
961
|
? undefined
|
|
982
962
|
: estimateWindowedKvLinearization({
|
|
@@ -1177,34 +1157,77 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1177
1157
|
if (snapshot.expected)
|
|
1178
1158
|
return;
|
|
1179
1159
|
logFile.writeIncident(snapshot);
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
// Deliberately narrow: only SIGILL, and only before readiness.
|
|
1187
|
-
// A SIGSEGV during startup can be a corrupt model file, and any
|
|
1188
|
-
// crash after readiness is attributable to the work rather than
|
|
1189
|
-
// the binary — quarantining a backend over either would demote
|
|
1190
|
-
// users to slow engines for transient reasons.
|
|
1191
|
-
if (snapshot.signal !== 'SIGILL' || snapshot.reachedReady)
|
|
1160
|
+
if (snapshot.panicKind === 'host-out-of-memory') {
|
|
1161
|
+
noteHostOutOfMemory({
|
|
1162
|
+
model: modelCatalogInfo?.id ?? defaultModelId,
|
|
1163
|
+
whileStarting: !snapshot.reachedReady,
|
|
1164
|
+
});
|
|
1165
|
+
log.warn(`[llama-server] Linux's out-of-memory killer stopped the engine (${modelCatalogInfo?.id ?? 'model'}, incident=${snapshot.incidentId}, ${snapshot.reachedReady ? 'while serving' : 'while loading'})`);
|
|
1192
1166
|
return;
|
|
1193
|
-
|
|
1194
|
-
|
|
1167
|
+
}
|
|
1168
|
+
// A build that dies before it ever answers /health, without saying
|
|
1169
|
+
// why, may not be able to run here at all — and relaunching it on every
|
|
1170
|
+
// request just reproduces the crash while a working lower-tier build
|
|
1171
|
+
// sits in the next directory. Count it; a few in a row write it down so
|
|
1172
|
+
// the next launch routes around it. Which exits qualify (a silent
|
|
1173
|
+
// SIGILL; a GPU build's silent crash status on Windows or signal on
|
|
1174
|
+
// Linux; a CUDA build with no code for this GPU) is decided in
|
|
1175
|
+
// unrunnable-build.ts, how many in llama-quarantine.ts.
|
|
1176
|
+
const backend = binaryBackend;
|
|
1177
|
+
const unrunnable = unrunnableBuildExit(snapshot, backend);
|
|
1178
|
+
if (!backend || !unrunnable)
|
|
1195
1179
|
return;
|
|
1196
|
-
const entry =
|
|
1180
|
+
const { strikes, entry } = recordLlamaStartupCrash(home, {
|
|
1197
1181
|
backend,
|
|
1198
1182
|
binaryPath: binary,
|
|
1199
|
-
signal:
|
|
1200
|
-
reason:
|
|
1201
|
-
'this machine does not support, or faulted inside GPU library startup',
|
|
1183
|
+
signal: unrunnable.signal,
|
|
1184
|
+
reason: unrunnable.reason,
|
|
1202
1185
|
});
|
|
1203
|
-
if (entry) {
|
|
1204
|
-
|
|
1186
|
+
if (!entry) {
|
|
1187
|
+
if (strikes === 0)
|
|
1188
|
+
return;
|
|
1189
|
+
lastStartupCrash = {
|
|
1190
|
+
incidentId: snapshot.incidentId,
|
|
1191
|
+
verdict: { kind: 'strike', strikes, threshold: LLAMA_QUARANTINE_STRIKES },
|
|
1192
|
+
};
|
|
1193
|
+
log.warn(`[llama-server] the ${backend} build crashed before it was ready (${unrunnable.signal}, incident=${snapshot.incidentId}); ${strikes} of ${LLAMA_QUARANTINE_STRIKES} in a row switch it off`);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
log.warn(`[llama-server] quarantined the ${backend} build after ${strikes} crashes in a row (${unrunnable.signal}, incident=${snapshot.incidentId}); the next launch will fall back to another backend`);
|
|
1197
|
+
// Discovery reads the quarantine only at startup. Point the env at the
|
|
1198
|
+
// fallback now, so the pool's rebuild of this failed engine on the next
|
|
1199
|
+
// request launches it instead of the build just written down. A pinned
|
|
1200
|
+
// backend is a decision, and discovery never routes around one either;
|
|
1201
|
+
// an env that has already moved on belongs to someone else's fallback.
|
|
1202
|
+
const pinned = config.llamaCppBackendOverride !== undefined && config.llamaCppBackendOverride !== 'auto';
|
|
1203
|
+
const fallback = pinned || process.env.GEZEL_LLAMA_SERVER_BIN !== binary
|
|
1204
|
+
? null
|
|
1205
|
+
: adoptLlamaFallback(home, binary, backend);
|
|
1206
|
+
lastStartupCrash = {
|
|
1207
|
+
incidentId: snapshot.incidentId,
|
|
1208
|
+
verdict: { kind: 'quarantined', ...(fallback ? { switchedTo: fallback.backend } : {}) },
|
|
1209
|
+
};
|
|
1210
|
+
if (fallback) {
|
|
1211
|
+
log.warn(`[llama-server] the next launch uses the ${fallback.backend} build: ${fallback.path}`);
|
|
1205
1212
|
}
|
|
1206
1213
|
},
|
|
1207
|
-
|
|
1214
|
+
// Any start that reaches readiness proves the build runs here, so the
|
|
1215
|
+
// crashes before it were not in a row. It does not prove the build got
|
|
1216
|
+
// its GPU: a GPU backend that cannot load or initialize leaves the engine
|
|
1217
|
+
// on the CPU without crashing (gpu-startup-watch.ts).
|
|
1218
|
+
onReady: () => {
|
|
1219
|
+
if (binaryBackend)
|
|
1220
|
+
clearLlamaStartupStrikes(home, binaryBackend);
|
|
1221
|
+
const unused = gpuWatch?.verdict() ?? null;
|
|
1222
|
+
noteGpuStartup(unused);
|
|
1223
|
+
if (unused) {
|
|
1224
|
+
log.warn(`[llama-server] the ${unused.backend} build started without its GPU (${unused.reason}${unused.detail ? `: ${unused.detail}` : ''}); every token runs on the CPU`);
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
onRawLine: (line) => {
|
|
1228
|
+
gpuWatch?.observe(line);
|
|
1229
|
+
providerHolder.current?.onStdoutLine(line);
|
|
1230
|
+
},
|
|
1208
1231
|
// GPU-OOM recovery ladder: when a start dies of VRAM exhaustion and the
|
|
1209
1232
|
// planner's offload decision was in play, degrade it one step
|
|
1210
1233
|
// (partial split → all expert/FFN weights to RAM → engine-owned fit) and let the
|
|
@@ -1250,6 +1273,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1250
1273
|
return true;
|
|
1251
1274
|
},
|
|
1252
1275
|
resolveLaunch: async () => {
|
|
1276
|
+
gpuWatch = new GpuStartupWatch(binaryBackend);
|
|
1253
1277
|
const port = cachedPort ?? (await pickFreePort());
|
|
1254
1278
|
cachedPort = port;
|
|
1255
1279
|
// Prepend the binary's own directory to PATH so the OS finds the
|
|
@@ -1276,9 +1300,6 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1276
1300
|
// model output reaches `delta.content` for provider-side salvage.
|
|
1277
1301
|
reasoningFormat: process.env.GEZEL_LLAMA_REASONING_FORMAT?.trim() || undefined,
|
|
1278
1302
|
reasoningPreserve,
|
|
1279
|
-
architecture: modelCatalogInfo?.architecture,
|
|
1280
|
-
modelId: defaultModelId ?? undefined,
|
|
1281
|
-
swaFullAutoFits,
|
|
1282
1303
|
backend: llamaBuildMetadata?.backend ?? process.env.GEZEL_LLAMA_SERVER_BACKEND,
|
|
1283
1304
|
hostCpuCount: availableParallelism(),
|
|
1284
1305
|
});
|
|
@@ -1429,6 +1450,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1429
1450
|
supervisor,
|
|
1430
1451
|
logFile,
|
|
1431
1452
|
slotSavePath,
|
|
1453
|
+
startupCrashVerdict: (incidentId) => lastStartupCrash?.incidentId === incidentId ? lastStartupCrash.verdict : undefined,
|
|
1432
1454
|
// Lets `listModels()` enumerate every installed model (not just
|
|
1433
1455
|
// the resident one) so `/v1/models` + pickers see the full set
|
|
1434
1456
|
// the engine pool can serve.
|
|
@@ -149,6 +149,11 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
149
149
|
* may have been pruned in between — re-seeding keeps it warm).
|
|
150
150
|
*/
|
|
151
151
|
private readonly restoredSessions;
|
|
152
|
+
/**
|
|
153
|
+
* Sessions that will never be resumed (see `markEphemeral`): a save seeds
|
|
154
|
+
* their gezel prefix file when it is missing and writes nothing else.
|
|
155
|
+
*/
|
|
156
|
+
private readonly ephemeralSessions;
|
|
152
157
|
/**
|
|
153
158
|
* Latched off when this llama-server refuses slot save/restore (501
|
|
154
159
|
* "This feature is not supported by multimodal" — fires for mmproj-
|
|
@@ -190,6 +195,7 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
190
195
|
prepareForSend(sessionId: string, systemPrompt?: string, layers?: SystemPromptLayers): Promise<void>;
|
|
191
196
|
buildRequestExtras(sessionId: string): Record<string, unknown>;
|
|
192
197
|
evict(sessionIds: readonly string[]): Promise<void>;
|
|
198
|
+
markEphemeral(sessionId: string): void;
|
|
193
199
|
reportUsage(): Promise<readonly EngineCacheUsage[]>;
|
|
194
200
|
warm(_sessionId: string, _messages: readonly CacheWarmMessage[]): Promise<void>;
|
|
195
201
|
/**
|
|
@@ -227,6 +233,12 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
227
233
|
private pruneDiskCache;
|
|
228
234
|
private saveSlotForSession;
|
|
229
235
|
private saveSlotForSessionUnlocked;
|
|
236
|
+
/**
|
|
237
|
+
* A one-shot session's only lasting value is the prompt prefix it shares
|
|
238
|
+
* with its gezel's next session. Save the slot straight to that prefix file
|
|
239
|
+
* when none exists yet; otherwise write nothing. Returns whether it wrote.
|
|
240
|
+
*/
|
|
241
|
+
private seedPrefixFromEphemeralSlot;
|
|
230
242
|
/** Try restore-by-session, then restore-by-prefix. Returns true if
|
|
231
243
|
* any restore succeeded. */
|
|
232
244
|
private tryRestoreForSession;
|
|
@@ -149,6 +149,11 @@ export class LlamaCppCacheAdapter {
|
|
|
149
149
|
* may have been pruned in between — re-seeding keeps it warm).
|
|
150
150
|
*/
|
|
151
151
|
restoredSessions = new Set();
|
|
152
|
+
/**
|
|
153
|
+
* Sessions that will never be resumed (see `markEphemeral`): a save seeds
|
|
154
|
+
* their gezel prefix file when it is missing and writes nothing else.
|
|
155
|
+
*/
|
|
156
|
+
ephemeralSessions = new Set();
|
|
152
157
|
/**
|
|
153
158
|
* Latched off when this llama-server refuses slot save/restore (501
|
|
154
159
|
* "This feature is not supported by multimodal" — fires for mmproj-
|
|
@@ -263,8 +268,12 @@ export class LlamaCppCacheAdapter {
|
|
|
263
268
|
this.sessionPrefix.delete(id);
|
|
264
269
|
this.sessionLayerPrefixes.delete(id);
|
|
265
270
|
this.restoredSessions.delete(id);
|
|
271
|
+
this.ephemeralSessions.delete(id);
|
|
266
272
|
}
|
|
267
273
|
}
|
|
274
|
+
markEphemeral(sessionId) {
|
|
275
|
+
this.ephemeralSessions.add(sessionId);
|
|
276
|
+
}
|
|
268
277
|
async reportUsage() {
|
|
269
278
|
const generation = this.engineGeneration;
|
|
270
279
|
const baseUrl = await this.resolveBaseUrl().catch(() => null);
|
|
@@ -297,7 +306,8 @@ export class LlamaCppCacheAdapter {
|
|
|
297
306
|
if (!entry || typeof entry !== 'object')
|
|
298
307
|
continue;
|
|
299
308
|
const slot = entry.id;
|
|
300
|
-
|
|
309
|
+
// The slot's resident tokens (prompt plus generated); there is no `n_cache_tokens`.
|
|
310
|
+
const cachedTokens = entry.n_prompt_tokens;
|
|
301
311
|
if (typeof slot !== 'number' || typeof cachedTokens !== 'number' || cachedTokens <= 0) {
|
|
302
312
|
continue;
|
|
303
313
|
}
|
|
@@ -410,6 +420,7 @@ export class LlamaCppCacheAdapter {
|
|
|
410
420
|
log.debug(`slot ${chosen} recycled: evicting session ${victimSession.slice(0, 8)} for ${sessionId.slice(0, 8)} (disk save: ${saved ? 'ok' : 'MISSED'})`);
|
|
411
421
|
this.sessionToSlot.delete(victimSession);
|
|
412
422
|
this.slotToSession.delete(chosen);
|
|
423
|
+
this.ephemeralSessions.delete(victimSession);
|
|
413
424
|
}
|
|
414
425
|
}
|
|
415
426
|
// We now have a slot id `chosen` that's free of any current binding.
|
|
@@ -534,6 +545,9 @@ export class LlamaCppCacheAdapter {
|
|
|
534
545
|
const baseUrl = await this.resolveBaseUrl().catch(() => null);
|
|
535
546
|
if (!baseUrl || generation !== this.engineGeneration)
|
|
536
547
|
return false;
|
|
548
|
+
if (this.ephemeralSessions.has(sessionId)) {
|
|
549
|
+
return this.seedPrefixFromEphemeralSlot(baseUrl, slot, sessionId, generation);
|
|
550
|
+
}
|
|
537
551
|
const filename = sessFilename(sessionId);
|
|
538
552
|
const ok = await this.invokeSlotAction(baseUrl, slot, 'save', filename, generation);
|
|
539
553
|
if (!ok)
|
|
@@ -571,6 +585,27 @@ export class LlamaCppCacheAdapter {
|
|
|
571
585
|
}
|
|
572
586
|
return true;
|
|
573
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* A one-shot session's only lasting value is the prompt prefix it shares
|
|
590
|
+
* with its gezel's next session. Save the slot straight to that prefix file
|
|
591
|
+
* when none exists yet; otherwise write nothing. Returns whether it wrote.
|
|
592
|
+
*/
|
|
593
|
+
async seedPrefixFromEphemeralSlot(baseUrl, slot, sessionId, generation) {
|
|
594
|
+
if (!this.slotSavePath)
|
|
595
|
+
return false;
|
|
596
|
+
const prefixId = this.sessionPrefix.get(sessionId);
|
|
597
|
+
if (!prefixId || this.seededPrefixes.has(prefixId))
|
|
598
|
+
return false;
|
|
599
|
+
const name = prefixFilename(prefixId);
|
|
600
|
+
if (await stat(join(this.slotSavePath, name)).catch(() => null)) {
|
|
601
|
+
this.seededPrefixes.add(prefixId);
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
const ok = await this.invokeSlotAction(baseUrl, slot, 'save', name, generation);
|
|
605
|
+
if (ok)
|
|
606
|
+
this.seededPrefixes.add(prefixId);
|
|
607
|
+
return ok;
|
|
608
|
+
}
|
|
574
609
|
/** Try restore-by-session, then restore-by-prefix. Returns true if
|
|
575
610
|
* any restore succeeded. */
|
|
576
611
|
async tryRestoreForSession(slot, sessionId) {
|