kiku-agent 1.2.0 → 1.3.0
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 +459 -407
- package/package.json +27 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +629 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +528 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +383 -179
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
export const GIB = 1024 ** 3;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const QUANT_BPW = Object.freeze({
|
|
5
|
+
fp32: 32.0,
|
|
6
|
+
f32: 32.0,
|
|
7
|
+
fp16: 16.0,
|
|
8
|
+
f16: 16.0,
|
|
9
|
+
bf16: 16.0,
|
|
10
|
+
int8: 8.0,
|
|
11
|
+
q8_0: 8.0,
|
|
12
|
+
q8: 8.0,
|
|
13
|
+
q6_k: 6.56,
|
|
14
|
+
q5_k_m: 5.5,
|
|
15
|
+
q5_k_s: 5.2,
|
|
16
|
+
q4_k_m: 4.5,
|
|
17
|
+
mxfp4: 4.25,
|
|
18
|
+
q4_k_s: 4.2,
|
|
19
|
+
q4_0: 4.2,
|
|
20
|
+
q4_1: 4.4,
|
|
21
|
+
iq4_xs: 4.25,
|
|
22
|
+
q3_k_l: 3.6,
|
|
23
|
+
q3_k_m: 3.4,
|
|
24
|
+
q3_k_s: 3.2,
|
|
25
|
+
q2_k: 2.6,
|
|
26
|
+
iq2_xxs: 2.1,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const QUANT_ALIASES = Object.freeze({
|
|
30
|
+
fp32: 'fp32', f32: 'fp32', float32: 'fp32',
|
|
31
|
+
fp16: 'fp16', f16: 'fp16', bf16: 'bf16', float16: 'fp16',
|
|
32
|
+
int8: 'int8', q8: 'q8_0', q80: 'q8_0', q8_0: 'q8_0',
|
|
33
|
+
q6k: 'q6_k', q6_k: 'q6_k',
|
|
34
|
+
q5km: 'q5_k_m', q5_k_m: 'q5_k_m', q5ks: 'q5_k_s', q5_k_s: 'q5_k_s',
|
|
35
|
+
q4km: 'q4_k_m', q4_k_m: 'q4_k_m', mxfp4: 'mxfp4', q4ks: 'q4_k_s', q4_k_s: 'q4_k_s',
|
|
36
|
+
q40: 'q4_0', q4_0: 'q4_0', q41: 'q4_1', q4_1: 'q4_1',
|
|
37
|
+
iq4xs: 'iq4_xs', iq4_xs: 'iq4_xs',
|
|
38
|
+
q3kl: 'q3_k_l', q3_k_l: 'q3_k_l', q3km: 'q3_k_m', q3_k_m: 'q3_k_m', q3ks: 'q3_k_s', q3_k_s: 'q3_k_s',
|
|
39
|
+
q2k: 'q2_k', q2_k: 'q2_k',
|
|
40
|
+
iq2xxs: 'iq2_xxs', iq2_xxs: 'iq2_xxs',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export const ACTIVATION_RANGE_GB = Object.freeze([0.15, 0.35]);
|
|
45
|
+
export const DEFAULT_ACTIVATION_GB = 0.25;
|
|
46
|
+
|
|
47
|
+
export const OVERHEAD_RANGE_GB = Object.freeze([0.6, 0.8]);
|
|
48
|
+
export const DEFAULT_OVERHEAD_GB = 0.7;
|
|
49
|
+
|
|
50
|
+
export const VISION_TOKENS_RANGE = Object.freeze([576, 2048]);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
export const ALLOCATION_THRESHOLDS = Object.freeze({ green: 0.85, yellow: 1.00 });
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
export const SWA_ARCH_PROFILES = Object.freeze({
|
|
58
|
+
gemma2: { windowTokens: 4096, patternLocal: 1, patternGlobal: 1 },
|
|
59
|
+
gemma3: { windowTokens: 1024, patternLocal: 5, patternGlobal: 1 },
|
|
60
|
+
gemma3n: { windowTokens: 512, patternLocal: 5, patternGlobal: 1 },
|
|
61
|
+
gemma4: { windowTokens: 1024, patternLocal: 5, patternGlobal: 1 },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export function knownSwaProfile(arch) {
|
|
65
|
+
const key = String(arch || '').toLowerCase();
|
|
66
|
+
if (!key) return null;
|
|
67
|
+
if (SWA_ARCH_PROFILES[key]) return { ...SWA_ARCH_PROFILES[key] };
|
|
68
|
+
const prefix = Object.keys(SWA_ARCH_PROFILES)
|
|
69
|
+
.filter((k) => key.startsWith(k))
|
|
70
|
+
.sort((a, b) => b.length - a.length)[0];
|
|
71
|
+
return prefix ? { ...SWA_ARCH_PROFILES[prefix] } : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function normalizeSwaProfile(swa) {
|
|
75
|
+
if (!swa || typeof swa !== 'object') return null;
|
|
76
|
+
const windowTokens = Math.round(Number(swa.windowTokens));
|
|
77
|
+
if (!Number.isFinite(windowTokens) || windowTokens <= 0) return null;
|
|
78
|
+
const patternLocal = Math.max(0, Math.round(Number(swa.patternLocal ?? 5)) || 0);
|
|
79
|
+
const patternGlobal = Math.max(0, Math.round(Number(swa.patternGlobal ?? 1)) || 0);
|
|
80
|
+
if (patternLocal + patternGlobal <= 0) return null;
|
|
81
|
+
return { windowTokens, patternLocal, patternGlobal };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function normalizeQuantName(quant) {
|
|
85
|
+
if (quant === null || quant === undefined) return null;
|
|
86
|
+
if (typeof quant === 'number' && Number.isFinite(quant)) return Number(quant);
|
|
87
|
+
const compact = String(quant).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
88
|
+
if (!compact) return null;
|
|
89
|
+
if (QUANT_ALIASES[compact]) return QUANT_ALIASES[compact];
|
|
90
|
+
if (QUANT_BPW[compact]) return compact;
|
|
91
|
+
return compact;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
export function parseBpw(quant) {
|
|
96
|
+
if (quant === null || quant === undefined || quant === '') return null;
|
|
97
|
+
if (typeof quant === 'number' && Number.isFinite(quant)) return quant;
|
|
98
|
+
const normalized = normalizeQuantName(quant);
|
|
99
|
+
if (typeof normalized === 'number') return normalized;
|
|
100
|
+
if (normalized && QUANT_BPW[normalized] !== undefined) return QUANT_BPW[normalized];
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function bytesToGb(bytes) {
|
|
105
|
+
return Number(bytes) / GIB;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function gbToBytes(gb) {
|
|
109
|
+
return Number(gb) * GIB;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function formatGb(bytes, digits = 2) {
|
|
113
|
+
if (bytes === null || bytes === undefined || !Number.isFinite(Number(bytes))) return '—';
|
|
114
|
+
return `${(Number(bytes) / GIB).toFixed(digits)} GB`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
export function estimateWeightsBytes({ paramCount = null, paramsB = null, quant = 'q4_k_m', weightBytesOverride = null } = {}) {
|
|
119
|
+
const bpw = parseBpw(quant);
|
|
120
|
+
if (weightBytesOverride !== null && weightBytesOverride !== undefined && Number.isFinite(Number(weightBytesOverride)) && Number(weightBytesOverride) > 0) {
|
|
121
|
+
let count = paramCount;
|
|
122
|
+
if (count === null || count === undefined) {
|
|
123
|
+
count = paramsB !== null && paramsB !== undefined && Number.isFinite(Number(paramsB))
|
|
124
|
+
? Math.round(Number(paramsB) * 1e9)
|
|
125
|
+
: null;
|
|
126
|
+
}
|
|
127
|
+
return { bytes: Math.round(Number(weightBytesOverride)), bpw, paramCount: count, source: 'override' };
|
|
128
|
+
}
|
|
129
|
+
let count = paramCount;
|
|
130
|
+
if (count === null || count === undefined) {
|
|
131
|
+
count = paramsB !== null && paramsB !== undefined && Number.isFinite(Number(paramsB))
|
|
132
|
+
? Math.round(Number(paramsB) * 1e9)
|
|
133
|
+
: null;
|
|
134
|
+
}
|
|
135
|
+
if (!count || count <= 0 || bpw === null) return { bytes: null, bpw, paramCount: count };
|
|
136
|
+
return { bytes: Math.round(count * bpw / 8), bpw, paramCount: count };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
export function computeKvBytesPerToken({ layers, kvHeads, headDim, cacheBits = 16 } = {}) {
|
|
141
|
+
if (!layers || !kvHeads || !headDim) return null;
|
|
142
|
+
return 2 * layers * kvHeads * headDim * (Number(cacheBits || 16) / 8);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function estimateKvCacheBytes({
|
|
146
|
+
layers = null,
|
|
147
|
+
kvHeads = null,
|
|
148
|
+
headDim = null,
|
|
149
|
+
contextLength = null,
|
|
150
|
+
batchSize = 1,
|
|
151
|
+
cacheBits = 16,
|
|
152
|
+
swa = null,
|
|
153
|
+
} = {}) {
|
|
154
|
+
const perLayerPerToken = (layers && kvHeads && headDim)
|
|
155
|
+
? 2 * kvHeads * headDim * (Number(cacheBits || 16) / 8)
|
|
156
|
+
: null;
|
|
157
|
+
const bytesPerToken = perLayerPerToken !== null ? perLayerPerToken * layers : null;
|
|
158
|
+
const ctx = Number(contextLength);
|
|
159
|
+
const profile = normalizeSwaProfile(swa);
|
|
160
|
+
const kvModel = profile ? 'swa-hybrid' : 'full-attention';
|
|
161
|
+
if (!perLayerPerToken || !ctx || ctx <= 0) {
|
|
162
|
+
return {
|
|
163
|
+
bytes: null,
|
|
164
|
+
bytesPerToken,
|
|
165
|
+
contextLength: Number.isFinite(ctx) ? ctx : null,
|
|
166
|
+
kvModel,
|
|
167
|
+
swa: null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const batch = Math.max(1, Number(batchSize) || 1);
|
|
171
|
+
if (profile) {
|
|
172
|
+
const pattern = profile.patternLocal + profile.patternGlobal;
|
|
173
|
+
const globalLayers = Math.max(1, Math.ceil((layers * profile.patternGlobal) / pattern));
|
|
174
|
+
const localLayers = Math.max(0, layers - globalLayers);
|
|
175
|
+
const window = Math.min(profile.windowTokens, ctx);
|
|
176
|
+
return {
|
|
177
|
+
bytes: Math.round(perLayerPerToken * (globalLayers * ctx + localLayers * window) * batch),
|
|
178
|
+
bytesPerToken,
|
|
179
|
+
contextLength: ctx,
|
|
180
|
+
kvModel,
|
|
181
|
+
swa: { ...profile, globalLayers, localLayers },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
bytes: Math.round(bytesPerToken * ctx * batch),
|
|
186
|
+
bytesPerToken,
|
|
187
|
+
contextLength: ctx,
|
|
188
|
+
kvModel,
|
|
189
|
+
swa: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
export function estimateActivationBytes({
|
|
195
|
+
batchSize = 1,
|
|
196
|
+
hiddenSize = null,
|
|
197
|
+
queryHeads = null,
|
|
198
|
+
kvHeads = null,
|
|
199
|
+
activationBits = 16,
|
|
200
|
+
} = {}) {
|
|
201
|
+
if (!hiddenSize) {
|
|
202
|
+
return { analyticBytes: null, reservedBytes: Math.round(DEFAULT_ACTIVATION_GB * GIB) };
|
|
203
|
+
}
|
|
204
|
+
const gqaFactor = queryHeads && kvHeads ? 5 * (queryHeads / kvHeads) : 5;
|
|
205
|
+
const batch = Math.max(1, Number(batchSize) || 1);
|
|
206
|
+
const analyticBytes = Math.round(
|
|
207
|
+
batch * hiddenSize * (34 + gqaFactor) * (Number(activationBits || 16) / 8)
|
|
208
|
+
);
|
|
209
|
+
return { analyticBytes, reservedBytes: Math.round(DEFAULT_ACTIVATION_GB * GIB) };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Full footprint model. Unknown components are returned as null and excluded
|
|
214
|
+
* from the total, with a warning explaining what is missing.
|
|
215
|
+
*
|
|
216
|
+
* @param {object} input
|
|
217
|
+
* @param {string} [input.modelId] Display / registry identifier.
|
|
218
|
+
* @param {number} [input.paramCount|paramsB] Total parameters (MoE: total loaded params).
|
|
219
|
+
* @param {string} [input.quant='q4_k_m'] Weight quantization.
|
|
220
|
+
* @param {number} [input.contextLength] Runtime sequence limit (text tokens).
|
|
221
|
+
* @param {number} [input.batchSize=1]
|
|
222
|
+
* @param {number} [input.cacheBits=16] KV cache precision (FP16=16, Q8_0=8).
|
|
223
|
+
* @param {number} [input.layers|kvHeads|queryHeads|headDim|hiddenSize]
|
|
224
|
+
* @param {object} [input.vision] { images, tokensPerImage, encoderParamsB }
|
|
225
|
+
* @param {number} [input.activationGb] Override static activation reservation.
|
|
226
|
+
* @param {number} [input.overheadGb] Override driver/runtime overhead.
|
|
227
|
+
* @returns {object} Breakdown in bytes + binary GB, warnings included.
|
|
228
|
+
*/
|
|
229
|
+
export function estimateVramFootprint(input = {}) {
|
|
230
|
+
const warnings = [];
|
|
231
|
+
const quant = input.quant ? normalizeQuantName(input.quant) : 'q4_k_m';
|
|
232
|
+
|
|
233
|
+
const weights = estimateWeightsBytes({
|
|
234
|
+
paramCount: input.paramCount,
|
|
235
|
+
paramsB: input.paramsB,
|
|
236
|
+
quant,
|
|
237
|
+
weightBytesOverride: input.weightBytesOverride,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const vision = input.vision || null;
|
|
241
|
+
|
|
242
|
+
let visionBytes = 0;
|
|
243
|
+
if (vision && (vision.encoderParamsB || vision.encoderParams)) {
|
|
244
|
+
const vpB = Number(vision.encoderParamsB ?? Number(vision.encoderParams) / 1e9);
|
|
245
|
+
if (Number.isFinite(vpB) && vpB > 0) {
|
|
246
|
+
visionBytes = Math.round(vpB * 1e9 * (weights.bpw ?? 16) / 8);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const textCtx = Number(input.contextLength) || 0;
|
|
251
|
+
let tokensPerImage = vision && vision.tokensPerImage ? Number(vision.tokensPerImage) : 0;
|
|
252
|
+
if (tokensPerImage && (tokensPerImage < VISION_TOKENS_RANGE[0] || tokensPerImage > VISION_TOKENS_RANGE[1])) {
|
|
253
|
+
tokensPerImage = Math.min(VISION_TOKENS_RANGE[1], Math.max(VISION_TOKENS_RANGE[0], tokensPerImage));
|
|
254
|
+
}
|
|
255
|
+
const images = vision && vision.images ? Math.max(0, Math.round(Number(vision.images))) : 0;
|
|
256
|
+
const effectiveContextLength = textCtx + (tokensPerImage ? images * tokensPerImage : 0);
|
|
257
|
+
|
|
258
|
+
const kv = estimateKvCacheBytes({
|
|
259
|
+
layers: input.layers,
|
|
260
|
+
kvHeads: input.kvHeads,
|
|
261
|
+
headDim: input.headDim,
|
|
262
|
+
contextLength: effectiveContextLength || null,
|
|
263
|
+
batchSize: input.batchSize,
|
|
264
|
+
cacheBits: input.cacheBits,
|
|
265
|
+
swa: input.swa,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const activation = estimateActivationBytes({
|
|
269
|
+
batchSize: input.batchSize,
|
|
270
|
+
hiddenSize: input.hiddenSize,
|
|
271
|
+
queryHeads: input.queryHeads,
|
|
272
|
+
kvHeads: input.kvHeads,
|
|
273
|
+
activationBits: 16,
|
|
274
|
+
});
|
|
275
|
+
const activationBytes = input.activationGb !== undefined && input.activationGb !== null
|
|
276
|
+
? Math.round(Number(input.activationGb) * GIB)
|
|
277
|
+
: activation.reservedBytes;
|
|
278
|
+
const overheadBytes = input.overheadGb !== undefined && input.overheadGb !== null
|
|
279
|
+
? Math.round(Number(input.overheadGb) * GIB)
|
|
280
|
+
: Math.round(DEFAULT_OVERHEAD_GB * GIB);
|
|
281
|
+
|
|
282
|
+
if (weights.bytes === null) {
|
|
283
|
+
warnings.push('Parameter count unknown — weights component excluded from total.');
|
|
284
|
+
}
|
|
285
|
+
if (kv.bytes === null) {
|
|
286
|
+
warnings.push('Architecture geometry incomplete (layers/kv_heads/head_dim) — KV cache component excluded.');
|
|
287
|
+
}
|
|
288
|
+
if (!textCtx) {
|
|
289
|
+
warnings.push('Context length not provided — KV cache computed against 0 tokens.');
|
|
290
|
+
}
|
|
291
|
+
if (vision && images > 0 && !tokensPerImage) {
|
|
292
|
+
warnings.push('Vision input detected but tokens-per-image unknown — context correction skipped.');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const totalBytes = weights.bytes === null
|
|
296
|
+
? null
|
|
297
|
+
: (weights.bytes ?? 0) + visionBytes + (kv.bytes ?? 0) + activationBytes + overheadBytes;
|
|
298
|
+
|
|
299
|
+
const gb = (b) => (b === null || b === undefined ? null : Number((b / GIB).toFixed(4)));
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
modelId: input.modelId || null,
|
|
303
|
+
quant,
|
|
304
|
+
quantBpw: weights.bpw,
|
|
305
|
+
paramCount: weights.paramCount,
|
|
306
|
+
batchSize: Math.max(1, Number(input.batchSize) || 1),
|
|
307
|
+
cacheBits: Number(input.cacheBits) || 16,
|
|
308
|
+
layers: input.layers ?? null,
|
|
309
|
+
kvHeads: input.kvHeads ?? null,
|
|
310
|
+
queryHeads: input.queryHeads ?? null,
|
|
311
|
+
headDim: input.headDim ?? null,
|
|
312
|
+
hiddenSize: input.hiddenSize ?? null,
|
|
313
|
+
contextLength: textCtx || null,
|
|
314
|
+
effectiveContextLength: effectiveContextLength || null,
|
|
315
|
+
vision: vision && (images > 0 || visionBytes > 0)
|
|
316
|
+
? { images, tokensPerImage: tokensPerImage || null, visionEncoderBytes: visionBytes || null }
|
|
317
|
+
: null,
|
|
318
|
+
weightsBytes: weights.bytes,
|
|
319
|
+
visionEncoderBytes: visionBytes || null,
|
|
320
|
+
kvCacheBytes: kv.bytes,
|
|
321
|
+
kvBytesPerToken: kv.bytesPerToken,
|
|
322
|
+
kvModel: kv.kvModel,
|
|
323
|
+
swa: kv.swa,
|
|
324
|
+
activationBytes,
|
|
325
|
+
activationAnalyticBytes: activation.analyticBytes,
|
|
326
|
+
activationSource: input.activationGb !== undefined && input.activationGb !== null ? 'override' : 'static-reservation',
|
|
327
|
+
overheadBytes,
|
|
328
|
+
totalBytes,
|
|
329
|
+
gb: {
|
|
330
|
+
weights: gb(weights.bytes),
|
|
331
|
+
visionEncoder: visionBytes ? gb(visionBytes) : null,
|
|
332
|
+
kvCache: gb(kv.bytes),
|
|
333
|
+
activation: gb(activationBytes),
|
|
334
|
+
overhead: gb(overheadBytes),
|
|
335
|
+
total: gb(totalBytes),
|
|
336
|
+
kvBytesPerToken: kv.bytesPerToken,
|
|
337
|
+
},
|
|
338
|
+
warnings,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
export function classifyAllocation({ totalBytes, availableBytes } = {}) {
|
|
344
|
+
if (!totalBytes || totalBytes <= 0 || !availableBytes || availableBytes <= 0) {
|
|
345
|
+
return {
|
|
346
|
+
zone: 'UNKNOWN',
|
|
347
|
+
usagePct: null,
|
|
348
|
+
headroomBytes: null,
|
|
349
|
+
message: 'Available VRAM or model weight parameters could not be determined — feasibility check skipped.',
|
|
350
|
+
requiresForce: false,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const usagePct = Number(((totalBytes / availableBytes) * 100).toFixed(1));
|
|
354
|
+
const headroomBytes = availableBytes - totalBytes;
|
|
355
|
+
if (totalBytes <= availableBytes * ALLOCATION_THRESHOLDS.green) {
|
|
356
|
+
return {
|
|
357
|
+
zone: 'GREEN',
|
|
358
|
+
usagePct,
|
|
359
|
+
headroomBytes,
|
|
360
|
+
message: 'Feasible: execution permitted without memory pressure.',
|
|
361
|
+
requiresForce: false,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (totalBytes <= availableBytes * ALLOCATION_THRESHOLDS.yellow) {
|
|
365
|
+
return {
|
|
366
|
+
zone: 'YELLOW',
|
|
367
|
+
usagePct,
|
|
368
|
+
headroomBytes,
|
|
369
|
+
message: 'Marginal: fits in physical VRAM but leaves limited headroom for peak prefill.',
|
|
370
|
+
requiresForce: false,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
zone: 'RED',
|
|
375
|
+
usagePct,
|
|
376
|
+
headroomBytes,
|
|
377
|
+
message: headroomBytes >= 0
|
|
378
|
+
? 'Critical: allocation at the physical VRAM limit.'
|
|
379
|
+
: 'Out of memory: allocation exceeds physical VRAM.',
|
|
380
|
+
requiresForce: true,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
export const QUANT_LADDER = Object.freeze(['fp16', 'q8_0', 'q6_k', 'q5_k_m', 'q4_k_m', 'mxfp4', 'q4_k_s', 'q4_0', 'q3_k_m', 'q2_k']);
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
export function recommendAdjustments(estimate, availableBytes, { targetZone = 'GREEN' } = {}) {
|
|
389
|
+
if (!estimate || !estimate.totalBytes || !availableBytes || availableBytes <= 0) return [];
|
|
390
|
+
const threshold = targetZone === 'GREEN' ? ALLOCATION_THRESHOLDS.green : ALLOCATION_THRESHOLDS.yellow;
|
|
391
|
+
const suggestions = [];
|
|
392
|
+
|
|
393
|
+
const kvBytes = estimate.kvCacheBytes ?? 0;
|
|
394
|
+
const fixedBytes = estimate.totalBytes - kvBytes;
|
|
395
|
+
const ctx = estimate.effectiveContextLength || estimate.contextLength;
|
|
396
|
+
if (ctx && kvBytes > 0) {
|
|
397
|
+
const budget = availableBytes * threshold - fixedBytes;
|
|
398
|
+
if (budget > 0) {
|
|
399
|
+
|
|
400
|
+
const kvFor = (c) => estimateKvCacheBytes({
|
|
401
|
+
layers: estimate.layers,
|
|
402
|
+
kvHeads: estimate.kvHeads,
|
|
403
|
+
headDim: estimate.headDim,
|
|
404
|
+
contextLength: c,
|
|
405
|
+
batchSize: estimate.batchSize,
|
|
406
|
+
cacheBits: estimate.cacheBits,
|
|
407
|
+
swa: estimate.swa,
|
|
408
|
+
}).bytes ?? 0;
|
|
409
|
+
const step = 1024;
|
|
410
|
+
let lo = step;
|
|
411
|
+
let hi = Math.floor(ctx / step) * step;
|
|
412
|
+
let best = 0;
|
|
413
|
+
while (lo <= hi) {
|
|
414
|
+
const mid = Math.floor((lo + hi) / 2 / step) * step;
|
|
415
|
+
if (kvFor(mid) <= budget) {
|
|
416
|
+
best = mid;
|
|
417
|
+
lo = mid + step;
|
|
418
|
+
} else {
|
|
419
|
+
hi = mid - step;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (best >= step && best < ctx) {
|
|
423
|
+
const projected = Math.round(fixedBytes + kvFor(best));
|
|
424
|
+
suggestions.push({
|
|
425
|
+
kind: 'context',
|
|
426
|
+
description: `Set --ctx ${best.toLocaleString('en-US')} to reduce footprint to ${formatGb(projected)}.`,
|
|
427
|
+
projectedBytes: projected,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (kvBytes > 0 && Number(estimate.cacheBits) === 16) {
|
|
434
|
+
const projected = estimate.totalBytes - Math.round(kvBytes / 2);
|
|
435
|
+
if (projected <= availableBytes * threshold) {
|
|
436
|
+
suggestions.push({
|
|
437
|
+
kind: 'kv-quant',
|
|
438
|
+
description: `Switch the KV cache to Q8_0 (--cache-type-k q8_0 --cache-type-v q8_0) for a ${formatGb(projected)} footprint.`,
|
|
439
|
+
projectedBytes: Math.round(projected),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const currentIndex = QUANT_LADDER.indexOf(estimate.quant);
|
|
445
|
+
if (estimate.weightsBytes && currentIndex !== -1) {
|
|
446
|
+
for (let i = currentIndex + 1; i < QUANT_LADDER.length; i++) {
|
|
447
|
+
const bpw = parseBpw(QUANT_LADDER[i]);
|
|
448
|
+
if (!bpw || !estimate.paramCount) continue;
|
|
449
|
+
const projectedWeights = Math.round(estimate.paramCount * bpw / 8);
|
|
450
|
+
const projected = projectedWeights + (estimate.kvCacheBytes ?? 0) +
|
|
451
|
+
estimate.activationBytes + estimate.overheadBytes;
|
|
452
|
+
if (projected <= availableBytes * threshold) {
|
|
453
|
+
suggestions.push({
|
|
454
|
+
kind: 'quantization',
|
|
455
|
+
description: `Switch to ${QUANT_LADDER[i]} (${bpw} bpw) for a ${formatGb(projected)} footprint.`,
|
|
456
|
+
projectedBytes: Math.round(projected),
|
|
457
|
+
});
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return suggestions;
|
|
464
|
+
}
|