@bendyline/gezel-inference 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/controller.d.ts +2 -0
- package/dist/cache/controller.js +38 -5
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/manifest.json +114 -0
- package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/raw-index.json +1 -1
- package/dist/catalog/image-loras/index.json +1 -0
- package/dist/catalog/image-loras/kr/krea-2-ink-wash/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-ink-wash/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-isometric-3d/manifest.json +32 -0
- package/dist/catalog/image-loras/kr/krea-2-isometric-3d/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-kids-drawing/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-kids-drawing/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-pixel-art/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-pixel-art/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-turbo-4step/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-turbo-4step/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/mo/moss-image-gouache/manifest.json +29 -0
- package/dist/catalog/image-loras/mo/moss-image-gouache/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/mo/moss-image-sft-4step/manifest.json +30 -0
- package/dist/catalog/image-loras/mo/moss-image-sft-4step/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/manifest.json +31 -0
- package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/raw-index.json +1 -0
- package/dist/catalog/image-loras/sd/sdxl-3d-render/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-3d-render/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-coloring-book/manifest.json +30 -0
- package/dist/catalog/image-loras/sd/sdxl-coloring-book/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-papercut/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-papercut/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-pixel-art/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-pixel-art/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/manifest.json +3 -0
- package/dist/catalog/image-models/index.json +1 -1
- package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/manifest.json +44 -0
- package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/versions/1.0.0/manifest.json +36 -0
- package/dist/catalog/image-models/raw-index.json +1 -1
- package/dist/catalog/knowledge-catalogs/index.json +1 -1
- package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/manifest.json +55 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/versions/2026.10.2/manifest.json +165 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.2/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.2/manifest.json +209 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.2/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.2/manifest.json +165 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.2/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.2/manifest.json +205 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.2/manifest.json +237 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.2/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/provenance.json +2 -1
- package/dist/engine-components.js +2 -2
- package/dist/engines/electron-native-reuse.d.ts +7 -1
- package/dist/engines/electron-native-reuse.js +12 -4
- package/dist/engines/native-file-manifest.json +546 -406
- package/dist/engines/native-manifest.d.ts +4 -4
- package/dist/engines/native-manifest.js +36 -36
- package/dist/engines/resolver.d.ts +1 -1
- package/dist/engines/resolver.js +48 -51
- package/dist/http/auth-context.d.ts +12 -0
- package/dist/http/routes/image-ensure-routes.d.ts +4 -0
- package/dist/http/routes/image-ensure-routes.js +102 -0
- package/dist/http/routes/v1-image-styles.d.ts +5 -0
- package/dist/http/routes/v1-image-styles.js +91 -0
- package/dist/http/routes/v1-images.d.ts +1 -0
- package/dist/http/routes/v1-images.js +15 -93
- package/dist/inference-service.js +2 -1
- package/dist/knowledge/worker-host.js +2 -0
- package/dist/machine-engine/inspect-runtime.js +12 -3
- package/dist/memory/embed-core.js +3 -0
- package/dist/memory/embeddings.js +2 -0
- package/dist/models/image-fit.js +2 -2
- package/dist/models/weight-facts.d.ts +5 -0
- package/dist/models/weight-facts.js +5 -2
- package/dist/providers/ds4/build-provider.js +1 -1
- package/dist/providers/ds4/residency.js +3 -3
- package/dist/providers/image/img2img-support.d.ts +36 -1
- package/dist/providers/image/img2img-support.js +30 -1
- package/dist/providers/image/local-engine.d.ts +1 -0
- package/dist/providers/image/local-engine.js +8 -2
- package/dist/providers/image/local-factory.d.ts +10 -1
- package/dist/providers/image/local-factory.js +33 -6
- package/dist/providers/image/mock.d.ts +8 -1
- package/dist/providers/image/mock.js +46 -1
- package/dist/providers/image/pull-registry.d.ts +25 -3
- package/dist/providers/image/pull-registry.js +96 -34
- package/dist/providers/image/sd-cpp.d.ts +63 -4
- package/dist/providers/image/sd-cpp.js +361 -38
- package/dist/providers/image/types.d.ts +76 -4
- package/dist/providers/image/types.js +8 -0
- package/dist/providers/llama-cpp/build-provider.js +3 -2
- package/dist/providers/llama-cpp/cache-adapter.d.ts +3 -0
- package/dist/providers/llama-cpp/cache-adapter.js +18 -4
- package/dist/providers/llama-cpp/devices.js +2 -2
- package/dist/providers/llama-cpp/engine-flags.d.ts +5 -0
- package/dist/providers/llama-cpp/engine-flags.js +5 -2
- package/dist/providers/llama-cpp/provider.js +20 -4
- package/dist/providers/mlx/provider.js +13 -4
- package/dist/providers/mlx/python/gezel_mlx_server.py +9 -2
- package/dist/providers/mlx/python/spec_decode.py +44 -5
- package/dist/providers/native/capacity-broker.d.ts +1 -1
- package/dist/providers/native/capacity-broker.js +8 -8
- package/dist/providers/native/capacity-budget.js +5 -4
- package/dist/providers/native/device-capacity-ledger.d.ts +2 -0
- package/dist/providers/native/device-capacity-ledger.js +36 -5
- package/dist/providers/native/device-capacity.js +4 -3
- package/dist/providers/native/local-engine-runtime.js +4 -4
- package/dist/providers/native/process-identity.d.ts +2 -0
- package/dist/providers/native/process-identity.js +70 -0
- package/dist/relevance/relevance-core.js +2 -0
- package/dist/relevance/relevance-model.js +2 -0
- package/dist/relevance/settings.js +2 -2
- package/dist/runtime-lock.d.ts +43 -5
- package/dist/runtime-lock.js +189 -26
- package/dist/system/host-memory.d.ts +46 -0
- package/dist/system/host-memory.js +89 -0
- package/dist/system/memory.js +5 -4
- package/dist/transformers-cache.js +2 -0
- package/dist/utils/download-common.d.ts +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageAspectRatio, ImageModelAuxiliaryRole, ImageQuality } from '@bendyline/gezel';
|
|
1
|
+
import type { ImageAspectRatio, ImageLoraPrompt, ImageLoraPurpose, ImageLoraRender, ImageModelAuxiliaryRole, ImageQuality } from '@bendyline/gezel';
|
|
2
2
|
export type { ImageModelAuxiliaryRole } from '@bendyline/gezel';
|
|
3
3
|
/**
|
|
4
4
|
* ImageProvider — the service-side abstraction for local image generation.
|
|
@@ -69,12 +69,21 @@ export interface ImageGenerationInput {
|
|
|
69
69
|
seed?: number;
|
|
70
70
|
/**
|
|
71
71
|
* Source images for image editing / reference-driven generation.
|
|
72
|
-
* Cloud providers accept multiple;
|
|
73
|
-
*
|
|
72
|
+
* Cloud providers accept multiple; a local model that reads references
|
|
73
|
+
* takes up to its limit, and sd-cpp's img2img path uses the first only
|
|
74
|
+
* (the others are dropped with a warning).
|
|
74
75
|
*/
|
|
75
76
|
inputImages?: ImageInputBytes[];
|
|
76
|
-
/** Img2img strength (0 = keep source, 1 = ignore source). sd-cpp only. */
|
|
77
|
+
/** Img2img strength (0 = keep source, 1 = ignore source). sd-cpp img2img only. */
|
|
77
78
|
strength?: number;
|
|
79
|
+
/**
|
|
80
|
+
* LoRAs to apply, by catalog id: a style, a fast mode. The engine reads each
|
|
81
|
+
* one's file, strength, prompt words and render settings from its own
|
|
82
|
+
* install, so an engine that predates LoRAs renders correctly without them.
|
|
83
|
+
*/
|
|
84
|
+
loras?: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
}>;
|
|
78
87
|
/**
|
|
79
88
|
* Optional callback fired as the engine advances through sampling
|
|
80
89
|
* steps. Today only the local sd-cpp provider emits these; cloud
|
|
@@ -95,6 +104,11 @@ export declare class ImageGenerationCancelledError extends Error {
|
|
|
95
104
|
readonly code = "image_generation_cancelled";
|
|
96
105
|
constructor();
|
|
97
106
|
}
|
|
107
|
+
/** A model made only for editing was asked for a picture with nothing to edit. */
|
|
108
|
+
export declare class ImageSourceRequiredError extends Error {
|
|
109
|
+
readonly code = "image_source_required";
|
|
110
|
+
constructor(modelName: string);
|
|
111
|
+
}
|
|
98
112
|
/**
|
|
99
113
|
* The engine could not fit the render into graphics memory. sd-server plans
|
|
100
114
|
* placement at launch and can misjudge free memory held by another engine;
|
|
@@ -120,6 +134,11 @@ export interface ImageGenerationMeta {
|
|
|
120
134
|
img2imgSkippedReason?: string;
|
|
121
135
|
/** Provider limits or adjustments to the requested image settings. */
|
|
122
136
|
settingsNotes?: string[];
|
|
137
|
+
/** The LoRAs the engine applied; absent when none. */
|
|
138
|
+
loras?: Array<{
|
|
139
|
+
id: string;
|
|
140
|
+
name: string;
|
|
141
|
+
}>;
|
|
123
142
|
}
|
|
124
143
|
export interface ImageGenerationOutput {
|
|
125
144
|
png: Buffer;
|
|
@@ -134,6 +153,45 @@ export interface InstalledImageModelInfo {
|
|
|
134
153
|
weightsKind?: 'checkpoint' | 'diffusion-model';
|
|
135
154
|
/** Explicit catalog capability recorded at pull time; absent → resolve via the ladder. */
|
|
136
155
|
supportsImg2Img?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Resolved editing capability (see `resolveImageEditCapability`), so a
|
|
158
|
+
* caller across the machine broker or a paired server can choose a model
|
|
159
|
+
* without the engine's assessment map.
|
|
160
|
+
*/
|
|
161
|
+
editMode?: 'reference' | 'img2img';
|
|
162
|
+
maxSourceImages?: number;
|
|
163
|
+
editOnly?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Installed LoRAs that fit this model. Carried on the model listing because
|
|
166
|
+
* that listing already reaches the product daemon from a machine broker or
|
|
167
|
+
* a paired server; an engine that predates LoRAs lists none.
|
|
168
|
+
*/
|
|
169
|
+
loras?: InstalledImageLoraSummary[];
|
|
170
|
+
}
|
|
171
|
+
/** An installed LoRA as the model listing names it. */
|
|
172
|
+
export interface InstalledImageLoraSummary {
|
|
173
|
+
id: string;
|
|
174
|
+
name: string;
|
|
175
|
+
purpose: ImageLoraPurpose;
|
|
176
|
+
/** The steps a speed LoRA renders in. */
|
|
177
|
+
steps?: number;
|
|
178
|
+
}
|
|
179
|
+
export interface InstalledImageLoraInfo extends InstalledImageLoraSummary {
|
|
180
|
+
baseModels: string[];
|
|
181
|
+
approxSizeBytes: number;
|
|
182
|
+
installedAt: string;
|
|
183
|
+
}
|
|
184
|
+
/** What a LoRA install needs from its catalog entry; persisted beside the file. */
|
|
185
|
+
export interface ImageLoraPullSpec {
|
|
186
|
+
downloadUrl: string;
|
|
187
|
+
sha256: string;
|
|
188
|
+
approxSizeBytes: number;
|
|
189
|
+
name: string;
|
|
190
|
+
baseModels: string[];
|
|
191
|
+
purpose: ImageLoraPurpose;
|
|
192
|
+
strength: number;
|
|
193
|
+
prompt?: ImageLoraPrompt;
|
|
194
|
+
render?: ImageLoraRender;
|
|
137
195
|
}
|
|
138
196
|
export type ImageModelPullEvent = {
|
|
139
197
|
type: 'progress';
|
|
@@ -187,6 +245,12 @@ export interface ImageModelPullSpec {
|
|
|
187
245
|
* weights-kind default.
|
|
188
246
|
*/
|
|
189
247
|
supportsImg2Img?: boolean;
|
|
248
|
+
/** Catalog `referenceImages`, persisted the same way. */
|
|
249
|
+
referenceImages?: {
|
|
250
|
+
max: number;
|
|
251
|
+
};
|
|
252
|
+
/** Catalog `editOnly`, persisted the same way. */
|
|
253
|
+
editOnly?: boolean;
|
|
190
254
|
/** Auxiliary files (VAE, text encoders) downloaded alongside the unet. */
|
|
191
255
|
auxiliaryFiles: ImageModelAuxiliaryPullSpec[];
|
|
192
256
|
}
|
|
@@ -225,6 +289,14 @@ export interface ImageProvider {
|
|
|
225
289
|
*/
|
|
226
290
|
pullModel(id: string, spec: ImageModelPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
|
|
227
291
|
deleteModel(id: string): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* LoRA management for an engine that applies them (the local sd-cpp
|
|
294
|
+
* engine). Rendering never needs these: the model listing carries the
|
|
295
|
+
* installed LoRAs and `generate` takes their ids.
|
|
296
|
+
*/
|
|
297
|
+
pullLora?(id: string, spec: ImageLoraPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
|
|
298
|
+
listLoras?(): Promise<InstalledImageLoraInfo[]>;
|
|
299
|
+
deleteLora?(id: string): Promise<void>;
|
|
228
300
|
/**
|
|
229
301
|
* Probe the underlying engine for reachability without doing real
|
|
230
302
|
* work. Implementations should be fast (sub-second) and never throw
|
|
@@ -6,6 +6,14 @@ export class ImageGenerationCancelledError extends Error {
|
|
|
6
6
|
this.name = 'ImageGenerationCancelledError';
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
/** A model made only for editing was asked for a picture with nothing to edit. */
|
|
10
|
+
export class ImageSourceRequiredError extends Error {
|
|
11
|
+
code = 'image_source_required';
|
|
12
|
+
constructor(modelName) {
|
|
13
|
+
super(`${modelName} changes a picture you give it. Attach the picture to change, or pick a model that draws new ones.`);
|
|
14
|
+
this.name = 'ImageSourceRequiredError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* The engine could not fit the render into graphics memory. sd-server plans
|
|
11
19
|
* placement at launch and can misjudge free memory held by another engine;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { availableParallelism } from 'node:os';
|
|
2
1
|
import { delimiter, dirname, join } from 'node:path';
|
|
3
2
|
import { DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS, createLogger, estimateLlamaCppResidentBytes, llamaCppBackendExclusion, llamaCppBackendLabel, } from '@bendyline/gezel';
|
|
4
3
|
import { LLAMA_QUARANTINE_STRIKES, clearLlamaStartupStrikes, recordLlamaStartupCrash, unrunnableBuildExit, } from '@bendyline/gezel/native';
|
|
@@ -6,6 +5,7 @@ import { gezelPaths } from '@bendyline/gezel/paths';
|
|
|
6
5
|
import { mmprojBudgetBytes, nativeVisionEnabledFor } from '../../chat/vision-capability.js';
|
|
7
6
|
import { effectiveEngineRelease, isEnginePinned } from '../../engines/native-manifest.js';
|
|
8
7
|
import { engineInstallPhase } from '../../engines/registry.js';
|
|
8
|
+
import { availableCpuCount, containerCpuLimit } from '../../system/host-memory.js';
|
|
9
9
|
import { localDeviceSettingsLabel, noModelYetMessage, notReadyError, } from '../active-install-message.js';
|
|
10
10
|
import { resolveCatalogLlamaCppEngineConfig, resolveCatalogReasoningBudget, } from '../catalog-model-config.js';
|
|
11
11
|
import { CapacityDeniedError, NOT_ENOUGH_MEMORY_MESSAGE, availableSystemRamBytes, formatContextCapacityDenial, minViableLocalContextTokens, planAdaptiveContextGrowth, pressureIdleGraceMs, resolveLlamaCppContextRequirement, } from '../native/capacity-broker.js';
|
|
@@ -1352,7 +1352,8 @@ export async function buildLlamaCppProvider(opts) {
|
|
|
1352
1352
|
reasoningFormat: process.env.GEZEL_LLAMA_REASONING_FORMAT?.trim() || undefined,
|
|
1353
1353
|
reasoningPreserve,
|
|
1354
1354
|
backend: llamaBuildMetadata?.backend ?? process.env.GEZEL_LLAMA_SERVER_BACKEND,
|
|
1355
|
-
hostCpuCount:
|
|
1355
|
+
hostCpuCount: availableCpuCount(),
|
|
1356
|
+
containerCpuLimit: containerCpuLimit() ?? undefined,
|
|
1356
1357
|
});
|
|
1357
1358
|
return {
|
|
1358
1359
|
command: binary,
|
|
@@ -201,6 +201,7 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
201
201
|
prepareForSend(sessionId: string, systemPrompt?: string, layers?: SystemPromptLayers): Promise<void>;
|
|
202
202
|
buildRequestExtras(sessionId: string): Record<string, unknown>;
|
|
203
203
|
evict(sessionIds: readonly string[]): Promise<void>;
|
|
204
|
+
private evictUnlocked;
|
|
204
205
|
markEphemeral(sessionId: string): void;
|
|
205
206
|
reportUsage(): Promise<readonly EngineCacheUsage[]>;
|
|
206
207
|
warm(_sessionId: string, _messages: readonly CacheWarmMessage[]): Promise<void>;
|
|
@@ -210,6 +211,7 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
210
211
|
* sessions for the next boot.
|
|
211
212
|
*/
|
|
212
213
|
flushAll(): Promise<number>;
|
|
214
|
+
private flushAllUnlocked;
|
|
213
215
|
/**
|
|
214
216
|
* Async path: allocate a slot for `sessionId`, save the LRU victim
|
|
215
217
|
* (if any) to disk, and try to restore the new slot from disk
|
|
@@ -217,6 +219,7 @@ export declare class LlamaCppCacheAdapter implements EngineCacheAdapter {
|
|
|
217
219
|
* {@link prepareForSend}.
|
|
218
220
|
*/
|
|
219
221
|
private allocateSlot;
|
|
222
|
+
private withAllocationLock;
|
|
220
223
|
private assertEngineGeneration;
|
|
221
224
|
private allocateSlotUnlocked;
|
|
222
225
|
/**
|
|
@@ -258,7 +258,11 @@ export class LlamaCppCacheAdapter {
|
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
async evict(sessionIds) {
|
|
261
|
-
|
|
261
|
+
return this.withAllocationLock((generation) => this.evictUnlocked(sessionIds, generation));
|
|
262
|
+
}
|
|
263
|
+
async evictUnlocked(sessionIds, generation) {
|
|
264
|
+
if (generation !== this.engineGeneration)
|
|
265
|
+
return;
|
|
262
266
|
// Save before forgetting so the session can resume on next access.
|
|
263
267
|
// Persistence is best-effort; failures are logged and the local
|
|
264
268
|
// mapping is still cleared (controller view stays consistent).
|
|
@@ -352,7 +356,11 @@ export class LlamaCppCacheAdapter {
|
|
|
352
356
|
async flushAll() {
|
|
353
357
|
if (!this.slotSavePath)
|
|
354
358
|
return 0;
|
|
355
|
-
|
|
359
|
+
return this.withAllocationLock((generation) => this.flushAllUnlocked(generation));
|
|
360
|
+
}
|
|
361
|
+
async flushAllUnlocked(generation) {
|
|
362
|
+
if (generation !== this.engineGeneration)
|
|
363
|
+
return 0;
|
|
356
364
|
let saved = 0;
|
|
357
365
|
for (const [sessionId, slot] of this.sessionToSlot) {
|
|
358
366
|
if (await this.saveSlotForSession(slot, sessionId))
|
|
@@ -369,6 +377,13 @@ export class LlamaCppCacheAdapter {
|
|
|
369
377
|
* {@link prepareForSend}.
|
|
370
378
|
*/
|
|
371
379
|
async allocateSlot(sessionId) {
|
|
380
|
+
return this.withAllocationLock((generation) => {
|
|
381
|
+
this.assertEngineGeneration(generation);
|
|
382
|
+
return this.allocateSlotUnlocked(sessionId, generation);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
// Eviction and idle flushing must not save a slot after a handoff has rebound it.
|
|
386
|
+
async withAllocationLock(work) {
|
|
372
387
|
const generation = this.engineGeneration;
|
|
373
388
|
const previous = this.allocationTail;
|
|
374
389
|
let release;
|
|
@@ -377,8 +392,7 @@ export class LlamaCppCacheAdapter {
|
|
|
377
392
|
});
|
|
378
393
|
await previous;
|
|
379
394
|
try {
|
|
380
|
-
|
|
381
|
-
return await this.allocateSlotUnlocked(sessionId, generation);
|
|
395
|
+
return await work(generation);
|
|
382
396
|
}
|
|
383
397
|
finally {
|
|
384
398
|
release();
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { execFile } from 'node:child_process';
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
20
|
-
import { totalmem } from 'node:os';
|
|
21
20
|
import { delimiter, dirname, join } from 'node:path';
|
|
22
21
|
import { promisify } from 'node:util';
|
|
23
22
|
import { windowsHeadlessSpawnOptions } from '@bendyline/gezel/native';
|
|
24
23
|
import { gezelHome } from '@bendyline/gezel/paths';
|
|
24
|
+
import { totalMemoryBytes } from '../../system/host-memory.js';
|
|
25
25
|
const execFileAsync = promisify(execFile);
|
|
26
26
|
const LINE_RE = /^\s*(\S+?):\s*(.+?)\s*\((\d+)\s*MiB,\s*(\d+)\s*MiB\s+free\)\s*$/;
|
|
27
27
|
/**
|
|
@@ -158,7 +158,7 @@ export function llamaBinaryFingerprint(binaryPath) {
|
|
|
158
158
|
export async function probeLlamaDevices(opts) {
|
|
159
159
|
const { binaryPath, home, force } = opts;
|
|
160
160
|
const now = opts.nowIso ?? (() => new Date().toISOString());
|
|
161
|
-
const systemRamBytes = opts.systemRamBytes ??
|
|
161
|
+
const systemRamBytes = opts.systemRamBytes ?? totalMemoryBytes();
|
|
162
162
|
const path = cachePath(home);
|
|
163
163
|
const binary = llamaBinaryFingerprint(binaryPath);
|
|
164
164
|
if (!force && existsSync(path)) {
|
|
@@ -133,6 +133,11 @@ export interface EngineFlagInput {
|
|
|
133
133
|
backend?: string | undefined;
|
|
134
134
|
/** Logical CPUs available to the daemon; caps the GPU-resident thread default. */
|
|
135
135
|
hostCpuCount?: number | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* CPUs a container leaves the daemon, when fewer than the host has (see
|
|
138
|
+
* `containerCpuLimit`). Without it llama.cpp sizes its pool from the host.
|
|
139
|
+
*/
|
|
140
|
+
containerCpuLimit?: number | undefined;
|
|
136
141
|
}
|
|
137
142
|
/** Default `--cache-reuse` chunk when neither config nor manifest set it. */
|
|
138
143
|
export declare const DEFAULT_CACHE_REUSE = 256;
|
|
@@ -100,7 +100,7 @@ function normalizeFlagKey(key) {
|
|
|
100
100
|
* `args` array.
|
|
101
101
|
*/
|
|
102
102
|
export function buildLlamaCppEngineArgs(input) {
|
|
103
|
-
const { config, perModel, planner, kvCacheType, slots, ggufHasMtp, installedDraftModelPath, reasoningFormat, reasoningPreserve, backend, hostCpuCount, } = input;
|
|
103
|
+
const { config, perModel, planner, kvCacheType, slots, ggufHasMtp, installedDraftModelPath, reasoningFormat, reasoningPreserve, backend, hostCpuCount, containerCpuLimit, } = input;
|
|
104
104
|
const args = [];
|
|
105
105
|
// ── GPU-layer offload (`--n-gpu-layers`) ──────────────────────────
|
|
106
106
|
const nGpuLayers = config.llamaCppNGpuLayers ?? perModel?.nGpuLayers ?? planner?.nGpuLayers;
|
|
@@ -203,9 +203,12 @@ export function buildLlamaCppEngineArgs(input) {
|
|
|
203
203
|
!(typeof nCpuMoe === 'number' && nCpuMoe > 0) &&
|
|
204
204
|
!(typeof nCpuFfn === 'number' && nCpuFfn > 0) &&
|
|
205
205
|
!extraArgsOwnThreads(config.llamaCppExtraArgs);
|
|
206
|
+
// In a container that allows fewer CPUs than the host has, the all-cores
|
|
207
|
+
// default oversubscribes the quota and spends each period throttled.
|
|
206
208
|
const threads = config.llamaCppThreads ??
|
|
207
209
|
perModel?.threads ??
|
|
208
|
-
(gpuResident ? gpuResidentThreads(hostCpuCount) : undefined)
|
|
210
|
+
(gpuResident ? gpuResidentThreads(hostCpuCount) : undefined) ??
|
|
211
|
+
(extraArgsOwnThreads(config.llamaCppExtraArgs) ? undefined : containerCpuLimit);
|
|
209
212
|
if (typeof threads === 'number')
|
|
210
213
|
args.push('--threads', String(threads));
|
|
211
214
|
const batchSize = config.llamaCppBatchSize ?? perModel?.batchSize;
|
|
@@ -32,6 +32,13 @@ import { classifyStartupLine } from './stdout-parser.js';
|
|
|
32
32
|
export * from '@bendyline/gezel/local-loop';
|
|
33
33
|
export { isFileRepairTurn as isScenarioFileRepairTurn } from '@bendyline/gezel/local-loop';
|
|
34
34
|
const log = createLogger('llama-cpp');
|
|
35
|
+
/**
|
|
36
|
+
* How long shutdown waits for the slot caches to reach disk. The app gives
|
|
37
|
+
* the whole daemon 15 s to quit, and a save the engine never answers must
|
|
38
|
+
* not spend all of it: an unsaved or half-written slot only means a fresh
|
|
39
|
+
* prefill next time, because a restore that fails falls back to one.
|
|
40
|
+
*/
|
|
41
|
+
const SHUTDOWN_FLUSH_BUDGET_MS = 8_000;
|
|
35
42
|
export class LlamaCppProvider {
|
|
36
43
|
name = 'llama-cpp';
|
|
37
44
|
queue;
|
|
@@ -627,12 +634,21 @@ export class LlamaCppProvider {
|
|
|
627
634
|
// llama-server exits the slot save endpoint is gone. flushAll is
|
|
628
635
|
// best-effort; failures during shutdown shouldn't block the stop.
|
|
629
636
|
if (this.cacheAdapter) {
|
|
637
|
+
let timer;
|
|
638
|
+
const budget = new Promise((resolve) => {
|
|
639
|
+
timer = setTimeout(() => resolve('timeout'), SHUTDOWN_FLUSH_BUDGET_MS);
|
|
640
|
+
timer.unref?.();
|
|
641
|
+
});
|
|
630
642
|
try {
|
|
631
|
-
|
|
643
|
+
// Best-effort persistence: a failure only means the next boot pays
|
|
644
|
+
// the prefill cost on resume.
|
|
645
|
+
const outcome = await Promise.race([this.cacheAdapter.flushAll().catch(() => 0), budget]);
|
|
646
|
+
if (outcome === 'timeout') {
|
|
647
|
+
log.warn(`slot cache save still running after ${SHUTDOWN_FLUSH_BUDGET_MS}ms at shutdown; stopping the engine`);
|
|
648
|
+
}
|
|
632
649
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// boot pays the prefill cost on resume, same as before.
|
|
650
|
+
finally {
|
|
651
|
+
clearTimeout(timer);
|
|
636
652
|
}
|
|
637
653
|
}
|
|
638
654
|
await this.supervisor?.stop();
|
|
@@ -73,7 +73,7 @@ export { buildMidStreamDropMessage, computePreFirstByteBudgetMs, estimatePromptT
|
|
|
73
73
|
import { FILE_REPAIR_MUTATION_TOOLS, FILE_REPAIR_READ_TOOLS, applyConstrainedTurnShape, disableTemplateThinking, fileRepairTargetPath, isSourceFileRepairPrompt, requiredToolOnly, writeFileOnlyTools, } from '../constrained-turn.js';
|
|
74
74
|
import { hasExplicitFullFileRewriteWording } from '../direct-file-work-prompt.js';
|
|
75
75
|
import { buildImmediateFileWriteNoMutationNudge, buildPrerequisiteRepairReadNudge, buildScenarioRepairNoMutationNudge, completeWorkspaceReadPaths, extractPrerequisiteRepairReadPaths, normalizeWorkspacePathForCompare, remainingPrerequisiteRepairReadPaths, } from '../file-repair-policy.js';
|
|
76
|
-
import { LOCAL_TURN_LIMITS, LocalTurnPolicy, compactLocalTurn, planFileTurn, repairTools, turnCheckpoint, } from '../local-turn-policy.js';
|
|
76
|
+
import { ConstrainedWriteRecovery, LOCAL_TURN_LIMITS, LocalTurnPolicy, compactLocalTurn, planFileTurn, repairTools, turnCheckpoint, } from '../local-turn-policy.js';
|
|
77
77
|
const MAX_TOOL_LOOP_TURNS = LOCAL_TURN_LIMITS.iterations;
|
|
78
78
|
// Hard per-turn ceiling when the caller doesn't pass `opts.timeoutMs`.
|
|
79
79
|
// 10 minutes matches what the manager passes for local providers in
|
|
@@ -984,6 +984,7 @@ class MlxSession extends StreamingSessionBase {
|
|
|
984
984
|
// validation error every iteration, "diagnosing" the schema in
|
|
985
985
|
// chat, and re-trying the same wrong shape forever. See the
|
|
986
986
|
// tracker's docstring for the threshold rationale.
|
|
987
|
+
const writeRecovery = new ConstrainedWriteRecovery();
|
|
987
988
|
const failureTracker = new ToolFailureTracker({
|
|
988
989
|
surgicalEditsAvailable,
|
|
989
990
|
delegationAvailable,
|
|
@@ -1164,7 +1165,9 @@ class MlxSession extends StreamingSessionBase {
|
|
|
1164
1165
|
log.debug(`turn#${seq}.${turn} required-call turn: ${opts.requiredTool} only`);
|
|
1165
1166
|
}
|
|
1166
1167
|
}
|
|
1167
|
-
const fileTurnPlan =
|
|
1168
|
+
const fileTurnPlan = writeRecovery.active
|
|
1169
|
+
? { kind: 'ordinary' }
|
|
1170
|
+
: planFileTurn(prompt, tools, opts?.fileTurnIntent);
|
|
1168
1171
|
const unreadInputs = unreadRequiredInputs(requiredInputs, requiredInputReads);
|
|
1169
1172
|
const immediateFileWriteTurn = fileTurnPlan.kind === 'create-file' && unreadInputs.length === 0;
|
|
1170
1173
|
if (fileTurnPlan.kind === 'create-file' &&
|
|
@@ -1219,6 +1222,11 @@ class MlxSession extends StreamingSessionBase {
|
|
|
1219
1222
|
(typeof body.max_tokens !== 'number' || body.max_tokens < FILE_WRITE_MIN_TOKENS)) {
|
|
1220
1223
|
body.max_tokens = FILE_WRITE_MIN_TOKENS;
|
|
1221
1224
|
}
|
|
1225
|
+
if (writeRecovery.lookupPending) {
|
|
1226
|
+
requestTools = tools?.filter((tool) => chatCompletionToolName(tool) === writeRecovery.lookupPending);
|
|
1227
|
+
applyConstrainedTurnShape(body);
|
|
1228
|
+
log.debug(`turn#${seq}.${turn} grounding recovery: research-only surface=${writeRecovery.lookupPending}`);
|
|
1229
|
+
}
|
|
1222
1230
|
if (requestTools && requestTools.length > 0)
|
|
1223
1231
|
body.tools = requestTools;
|
|
1224
1232
|
// Tool-schema size breakdown (opt-in: GEZEL_PROMPT_BREAKDOWN=1). The
|
|
@@ -2803,6 +2811,7 @@ class MlxSession extends StreamingSessionBase {
|
|
|
2803
2811
|
}
|
|
2804
2812
|
}
|
|
2805
2813
|
const tracked = failureTracker.recordResult(call.function.name, output);
|
|
2814
|
+
writeRecovery.recordResult(call.function.name, output, tracked, tools);
|
|
2806
2815
|
terminalActionClosing ??= terminalToolClosingText(this.deps.terminalToolPolicy, call.function.name, args, output);
|
|
2807
2816
|
// Layer the same-args repeat tracker on top of the failure
|
|
2808
2817
|
// tracker. The failure tracker catches "tool keeps erroring";
|
|
@@ -2877,7 +2886,7 @@ class MlxSession extends StreamingSessionBase {
|
|
|
2877
2886
|
});
|
|
2878
2887
|
}
|
|
2879
2888
|
this.turnPolicy.checkpoint();
|
|
2880
|
-
if (fileRepairTurn && repairMutationSucceeded) {
|
|
2889
|
+
if (fileRepairTurn && repairMutationSucceeded && !writeRecovery.active) {
|
|
2881
2890
|
const closing = 'Updated the requested file.';
|
|
2882
2891
|
this.messages.push({ role: 'assistant', content: closing });
|
|
2883
2892
|
return closing;
|
|
@@ -2898,7 +2907,7 @@ class MlxSession extends StreamingSessionBase {
|
|
|
2898
2907
|
log.info(`turn#${seq} END terminal-tool-bail afterMs=${Date.now() - start} loopTurns=${turn + 1}`);
|
|
2899
2908
|
return fullText;
|
|
2900
2909
|
}
|
|
2901
|
-
if (immediateFileWritePaths.length > 0) {
|
|
2910
|
+
if (immediateFileWritePaths.length > 0 && !writeRecovery.active) {
|
|
2902
2911
|
// File was truncated mid-content and we still have continuation
|
|
2903
2912
|
// budget — keep the turn alive instead of bailing on a partial.
|
|
2904
2913
|
// The tool result already carries the "append the rest" hint;
|
|
@@ -2759,10 +2759,17 @@ class BatchEngine:
|
|
|
2759
2759
|
cache_seed.trim_layers(cache_layers, excess)
|
|
2760
2760
|
except Exception: # noqa: BLE001 — snapshot path covers hybrids
|
|
2761
2761
|
pass
|
|
2762
|
+
cache_tokens = full_prompt + [int(t) for t in sub.token_ids]
|
|
2763
|
+
if cache_seed.all_trimmable(cache_layers):
|
|
2764
|
+
cache_tokens = spec_decode.cached_token_prefix(cache_layers, cache_tokens)
|
|
2765
|
+
if cache_tokens is None:
|
|
2766
|
+
# Leave any prior valid entry intact rather than persisting
|
|
2767
|
+
# cache layers whose token boundary cannot be established.
|
|
2768
|
+
cache_layers = None
|
|
2762
2769
|
r = types.SimpleNamespace(
|
|
2763
2770
|
finish_reason=finish_reason,
|
|
2764
|
-
prompt_cache=list(cache_layers),
|
|
2765
|
-
all_tokens=
|
|
2771
|
+
prompt_cache=list(cache_layers) if cache_layers is not None else None,
|
|
2772
|
+
all_tokens=cache_tokens,
|
|
2766
2773
|
token=sub.token_ids[-1] if sub.token_ids else 0,
|
|
2767
2774
|
)
|
|
2768
2775
|
self._finish(sub, r)
|
|
@@ -117,8 +117,11 @@ def resolve_spec(args, model, text_tower: Optional[str], log=print) -> Optional[
|
|
|
117
117
|
except Exception as exc: # noqa: BLE001
|
|
118
118
|
_off(log, f"drafter incompatible with target: {exc}")
|
|
119
119
|
return None
|
|
120
|
-
if
|
|
121
|
-
|
|
120
|
+
if (
|
|
121
|
+
callable(getattr(drafter, "prefill_from_target_hidden", None))
|
|
122
|
+
and not install_chunked_draft_prefill(
|
|
123
|
+
drafter, int(getattr(args, "prefill_step_size", 0) or 0), log=log
|
|
124
|
+
)
|
|
122
125
|
):
|
|
123
126
|
log(
|
|
124
127
|
"[spec] drafter has no incremental prefill surface — long prompts "
|
|
@@ -240,6 +243,11 @@ def draft_prefill_gate(spec: Optional[SpecState], prompt_len: int) -> Tuple[bool
|
|
|
240
243
|
throw that work away."""
|
|
241
244
|
if spec is None:
|
|
242
245
|
return True, ""
|
|
246
|
+
# Shared-KV drafters (e.g. Gemma's assistant) have no separate prompt
|
|
247
|
+
# prefill. Both round loops skip this optional hook, so there is no
|
|
248
|
+
# quadratic drafter prefill to limit for that capability shape.
|
|
249
|
+
if not callable(getattr(spec.drafter, "prefill_from_target_hidden", None)):
|
|
250
|
+
return True, ""
|
|
243
251
|
if getattr(spec.drafter, "_gezel_chunked_prefill", False):
|
|
244
252
|
return True, ""
|
|
245
253
|
if int(prompt_len) > UNCHUNKED_DRAFT_PREFILL_LIMIT:
|
|
@@ -278,9 +286,21 @@ def cache_matches_tower(layers, tower) -> bool:
|
|
|
278
286
|
return False
|
|
279
287
|
if layers is None or len(layers) != len(expect):
|
|
280
288
|
return False
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
|
|
290
|
+
def matches(actual, expected):
|
|
291
|
+
if type(actual).__name__ == type(expected).__name__:
|
|
292
|
+
return True
|
|
293
|
+
# Upstream MTP deliberately wraps a rotating cache in this subclass
|
|
294
|
+
# to roll back rejected drafts. It is still the same target layout;
|
|
295
|
+
# rejecting our own saved cache here forces every follow-up cold.
|
|
296
|
+
# Do not admit arbitrary subclasses (e.g. batch-shaped cache adapters).
|
|
297
|
+
return (
|
|
298
|
+
type(expected).__name__ == "RotatingKVCache"
|
|
299
|
+
and type(actual).__name__ == "BufferedRotatingKVCache"
|
|
300
|
+
and isinstance(actual, type(expected))
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return all(matches(a, b) for a, b in zip(layers, expect))
|
|
284
304
|
|
|
285
305
|
|
|
286
306
|
def clone_layers(layers) -> List[Any]:
|
|
@@ -294,6 +314,25 @@ def clone_layers(layers) -> List[Any]:
|
|
|
294
314
|
return out
|
|
295
315
|
|
|
296
316
|
|
|
317
|
+
def cached_token_prefix(layers, tokens: Sequence[int]) -> Optional[List[int]]:
|
|
318
|
+
"""Describe only tokens actually fed into a trimmable target cache.
|
|
319
|
+
|
|
320
|
+
A final bonus token can be emitted before it is fed back to the target.
|
|
321
|
+
Saving it as cached makes the next seed fail its offset consistency guard.
|
|
322
|
+
Unknown or disagreeing offsets must remain a miss, never guessed state.
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
offsets = [int(c.offset) for c in layers]
|
|
326
|
+
except (AttributeError, TypeError, ValueError):
|
|
327
|
+
return None
|
|
328
|
+
if not offsets or len(set(offsets)) != 1:
|
|
329
|
+
return None
|
|
330
|
+
count = offsets[0]
|
|
331
|
+
if count <= 0 or count > len(tokens):
|
|
332
|
+
return None
|
|
333
|
+
return list(tokens[:count])
|
|
334
|
+
|
|
335
|
+
|
|
297
336
|
def chunked_prefill_steps(
|
|
298
337
|
tower,
|
|
299
338
|
cache,
|
|
@@ -31,7 +31,7 @@ export interface CapacityBrokerOptions {
|
|
|
31
31
|
* tests and for users who want to opt out of the budget entirely.
|
|
32
32
|
*/
|
|
33
33
|
budgetBytes?: number;
|
|
34
|
-
/** Test seam — defaults to {@link
|
|
34
|
+
/** Test seam — defaults to {@link totalMemoryBytes}. */
|
|
35
35
|
systemRamBytes?: () => number;
|
|
36
36
|
/**
|
|
37
37
|
* Whether this host shares one memory pool between CPU and GPU, which
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { execFileSync } from 'node:child_process';
|
|
20
20
|
import { readFileSync } from 'node:fs';
|
|
21
|
-
import { freemem, totalmem } from 'node:os';
|
|
22
21
|
import { CONSTRAINED_LOCAL_CONTEXT_FLOOR_TOKENS, LLAMA_CPP_COMPUTE_HEADROOM, LOCAL_CONTEXT_FLOOR_TOKENS, MLX_COMPUTE_HEADROOM, createLogger, estimateLlamaCppResidentBytes, estimateMlxResidentBytes, localContextFloorTokens, } from '@bendyline/gezel';
|
|
22
|
+
import { capToContainer, freeMemoryBytes, totalMemoryBytes } from '../../system/host-memory.js';
|
|
23
23
|
import { autoAllowRamSpillover, computeCapacityBudget, fastMemoryBudgetBytes, getDetectedGpuVramBytes, } from './capacity-budget.js';
|
|
24
24
|
export { autoAllowRamSpillover, autoDetectBudgetBytes, computeCapacityBudget, getDetectedGpuVramBytes, pressureIdleGraceMs, setDetectedGpuVramBytes, } from './capacity-budget.js';
|
|
25
25
|
export { fastMemoryBudgetBytes };
|
|
@@ -156,7 +156,7 @@ export class CapacityBroker {
|
|
|
156
156
|
explicitAllowRamSpillover;
|
|
157
157
|
reservations = new Map();
|
|
158
158
|
constructor(opts = {}) {
|
|
159
|
-
this.systemRamBytes = (opts.systemRamBytes ??
|
|
159
|
+
this.systemRamBytes = (opts.systemRamBytes ?? totalMemoryBytes)();
|
|
160
160
|
this.unifiedMemory = opts.unifiedMemory;
|
|
161
161
|
this.gpuVramBytes = opts.gpuVramBytes;
|
|
162
162
|
this.reservedGpuBytes = opts.reservedGpuBytes;
|
|
@@ -467,24 +467,24 @@ export function availableSystemRamBytes() {
|
|
|
467
467
|
// vm_stat categories overlap (file-backed pages span active+inactive),
|
|
468
468
|
// so the raw sum can exceed physical RAM; cap at 90% of totalmem — the
|
|
469
469
|
// consumers already hold back their own OS reserves on top.
|
|
470
|
-
const cap =
|
|
470
|
+
const cap = totalMemoryBytes() * 0.9;
|
|
471
471
|
try {
|
|
472
472
|
if (process.platform === 'darwin') {
|
|
473
473
|
const out = execFileSync('/usr/bin/vm_stat', { encoding: 'utf8', timeout: 3_000 });
|
|
474
474
|
const parsed = parseVmStatAvailableBytes(out);
|
|
475
475
|
if (parsed !== null)
|
|
476
|
-
return Math.max(Math.min(parsed, cap),
|
|
476
|
+
return capToContainer(Math.max(Math.min(parsed, cap), freeMemoryBytes()));
|
|
477
477
|
}
|
|
478
478
|
else if (process.platform === 'linux') {
|
|
479
479
|
const parsed = parseMeminfoAvailableBytes(readFileSync('/proc/meminfo', 'utf8'));
|
|
480
480
|
if (parsed !== null)
|
|
481
|
-
return Math.max(Math.min(parsed, cap),
|
|
481
|
+
return capToContainer(Math.max(Math.min(parsed, cap), freeMemoryBytes()));
|
|
482
482
|
}
|
|
483
483
|
}
|
|
484
484
|
catch {
|
|
485
485
|
// Probe failure falls through to the conservative number.
|
|
486
486
|
}
|
|
487
|
-
return
|
|
487
|
+
return freeMemoryBytes();
|
|
488
488
|
}
|
|
489
489
|
/** Pure vm_stat parser (exported for tests). Returns null when the output
|
|
490
490
|
* doesn't look like vm_stat. */
|
|
@@ -664,7 +664,7 @@ export function llamaCppSlotCeiling(opts) {
|
|
|
664
664
|
* going to free 5 GiB, because nothing was holding it but the estimate.
|
|
665
665
|
* Planning and admission must read one number.
|
|
666
666
|
*/
|
|
667
|
-
export function liveRamOsReserveBytes(totalRamBytes =
|
|
667
|
+
export function liveRamOsReserveBytes(totalRamBytes = totalMemoryBytes()) {
|
|
668
668
|
return Math.min(4 * GIB, Math.max(2 * GIB, totalRamBytes * 0.1));
|
|
669
669
|
}
|
|
670
670
|
/**
|
|
@@ -769,7 +769,7 @@ export function minViableLocalContextTokens(opts) {
|
|
|
769
769
|
const override = envMinContextTokens();
|
|
770
770
|
if (override !== null)
|
|
771
771
|
return override;
|
|
772
|
-
const systemRamBytes = opts?.systemRamBytes ??
|
|
772
|
+
const systemRamBytes = opts?.systemRamBytes ?? totalMemoryBytes();
|
|
773
773
|
const budget = opts?.budget ?? computeCapacityBudget({ systemRamBytes });
|
|
774
774
|
return localContextFloorTokens({
|
|
775
775
|
totalRamBytes: systemRamBytes,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { arch, platform
|
|
1
|
+
import { arch, platform } from 'node:os';
|
|
2
|
+
import { totalMemoryBytes } from '../../system/host-memory.js';
|
|
2
3
|
const GIB = 1024 ** 3;
|
|
3
4
|
/**
|
|
4
5
|
* Ceiling on the auto-derived SYSTEM-RAM share, however much RAM the host
|
|
@@ -152,7 +153,7 @@ export function isUnifiedMemoryHost() {
|
|
|
152
153
|
* `GEZEL_CAPACITY_BUDGET_GB` overrides the total, including the setting.
|
|
153
154
|
*/
|
|
154
155
|
export function computeCapacityBudget(input = {}) {
|
|
155
|
-
const systemRamBytes = input.systemRamBytes ??
|
|
156
|
+
const systemRamBytes = input.systemRamBytes ?? totalMemoryBytes();
|
|
156
157
|
// Reading the ambient probe is for callers that know nothing about the
|
|
157
158
|
// host. One that passes `unifiedMemory` has already described it.
|
|
158
159
|
const measuredVram = input.gpuVramBytes !== undefined
|
|
@@ -269,7 +270,7 @@ function envOverrideBytes() {
|
|
|
269
270
|
* (Settings → the engine pages) is the dial for owners who want that traded
|
|
270
271
|
* either way.
|
|
271
272
|
*/
|
|
272
|
-
export function autoDetectBudgetBytes(systemRamBytes =
|
|
273
|
+
export function autoDetectBudgetBytes(systemRamBytes = totalMemoryBytes(), opts = {}) {
|
|
273
274
|
return computeCapacityBudget({ systemRamBytes, ...opts }).budgetBytes;
|
|
274
275
|
}
|
|
275
276
|
/**
|
|
@@ -277,7 +278,7 @@ export function autoDetectBudgetBytes(systemRamBytes = totalmem(), opts = {}) {
|
|
|
277
278
|
* the whole budget otherwise. KV slots, prompt caches, and anything else
|
|
278
279
|
* that must live where the compute happens size against this.
|
|
279
280
|
*/
|
|
280
|
-
export function fastMemoryBudgetBytes(systemRamBytes =
|
|
281
|
+
export function fastMemoryBudgetBytes(systemRamBytes = totalMemoryBytes(), opts = {}) {
|
|
281
282
|
return computeCapacityBudget({ systemRamBytes, ...opts }).fastBytes;
|
|
282
283
|
}
|
|
283
284
|
/** Fast pool at or below which a host is treated as accelerator-constrained. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NativeCapacityCommand, NativeCapacityReply } from '@bendyline/gezel';
|
|
2
|
+
import { readProcessIdentities } from './process-identity.js';
|
|
2
3
|
export interface DeviceCapacitySample {
|
|
3
4
|
budgetBytes: number;
|
|
4
5
|
gpuBudgetBytes: number;
|
|
@@ -25,6 +26,7 @@ export declare class DeviceCapacityLedger {
|
|
|
25
26
|
directory: string;
|
|
26
27
|
sample: () => Promise<DeviceCapacitySample>;
|
|
27
28
|
alive?: (pid: number) => boolean;
|
|
29
|
+
identities?: typeof readProcessIdentities;
|
|
28
30
|
});
|
|
29
31
|
execute(command: NativeCapacityCommand): Promise<NativeCapacityReply>;
|
|
30
32
|
private transact;
|