turbollm 0.6.0 → 0.6.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/cli.js
CHANGED
|
@@ -569,12 +569,32 @@ async function probeMlx(python) {
|
|
|
569
569
|
);
|
|
570
570
|
return `mlx-lm ${stdout.trim()}`;
|
|
571
571
|
}
|
|
572
|
-
function mlxServerCommand(python, model, port2, host2) {
|
|
572
|
+
function mlxServerCommand(python, model, port2, host2, extraArgs = []) {
|
|
573
573
|
return {
|
|
574
574
|
cmd: python,
|
|
575
|
-
args: [
|
|
575
|
+
args: [
|
|
576
|
+
"-m",
|
|
577
|
+
"mlx_lm",
|
|
578
|
+
"server",
|
|
579
|
+
"--model",
|
|
580
|
+
model,
|
|
581
|
+
"--host",
|
|
582
|
+
host2,
|
|
583
|
+
"--port",
|
|
584
|
+
String(port2),
|
|
585
|
+
...extraArgs
|
|
586
|
+
]
|
|
576
587
|
};
|
|
577
588
|
}
|
|
589
|
+
function mlxSamplingArgs(s) {
|
|
590
|
+
if (!s) return [];
|
|
591
|
+
const a = [];
|
|
592
|
+
if (typeof s.temp === "number") a.push("--temp", String(s.temp));
|
|
593
|
+
if (typeof s.topP === "number") a.push("--top-p", String(s.topP));
|
|
594
|
+
if (typeof s.topK === "number") a.push("--top-k", String(s.topK));
|
|
595
|
+
if (typeof s.minP === "number") a.push("--min-p", String(s.minP));
|
|
596
|
+
return a;
|
|
597
|
+
}
|
|
578
598
|
|
|
579
599
|
// src/engines/slot-cache.ts
|
|
580
600
|
import { createHash } from "crypto";
|
|
@@ -705,6 +725,11 @@ function vllmServerCommand(python, model, port2, host2, tensorParallelSize = 1)
|
|
|
705
725
|
"vllm.entrypoints.openai.api_server",
|
|
706
726
|
"--model",
|
|
707
727
|
model,
|
|
728
|
+
// Serve under a fixed alias so requests can address the model by a stable name
|
|
729
|
+
// (TurboLLM's internal key is a display string with spaces). Mirrors mlx-lm's
|
|
730
|
+
// built-in `default_model` alias; see engineModelAlias() in compat.ts.
|
|
731
|
+
"--served-model-name",
|
|
732
|
+
"default_model",
|
|
708
733
|
"--host",
|
|
709
734
|
host2,
|
|
710
735
|
"--port",
|
|
@@ -781,7 +806,7 @@ var Manager = class {
|
|
|
781
806
|
}
|
|
782
807
|
}
|
|
783
808
|
const { cmd, args } = engineCommand(opts, port2, slotSavePath);
|
|
784
|
-
const child = spawn(cmd, args, { cwd: dirname2(cmd), windowsHide: true });
|
|
809
|
+
const child = spawn(cmd, args, { cwd: dirname2(cmd), windowsHide: true, env: pyEngineEnv(opts.engine.kind, this.store.dir()) });
|
|
785
810
|
child.stdout?.pipe(logStream, { end: false });
|
|
786
811
|
child.stderr?.pipe(logStream, { end: false });
|
|
787
812
|
this.state = "starting";
|
|
@@ -953,10 +978,22 @@ var Manager = class {
|
|
|
953
978
|
this.resolveExited?.();
|
|
954
979
|
}
|
|
955
980
|
async readiness(child, port2) {
|
|
956
|
-
const
|
|
981
|
+
const kind = this.opts?.engine.kind ?? "llama-server";
|
|
982
|
+
const deadline = Date.now() + readinessTimeoutMs(kind);
|
|
957
983
|
for (; ; ) {
|
|
958
984
|
await sleep(500);
|
|
959
985
|
if (this.child !== child || this.state !== "starting") return;
|
|
986
|
+
if (kind === "mlx" || kind === "vllm") {
|
|
987
|
+
const loadErr = detectPyLoadFailure(readTail(this.logPathStr, 200));
|
|
988
|
+
if (loadErr) {
|
|
989
|
+
if (this.child === child && this.state === "starting") {
|
|
990
|
+
this.state = "error";
|
|
991
|
+
this.errInfo = { code: "model_load_failed", message: loadErr, exitCode: -1, logTail: readTail(this.logPathStr, 20) };
|
|
992
|
+
child.kill("SIGKILL");
|
|
993
|
+
}
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
960
997
|
if (await probeReady(port2)) {
|
|
961
998
|
if (this.child === child && this.state === "starting") {
|
|
962
999
|
this.state = "running";
|
|
@@ -988,7 +1025,7 @@ var Manager = class {
|
|
|
988
1025
|
};
|
|
989
1026
|
function engineCommand(opts, port2, slotSavePath) {
|
|
990
1027
|
if (opts.engine.kind === "mlx") {
|
|
991
|
-
return mlxServerCommand(opts.engine.binPath, opts.modelPath, port2, "127.0.0.1");
|
|
1028
|
+
return mlxServerCommand(opts.engine.binPath, opts.modelPath, port2, "127.0.0.1", opts.extraArgs);
|
|
992
1029
|
}
|
|
993
1030
|
if (opts.engine.kind === "vllm") {
|
|
994
1031
|
return vllmServerCommand(opts.engine.binPath, opts.modelPath, port2, "127.0.0.1", opts.tensorParallelSize);
|
|
@@ -998,6 +1035,27 @@ function engineCommand(opts, port2, slotSavePath) {
|
|
|
998
1035
|
function readinessTimeoutMs(kind) {
|
|
999
1036
|
return kind === "vllm" ? 6e5 : 12e4;
|
|
1000
1037
|
}
|
|
1038
|
+
function pyEngineEnv(kind, dataDir) {
|
|
1039
|
+
if (kind !== "mlx" && kind !== "vllm") return void 0;
|
|
1040
|
+
const hfHome = join6(dataDir, "hf-cache");
|
|
1041
|
+
const hubCache = join6(hfHome, "hub");
|
|
1042
|
+
mkdirSync4(hubCache, { recursive: true });
|
|
1043
|
+
return {
|
|
1044
|
+
...process.env,
|
|
1045
|
+
HF_HUB_OFFLINE: "1",
|
|
1046
|
+
TRANSFORMERS_OFFLINE: "1",
|
|
1047
|
+
HF_HOME: hfHome,
|
|
1048
|
+
HF_HUB_CACHE: hubCache
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function detectPyLoadFailure(lines) {
|
|
1052
|
+
const text = lines.join("\n");
|
|
1053
|
+
const isLoadCrash = /Exception in thread[^\n]*_generate/.test(text) || /in load_default\b/.test(text) || /in load_model\b/.test(text) || /load_weights/.test(text);
|
|
1054
|
+
if (!isLoadCrash) return null;
|
|
1055
|
+
const errLine = [...lines].reverse().find((l) => /^[A-Za-z_][\w.]*(Error|Exception):/.test(l.trim()));
|
|
1056
|
+
const detail = (errLine ? errLine.trim() : "the model failed to load").slice(0, 200);
|
|
1057
|
+
return `MLX could not load this model \u2014 ${detail} This usually means the installed mlx-lm version does not support this model's architecture or quantization.`;
|
|
1058
|
+
}
|
|
1001
1059
|
function buildArgs(opts, port2, slotSavePath) {
|
|
1002
1060
|
const args = ["-m", opts.modelPath, "--host", "127.0.0.1", "--port", String(port2)];
|
|
1003
1061
|
const flags = opts.engine.capabilities.flags;
|
|
@@ -1715,6 +1773,10 @@ function engineAcceptsFormat(engineKind, format) {
|
|
|
1715
1773
|
if (engineKind === "vllm") return format === "mlx";
|
|
1716
1774
|
return format === "gguf";
|
|
1717
1775
|
}
|
|
1776
|
+
var ENGINE_MODEL_ALIAS = "default_model";
|
|
1777
|
+
function engineModelAlias(engineKind) {
|
|
1778
|
+
return engineKind === "mlx" || engineKind === "vllm" ? ENGINE_MODEL_ALIAS : null;
|
|
1779
|
+
}
|
|
1718
1780
|
|
|
1719
1781
|
// src/models/scanner.ts
|
|
1720
1782
|
import { existsSync as existsSync9, lstatSync, readdirSync as readdirSync3, readFileSync as readFileSync3, rmSync as rmSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -3760,13 +3822,14 @@ var ModelRouter = class {
|
|
|
3760
3822
|
const cfg2 = this.store.snapshot();
|
|
3761
3823
|
const sys = getSysInfo();
|
|
3762
3824
|
if (entry.format !== "gguf") {
|
|
3763
|
-
const
|
|
3825
|
+
const savedProfile = cfg2.modelProfiles[entry.key];
|
|
3764
3826
|
return {
|
|
3765
3827
|
engine,
|
|
3766
3828
|
model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: false },
|
|
3767
3829
|
modelPath: entry.path,
|
|
3768
|
-
|
|
3769
|
-
|
|
3830
|
+
// MLX honors sampling as launch defaults; vLLM takes no extra flags here.
|
|
3831
|
+
extraArgs: engine.kind === "mlx" ? mlxSamplingArgs(savedProfile?.sampling) : [],
|
|
3832
|
+
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
|
|
3770
3833
|
};
|
|
3771
3834
|
}
|
|
3772
3835
|
const saved = cfg2.modelProfiles[entry.key];
|
|
@@ -4405,13 +4468,15 @@ function registerApi(app2, d) {
|
|
|
4405
4468
|
}
|
|
4406
4469
|
let opts2;
|
|
4407
4470
|
if (entry.format !== "gguf") {
|
|
4408
|
-
const
|
|
4471
|
+
const savedProfile = cfg2.modelProfiles[entry.key];
|
|
4409
4472
|
opts2 = {
|
|
4410
4473
|
engine: active,
|
|
4411
4474
|
model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: false },
|
|
4412
4475
|
modelPath: entry.path,
|
|
4413
|
-
|
|
4414
|
-
|
|
4476
|
+
// MLX honors sampling as launch defaults; vLLM gets no extra flags here
|
|
4477
|
+
// (its multi-GPU shard count maps to --tensor-parallel-size below).
|
|
4478
|
+
extraArgs: active.kind === "mlx" ? mlxSamplingArgs(savedProfile?.sampling) : [],
|
|
4479
|
+
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
|
|
4415
4480
|
};
|
|
4416
4481
|
} else {
|
|
4417
4482
|
const saved = cfg2.modelProfiles[entry.key];
|
|
@@ -5420,7 +5485,9 @@ async function runGeneration(d, stream, ctx) {
|
|
|
5420
5485
|
if (!(k in SAMPLING_KEYS) && k !== "stop") samplingOverride[k] = v;
|
|
5421
5486
|
}
|
|
5422
5487
|
const reqBody = {
|
|
5423
|
-
|
|
5488
|
+
// mlx-lm / vLLM serve under a fixed alias and 404 on TurboLLM's internal key;
|
|
5489
|
+
// llama.cpp ignores the field. engineModelAlias() returns the right value per kind.
|
|
5490
|
+
model: engineModelAlias(d.registry.active()?.kind ?? "") ?? ms.model.key,
|
|
5424
5491
|
messages: engineMessages,
|
|
5425
5492
|
stream: true,
|
|
5426
5493
|
stream_options: { include_usage: true },
|
|
@@ -5668,7 +5735,7 @@ async function autoTitle(d, convId, prevMessages, assistantReply, target) {
|
|
|
5668
5735
|
method: "POST",
|
|
5669
5736
|
headers: { "Content-Type": "application/json" },
|
|
5670
5737
|
body: JSON.stringify({
|
|
5671
|
-
model: ms.model?.key,
|
|
5738
|
+
model: engineModelAlias(d.registry.active()?.kind ?? "") ?? ms.model?.key,
|
|
5672
5739
|
messages: titleMessages,
|
|
5673
5740
|
stream: false,
|
|
5674
5741
|
temperature: 0.3,
|
|
@@ -6021,6 +6088,8 @@ function registerGateway(app2, d) {
|
|
|
6021
6088
|
const status = d.manager.status();
|
|
6022
6089
|
const modelName = status.state === "running" ? status.model?.name ?? req.model ?? "local" : req.model ?? "local";
|
|
6023
6090
|
const oaiBody = mapToOpenAI(req);
|
|
6091
|
+
const oaiAlias = engineModelAlias(d.registry.active()?.kind ?? "");
|
|
6092
|
+
if (oaiAlias) oaiBody.model = oaiAlias;
|
|
6024
6093
|
d.manager.generationStart();
|
|
6025
6094
|
let res;
|
|
6026
6095
|
try {
|
|
@@ -6144,6 +6213,10 @@ function registerGateway(app2, d) {
|
|
|
6144
6213
|
if (parsedBody && maxLimit > 0) {
|
|
6145
6214
|
parsedBody.max_tokens = clampMaxTokens(parsedBody.max_tokens, maxLimit);
|
|
6146
6215
|
}
|
|
6216
|
+
if (parsedBody) {
|
|
6217
|
+
const alias = engineModelAlias(d.registry.active()?.kind ?? "");
|
|
6218
|
+
if (alias) parsedBody.model = alias;
|
|
6219
|
+
}
|
|
6147
6220
|
headers.delete("content-length");
|
|
6148
6221
|
init.body = parsedBody ? JSON.stringify(parsedBody) : "";
|
|
6149
6222
|
} else {
|