How We Train a YOLO Model from Scratch in One Lab Session
Last Thursday, a student in our current Computer Vision & Visual AI cohort walked into office hours with a hard drive full of warehouse shelf photos and a product requirements doc that said "detect misplaced items in real time." By the end of that same two-hour session, she had a live endpoint returning bounding boxes. Not a slide deck promise. That's what happens when the environment is already configured and you stop wasting time on CUDA versioning.
This post walks through the exact process we use in AI Labs' GPU Training Lab to train a YOLO model on custom data and ship it somewhere real.
What the GPU Training Lab Actually Gives You
The lab spins up a JupyterLab instance backed by an A100 80GB, with Ultralytics 8.2, CUDA 12.4, and PyTorch 2.3 already installed. No conda environment hell. No driver mismatches. The YOLOv8 GPU training environment is just there, which sounds minor until you've watched someone spend 45 minutes on a torch.cuda.is_available() that returns False. That particular support request showed up three times in a single week during our last cohort. Three times.
We also pre-mount a shared CVAT (Computer Vision Annotation Tool) instance so students can export annotations directly in YOLO format. The directory structure lands exactly where Ultralytics expects it:
dataset/
images/
train/
val/
labels/
train/
val/
data.yaml
That data.yaml is the thing people mess up most often. It needs absolute paths in the lab environment, not relative ones. We've baked a generation script into the starter notebook so nobody has to remember that on their own.
How Does the Training Command Actually Work?
Once your labels are exported and data.yaml is correct, training a YOLOv8 nano model on a 500-image custom dataset looks like this:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model.train(
data="/workspace/dataset/data.yaml",
epochs=100,
imgsz=640,
batch=32,
device=0,
project="/workspace/runs",
name="shelf_detector_v1",
augment=True,
hsv_h=0.015,
hsv_s=0.7,
flipud=0.3,
mosaic=1.0,
)
That runs in about 11 minutes on the A100. You get a best.pt checkpoint, a confusion matrix, and a PR curve, all written to /workspace/runs/shelf_detector_v1/. The built-in augmentation matters a lot when you have fewer than 500 images per class. mosaic, flipud, HSV shifts, then Albumentations on top for geometric distortions. We've seen mAP50 jump from 0.71 to 0.84 just from that combination. Students who skip augmentation and go straight to "I need more data" are almost always wrong.
One thing students consistently underestimate: validation split. Default is 20%. On a 300-image dataset that's 60 val images, which is statistically shaky. We push students to 25% val, or to use the k-fold cross-validation notebooks we keep in the lab's shared templates folder.
When Should You Move to a Larger Model?
Stick with YOLOv8n until your mAP50 on the validation set plateaus below your target. Nano is fast enough that you can run five experiments in the time it takes to train YOLOv8m once. The jump from nano to small (YOLOv8s) typically adds 3 to 5 mAP50 points on datasets with more than 10 classes. Medium earns its weight when objects are small relative to the frame or you need sub-5% false positive rates in dense scenes. Those are the two cases where I'd reach for YOLOv8m without hesitation. Everything else, start small and measure.
For the warehouse use case above, YOLOv8s was the right call. Two classes, decent image resolution, latency budget under 100ms. Nano hit 0.87 mAP50, small hit 0.91. She shipped small.
Deploying to a Live Endpoint in Under 20 Minutes
This is the part most YOLO object detection tutorials skip entirely. Training is the easy half.
We export best.pt to ONNX first:
model = YOLO("/workspace/runs/shelf_detector_v1/weights/best.pt")
model.export(format="onnx", dynamic=True, simplify=True)
Then we wrap inference in a FastAPI app (the template lives in the lab's /templates/deployment/ folder), containerize with Docker, and push to Cloud Run. The whole thing runs on CPU at Cloud Run's minimum instance size for low-traffic endpoints, which keeps costs under $5/month for a demo. Students who need GPU inference swap Cloud Run for a Vertex AI Prediction endpoint with an L4 instance.
The final call looks like this from any HTTP client:
curl -X POST https://shelf-detector-xxxx.run.app/predict \
-F "file=@test_image.jpg" \
| jq '.detections'
Bounding boxes, class names, confidence scores. Done.
Custom YOLO Training Is a Pipeline Problem, Not a Model Problem
Every time a student's custom YOLO training run produces garbage results, the issue is upstream: inconsistent labels, wrong image paths, class imbalance nobody caught, or validation leakage from augmenting before splitting. I've reviewed maybe 40 failed training runs across the last two cohorts. The model architecture was the culprit exactly once.
The GPU Training Lab forces you to confront the pipeline because the model part is already solved. That's the point. If you want to go deeper on the full workflow, including multi-class detection, tracking with ByteTrack, and edge deployment to Jetson, that's what Computer Vision & Visual AI is built around.
The question isn't whether YOLOv8 can detect your objects. It almost certainly can. The question is whether your labels are clean enough to let it.