Skip to content

Commit 2e3fd7e

Browse files
committed
Add local Python runtime management, managed inference servers, and native download/recommender native libs
- python-runtime-manager.ts: venv lifecycle + stdin/stdout JSON-line worker protocol for MLX/vLLM backends - local-server-manager.ts: managed HTTP inference servers (MLX/vLLM/ROCm) with health checks and idle shutdown - RuntimeManager.tsx: UI for managing local backends - lib/: Rust native download manager (napi bindings) - ml/hardware-recommender/: training data + model for hardware-based recommendations - app/python/runtime_worker.py: worker script for managed Python runtimes
1 parent ea87621 commit 2e3fd7e

46 files changed

Lines changed: 11338 additions & 320 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎app/python/runtime_worker.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import os
4+
import platform
5+
import resource
6+
import sys
7+
import time
8+
9+
PROTOCOL_VERSION = 1
10+
STARTED = time.monotonic()
11+
12+
def log(level, event, **fields):
13+
print(json.dumps({"timestamp": time.time(), "level": level, "event": event, **fields}), file=sys.stderr, flush=True)
14+
15+
def response(request_id, ok, result=None, code=None, message=None):
16+
payload = {"protocol": PROTOCOL_VERSION, "id": request_id, "ok": ok}
17+
if ok: payload["result"] = result
18+
else: payload["error"] = {"code": code, "message": message}
19+
print(json.dumps(payload), flush=True)
20+
21+
log("info", "worker_started", protocol=PROTOCOL_VERSION, pid=os.getpid())
22+
for line in sys.stdin:
23+
try:
24+
request = json.loads(line)
25+
request_id = str(request.get("id", ""))
26+
if request.get("protocol") != PROTOCOL_VERSION:
27+
response(request_id, False, code="protocol_mismatch", message=f"Expected protocol {PROTOCOL_VERSION}")
28+
continue
29+
method = request.get("method")
30+
if method == "health":
31+
response(request_id, True, {"status": "ok", "protocol": PROTOCOL_VERSION, "pid": os.getpid(), "python": platform.python_version(), "uptimeSeconds": time.monotonic() - STARTED})
32+
elif method == "metrics":
33+
usage = resource.getrusage(resource.RUSAGE_SELF)
34+
response(request_id, True, {"uptimeSeconds": time.monotonic() - STARTED, "maxRssKb": usage.ru_maxrss, "userCpuSeconds": usage.ru_utime, "systemCpuSeconds": usage.ru_stime})
35+
elif method == "shutdown":
36+
response(request_id, True, {"shuttingDown": True}); log("info", "worker_shutdown"); break
37+
else:
38+
response(request_id, False, code="method_not_found", message=f"Unknown method: {method}")
39+
except Exception as error:
40+
log("error", "request_failed", error=str(error))
41+
response("", False, code="invalid_request", message=str(error))

‎app/src/local-server-manager.test.ts‎

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { describe, it, expect } from "vitest";
2-
import { buildRuntimeProbe, buildServerCommand, describeSpawnFailure } from "./local-server-manager";
2+
import { buildRuntimeProbe, buildServerCommand, describeSpawnFailure, identityMatches, explainStartupFailure } from "./local-server-manager";
33

44
describe("buildServerCommand", () => {
55
it("builds an mlx_lm.server invocation with the default python", () => {
6-
const { command, args } = buildServerCommand("mlx", "mlx-community/Llama-3.2-3B-Instruct-4bit", {});
6+
const { command, args } = buildServerCommand("mlx", "mlx-community/Llama-3.2-3B-Instruct-4bit", {}, "darwin", 49151);
77
expect(command).toBe("python3");
88
expect(args).toContain("mlx_lm.server");
99
expect(args).toContain("mlx-community/Llama-3.2-3B-Instruct-4bit");
@@ -12,54 +12,67 @@ describe("buildServerCommand", () => {
1212
});
1313

1414
it("respects a custom python interpreter path", () => {
15-
const { command } = buildServerCommand("mlx", "some/model", { mlxPythonPath: "/opt/python/bin/python3.12" });
15+
const { command } = buildServerCommand("mlx", "some/model", { mlxPythonPath: "/opt/python/bin/python3.12" }, "darwin", 49151);
1616
expect(command).toBe("/opt/python/bin/python3.12");
1717
});
1818

1919
it("builds a llama-server invocation with full GPU offload for rocm", () => {
2020
const { command, args } = buildServerCommand("rocm", "/models/llama.gguf", {
2121
rocmServerPath: "/opt/rocm-llama/llama-server",
22-
});
22+
}, "linux", 49152);
2323
expect(command).toBe("/opt/rocm-llama/llama-server");
2424
expect(args).toEqual(
2525
expect.arrayContaining(["-m", "/models/llama.gguf", "--n-gpu-layers", "999"])
2626
);
2727
});
2828

2929
it("falls back to PATH lookup when no rocm binary path is configured", () => {
30-
const { command } = buildServerCommand("rocm", "/models/llama.gguf", {});
30+
const { command } = buildServerCommand("rocm", "/models/llama.gguf", {}, "linux", 49152);
3131
expect(command).toBe("llama-server");
3232
});
3333

34-
it("uses distinct fixed ports per backend so both can run at once", () => {
35-
const mlx = buildServerCommand("mlx", "m", {});
36-
const rocm = buildServerCommand("rocm", "m", {});
37-
const vllm = buildServerCommand("vllm", "m", {});
34+
it("uses the dynamically allocated port supplied to each backend", () => {
35+
const mlx = buildServerCommand("mlx", "m", {}, "darwin", 49151);
36+
const rocm = buildServerCommand("rocm", "m", {}, "linux", 49152);
37+
const vllm = buildServerCommand("vllm", "m", {}, "linux", 49153);
3838
const portOf = (args: string[]) => args[args.indexOf("--port") + 1];
3939
expect(portOf(mlx.args)).not.toBe(portOf(rocm.args));
4040
expect(new Set([portOf(mlx.args), portOf(rocm.args), portOf(vllm.args)]).size).toBe(3);
4141
});
4242

4343
it("builds a managed vLLM OpenAI server command", () => {
44-
const { command, args } = buildServerCommand("vllm", "meta-llama/Llama-3.1-8B-Instruct", {}, "linux");
44+
const { command, args } = buildServerCommand("vllm", "meta-llama/Llama-3.1-8B-Instruct", {}, "linux", 49153);
4545
expect(command).toBe("vllm");
4646
expect(args).toEqual(
4747
expect.arrayContaining(["serve", "meta-llama/Llama-3.1-8B-Instruct", "--host", "127.0.0.1"])
4848
);
4949
});
5050

5151
it("allows a vLLM command override without requiring one", () => {
52-
const { command } = buildServerCommand("vllm", "some/model", { vllmCommand: "/opt/vllm/bin/vllm" });
52+
const { command } = buildServerCommand("vllm", "some/model", { vllmCommand: "/opt/vllm/bin/vllm" }, "linux", 49153);
5353
expect(command).toBe("/opt/vllm/bin/vllm");
5454
});
5555

5656
it("launches vLLM through WSL automatically on Windows", () => {
57-
const { command, args } = buildServerCommand("vllm", "some/model", {}, "win32");
57+
const { command, args } = buildServerCommand("vllm", "some/model", {}, "win32", 49153);
5858
expect(command).toBe("wsl.exe");
5959
expect(args.slice(0, 4)).toEqual(["--", "vllm", "serve", "some/model"]);
6060
});
6161
});
6262

63+
describe("runtime identity health", () => {
64+
it("requires an OpenAI model list containing the expected model", () => {
65+
expect(identityMatches({ data: [{ id: "meta-llama/Llama-3.1-8B-Instruct" }] }, "meta-llama/Llama-3.1-8B-Instruct")).toBe(true);
66+
expect(identityMatches({ status: "ok" }, "meta-llama/Llama-3.1-8B-Instruct")).toBe(false);
67+
expect(identityMatches({ data: [{ id: "another-model" }] }, "meta-llama/Llama-3.1-8B-Instruct")).toBe(false);
68+
});
69+
70+
it("turns common startup logs into actionable explanations", () => {
71+
expect(explainStartupFailure("vllm", ["CUDA out of memory"])).toMatch(/RAM or VRAM/);
72+
expect(explainStartupFailure("mlx", ["ModuleNotFoundError: No module named mlx_lm"])).toMatch(/pip install mlx-lm/);
73+
});
74+
});
75+
6376
describe("describeSpawnFailure", () => {
6477
it("points mlx failures at the mlx-lm install", () => {
6578
expect(describeSpawnFailure("mlx")).toMatch(/mlx-lm/);

0 commit comments

Comments
 (0)