badgr-cli 1.0.20 → 1.0.21
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 +10 -2
- package/src/commands/run.js +14 -5
- package/src/commands/serve.js +8 -1
- package/tests/commands.test.js +25 -0
package/package.json
CHANGED
package/src/badgr.js
CHANGED
|
@@ -26,10 +26,14 @@ ${chalk.bold('COMMANDS')}
|
|
|
26
26
|
${chalk.cyan('badgr capacity')} Check what GPU capacity is available right now
|
|
27
27
|
|
|
28
28
|
${chalk.bold('EXAMPLES')}
|
|
29
|
-
${chalk.dim('# Simplest — Badgr picks the GPU:')}
|
|
29
|
+
${chalk.dim('# Simplest — Badgr picks the GPU (RunPod, reliable):')}
|
|
30
30
|
badgr run python train.py
|
|
31
31
|
badgr serve meta-llama/Llama-3.1-8B-Instruct
|
|
32
32
|
|
|
33
|
+
${chalk.dim('# Opt into cheaper budget providers (Vast.ai etc.):')}
|
|
34
|
+
badgr run python train.py --cheap
|
|
35
|
+
badgr serve meta-llama/Llama-3.1-8B-Instruct --cheap
|
|
36
|
+
|
|
33
37
|
${chalk.dim('# Pin a specific GPU:')}
|
|
34
38
|
badgr run python train.py --gpu A100
|
|
35
39
|
badgr serve meta-llama/Llama-3.1-8B-Instruct --gpu L40S
|
|
@@ -44,10 +48,12 @@ ${chalk.bold('EXAMPLES')}
|
|
|
44
48
|
badgr receipts dep-abc123
|
|
45
49
|
|
|
46
50
|
${chalk.bold('badgr run OPTIONS')}
|
|
47
|
-
--gpu <type> GPU type (default: auto — Badgr picks
|
|
51
|
+
--gpu <type> GPU type (default: auto — Badgr picks best available on RunPod)
|
|
52
|
+
--cheap Search budget providers too (Vast.ai etc.) for lower prices
|
|
48
53
|
--image <image> Docker image (default: python:3.11-slim)
|
|
49
54
|
--count <n> Number of GPUs (default: 1)
|
|
50
55
|
--region US|EU|AU Region preference
|
|
56
|
+
--tier 1|2 Provider tier: 1 = reliable (default), 2 = budget
|
|
51
57
|
--max-price <$/hr> Hard spend cap per GPU-hour
|
|
52
58
|
--max-runtime <min> Auto-stop after N minutes (recommended)
|
|
53
59
|
--max-cost <$> Auto-stop when spend reaches this amount
|
|
@@ -55,8 +61,10 @@ ${chalk.bold('badgr run OPTIONS')}
|
|
|
55
61
|
|
|
56
62
|
${chalk.bold('badgr serve OPTIONS')}
|
|
57
63
|
--gpu <type> GPU type (default: auto — inferred from model size)
|
|
64
|
+
--cheap Search budget providers too (Vast.ai etc.) for lower prices
|
|
58
65
|
--count <n> Number of GPUs (default: 1)
|
|
59
66
|
--region US|EU|AU Region preference
|
|
67
|
+
--tier 1|2 Provider tier: 1 = reliable (default), 2 = budget
|
|
60
68
|
--max-price <$/hr> Hard spend cap per GPU-hour
|
|
61
69
|
--no-wait Skip endpoint health check
|
|
62
70
|
|
package/src/commands/run.js
CHANGED
|
@@ -19,6 +19,7 @@ export function parseRunArgs(args) {
|
|
|
19
19
|
if (args[i] === '--count') { flags.count = parseInt(args[++i], 10); i++; continue; }
|
|
20
20
|
if (args[i] === '--region') { flags.region = args[++i]; i++; continue; }
|
|
21
21
|
if (args[i] === '--tier') { flags.tier = args[++i]; i++; continue; }
|
|
22
|
+
if (args[i] === '--cheap') { flags.cheap = true; i++; continue; }
|
|
22
23
|
if (args[i] === '--max-price') { flags.maxPrice = parseFloat(args[++i]); i++; continue; }
|
|
23
24
|
if (args[i] === '--name') { flags.name = args[++i]; i++; continue; }
|
|
24
25
|
if (args[i] === '--detach') { flags.detach = true; i++; continue; }
|
|
@@ -269,11 +270,12 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
|
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
// Ask the backend for the
|
|
273
|
+
// Ask the backend for the best available GPU right now.
|
|
274
|
+
// Defaults to tier 1 (RunPod) for reliability; pass tier='2' for budget providers.
|
|
273
275
|
// Returns { gpu, region, price } or null when nothing is available.
|
|
274
|
-
async function findAutoGpu(config, chalk) {
|
|
276
|
+
async function findAutoGpu(config, chalk, tier = '1') {
|
|
275
277
|
try {
|
|
276
|
-
const params = new URLSearchParams({ max_price: '10' });
|
|
278
|
+
const params = new URLSearchParams({ max_price: '10', tier });
|
|
277
279
|
return await callApi(`/capacity/auto?${params}`, {
|
|
278
280
|
apiKey: config.apiKey,
|
|
279
281
|
baseUrl: config.baseUrl,
|
|
@@ -307,6 +309,12 @@ export async function runCommand(config, args, chalk) {
|
|
|
307
309
|
const maxRuntimeMs = flags.maxRuntime ? flags.maxRuntime * 60 * 1000 : null;
|
|
308
310
|
const maxCost = flags.maxCost ?? null;
|
|
309
311
|
|
|
312
|
+
// Resolve effective tier: --cheap and --tier 2 opt into budget providers;
|
|
313
|
+
// everything else defaults to tier 1 (RunPod only) for reliability.
|
|
314
|
+
const effectiveTier = (flags.cheap || flags.tier === '2' || flags.tier === 'tier2' || flags.tier === 'tier-2')
|
|
315
|
+
? '2'
|
|
316
|
+
: (flags.tier || '1');
|
|
317
|
+
|
|
310
318
|
// ── Auto GPU selection (no --gpu specified) ────────────────────────────────
|
|
311
319
|
let gpu = flags.gpu ? flags.gpu.toUpperCase().replace('-', '_') : null;
|
|
312
320
|
let autoRegion = null;
|
|
@@ -315,12 +323,13 @@ export async function runCommand(config, args, chalk) {
|
|
|
315
323
|
console.log(chalk.bold('\n⚡ Running GPU job\n'));
|
|
316
324
|
if (command) console.log(` ${chalk.bold('Command:')} ${command.join(' ')}`);
|
|
317
325
|
if (image) console.log(` ${chalk.bold('Image:')} ${image}`);
|
|
326
|
+
if (effectiveTier === '2') console.log(` ${chalk.dim('(budget mode — searching all providers)')}`);
|
|
318
327
|
console.log();
|
|
319
328
|
process.stdout.write(chalk.dim(' Finding GPU...'));
|
|
320
329
|
|
|
321
330
|
let best;
|
|
322
331
|
try {
|
|
323
|
-
best = await findAutoGpu(config, chalk);
|
|
332
|
+
best = await findAutoGpu(config, chalk, effectiveTier);
|
|
324
333
|
} catch (err) {
|
|
325
334
|
process.stdout.write('\n');
|
|
326
335
|
console.error(chalk.red(`\n ✗ Could not find GPU capacity: ${err.message}\n`));
|
|
@@ -385,7 +394,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
385
394
|
...(effectiveRegion ? { region: effectiveRegion } : {}),
|
|
386
395
|
max_price_per_hour: flags.maxPrice,
|
|
387
396
|
name: flags.name,
|
|
388
|
-
|
|
397
|
+
tier: effectiveTier,
|
|
389
398
|
};
|
|
390
399
|
}
|
|
391
400
|
|
package/src/commands/serve.js
CHANGED
|
@@ -18,6 +18,7 @@ export function parseServeArgs(args) {
|
|
|
18
18
|
if (args[i] === '--count') { flags.count = parseInt(args[++i], 10); i++; continue; }
|
|
19
19
|
if (args[i] === '--region') { flags.region = args[++i]; i++; continue; }
|
|
20
20
|
if (args[i] === '--tier') { flags.tier = args[++i]; i++; continue; }
|
|
21
|
+
if (args[i] === '--cheap') { flags.cheap = true; i++; continue; }
|
|
21
22
|
if (args[i] === '--max-price') { flags.maxPrice = parseFloat(args[++i]); i++; continue; }
|
|
22
23
|
if (args[i] === '--name') { flags.name = args[++i]; i++; continue; }
|
|
23
24
|
if (args[i] === '--no-wait') { flags.noWait = true; i++; continue; }
|
|
@@ -64,9 +65,15 @@ export async function serveCommand(config, args, chalk) {
|
|
|
64
65
|
const gpu = flags.gpu ? flags.gpu.toUpperCase().replace('-', '_') : 'AUTO';
|
|
65
66
|
const gpuLabel = gpu === 'AUTO' ? 'auto' : gpu;
|
|
66
67
|
|
|
68
|
+
// Default to tier 1 (RunPod) for reliability; --cheap or --tier 2 opts into budget providers.
|
|
69
|
+
const effectiveTier = (flags.cheap || flags.tier === '2' || flags.tier === 'tier2' || flags.tier === 'tier-2')
|
|
70
|
+
? '2'
|
|
71
|
+
: (flags.tier || '1');
|
|
72
|
+
|
|
67
73
|
console.log(chalk.bold('\nServing model\n'));
|
|
68
74
|
console.log(` ${chalk.bold('Model:')} ${model}`);
|
|
69
75
|
console.log(` ${chalk.bold('GPU:')} ${gpuLabel}`);
|
|
76
|
+
if (effectiveTier === '2') console.log(` ${chalk.dim('(budget mode — searching all providers)')}`);
|
|
70
77
|
console.log();
|
|
71
78
|
process.stdout.write(chalk.dim(' Finding GPU capacity...\n'));
|
|
72
79
|
|
|
@@ -80,7 +87,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
80
87
|
...(regionOverride || effectiveRegion ? { region: regionOverride || effectiveRegion } : {}),
|
|
81
88
|
max_price_per_hour: flags.maxPrice,
|
|
82
89
|
name: flags.name,
|
|
83
|
-
|
|
90
|
+
tier: effectiveTier,
|
|
84
91
|
};
|
|
85
92
|
}
|
|
86
93
|
|
package/tests/commands.test.js
CHANGED
|
@@ -71,6 +71,21 @@ describe('parseRunArgs', () => {
|
|
|
71
71
|
const { flags } = parseRunArgs(['python', 'train.py', '--max-cost', '5.00']);
|
|
72
72
|
expect(flags.maxCost).toBe(5.0);
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it('parses --cheap flag', () => {
|
|
76
|
+
const { flags } = parseRunArgs(['python', 'train.py', '--cheap']);
|
|
77
|
+
expect(flags.cheap).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('--cheap defaults to falsy when not passed', () => {
|
|
81
|
+
const { flags } = parseRunArgs(['python', 'train.py']);
|
|
82
|
+
expect(flags.cheap).toBeFalsy();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('parses --tier 2 flag', () => {
|
|
86
|
+
const { flags } = parseRunArgs(['python', 'train.py', '--tier', '2']);
|
|
87
|
+
expect(flags.tier).toBe('2');
|
|
88
|
+
});
|
|
74
89
|
});
|
|
75
90
|
|
|
76
91
|
describe('classifyFailure', () => {
|
|
@@ -187,6 +202,16 @@ describe('parseServeArgs', () => {
|
|
|
187
202
|
const { model } = parseServeArgs(['--gpu', 'RTX_4090']);
|
|
188
203
|
expect(model).toBeNull();
|
|
189
204
|
});
|
|
205
|
+
|
|
206
|
+
it('parses --cheap flag', () => {
|
|
207
|
+
const { flags } = parseServeArgs(['my/model', '--cheap']);
|
|
208
|
+
expect(flags.cheap).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('--cheap defaults to falsy when not passed', () => {
|
|
212
|
+
const { flags } = parseServeArgs(['my/model']);
|
|
213
|
+
expect(flags.cheap).toBeFalsy();
|
|
214
|
+
});
|
|
190
215
|
});
|
|
191
216
|
|
|
192
217
|
describe('promptFallback output', () => {
|