@bendyline/gezel-inference 1.0.1 → 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/dist/engines/native-file-manifest.json +404 -404
- package/dist/engines/native-manifest.d.ts +4 -4
- package/dist/engines/native-manifest.js +34 -34
- 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 +35 -0
- package/dist/http/routes/v1-chat.js +10 -1
- package/dist/http/routes/v1-models.js +6 -4
- package/dist/http/routes/v1-responses.js +7 -1
- 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 +141 -144
- package/dist/providers/llama-cpp/cache-adapter.js +2 -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.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 +3 -4
- package/dist/providers/mlx/python/qwen4_ple.py +9 -2
- package/dist/providers/native/capacity-broker.d.ts +21 -15
- package/dist/providers/native/capacity-broker.js +16 -10
- 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 +16 -19
- 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/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 +94 -13
- package/dist/transformers-cache.d.ts +2 -0
- package/dist/transformers-cache.js +6 -3
- package/package.json +5 -5
|
@@ -151,12 +151,16 @@ export class CapacityBroker {
|
|
|
151
151
|
systemRamBytes;
|
|
152
152
|
unifiedMemory;
|
|
153
153
|
gpuVramBytes;
|
|
154
|
+
reservedGpuBytes;
|
|
155
|
+
gpuAddressableBytes;
|
|
154
156
|
explicitAllowRamSpillover;
|
|
155
157
|
reservations = new Map();
|
|
156
158
|
constructor(opts = {}) {
|
|
157
159
|
this.systemRamBytes = (opts.systemRamBytes ?? totalmem)();
|
|
158
160
|
this.unifiedMemory = opts.unifiedMemory;
|
|
159
161
|
this.gpuVramBytes = opts.gpuVramBytes;
|
|
162
|
+
this.reservedGpuBytes = opts.reservedGpuBytes;
|
|
163
|
+
this.gpuAddressableBytes = opts.gpuAddressableBytes;
|
|
160
164
|
this.explicitAllowRamSpillover = opts.allowRamSpillover ?? null;
|
|
161
165
|
this.explicitBudgetBytes = opts.budgetBytes ?? null;
|
|
162
166
|
// Assigned by applyBudget; the definite-assignment dance TS wants for
|
|
@@ -189,6 +193,12 @@ export class CapacityBroker {
|
|
|
189
193
|
: {
|
|
190
194
|
...(this.unifiedMemory === undefined ? {} : { unifiedMemory: this.unifiedMemory }),
|
|
191
195
|
...(this.gpuVramBytes === undefined ? {} : { gpuVramBytes: this.gpuVramBytes }),
|
|
196
|
+
...(this.reservedGpuBytes === undefined
|
|
197
|
+
? {}
|
|
198
|
+
: { reservedGpuBytes: this.reservedGpuBytes }),
|
|
199
|
+
...(this.gpuAddressableBytes === undefined
|
|
200
|
+
? {}
|
|
201
|
+
: { gpuAddressableBytes: this.gpuAddressableBytes }),
|
|
192
202
|
}),
|
|
193
203
|
});
|
|
194
204
|
}
|
|
@@ -714,11 +724,9 @@ function envGrowthCeilingTokens() {
|
|
|
714
724
|
return Number.isFinite(tokens) && tokens > 0 ? tokens : null;
|
|
715
725
|
}
|
|
716
726
|
/**
|
|
717
|
-
* The most KV cache
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
* numeric context, a pinned `swaFull`) are the operator's call and bypass it;
|
|
721
|
-
* it never shortens the ladder-settled base window either.
|
|
727
|
+
* The most KV cache adaptive growth plans for one llama.cpp engine. An
|
|
728
|
+
* explicit numeric context is the operator's call and skips growth
|
|
729
|
+
* altogether, and the ceiling never shortens the ladder-settled base window.
|
|
722
730
|
*
|
|
723
731
|
* Past this a bigger cache buys nothing: llama.cpp's prefill and decode cost
|
|
724
732
|
* follows the tokens actually present, not the reservation, so the rest only
|
|
@@ -959,11 +967,9 @@ export const ADAPTIVE_GROWTH_TRADE_MIN_GAIN = 1.25;
|
|
|
959
967
|
* settled slots and granted the un-grown request.
|
|
960
968
|
*
|
|
961
969
|
* Deliberately a second clamp pass rather than a bigger first request:
|
|
962
|
-
* the first ladder pass sizes slot ceilings
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
* `--swa-full` for models whose full cache fits at 64K and grow KV into
|
|
966
|
-
* system-RAM spill on discrete cards.
|
|
970
|
+
* the first ladder pass sizes slot ceilings and RAM-spillover admission,
|
|
971
|
+
* both of which must stay anchored on the practical 64K target — growing
|
|
972
|
+
* the request there would grow KV into system-RAM spill on discrete cards.
|
|
967
973
|
*
|
|
968
974
|
* Slot trade: when `allowSlotTrade` is set and the target is out of reach
|
|
969
975
|
* at the settled slot count, growth walks the count down — taking the
|
|
@@ -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,
|
|
@@ -1161,7 +1163,7 @@ export class LocalEngineRuntime {
|
|
|
1161
1163
|
// engine: the full plan must run so a stale resident window surfaces as
|
|
1162
1164
|
// restart-required instead of masquerading as the applied setting.
|
|
1163
1165
|
const shortCircuit = (residentContextWindow !== undefined && !overrideActive) || envNumCtx !== undefined;
|
|
1164
|
-
const {
|
|
1166
|
+
const { defaultLocalEngineSlots, estimatePerSlotKvBytes, kvQuantScale, llamaCppSlotCeiling, planAdaptiveContextGrowth, planCtxTokensForMemory, plannedLocalEngineSlots, } = await import('./capacity-broker.js');
|
|
1165
1167
|
const capacity = await this.previewCapacityInputs('llama-cpp', modelId, capacityOpts);
|
|
1166
1168
|
const { budgetBytes: admissionBudgetBytes, fastBudgetBytes, committedOtherBytes } = capacity;
|
|
1167
1169
|
const configuredSlots = config.providerConcurrency?.['llama-cpp'];
|
|
@@ -1481,27 +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
|
-
(exactKvAtReference / referenceCtx) * grantedCtx * slots > autoKvCacheMaxBytes();
|
|
1497
|
-
const windowedCacheWillRun = (!admission.minimumSatisfied ||
|
|
1498
|
-
admission.clamped ||
|
|
1499
|
-
admission.slots < slots ||
|
|
1500
|
-
fullCacheOverCeiling) &&
|
|
1501
|
-
(explicitSwaFull === false ||
|
|
1502
|
-
(explicitSwaFull === undefined &&
|
|
1503
|
-
isGemmaModel({ architecture: installed.architecture, modelId })));
|
|
1504
|
-
if (windowedCacheWillRun) {
|
|
1497
|
+
if (explicitSwaFull !== true) {
|
|
1505
1498
|
const windowed = estimateWindowedKvLinearization({
|
|
1506
1499
|
blockCount: summary.blockCount,
|
|
1507
1500
|
embeddingLength: summary.embeddingLength,
|
|
@@ -1541,9 +1534,13 @@ export class LocalEngineRuntime {
|
|
|
1541
1534
|
fixedPerSlotBytes: windowed.fixedBytes,
|
|
1542
1535
|
};
|
|
1543
1536
|
}
|
|
1544
|
-
else if ((
|
|
1545
|
-
|
|
1546
|
-
|
|
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.
|
|
1547
1544
|
return { grantedCtx, slots, kvCacheType: kv, plannedFieldsOk: false };
|
|
1548
1545
|
}
|
|
1549
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;
|
|
@@ -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[];
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { readFileSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
const DRM_ROOT = '/sys/class/drm';
|
|
10
|
+
const AMD_PCI_VENDOR = 0x1002;
|
|
11
|
+
/**
|
|
12
|
+
* One entry per amdgpu device: `dedicatedBytes` from `mem_info_vram_total`,
|
|
13
|
+
* `sharedSystemBytes` from `mem_info_gtt_total` (the system RAM the kernel
|
|
14
|
+
* lets it map). Connector entries (`card0-HDMI-A-1`) and render nodes are
|
|
15
|
+
* skipped. Empty off Linux, without amdgpu, or when sysfs cannot be read.
|
|
16
|
+
*/
|
|
17
|
+
export function readAmdgpuAdapters(io = {}) {
|
|
18
|
+
const readFile = io.readFile ?? ((path) => readFileSync(path, 'utf8'));
|
|
19
|
+
const readDir = io.readDir ?? ((path) => readdirSync(path));
|
|
20
|
+
const read = (path) => {
|
|
21
|
+
try {
|
|
22
|
+
return readFile(path).trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
let cards;
|
|
29
|
+
try {
|
|
30
|
+
cards = readDir(DRM_ROOT).filter((entry) => /^card\d+$/.test(entry));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const adapters = [];
|
|
36
|
+
for (const card of cards) {
|
|
37
|
+
const device = join(DRM_ROOT, card, 'device');
|
|
38
|
+
if (Number.parseInt(read(join(device, 'vendor')) ?? '', 16) !== AMD_PCI_VENDOR)
|
|
39
|
+
continue;
|
|
40
|
+
const vram = byteCount(read(join(device, 'mem_info_vram_total')));
|
|
41
|
+
if (vram === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
const gtt = byteCount(read(join(device, 'mem_info_gtt_total')));
|
|
44
|
+
adapters.push({
|
|
45
|
+
vendorId: AMD_PCI_VENDOR,
|
|
46
|
+
dedicatedBytes: vram,
|
|
47
|
+
...(gtt !== undefined ? { sharedSystemBytes: gtt } : {}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return adapters;
|
|
51
|
+
}
|
|
52
|
+
function byteCount(raw) {
|
|
53
|
+
const value = raw ? Number(raw) : Number.NaN;
|
|
54
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
55
|
+
}
|