badgr-cli 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/badgr.js +6 -0
- package/src/commands/run.js +58 -13
- package/src/commands/serve.js +15 -11
- package/src/commands/test-run.js +147 -0
- package/tests/commands.test.js +41 -1
package/package.json
CHANGED
package/src/badgr.js
CHANGED
|
@@ -11,6 +11,7 @@ import { runCommand } from './commands/run.js';
|
|
|
11
11
|
import { serveCommand } from './commands/serve.js';
|
|
12
12
|
import { modelsCommand } from './commands/models.js';
|
|
13
13
|
import { capacityCommand } from './commands/capacity.js';
|
|
14
|
+
import { testCommand } from './commands/test-run.js';
|
|
14
15
|
|
|
15
16
|
const HELP = `
|
|
16
17
|
${chalk.bold('badgr')} — run or serve GPU workloads from one command
|
|
@@ -23,9 +24,13 @@ ${chalk.bold('COMMANDS')}
|
|
|
23
24
|
${chalk.cyan('badgr logs <id>')} Stream logs for a running job or endpoint
|
|
24
25
|
${chalk.cyan('badgr down <id>')} Stop a deployment and end billing
|
|
25
26
|
${chalk.cyan('badgr receipts')} Show cost history
|
|
27
|
+
${chalk.cyan('badgr test')} Run an end-to-end test (provision → run → teardown)
|
|
26
28
|
${chalk.cyan('badgr capacity')} Check what GPU capacity is available right now
|
|
27
29
|
|
|
28
30
|
${chalk.bold('EXAMPLES')}
|
|
31
|
+
${chalk.dim('# Verify the stack works end-to-end:')}
|
|
32
|
+
badgr test
|
|
33
|
+
|
|
29
34
|
${chalk.dim('# Simplest — Badgr picks the GPU (RunPod, reliable):')}
|
|
30
35
|
badgr run python train.py
|
|
31
36
|
badgr serve meta-llama/Llama-3.1-8B-Instruct
|
|
@@ -98,6 +103,7 @@ async function main() {
|
|
|
98
103
|
case 'receipts': return receiptsCommand(config, rest, chalk);
|
|
99
104
|
case 'models': return modelsCommand(config, chalk);
|
|
100
105
|
case 'capacity': return capacityCommand(config, rest, chalk);
|
|
106
|
+
case 'test': return testCommand(config, chalk);
|
|
101
107
|
// legacy aliases kept for compatibility
|
|
102
108
|
case 'up': return upCommand(config, rest, chalk);
|
|
103
109
|
case 'config': {
|
package/src/commands/run.js
CHANGED
|
@@ -270,12 +270,53 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Infer a workload profile name from the command the user wants to run.
|
|
275
|
+
* The backend uses this to enforce a VRAM floor when picking a GPU.
|
|
276
|
+
*
|
|
277
|
+
* Note: \b word boundaries are intentionally avoided for keyword checks
|
|
278
|
+
* because keywords commonly appear inside filenames joined by underscores
|
|
279
|
+
* (e.g. lora_train.py, run_sdxl.py) where `_` is a word character.
|
|
280
|
+
*/
|
|
281
|
+
export function inferWorkload(command) {
|
|
282
|
+
if (!command || command.length === 0) return 'general';
|
|
283
|
+
|
|
284
|
+
const cmd = command.join(' ').toLowerCase();
|
|
285
|
+
|
|
286
|
+
// Trivial one-liner / smoke test
|
|
287
|
+
if (/print\s*\(|['"]hello/.test(cmd) && cmd.length < 80) return 'smoke_test';
|
|
288
|
+
|
|
289
|
+
// LoRA / QLoRA / PEFT fine-tuning
|
|
290
|
+
if (/lora|qlora|finetune|fine[_-]tun|peft/.test(cmd)) return 'lora_finetune';
|
|
291
|
+
|
|
292
|
+
// Diffusion / image generation
|
|
293
|
+
if (/diffusion|stable.?diff|sdxl|sd.?xl|comfyui|a1111|invoke|kohya/.test(cmd)) return 'image_gen';
|
|
294
|
+
|
|
295
|
+
// vLLM / TGI / inference server
|
|
296
|
+
if (/vllm|[^a-z]tgi[^a-z]|^tgi\b|tgi$|text.generation.inference/.test(cmd)) return 'inference_small';
|
|
297
|
+
|
|
298
|
+
// Explicit training script
|
|
299
|
+
if (/\btrain\.py\b/.test(cmd)) return 'lora_finetune';
|
|
300
|
+
|
|
301
|
+
return 'general';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const WORKLOAD_LABELS = {
|
|
305
|
+
smoke_test: 'smoke test',
|
|
306
|
+
general: 'GPU job',
|
|
307
|
+
lora_finetune: 'fine-tuning (40GB+ VRAM)',
|
|
308
|
+
image_gen: 'image generation',
|
|
309
|
+
inference_small: 'inference (7B–8B model)',
|
|
310
|
+
inference_medium: 'inference (30B–34B model)',
|
|
311
|
+
inference_large: 'inference (70B+ model)',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Ask the backend for the best available GPU for a given workload.
|
|
315
|
+
// Routes by: workload → min VRAM → provider tier → cheapest match.
|
|
316
|
+
// Returns { gpu, region, price, workload, workload_desc } or null when nothing is available.
|
|
317
|
+
async function findAutoGpu(config, chalk, tier = '1', workload = 'general') {
|
|
277
318
|
try {
|
|
278
|
-
const params = new URLSearchParams({ max_price: '10', tier });
|
|
319
|
+
const params = new URLSearchParams({ max_price: '10', tier, workload });
|
|
279
320
|
return await callApi(`/capacity/auto?${params}`, {
|
|
280
321
|
apiKey: config.apiKey,
|
|
281
322
|
baseUrl: config.baseUrl,
|
|
@@ -319,17 +360,21 @@ export async function runCommand(config, args, chalk) {
|
|
|
319
360
|
let gpu = flags.gpu ? flags.gpu.toUpperCase().replace('-', '_') : null;
|
|
320
361
|
let autoRegion = null;
|
|
321
362
|
|
|
363
|
+
// Infer workload from the command so the backend can apply the correct VRAM floor.
|
|
364
|
+
const workload = command ? inferWorkload(command) : 'general';
|
|
365
|
+
const workloadLabel = WORKLOAD_LABELS[workload] || 'GPU job';
|
|
366
|
+
|
|
322
367
|
if (!gpu) {
|
|
323
|
-
console.log(chalk.bold(
|
|
368
|
+
console.log(chalk.bold(`\n⚡ Running ${workloadLabel}\n`));
|
|
324
369
|
if (command) console.log(` ${chalk.bold('Command:')} ${command.join(' ')}`);
|
|
325
370
|
if (image) console.log(` ${chalk.bold('Image:')} ${image}`);
|
|
326
371
|
if (effectiveTier === '2') console.log(` ${chalk.dim('(budget mode — searching all providers)')}`);
|
|
327
372
|
console.log();
|
|
328
|
-
process.stdout.write(chalk.dim(' Finding GPU...'));
|
|
373
|
+
process.stdout.write(chalk.dim(' Finding best GPU...'));
|
|
329
374
|
|
|
330
375
|
let best;
|
|
331
376
|
try {
|
|
332
|
-
best = await findAutoGpu(config, chalk, effectiveTier);
|
|
377
|
+
best = await findAutoGpu(config, chalk, effectiveTier, workload);
|
|
333
378
|
} catch (err) {
|
|
334
379
|
process.stdout.write('\n');
|
|
335
380
|
console.error(chalk.red(`\n ✗ Could not find GPU capacity: ${err.message}\n`));
|
|
@@ -344,17 +389,17 @@ export async function runCommand(config, args, chalk) {
|
|
|
344
389
|
process.exit(1);
|
|
345
390
|
}
|
|
346
391
|
|
|
347
|
-
|
|
392
|
+
const vramNote = best.min_vram_gb ? chalk.dim(` (${best.min_vram_gb}GB VRAM)`) : '';
|
|
393
|
+
console.log(`\n ${chalk.bold('Selected:')} ${chalk.cyan(best.gpu)} in ${best.region} — ${chalk.green('$' + best.price.toFixed(2) + '/hr')}${vramNote}`);
|
|
348
394
|
console.log();
|
|
349
395
|
|
|
350
|
-
if (process.stdin.isTTY) {
|
|
351
|
-
|
|
396
|
+
if (effectiveTier === '2' && process.stdin.isTTY) {
|
|
397
|
+
// Budget mode: confirm because the user is being routed to a less reliable provider.
|
|
398
|
+
const answer = await askConfirm(` Press ${chalk.bold('Enter')} to run on budget provider, or ${chalk.bold('q')} to cancel: `);
|
|
352
399
|
if (answer.toLowerCase() === 'q') {
|
|
353
400
|
console.log(chalk.dim('\n Cancelled.\n'));
|
|
354
401
|
process.exit(0);
|
|
355
402
|
}
|
|
356
|
-
} else {
|
|
357
|
-
console.log(chalk.dim(` Auto-selecting ${best.gpu} (non-interactive).`));
|
|
358
403
|
}
|
|
359
404
|
|
|
360
405
|
gpu = best.gpu;
|
package/src/commands/serve.js
CHANGED
|
@@ -28,21 +28,26 @@ export function parseServeArgs(args) {
|
|
|
28
28
|
return { model, flags };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function _serveStageLabel(elapsedSec) {
|
|
32
|
+
if (elapsedSec < 45) return 'Starting vLLM…';
|
|
33
|
+
if (elapsedSec < 150) return 'Downloading model…';
|
|
34
|
+
return 'Waiting for /v1/models…';
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
async function waitForEndpoint(endpointUrl, timeoutMs = 5 * 60 * 1000, chalk) {
|
|
32
|
-
const
|
|
33
|
-
|
|
38
|
+
const startMs = Date.now();
|
|
39
|
+
const deadline = startMs + timeoutMs;
|
|
34
40
|
|
|
35
41
|
while (Date.now() < deadline) {
|
|
36
|
-
attempt++;
|
|
37
42
|
try {
|
|
38
43
|
const res = await fetch(`${endpointUrl}/models`, { signal: AbortSignal.timeout(8000) });
|
|
39
|
-
if (res.ok) return true;
|
|
44
|
+
if (res.ok) { process.stdout.write('\n'); return true; }
|
|
40
45
|
} catch {
|
|
41
46
|
// still starting
|
|
42
47
|
}
|
|
43
|
-
const elapsed = Math.round((Date.now() -
|
|
48
|
+
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
44
49
|
process.stdout.write(
|
|
45
|
-
`\r ${chalk.dim(`
|
|
50
|
+
`\r ${chalk.dim(_serveStageLabel(elapsed) + ` (${elapsed}s)`)} `
|
|
46
51
|
);
|
|
47
52
|
await new Promise(r => setTimeout(r, 8000));
|
|
48
53
|
}
|
|
@@ -212,12 +217,11 @@ export async function serveCommand(config, args, chalk) {
|
|
|
212
217
|
|
|
213
218
|
// ── Result ────────────────────────────────────────────────────────────────
|
|
214
219
|
if (endpointReady) {
|
|
215
|
-
console.log(chalk.green('\n✓ Endpoint ready\n'));
|
|
220
|
+
console.log(chalk.green('\n ✓ Endpoint ready\n'));
|
|
216
221
|
} else {
|
|
217
|
-
console.log(chalk.yellow('\n⏳ Endpoint still starting
|
|
218
|
-
console.log(chalk.
|
|
219
|
-
console.log(chalk.dim(`
|
|
220
|
-
console.log(chalk.dim(` Stop if it never starts: badgr down ${dep.deployment_id}`));
|
|
222
|
+
console.log(chalk.yellow('\n ⏳ Endpoint still starting — model download may still be in progress.\n'));
|
|
223
|
+
console.log(` ${chalk.bold('Stop billing now:')} ${chalk.dim(`badgr down ${dep.deployment_id}`)}`);
|
|
224
|
+
console.log(` ${chalk.bold('Continue watching:')} ${chalk.dim(`badgr logs ${dep.deployment_id}`)}`);
|
|
221
225
|
console.log();
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { requireApiKey } from '../config.js';
|
|
2
|
+
import { callApi, terminateDeployment } from '../api.js';
|
|
3
|
+
import { addReceipt, generateReceiptId } from '../store.js';
|
|
4
|
+
|
|
5
|
+
// max $1.50/hr × 2 min ≈ $0.05 total spend cap
|
|
6
|
+
const TEST_MAX_PRICE = 1.50;
|
|
7
|
+
const TEST_MAX_RUNTIME_MS = 2 * 60 * 1000;
|
|
8
|
+
const TEST_COMMAND = ['python', '-c', "print('hello from badgr')"];
|
|
9
|
+
const TEST_IMAGE = 'python:3.11-slim';
|
|
10
|
+
const EXPECTED_OUTPUT = 'hello from badgr';
|
|
11
|
+
|
|
12
|
+
function step(chalk, ok, msg, detail = '') {
|
|
13
|
+
const icon = ok ? chalk.green('✓') : chalk.red('✗');
|
|
14
|
+
const suffix = detail ? chalk.dim(` — ${detail}`) : '';
|
|
15
|
+
console.log(` ${icon} ${msg}${suffix}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function pollStatus(config, depId, targetStatuses, timeoutMs) {
|
|
19
|
+
const deadline = Date.now() + timeoutMs;
|
|
20
|
+
while (Date.now() < deadline) {
|
|
21
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
22
|
+
try {
|
|
23
|
+
const dep = await callApi(`/deployments/${depId}`, {
|
|
24
|
+
apiKey: config.apiKey,
|
|
25
|
+
baseUrl: config.baseUrl,
|
|
26
|
+
});
|
|
27
|
+
if (targetStatuses.has(dep.status)) return dep;
|
|
28
|
+
} catch { /* retry */ }
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function pollLogs(config, depId, expected, timeoutMs) {
|
|
34
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
|
+
while (Date.now() < deadline) {
|
|
36
|
+
await new Promise(r => setTimeout(r, 4000));
|
|
37
|
+
try {
|
|
38
|
+
const data = await callApi(`/deployments/${depId}/logs`, {
|
|
39
|
+
apiKey: config.apiKey,
|
|
40
|
+
baseUrl: config.baseUrl,
|
|
41
|
+
});
|
|
42
|
+
const lines = data?.logs ?? [];
|
|
43
|
+
if (lines.some(l => l.includes(expected))) return true;
|
|
44
|
+
} catch { /* retry */ }
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function testCommand(config, chalk) {
|
|
50
|
+
requireApiKey(config);
|
|
51
|
+
|
|
52
|
+
console.log(chalk.bold('\n⚡ Running end-to-end test\n'));
|
|
53
|
+
console.log(chalk.dim(` Command: ${TEST_COMMAND.join(' ')}`));
|
|
54
|
+
console.log(chalk.dim(` Provider: RunPod (Tier 1 — reliable)`));
|
|
55
|
+
console.log(chalk.dim(` Budget: max $${TEST_MAX_PRICE.toFixed(2)}/hr · 2 minute cap (~$0.05 max)`));
|
|
56
|
+
console.log();
|
|
57
|
+
|
|
58
|
+
const rcptId = generateReceiptId();
|
|
59
|
+
let depId;
|
|
60
|
+
|
|
61
|
+
// ── 1. Provision ─────────────────────────────────────────────────────────
|
|
62
|
+
process.stdout.write(chalk.dim(' Provisioning GPU...'));
|
|
63
|
+
let dep;
|
|
64
|
+
try {
|
|
65
|
+
dep = await callApi('/run', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
apiKey: config.apiKey,
|
|
68
|
+
baseUrl: config.baseUrl,
|
|
69
|
+
body: {
|
|
70
|
+
command: TEST_COMMAND,
|
|
71
|
+
image: TEST_IMAGE,
|
|
72
|
+
tier: '1',
|
|
73
|
+
// smoke_test workload → cheapest reliable GPU with ≥4 GB VRAM
|
|
74
|
+
gpu: 'RTX_3080',
|
|
75
|
+
max_price_per_hour: TEST_MAX_PRICE,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
depId = dep.deployment_id;
|
|
79
|
+
process.stdout.write('\n');
|
|
80
|
+
step(chalk, true, 'Provisioned', `${dep.deployment_id} on ${dep.gpu_type}`);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
process.stdout.write('\n');
|
|
83
|
+
step(chalk, false, 'Provisioned', err.message);
|
|
84
|
+
console.log();
|
|
85
|
+
console.error(chalk.red(' Test failed — could not provision GPU.\n'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── 2. Container started ─────────────────────────────────────────────────
|
|
90
|
+
process.stdout.write(chalk.dim(' Waiting for container to start...'));
|
|
91
|
+
const started = await pollStatus(
|
|
92
|
+
config, depId,
|
|
93
|
+
new Set(['running', 'failed', 'stopped', 'completed']),
|
|
94
|
+
TEST_MAX_RUNTIME_MS,
|
|
95
|
+
);
|
|
96
|
+
process.stdout.write('\n');
|
|
97
|
+
|
|
98
|
+
if (!started || started.status === 'failed') {
|
|
99
|
+
step(chalk, false, 'Container started', started?.status ?? 'timeout');
|
|
100
|
+
console.log();
|
|
101
|
+
console.error(chalk.red(' Test failed — container did not start.\n'));
|
|
102
|
+
try { await terminateDeployment(config, depId); } catch { /* best-effort */ }
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
step(chalk, true, 'Container started');
|
|
106
|
+
|
|
107
|
+
// ── 3. Command output ────────────────────────────────────────────────────
|
|
108
|
+
process.stdout.write(chalk.dim(' Checking command output...'));
|
|
109
|
+
const gotOutput = await pollLogs(config, depId, EXPECTED_OUTPUT, 90_000);
|
|
110
|
+
process.stdout.write('\n');
|
|
111
|
+
if (gotOutput) {
|
|
112
|
+
step(chalk, true, 'Command printed output');
|
|
113
|
+
} else {
|
|
114
|
+
step(chalk, false, 'Command printed output', 'not found in logs (logs may be buffered)');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── 4. Stop billing ──────────────────────────────────────────────────────
|
|
118
|
+
process.stdout.write(chalk.dim(' Stopping deployment...'));
|
|
119
|
+
let stopped = false;
|
|
120
|
+
try {
|
|
121
|
+
await terminateDeployment(config, depId);
|
|
122
|
+
stopped = true;
|
|
123
|
+
} catch { /* best-effort */ }
|
|
124
|
+
process.stdout.write('\n');
|
|
125
|
+
step(chalk, stopped, 'Billing stopped');
|
|
126
|
+
|
|
127
|
+
// ── 5. Receipt ───────────────────────────────────────────────────────────
|
|
128
|
+
addReceipt({
|
|
129
|
+
receiptId: rcptId,
|
|
130
|
+
action: 'badgr test',
|
|
131
|
+
deploymentId: depId,
|
|
132
|
+
gpu: dep.gpu_type,
|
|
133
|
+
status: 'test_complete',
|
|
134
|
+
createdAt: new Date().toISOString(),
|
|
135
|
+
});
|
|
136
|
+
step(chalk, true, 'Receipt created', rcptId);
|
|
137
|
+
|
|
138
|
+
// ── Summary ──────────────────────────────────────────────────────────────
|
|
139
|
+
console.log();
|
|
140
|
+
const passed = stopped;
|
|
141
|
+
if (passed) {
|
|
142
|
+
console.log(chalk.green(chalk.bold(' ✓ Test passed\n')));
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.red(chalk.bold(' ✗ Test failed\n')));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
package/tests/commands.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { parseRunArgs, classifyFailure } from '../src/commands/run.js';
|
|
2
|
+
import { parseRunArgs, classifyFailure, inferWorkload } from '../src/commands/run.js';
|
|
3
3
|
import { parseServeArgs } from '../src/commands/serve.js';
|
|
4
|
+
import { testCommand } from '../src/commands/test-run.js';
|
|
4
5
|
import { rankAlternatives, diffDescription, promptFallback } from '../src/fallback.js';
|
|
5
6
|
|
|
6
7
|
describe('parseRunArgs', () => {
|
|
@@ -214,6 +215,45 @@ describe('parseServeArgs', () => {
|
|
|
214
215
|
});
|
|
215
216
|
});
|
|
216
217
|
|
|
218
|
+
describe('testCommand', () => {
|
|
219
|
+
it('is a function', () => {
|
|
220
|
+
expect(typeof testCommand).toBe('function');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('inferWorkload', () => {
|
|
225
|
+
it('returns general for empty command', () => {
|
|
226
|
+
expect(inferWorkload([])).toBe('general');
|
|
227
|
+
expect(inferWorkload(null)).toBe('general');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('returns smoke_test for short print() one-liners', () => {
|
|
231
|
+
expect(inferWorkload(['python', '-c', "print('hello')"])).toBe('smoke_test');
|
|
232
|
+
expect(inferWorkload(['python', '-c', "print('hello from badgr')"])).toBe('smoke_test');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('returns general for a typical script', () => {
|
|
236
|
+
expect(inferWorkload(['python', 'script.py'])).toBe('general');
|
|
237
|
+
expect(inferWorkload(['python', 'run.py', '--epochs', '10'])).toBe('general');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('returns lora_finetune for LoRA/fine-tuning keywords', () => {
|
|
241
|
+
expect(inferWorkload(['python', 'lora_train.py'])).toBe('lora_finetune');
|
|
242
|
+
expect(inferWorkload(['python', '-c', 'import peft; finetune()'])).toBe('lora_finetune');
|
|
243
|
+
expect(inferWorkload(['python', 'train.py', '--epochs', '3'])).toBe('lora_finetune');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('returns image_gen for diffusion keywords', () => {
|
|
247
|
+
expect(inferWorkload(['python', 'stable_diff.py'])).toBe('image_gen');
|
|
248
|
+
expect(inferWorkload(['python', 'run_sdxl.py'])).toBe('image_gen');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('returns inference_small for vllm/tgi', () => {
|
|
252
|
+
expect(inferWorkload(['python', '-m', 'vllm.entrypoints.openai.api_server'])).toBe('inference_small');
|
|
253
|
+
expect(inferWorkload(['python', 'serve_tgi.py'])).toBe('inference_small');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
217
257
|
describe('promptFallback output', () => {
|
|
218
258
|
it('does not print a Difference line', async () => {
|
|
219
259
|
const lines = [];
|