badgr-cli 1.0.22 → 1.0.23
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/commands/run.js +24 -33
- package/src/commands/serve.js +29 -25
- package/src/commands/status.js +2 -2
- package/src/commands/test-run.js +35 -17
package/package.json
CHANGED
package/src/commands/run.js
CHANGED
|
@@ -2,7 +2,6 @@ import readline from 'readline';
|
|
|
2
2
|
import { requireApiKey } from '../config.js';
|
|
3
3
|
import { callApi, terminateDeployment } from '../api.js';
|
|
4
4
|
import { addReceipt, updateReceipt, generateReceiptId } from '../store.js';
|
|
5
|
-
import { rankAlternatives, promptFallback } from '../fallback.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* badgr run python train.py # gpu=auto, attached
|
|
@@ -454,56 +453,48 @@ export async function runCommand(config, args, chalk) {
|
|
|
454
453
|
} catch (err) {
|
|
455
454
|
const d = err.errorData;
|
|
456
455
|
if (d?.code === 'NO_CAPACITY_MATCH') {
|
|
457
|
-
if (
|
|
458
|
-
console.error(chalk.red(
|
|
459
|
-
console.error(chalk.dim(' Pass --fallback closest to auto-select an alternative.'));
|
|
460
|
-
process.exit(1);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const pool = [
|
|
464
|
-
...(Array.isArray(d.same_gpu_other_regions) ? d.same_gpu_other_regions : []),
|
|
465
|
-
...(Array.isArray(d.alternatives) ? d.alternatives : []),
|
|
466
|
-
];
|
|
467
|
-
|
|
468
|
-
if (pool.length === 0) {
|
|
469
|
-
console.error(chalk.red(`\n ✗ ${gpu} isn't available right now and no alternatives were found.\n`));
|
|
456
|
+
if (effectiveTier === '2') {
|
|
457
|
+
console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
|
|
470
458
|
console.error(chalk.dim(' Try `badgr capacity` to see what\'s available, or try again shortly.'));
|
|
471
459
|
process.exit(1);
|
|
472
460
|
}
|
|
473
461
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
462
|
+
// Tier 1 (RunPod) is out of capacity — offer budget providers.
|
|
463
|
+
if (process.stdin.isTTY) {
|
|
464
|
+
const answer = await askConfirm(
|
|
465
|
+
`\n RunPod has no suitable capacity. Try budget marketplace GPUs? (Vast.ai, Salad) [${chalk.bold('Enter')}/${chalk.bold('q')}]: `
|
|
466
|
+
);
|
|
467
|
+
if (answer.toLowerCase() === 'q') {
|
|
468
|
+
console.log(chalk.dim('\n Cancelled.\n'));
|
|
469
|
+
process.exit(0);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
console.log(chalk.dim('\n RunPod capacity unavailable — falling back to budget providers...\n'));
|
|
480
473
|
}
|
|
481
474
|
|
|
482
|
-
console.log(chalk.dim(
|
|
483
|
-
|
|
475
|
+
console.log(chalk.dim(' Searching budget providers (Vast.ai, Salad)...'));
|
|
484
476
|
try {
|
|
485
477
|
dep = await callApi('/run', {
|
|
486
478
|
method: 'POST',
|
|
487
479
|
apiKey: config.apiKey,
|
|
488
480
|
baseUrl: config.baseUrl,
|
|
489
|
-
body: buildBody(
|
|
481
|
+
body: { ...buildBody(), tier: '2' },
|
|
490
482
|
});
|
|
491
483
|
} catch (err2) {
|
|
492
484
|
const d2 = err2.errorData;
|
|
493
|
-
if (d2?.code === '
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
485
|
+
if (d2?.code === 'NO_CAPACITY_MATCH') {
|
|
486
|
+
console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
|
|
487
|
+
console.error(chalk.dim(' Try `badgr capacity` to see what\'s available, or try again shortly.'));
|
|
488
|
+
} else if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
489
|
+
console.error(chalk.red(`\n ✗ Budget provider found capacity but could not start the machine. Please try again.\n`));
|
|
499
490
|
if (process.env.BADGR_DEBUG === '1' || process.env.BADGR_DEBUG === 'true') {
|
|
500
491
|
if (d2?.debug_error) console.error(chalk.dim(` Provider detail: ${d2.debug_error}`));
|
|
492
|
+
} else {
|
|
493
|
+
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.\n`));
|
|
501
494
|
}
|
|
502
495
|
} else {
|
|
503
|
-
console.error(chalk.red(`\n ✗
|
|
504
|
-
|
|
505
|
-
if (!(process.env.BADGR_DEBUG === '1' || process.env.BADGR_DEBUG === 'true')) {
|
|
506
|
-
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.\n`));
|
|
496
|
+
console.error(chalk.red(`\n ✗ Could not start job on budget providers: ${err2.message}\n`));
|
|
497
|
+
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.`));
|
|
507
498
|
}
|
|
508
499
|
process.exit(1);
|
|
509
500
|
}
|
package/src/commands/serve.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
1
2
|
import { requireApiKey } from '../config.js';
|
|
2
3
|
import { callApi } from '../api.js';
|
|
3
4
|
import { addDeployment, addReceipt, updateReceipt, generateReceiptId } from '../store.js';
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
function askConfirm(prompt) {
|
|
7
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise(resolve => rl.question(prompt, ans => { rl.close(); resolve(ans.trim()); }));
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* badgr serve meta-llama/Llama-3.1-8B-Instruct
|
|
@@ -107,46 +112,45 @@ export async function serveCommand(config, args, chalk) {
|
|
|
107
112
|
} catch (err) {
|
|
108
113
|
const d = err.errorData;
|
|
109
114
|
if (d?.code === 'NO_CAPACITY_MATCH') {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
...(Array.isArray(d.alternatives) ? d.alternatives : []),
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
if (pool.length === 0) {
|
|
116
|
-
console.error(chalk.red(`\n ✗ No GPU capacity available right now.\n`));
|
|
115
|
+
if (effectiveTier === '2') {
|
|
116
|
+
console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
|
|
117
117
|
console.error(chalk.dim(' Run `badgr capacity` to see alternatives, or try again shortly.'));
|
|
118
118
|
process.exit(1);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
// Tier 1 (RunPod) out of capacity — offer budget providers.
|
|
122
|
+
if (process.stdin.isTTY) {
|
|
123
|
+
const answer = await askConfirm(
|
|
124
|
+
`\n RunPod has no suitable capacity. Try budget marketplace GPUs? (Vast.ai, Salad) [${chalk.bold('Enter')}/${chalk.bold('q')}]: `
|
|
125
|
+
);
|
|
126
|
+
if (answer.toLowerCase() === 'q') {
|
|
127
|
+
console.log(chalk.dim('\n Cancelled.\n'));
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.log(chalk.dim('\n RunPod capacity unavailable — falling back to budget providers...\n'));
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
console.log(chalk.dim(
|
|
130
|
-
|
|
134
|
+
console.log(chalk.dim(' Searching budget providers (Vast.ai, Salad)...'));
|
|
131
135
|
try {
|
|
132
136
|
dep = await callApi('/serve', {
|
|
133
137
|
method: 'POST',
|
|
134
138
|
apiKey: config.apiKey,
|
|
135
139
|
baseUrl: config.baseUrl,
|
|
136
|
-
body: buildBody(
|
|
140
|
+
body: { ...buildBody(), tier: '2' },
|
|
137
141
|
});
|
|
138
142
|
} catch (err2) {
|
|
139
143
|
const d2 = err2.errorData;
|
|
140
|
-
if (d2?.code === '
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
if (d2?.code === 'NO_CAPACITY_MATCH') {
|
|
145
|
+
console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
|
|
146
|
+
console.error(chalk.dim(' Run `badgr capacity` to see alternatives, or try again shortly.'));
|
|
147
|
+
} else if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
148
|
+
console.error(chalk.red(`\n ✗ Budget provider found capacity but could not start the endpoint. Please try again.\n`));
|
|
149
|
+
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
|
|
146
150
|
} else {
|
|
147
|
-
console.error(chalk.red(`\n ✗ Failed to start endpoint on
|
|
151
|
+
console.error(chalk.red(`\n ✗ Failed to start endpoint on budget providers: ${err2.message}\n`));
|
|
152
|
+
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
|
|
148
153
|
}
|
|
149
|
-
console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
|
|
150
154
|
process.exit(1);
|
|
151
155
|
}
|
|
152
156
|
} else if (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR') {
|
package/src/commands/status.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function statusCommand(config, args, chalk) {
|
|
|
47
47
|
|
|
48
48
|
console.log(chalk.bold('\nRunning now:\n'));
|
|
49
49
|
for (const d of running) {
|
|
50
|
-
const id = d.
|
|
50
|
+
const id = d.deployment_id || d.name;
|
|
51
51
|
const type = d.workload_type === 'endpoint' ? 'endpoint' : 'job';
|
|
52
52
|
const gpu = d.gpu_type || '—';
|
|
53
53
|
const rate = d.cost_per_hour > 0 ? chalk.yellow(`$${d.cost_per_hour.toFixed(2)}/hr`) : '';
|
|
@@ -74,7 +74,7 @@ export async function statusCommand(config, args, chalk) {
|
|
|
74
74
|
|
|
75
75
|
console.log(chalk.bold('Stop billing:\n'));
|
|
76
76
|
for (const d of running) {
|
|
77
|
-
const id = d.
|
|
77
|
+
const id = d.deployment_id || d.name;
|
|
78
78
|
console.log(` ${chalk.cyan(`badgr down ${id}`)}`);
|
|
79
79
|
}
|
|
80
80
|
console.log();
|
package/src/commands/test-run.js
CHANGED
|
@@ -59,32 +59,50 @@ export async function testCommand(config, chalk) {
|
|
|
59
59
|
let depId;
|
|
60
60
|
|
|
61
61
|
// ── 1. Provision ─────────────────────────────────────────────────────────
|
|
62
|
-
process.stdout.write(chalk.dim(' Provisioning GPU...'));
|
|
62
|
+
process.stdout.write(chalk.dim(' Provisioning GPU (RunPod)...'));
|
|
63
63
|
let dep;
|
|
64
|
+
const baseBody = {
|
|
65
|
+
command: TEST_COMMAND,
|
|
66
|
+
image: TEST_IMAGE,
|
|
67
|
+
gpu: 'RTX_3080',
|
|
68
|
+
max_price_per_hour: TEST_MAX_PRICE,
|
|
69
|
+
};
|
|
64
70
|
try {
|
|
65
71
|
dep = await callApi('/run', {
|
|
66
72
|
method: 'POST',
|
|
67
73
|
apiKey: config.apiKey,
|
|
68
74
|
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
|
-
},
|
|
75
|
+
body: { ...baseBody, tier: '1' },
|
|
77
76
|
});
|
|
78
|
-
depId = dep.deployment_id;
|
|
79
|
-
process.stdout.write('\n');
|
|
80
|
-
step(chalk, true, 'Provisioned', `${dep.deployment_id} on ${dep.gpu_type}`);
|
|
81
77
|
} catch (err) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
if (err.errorData?.code === 'NO_CAPACITY_MATCH') {
|
|
79
|
+
process.stdout.write('\n');
|
|
80
|
+
process.stdout.write(chalk.dim(' RunPod unavailable, trying budget providers...'));
|
|
81
|
+
try {
|
|
82
|
+
dep = await callApi('/run', {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
apiKey: config.apiKey,
|
|
85
|
+
baseUrl: config.baseUrl,
|
|
86
|
+
body: { ...baseBody, tier: '2' },
|
|
87
|
+
});
|
|
88
|
+
} catch (err2) {
|
|
89
|
+
process.stdout.write('\n');
|
|
90
|
+
step(chalk, false, 'Provisioned', err2.message);
|
|
91
|
+
console.log();
|
|
92
|
+
console.error(chalk.red(' Test failed — no GPU capacity available on any provider.\n'));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
process.stdout.write('\n');
|
|
97
|
+
step(chalk, false, 'Provisioned', err.message);
|
|
98
|
+
console.log();
|
|
99
|
+
console.error(chalk.red(' Test failed — could not provision GPU.\n'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
87
102
|
}
|
|
103
|
+
depId = dep.deployment_id;
|
|
104
|
+
process.stdout.write('\n');
|
|
105
|
+
step(chalk, true, 'Provisioned', `${dep.deployment_id} on ${dep.gpu_type}`);
|
|
88
106
|
|
|
89
107
|
// ── 2. Container started ─────────────────────────────────────────────────
|
|
90
108
|
process.stdout.write(chalk.dim(' Waiting for container to start...'));
|