266 lines · 9.1 KB · TypeScript
RawDownload
import { connect } from "node:net";import { hostname } from "node:os";import { existsSync, readFileSync } from "node:fs";import { join, resolve, sep } from "node:path";import type { RouteRegistrar } from "../../http/route-registrar";import type { SystemConfigResponse } from "../models/types";import { badRequest, notFound } from "../../core/errors";import { parseJsonObjectBody } from "../../core/validation";import { findObservedInferenceProcess } from "../../core/function-observability";import { estimateWeightsSizeBytes } from "../models/model-browser";import { getGpuInfo } from "./platform/gpu";import { getSystemRuntimeInfo } from "../engines/runtimes/runtime-info";import { buildCompatibilityReport } from "./platform/compatibility-report";import { registerMonitoringRoutes } from "./metrics-routes";import { registerLogsRoutes } from "./logs-routes";import { registerUsageRoutes } from "./usage-routes";const SYSTEM_SERVICE_CHECK_HOST = "127.0.0.1";const SYSTEM_COMPAT_SERVICE_CHECK_TIMEOUT_MS = 500;const SYSTEM_DEFAULT_SERVICE_CHECK_TIMEOUT_MS = 1_000; export const registerSystemRoutes: RouteRegistrar = (app, context) => {  const checkService = (    host: string,    port: number,    timeoutMs = SYSTEM_DEFAULT_SERVICE_CHECK_TIMEOUT_MS,  ): Promise<boolean> => {    return new Promise((resolve) => {      const socket = connect({ port, host });      let settled = false;      const finalize = (result: boolean): void => {        if (settled) return;        settled = true;        socket.destroy();        resolve(result);      };       socket.setTimeout(timeoutMs);      socket.once("connect", () => finalize(true));      socket.once("timeout", () => finalize(false));      socket.once("error", () => finalize(false));    });  };   app.get("/status", async (ctx) => {    const current = await findObservedInferenceProcess(context, "status");    return ctx.json({      running: Boolean(current),      process: current,      inference_port: context.config.inference_port,      launching: context.launchState.getLaunchingRecipeId(),      launch_failures: context.launchFailureBudget.listActive(),    });  });   app.get("/gpus", async (ctx) => {    const gpus = getGpuInfo();    return ctx.json({      count: gpus.length,      gpus,    });  });   app.get("/compat", async (ctx) => {    const known = await findObservedInferenceProcess(context, "compat");    const runtime = await getSystemRuntimeInfo(context.config, known);    const portOpen = await checkService(      SYSTEM_SERVICE_CHECK_HOST,      context.config.inference_port,      SYSTEM_COMPAT_SERVICE_CHECK_TIMEOUT_MS,    );     const report = buildCompatibilityReport({      runtime,      inference_port: context.config.inference_port,      inference_port_open: portOpen,      inference_process_known: Boolean(known),      gpu_monitoring: runtime.gpu_monitoring,    });     return ctx.json(report);  });   app.post("/vram-calculator", async (ctx) => {    const body = await parseJsonObjectBody(ctx);     const model = typeof body["model"] === "string" ? body["model"].trim() : "";    const contextLength = Number(body["context_length"] ?? 0);    const tpSize = Number(body["tp_size"] ?? 1);    const kvDtype = typeof body["kv_dtype"] === "string" ? body["kv_dtype"] : "auto";     if (!model) {      throw badRequest("model is required");    }    if (!Number.isFinite(contextLength) || contextLength <= 0) {      throw badRequest("context_length must be a positive number");    }    if (!Number.isFinite(tpSize) || tpSize <= 0) {      throw badRequest("tp_size must be a positive number");    }     const resolved = resolve(model);    const modelsRoot = resolve(context.config.models_dir);    const rootPrefix = modelsRoot.endsWith(sep) ? modelsRoot : modelsRoot + sep;    if (!resolved.startsWith(rootPrefix)) {      throw badRequest("model must be inside models_dir");    }    if (!existsSync(resolved)) {      throw notFound("Model path not found");    }     const weightsBytes = estimateWeightsSizeBytes(resolved, false);    if (!weightsBytes || weightsBytes <= 0) {      throw notFound("Model weights not found");    }     let config: Record<string, unknown> = {};    const configPath = join(resolved, "config.json");    if (existsSync(configPath)) {      try {        const raw = readFileSync(configPath, "utf-8");        config = JSON.parse(raw) as Record<string, unknown>;      } catch {        config = {};      }    }     const getNumber = (value: unknown): number | undefined => {      if (typeof value === "number" && Number.isFinite(value)) return value;      if (typeof value === "string" && value.trim() && !Number.isNaN(Number(value))) {        return Number(value);      }      return undefined;    };     const layerCount =      getNumber(config["num_hidden_layers"]) ??      getNumber(config["n_layer"]) ??      getNumber(config["num_layers"]);    const hiddenSize =      getNumber(config["hidden_size"]) ??      getNumber(config["n_embd"]) ??      getNumber(config["d_model"]) ??      getNumber(config["dim"]);    const headCount =      getNumber(config["num_attention_heads"]) ??      getNumber(config["n_head"]) ??      getNumber(config["num_heads"]);    const keyValueHeadCount =      getNumber(config["num_key_value_heads"]) ?? getNumber(config["num_kv_heads"]) ?? headCount;    const headDim =      getNumber(config["head_dim"]) ??      (hiddenSize && headCount ? hiddenSize / headCount : undefined);     const kvBytesPerValue = kvDtype.toLowerCase() === "fp8" ? 1 : 2;    let kvCacheBytes = 0;    if (layerCount && keyValueHeadCount && headDim) {      kvCacheBytes = contextLength * layerCount * keyValueHeadCount * headDim * 2 * kvBytesPerValue;    }     const weightsTotalGb = weightsBytes / 1024 ** 3;    const weightsPerGpuGb = weightsTotalGb / tpSize;    const kvCachePerGpuGb = kvCacheBytes > 0 ? kvCacheBytes / 1024 ** 3 / tpSize : 0;    const activationsPerGpuGb = Math.max(0.5, weightsPerGpuGb * 0.1);    const overheadPerGpuGb = 2.0;    const perGpuGb = weightsPerGpuGb + kvCachePerGpuGb + activationsPerGpuGb + overheadPerGpuGb;    const totalGb = perGpuGb * tpSize;     const gpus = getGpuInfo();    let perGpuCapacityGb = 0;    if (gpus.length >= tpSize && tpSize > 0) {      const candidates = gpus.slice(0, tpSize).map((gpu) => gpu.memory_total_mb / 1024);      perGpuCapacityGb = Math.min(...candidates);    }     const fits = perGpuCapacityGb > 0 ? perGpuGb <= perGpuCapacityGb : true;    const utilizationPercent = perGpuCapacityGb > 0 ? (perGpuGb / perGpuCapacityGb) * 100 : 0;     return ctx.json({      model_size_gb: weightsTotalGb,      context_memory_gb: kvCachePerGpuGb * tpSize,      overhead_gb: overheadPerGpuGb,      total_gb: totalGb,      fits_in_vram: fits,      fits,      utilization_percent: utilizationPercent,      breakdown: {        model_weights_gb: weightsPerGpuGb,        kv_cache_gb: kvCachePerGpuGb,        activations_gb: activationsPerGpuGb,        per_gpu_gb: perGpuGb,        total_gb: totalGb,      },    });  });   app.get("/config", async (ctx) => {    const services: Array<{      name: string;      port: number;      internal_port: number;      protocol: string;      status: string;      description?: string | null;    }> = [];    services.push({      name: "Controller",      port: context.config.port,      internal_port: context.config.port,      protocol: "http",      status: "running",      description: "Controller service (Bun/Hono)",    });     const current = await findObservedInferenceProcess(context, "config");    const inferenceStatus = current ? "running" : "stopped";     services.push({      name: "Inference runtime",      port: context.config.inference_port,      internal_port: context.config.inference_port,      protocol: "http",      status: inferenceStatus,      description: "Inference backend (vLLM, SGLang, llama.cpp, or MLX)",    });     const frontendReachable = await checkService("localhost", 3000);    services.push({      name: "Frontend",      port: 3000,      internal_port: 3000,      protocol: "http",      status: frontendReachable ? "running" : "stopped",      description: "Next.js web UI",    });     const runtime = await getSystemRuntimeInfo(context.config, current);     const payload: SystemConfigResponse = {      config: {        host: context.config.host,        port: context.config.port,        inference_port: context.config.inference_port,        api_key_configured: Boolean(context.config.api_key),        models_dir: context.config.models_dir,        data_dir: context.config.data_dir,        db_path: context.config.db_path,        sglang_python: context.config.sglang_python ?? null,        llama_bin: context.config.llama_bin ?? null,        mlx_python: context.config.mlx_python ?? null,      },      services,      environment: {        controller_url: `http://${hostname()}:${context.config.port}`,        inference_url: `http://${hostname()}:${context.config.inference_port}`,        frontend_url: `http://${hostname()}:3000`,      },      runtime,    };     return ctx.json(payload);  });   registerMonitoringRoutes(app, context);  registerLogsRoutes(app, context);  registerUsageRoutes(app, context);};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t