llm-tuner 0.4.0 → 0.4.1
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/package.json +1 -1
- package/src/core/engines/lmstudio.js +69 -16
package/package.json
CHANGED
|
@@ -170,9 +170,10 @@ export const lmstudio = {
|
|
|
170
170
|
|
|
171
171
|
async benchmarkViaLms(ctx, model, candidate, opts) {
|
|
172
172
|
const bin = ctx.detection.bin;
|
|
173
|
-
const
|
|
173
|
+
const modelKey = model.key || model;
|
|
174
|
+
const restore = await this.writeModelConfig(ctx, modelKey, candidate, model.meta);
|
|
174
175
|
try {
|
|
175
|
-
const args = ['load',
|
|
176
|
+
const args = ['load', modelKey, '-y', '--identifier', 'llm-tuner-bench', '-c', String(candidate.ctx), '--gpu', candidate.fullOffload ? 'max' : String(candidate.gpuLayers / candidate.totalLayers)];
|
|
176
177
|
const r = await run(bin, args, { timeout: 600_000 });
|
|
177
178
|
if (r.code !== 0) return { ok: false, error: (r.stderr || r.stdout).slice(-400) };
|
|
178
179
|
return await runApiBenchmark({ kind: 'lmstudio', baseUrl: 'http://127.0.0.1:1234', model: 'llm-tuner-bench', ...opts });
|
|
@@ -182,17 +183,52 @@ export const lmstudio = {
|
|
|
182
183
|
}
|
|
183
184
|
},
|
|
184
185
|
|
|
185
|
-
|
|
186
|
+
/** Resolve the concrete indexedModelIdentifier for a given key or file. */
|
|
187
|
+
async resolveConcreteIdentifier(ctx, key, meta) {
|
|
188
|
+
if (!key) return key;
|
|
189
|
+
if (key.includes('/') && key.endsWith('.gguf')) return key;
|
|
190
|
+
const home = ctx.detection?.home || (await lmsHome());
|
|
191
|
+
const idx = await readJson(path.join(home, '.internal', 'model-index-cache.json'), { models: [] });
|
|
192
|
+
const entries = idx.models || [];
|
|
193
|
+
const byId = (id) => entries.find((e) => e.indexedModelIdentifier === id && !e.virtual);
|
|
194
|
+
let entry = null;
|
|
195
|
+
if (meta?.file) {
|
|
196
|
+
entry = entries.find((e) => e.entryPoint?.absPath === meta.file || (e.concreteModelDirAbsolutePath && meta.file.startsWith(e.concreteModelDirAbsolutePath)));
|
|
197
|
+
}
|
|
198
|
+
if (!entry) {
|
|
199
|
+
entry = entries.find((e) => e.indexedModelIdentifier === key || e.defaultIdentifier === key || e.virtual?.baseChain?.[0] === key);
|
|
200
|
+
}
|
|
201
|
+
const concrete = entry?.virtual?.concreteModelIndexedModelIdentifier || entry?.virtual?.baseChain?.at?.(-1);
|
|
202
|
+
if (concrete) entry = byId(concrete) || entry;
|
|
203
|
+
if (entry?.indexedModelIdentifier) return entry.indexedModelIdentifier;
|
|
204
|
+
if (entry?.entryPoint?.relPath) return entry.entryPoint.relPath;
|
|
205
|
+
if (ctx.detection?.bin) {
|
|
206
|
+
const r = await run(ctx.detection.bin, ['ls', '--json']);
|
|
207
|
+
if (r.code === 0) {
|
|
208
|
+
try {
|
|
209
|
+
const list = JSON.parse(r.stdout);
|
|
210
|
+
const found = (Array.isArray(list) ? list : []).find((m) => m && (m.modelKey === key || m.path === key || m.indexedModelIdentifier === key));
|
|
211
|
+
if (found?.indexedModelIdentifier || found?.path) return found.indexedModelIdentifier || found.path;
|
|
212
|
+
} catch { /* ignore */ }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return key;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
async configPaths(ctx, key, meta) {
|
|
186
219
|
const internal = path.join(ctx.detection.home, '.internal');
|
|
220
|
+
const concrete = key ? await this.resolveConcreteIdentifier(ctx, key, meta) : '';
|
|
187
221
|
return {
|
|
188
|
-
modelConfig: path.join(internal, 'user-concrete-model-default-config', `${key}.json`),
|
|
222
|
+
modelConfig: path.join(internal, 'user-concrete-model-default-config', `${concrete || key}.json`),
|
|
223
|
+
aliasConfig: concrete && concrete !== key ? path.join(internal, 'user-concrete-model-default-config', `${key}.json`) : null,
|
|
189
224
|
hardwareConfig: path.join(internal, 'hardware-config.json'),
|
|
190
225
|
};
|
|
191
226
|
},
|
|
192
227
|
|
|
193
|
-
async writeModelConfig(ctx, key, c) {
|
|
194
|
-
const
|
|
195
|
-
const
|
|
228
|
+
async writeModelConfig(ctx, key, c, meta) {
|
|
229
|
+
const paths = await this.configPaths(ctx, key, meta);
|
|
230
|
+
const file = paths.modelConfig;
|
|
231
|
+
const cur = (await readJson(file)) || (paths.aliasConfig ? (await readJson(paths.aliasConfig)) : null) || { preset: '', operation: { fields: [] }, load: { fields: [] } };
|
|
196
232
|
const fields = new Map((cur.load?.fields || []).map((f) => [f.key, f.value]));
|
|
197
233
|
const set = (k, v) => fields.set(k, v);
|
|
198
234
|
set('llm.load.contextLength', c.ctx);
|
|
@@ -207,15 +243,24 @@ export const lmstudio = {
|
|
|
207
243
|
set('llm.load.llama.kCacheQuantizationType', { checked: c.kvType !== 'f16', value: c.kvType });
|
|
208
244
|
set('llm.load.llama.vCacheQuantizationType', { checked: c.kvType !== 'f16', value: c.kvType });
|
|
209
245
|
if (c.cpuMoeLayers) set('llm.load.numCpuExpertLayersRatio', +(c.cpuMoeLayers / (c.totalLayers - 1)).toFixed(3));
|
|
210
|
-
const next = { ...cur, load: { ...(cur.load || {}), fields: [...fields].map(([
|
|
211
|
-
|
|
246
|
+
const next = { ...cur, load: { ...(cur.load || {}), fields: [...fields].map(([k, value]) => ({ key: k, value })) } };
|
|
247
|
+
const fileExists = await exists(file);
|
|
248
|
+
if (fileExists && JSON.stringify(next) === JSON.stringify(cur)) {
|
|
249
|
+
if (paths.aliasConfig && !(await exists(paths.aliasConfig))) {
|
|
250
|
+
await writeJsonWithBackup(paths.aliasConfig, next).catch(() => null);
|
|
251
|
+
}
|
|
252
|
+
return { file, backup: null, unchanged: true };
|
|
253
|
+
}
|
|
212
254
|
const backup = await writeJsonWithBackup(file, next);
|
|
255
|
+
if (paths.aliasConfig) {
|
|
256
|
+
await writeJsonWithBackup(paths.aliasConfig, next).catch(() => null);
|
|
257
|
+
}
|
|
213
258
|
return { file, backup };
|
|
214
259
|
},
|
|
215
260
|
|
|
216
261
|
/** hardware-config.json is superjson: {"json":[[backendName,{fields:[...]}]],"meta":{...}} */
|
|
217
262
|
async writeHardwareConfig(ctx, c) {
|
|
218
|
-
const file = this.configPaths(ctx, '').hardwareConfig;
|
|
263
|
+
const file = (await this.configPaths(ctx, '')).hardwareConfig;
|
|
219
264
|
const be = await this.backend(ctx);
|
|
220
265
|
const backendName = be?.name || 'llama.cpp';
|
|
221
266
|
const cur = (await readJson(file)) || { json: [], meta: { values: ['map'] } };
|
|
@@ -269,7 +314,7 @@ export const lmstudio = {
|
|
|
269
314
|
|
|
270
315
|
/** Does hardware-config.json already match what this candidate needs? */
|
|
271
316
|
async isHardwareApplied(ctx, c) {
|
|
272
|
-
const file = this.configPaths(ctx, '').hardwareConfig;
|
|
317
|
+
const file = (await this.configPaths(ctx, '')).hardwareConfig;
|
|
273
318
|
const be = await this.backend(ctx);
|
|
274
319
|
const cur = await readJson(file);
|
|
275
320
|
const entry = (cur?.json || []).find(([name]) => name === (be?.name || 'llama.cpp'));
|
|
@@ -288,7 +333,8 @@ export const lmstudio = {
|
|
|
288
333
|
* overwritten by the app on exit, so the app is closed while it changes.
|
|
289
334
|
*/
|
|
290
335
|
async apply(ctx, model, c, { closeApp = true, relaunch = true, dryRun = false } = {}) {
|
|
291
|
-
const
|
|
336
|
+
const modelKey = model.key || model;
|
|
337
|
+
const paths = await this.configPaths(ctx, modelKey, model.meta);
|
|
292
338
|
const hardwareOk = await this.isHardwareApplied(ctx, c);
|
|
293
339
|
const det = await this.detect();
|
|
294
340
|
const plan = { files: hardwareOk ? [paths.modelConfig] : [paths.modelConfig, paths.hardwareConfig], restartsApp: !hardwareOk && det.appRunning, candidate: c };
|
|
@@ -304,7 +350,7 @@ export const lmstudio = {
|
|
|
304
350
|
if (b.backup) backups.push(b.backup);
|
|
305
351
|
changes.push({ code: 'changes.lmsHardware' });
|
|
306
352
|
}
|
|
307
|
-
const a = await this.writeModelConfig(ctx,
|
|
353
|
+
const a = await this.writeModelConfig(ctx, modelKey, c, model.meta);
|
|
308
354
|
if (a.backup) backups.push(a.backup);
|
|
309
355
|
if (!a.unchanged) changes.push(c.fullOffload
|
|
310
356
|
? { code: 'changes.lmsModelFull', params: { ctx: c.ctx, kvType: c.kvType, threads: c.threads } }
|
|
@@ -317,16 +363,23 @@ export const lmstudio = {
|
|
|
317
363
|
},
|
|
318
364
|
|
|
319
365
|
/** Load the model the normal way (using the saved config) and optionally measure it. */
|
|
320
|
-
async load(ctx, model, { depthTokens = 0, genTokens = 200, onProgress } = {}) {
|
|
366
|
+
async load(ctx, model, { candidate, depthTokens = 0, genTokens = 200, onProgress } = {}) {
|
|
321
367
|
const bin = ctx.detection.bin;
|
|
322
368
|
if (!bin) return benchError('bench.lmsUnavailable');
|
|
323
369
|
await run(bin, ['server', 'start']);
|
|
324
370
|
await run(bin, ['unload', '--all']);
|
|
325
371
|
onProgress?.({ phase: 'load' });
|
|
326
|
-
const
|
|
372
|
+
const modelKey = model.key || model;
|
|
373
|
+
const args = ['load', modelKey, '-y'];
|
|
374
|
+
if (candidate?.ctx) args.push('-c', String(candidate.ctx));
|
|
375
|
+
if (candidate?.gpuLayers != null && candidate?.totalLayers) {
|
|
376
|
+
args.push('--gpu', candidate.fullOffload ? 'max' : String(+(candidate.gpuLayers / candidate.totalLayers).toFixed(3)));
|
|
377
|
+
}
|
|
378
|
+
if (candidate?.parallel) args.push('--parallel', String(candidate.parallel));
|
|
379
|
+
const r = await run(bin, args, { timeout: 600_000 });
|
|
327
380
|
if (r.code !== 0) return { ok: false, error: (r.stderr || r.stdout).slice(-400) };
|
|
328
381
|
const url = ctx.detection?.apiUrl || apiUrl();
|
|
329
|
-
return runApiBenchmark({ kind: 'lmstudio', baseUrl: url, model:
|
|
382
|
+
return runApiBenchmark({ kind: 'lmstudio', baseUrl: url, model: modelKey, depthTokens, genTokens, onProgress });
|
|
330
383
|
},
|
|
331
384
|
|
|
332
385
|
async chat(ctx, model, messages) {
|