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: ["-m", "mlx_lm", "server", "--model", model, "--host", host2, "--port", String(port2)]
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 deadline = Date.now() + readinessTimeoutMs(this.opts?.engine.kind ?? "llama-server");
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 savedGpu = cfg2.modelProfiles[entry.key]?.gpu;
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
- extraArgs: [],
3769
- tensorParallelSize: savedGpu?.tensorParallelSize
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 savedGpu = cfg2.modelProfiles[entry.key]?.gpu;
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
- extraArgs: [],
4414
- tensorParallelSize: savedGpu?.tensorParallelSize
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
- model: ms.model.key,
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 {