feat(cluster,api,models,cli): add cluster-aware model catalog deployments and request routing

This commit is contained in:
2026-04-20 23:00:50 +00:00
parent 83cacd0cf1
commit 4f2266e1b7
55 changed files with 3970 additions and 1630 deletions
+40 -12
View File
@@ -8,7 +8,7 @@ import process from 'node:process';
import { promises as fs } from 'node:fs';
import { execSync } from 'node:child_process';
import { logger } from './logger.ts';
import { theme, symbols } from './colors.ts';
import { symbols, theme } from './colors.ts';
import { PATHS, VERSION } from './constants.ts';
/**
@@ -122,7 +122,9 @@ WantedBy=multi-user.target
// Display GPU status
await this.displayGpuStatus();
} catch (error) {
logger.error(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`);
logger.error(
`Failed to get status: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
@@ -152,9 +154,15 @@ WantedBy=multi-user.target
logger.log('');
if (isActive) {
logger.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`);
logger.log(
`${symbols.running} ${theme.success('Service:')} ${
theme.statusActive('active (running)')
}`,
);
} else {
logger.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`);
logger.log(
`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`,
);
}
if (pid || memory) {
@@ -166,7 +174,9 @@ WantedBy=multi-user.target
logger.log('');
} catch (_error) {
logger.log('');
logger.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`);
logger.log(
`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`,
);
logger.log('');
}
}
@@ -177,8 +187,11 @@ WantedBy=multi-user.target
private async displayContainerStatus(): Promise<void> {
try {
// Try to get container info from docker
const output = execSync('docker ps --filter "name=modelgrid" --format "{{.Names}}\\t{{.Status}}"', { encoding: 'utf-8' });
const lines = output.trim().split('\n').filter(l => l.trim());
const output = execSync(
'docker ps --filter "name=modelgrid" --format "{{.Names}}\\t{{.Status}}"',
{ encoding: 'utf-8' },
);
const lines = output.trim().split('\n').filter((l) => l.trim());
if (lines.length === 0) {
logger.info('Containers: None running');
@@ -191,7 +204,11 @@ WantedBy=multi-user.target
const [name, status] = line.split('\t');
const isUp = status?.toLowerCase().includes('up');
logger.log(` ${isUp ? symbols.running : symbols.stopped} ${theme.highlight(name)} - ${isUp ? theme.success(status) : theme.dim(status)}`);
logger.log(
` ${isUp ? symbols.running : symbols.stopped} ${theme.highlight(name)} - ${
isUp ? theme.success(status) : theme.dim(status)
}`,
);
}
logger.log('');
} catch (_error) {
@@ -205,7 +222,10 @@ WantedBy=multi-user.target
private async displayGpuStatus(): Promise<void> {
try {
// Try nvidia-smi
const output = execSync('nvidia-smi --query-gpu=name,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits', { encoding: 'utf-8' });
const output = execSync(
'nvidia-smi --query-gpu=name,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits',
{ encoding: 'utf-8' },
);
const lines = output.trim().split('\n');
if (lines.length === 0) {
@@ -215,11 +235,15 @@ WantedBy=multi-user.target
logger.info(`GPUs (${lines.length}):`);
for (const line of lines) {
const [name, util, memUsed, memTotal] = line.split(',').map(s => s.trim());
const [name, util, memUsed, memTotal] = line.split(',').map((s) => s.trim());
const memPercent = Math.round((parseInt(memUsed) / parseInt(memTotal)) * 100);
logger.log(` ${symbols.info} ${theme.gpuNvidia(name)}`);
logger.log(` Utilization: ${theme.highlight(util + '%')} Memory: ${theme.info(memUsed)}/${memTotal} MB (${memPercent}%)`);
logger.log(
` Utilization: ${theme.highlight(util + '%')} Memory: ${
theme.info(memUsed)
}/${memTotal} MB (${memPercent}%)`,
);
}
logger.log('');
} catch (_error) {
@@ -275,7 +299,11 @@ WantedBy=multi-user.target
logger.log('');
logger.error('No configuration found');
logger.log(` ${theme.dim('Config file:')} ${PATHS.CONFIG_FILE}`);
logger.log(` ${theme.dim('Run')} ${theme.command('modelgrid config init')} ${theme.dim('to create one')}`);
logger.log(
` ${theme.dim('Run')} ${theme.command('modelgrid config init')} ${
theme.dim('to create one')
}`,
);
logger.log('');
throw new Error('Configuration not found');
}