@bendyline/gezel-inference 1.0.8 → 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/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 +3 -2
- 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/python/spec_decode.py +23 -2
- package/dist/providers/mlx/python/tool_args_json.py +59 -21
- package/dist/providers/mlx/venv.d.ts +19 -1
- package/dist/providers/mlx/venv.js +21 -2
- 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/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/package.json +4 -4
|
@@ -6,6 +6,10 @@ export { DS4_FULL_RESIDENCY_HEADROOM_BYTES };
|
|
|
6
6
|
* Conservative floor for the full-residency working set. Exclusivity is an
|
|
7
7
|
* explicit device lease at process startup, not a consequence of this number:
|
|
8
8
|
* the automatic model budget can exceed 96 GiB on larger machines.
|
|
9
|
+
*
|
|
10
|
+
* Only for a launch nobody measured: an entry with `fullResidencyMemory` is
|
|
11
|
+
* reserved at that working set ({@link planDs4MeasuredLaunch}). Flooring a
|
|
12
|
+
* 45 GiB Qwen3.8 launch here put it over a 96 GiB Mac's 80 GiB budget.
|
|
9
13
|
*/
|
|
10
14
|
export const DS4_FULL_RESIDENCY_RESERVATION_BYTES = 96 * GB;
|
|
11
15
|
/**
|
|
@@ -80,20 +84,97 @@ export function shouldUseDs4SsdStreaming(opts = {}) {
|
|
|
80
84
|
* the old RAM-tier heuristic which incorrectly gave both models 64 GiB on a
|
|
81
85
|
* 128 GiB Mac. Manual values are still accepted, but clamped so fixed model
|
|
82
86
|
* state + the cache cannot consume the runtime/OS reserve.
|
|
87
|
+
*
|
|
88
|
+
* The 24 GiB headroom guards a footprint guessed as `residentBytes −
|
|
89
|
+
* cacheExpertsBytes`, which omits graph and staging memory ds4 also holds.
|
|
90
|
+
* An entry with a measured streamed footprint has no such gap, so its cache
|
|
91
|
+
* may use whatever this device's local-model budget leaves beside it. A fixed
|
|
92
|
+
* 24 GiB refused Qwen3.8 on 32 GiB Macs whose budget held it with room to spare.
|
|
83
93
|
*/
|
|
84
94
|
export function planDs4ExpertCache(opts) {
|
|
85
95
|
const totalRamBytes = opts.totalRamBytes ?? totalMemoryBytes();
|
|
86
96
|
const catalogCacheBytes = opts.catalogCacheBytes ?? 0;
|
|
87
|
-
const fixedResidentBytes = Math.max(0, (opts.catalogResidentBytes ?? 0) - catalogCacheBytes);
|
|
88
97
|
const fallbackGb = totalRamBytes >= 120 * GB ? 64 : totalRamBytes >= 88 * GB ? 48 : 32;
|
|
89
98
|
const requestedGb = opts.configuredGb ?? (catalogCacheBytes > 0 ? catalogCacheBytes / GB : fallbackGb);
|
|
90
|
-
const
|
|
99
|
+
const modelBudget = opts.measuredFixedBytes !== undefined && opts.budgetBytes !== undefined;
|
|
100
|
+
const safeCacheBytes = modelBudget
|
|
101
|
+
? opts.budgetBytes - opts.measuredFixedBytes
|
|
102
|
+
: totalRamBytes -
|
|
103
|
+
Math.max(0, (opts.catalogResidentBytes ?? 0) - catalogCacheBytes) -
|
|
104
|
+
DS4_FULL_RESIDENCY_HEADROOM_BYTES;
|
|
91
105
|
const safeCacheGb = Math.floor(safeCacheBytes / GB);
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
const minCacheGb = Math.max(1, Math.ceil((opts.minCacheBytes ?? 0) / GB));
|
|
107
|
+
const limit = modelBudget ? 'model-budget' : 'headroom';
|
|
108
|
+
if (safeCacheGb < minCacheGb) {
|
|
109
|
+
return { cacheGb: minCacheGb, requestedGb, clamped: true, safe: false, limit };
|
|
110
|
+
}
|
|
111
|
+
const cacheGb = Math.max(minCacheGb, Math.min(requestedGb, safeCacheGb));
|
|
112
|
+
return { cacheGb, requestedGb, clamped: cacheGb !== requestedGb, safe: true, limit };
|
|
113
|
+
}
|
|
114
|
+
/** Evaluate a measured line at a window, never below its context-free part. */
|
|
115
|
+
export function ds4MeasuredBytesAt(line, ctxTokens) {
|
|
116
|
+
const contextFree = Math.max(0, line.bytes - line.bytesPerToken * line.ctxTokens);
|
|
117
|
+
return Math.round(contextFree + line.bytesPerToken * Math.max(0, ctxTokens));
|
|
118
|
+
}
|
|
119
|
+
function fullResidencyLine(source) {
|
|
120
|
+
const m = source?.fullResidencyMemory;
|
|
121
|
+
return m
|
|
122
|
+
? { bytes: m.workingSetBytes, ctxTokens: m.ctxTokens, bytesPerToken: m.bytesPerToken }
|
|
123
|
+
: undefined;
|
|
124
|
+
}
|
|
125
|
+
function streamingLine(source) {
|
|
126
|
+
const m = source?.streamingMemory;
|
|
127
|
+
return m
|
|
128
|
+
? { bytes: m.fixedBytes, ctxTokens: m.ctxTokens, bytesPerToken: m.bytesPerToken }
|
|
129
|
+
: undefined;
|
|
130
|
+
}
|
|
131
|
+
/** Measured streamed footprint without the expert cache, at a window; undefined when unmeasured. */
|
|
132
|
+
export function ds4StreamingFixedBytes(source, ctxTokens) {
|
|
133
|
+
const line = streamingLine(source);
|
|
134
|
+
return line ? ds4MeasuredBytesAt(line, ctxTokens) : undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Price a launch from the entry's measurement for the chosen mode. Undefined
|
|
138
|
+
* when that mode is unmeasured (or a streamed launch has no budget to size
|
|
139
|
+
* against), so callers keep their catalog-`residentBytes` path, including the
|
|
140
|
+
* 96 GiB full-residency floor that stands in for a missing measurement.
|
|
141
|
+
*/
|
|
142
|
+
export function planDs4MeasuredLaunch(opts) {
|
|
143
|
+
const sidecarBytes = opts.sidecarBytes ?? 0;
|
|
144
|
+
if (!opts.ssdStreaming) {
|
|
145
|
+
const line = fullResidencyLine(opts.source);
|
|
146
|
+
if (!line)
|
|
147
|
+
return undefined;
|
|
148
|
+
const contextFreeBytes = ds4MeasuredBytesAt(line, 0) + sidecarBytes;
|
|
149
|
+
return {
|
|
150
|
+
workingSetBytes: contextFreeBytes + line.bytesPerToken * Math.max(0, opts.numCtx),
|
|
151
|
+
contextFreeBytes,
|
|
152
|
+
bytesPerToken: line.bytesPerToken,
|
|
153
|
+
};
|
|
94
154
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
155
|
+
const line = streamingLine(opts.source);
|
|
156
|
+
if (!line || opts.budgetBytes === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
const fixedBytes = ds4MeasuredBytesAt(line, opts.numCtx) + sidecarBytes;
|
|
159
|
+
const cache = planDs4ExpertCache({
|
|
160
|
+
...(opts.configuredCacheGb !== undefined ? { configuredGb: opts.configuredCacheGb } : {}),
|
|
161
|
+
...(opts.source?.cacheExpertsBytes !== undefined
|
|
162
|
+
? { catalogCacheBytes: opts.source.cacheExpertsBytes }
|
|
163
|
+
: {}),
|
|
164
|
+
totalRamBytes: opts.totalRamBytes,
|
|
165
|
+
measuredFixedBytes: fixedBytes,
|
|
166
|
+
budgetBytes: opts.budgetBytes,
|
|
167
|
+
...(opts.source?.streamingMemory?.minCacheBytes !== undefined
|
|
168
|
+
? { minCacheBytes: opts.source.streamingMemory.minCacheBytes }
|
|
169
|
+
: {}),
|
|
170
|
+
});
|
|
171
|
+
const cacheBytes = cache.cacheGb * GB;
|
|
172
|
+
return {
|
|
173
|
+
workingSetBytes: fixedBytes + cacheBytes,
|
|
174
|
+
contextFreeBytes: ds4MeasuredBytesAt(line, 0) + sidecarBytes + cacheBytes,
|
|
175
|
+
bytesPerToken: line.bytesPerToken,
|
|
176
|
+
cache,
|
|
177
|
+
};
|
|
97
178
|
}
|
|
98
179
|
/** Effective capacity-broker reservation for the chosen DS4 residency mode. */
|
|
99
180
|
export function ds4ResidentBytesForMode(catalogResidentBytes, ssdStreaming, ssdStreamingSupported = true) {
|
|
@@ -285,8 +285,9 @@ export class StableDiffusionCppProvider {
|
|
|
285
285
|
this.launchState.modelId = modelId;
|
|
286
286
|
await this.supervisor.stop();
|
|
287
287
|
}
|
|
288
|
-
// A render the caller abandons keeps running inside sd-server,
|
|
289
|
-
// cannot interrupt one
|
|
288
|
+
// A render the caller abandons keeps running inside sd-server, whose
|
|
289
|
+
// `/sdapi` endpoints cannot interrupt one they have started, so
|
|
290
|
+
// cancelling stops the engine.
|
|
290
291
|
const request = new AbortController();
|
|
291
292
|
const stopRender = () => {
|
|
292
293
|
request.abort();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whether llama-server is getting the hardware this machine has, as the
|
|
3
3
|
* engine process sees it: the CUDA build discovery passed over for the
|
|
4
|
-
* driver (`GEZEL_LLAMA_CUDA_BLOCKER`),
|
|
4
|
+
* driver (`GEZEL_LLAMA_CUDA_BLOCKER`), the ROCm build it passed over for this
|
|
5
|
+
* machine's AMD GPUs (`GEZEL_LLAMA_ROCM_BLOCKER`), a GPU build that started without its
|
|
5
6
|
* GPU (gpu-startup-watch.ts), and Linux's out-of-memory killer stopping the
|
|
6
7
|
* engine. Each is invisible otherwise — the engine keeps answering, slower,
|
|
7
8
|
* or a chat fails with a crash that reads like any other.
|
|
@@ -24,7 +25,7 @@ export declare function noteHostOutOfMemory(input: {
|
|
|
24
25
|
model?: string | undefined;
|
|
25
26
|
whileStarting: boolean;
|
|
26
27
|
}, now?: Date): void;
|
|
27
|
-
declare const BACKENDS: readonly ["cuda", "vulkan", "metal", "cpu"];
|
|
28
|
+
declare const BACKENDS: readonly ["cuda", "rocm", "vulkan", "metal", "cpu"];
|
|
28
29
|
/**
|
|
29
30
|
* The backend this process's on-device llama-server runs, for catalog
|
|
30
31
|
* backend exclusions: the build discovery resolved, else what the probe
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whether llama-server is getting the hardware this machine has, as the
|
|
3
3
|
* engine process sees it: the CUDA build discovery passed over for the
|
|
4
|
-
* driver (`GEZEL_LLAMA_CUDA_BLOCKER`),
|
|
4
|
+
* driver (`GEZEL_LLAMA_CUDA_BLOCKER`), the ROCm build it passed over for this
|
|
5
|
+
* machine's AMD GPUs (`GEZEL_LLAMA_ROCM_BLOCKER`), a GPU build that started without its
|
|
5
6
|
* GPU (gpu-startup-watch.ts), and Linux's out-of-memory killer stopping the
|
|
6
7
|
* engine. Each is invisible otherwise — the engine keeps answering, slower,
|
|
7
8
|
* or a chat fails with a crash that reads like any other.
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
* Process-wide like the backend env it sits beside: every llama provider in
|
|
10
11
|
* this process launches the same build.
|
|
11
12
|
*/
|
|
12
|
-
import { missingMachineRuntime, readLlamaCudaBlocker, vcRedistributableUrl, } from '@bendyline/gezel/native';
|
|
13
|
+
import { missingMachineRuntime, readLlamaCudaBlocker, readRocmBlocker, vcRedistributableUrl, } from '@bendyline/gezel/native';
|
|
13
14
|
/**
|
|
14
15
|
* Reading an engine's import tables takes tens of milliseconds of synchronous
|
|
15
16
|
* file reads, and the answer only changes when someone installs the runtime.
|
|
@@ -61,7 +62,7 @@ export function noteHostOutOfMemory(input, now = new Date()) {
|
|
|
61
62
|
whileStarting: input.whileStarting,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
|
-
const BACKENDS = ['cuda', 'vulkan', 'metal', 'cpu'];
|
|
65
|
+
const BACKENDS = ['cuda', 'rocm', 'vulkan', 'metal', 'cpu'];
|
|
65
66
|
/**
|
|
66
67
|
* The backend this process's on-device llama-server runs, for catalog
|
|
67
68
|
* backend exclusions: the build discovery resolved, else what the probe
|
|
@@ -85,6 +86,7 @@ export function llamaBackendHealthView() {
|
|
|
85
86
|
const runningRaw = process.env.GEZEL_LLAMA_SERVER_BACKEND;
|
|
86
87
|
const running = BACKENDS.find((backend) => backend === runningRaw);
|
|
87
88
|
const cudaBlocker = readLlamaCudaBlocker();
|
|
89
|
+
const rocmBlocker = readRocmBlocker();
|
|
88
90
|
// A verdict about a build the process has since moved off (a quarantine
|
|
89
91
|
// fallback, a lifted quarantine) no longer describes what runs.
|
|
90
92
|
const unused = gpuUnused && gpuUnused.backend === running ? gpuUnused : null;
|
|
@@ -92,6 +94,7 @@ export function llamaBackendHealthView() {
|
|
|
92
94
|
return {
|
|
93
95
|
...(running ? { running } : {}),
|
|
94
96
|
...(cudaBlocker ? { cudaBlocker } : {}),
|
|
97
|
+
...(rocmBlocker ? { rocmBlocker } : {}),
|
|
95
98
|
...(unused ? { gpuUnused: unused } : {}),
|
|
96
99
|
...(missingRuntime ? { missingRuntime } : {}),
|
|
97
100
|
...(outOfMemory ? { outOfMemory } : {}),
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* next engine launch instead of the next app start.
|
|
12
12
|
*/
|
|
13
13
|
import { LLAMA_QUARANTINE_STRIKES, clearLlamaQuarantine, createLlamaBuildCheck, isBinaryQuarantined, llamaVariantBinaryPath, publishLlamaCudaBlocker, readLlamaQuarantine, resolveLlamaFallbackBinary, resolveLlamaVariantBinary, } from '@bendyline/gezel/native';
|
|
14
|
-
const BACKENDS = ['cuda', 'vulkan', 'metal', 'cpu'];
|
|
14
|
+
const BACKENDS = ['cuda', 'rocm', 'vulkan', 'metal', 'cpu'];
|
|
15
15
|
export function isLlamaBackend(value) {
|
|
16
16
|
return BACKENDS.some((backend) => backend === value);
|
|
17
17
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { delimiter, dirname, join } from 'node:path';
|
|
2
|
-
import { DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS, createLogger, estimateLlamaCppResidentBytes, llamaCppBackendExclusion, llamaCppBackendLabel, } from '@bendyline/gezel';
|
|
3
|
-
import { LLAMA_QUARANTINE_STRIKES, clearLlamaStartupStrikes, recordLlamaStartupCrash, unrunnableBuildExit, } from '@bendyline/gezel/native';
|
|
1
|
+
import { basename, delimiter, dirname, join } from 'node:path';
|
|
2
|
+
import { DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS, createLogger, estimateLlamaCppResidentBytes, llamaCppBackendExclusion, llamaCppBackendLabel, llamaCppDraftResidentBytes, llamaCppSpecNeedsDraftModel, } from '@bendyline/gezel';
|
|
3
|
+
import { LLAMA_QUARANTINE_STRIKES, clearLlamaStartupStrikes, recordLlamaStartupCrash, rocmLaunchEnv, unrunnableBuildExit, } from '@bendyline/gezel/native';
|
|
4
4
|
import { gezelPaths } from '@bendyline/gezel/paths';
|
|
5
5
|
import { mmprojBudgetBytes, nativeVisionEnabledFor } from '../../chat/vision-capability.js';
|
|
6
6
|
import { effectiveEngineRelease, isEnginePinned } from '../../engines/native-manifest.js';
|
|
7
7
|
import { engineInstallPhase } from '../../engines/registry.js';
|
|
8
|
+
import { llamaDownloadVariant } from '../../engines/resolver.js';
|
|
8
9
|
import { availableCpuCount, containerCpuLimit } from '../../system/host-memory.js';
|
|
9
10
|
import { localDeviceSettingsLabel, noModelYetMessage, notReadyError, } from '../active-install-message.js';
|
|
10
11
|
import { resolveCatalogLlamaCppEngineConfig, resolveCatalogReasoningBudget, } from '../catalog-model-config.js';
|
|
@@ -27,7 +28,7 @@ import { isGemmaModel, planLlamaCppKv, resolveLlamaCppKvCacheType, } from './kv-
|
|
|
27
28
|
import { degradeDenseFfnOffloadDecision, degradeMoeOffloadDecision, estimateExactPerSlotKvBytesF16, estimateKvReserveBytes, estimateWindowedKvLinearization, planDenseFfnOffload, planMoeOffload, shedSlotsForGpuResidency, } from './offload-planner.js';
|
|
28
29
|
import { LlamaCppProvider } from './provider.js';
|
|
29
30
|
import { reasoningLaunchOverridesFromEnv } from './reasoning-launch.js';
|
|
30
|
-
import { resolveSpecDraft } from './spec-draft.js';
|
|
31
|
+
import { brokenDrafters, drafterCostsContext, drafterCostsGpuResidency, llamaCppSidecarLoads, resolveSpecDraft, } from './spec-draft.js';
|
|
31
32
|
const log = createLogger('chat');
|
|
32
33
|
function activeLlamaInstall(models) {
|
|
33
34
|
return models?.getActiveInstalls().find((i) => i.phase !== undefined) ?? null;
|
|
@@ -50,7 +51,7 @@ export function ensureLlamaEngineStatus(registry, config) {
|
|
|
50
51
|
const override = config.llamaCppBackendOverride;
|
|
51
52
|
const backend = override && override !== 'auto'
|
|
52
53
|
? override
|
|
53
|
-
: process.env.GEZEL_LLAMA_DETECTED_BACKEND || undefined;
|
|
54
|
+
: llamaDownloadVariant(process.env.GEZEL_LLAMA_DETECTED_BACKEND || undefined);
|
|
54
55
|
const { snapshot } = registry.ensure('llama-server', backend);
|
|
55
56
|
if (snapshot.error) {
|
|
56
57
|
return { detail: `On-device engine couldn't be downloaded: ${snapshot.error}`, failed: true };
|
|
@@ -283,10 +284,13 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
283
284
|
}
|
|
284
285
|
// Install refuses an excluded backend; this catches a model installed
|
|
285
286
|
// before the exclusion was authored, or copied in by hand.
|
|
287
|
+
let catalogDraftResidentBytes;
|
|
286
288
|
if (modelCatalogInfo && opts.catalog) {
|
|
287
289
|
const engineBackend = binaryBackend ?? currentLlamaCppBackend();
|
|
288
290
|
const detail = await opts.catalog.get('chat-model', modelCatalogInfo.id).catch(() => null);
|
|
289
|
-
const
|
|
291
|
+
const catalogSource = detail?.manifest.kind === 'chat-model' ? detail.manifest.llamaCpp : undefined;
|
|
292
|
+
catalogDraftResidentBytes = llamaCppDraftResidentBytes(catalogSource?.draftModel, modelCatalogInfo.draftModelPath);
|
|
293
|
+
const excluded = llamaCppBackendExclusion(catalogSource, engineBackend);
|
|
290
294
|
if (excluded && engineBackend) {
|
|
291
295
|
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
296
|
err.isActionable = true;
|
|
@@ -338,6 +342,41 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
338
342
|
const manifestEngineConfig = opts.catalog
|
|
339
343
|
? await resolveCatalogLlamaCppEngineConfig(opts.catalog, opts.modelOverride?.modelId ?? defaultModelId)
|
|
340
344
|
: undefined;
|
|
345
|
+
// Speculative-decoding resolution — the catalog's per-backend overlay, and
|
|
346
|
+
// whether the drafter it names is on disk. Settled here, above the memory
|
|
347
|
+
// planning, for the same reason as vision: the launch and the budget must
|
|
348
|
+
// agree on whether a drafter is resident. See providers/llama-cpp/spec-draft.ts.
|
|
349
|
+
const llamaBuildMetadata = binary ? await readLlamaCppBuildMetadata(binary) : null;
|
|
350
|
+
const llamaBackend = llamaBuildMetadata?.backend ?? process.env.GEZEL_LLAMA_SERVER_BACKEND;
|
|
351
|
+
const resolveSpec = (installedDraftModelPath) => resolveSpecDraft({
|
|
352
|
+
perModel: manifestEngineConfig,
|
|
353
|
+
backend: llamaBackend,
|
|
354
|
+
configSpecType: config.llamaCppSpecType,
|
|
355
|
+
configDraftModelPath: config.llamaCppDraftModelPath,
|
|
356
|
+
installedDraftModelPath,
|
|
357
|
+
resolveDraftPath: (id) => opts.llamaCppModels ? opts.llamaCppModels.resolveModelPath(id) : Promise.resolve(null),
|
|
358
|
+
});
|
|
359
|
+
const brokenDrafterKey = `${binary ?? ''}|${modelCatalogInfo?.draftModelPath ?? ''}`;
|
|
360
|
+
let drafterInUse = brokenDrafters.has(brokenDrafterKey)
|
|
361
|
+
? undefined
|
|
362
|
+
: modelCatalogInfo?.draftModelPath;
|
|
363
|
+
let specDraft = await resolveSpec(drafterInUse);
|
|
364
|
+
if (specDraft.log)
|
|
365
|
+
log[specDraft.log.level](specDraft.log.message);
|
|
366
|
+
let resolvedManifestEngineConfig = specDraft.perModel;
|
|
367
|
+
const specTypeNow = () => config.llamaCppSpecType ?? resolvedManifestEngineConfig?.spec?.type;
|
|
368
|
+
const draftBudgetBytes = () => (drafterInUse ? (modelCatalogInfo?.draftModelSizeBytes ?? 0) : 0);
|
|
369
|
+
// A drafter the catalog chose, which this launch can run without. One someone
|
|
370
|
+
// forced stays theirs to answer for.
|
|
371
|
+
const catalogDrafterLoaded = () => !!drafterInUse &&
|
|
372
|
+
!config.llamaCppSpecType &&
|
|
373
|
+
!config.llamaCppDraftModelPath &&
|
|
374
|
+
llamaCppSpecNeedsDraftModel(resolvedManifestEngineConfig?.spec?.type);
|
|
375
|
+
const leaveDrafterOut = async () => {
|
|
376
|
+
drafterInUse = undefined;
|
|
377
|
+
specDraft = await resolveSpec(undefined);
|
|
378
|
+
resolvedManifestEngineConfig = specDraft.perModel;
|
|
379
|
+
};
|
|
341
380
|
const PREFERRED_CTX_DEFAULT = 65_536;
|
|
342
381
|
// Explicit numeric values win: env, then the per-model override, then the
|
|
343
382
|
// machine-wide config. Otherwise Adaptive uses the per-model manifest
|
|
@@ -413,6 +452,13 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
413
452
|
headerSummary = null;
|
|
414
453
|
}
|
|
415
454
|
}
|
|
455
|
+
if (!llamaCppSidecarLoads({
|
|
456
|
+
specType: specTypeNow(),
|
|
457
|
+
targetHasMtp: (headerSummary?.nextnPredictLayers ?? 0) > 0,
|
|
458
|
+
perBackend: !!manifestEngineConfig?.spec?.byBackend,
|
|
459
|
+
})) {
|
|
460
|
+
drafterInUse = undefined;
|
|
461
|
+
}
|
|
416
462
|
const exactPerSlotKvF16 = headerSummary
|
|
417
463
|
? estimateExactPerSlotKvBytesF16({
|
|
418
464
|
blockCount: headerSummary.blockCount,
|
|
@@ -462,8 +508,10 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
462
508
|
// contract in planLlamaCppKv. Priced from the cached exact GGUF walk above,
|
|
463
509
|
// including any v0.4 lazy-tensor deduction.
|
|
464
510
|
const PLAN_REFERENCE_CTX = 4096;
|
|
465
|
-
const planResidentBytes = estimateLlamaCppResidentBytes(planWeightsBytes, {
|
|
511
|
+
const planResidentBytes = () => estimateLlamaCppResidentBytes(planWeightsBytes, {
|
|
466
512
|
mmprojBytes: visionBudgetBytes,
|
|
513
|
+
draftBytes: draftBudgetBytes(),
|
|
514
|
+
draftResidentBytes: catalogDraftResidentBytes,
|
|
467
515
|
});
|
|
468
516
|
const planCapacity = hostCapacity;
|
|
469
517
|
const planKvBytesPerToken = (kv) => {
|
|
@@ -506,7 +554,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
506
554
|
slots: slotCount,
|
|
507
555
|
minimumPerTurnCtxTokens: ctxTokens,
|
|
508
556
|
kvBytesPerToken: planKvBytesPerToken(kv),
|
|
509
|
-
weightsResidentBytes: planResidentBytes,
|
|
557
|
+
weightsResidentBytes: planResidentBytes(),
|
|
510
558
|
budgetBytes: brokerSnap?.enforced ? brokerSnap.budgetBytes : planCapacity.budgetBytes,
|
|
511
559
|
committedOtherBytes,
|
|
512
560
|
freeSystemRamBytes: availableSystemRamBytes(),
|
|
@@ -514,7 +562,51 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
514
562
|
});
|
|
515
563
|
return probe.minimumSatisfied && probe.slots >= slotCount;
|
|
516
564
|
};
|
|
517
|
-
|
|
565
|
+
// One slot's KV at `ctxTokens`, priced the way the launch reservation
|
|
566
|
+
// prices it below: the windowed cache unless `--swa-full` is pinned.
|
|
567
|
+
const planSlotKvReserveBytes = (kv, ctxTokens) => {
|
|
568
|
+
if (!headerSummary)
|
|
569
|
+
return planKvBytesPerToken(kv) * ctxTokens;
|
|
570
|
+
const geometry = {
|
|
571
|
+
blockCount: headerSummary.blockCount,
|
|
572
|
+
embeddingLength: headerSummary.embeddingLength,
|
|
573
|
+
headCount: headerSummary.headCount,
|
|
574
|
+
headCountKv: headerSummary.headCountKv,
|
|
575
|
+
headCountKvPerLayer: headerSummary.headCountKvPerLayer,
|
|
576
|
+
slidingWindowPattern: headerSummary.slidingWindowPattern,
|
|
577
|
+
sharedKvLayers: headerSummary.sharedKvLayers,
|
|
578
|
+
loopCount: headerSummary.loopCount,
|
|
579
|
+
keyLength: headerSummary.keyLength,
|
|
580
|
+
valueLength: headerSummary.valueLength,
|
|
581
|
+
keyLengthSwa: headerSummary.keyLengthSwa,
|
|
582
|
+
valueLengthSwa: headerSummary.valueLengthSwa,
|
|
583
|
+
fullAttentionInterval: headerSummary.fullAttentionInterval,
|
|
584
|
+
ssmInnerSize: headerSummary.ssmInnerSize,
|
|
585
|
+
ssmStateSize: headerSummary.ssmStateSize,
|
|
586
|
+
ssmConvKernel: headerSummary.ssmConvKernel,
|
|
587
|
+
kvCacheType: kv,
|
|
588
|
+
};
|
|
589
|
+
const windowed = (config.llamaCppSwaFull ?? manifestEngineConfig?.swaFull ?? false)
|
|
590
|
+
? undefined
|
|
591
|
+
: estimateWindowedKvLinearization({
|
|
592
|
+
...geometry,
|
|
593
|
+
slidingWindow: headerSummary.slidingWindow,
|
|
594
|
+
});
|
|
595
|
+
return windowed
|
|
596
|
+
? windowed.fixedBytes + windowed.bytesPerToken * ctxTokens
|
|
597
|
+
: (estimateKvReserveBytes({ ...geometry, ctxTokens }) ?? planKvBytesPerToken(kv) * ctxTokens);
|
|
598
|
+
};
|
|
599
|
+
// What a discrete card leaves this launch now: free graphics memory as
|
|
600
|
+
// device admission measures it, within the card's usable share. Undefined
|
|
601
|
+
// off discrete cards, where there is no separate pool to run out of.
|
|
602
|
+
const planGpuPoolBytes = async () => {
|
|
603
|
+
const kind = brokerSnap?.enforced ? brokerSnap.pools.kind : hostCapacity.kind;
|
|
604
|
+
if (kind !== 'discrete-gpu')
|
|
605
|
+
return undefined;
|
|
606
|
+
const freeGpu = await measureAvailableGpuBytes().catch(() => undefined);
|
|
607
|
+
return Math.max(0, Math.min(budgetBytes - committedOtherBytes, freeGpu ?? Number.POSITIVE_INFINITY));
|
|
608
|
+
};
|
|
609
|
+
const planKv = () => planLlamaCppKv({
|
|
518
610
|
architecture: modelCatalogInfo?.architecture,
|
|
519
611
|
modelId: defaultModelId ?? undefined,
|
|
520
612
|
override: config.llamaCppKvCacheType,
|
|
@@ -527,6 +619,42 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
527
619
|
fitsAt: planFitsAt,
|
|
528
620
|
maxSlots: defaultLocalEngineSlots(budgetBytes),
|
|
529
621
|
});
|
|
622
|
+
let kvPlan = planKv();
|
|
623
|
+
// A drafter buys speed; context is what a session runs out of. When a
|
|
624
|
+
// drafter the catalog chose would cap the target window or cost its KV
|
|
625
|
+
// precision, it goes and the model decodes the way it does without one.
|
|
626
|
+
// Growth above the target, sized later, simply has less room. On a
|
|
627
|
+
// discrete card it must also leave the model wholly on the GPU in the
|
|
628
|
+
// memory free now, which is what device admission charges the launch.
|
|
629
|
+
if (draftBudgetBytes() > 0 && !config.llamaCppSpecType && !config.llamaCppDraftModelPath) {
|
|
630
|
+
const targetCtx = kvPlan.ctxCapTokens ?? effectiveNumCtx;
|
|
631
|
+
const fitsWithDrafter = planFitsAt(kvPlan.kvCacheType, targetCtx, 1);
|
|
632
|
+
const drafter = drafterInUse;
|
|
633
|
+
drafterInUse = undefined;
|
|
634
|
+
const withoutDrafter = planKv();
|
|
635
|
+
drafterInUse = drafter;
|
|
636
|
+
let drafterCost;
|
|
637
|
+
if (drafterCostsContext(kvPlan, withoutDrafter, fitsWithDrafter)) {
|
|
638
|
+
drafterCost = `beside it the ${effectiveNumCtx}-token window would not keep its size and KV precision`;
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
const gpuPoolBytes = await planGpuPoolBytes();
|
|
642
|
+
const withDrafterBytes = planResidentBytes() + planSlotKvReserveBytes(kvPlan.kvCacheType, targetCtx);
|
|
643
|
+
if (drafterCostsGpuResidency(withDrafterBytes, gpuPoolBytes)) {
|
|
644
|
+
drafterCost = `beside it the model needs ${(withDrafterBytes / 1024 ** 3).toFixed(1)} GiB of graphics memory and ${((gpuPoolBytes ?? 0) / 1024 ** 3).toFixed(1)} GiB is available`;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (drafterCost) {
|
|
648
|
+
log.info(`[llama-cpp] ${modelCatalogInfo?.id ?? defaultModelId ?? 'model'}: leaving the speculative drafter out; ${drafterCost}`);
|
|
649
|
+
await leaveDrafterOut();
|
|
650
|
+
kvPlan = withoutDrafter;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// What a loaded drafter holds on the GPU, as the resident estimate prices it.
|
|
654
|
+
const drafterGpuBytes = () => estimateLlamaCppResidentBytes(0, {
|
|
655
|
+
draftBytes: draftBudgetBytes(),
|
|
656
|
+
draftResidentBytes: catalogDraftResidentBytes,
|
|
657
|
+
}) - estimateLlamaCppResidentBytes(0);
|
|
530
658
|
kvCacheType = kvPlan.kvCacheType;
|
|
531
659
|
if (kvPlan.upgraded) {
|
|
532
660
|
log.info(`[llama-cpp] ${modelCatalogInfo?.id ?? defaultModelId ?? 'model'}: trading f16 KV for q8_0 to fit a second engine slot (single-slot SWA session alternation re-prefills wholesale; KV A/B 2026-08-03 showed no measurable q8_0 fidelity cost)`);
|
|
@@ -552,7 +680,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
552
680
|
// concurrent requests correctly with zero engine errors, so the failure this
|
|
553
681
|
// clamp guards against looks stale — but one probe is not grounds for
|
|
554
682
|
// deleting a safety measure, so it stays for anyone who opted in explicitly.
|
|
555
|
-
const selectedSpecType =
|
|
683
|
+
const selectedSpecType = specTypeNow();
|
|
556
684
|
if (selectedSpecType === 'draft-mtp' && slots > 1) {
|
|
557
685
|
log.info(`[llama-cpp] limiting ${modelCatalogInfo?.id ?? 'MTP model'} to one slot because draft-mtp is not reliable with --parallel > 1 in the bundled engine`);
|
|
558
686
|
slots = 1;
|
|
@@ -614,18 +742,6 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
614
742
|
: catalogReasoningBudgetTokens !== undefined
|
|
615
743
|
? 'catalog'
|
|
616
744
|
: 'unbounded';
|
|
617
|
-
// Speculative-decoding draft resolution — a manifest names its draft by
|
|
618
|
-
// catalog id; resolve it to the installed GGUF path (or disable) before the
|
|
619
|
-
// argv is built. See providers/llama-cpp/spec-draft.ts for the full rules.
|
|
620
|
-
const specDraft = await resolveSpecDraft({
|
|
621
|
-
perModel: manifestEngineConfig,
|
|
622
|
-
configSpecType: config.llamaCppSpecType,
|
|
623
|
-
configDraftModelPath: config.llamaCppDraftModelPath,
|
|
624
|
-
resolveDraftPath: (id) => opts.llamaCppModels ? opts.llamaCppModels.resolveModelPath(id) : Promise.resolve(null),
|
|
625
|
-
});
|
|
626
|
-
const resolvedManifestEngineConfig = specDraft.perModel;
|
|
627
|
-
if (specDraft.log)
|
|
628
|
-
log[specDraft.log.level](specDraft.log.message);
|
|
629
745
|
// Phase v4 — hardware-aware structured-weight offload. Probe device VRAM (cached to
|
|
630
746
|
// `engines/llama-cpp/devices.json` via `--list-devices`) and read the
|
|
631
747
|
// model's MoE metadata from its GGUF header; the planner decides
|
|
@@ -654,7 +770,6 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
654
770
|
// cross-check for an explicit draft-mtp request.
|
|
655
771
|
let ggufHasMtp = false;
|
|
656
772
|
let mtpLayerCount = 0;
|
|
657
|
-
const llamaBuildMetadata = binary ? await readLlamaCppBuildMetadata(binary) : null;
|
|
658
773
|
let llamaGpuDevice = null;
|
|
659
774
|
let nvidiaRuntimeDevice;
|
|
660
775
|
let llamaDevices = [];
|
|
@@ -677,8 +792,10 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
677
792
|
offloadBlockCount = summary.blockCount;
|
|
678
793
|
mtpLayerCount = summary.nextnPredictLayers ?? 0;
|
|
679
794
|
ggufHasMtp = mtpLayerCount > 0;
|
|
680
|
-
|
|
681
|
-
|
|
795
|
+
// Only a companion this launch loads can supply the head: one left out
|
|
796
|
+
// for memory would turn MTP on with nothing to run it.
|
|
797
|
+
if (!ggufHasMtp && drafterInUse) {
|
|
798
|
+
const draftSummary = await readGgufSummaryAsync(drafterInUse);
|
|
682
799
|
mtpLayerCount = draftSummary.nextnPredictLayers ?? 0;
|
|
683
800
|
ggufHasMtp = mtpLayerCount > 0;
|
|
684
801
|
}
|
|
@@ -687,6 +804,8 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
687
804
|
const residentWeightBytes = Math.max(0, approxBytes - lazyTensorBytes);
|
|
688
805
|
const residentBytes = estimateLlamaCppResidentBytes(residentWeightBytes, {
|
|
689
806
|
mmprojBytes: visionBudgetBytes,
|
|
807
|
+
draftBytes: draftBudgetBytes(),
|
|
808
|
+
draftResidentBytes: catalogDraftResidentBytes,
|
|
690
809
|
});
|
|
691
810
|
if (lazyTensorBytes > 0) {
|
|
692
811
|
log.info(`[llama-cpp] ${modelCatalogInfo?.id ?? defaultModelId ?? 'model'}: lazy-reading ${(lazyTensorBytes / 1024 ** 3).toFixed(1)} GiB of row-addressable embeddings from the GGUF; budgeting ${(residentWeightBytes / 1024 ** 3).toFixed(1)} GiB as resident model weights`);
|
|
@@ -1025,6 +1144,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1025
1144
|
split,
|
|
1026
1145
|
...(summary.blockCount !== undefined ? { blockCount: summary.blockCount } : {}),
|
|
1027
1146
|
...(kvReserveBytes !== undefined ? { kvReserveBytes } : {}),
|
|
1147
|
+
additionalGpuBytes: drafterGpuBytes(),
|
|
1028
1148
|
}
|
|
1029
1149
|
: {}),
|
|
1030
1150
|
});
|
|
@@ -1063,7 +1183,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1063
1183
|
...(denseFfnSplit ? { split: denseFfnSplit } : {}),
|
|
1064
1184
|
...(summary.blockCount !== undefined ? { blockCount: summary.blockCount } : {}),
|
|
1065
1185
|
...(kvReserveBytes !== undefined ? { kvReserveBytes } : {}),
|
|
1066
|
-
additionalGpuBytes: visionBudgetBytes,
|
|
1186
|
+
additionalGpuBytes: visionBudgetBytes + drafterGpuBytes(),
|
|
1067
1187
|
ramBudgetBytes: brokerSnap?.enforced
|
|
1068
1188
|
? brokerSnap.pools.ramShareBytes
|
|
1069
1189
|
: hostCapacity.ramShareBytes,
|
|
@@ -1100,8 +1220,8 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1100
1220
|
// metadata gate remains load-bearing even though supported GGUFs are
|
|
1101
1221
|
// now default-on.
|
|
1102
1222
|
if (ggufHasMtp) {
|
|
1103
|
-
log.info(`[llama-cpp] ${modelCatalogInfo?.id ?? 'model'} ships an MTP head (nextn_predict_layers=${mtpLayerCount}); speculative decoding ${
|
|
1104
|
-
?
|
|
1223
|
+
log.info(`[llama-cpp] ${modelCatalogInfo?.id ?? 'model'} ships an MTP head (nextn_predict_layers=${mtpLayerCount}); speculative decoding ${specTypeNow()
|
|
1224
|
+
? `runs ${specTypeNow()} (${config.llamaCppSpecType ? 'Settings' : 'catalog'})`
|
|
1105
1225
|
: 'is ON by default (+4-19% decode; disable with llamaCppSpecType: "none")'}.`);
|
|
1106
1226
|
}
|
|
1107
1227
|
}
|
|
@@ -1183,6 +1303,8 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1183
1303
|
providerHolder.current?.getCacheAdapter()?.resetEngineState();
|
|
1184
1304
|
if (snapshot.expected)
|
|
1185
1305
|
return;
|
|
1306
|
+
if (catalogDrafterLoaded())
|
|
1307
|
+
brokenDrafters.add(brokenDrafterKey);
|
|
1186
1308
|
logFile.writeIncident(snapshot);
|
|
1187
1309
|
if (snapshot.panicKind === 'host-out-of-memory') {
|
|
1188
1310
|
noteHostOutOfMemory({
|
|
@@ -1264,6 +1386,15 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1264
1386
|
// restarts of this provider, so a recovered engine doesn't re-OOM on its
|
|
1265
1387
|
// next boot.
|
|
1266
1388
|
recoverStartup: ({ panicKind }) => {
|
|
1389
|
+
// The drafter is the one part of this launch it can do without, and
|
|
1390
|
+
// drafters have changed format between engine releases. Without this a
|
|
1391
|
+
// bad one reads as a build that cannot run, and three of those quarantine
|
|
1392
|
+
// the whole GPU backend; `resolveLaunch` drops it from the retry.
|
|
1393
|
+
if (catalogDrafterLoaded()) {
|
|
1394
|
+
brokenDrafters.add(brokenDrafterKey);
|
|
1395
|
+
log.warn(`[llama-cpp] ${panicKind ?? 'failed start'} while starting ${modelCatalogInfo?.id ?? 'model'} with its speculative drafter — retrying without it`);
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1267
1398
|
if (panicKind !== 'cuda-out-of-memory' && panicKind !== 'vulkan-out-of-memory') {
|
|
1268
1399
|
return false;
|
|
1269
1400
|
}
|
|
@@ -1318,6 +1449,9 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1318
1449
|
return true;
|
|
1319
1450
|
},
|
|
1320
1451
|
resolveLaunch: async () => {
|
|
1452
|
+
if (catalogDrafterLoaded() && brokenDrafters.has(brokenDrafterKey)) {
|
|
1453
|
+
await leaveDrafterOut();
|
|
1454
|
+
}
|
|
1321
1455
|
gpuWatch = new GpuStartupWatch(binaryBackend);
|
|
1322
1456
|
// Again at every launch, not only at build: llama.cpp from the 0.1.51
|
|
1323
1457
|
// release refuses a --slot-save-path that does not exist ("not a
|
|
@@ -1345,7 +1479,7 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1345
1479
|
kvCacheType,
|
|
1346
1480
|
slots,
|
|
1347
1481
|
ggufHasMtp,
|
|
1348
|
-
installedDraftModelPath:
|
|
1482
|
+
installedDraftModelPath: drafterInUse,
|
|
1349
1483
|
// Opt-in A/B lever: `GEZEL_LLAMA_REASONING_FORMAT=none`
|
|
1350
1484
|
// disables llama-server's chat-template output parsing so mangled
|
|
1351
1485
|
// model output reaches `delta.content` for provider-side salvage.
|
|
@@ -1483,6 +1617,8 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1483
1617
|
ubatchSize: lastArgValue(resolvedAdvancedArgs, '--ubatch-size') ?? 'default',
|
|
1484
1618
|
flashAttention: lastArgValue(resolvedAdvancedArgs, '--flash-attn') ?? 'default',
|
|
1485
1619
|
threads: lastArgValue(resolvedAdvancedArgs, '--threads') ?? 'default',
|
|
1620
|
+
specType: lastArgValue(resolvedAdvancedArgs, '--spec-type') ?? 'default',
|
|
1621
|
+
specDraftModel: basename(lastArgValue(resolvedAdvancedArgs, '--spec-draft-model') ?? 'none'),
|
|
1486
1622
|
reasoningBudgetTokens: reasoningBudgetTokens ?? 'unbounded',
|
|
1487
1623
|
reasoningBudgetSource,
|
|
1488
1624
|
reasoningPreserve,
|
|
@@ -1493,6 +1629,9 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1493
1629
|
// Equivalent to `--api-key`, but out of `argv` so it does not show
|
|
1494
1630
|
// up in `ps` on a shared machine.
|
|
1495
1631
|
LLAMA_API_KEY: engineApiKey(),
|
|
1632
|
+
// A ROCm build's kernel packs, rocBLAS routing, and the AMD GPUs it
|
|
1633
|
+
// has no code for kept out of sight (rocm-devices.ts).
|
|
1634
|
+
...rocmLaunchEnv(home, binary, llamaBuildMetadata?.backend ?? binaryBackend),
|
|
1496
1635
|
},
|
|
1497
1636
|
baseUrl: `http://127.0.0.1:${port}`,
|
|
1498
1637
|
};
|
|
@@ -19,7 +19,7 @@ import { execFile } from 'node:child_process';
|
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { delimiter, dirname, join } from 'node:path';
|
|
21
21
|
import { promisify } from 'node:util';
|
|
22
|
-
import { windowsHeadlessSpawnOptions } from '@bendyline/gezel/native';
|
|
22
|
+
import { rocmLaunchEnv, windowsHeadlessSpawnOptions } from '@bendyline/gezel/native';
|
|
23
23
|
import { gezelHome } from '@bendyline/gezel/paths';
|
|
24
24
|
import { totalMemoryBytes } from '../../system/host-memory.js';
|
|
25
25
|
const execFileAsync = promisify(execFile);
|
|
@@ -191,6 +191,9 @@ export async function probeLlamaDevices(opts) {
|
|
|
191
191
|
env: {
|
|
192
192
|
...process.env,
|
|
193
193
|
PATH: inheritedPath ? `${binDir}${delimiter}${inheritedPath}` : binDir,
|
|
194
|
+
// A ROCm build lists only the GPUs its launch will see, so the VRAM
|
|
195
|
+
// the planner reads is the VRAM the engine gets.
|
|
196
|
+
...rocmLaunchEnv(home, binaryPath),
|
|
194
197
|
},
|
|
195
198
|
// This is an awaited probe, not an independent process. Hide its
|
|
196
199
|
// Windows console without detaching it from the daemon.
|
|
@@ -64,6 +64,12 @@ export interface PerModelLlamaCppEngineConfig {
|
|
|
64
64
|
mtp?: boolean;
|
|
65
65
|
draftModelId?: string;
|
|
66
66
|
nMax?: number;
|
|
67
|
+
sampling?: 'greedy' | 'probabilistic';
|
|
68
|
+
byBackend?: Partial<Record<string, {
|
|
69
|
+
type?: string;
|
|
70
|
+
nMax?: number;
|
|
71
|
+
sampling?: 'greedy' | 'probabilistic';
|
|
72
|
+
}>>;
|
|
67
73
|
};
|
|
68
74
|
}
|
|
69
75
|
/** Phase v2 hardware-aware offload decision (lowest precedence). */
|
|
@@ -106,7 +112,8 @@ export interface EngineFlagInput {
|
|
|
106
112
|
/**
|
|
107
113
|
* Absolute path to a catalog-installed speculative draft sidecar.
|
|
108
114
|
* Separate MTP assistants (Gemma) and other draft algorithms need this;
|
|
109
|
-
* combined MTP GGUFs (Qwen)
|
|
115
|
+
* combined MTP GGUFs (Qwen) leave it unset, even with a DFlash drafter on
|
|
116
|
+
* disk: `--spec-draft-model` would make MTP load the wrong file.
|
|
110
117
|
*/
|
|
111
118
|
installedDraftModelPath?: string | undefined;
|
|
112
119
|
/**
|