193 lines · 6.4 KB · TypeScript
RawDownload
// CRITICALimport type { Recipe } from "./types"; function safeNumber(value: unknown): number | undefined {  if (value === null || value === undefined || value === "") return undefined;  const num = typeof value === "number" ? value : Number(value);  return Number.isFinite(num) ? num : undefined;} export function slugifyRecipeId(input: string): string {  return input    .trim()    .toLowerCase()    .replace(/[^a-z0-9]+/g, "-")    .replace(/^-+|-+$/g, "");} export function recipeToCommand(recipe: Recipe): string {  const lines: string[] = [];   const tp = recipe.tp || recipe.tensor_parallel_size || 1;  const pp = recipe.pp || recipe.pipeline_parallel_size || 1;  const totalGpus = tp * pp;  if (totalGpus > 1) {    const gpuIds = Array.from({ length: totalGpus }, (_, i) => i).join(",");    lines.push(`CUDA_VISIBLE_DEVICES=${gpuIds} \\`);  }   const backend = recipe.backend || "vllm";  if (backend === "sglang") {    lines.push(`python -m sglang.launch_server \\`);    lines.push(`  --model-path ${recipe.model_path} \\`);  } else {    lines.push(`vllm serve ${recipe.model_path} \\`);  }   const args: [string, string | number | boolean | undefined][] = [    ["--tensor-parallel-size", tp],    ["--pipeline-parallel-size", pp > 1 ? pp : undefined],    ["--dtype", recipe.dtype],    ["--max-model-len", recipe.max_model_len],    [      "--block-size",      safeNumber(recipe.block_size ?? (recipe.extra_args?.block_size as number | undefined)),    ],    ["--max-num-seqs", recipe.max_num_seqs],    ["--max-num-batched-tokens", recipe.max_num_batched_tokens],    ["--gpu-memory-utilization", recipe.gpu_memory_utilization],    [      "--swap-space",      safeNumber(recipe.swap_space ?? (recipe.extra_args?.swap_space as number | undefined)),    ],    ["--kv-cache-dtype", recipe.kv_cache_dtype],    ["--quantization", recipe.quantization],    [      "--reasoning-parser",      (recipe.reasoning_parser ?? (recipe.extra_args?.reasoning_parser as string | undefined)) as        | string        | undefined,    ],    ["--tool-call-parser", recipe.tool_call_parser],    ["--served-model-name", recipe.served_model_name],  ];   if (recipe.enable_auto_tool_choice) args.push(["--enable-auto-tool-choice", true]);  if (recipe.disable_custom_all_reduce ?? recipe.extra_args?.disable_custom_all_reduce)    args.push(["--disable-custom-all-reduce", true]);  if (recipe.trust_remote_code ?? recipe.extra_args?.trust_remote_code)    args.push(["--trust-remote-code", true]);  if (recipe.disable_log_requests ?? recipe.extra_args?.disable_log_requests)    args.push(["--disable-log-requests", true]);  if (recipe.enable_expert_parallel ?? recipe.extra_args?.enable_expert_parallel)    args.push(["--enable-expert-parallel", true]);   for (const [flag, value] of args) {    if (value === undefined || value === null || value === "") continue;    if (typeof value === "boolean") {      if (value) lines.push(`  ${flag} \\`);    } else {      lines.push(`  ${flag} ${value} \\`);    }  }   lines.push(`  --host ${recipe.host || "0.0.0.0"} \\`);  lines.push(`  --port ${recipe.port || 8000}`);   return lines.join("\n");} export function parseCommand(command: string, existingRecipe?: Partial<Recipe>): Recipe {  const recipe: Recipe = {    id: existingRecipe?.id || "",    name: existingRecipe?.name || "",    model_path: existingRecipe?.model_path || "",    backend: "vllm",    extra_args: {},  };   const normalizedCmd = command    .replace(/\\\s*\n/g, " ")    .replace(/\s+/g, " ")    .trim();  if (normalizedCmd.includes("sglang")) recipe.backend = "sglang";   const modelMatch = normalizedCmd.match(/(?:vllm serve|--model-path|--model)\s+(\/[^\s]+)/);  if (modelMatch) recipe.model_path = modelMatch[1];   const cudaMatch = normalizedCmd.match(/CUDA_VISIBLE_DEVICES=([\d,]+)/);  if (cudaMatch) recipe.extra_args!.cuda_visible_devices = cudaMatch[1];   const flagPattern = /--([\w-]+)(?:\s+([^\s-][^\s]*))?/g;  let match: RegExpExecArray | null;  while ((match = flagPattern.exec(normalizedCmd)) !== null) {    const [, flag, value] = match;    switch (flag) {      case "tensor-parallel-size":        recipe.tp = parseInt(value) || 1;        recipe.tensor_parallel_size = recipe.tp;        break;      case "pipeline-parallel-size":        recipe.pp = parseInt(value) || 1;        recipe.pipeline_parallel_size = recipe.pp;        break;      case "max-model-len":        recipe.max_model_len = parseInt(value) || undefined;        break;      case "gpu-memory-utilization":        recipe.gpu_memory_utilization = parseFloat(value) || 0.9;        break;      case "max-num-seqs":        recipe.max_num_seqs = parseInt(value) || undefined;        break;      case "max-num-batched-tokens":        recipe.max_num_batched_tokens = parseInt(value) || undefined;        break;      case "kv-cache-dtype":        recipe.kv_cache_dtype = value;        break;      case "quantization":        recipe.quantization = value;        break;      case "dtype":        recipe.dtype = value;        break;      case "tool-call-parser":        recipe.tool_call_parser = value;        break;      case "served-model-name":        recipe.served_model_name = value;        break;      case "model":      case "model-path":        if (!recipe.model_path && value) recipe.model_path = value;        break;      case "port":        recipe.port = parseInt(value) || 8000;        break;      case "block-size":        recipe.block_size = parseInt(value);        break;      case "swap-space":        recipe.swap_space = parseInt(value);        break;      case "reasoning-parser":        recipe.reasoning_parser = value;        break;      case "enable-auto-tool-choice":        recipe.enable_auto_tool_choice = true;        break;      case "disable-custom-all-reduce":        recipe.disable_custom_all_reduce = true;        break;      case "trust-remote-code":        recipe.trust_remote_code = true;        break;      case "disable-log-requests":        recipe.disable_log_requests = true;        break;      case "enable-expert-parallel":        recipe.enable_expert_parallel = true;        break;    }  }   if (!recipe.id && recipe.model_path) {    recipe.id = slugifyRecipeId(recipe.model_path.split("/").pop() || "new-recipe");  }  if (!recipe.name && recipe.model_path) {    recipe.name = recipe.model_path.split("/").pop() || "New Recipe";  }   return recipe;}