badgr-cli 1.0.37 → 1.0.39

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/README.md CHANGED
@@ -275,7 +275,7 @@ Additional GPU types may be routable depending on current capacity — check wit
275
275
 
276
276
  Pricing is confirmed before provisioning. Use `--dry-run` to see pricing before committing.
277
277
 
278
- Full GPU support details: see [GPU_SUPPORT.md](../../GPU_SUPPORT.md) in the repo root.
278
+ Full GPU support details: see [NOTES.md](../../NOTES.md#gpu-support) in the repo root.
279
279
 
280
280
  ---
281
281
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.0.37",
4
- "description": "Badgr, run or serve GPU workloads from one command",
3
+ "version": "1.0.39",
4
+ "description": "DEPRECATED: Use badgr-agent CLI instead. Badgr, run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "badgr": "src/badgr.js"
@@ -12,23 +12,21 @@
12
12
  "test:watch": "vitest"
13
13
  },
14
14
  "dependencies": {
15
- "@inquirer/prompts": "^8.5.2",
16
- "chalk": "^5.3.0"
15
+ "badgr-agent": "1.0.0"
17
16
  },
18
17
  "devDependencies": {
19
18
  "vitest": "^4.1.8"
20
19
  },
21
20
  "engines": {
22
- "node": ">=18.0.0"
21
+ "node": ">=20.10.0"
23
22
  },
24
23
  "keywords": [
25
24
  "gpu",
26
25
  "cli",
27
26
  "ai",
28
27
  "compute",
29
- "modal",
30
28
  "gateway",
31
29
  "openai"
32
30
  ],
33
- "license": "MIT"
31
+ "license": "Apache-2.0"
34
32
  }
package/src/badgr.js CHANGED
@@ -17,6 +17,9 @@ import { comfyuiCommand } from './commands/comfyui.js';
17
17
  import { trainCommand } from './commands/train.js';
18
18
  import { transcribeCommand } from './commands/transcribe.js';
19
19
  import { embedCommand } from './commands/embed.js';
20
+ import { templateCommand } from './commands/template.js';
21
+ import { workloadCommand } from './commands/workload.js';
22
+ import { workspaceCommand } from './commands/workspace.js';
20
23
 
21
24
  const HELP = `
22
25
  ${chalk.bold('badgr')} — run or serve GPU workloads from one command
@@ -32,12 +35,19 @@ ${chalk.bold('COMMANDS')}
32
35
  ${chalk.cyan('badgr test')} Run an end-to-end test (provision → run → teardown)
33
36
  ${chalk.cyan('badgr capacity')} Check what GPU capacity is available right now
34
37
  ${chalk.cyan('badgr billing')} Show balance and add funds
38
+ ${chalk.cyan('badgr workload list')} List saved workloads
39
+ ${chalk.cyan('badgr workload run <name>')} Rerun a saved workload
40
+ ${chalk.cyan('badgr workspace list')} List workspace trackers (job history + cost per named context)
41
+ ${chalk.cyan('badgr workspace create')} Create a workspace tracker (link jobs to a named storage path)
35
42
 
36
43
  ${chalk.bold('SHORTCUTS')} ${chalk.dim('(wrappers around run / serve for common workloads)')}
37
44
  ${chalk.cyan('badgr comfyui run <workflow.json>')} Launch ComfyUI, return endpoint URL
38
45
  ${chalk.cyan('badgr train <config.yaml>')} LoRA / fine-tuning job, stream logs
39
46
  ${chalk.cyan('badgr transcribe <audio>')} Whisper transcription, print transcript
40
47
  ${chalk.cyan('badgr embed <model> <input>')} Text embeddings, output JSONL
48
+ ${chalk.cyan('badgr serve template <name>')} Launch an endpoint template (vllm, invokeai, comfyui, …)
49
+ ${chalk.cyan('badgr run template <name>')} Launch a job template (axolotl, unsloth)
50
+ ${chalk.cyan('badgr template list')} Browse all pre-built templates
41
51
 
42
52
  ${chalk.bold('EXAMPLES')}
43
53
  ${chalk.dim('# Verify the stack works end-to-end:')}
@@ -46,6 +56,12 @@ ${chalk.bold('EXAMPLES')}
46
56
  ${chalk.dim('# Serve a model (OpenAI-compatible):')}
47
57
  badgr serve meta-llama/Llama-3.1-8B-Instruct --max-cost 10
48
58
 
59
+ ${chalk.dim('# Serve a Hugging Face GGUF file via llama.cpp:')}
60
+ badgr serve --runtime llama.cpp \\
61
+ --hf-repo org/model-repo \\
62
+ --hf-file model.gguf \\
63
+ --max-cost 10
64
+
49
65
  ${chalk.dim('# Launch ComfyUI with a workflow:')}
50
66
  badgr comfyui run workflow.json --max-cost 5
51
67
 
@@ -94,7 +110,10 @@ ${chalk.bold('badgr serve OPTIONS')}
94
110
  --count <n> Number of GPUs (default: 1)
95
111
  --region US|EU|AU Region preference
96
112
  --max-price <$/hr> Hard spend cap per GPU-hour
97
- --health-path <path> Readiness path to poll (auto-detected for comfyui /system_stats)
113
+ --runtime llama.cpp Serve a HF GGUF file via llama.cpp instead of vLLM
114
+ --hf-repo <repo> Hugging Face repo (use with --runtime llama.cpp), e.g. org/model-repo
115
+ --hf-file <file> GGUF filename within that repo, e.g. model.gguf
116
+ --health-path <path> Readiness path to poll (auto-detected: comfyui → /system_stats, llama.cpp → /health)
98
117
  --no-wait Skip endpoint health check
99
118
 
100
119
  ${chalk.bold('AFTER SERVING')}
@@ -133,6 +152,9 @@ async function main() {
133
152
  case 'train': return trainCommand(config, rest, chalk);
134
153
  case 'transcribe': return transcribeCommand(config, rest, chalk);
135
154
  case 'embed': return embedCommand(config, rest, chalk);
155
+ case 'template': return templateCommand(config, rest, chalk);
156
+ case 'workload': return workloadCommand(config, rest, chalk);
157
+ case 'workspace': return workspaceCommand(config, rest, chalk);
136
158
  // legacy aliases kept for compatibility
137
159
  case 'up': return upCommand(config, rest, chalk);
138
160
  case 'config': {
package/src/catalog.js ADDED
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Template catalog — shared by template.js, serve.js, and run.js.
3
+ * No imports from other badgr commands to avoid circular dependencies.
4
+ */
5
+
6
+ export const TEMPLATES = [
7
+ {
8
+ name: 'comfyui',
9
+ title: 'ComfyUI',
10
+ description: 'Stable Diffusion image generation with ComfyUI node editor',
11
+ type: 'endpoint',
12
+ image: process.env.COMFYUI_IMAGE || 'yanwk/comfyui-boot:cu126-megapak',
13
+ gpu: 'RTX_4090',
14
+ gpu_count: 1,
15
+ port: 8188,
16
+ health_path: '/system_stats',
17
+ min_vram_gb: 16,
18
+ env: {},
19
+ notes: [
20
+ 'Access the ComfyUI UI at the returned endpoint URL.',
21
+ 'Tip: use `badgr comfyui run workflow.json` to also queue a workflow at boot.',
22
+ ],
23
+ },
24
+ {
25
+ name: 'axolotl',
26
+ title: 'Axolotl Fine-Tuning',
27
+ description: 'LLM LoRA/QLoRA fine-tuning with the Axolotl training framework',
28
+ type: 'job',
29
+ image: 'axolotlai/axolotl-cloud-uv:main-latest',
30
+ gpu: 'A100',
31
+ gpu_count: 1,
32
+ min_vram_gb: 40,
33
+ env: {
34
+ HF_TOKEN: '<your-hf-token>',
35
+ WANDB_API_KEY: '<optional>',
36
+ HF_HUB_CACHE: '/workspace/hf-cache',
37
+ },
38
+ notes: [
39
+ 'Preferred: `badgr train config.yaml` auto-detects Axolotl configs.',
40
+ 'Pass HF_TOKEN via --env or BADGR_HF_TOKEN env var.',
41
+ ],
42
+ },
43
+ {
44
+ name: 'unsloth',
45
+ title: 'Unsloth Fine-Tuning',
46
+ description: 'Fast LoRA/QLoRA fine-tuning — 2x faster, 60% less VRAM than baseline',
47
+ type: 'job',
48
+ image: 'unslothai/unsloth:latest',
49
+ gpu: 'RTX_4090',
50
+ gpu_count: 1,
51
+ min_vram_gb: 16,
52
+ env: {
53
+ HF_TOKEN: '<your-hf-token>',
54
+ },
55
+ notes: [
56
+ 'Use `badgr train config.yaml --framework unsloth` for config-driven runs.',
57
+ 'Unsloth detects the GPU environment automatically.',
58
+ ],
59
+ },
60
+ {
61
+ name: 'vllm',
62
+ title: 'vLLM OpenAI Server',
63
+ description: 'OpenAI-compatible LLM inference endpoint powered by vLLM',
64
+ type: 'endpoint',
65
+ image: 'vllm/vllm-openai:latest',
66
+ gpu: 'RTX_4090',
67
+ gpu_count: 1,
68
+ port: 8000,
69
+ health_path: '/v1/models',
70
+ min_vram_gb: 24,
71
+ env: {
72
+ MODEL: 'meta-llama/Llama-3.1-8B-Instruct',
73
+ HF_TOKEN: '<for-gated-models>',
74
+ MAX_MODEL_LEN: '8192',
75
+ TENSOR_PARALLEL_SIZE: '1',
76
+ },
77
+ command: [
78
+ 'python', '-m', 'vllm.entrypoints.openai.api_server',
79
+ '--model', '${MODEL}',
80
+ '--host', '0.0.0.0',
81
+ '--port', '8000',
82
+ '--tensor-parallel-size', '${TENSOR_PARALLEL_SIZE}',
83
+ ],
84
+ notes: [
85
+ 'Override MODEL with any HuggingFace model ID via --env MODEL=<id>.',
86
+ 'For multi-GPU: increase gpu_count and TENSOR_PARALLEL_SIZE to match.',
87
+ 'HF_TOKEN required for gated models (Llama, Gemma, Mistral).',
88
+ 'OpenAI client: OpenAI(base_url="<endpoint>/v1", api_key="<key>")',
89
+ ],
90
+ },
91
+ {
92
+ name: 'llama-cpp',
93
+ title: 'llama.cpp Server',
94
+ description: 'Fast quantised (GGUF) model inference via llama.cpp HTTP server',
95
+ type: 'endpoint',
96
+ image: 'michaelmanleyx/llama-cpp:server-cuda',
97
+ gpu: 'RTX_4090',
98
+ gpu_count: 1,
99
+ port: 8080,
100
+ health_path: '/health',
101
+ min_vram_gb: 8,
102
+ env: {
103
+ LLAMA_ARG_HF_REPO: 'bartowski/Meta-Llama-3.1-8B-Instruct-GGUF',
104
+ LLAMA_ARG_HF_FILE: 'Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf',
105
+ },
106
+ notes: [
107
+ 'Preferred: `badgr serve --runtime llama.cpp --hf-repo <repo> --hf-file <file>`.',
108
+ 'Override LLAMA_ARG_HF_REPO and LLAMA_ARG_HF_FILE via --env.',
109
+ 'Supports any GGUF from HuggingFace Hub.',
110
+ ],
111
+ },
112
+ {
113
+ name: 'invokeai',
114
+ title: 'InvokeAI',
115
+ description: 'Professional Stable Diffusion image generation and editing with InvokeAI',
116
+ type: 'endpoint',
117
+ image: 'ghcr.io/invoke-ai/invokeai:latest',
118
+ gpu: 'RTX_4090',
119
+ gpu_count: 1,
120
+ port: 9090,
121
+ health_path: '/api/v1/app/version',
122
+ min_vram_gb: 16,
123
+ env: {
124
+ INVOKEAI_ROOT: '/workspace/invokeai',
125
+ PUBLIC_KEY: '<optional-ssh-public-key>',
126
+ },
127
+ notes: [
128
+ 'First boot downloads models — allow 5–10 min before the UI is ready.',
129
+ 'Set PUBLIC_KEY to inject an SSH key for SCP file transfers.',
130
+ 'Models and outputs persist under INVOKEAI_ROOT.',
131
+ ],
132
+ },
133
+ {
134
+ name: 'kohya-ss',
135
+ title: 'Kohya SS Training',
136
+ description: 'Stable Diffusion LoRA/DreamBooth/fine-tuning via Kohya SS web GUI',
137
+ type: 'endpoint',
138
+ image: 'bmaltais/kohya-ss-gui:latest',
139
+ gpu: 'RTX_4090',
140
+ gpu_count: 1,
141
+ port: 7860,
142
+ health_path: '/',
143
+ min_vram_gb: 8,
144
+ env: {
145
+ HUGGINGFACE_TOKEN: '<optional>',
146
+ },
147
+ notes: [
148
+ 'Kohya SS GUI is available at the returned endpoint URL (port 7860).',
149
+ 'For headless LoRA training, use `badgr train config.yaml` instead.',
150
+ ],
151
+ },
152
+ {
153
+ name: 'text-gen-webui',
154
+ title: 'Text Generation WebUI',
155
+ description: 'Oobabooga text-generation-webui — feature-rich LLM front-end for many backends',
156
+ type: 'endpoint',
157
+ image: 'atinoda/text-generation-webui:default-nightly',
158
+ gpu: 'RTX_4090',
159
+ gpu_count: 1,
160
+ port: 7860,
161
+ health_path: '/',
162
+ min_vram_gb: 16,
163
+ env: {
164
+ HF_TOKEN: '<for-gated-models>',
165
+ },
166
+ notes: [
167
+ 'Load models from the web UI or pre-set via command args inside the container.',
168
+ 'Supports llama.cpp, ExLlamaV2, AutoGPTQ, transformers, and more.',
169
+ ],
170
+ },
171
+ {
172
+ name: 'sglang',
173
+ title: 'SGLang Server',
174
+ description: 'High-throughput LLM serving optimised for structured generation (SGLang)',
175
+ type: 'endpoint',
176
+ image: 'lmsysorg/sglang:latest',
177
+ gpu: 'A100',
178
+ gpu_count: 1,
179
+ port: 30000,
180
+ health_path: '/health',
181
+ min_vram_gb: 40,
182
+ env: {
183
+ MODEL_PATH: 'meta-llama/Llama-3.1-8B-Instruct',
184
+ HF_TOKEN: '<for-gated-models>',
185
+ TP_SIZE: '1',
186
+ },
187
+ command: [
188
+ 'python', '-m', 'sglang.launch_server',
189
+ '--model-path', '${MODEL_PATH}',
190
+ '--host', '0.0.0.0',
191
+ '--port', '30000',
192
+ '--tp', '${TP_SIZE}',
193
+ ],
194
+ notes: [
195
+ 'Optimised for structured output (JSON schema, regex) and RadixAttention.',
196
+ 'OpenAI-compatible API on port 30000.',
197
+ 'Increase TP_SIZE and gpu_count together for tensor parallelism.',
198
+ 'HF_TOKEN required for gated models.',
199
+ ],
200
+ },
201
+ {
202
+ name: 'tgi',
203
+ title: 'Text Generation Inference (TGI)',
204
+ description: 'Hugging Face TGI — production-grade LLM inference with continuous batching',
205
+ type: 'endpoint',
206
+ image: 'ghcr.io/huggingface/text-generation-inference:latest',
207
+ gpu: 'RTX_4090',
208
+ gpu_count: 1,
209
+ port: 80,
210
+ health_path: '/health',
211
+ min_vram_gb: 24,
212
+ env: {
213
+ MODEL_ID: 'meta-llama/Llama-3.1-8B-Instruct',
214
+ HF_TOKEN: '<for-gated-models>',
215
+ MAX_INPUT_LENGTH: '4096',
216
+ MAX_TOTAL_TOKENS: '8192',
217
+ NUM_SHARD: '1',
218
+ },
219
+ notes: [
220
+ 'OpenAI-compatible API at /v1/chat/completions.',
221
+ 'Set NUM_SHARD equal to gpu_count for sharded (tensor-parallel) inference.',
222
+ 'HF_TOKEN required for Llama, Gemma, and other gated models.',
223
+ ],
224
+ },
225
+ {
226
+ name: 'auto1111',
227
+ title: 'AUTOMATIC1111',
228
+ description: 'Stable Diffusion web UI — the largest SD ecosystem with ControlNet, LoRA, img2img, inpainting, and upscaling',
229
+ type: 'endpoint',
230
+ image: 'aidockorg/stable-diffusion-webui-cuda:latest',
231
+ gpu: 'RTX_4090',
232
+ gpu_count: 1,
233
+ port: 7860,
234
+ health_path: '/',
235
+ min_vram_gb: 6,
236
+ env: {
237
+ WEBUI_FLAGS: '--xformers',
238
+ },
239
+ notes: [
240
+ 'Web UI at the returned endpoint URL (port 7860).',
241
+ 'Place model checkpoints under /opt/dl-ui/repositories/stable-diffusion-webui/models/Stable-diffusion/.',
242
+ 'Override WEBUI_FLAGS to pass extra launch args (e.g. --medvram for lower VRAM).',
243
+ ],
244
+ },
245
+ {
246
+ name: 'forge',
247
+ title: 'Stable Diffusion WebUI Forge',
248
+ description: 'Optimised fork of A1111 with GPU memory improvements — runs models that OOM on standard A1111',
249
+ type: 'endpoint',
250
+ image: 'aidockorg/stable-diffusion-webui-forge-cuda:latest',
251
+ gpu: 'RTX_4090',
252
+ gpu_count: 1,
253
+ port: 7860,
254
+ health_path: '/',
255
+ min_vram_gb: 4,
256
+ env: {
257
+ WEBUI_FLAGS: '--xformers',
258
+ },
259
+ notes: [
260
+ 'Web UI at the returned endpoint URL (port 7860).',
261
+ 'Forge uses less VRAM than A1111 for the same model — good for 8–12 GB cards.',
262
+ 'Compatible with most A1111 extensions.',
263
+ ],
264
+ },
265
+ {
266
+ name: 'nerfstudio',
267
+ title: 'Nerfstudio',
268
+ description: 'NeRF and 3D Gaussian Splatting training and reconstruction from image captures',
269
+ type: 'job',
270
+ image: 'ghcr.io/nerfstudio-project/nerfstudio:latest',
271
+ gpu: 'RTX_4090',
272
+ gpu_count: 1,
273
+ min_vram_gb: 16,
274
+ env: {
275
+ METHOD: 'nerfacto',
276
+ },
277
+ notes: [
278
+ 'Mount your images to /workspace/data and your output dir to /workspace/outputs.',
279
+ 'Override METHOD to switch between nerfacto, splatfacto, instant-ngp, etc.',
280
+ 'Training a typical scene takes 15–45 min depending on method and image count.',
281
+ ],
282
+ },
283
+ {
284
+ name: 'openfold',
285
+ title: 'OpenFold',
286
+ description: 'Trainable, memory-efficient GPU-friendly PyTorch reproduction of AlphaFold 2 for protein structure prediction',
287
+ type: 'job',
288
+ image: 'nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04',
289
+ gpu: 'A100',
290
+ gpu_count: 1,
291
+ min_vram_gb: 40,
292
+ env: {
293
+ OPENFOLD_REPO: 'https://github.com/aqlaboratory/openfold',
294
+ },
295
+ notes: [
296
+ 'No pre-built public image exists — this uses a CUDA base; clone and install OpenFold at boot.',
297
+ 'See https://github.com/aqlaboratory/openfold for full install steps.',
298
+ 'Full inference on a single sequence: ~5–20 min on an A100.',
299
+ ],
300
+ },
301
+ {
302
+ name: 'blender-render',
303
+ title: 'Blender GPU Render',
304
+ description: 'Headless Blender GPU rendering via CUDA — render .blend files to image sequences or video',
305
+ type: 'job',
306
+ image: 'blenderkit/headless-blender:blender-4.4',
307
+ gpu: 'RTX_4090',
308
+ gpu_count: 1,
309
+ min_vram_gb: 8,
310
+ env: {
311
+ BLEND_FILE: '/workspace/scene.blend',
312
+ FRAME_START: '1',
313
+ FRAME_END: '250',
314
+ },
315
+ notes: [
316
+ 'Mount your .blend file to /workspace/scene.blend.',
317
+ 'Set FRAME_START and FRAME_END to the frame range to render.',
318
+ 'Output frames are written to /workspace/output/ by default.',
319
+ ],
320
+ },
321
+ {
322
+ name: 'openmm',
323
+ title: 'OpenMM',
324
+ description: 'GPU-accelerated molecular dynamics simulation using the OpenMM toolkit',
325
+ type: 'job',
326
+ image: 'saladtechnologies/openmm:latest',
327
+ gpu: 'RTX_4090',
328
+ gpu_count: 1,
329
+ min_vram_gb: 8,
330
+ env: {
331
+ SIMULATION_SCRIPT: '/workspace/simulate.py',
332
+ },
333
+ notes: [
334
+ 'Mount your Python simulation script to /workspace/simulate.py.',
335
+ 'OpenMM auto-selects CUDA platform when a GPU is present.',
336
+ 'Typical MD simulation runs: minutes to hours depending on system size and steps.',
337
+ ],
338
+ },
339
+ {
340
+ name: 'gromacs',
341
+ title: 'GROMACS',
342
+ description: 'High-performance molecular dynamics package with GPU-accelerated bonded and non-bonded calculations',
343
+ type: 'job',
344
+ image: 'scientiflow/gromacs-gpu:2024.3',
345
+ gpu: 'RTX_4090',
346
+ gpu_count: 1,
347
+ min_vram_gb: 8,
348
+ env: {
349
+ GMX_GPU_DD_COMMS: 'true',
350
+ GMX_GPU_PME_PP_COMMS: 'true',
351
+ },
352
+ notes: [
353
+ 'Mount your .tpr input file and output directory to /workspace.',
354
+ 'GROMACS auto-detects CUDA GPU — no extra flags needed.',
355
+ 'For multi-GPU: increase gpu_count and pass -ntmpi / -ntomp flags via command override.',
356
+ ],
357
+ },
358
+ {
359
+ name: 'lammps',
360
+ title: 'LAMMPS',
361
+ description: 'Large-scale Atomic/Molecular Massively Parallel Simulator with GPU acceleration via Kokkos/CUDA',
362
+ type: 'job',
363
+ image: 'nvcr.io/hpc/lammps:release-29Sep2021',
364
+ gpu: 'RTX_4090',
365
+ gpu_count: 1,
366
+ min_vram_gb: 8,
367
+ env: {
368
+ LAMMPS_INPUT: '/workspace/in.lammps',
369
+ },
370
+ notes: [
371
+ 'Mount your LAMMPS input script to /workspace/in.lammps.',
372
+ 'GPU acceleration uses the Kokkos or GPU package — enable via -pk gpu 1 in your input.',
373
+ 'Simulation runtime scales with system size; use --max-runtime to cap spend.',
374
+ ],
375
+ },
376
+ {
377
+ name: 'diffusers',
378
+ title: 'Hugging Face Diffusers',
379
+ description: 'State-of-the-art diffusion model library for image, video, and audio generation',
380
+ type: 'job',
381
+ image: 'diffusers/diffusers-pytorch-cuda:latest',
382
+ gpu: 'RTX_4090',
383
+ gpu_count: 1,
384
+ min_vram_gb: 16,
385
+ env: {
386
+ HF_TOKEN: '<for-gated-models>',
387
+ MODEL_ID: 'stabilityai/stable-diffusion-xl-base-1.0',
388
+ SCRIPT: '/workspace/run.py',
389
+ },
390
+ notes: [
391
+ 'Mount your generation or training script to /workspace/run.py.',
392
+ 'HF_TOKEN required for gated models.',
393
+ 'Supports SDXL, FLUX, Stable Diffusion 3, ControlNet, IP-Adapter, and more.',
394
+ ],
395
+ },
396
+ {
397
+ name: 'torchtune',
398
+ title: 'torchtune',
399
+ description: 'PyTorch-native post-training library for LLM fine-tuning via config-driven YAML recipes',
400
+ type: 'job',
401
+ image: 'pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel',
402
+ gpu: 'A100',
403
+ gpu_count: 1,
404
+ min_vram_gb: 40,
405
+ env: {
406
+ HF_TOKEN: '<for-gated-models>',
407
+ RECIPE: 'lora_finetune_single_device',
408
+ CONFIG: '/workspace/config.yaml',
409
+ },
410
+ notes: [
411
+ 'Install torchtune at boot: pip install torchtune.',
412
+ 'Mount your YAML recipe config to /workspace/config.yaml.',
413
+ 'HF_TOKEN required for Llama and other gated base models.',
414
+ 'Supports LoRA, QLoRA, DPO, full fine-tune, and quantisation-aware training.',
415
+ ],
416
+ },
417
+ ];
418
+
419
+ export const TEMPLATE_MAP = Object.fromEntries(TEMPLATES.map(t => [t.name, t]));
420
+
421
+ /**
422
+ * Build the args array passed to serveCommand / runCommand.
423
+ * Template defaults are applied first; CLI overrides win.
424
+ */
425
+ export function buildTemplateFlags(template, overrides) {
426
+ const args = ['--image', template.image];
427
+
428
+ args.push('--gpu', overrides.gpu ?? template.gpu);
429
+
430
+ const count = overrides.count ?? template.gpu_count ?? 1;
431
+ if (count > 1) args.push('--count', String(count));
432
+
433
+ if (overrides.region) args.push('--region', overrides.region);
434
+ if (overrides.maxCost != null) args.push('--max-cost', String(overrides.maxCost));
435
+ if (overrides.maxPrice != null) args.push('--max-price', String(overrides.maxPrice));
436
+ if (overrides.tier) args.push('--tier', overrides.tier);
437
+ if (overrides.name) args.push('--name', overrides.name);
438
+ if (overrides.noWait) args.push('--no-wait');
439
+ if (overrides.persistent) args.push('--persistent');
440
+
441
+ if (template.type === 'endpoint' && template.health_path) {
442
+ args.push('--health-path', template.health_path);
443
+ }
444
+
445
+ // Merge env: template defaults first, user overrides win
446
+ const mergedEnv = { ...(template.env || {}), ...(overrides.env || {}) };
447
+ for (const [k, v] of Object.entries(mergedEnv)) {
448
+ if (!v.startsWith('<')) args.push('--env', `${k}=${v}`);
449
+ }
450
+
451
+ return args;
452
+ }
453
+
454
+ /** Parse the flags that follow `badgr serve template <name>` or `badgr run template <name>`. */
455
+ export function parseTemplateOverrides(args) {
456
+ const overrides = { env: {} };
457
+ let i = 0;
458
+ while (i < args.length) {
459
+ const a = args[i];
460
+ if (a === '--gpu') { overrides.gpu = args[++i]; i++; continue; }
461
+ if (a === '--count') { overrides.count = parseInt(args[++i], 10); i++; continue; }
462
+ if (a === '--region') { overrides.region = args[++i]; i++; continue; }
463
+ if (a === '--max-cost') { overrides.maxCost = parseFloat(args[++i]); i++; continue; }
464
+ if (a === '--max-price') { overrides.maxPrice = parseFloat(args[++i]); i++; continue; }
465
+ if (a === '--max-runtime') { overrides.maxRuntime = parseFloat(args[++i]); i++; continue; }
466
+ if (a === '--tier') { overrides.tier = args[++i]; i++; continue; }
467
+ if (a === '--name') { overrides.name = args[++i]; i++; continue; }
468
+ if (a === '--no-wait') { overrides.noWait = true; i++; continue; }
469
+ if (a === '--persistent') { overrides.persistent = true; i++; continue; }
470
+ if (a === '--env') {
471
+ const kv = args[++i]; i++;
472
+ const idx = kv.indexOf('=');
473
+ if (idx > 0) overrides.env[kv.slice(0, idx)] = kv.slice(idx + 1);
474
+ continue;
475
+ }
476
+ i++;
477
+ }
478
+ return overrides;
479
+ }
@@ -12,7 +12,7 @@ import { addDeployment, addReceipt, updateReceipt, generateReceiptId } from '../
12
12
  import { normalizeTier, callWithFallback, HIGH_RATE_THRESHOLD } from '../fallback.js';
13
13
  import { formatCliError } from '../errors.js';
14
14
 
15
- const COMFYUI_IMAGE = 'yanwk/comfyui-boot:latest';
15
+ const COMFYUI_IMAGE = process.env.COMFYUI_IMAGE || 'yanwk/comfyui-boot:cu126-megapak';
16
16
  const HEALTH_PATH = '/system_stats';
17
17
  const WAIT_TIMEOUT_MS = 10 * 60 * 1000; // 10 min — model downloads on first boot
18
18
  const MAX_WORKFLOW_B = 1 * 1024 * 1024; // 1 MB workflow limit
@@ -5,7 +5,8 @@ function fmtMs(ms) {
5
5
  return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
6
6
  }
7
7
 
8
- function fmtRuntime(s) {
8
+ export function fmtRuntime(s) {
9
+ if (s == null) return '—';
9
10
  if (s < 60) return `${s}s`;
10
11
  return `${Math.floor(s / 60)}m ${s % 60}s`;
11
12
  }