@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
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
* VRAM and RAM are common on small discrete-GPU machines, so their ratio is
|
|
6
6
|
* not enough to tell a GeForce from a genuinely shared GB10/iGPU pool.
|
|
7
7
|
*/
|
|
8
|
-
export declare function setDetectedGpuVramBytes(bytes: number | null, unifiedMemory?: boolean): void;
|
|
8
|
+
export declare function setDetectedGpuVramBytes(bytes: number | null, unifiedMemory?: boolean, reservedGpuBytes?: number | null, gpuAddressableBytes?: number | null): void;
|
|
9
|
+
/**
|
|
10
|
+
* What engines may hold of memory an integrated GPU owns or can reach — its
|
|
11
|
+
* carve-out, or carve-out plus GTT — after the share the desktop it also
|
|
12
|
+
* drives needs.
|
|
13
|
+
*/
|
|
14
|
+
export declare function usableReservedGpuBytes(reservedGpuBytes: number | null | undefined): number;
|
|
9
15
|
/** The published accelerator-memory figure, or null when none was measured. */
|
|
10
16
|
export declare function getDetectedGpuVramBytes(): number | null;
|
|
11
17
|
/**
|
|
@@ -26,13 +32,19 @@ export interface CapacityBudget {
|
|
|
26
32
|
budgetBytes: number;
|
|
27
33
|
/**
|
|
28
34
|
* The FAST pool inside that budget: usable VRAM on a discrete card, the
|
|
29
|
-
* whole budget on unified / CPU hosts
|
|
35
|
+
* whole budget on unified / CPU hosts — except an integrated GPU that can
|
|
36
|
+
* reach less than that (Linux GTT; see
|
|
37
|
+
* {@link CapacityBudgetInput.gpuAddressableBytes}). Size KV slots and prompt caches
|
|
30
38
|
* against this, not against {@link budgetBytes} — on a discrete card the
|
|
31
39
|
* rest of the budget is system RAM that weights stream from, and putting
|
|
32
40
|
* more KV there than the card holds is how a GPU OOMs.
|
|
33
41
|
*/
|
|
34
42
|
fastBytes: number;
|
|
35
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Usable VRAM on a discrete card. On a unified host, the usable part of an
|
|
45
|
+
* integrated GPU's firmware carve-out — memory the GPU owns that free-RAM
|
|
46
|
+
* readings never include — and 0 when there is none. 0 on CPU-only hosts.
|
|
47
|
+
*/
|
|
36
48
|
vramBytes: number;
|
|
37
49
|
/** The system-RAM share of the budget. */
|
|
38
50
|
ramShareBytes: number;
|
|
@@ -59,6 +71,26 @@ export interface CapacityBudgetInput {
|
|
|
59
71
|
*/
|
|
60
72
|
gpuVramBytes?: number | null;
|
|
61
73
|
unifiedMemory?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Memory firmware sets aside for an integrated GPU (an AMD "UMA frame
|
|
76
|
+
* buffer"): carved out before the OS counts RAM, so it is NOT inside
|
|
77
|
+
* `systemRamBytes`, and only the GPU can use it. Credited on a unified host
|
|
78
|
+
* only — an engine on the CPU build cannot reach it. Without this, raising
|
|
79
|
+
* the carve-out shrank the budget: the bytes left the RAM figure and were
|
|
80
|
+
* counted nowhere. Omit to fall back to the last published probe, under the
|
|
81
|
+
* same rule as `gpuVramBytes`.
|
|
82
|
+
*/
|
|
83
|
+
reservedGpuBytes?: number | null;
|
|
84
|
+
/**
|
|
85
|
+
* The most memory an integrated GPU can reach: on Linux amdgpu, its
|
|
86
|
+
* carve-out plus GTT, the window of system RAM the kernel lets it map
|
|
87
|
+
* (half of RAM by default). Caps the FAST pool on a unified host — the GPU
|
|
88
|
+
* cannot hold more KV or weights than this, whatever the RAM share says —
|
|
89
|
+
* while the admission budget keeps the rest for layers the CPU runs. Omit
|
|
90
|
+
* to fall back to the last published probe, under the same rule as
|
|
91
|
+
* `gpuVramBytes`.
|
|
92
|
+
*/
|
|
93
|
+
gpuAddressableBytes?: number | null;
|
|
62
94
|
}
|
|
63
95
|
/**
|
|
64
96
|
* Resolve the memory a host can give resident local engines, per pool.
|
|
@@ -67,6 +67,13 @@ const VRAM_USABLE_FRACTION = 0.95;
|
|
|
67
67
|
* bytes, so those hosts take the unified curve instead.
|
|
68
68
|
*/
|
|
69
69
|
const UNIFIED_VRAM_RATIO = 0.75;
|
|
70
|
+
/**
|
|
71
|
+
* Held back from an integrated GPU's firmware carve-out (see
|
|
72
|
+
* {@link CapacityBudgetInput.reservedGpuBytes}) for the desktop it also
|
|
73
|
+
* drives. The discrete card's 5% margin is too thin here: on a 512 MiB
|
|
74
|
+
* carve-out it would leave the compositor 25 MiB.
|
|
75
|
+
*/
|
|
76
|
+
const CARVE_OUT_DISPLAY_RESERVE_BYTES = 512 * 1024 ** 2;
|
|
70
77
|
/**
|
|
71
78
|
* Last accelerator-memory figure `detectMemoryProfile` measured on this host,
|
|
72
79
|
* or null when nothing has probed yet / no GPU memory figure was published.
|
|
@@ -81,6 +88,8 @@ const UNIFIED_VRAM_RATIO = 0.75;
|
|
|
81
88
|
*/
|
|
82
89
|
let detectedGpuVramBytes = null;
|
|
83
90
|
let detectedGpuUnifiedMemory;
|
|
91
|
+
let detectedGpuReservedBytes = null;
|
|
92
|
+
let detectedGpuAddressableBytes = null;
|
|
84
93
|
/**
|
|
85
94
|
* Publish the measured accelerator pool. Called by `detectMemoryProfile`.
|
|
86
95
|
*
|
|
@@ -88,10 +97,26 @@ let detectedGpuUnifiedMemory;
|
|
|
88
97
|
* VRAM and RAM are common on small discrete-GPU machines, so their ratio is
|
|
89
98
|
* not enough to tell a GeForce from a genuinely shared GB10/iGPU pool.
|
|
90
99
|
*/
|
|
91
|
-
export function setDetectedGpuVramBytes(bytes, unifiedMemory) {
|
|
92
|
-
detectedGpuVramBytes =
|
|
93
|
-
typeof bytes === 'number' && Number.isFinite(bytes) && bytes > 0 ? bytes : null;
|
|
100
|
+
export function setDetectedGpuVramBytes(bytes, unifiedMemory, reservedGpuBytes, gpuAddressableBytes) {
|
|
101
|
+
detectedGpuVramBytes = positiveOrNull(bytes);
|
|
94
102
|
detectedGpuUnifiedMemory = unifiedMemory;
|
|
103
|
+
detectedGpuReservedBytes = positiveOrNull(reservedGpuBytes);
|
|
104
|
+
detectedGpuAddressableBytes = positiveOrNull(gpuAddressableBytes);
|
|
105
|
+
}
|
|
106
|
+
function positiveOrNull(bytes) {
|
|
107
|
+
return typeof bytes === 'number' && Number.isFinite(bytes) && bytes > 0 ? bytes : null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What engines may hold of memory an integrated GPU owns or can reach — its
|
|
111
|
+
* carve-out, or carve-out plus GTT — after the share the desktop it also
|
|
112
|
+
* drives needs.
|
|
113
|
+
*/
|
|
114
|
+
export function usableReservedGpuBytes(reservedGpuBytes) {
|
|
115
|
+
const reserved = positiveOrNull(reservedGpuBytes);
|
|
116
|
+
if (reserved === null)
|
|
117
|
+
return 0;
|
|
118
|
+
const display = Math.max(CARVE_OUT_DISPLAY_RESERVE_BYTES, Math.ceil(reserved * (1 - VRAM_USABLE_FRACTION)));
|
|
119
|
+
return Math.max(0, reserved - display);
|
|
95
120
|
}
|
|
96
121
|
/** The published accelerator-memory figure, or null when none was measured. */
|
|
97
122
|
export function getDetectedGpuVramBytes() {
|
|
@@ -144,6 +169,16 @@ export function computeCapacityBudget(input = {}) {
|
|
|
144
169
|
const sharedPool = hasCard && measuredVram >= systemRamBytes * UNIFIED_VRAM_RATIO;
|
|
145
170
|
const unified = measuredUnifiedMemory ?? (isUnifiedMemoryHost() || sharedPool);
|
|
146
171
|
const discrete = hasCard && !unified;
|
|
172
|
+
const reservedGpuBytes = input.reservedGpuBytes !== undefined
|
|
173
|
+
? input.reservedGpuBytes
|
|
174
|
+
: input.unifiedMemory === undefined && input.gpuVramBytes === undefined
|
|
175
|
+
? detectedGpuReservedBytes
|
|
176
|
+
: null;
|
|
177
|
+
const gpuAddressableBytes = input.gpuAddressableBytes !== undefined
|
|
178
|
+
? input.gpuAddressableBytes
|
|
179
|
+
: input.unifiedMemory === undefined && input.gpuVramBytes === undefined
|
|
180
|
+
? detectedGpuAddressableBytes
|
|
181
|
+
: null;
|
|
147
182
|
const highMemory = systemRamBytes >= HIGH_MEMORY_THRESHOLD_BYTES;
|
|
148
183
|
// Whichever bound leaves the host MORE memory wins between the fraction and
|
|
149
184
|
// the capped reserve, then the 4 GiB floor reserve caps them both. Written
|
|
@@ -153,7 +188,11 @@ export function computeCapacityBudget(input = {}) {
|
|
|
153
188
|
const unifiedShare = Math.max(0, Math.min(Math.max(unifiedFractionShare, systemRamBytes - UNIFIED_OS_RESERVE_MAX_BYTES), systemRamBytes - UNIFIED_OS_RESERVE_BYTES));
|
|
154
189
|
const nonUnifiedShare = Math.min(Math.floor(systemRamBytes * (highMemory ? 0.8 : 0.6)), BUDGET_CAP_BYTES);
|
|
155
190
|
const ramShareBytes = unified ? unifiedShare : nonUnifiedShare;
|
|
156
|
-
const vramBytes = discrete
|
|
191
|
+
const vramBytes = discrete
|
|
192
|
+
? Math.floor(measuredVram * VRAM_USABLE_FRACTION)
|
|
193
|
+
: unified
|
|
194
|
+
? usableReservedGpuBytes(reservedGpuBytes)
|
|
195
|
+
: 0;
|
|
157
196
|
// The pre-cap curve, retained for slot sizing only.
|
|
158
197
|
const concurrencyRamShare = unified
|
|
159
198
|
? Math.max(0, Math.min(unifiedFractionShare, systemRamBytes - UNIFIED_OS_RESERVE_BYTES, BUDGET_CAP_BYTES))
|
|
@@ -175,7 +214,12 @@ export function computeCapacityBudget(input = {}) {
|
|
|
175
214
|
overriddenByEnv: true,
|
|
176
215
|
};
|
|
177
216
|
}
|
|
178
|
-
const
|
|
217
|
+
const addressable = positiveOrNull(gpuAddressableBytes);
|
|
218
|
+
const fastBytes = discrete
|
|
219
|
+
? vramBytes
|
|
220
|
+
: unified && addressable !== null
|
|
221
|
+
? Math.min(auto, usableReservedGpuBytes(addressable))
|
|
222
|
+
: auto;
|
|
179
223
|
return {
|
|
180
224
|
budgetBytes: auto,
|
|
181
225
|
fastBytes,
|
|
@@ -73,7 +73,7 @@ export declare function configuredEngineMemoryBytes(config: {
|
|
|
73
73
|
* download pages hide is exactly one the launch path refuses. Omit
|
|
74
74
|
* `configuredBytes` for the machine's automatic ceilings.
|
|
75
75
|
*/
|
|
76
|
-
export declare function staticCapacityCeilings(budget: Pick<CapacityBudget, 'budgetBytes' | 'fastBytes' | 'kind'>, ramBytes: number, configuredBytes?: number): {
|
|
76
|
+
export declare function staticCapacityCeilings(budget: Pick<CapacityBudget, 'budgetBytes' | 'fastBytes' | 'kind' | 'vramBytes'>, ramBytes: number, configuredBytes?: number, platform?: NodeJS.Platform): {
|
|
77
77
|
budgetBytes: number;
|
|
78
78
|
gpuBudgetBytes: number;
|
|
79
79
|
};
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { homedir, totalmem } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { GEZEL_VERSION, NativeCapacityCommandSchema, NativeCapacityReplySchema, awakeNow, createLogger, isUnstampedDevBuild, } from '@bendyline/gezel';
|
|
5
|
+
import { DS4_FULL_RESIDENCY_HEADROOM_BYTES, GEZEL_VERSION, NativeCapacityCommandSchema, NativeCapacityReplySchema, awakeNow, createLogger, isUnstampedDevBuild, } from '@bendyline/gezel';
|
|
6
6
|
import { readSystemServiceRuntime, systemServiceHome } from '@bendyline/gezel-client/node';
|
|
7
7
|
import { ConfigStore } from '../../fs/config-store.js';
|
|
8
8
|
import { createPinnedFetch } from '../../remotes/pinned-fetch.js';
|
|
@@ -119,21 +119,39 @@ export function configuredEngineMemoryBytes(config) {
|
|
|
119
119
|
? config.localEngineMemoryGb * GIB
|
|
120
120
|
: undefined;
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* GPU bytes a unified host may commit to one launch. macOS keeps 0.75 × RAM,
|
|
124
|
+
* near where Metal's recommended GPU working set sits. Linux unified hosts
|
|
125
|
+
* (DGX Spark GB10) have no such limit, so a large one may commit all but the
|
|
126
|
+
* OS reserve ds4's residency gate keeps: on a 121.6 GiB Spark, 0.75 × RAM
|
|
127
|
+
* (91.2 GiB) refused a GLM 5.3 Q2 launch that runs at 105 GiB with ~9 GiB to
|
|
128
|
+
* spare. Smaller Linux hosts stay on the 0.75 curve, which is the larger one there.
|
|
129
|
+
*/
|
|
130
|
+
function unifiedGpuCeilingBytes(ramBytes, platform) {
|
|
131
|
+
const curve = ramBytes * 0.75;
|
|
132
|
+
return platform === 'linux'
|
|
133
|
+
? Math.max(curve, ramBytes - DS4_FULL_RESIDENCY_HEADROOM_BYTES)
|
|
134
|
+
: curve;
|
|
135
|
+
}
|
|
122
136
|
/**
|
|
123
137
|
* The ledger's static ceilings: a request above either is refused at acquire,
|
|
124
138
|
* whatever else is running. Shared with the media fit check so a model the
|
|
125
139
|
* download pages hide is exactly one the launch path refuses. Omit
|
|
126
140
|
* `configuredBytes` for the machine's automatic ceilings.
|
|
127
141
|
*/
|
|
128
|
-
export function staticCapacityCeilings(budget, ramBytes, configuredBytes) {
|
|
142
|
+
export function staticCapacityCeilings(budget, ramBytes, configuredBytes, platform = process.platform) {
|
|
129
143
|
const budgetBytes = configuredBytes === undefined
|
|
130
144
|
? budget.budgetBytes
|
|
131
145
|
: Math.min(configuredBytes, budget.budgetBytes);
|
|
132
146
|
return {
|
|
133
147
|
budgetBytes,
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
148
|
+
// Non-reclaimable accelerator commitments get their own ceiling: a larger
|
|
149
|
+
// RAM budget is not permission to wire the whole machine. An integrated
|
|
150
|
+
// GPU's carve-out (`vramBytes` on a unified host) is already the GPU's,
|
|
151
|
+
// and sits outside `ramBytes`.
|
|
152
|
+
gpuBudgetBytes: Math.min(budgetBytes, budget.kind === 'unified'
|
|
153
|
+
? Math.min(budget.fastBytes, unifiedGpuCeilingBytes(ramBytes, platform) + budget.vramBytes)
|
|
154
|
+
: budget.fastBytes),
|
|
137
155
|
};
|
|
138
156
|
}
|
|
139
157
|
export async function sampleDeviceCapacity(home) {
|
|
@@ -260,6 +260,8 @@ export class LocalEngineRuntime {
|
|
|
260
260
|
? {
|
|
261
261
|
unifiedMemory: memoryProfile.gpuMemoryKind === 'integrated' ||
|
|
262
262
|
memoryProfile.gpuMemoryKind === 'unified',
|
|
263
|
+
reservedGpuBytes: memoryProfile.gpuReservedBytes ?? null,
|
|
264
|
+
gpuAddressableBytes: memoryProfile.gpuAddressableBytes ?? null,
|
|
263
265
|
}
|
|
264
266
|
: {}),
|
|
265
267
|
allowRamSpillover: config.allowRamSpillover ?? null,
|
|
@@ -1481,22 +1483,18 @@ export class LocalEngineRuntime {
|
|
|
1481
1483
|
vramBytes: capacity.vramBytes,
|
|
1482
1484
|
});
|
|
1483
1485
|
// Mirror the launch path's windowed-cache admission (see
|
|
1484
|
-
// buildLlamaCppProvider):
|
|
1485
|
-
//
|
|
1486
|
-
// full-attention plan above overstates
|
|
1487
|
-
//
|
|
1488
|
-
//
|
|
1486
|
+
// buildLlamaCppProvider): unless `swaFull` is pinned, a
|
|
1487
|
+
// sliding-window model runs the windowed cache, which the
|
|
1488
|
+
// full-attention plan above overstates — re-plan with the windowed
|
|
1489
|
+
// linearization so the previewed window matches what the engine will
|
|
1490
|
+
// actually grant.
|
|
1489
1491
|
// Strict model-max deliberately does NOT gate this: for SWA models
|
|
1490
1492
|
// the windowed cache is the only layout whose native-window KV can
|
|
1491
1493
|
// fit real machines, and the strict minimum rides inside
|
|
1492
1494
|
// `requirement.minimumPerTurnCtxTokens`, so the windowed
|
|
1493
1495
|
// re-plan sheds slots or denies but never shortens the window.
|
|
1494
1496
|
const explicitSwaFull = config.llamaCppSwaFull ?? manifestEngineConfig?.swaFull;
|
|
1495
|
-
|
|
1496
|
-
(explicitSwaFull === false ||
|
|
1497
|
-
(explicitSwaFull === undefined &&
|
|
1498
|
-
isGemmaModel({ architecture: installed.architecture, modelId })));
|
|
1499
|
-
if (windowedCacheWillRun) {
|
|
1497
|
+
if (explicitSwaFull !== true) {
|
|
1500
1498
|
const windowed = estimateWindowedKvLinearization({
|
|
1501
1499
|
blockCount: summary.blockCount,
|
|
1502
1500
|
embeddingLength: summary.embeddingLength,
|
|
@@ -1536,9 +1534,13 @@ export class LocalEngineRuntime {
|
|
|
1536
1534
|
fixedPerSlotBytes: windowed.fixedBytes,
|
|
1537
1535
|
};
|
|
1538
1536
|
}
|
|
1539
|
-
else if ((
|
|
1540
|
-
|
|
1541
|
-
|
|
1537
|
+
else if ((!admission.minimumSatisfied || admission.clamped || admission.slots < slots) &&
|
|
1538
|
+
(explicitSwaFull === false
|
|
1539
|
+
? (summary.slidingWindow ?? 0) > 0
|
|
1540
|
+
: isGemmaModel({ architecture: installed.architecture, modelId }))) {
|
|
1541
|
+
// SWA model without a readable layout, over budget on full
|
|
1542
|
+
// math: the launch path leaves such a launch untouched — preview
|
|
1543
|
+
// the requested window.
|
|
1542
1544
|
return { grantedCtx, slots, kvCacheType: kv, plannedFieldsOk: false };
|
|
1543
1545
|
}
|
|
1544
1546
|
}
|
|
@@ -50,5 +50,7 @@ export async function measuredCapacityBudget() {
|
|
|
50
50
|
systemRamBytes: profile.totalRamBytes,
|
|
51
51
|
gpuVramBytes: profile.gpuVramBytes,
|
|
52
52
|
unifiedMemory: profile.gpuMemoryKind === 'integrated' || profile.gpuMemoryKind === 'unified',
|
|
53
|
+
reservedGpuBytes: profile.gpuReservedBytes ?? null,
|
|
54
|
+
gpuAddressableBytes: profile.gpuAddressableBytes ?? null,
|
|
53
55
|
});
|
|
54
56
|
}
|
|
@@ -158,6 +158,11 @@ export interface NativeEngineSupervisorOptions {
|
|
|
158
158
|
onLog?: (line: string) => void;
|
|
159
159
|
/** Called for both expected and unexpected exits after a snapshot is sealed. */
|
|
160
160
|
onExit?: (snapshot: NativeEngineExitSnapshot) => void | Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Called each time a start reaches readiness. Errors are caught and logged;
|
|
163
|
+
* a failing hook never fails the start.
|
|
164
|
+
*/
|
|
165
|
+
onReady?: (launch: NativeEngineLaunch) => void;
|
|
161
166
|
/**
|
|
162
167
|
* Consulted after a start attempt fails (child died or never became
|
|
163
168
|
* ready — not spawn errors, and not crashes after readiness). Return
|
|
@@ -214,6 +219,13 @@ export interface NativeEngineSupervisorOptions {
|
|
|
214
219
|
psRunner?: () => Promise<NativeProcessSnapshot[]>;
|
|
215
220
|
/** Platform seam for Windows owner-liveness semantics in orphan tests. */
|
|
216
221
|
platform?: NodeJS.Platform;
|
|
222
|
+
/**
|
|
223
|
+
* The kernel's running count of out-of-memory kills, or null when it
|
|
224
|
+
* cannot be read. Compared across a child's life to tell Linux's
|
|
225
|
+
* out-of-memory killer from any other SIGKILL. Defaults to `oom_kill` in
|
|
226
|
+
* `/proc/vmstat` on Linux (any account can read it) and null elsewhere.
|
|
227
|
+
*/
|
|
228
|
+
readOomKillCount?: () => number | null;
|
|
217
229
|
/**
|
|
218
230
|
* Test seam paired with `psRunner` — invoked instead of
|
|
219
231
|
* `process.kill(pid, 'SIGKILL')`. Lets unit tests assert which pids
|
|
@@ -263,6 +275,7 @@ export declare class NativeEngineSupervisor {
|
|
|
263
275
|
private readonly fetchImpl;
|
|
264
276
|
private readonly onLog;
|
|
265
277
|
private readonly onExit?;
|
|
278
|
+
private readonly onReady?;
|
|
266
279
|
private readonly recoverStartup?;
|
|
267
280
|
private readonly onRawLine?;
|
|
268
281
|
private readonly logPrefix;
|
|
@@ -271,6 +284,9 @@ export declare class NativeEngineSupervisor {
|
|
|
271
284
|
private readonly psRunner;
|
|
272
285
|
private readonly killProcess;
|
|
273
286
|
private readonly platform;
|
|
287
|
+
private readonly readOomKillCount;
|
|
288
|
+
/** `oom_kill` when the current child spawned; null where it cannot be read. */
|
|
289
|
+
private currentOomKills;
|
|
274
290
|
private readonly logListeners;
|
|
275
291
|
private recentStarts;
|
|
276
292
|
private lastUsedAt;
|
|
@@ -482,6 +498,13 @@ export declare class NativeEngineSupervisor {
|
|
|
482
498
|
*/
|
|
483
499
|
private runFreeze;
|
|
484
500
|
private clearIdleTimer;
|
|
501
|
+
/**
|
|
502
|
+
* The counter's movement since `before`, when it rose. Another process
|
|
503
|
+
* killed in the same window would also move it, but a child that died by a
|
|
504
|
+
* SIGKILL this supervisor did not send while the counter rose is, in
|
|
505
|
+
* practice, the one the killer chose.
|
|
506
|
+
*/
|
|
507
|
+
private oomKillsSince;
|
|
485
508
|
private clearFreezeTimer;
|
|
486
509
|
private clearHealthTimer;
|
|
487
510
|
}
|
|
@@ -490,6 +513,8 @@ export declare function classifyNativeEnginePanic(line: string): {
|
|
|
490
513
|
kind: NativeEnginePanicKind;
|
|
491
514
|
line: string;
|
|
492
515
|
} | undefined;
|
|
516
|
+
/** `oom_kill` from `/proc/vmstat` (Linux 4.13+), or null when it cannot be read. */
|
|
517
|
+
export declare function readLinuxOomKillCount(): number | null;
|
|
493
518
|
/** Parse the portable `ps -axo pid=,ppid=,command=` shape. */
|
|
494
519
|
export declare function parseNativeProcessSnapshot(stdout: string): NativeProcessSnapshot[];
|
|
495
520
|
/** `code` stamped on every engine-launch failure. See {@link isEngineLaunchError}. */
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* so we can drive the lifecycle without a real binary.
|
|
22
22
|
*/
|
|
23
23
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
24
25
|
import { basename } from 'node:path';
|
|
25
26
|
import { awakeNow, createLogger } from '@bendyline/gezel';
|
|
26
27
|
import { listProcessSnapshots } from '@bendyline/gezel-client/node';
|
|
@@ -179,6 +180,7 @@ export class NativeEngineSupervisor {
|
|
|
179
180
|
fetchImpl;
|
|
180
181
|
onLog;
|
|
181
182
|
onExit;
|
|
183
|
+
onReady;
|
|
182
184
|
recoverStartup;
|
|
183
185
|
onRawLine;
|
|
184
186
|
logPrefix;
|
|
@@ -187,6 +189,9 @@ export class NativeEngineSupervisor {
|
|
|
187
189
|
psRunner;
|
|
188
190
|
killProcess;
|
|
189
191
|
platform;
|
|
192
|
+
readOomKillCount;
|
|
193
|
+
/** `oom_kill` when the current child spawned; null where it cannot be read. */
|
|
194
|
+
currentOomKills = null;
|
|
190
195
|
// Ad-hoc log listeners attached at runtime (vs. the construction-time
|
|
191
196
|
// `onLog` / `onRawLine` hooks). Used by the owning provider to
|
|
192
197
|
// subscribe to engine log lines for the duration of a single request
|
|
@@ -274,6 +279,8 @@ export class NativeEngineSupervisor {
|
|
|
274
279
|
this.onLog = opts.onLog ?? ((line) => log.info(line));
|
|
275
280
|
if (opts.onExit)
|
|
276
281
|
this.onExit = opts.onExit;
|
|
282
|
+
if (opts.onReady)
|
|
283
|
+
this.onReady = opts.onReady;
|
|
277
284
|
if (opts.recoverStartup)
|
|
278
285
|
this.recoverStartup = opts.recoverStartup;
|
|
279
286
|
if (opts.onRawLine)
|
|
@@ -282,6 +289,8 @@ export class NativeEngineSupervisor {
|
|
|
282
289
|
this.readinessPath = opts.readinessPath ?? '/health';
|
|
283
290
|
this.readyOnAnyResponse = opts.readyOnAnyResponse ?? false;
|
|
284
291
|
this.platform = opts.platform ?? process.platform;
|
|
292
|
+
this.readOomKillCount =
|
|
293
|
+
opts.readOomKillCount ?? (this.platform === 'linux' ? readLinuxOomKillCount : () => null);
|
|
285
294
|
this.psRunner = opts.psRunner ?? (() => defaultPsRunner(this.platform));
|
|
286
295
|
this.killProcess = opts.killProcess ?? ((pid, sig) => process.kill(pid, sig));
|
|
287
296
|
}
|
|
@@ -600,6 +609,7 @@ export class NativeEngineSupervisor {
|
|
|
600
609
|
this.currentDiagnostics = launch.diagnostics ? { ...launch.diagnostics } : undefined;
|
|
601
610
|
this.currentOutput = '';
|
|
602
611
|
this.currentPanic = undefined;
|
|
612
|
+
this.currentOomKills = this.readOomKillCount();
|
|
603
613
|
this.expectedExitReason = undefined;
|
|
604
614
|
this.onLog(`${this.logPrefix} launch ${JSON.stringify({
|
|
605
615
|
pid: this.currentPid ?? null,
|
|
@@ -698,6 +708,12 @@ export class NativeEngineSupervisor {
|
|
|
698
708
|
this.state = { kind: 'running', launch, child, healthFails: 0 };
|
|
699
709
|
this.resetIdleTimer();
|
|
700
710
|
this.startHealthWatch();
|
|
711
|
+
try {
|
|
712
|
+
this.onReady?.(launch);
|
|
713
|
+
}
|
|
714
|
+
catch (hookErr) {
|
|
715
|
+
this.onLog(`${this.logPrefix} onReady hook threw: ${hookErr instanceof Error ? hookErr.message : hookErr}`);
|
|
716
|
+
}
|
|
701
717
|
}
|
|
702
718
|
catch (err) {
|
|
703
719
|
// The panic classifier state is cleared by handleExit — read it now,
|
|
@@ -998,13 +1014,23 @@ export class NativeEngineSupervisor {
|
|
|
998
1014
|
// the output didn't already explain the death more precisely: an
|
|
999
1015
|
// engine that printed `CUDA error: …` and then took SIGILL on the
|
|
1000
1016
|
// way down is a CUDA fault, not an unrunnable build.
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1017
|
+
// An out-of-memory kill outranks anything the engine printed: a CUDA or
|
|
1018
|
+
// Vulkan allocation line on the way there is a symptom of the same
|
|
1019
|
+
// shortage, and the GPU-to-RAM ladder those lines trigger cannot help
|
|
1020
|
+
// when RAM is what ran out.
|
|
1021
|
+
const oomKilled = signal === 'SIGKILL' && !expected ? this.oomKillsSince(this.currentOomKills) : null;
|
|
1022
|
+
const panic = oomKilled
|
|
1023
|
+
? {
|
|
1024
|
+
kind: 'host-out-of-memory',
|
|
1025
|
+
line: `engine killed by the Linux out-of-memory killer (oom_kill ${oomKilled.before} -> ${oomKilled.after})`,
|
|
1026
|
+
}
|
|
1027
|
+
: (this.currentPanic ??
|
|
1028
|
+
(signal === 'SIGILL'
|
|
1029
|
+
? {
|
|
1030
|
+
kind: 'illegal-instruction',
|
|
1031
|
+
line: 'engine terminated by SIGILL (illegal instruction) with no diagnostic output',
|
|
1032
|
+
}
|
|
1033
|
+
: undefined));
|
|
1008
1034
|
const snapshot = {
|
|
1009
1035
|
incidentId: `native-${pid ?? 'unknown'}-${exitedAt}`,
|
|
1010
1036
|
...(pid !== undefined ? { pid } : {}),
|
|
@@ -1062,6 +1088,7 @@ export class NativeEngineSupervisor {
|
|
|
1062
1088
|
this.currentDiagnostics = undefined;
|
|
1063
1089
|
this.currentOutput = '';
|
|
1064
1090
|
this.currentPanic = undefined;
|
|
1091
|
+
this.currentOomKills = null;
|
|
1065
1092
|
this.expectedExitReason = undefined;
|
|
1066
1093
|
if (priorState.kind === 'stopped')
|
|
1067
1094
|
return;
|
|
@@ -1313,6 +1340,18 @@ export class NativeEngineSupervisor {
|
|
|
1313
1340
|
this.idleTimer = undefined;
|
|
1314
1341
|
}
|
|
1315
1342
|
}
|
|
1343
|
+
/**
|
|
1344
|
+
* The counter's movement since `before`, when it rose. Another process
|
|
1345
|
+
* killed in the same window would also move it, but a child that died by a
|
|
1346
|
+
* SIGKILL this supervisor did not send while the counter rose is, in
|
|
1347
|
+
* practice, the one the killer chose.
|
|
1348
|
+
*/
|
|
1349
|
+
oomKillsSince(before) {
|
|
1350
|
+
if (before === null)
|
|
1351
|
+
return null;
|
|
1352
|
+
const after = this.readOomKillCount();
|
|
1353
|
+
return after !== null && after > before ? { before, after } : null;
|
|
1354
|
+
}
|
|
1316
1355
|
clearFreezeTimer() {
|
|
1317
1356
|
if (this.freezeTimer) {
|
|
1318
1357
|
clearTimeout(this.freezeTimer);
|
|
@@ -1346,6 +1385,9 @@ export function classifyNativeEnginePanic(line) {
|
|
|
1346
1385
|
if (/CUDA error:.*device-side assert/i.test(line)) {
|
|
1347
1386
|
return { kind: 'cuda-device-assert', line: clipped };
|
|
1348
1387
|
}
|
|
1388
|
+
if (/CUDA error:\s*(?:no kernel image is available|the provided PTX was compiled with an unsupported toolchain)/i.test(line)) {
|
|
1389
|
+
return { kind: 'cuda-unsupported-device', line: clipped };
|
|
1390
|
+
}
|
|
1349
1391
|
if (/CUDA error:/i.test(line))
|
|
1350
1392
|
return { kind: 'cuda-error', line: clipped };
|
|
1351
1393
|
if (/assert(?:ion)? failed|GGML_ASSERT/i.test(line)) {
|
|
@@ -1353,6 +1395,16 @@ export function classifyNativeEnginePanic(line) {
|
|
|
1353
1395
|
}
|
|
1354
1396
|
return undefined;
|
|
1355
1397
|
}
|
|
1398
|
+
/** `oom_kill` from `/proc/vmstat` (Linux 4.13+), or null when it cannot be read. */
|
|
1399
|
+
export function readLinuxOomKillCount() {
|
|
1400
|
+
try {
|
|
1401
|
+
const match = /^oom_kill (\d+)$/m.exec(readFileSync('/proc/vmstat', 'utf8'));
|
|
1402
|
+
return match?.[1] ? Number(match[1]) : null;
|
|
1403
|
+
}
|
|
1404
|
+
catch {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1356
1408
|
function panicPriority(kind) {
|
|
1357
1409
|
if (kind === 'cuda-error')
|
|
1358
1410
|
return 1;
|
|
@@ -73,6 +73,7 @@ export declare const IdentityResponseSchema: z.ZodObject<{
|
|
|
73
73
|
"machine-engine": "machine-engine";
|
|
74
74
|
"legacy-full": "legacy-full";
|
|
75
75
|
}>>;
|
|
76
|
+
pairingMethods: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
77
|
}, z.core.$strip>;
|
|
77
78
|
export type IdentityResponse = z.infer<typeof IdentityResponseSchema>;
|
|
78
79
|
/** Hex SHA-256 of a PEM-encoded X.509 cert's DER bytes. */
|
package/dist/remotes/identity.js
CHANGED
|
@@ -70,6 +70,12 @@ export const IdentityResponseSchema = z.object({
|
|
|
70
70
|
gezelVersion: z.string().optional(),
|
|
71
71
|
protocolVersion: z.number().optional(),
|
|
72
72
|
serviceRole: z.enum(['user', 'machine-engine', 'legacy-full']).optional(),
|
|
73
|
+
/**
|
|
74
|
+
* How a client may pair: `code` (a connect code shown on this device,
|
|
75
|
+
* SPAKE2 over `/v1/apps/pair/*`) and/or `approval` (`/v1/apps/register`,
|
|
76
|
+
* approved here). Absent → a server from before connect codes: approval only.
|
|
77
|
+
*/
|
|
78
|
+
pairingMethods: z.array(z.string()).max(8).optional(),
|
|
73
79
|
});
|
|
74
80
|
/** Hex SHA-256 of a PEM-encoded X.509 cert's DER bytes. */
|
|
75
81
|
export function certFingerprintFromPem(certPem) {
|
|
@@ -17,6 +17,8 @@ import type { PairedRemote, RemotesRegistry } from './types.js';
|
|
|
17
17
|
export type ClosableFetch = typeof fetch & {
|
|
18
18
|
close(): Promise<void>;
|
|
19
19
|
};
|
|
20
|
+
/** Connect timeout for a paired peer (see {@link createPinnedFetch}). */
|
|
21
|
+
export declare const PINNED_CONNECT_TIMEOUT_MS = 5000;
|
|
20
22
|
/**
|
|
21
23
|
* Pin a single self-signed cert as the sole CA and skip hostname/SAN
|
|
22
24
|
* verification. Long-call-friendly (no header/body timeouts) for streamed
|
|
@@ -15,6 +15,29 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Agent, fetch as undiciFetch } from 'undici';
|
|
17
17
|
import { IdentityResponseSchema, certFingerprintFromPem, fingerprintOfPublicKeyPem, verifyIdentitySignature, } from './identity.js';
|
|
18
|
+
/** Connect timeout for a paired peer (see {@link createPinnedFetch}). */
|
|
19
|
+
export const PINNED_CONNECT_TIMEOUT_MS = 5_000;
|
|
20
|
+
/** Failures before any TLS handshake: no certificate was presented, so none can be stale. */
|
|
21
|
+
const NO_HANDSHAKE_CODES = new Set([
|
|
22
|
+
'ECONNREFUSED',
|
|
23
|
+
'EHOSTUNREACH',
|
|
24
|
+
'ENETUNREACH',
|
|
25
|
+
'EHOSTDOWN',
|
|
26
|
+
'ENETDOWN',
|
|
27
|
+
'ETIMEDOUT',
|
|
28
|
+
'EADDRNOTAVAIL',
|
|
29
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
30
|
+
]);
|
|
31
|
+
function failedBeforeHandshake(err) {
|
|
32
|
+
let current = err;
|
|
33
|
+
for (let depth = 0; depth < 6 && current && typeof current === 'object'; depth++) {
|
|
34
|
+
const code = current.code;
|
|
35
|
+
if (typeof code === 'string' && NO_HANDSHAKE_CODES.has(code))
|
|
36
|
+
return true;
|
|
37
|
+
current = current.cause;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
18
41
|
async function closeFetch(fetchImpl) {
|
|
19
42
|
const close = fetchImpl?.close;
|
|
20
43
|
if (typeof close === 'function')
|
|
@@ -34,6 +57,10 @@ export function createPinnedFetch(certPem) {
|
|
|
34
57
|
// IP; the cert identity itself is the pin, reinforced by the identity
|
|
35
58
|
// signature verified at connect time.
|
|
36
59
|
checkServerIdentity: () => undefined,
|
|
60
|
+
// A peer on the local network answers a connect within milliseconds.
|
|
61
|
+
// Waiting undici's default 10 s for one that left the network only
|
|
62
|
+
// delays the fallback; streamed replies keep their unlimited timeouts.
|
|
63
|
+
timeout: PINNED_CONNECT_TIMEOUT_MS,
|
|
37
64
|
},
|
|
38
65
|
allowH2: true,
|
|
39
66
|
headersTimeout: 0,
|
|
@@ -100,7 +127,9 @@ export function createPairedRemoteFetch(remote, registry, deps = {}) {
|
|
|
100
127
|
return await active(url, init);
|
|
101
128
|
}
|
|
102
129
|
catch (originalError) {
|
|
103
|
-
|
|
130
|
+
// A refresh re-dials the same address; when that address answered
|
|
131
|
+
// nothing at all it doubles the wait and fails the same way.
|
|
132
|
+
if (init?.signal?.aborted || failedBeforeHandshake(originalError))
|
|
104
133
|
throw originalError;
|
|
105
134
|
refresh ??= refreshPairedCertificate(remote, registry, makeBootstrap).then((cert) => {
|
|
106
135
|
const previous = active;
|
package/dist/remotes/types.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export interface RemoteKnownModel {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
contextWindow?: number;
|
|
5
|
+
supportsTools?: boolean;
|
|
6
|
+
supportsReasoning?: boolean;
|
|
7
|
+
parameterSize?: string;
|
|
8
|
+
}
|
|
1
9
|
export interface PairedRemote {
|
|
2
10
|
/** Stable id A assigns to this server = its full identity fingerprint. */
|
|
3
11
|
remoteId: string;
|
|
@@ -29,6 +37,12 @@ export interface PairedRemote {
|
|
|
29
37
|
pairedAt: number;
|
|
30
38
|
/** Last time A successfully reached this server (in-memory unless flushed). */
|
|
31
39
|
lastSeenAt?: number;
|
|
40
|
+
/**
|
|
41
|
+
* The server's chat models the last time A listed them, already namespaced
|
|
42
|
+
* for A. Kept so a picker can show them, marked unavailable, while the
|
|
43
|
+
* server is asleep or A is off its network.
|
|
44
|
+
*/
|
|
45
|
+
knownModels?: RemoteKnownModel[];
|
|
32
46
|
/**
|
|
33
47
|
* Runtime-managed connections are never persisted and cannot be removed by
|
|
34
48
|
* the ordinary pairing UI. Today the only managed peer is this computer's
|
package/dist/runtime-lock.js
CHANGED
|
@@ -9,7 +9,7 @@ export class SingleInstanceError extends Error {
|
|
|
9
9
|
lockPath;
|
|
10
10
|
constructor(holderPid, lockPath) {
|
|
11
11
|
super(holderPid > 0
|
|
12
|
-
? `Another gezel daemon is already running for this home (pid ${holderPid}). Stop it first with \`gezel stop
|
|
12
|
+
? `Another gezel daemon is already running for this home (pid ${holderPid}). Stop it first with \`gezel stop\`, or use a different GEZEL_HOME.`
|
|
13
13
|
: `Could not acquire the single-instance lock at ${lockPath}.`);
|
|
14
14
|
this.holderPid = holderPid;
|
|
15
15
|
this.lockPath = lockPath;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The amdgpu devices Linux describes in sysfs, for the carve-out decision in
|
|
3
|
+
* gpu-carve-out.ts — the Linux counterpart of the DXCore adapter listing the
|
|
4
|
+
* device-health helper reads on Windows. Everything read here is
|
|
5
|
+
* world-readable, so the machine service's unprivileged account sees it too.
|
|
6
|
+
*/
|
|
7
|
+
import type { GpuAdapterFacts } from './gpu-carve-out.js';
|
|
8
|
+
export interface SysfsIo {
|
|
9
|
+
readFile?: (path: string) => string;
|
|
10
|
+
readDir?: (path: string) => string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One entry per amdgpu device: `dedicatedBytes` from `mem_info_vram_total`,
|
|
14
|
+
* `sharedSystemBytes` from `mem_info_gtt_total` (the system RAM the kernel
|
|
15
|
+
* lets it map). Connector entries (`card0-HDMI-A-1`) and render nodes are
|
|
16
|
+
* skipped. Empty off Linux, without amdgpu, or when sysfs cannot be read.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readAmdgpuAdapters(io?: SysfsIo): GpuAdapterFacts[];
|