# Training YOLO Object Detection Models

## What We're Building

A complete YOLOv8 object detection training pipeline on Clore.ai GPUs. Train custom detection, segmentation, and pose estimation models with automatic GPU provisioning, data preparation, and model export.

**Key Features:**

* Automatic GPU provisioning via Clore.ai API
* YOLOv8 detection, segmentation, and pose models
* Custom dataset training (COCO format)
* Data augmentation and preprocessing
* Model export (ONNX, TensorRT, CoreML)
* Training metrics and visualization
* Multi-GPU training support

## Prerequisites

* Clore.ai account with API key ([get one here](https://clore.ai))
* Python 3.10+
* Labeled dataset (YOLO format or COCO format)

```bash
pip install requests paramiko scp ultralytics roboflow
```

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                  YOLOv8 Training Pipeline                        │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │  Dataset    │  │  Training   │  │   Export & Deploy       │  │
│  │  Roboflow/  │──│  YOLOv8     │──│   ONNX/TensorRT/CoreML  │  │
│  │  Local      │  │  Ultralytics│  │                         │  │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
│         │                │                    │                  │
│         └────────────────┴────────────────────┘                  │
│                          │                                       │
│                 ┌────────▼────────┐                              │
│                 │  Clore.ai GPU   │                              │
│                 │  RTX 4090/A100  │                              │
│                 └─────────────────┘                              │
└─────────────────────────────────────────────────────────────────┘
```

## Step 1: Clore.ai YOLO Client

```python
# clore_yolo_client.py
import requests
import time
import secrets
from typing import Dict, Any, Optional
from dataclasses import dataclass

@dataclass
class YOLOServer:
    """GPU server for YOLO training."""
    server_id: int
    order_id: int
    ssh_host: str
    ssh_port: int
    ssh_password: str
    gpu_model: str
    gpu_count: int
    hourly_cost: float


class CloreYOLOClient:
    """Clore.ai client for YOLO training."""
    
    BASE_URL = "https://api.clore.ai"
    YOLO_IMAGE = "ultralytics/ultralytics:latest-python"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.headers = {"auth": api_key}
    
    def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
        """Make API request."""
        url = f"{self.BASE_URL}{endpoint}"
        
        for attempt in range(3):
            response = requests.request(
                method, url,
                headers=self.headers,
                timeout=30,
                **kwargs
            )
            data = response.json()
            
            if data.get("code") == 5:
                time.sleep(2 ** attempt)
                continue
            
            if data.get("code") != 0:
                raise Exception(f"API Error: {data}")
            return data
        
        raise Exception("Max retries exceeded")
    
    def find_yolo_gpu(self, max_price_usd: float = 0.50) -> Optional[Dict]:
        """Find GPU suitable for YOLO training."""
        servers = self._request("GET", "/v1/marketplace")["servers"]
        
        # GPUs good for YOLO (fast training, reasonable VRAM)
        yolo_gpus = ["RTX 4090", "RTX 4080", "RTX 3090", "RTX 3080",
                     "A100", "A6000", "A5000"]
        
        candidates = []
        for server in servers:
            if server.get("rented"):
                continue
            
            gpu_array = server.get("gpu_array", [])
            if not any(any(g in gpu for g in yolo_gpus) for gpu in gpu_array):
                continue
            
            price = server.get("price", {}).get("usd", {}).get("spot")
            if not price or price > max_price_usd:
                continue
            
            candidates.append({
                "id": server["id"],
                "gpus": gpu_array,
                "gpu_count": len(gpu_array),
                "price_usd": price,
                "reliability": server.get("reliability", 0)
            })
        
        if not candidates:
            return None
        
        candidates.sort(key=lambda x: (x["price_usd"], -x["reliability"]))
        return candidates[0]
    
    def rent_yolo_server(self, server: Dict, use_spot: bool = True) -> YOLOServer:
        """Rent a server for YOLO training."""
        ssh_password = secrets.token_urlsafe(16)
        
        order_data = {
            "renting_server": server["id"],
            "type": "spot" if use_spot else "on-demand",
            "currency": "CLORE-Blockchain",
            "image": self.YOLO_IMAGE,
            "ports": {"22": "tcp", "6006": "http"},
            "env": {"NVIDIA_VISIBLE_DEVICES": "all"},
            "ssh_password": ssh_password
        }
        
        if use_spot:
            order_data["spotprice"] = server["price_usd"] * 1.15
        
        result = self._request("POST", "/v1/create_order", json=order_data)
        order_id = result["order_id"]
        
        # Wait for server
        for _ in range(120):
            orders = self._request("GET", "/v1/my_orders")["orders"]
            order = next((o for o in orders if o["order_id"] == order_id), None)
            
            if order and order.get("status") == "running":
                conn = order["connection"]["ssh"]
                parts = conn.split()
                ssh_host = parts[1].split("@")[1] if "@" in parts[1] else parts[1]
                ssh_port = int(parts[-1]) if "-p" in conn else 22
                
                return YOLOServer(
                    server_id=server["id"],
                    order_id=order_id,
                    ssh_host=ssh_host,
                    ssh_port=ssh_port,
                    ssh_password=ssh_password,
                    gpu_model=server["gpus"][0] if server["gpus"] else "Unknown",
                    gpu_count=server["gpu_count"],
                    hourly_cost=server["price_usd"]
                )
            
            time.sleep(2)
        
        raise Exception("Timeout waiting for server")
    
    def cancel_order(self, order_id: int):
        """Cancel an order."""
        self._request("POST", "/v1/cancel_order", json={"id": order_id})
```

## Step 2: YOLO Training Engine

```python
# yolo_trainer.py
import paramiko
from scp import SCPClient
import json
import time
import os
from typing import Dict, List, Optional
from dataclasses import dataclass

@dataclass
class TrainingConfig:
    """YOLO training configuration."""
    model: str = "yolov8n.pt"  # yolov8n, yolov8s, yolov8m, yolov8l, yolov8x
    task: str = "detect"  # detect, segment, classify, pose
    epochs: int = 100
    batch_size: int = 16
    img_size: int = 640
    learning_rate: float = 0.01
    device: str = "0"
    workers: int = 8
    patience: int = 50
    project: str = "yolo_training"
    name: str = "run"


@dataclass
class TrainingResult:
    """Training results."""
    model_path: str
    metrics: Dict
    training_time_seconds: float
    epochs_completed: int
    success: bool
    error: Optional[str] = None


class RemoteYOLOTrainer:
    """Execute YOLO training on remote GPU."""
    
    def __init__(self, ssh_host: str, ssh_port: int, ssh_password: str):
        self.ssh_host = ssh_host
        self.ssh_port = ssh_port
        self.ssh_password = ssh_password
        self._ssh = None
        self._scp = None
    
    def connect(self):
        """Establish SSH connection."""
        self._ssh = paramiko.SSHClient()
        self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self._ssh.connect(
            self.ssh_host,
            port=self.ssh_port,
            username="root",
            password=self.ssh_password,
            timeout=30
        )
        self._scp = SCPClient(self._ssh.get_transport())
    
    def disconnect(self):
        """Close connections."""
        if self._scp:
            self._scp.close()
        if self._ssh:
            self._ssh.close()
    
    def _exec(self, cmd: str, timeout: int = 7200) -> str:
        """Execute command."""
        stdin, stdout, stderr = self._ssh.exec_command(cmd, timeout=timeout)
        stdout.channel.recv_exit_status()
        return stdout.read().decode()
    
    def upload_dataset(self, local_path: str, dataset_name: str = "dataset"):
        """Upload dataset to server."""
        remote_path = f"/tmp/{dataset_name}"
        self._exec(f"mkdir -p {remote_path}")
        self._scp.put(local_path, remote_path, recursive=True)
        return remote_path
    
    def upload_file(self, local_path: str, remote_path: str):
        """Upload single file."""
        self._scp.put(local_path, remote_path)
    
    def download_file(self, remote_path: str, local_path: str):
        """Download file."""
        self._scp.get(remote_path, local_path)
    
    def download_directory(self, remote_path: str, local_path: str):
        """Download directory."""
        self._scp.get(remote_path, local_path, recursive=True)
    
    def setup_environment(self):
        """Ensure YOLOv8 is installed."""
        print("Setting up environment...")
        self._exec("pip install -q ultralytics")
        self._exec("mkdir -p /tmp/yolo_training")
    
    def verify_gpu(self) -> Dict:
        """Verify GPU availability."""
        script = '''
import torch
from ultralytics import YOLO

print(f"CUDA available: {torch.cuda.is_available()}")
print(f"Device count: {torch.cuda.device_count()}")
if torch.cuda.is_available():
    print(f"Device name: {torch.cuda.get_device_name(0)}")
    print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
'''
        output = self._exec(f"python3 -c '{script}'")
        return {"output": output}
    
    def train(self, dataset_yaml: str, config: TrainingConfig) -> TrainingResult:
        """Train YOLO model."""
        
        training_script = f'''
import json
import time
from ultralytics import YOLO

start_time = time.time()
result = {{"success": False}}

try:
    # Load model
    model = YOLO("{config.model}")
    
    # Train
    results = model.train(
        data="{dataset_yaml}",
        epochs={config.epochs},
        batch={config.batch_size},
        imgsz={config.img_size},
        lr0={config.learning_rate},
        device="{config.device}",
        workers={config.workers},
        patience={config.patience},
        project="/tmp/{config.project}",
        name="{config.name}",
        exist_ok=True,
        verbose=True
    )
    
    # Get best model path
    best_model = f"/tmp/{config.project}/{config.name}/weights/best.pt"
    
    # Validate
    metrics = model.val()
    
    result = {{
        "success": True,
        "model_path": best_model,
        "metrics": {{
            "mAP50": float(metrics.box.map50) if hasattr(metrics.box, 'map50') else 0,
            "mAP50_95": float(metrics.box.map) if hasattr(metrics.box, 'map') else 0,
            "precision": float(metrics.box.mp) if hasattr(metrics.box, 'mp') else 0,
            "recall": float(metrics.box.mr) if hasattr(metrics.box, 'mr') else 0
        }},
        "epochs_completed": {config.epochs},
        "training_time": time.time() - start_time
    }}
    
except Exception as e:
    result = {{"success": False, "error": str(e)}}

print("RESULT:" + json.dumps(result))
'''
        
        # Write script
        self._exec(f"cat > /tmp/train_yolo.py << 'EOF'\n{training_script}\nEOF")
        
        # Run training
        print(f"Training {config.model} for {config.epochs} epochs...")
        output = self._exec("python3 /tmp/train_yolo.py 2>&1", timeout=86400)
        
        # Parse result
        for line in output.split("\n"):
            if line.startswith("RESULT:"):
                result_data = json.loads(line[7:])
                return TrainingResult(
                    model_path=result_data.get("model_path", ""),
                    metrics=result_data.get("metrics", {}),
                    training_time_seconds=result_data.get("training_time", 0),
                    epochs_completed=result_data.get("epochs_completed", 0),
                    success=result_data.get("success", False),
                    error=result_data.get("error")
                )
        
        return TrainingResult(
            model_path="",
            metrics={},
            training_time_seconds=0,
            epochs_completed=0,
            success=False,
            error="Failed to parse training result"
        )
    
    def export_model(self, model_path: str, format: str = "onnx") -> str:
        """Export model to different format."""
        export_script = f'''
from ultralytics import YOLO
model = YOLO("{model_path}")
path = model.export(format="{format}")
print(f"EXPORTED:{path}")
'''
        output = self._exec(f"python3 -c '{export_script}'")
        
        for line in output.split("\n"):
            if line.startswith("EXPORTED:"):
                return line[9:]
        
        return ""
```

## Step 3: Complete YOLO Training Pipeline

```python
# yolo_pipeline.py
import os
import time
import yaml
from typing import Optional
from dataclasses import asdict

from clore_yolo_client import CloreYOLOClient, YOLOServer
from yolo_trainer import RemoteYOLOTrainer, TrainingConfig, TrainingResult


class YOLOPipeline:
    """End-to-end YOLO training pipeline on Clore.ai."""
    
    def __init__(self, api_key: str):
        self.client = CloreYOLOClient(api_key)
        self.server: YOLOServer = None
        self.trainer: RemoteYOLOTrainer = None
    
    def setup(self, max_price_usd: float = 0.50):
        """Provision GPU for YOLO training."""
        
        print("🔍 Finding GPU for YOLO training...")
        gpu = self.client.find_yolo_gpu(max_price_usd=max_price_usd)
        
        if not gpu:
            raise Exception(f"No GPU available under ${max_price_usd}/hr")
        
        print(f"   Found: {gpu['gpus']} @ ${gpu['price_usd']:.2f}/hr")
        
        print("🚀 Provisioning server...")
        self.server = self.client.rent_yolo_server(gpu)
        
        print(f"   Server ready: {self.server.ssh_host}:{self.server.ssh_port}")
        
        # Connect trainer
        self.trainer = RemoteYOLOTrainer(
            self.server.ssh_host,
            self.server.ssh_port,
            self.server.ssh_password
        )
        self.trainer.connect()
        self.trainer.setup_environment()
        self.trainer.verify_gpu()
        
        return self
    
    def prepare_dataset(self, 
                        images_path: str,
                        labels_path: str,
                        classes: list,
                        val_split: float = 0.2) -> str:
        """Prepare and upload dataset."""
        
        # Create dataset.yaml
        dataset_yaml = {
            "path": "/tmp/dataset",
            "train": "images/train",
            "val": "images/val",
            "names": {i: name for i, name in enumerate(classes)}
        }
        
        # Write YAML locally
        yaml_path = "/tmp/dataset.yaml"
        with open(yaml_path, "w") as f:
            yaml.dump(dataset_yaml, f)
        
        # Upload dataset
        print("📤 Uploading dataset...")
        self.trainer.upload_dataset(images_path, "dataset/images")
        self.trainer.upload_dataset(labels_path, "dataset/labels")
        self.trainer.upload_file(yaml_path, "/tmp/dataset.yaml")
        
        return "/tmp/dataset.yaml"
    
    def train(self, 
              dataset_yaml: str,
              model: str = "yolov8n.pt",
              epochs: int = 100,
              batch_size: int = 16,
              img_size: int = 640) -> TrainingResult:
        """Train YOLO model."""
        
        config = TrainingConfig(
            model=model,
            epochs=epochs,
            batch_size=batch_size,
            img_size=img_size
        )
        
        return self.trainer.train(dataset_yaml, config)
    
    def export(self, model_path: str, format: str = "onnx") -> str:
        """Export trained model."""
        return self.trainer.export_model(model_path, format)
    
    def download_model(self, remote_path: str, local_path: str):
        """Download trained model."""
        self.trainer.download_file(remote_path, local_path)
    
    def download_training_results(self, local_dir: str):
        """Download all training results."""
        self.trainer.download_directory("/tmp/yolo_training", local_dir)
    
    def cleanup(self):
        """Release resources."""
        if self.trainer:
            self.trainer.disconnect()
        if self.server:
            print("🧹 Releasing server...")
            self.client.cancel_order(self.server.order_id)
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.cleanup()
```

## Full Script: Production YOLO Training

```python
#!/usr/bin/env python3
"""
YOLOv8 Training on Clore.ai GPUs.

Usage:
    # Train with local dataset
    python train_yolo.py --api-key YOUR_API_KEY --data dataset.yaml --model yolov8s.pt --epochs 100
    
    # Train with Roboflow dataset
    python train_yolo.py --api-key YOUR_API_KEY --roboflow WORKSPACE/PROJECT/VERSION --model yolov8m.pt
"""

import argparse
import os
import time
import json
import secrets
import requests
import paramiko
from scp import SCPClient
from typing import Dict, Optional
from dataclasses import dataclass


@dataclass
class TrainingResult:
    model_path: str
    mAP50: float
    mAP50_95: float
    precision: float
    recall: float
    epochs: int
    time_seconds: float
    cost_usd: float
    success: bool


class CloreYOLOTrainer:
    """Complete YOLOv8 training on Clore.ai."""
    
    BASE_URL = "https://api.clore.ai"
    IMAGE = "ultralytics/ultralytics:latest-python"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.headers = {"auth": api_key}
        self.order_id = None
        self.ssh_host = None
        self.ssh_port = None
        self.ssh_password = None
        self.hourly_cost = 0.0
        self._ssh = None
        self._scp = None
    
    def _api(self, method: str, endpoint: str, **kwargs) -> Dict:
        url = f"{self.BASE_URL}{endpoint}"
        for attempt in range(3):
            response = requests.request(method, url, headers=self.headers, **kwargs)
            data = response.json()
            if data.get("code") == 5:
                time.sleep(2 ** attempt)
                continue
            if data.get("code") != 0:
                raise Exception(f"API Error: {data}")
            return data
        raise Exception("Max retries")
    
    def setup(self, max_price: float = 0.50):
        print("🔍 Finding GPU...")
        servers = self._api("GET", "/v1/marketplace")["servers"]
        
        gpus = ["RTX 4090", "RTX 4080", "RTX 3090", "RTX 3080", "A100"]
        candidates = []
        
        for s in servers:
            if s.get("rented"):
                continue
            gpu_array = s.get("gpu_array", [])
            if not any(any(g in gpu for g in gpus) for gpu in gpu_array):
                continue
            price = s.get("price", {}).get("usd", {}).get("spot")
            if price and price <= max_price:
                candidates.append({"id": s["id"], "gpus": gpu_array, "price": price})
        
        if not candidates:
            raise Exception(f"No GPU under ${max_price}/hr")
        
        gpu = min(candidates, key=lambda x: x["price"])
        print(f"   {gpu['gpus']} @ ${gpu['price']:.2f}/hr")
        
        self.ssh_password = secrets.token_urlsafe(16)
        self.hourly_cost = gpu["price"]
        
        print("🚀 Provisioning server...")
        order_data = {
            "renting_server": gpu["id"],
            "type": "spot",
            "currency": "CLORE-Blockchain",
            "image": self.IMAGE,
            "ports": {"22": "tcp"},
            "env": {"NVIDIA_VISIBLE_DEVICES": "all"},
            "ssh_password": self.ssh_password,
            "spotprice": gpu["price"] * 1.15
        }
        
        result = self._api("POST", "/v1/create_order", json=order_data)
        self.order_id = result["order_id"]
        
        print("⏳ Waiting for server...")
        for _ in range(120):
            orders = self._api("GET", "/v1/my_orders")["orders"]
            order = next((o for o in orders if o["order_id"] == self.order_id), None)
            if order and order.get("status") == "running":
                conn = order["connection"]["ssh"]
                parts = conn.split()
                self.ssh_host = parts[1].split("@")[1]
                self.ssh_port = int(parts[-1]) if "-p" in conn else 22
                break
            time.sleep(2)
        else:
            raise Exception("Timeout")
        
        # Connect SSH
        self._ssh = paramiko.SSHClient()
        self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self._ssh.connect(self.ssh_host, port=self.ssh_port,
                          username="root", password=self.ssh_password, timeout=30)
        self._scp = SCPClient(self._ssh.get_transport())
        
        print(f"✅ Server ready: {self.ssh_host}:{self.ssh_port}")
        
        # Setup YOLO
        print("📦 Setting up YOLOv8...")
        self._exec("pip install -q ultralytics", timeout=120)
    
    def _exec(self, cmd: str, timeout: int = 86400) -> str:
        stdin, stdout, stderr = self._ssh.exec_command(cmd, timeout=timeout)
        stdout.channel.recv_exit_status()
        return stdout.read().decode()
    
    def upload_dataset(self, local_path: str) -> str:
        """Upload local dataset."""
        print(f"📤 Uploading dataset from {local_path}...")
        remote_path = "/tmp/dataset"
        self._exec(f"mkdir -p {remote_path}")
        self._scp.put(local_path, remote_path, recursive=True)
        return remote_path
    
    def download_roboflow(self, workspace: str, project: str, version: int, api_key: str) -> str:
        """Download dataset from Roboflow."""
        print(f"📥 Downloading from Roboflow: {workspace}/{project}/v{version}")
        
        script = f'''
from roboflow import Roboflow
rf = Roboflow(api_key="{api_key}")
project = rf.workspace("{workspace}").project("{project}")
dataset = project.version({version}).download("yolov8", location="/tmp/dataset")
print("DONE:/tmp/dataset/data.yaml")
'''
        output = self._exec(f"python3 -c '{script}'", timeout=600)
        
        for line in output.split("\n"):
            if line.startswith("DONE:"):
                return line[5:]
        
        return "/tmp/dataset/data.yaml"
    
    def train(self, data_yaml: str, model: str = "yolov8n.pt", epochs: int = 100,
              batch: int = 16, imgsz: int = 640) -> TrainingResult:
        
        script = f'''
import json
import time
from ultralytics import YOLO

start = time.time()
result = {{"success": False}}

try:
    model = YOLO("{model}")
    results = model.train(
        data="{data_yaml}",
        epochs={epochs},
        batch={batch},
        imgsz={imgsz},
        device=0,
        project="/tmp/runs",
        name="train",
        exist_ok=True,
        verbose=True
    )
    
    metrics = model.val()
    
    result = {{
        "success": True,
        "model_path": "/tmp/runs/train/weights/best.pt",
        "mAP50": float(metrics.box.map50) if hasattr(metrics.box, 'map50') else 0,
        "mAP50_95": float(metrics.box.map) if hasattr(metrics.box, 'map') else 0,
        "precision": float(metrics.box.mp) if hasattr(metrics.box, 'mp') else 0,
        "recall": float(metrics.box.mr) if hasattr(metrics.box, 'mr') else 0,
        "epochs": {epochs},
        "time": time.time() - start
    }}
except Exception as e:
    result = {{"success": False, "error": str(e)}}

print("RESULT:" + json.dumps(result))
'''
        
        self._exec(f"cat > /tmp/train.py << 'EOF'\n{script}\nEOF")
        
        print(f"🎯 Training {model} for {epochs} epochs...")
        start = time.time()
        output = self._exec("python3 /tmp/train.py 2>&1", timeout=86400)
        elapsed = time.time() - start
        
        # Parse result
        result_data = {"success": False}
        for line in output.split("\n"):
            if line.startswith("RESULT:"):
                result_data = json.loads(line[7:])
                break
        
        cost = (elapsed / 3600) * self.hourly_cost
        
        return TrainingResult(
            model_path=result_data.get("model_path", ""),
            mAP50=result_data.get("mAP50", 0),
            mAP50_95=result_data.get("mAP50_95", 0),
            precision=result_data.get("precision", 0),
            recall=result_data.get("recall", 0),
            epochs=result_data.get("epochs", 0),
            time_seconds=elapsed,
            cost_usd=cost,
            success=result_data.get("success", False)
        )
    
    def export(self, model_path: str, format: str = "onnx") -> str:
        """Export model to different format."""
        script = f'''
from ultralytics import YOLO
model = YOLO("{model_path}")
path = model.export(format="{format}")
print(f"EXPORTED:{{path}}")
'''
        output = self._exec(f"python3 -c '{script}'", timeout=600)
        
        for line in output.split("\n"):
            if line.startswith("EXPORTED:"):
                return line[9:]
        return ""
    
    def download_model(self, remote_path: str, local_path: str):
        """Download model file."""
        os.makedirs(os.path.dirname(local_path) or ".", exist_ok=True)
        self._scp.get(remote_path, local_path)
    
    def cleanup(self):
        if self._scp:
            self._scp.close()
        if self._ssh:
            self._ssh.close()
        if self.order_id:
            print("🧹 Releasing server...")
            self._api("POST", "/v1/cancel_order", json={"id": self.order_id})
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.cleanup()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--api-key", required=True, help="Clore.ai API key")
    parser.add_argument("--data", help="Local dataset path or dataset.yaml")
    parser.add_argument("--roboflow", help="Roboflow dataset (WORKSPACE/PROJECT/VERSION)")
    parser.add_argument("--roboflow-key", help="Roboflow API key")
    parser.add_argument("--model", default="yolov8n.pt", help="Base model")
    parser.add_argument("--epochs", type=int, default=100)
    parser.add_argument("--batch", type=int, default=16)
    parser.add_argument("--imgsz", type=int, default=640)
    parser.add_argument("--output", default="./best.pt")
    parser.add_argument("--export", choices=["onnx", "torchscript", "tflite", "coreml"])
    parser.add_argument("--max-price", type=float, default=0.50)
    args = parser.parse_args()
    
    with CloreYOLOTrainer(args.api_key) as trainer:
        trainer.setup(args.max_price)
        
        # Get dataset
        if args.roboflow:
            parts = args.roboflow.split("/")
            workspace, project, version = parts[0], parts[1], int(parts[2])
            data_yaml = trainer.download_roboflow(workspace, project, version, args.roboflow_key)
        elif args.data:
            if os.path.isdir(args.data):
                trainer.upload_dataset(args.data)
                data_yaml = "/tmp/dataset/data.yaml"
            else:
                trainer._scp.put(args.data, "/tmp/data.yaml")
                data_yaml = "/tmp/data.yaml"
        else:
            # Use COCO128 for demo
            data_yaml = "coco128.yaml"
        
        # Train
        result = trainer.train(data_yaml, args.model, args.epochs, args.batch, args.imgsz)
        
        print("\n" + "="*60)
        print("📊 TRAINING COMPLETE")
        print("="*60)
        print(f"   Model: {args.model}")
        print(f"   Epochs: {result.epochs}")
        print(f"   Time: {result.time_seconds:.1f}s ({result.time_seconds/60:.1f} min)")
        print(f"   Cost: ${result.cost_usd:.4f}")
        print(f"\n📈 Metrics:")
        print(f"   mAP50: {result.mAP50:.4f}")
        print(f"   mAP50-95: {result.mAP50_95:.4f}")
        print(f"   Precision: {result.precision:.4f}")
        print(f"   Recall: {result.recall:.4f}")
        
        if result.success and result.model_path:
            # Download model
            trainer.download_model(result.model_path, args.output)
            print(f"\n✅ Model saved: {args.output}")
            
            # Export if requested
            if args.export:
                print(f"\n📦 Exporting to {args.export}...")
                exported = trainer.export(result.model_path, args.export)
                if exported:
                    export_local = args.output.replace(".pt", f".{args.export}")
                    trainer.download_model(exported, export_local)
                    print(f"   Exported: {export_local}")


if __name__ == "__main__":
    main()
```

## Example Training Commands

```bash
# Train YOLOv8 nano on COCO128 (demo)
python train_yolo.py --api-key YOUR_KEY --model yolov8n.pt --epochs 50

# Train YOLOv8 small on custom dataset
python train_yolo.py --api-key YOUR_KEY --data ./my_dataset --model yolov8s.pt --epochs 100

# Train from Roboflow dataset
python train_yolo.py --api-key YOUR_KEY \
    --roboflow myworkspace/myproject/1 \
    --roboflow-key RF_API_KEY \
    --model yolov8m.pt --epochs 150

# Train and export to ONNX
python train_yolo.py --api-key YOUR_KEY --data dataset.yaml \
    --model yolov8l.pt --epochs 200 --export onnx
```

## Model Variants Comparison

| Model   | Size   | mAP50 | Speed (V100) | Clore.ai Cost (100 epochs) |
| ------- | ------ | ----- | ------------ | -------------------------- |
| YOLOv8n | 3.2MB  | 37.3  | 1.2ms        | **\~$0.15**                |
| YOLOv8s | 11.2MB | 44.9  | 2.0ms        | **\~$0.25**                |
| YOLOv8m | 25.9MB | 50.2  | 3.5ms        | **\~$0.40**                |
| YOLOv8l | 43.7MB | 52.9  | 5.5ms        | **\~$0.60**                |
| YOLOv8x | 68.2MB | 53.9  | 8.5ms        | **\~$0.80**                |

## Cost Comparison

| Platform         | RTX 4090 | 100 epochs COCO | Cost      |
| ---------------- | -------- | --------------- | --------- |
| **Clore.ai**     | $0.35/hr | \~45 min        | **$0.26** |
| AWS p3.2xlarge   | $3.06/hr | \~90 min        | $4.59     |
| Google Colab Pro | $10/mo   | \~60 min        | Limited   |
| Lambda Labs      | $1.10/hr | \~45 min        | $0.83     |

## Next Steps

* [Reinforcement Learning](/machine-learning-and-training/reinforcement-learning.md)
* [Training Scheduler](/machine-learning-and-training/training-scheduler.md)
* [Batch Inference](/inference-and-deployment/batch-inference.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.clore.ai/machine-learning-and-training/yolo-training.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
