#!/usr/bin/env python3 """Cloud GPU deployment launcher for FSI_Edge training. Supports: - RunPod (Serverless + Secure Cloud) - Vast.ai - Lambda Labs - Manual mode (generates setup script only) Usage: python cloud_launch.py --provider runpod --preset production --model-size 800M python cloud_launch.py --provider vast --preset dev --gpus 8 python cloud_launch.py --provider lambda --preset quick python cloud_launch.py --provider manual --output-dir ./deploy # generates setup script """ import os import sys import json import argparse import subprocess import tempfile import shutil from pathlib import Path from datetime import datetime REPO_ROOT = Path(__file__).resolve().parent.parent PRESETS = { "quick": { "model_size": "360M", "stages": "stage1,stage1b", "batch_size": 4, "max_steps": 5000, "fp16": True, "gpu_type": "RTX 4090", "min_vram_gb": 24, "num_gpus": 1, "data_samples": 10000, }, "dev": { "model_size": "800M", "stages": "stage1,stage1b,stage2,stage2b", "batch_size": 8, "max_steps": 50000, "fp16": True, "gpu_type": "A100-80GB", "min_vram_gb": 80, "num_gpus": 4, "data_samples": 100000, }, "production": { "model_size": "800M", "stages": "stage0,stage1,stage1b,stage2,stage2b,stage3,stage3b,stage4,stage5", "batch_size": 32, "max_steps": 500000, "fp16": True, "gpu_type": "H100-80GB", "min_vram_gb": 80, "num_gpus": 8, "data_samples": 5000000, }, "production_fast": { "model_size": "800M", "stages": "stage0,stage1,stage1b,stage2,stage2b,stage3,stage3b,stage4", "batch_size": 64, "max_steps": 200000, "fp16": True, "gpu_type": "H100-80GB", "min_vram_gb": 80, "num_gpus": 8, "data_samples": 2000000, }, } PROVIDER_CONFIGS = { "runpod": { "template_id": "runpod-pytorch:2.2.0-cuda12.1.0", "container_disk_gb": 50, "supported_gpus": ["RTX 4090", "A100-80GB", "A100-SXM-80GB", "H100-80GB", "H100-SXM-80GB"], }, "vast": { "supported_gpus": ["RTX 4090", "A100-80GB", "A100-SXM-80GB", "H100-80GB", "H100-SXM-80GB"], "disk_gb": 100, }, "lambda": { "supported_gpu_types": ["1x A100", "4x A100", "8x A100", "8x H100"], "base_image": "nvidia/cuda:12.1.0-runtime-ubuntu22.04", }, } SETUP_SCRIPT = """#!/bin/bash set -e echo "=== FSI_Edge Cloud Instance Setup ===" export DEBIAN_FRONTEND=noninteractive export CUDA_HOME=/usr/local/cuda export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH export NCCL_DEBUG=INFO export NCCL_IB_DISABLE=0 export NCCL_IB_HCA=$(ibstatus 2>/dev/null | grep -oP 'mlx5_\\d+' | head -1 || echo "") export TORCH_NCCL_ASYNC_ERROR_HANDLING=1 apt-get update && apt-get install -y --no-install-recommends \\ git curl wget build-essential python3-dev python3-pip \\ libopenmpi-dev openmpi-bin \\ libnccl-dev libnccl2 nccl-tools \\ ibverbs-providers libibverbs-dev \\ ninja-build pip install --upgrade pip setuptools wheel if ! nvidia-smi &>/dev/null; then echo "ERROR: No NVIDIA GPU detected!" exit 1 fi echo "GPU: $(nvidia-smi --query-gpu=name --format=csv,noheader | head -1)" echo "VRAM: $(nvidia-smi --query-gpu=memory.total --format=csv,noheader | head -1)" echo "CUDA: $(nvcc --version 2>/dev/null | tail -1 || nvidia-smi | grep 'CUDA Version' || echo 'unknown')" echo "=== Installing FSI_Edge dependencies ===" cd /workspace/FSI_Edge pip install torch==2.2.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install -r requirements.txt pip install flash-attn --no-build-isolation pip install deepspeed mpi4py echo "=== Generating training data ===" python data/prepare_data.py --samples {data_samples} echo "=== Setup complete ===" echo "To launch training:" echo " python run_pipeline.py --preset {preset} --model-size {model_size} --stages {stages} [additional args]" echo "" echo "For multi-node (if applicable):" echo ' torchrun --nnodes=$NNODES --nproc-per-node=$NPROC_PER_NODE --rdzv-endpoint=$MASTER_ADDR run_pipeline.py ...' """ TRAIN_LAUNCH_TEMPLATE = """#!/bin/bash set -e echo "=== FSI_Edge Training Launch ===" cd /workspace/FSI_Edge NNODES=${{NNODES:-1}} NPROC_PER_NODE=${{NPROC_PER_NODE:-{num_gpus}}} MASTER_ADDR=${{MASTER_ADDR:-localhost}} MASTER_PORT=${{MASTER_PORT:-29500}} WORLD_SIZE=$((NNODES * NPROC_PER_NODE)) EXTRA_ARGS="${{@}}" if [ $NNODES -eq 1 ] && [ $NPROC_PER_NODE -eq 1 ]; then CMD="python run_pipeline.py --preset {preset} --model-size {model_size} --stages \\"{stages}\\" --batch-size {batch_size} --max-steps {max_steps} --output-dir /workspace/FSI_Edge/output/{run_id}" else CMD="torchrun --nnodes=$NNODES --nproc-per-node=$NPROC_PER_NODE --rdzv-endpoint=$MASTER_ADDR:$MASTER_PORT --rdzv-backend=c10d --max-restarts=3 run_pipeline.py --preset {preset} --model-size {model_size} --stages \\"{stages}\\" --batch-size {batch_size} --max-steps {max_steps} --output-dir /workspace/FSI_Edge/output/{run_id}" fi if [ -n "$WANDB_API_KEY" ]; then CMD="$CMD --wandb-project fsi_edge" fi if [ -n "$HF_TOKEN" ]; then CMD="$CMD --repo-id fsi_edge/fsi_edge-{model_size} --hf-token $HF_TOKEN" fi echo "Running: $CMD $EXTRA_ARGS" eval "$CMD $EXTRA_ARGS" """ def generate_setup_script(preset_name, preset_config): data_samples = preset_config.get("data_samples", 500000) return SETUP_SCRIPT.format( data_samples=data_samples, preset=preset_name, model_size=preset_config["model_size"], stages=preset_config["stages"], ) def generate_launch_script(preset_name, preset_config, run_id=None): run_id = run_id or datetime.now().strftime("%Y%m%d_%H%M%S") return TRAIN_LAUNCH_TEMPLATE.format( preset=preset_name, model_size=preset_config["model_size"], stages=preset_config["stages"], batch_size=preset_config["batch_size"], max_steps=preset_config["max_steps"], num_gpus=preset_config.get("num_gpus", 1), run_id=run_id, ) def deploy_manual(preset_name, preset_config, output_dir): output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) setup_script = output_dir / "setup.sh" setup_script.write_text(generate_setup_script(preset_name, preset_config)) setup_script.chmod(0o755) launch_script = output_dir / "launch.sh" launch_script.write_text(generate_launch_script(preset_name, preset_config)) launch_script.chmod(0o755) readme = output_dir / "README.md" readme.write_text(f"""# FSI_Edge Cloud Deployment — {preset_name} ## Upload to GPU Instance ```bash # Copy FSI_Edge to instance rsync -avz --exclude='__pycache__' --exclude='.git' /FSI_Edge/ user@instance:/workspace/FSI_Edge/ # SSH in and run setup ssh user@instance cd /workspace/FSI_Edge bash {output_dir.name}/setup.sh # Launch training bash {output_dir.name}/launch.sh ``` ## Multi-Node (if applicable) ```bash # On master: NNODES=2 NPROC_PER_NODE={preset_config['num_gpus']} bash {output_dir.name}/launch.sh # On worker (same command, auto-discovers via env): NNODES=2 NPROC_PER_NODE={preset_config['num_gpus']} MASTER_ADDR= bash {output_dir.name}/launch.sh ``` ## Config | Setting | Value | |---------|-------| | Model | {preset_config['model_size']} | | Stages | {preset_config['stages']} | | Batch Size | {preset_config['batch_size']} | | Max Steps | {preset_config['max_steps']} | | GPUs | {preset_config['num_gpus']} | | Data Samples | {preset_config['data_samples']} | """) # Create tarball shutil.make_archive( str(output_dir / f"fsi_edge_{preset_name}_deploy"), "gztar", REPO_ROOT, ) print(f"Deployment package created in {output_dir}/") print(f" setup.sh — Instance setup script") print(f" launch.sh — Training launch script") print(f" README.md — Deployment instructions") print(f" fsi_edge_{preset_name}_deploy.tar.gz — Full source tarball") return output_dir def deploy_runpod(preset_name, preset_config, api_key=None, pod_id=None): """Generate RunPod deployment config or launch via API.""" config = { "name": f"fsi_edge_{preset_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}", "imageName": "nvidia/cuda:12.1.0-runtime-ubuntu22.04", "containerDiskSizeGb": 50, "volumeInGb": 0, "ports": "22/tcp,8888/tcp,29500/tcp", "gpuTypeName": preset_config["gpu_type"], "gpuCount": preset_config["num_gpus"], "env": [ {"key": "WANDB_API_KEY", "value": os.environ.get("WANDB_API_KEY", "")}, {"key": "HF_TOKEN", "value": os.environ.get("HF_TOKEN", "")}, ], } config_path = Path.cwd() / f"runpod_{preset_name}.json" config_path.write_text(json.dumps(config, indent=2)) print(f"RunPod config written: {config_path}") print() print("To deploy via RunPod CLI:") print(f" # Install: pip install runpod") print(f" # Deploy: runpodctl pod create --config {config_path.name}") print() print("Or use the RunPod web UI at https://www.runpod.io") print("1. Click 'Deploy' → 'Secure Cloud'") print("2. Select template: RunPod PyTorch 2.2") print(f"3. GPU: {preset_config['gpu_type']} x {preset_config['num_gpus']}") print(f"4. Container Disk: 50GB") print("5. Launch and SSH in, then:") print(f" - git clone https://github.com/YOUR_ORG/FSI_Edge.git /workspace/FSI_Edge") print(f" - cd /workspace/FSI_Edge && bash scripts/setup.sh") print(f" - bash scripts/launch.sh") if api_key: try: import runpod runpod.api_key = api_key print("API-based deployment coming soon — use config file for now.") except ImportError: print("Install 'runpod' for API-based deployment: pip install runpod") return config_path def deploy_vast(preset_name, preset_config, api_key=None): """Generate Vast.ai deployment config.""" config = { "image": "nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04", "env": { "WANDB_API_KEY": os.environ.get("WANDB_API_KEY", ""), "HF_TOKEN": os.environ.get("HF_TOKEN", ""), }, "disk": 100, "gpu_ids": [preset_config["gpu_type"]], "num_gpus": preset_config["num_gpus"], "ssh_host": "0.0.0.0", "ssh_port": 42001, "label": f"fsi_edge_{preset_name}", } config_path = Path.cwd() / f"vast_{preset_name}.json" config_path.write_text(json.dumps(config, indent=2)) print(f"Vast.ai config written: {config_path}") print() print("To deploy on Vast.ai:") print(f" pip install vastai") print(f" vastai create instance {config_path.name}") print() print(f"Or browse: https://cloud.vast.ai/?gpu_ids={preset_config['gpu_type'].replace(' ', '+')}") print(f"Then SSH in and run setup + launch scripts.") if api_key: try: # Attempt API-based instance creation result = subprocess.run( ["vastai", "create", "instance", str(config_path)], capture_output=True, text=True, timeout=30, ) print(f"Vast.ai API result: {result.stdout}") if result.stderr: print(f" stderr: {result.stderr}") except Exception as e: print(f" Auto-deploy attempted but failed: {e}") print(" Use the config file above to deploy manually.") return config_path def deploy_lambda(preset_name, preset_config, api_key=None): """Generate Lambda Labs deployment config.""" gpu_map = { 1: "1x A100", 4: "4x A100", 8: "8x A100", } gpu_type = gpu_map.get(preset_config["num_gpus"], "8x A100") config = { "name": f"fsi_edge_{preset_name}", "instance_type": { "gpu_type": gpu_type, "region": "us-east-1", }, "ssh_key_names": [], "file_system_names": [], "quantity": 1, } config_path = Path.cwd() / f"lambda_{preset_name}.json" config_path.write_text(json.dumps(config, indent=2)) print(f"Lambda Labs config written: {config_path}") print() print("To deploy on Lambda Labs:") print(f" pip install lambda-cloud") print(f" lambda-cloud launch instance {config_path.name}") print() print("Or use the web UI: https://cloud.lambdalabs.com/instances") print(f"Select: {gpu_type}") print("Then SSH in and run setup + launch scripts.") return config_path def main(): parser = argparse.ArgumentParser( description="FSI_Edge Cloud GPU Deployment Launcher", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python scripts/cloud_launch.py --provider runpod --preset production --model-size 800M python scripts/cloud_launch.py --provider vast --preset dev --gpus 4 python scripts/cloud_launch.py --provider manual --output-dir ./deploy """, ) parser.add_argument("--provider", type=str, default="manual", choices=["runpod", "vast", "lambda", "manual"], help="Cloud provider to deploy to") parser.add_argument("--preset", type=str, default="dev", choices=list(PRESETS.keys()), help="Training preset configuration") parser.add_argument("--model-size", type=str, default=None, choices=["360M", "800M", "1.5B"], help="Override model size") parser.add_argument("--stages", type=str, default=None, help="Override stages (e.g. 'stage1,stage2,stage3')") parser.add_argument("--gpus", type=int, default=None, help="Override number of GPUs") parser.add_argument("--batch-size", type=int, default=None, help="Override batch size") parser.add_argument("--max-steps", type=int, default=None, help="Override max training steps") parser.add_argument("--output-dir", type=str, default="./deploy", help="Output directory for deployment files (manual mode)") parser.add_argument("--api-key", type=str, default=None, help="Provider API key (optional, for automated deployment)") parser.add_argument("--data-samples", type=int, default=None, help="Override number of synthetic data samples") args = parser.parse_args() # Build config from preset + overrides config = dict(PRESETS[args.preset]) if args.model_size: config["model_size"] = args.model_size if args.stages: config["stages"] = args.stages if args.gpus: config["num_gpus"] = args.gpus if args.batch_size: config["batch_size"] = args.batch_size if args.max_steps: config["max_steps"] = args.max_steps if args.data_samples: config["data_samples"] = args.data_samples print(f"FSI_Edge Cloud Deployment") print(f" Provider: {args.provider}") print(f" Preset: {args.preset}") print(f" Model: {config['model_size']}") print(f" Stages: {config['stages']}") print(f" GPUs: {config['num_gpus']}") print(f" Batch: {config['batch_size']}") print(f" Steps: {config['max_steps']}") print() if args.provider == "manual": deploy_manual(args.preset, config, args.output_dir) elif args.provider == "runpod": deploy_runpod(args.preset, config, args.api_key) elif args.provider == "vast": deploy_vast(args.preset, config, args.api_key) elif args.provider == "lambda": deploy_lambda(args.preset, config, args.api_key) print(f"\nDeployment package generated. Copy to your GPU instance and run setup.sh -> launch.sh") if __name__ == "__main__": main()