174 lines · 6.3 KB · TypeScript
RawDownload
import 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;}