Skip to content

deepghs/yolov8

Repository files navigation

dghs-yolov8

A thin, opinionated wrapper around Ultralytics that streamlines the training → export → publish pipeline used by the deepghs team. It keeps a single Python entry point for training, takes care of resuming, automatically writes the metadata needed for downstream tooling, and ships CLIs for ONNX export and publishing trained models to HuggingFace (and, optionally, Roboflow).

Heads up: this repository is not published to PyPI. Always install from a local clone using the requirements*.txt files described below.

Table of contents

Features

  • One-line training entry for object detection and instance segmentation with train_object_detection / train_segmentation.
  • Supports the full Ultralytics line: YOLOv8 / v9 / v10 / v11 / v12 and RT-DETR, selectable through plain function arguments or env vars.
  • Auto-resume when weights/last.pt is present in the work directory — re-running the same training script picks up where it left off.
  • Captures model_type.json and problem_type metadata reliably, surviving Ultralytics' early work-directory wipe.
  • python -m yolov8.export packages a training run into a portable archive (best checkpoint, ONNX, training curves, confusion matrix, anonymised TensorBoard logs).
  • python -m yolov8.quantize runs a one-shot INT8 post-training quantization with the empirically validated Tier S recipe (Percentile 99.999 + symmetric + per-channel + reduce_range, N=128 random calibration). The deployable .int8.onnx carries its own re-validated threshold. See Quantizing to INT8.
  • python -m yolov8.publish huggingface uploads the archive to a HuggingFace model repo with one command, including a recommended F1 threshold computed directly from the validator metrics. Optional --with-int8 also publishes the INT8 ONNX + its eval / threshold sidecars.
  • python -m yolov8.list regenerates a comparison table in the HuggingFace repo's README.md, in place, without disturbing the rest of the page.
  • Optional Roboflow publish path kept for legacy users (see the dedicated section below).

Installation

git clone https://github.com/deepghs/yolov8.git
cd yolov8

# Main install: training (yolov8/v9/v10/v11/v12, rtdetr) +
# huggingface publish + onnx export.
pip install -r requirements.txt
pip install -r requirements-onnx.txt

# Optional extra: enable `python -m yolov8.publish roboflow ...`.
# Roboflow is no longer a primary feature here; this set is kept for legacy
# use and pins ultralytics back to 8.0.196 for SDK compatibility, so install
# it only if you actually need the Roboflow path.
pip install -r requirements-roboflow.txt

GPU is required for any meaningful training. ONNX export and publishing run fine on CPU.

Training

The training entry points are plain Python functions. Both accept arbitrary extra keyword arguments and forward them to Ultralytics' model.train(...), so anything the Ultralytics training settings documents is available — imgsz, lr0, optimizer, device, workers, augment, etc.

Datasets follow the standard Ultralytics layout. See the YOLO format reference for the directory structure and data.yaml schema. If you point the wrapper at a directory, it will look for data.yaml (or data.yml) inside it.

Object detection

import os
import re

from yolov8.train import train_object_detection

if __name__ == '__main__':
    # Path to your dataset root. Either a directory containing data.yaml
    # (or data.yml) or the data.yaml file itself works.
    # Dataset format reference:
    #   https://docs.ultralytics.com/datasets/detect/#ultralytics-yolo-format
    dataset_dir = 'dir/to/your/dataset'

    # Pick a model family (YVERSION) and size (LEVEL).
    #
    # LEVEL — model scale, controls capacity vs speed:
    #   - yolov8 / v10 / v11 / v12: n / s / m / l / x
    #   - yolov9                  : t / s / m / l / x
    #   - rtdetr                  : l / x
    #
    # YVERSION — which model family to use:
    #   - 8       — YOLOv8   (https://docs.ultralytics.com/models/yolov8/)
    #   - 9       — YOLOv9   (https://docs.ultralytics.com/models/yolov9/)
    #   - 10      — YOLOv10  (https://docs.ultralytics.com/models/yolov10/)
    #   - 11      — YOLO11   (https://docs.ultralytics.com/models/yolo11/)
    #   - 12      — YOLO12
    #   - 'rtdetr' — RT-DETR (https://docs.ultralytics.com/models/rtdetr/)
    #
    # All of these are available out of the box once you have installed
    # `requirements.txt` (the previous `requirements-raw.txt` split is gone).
    level = os.environ.get('LEVEL', 's')
    yversion = os.environ.get('YVERSION', '8') or '8'
    if re.fullmatch(r'^\d+$', yversion):
        yversion = int(yversion)

    # Build a suffix so different families do not overwrite each other under runs/.
    if isinstance(yversion, int) and yversion != 8:
        suffix = f'_yv{yversion}'
    elif yversion == 'rtdetr':  # RT-DETR results have been disappointing in our setting; not recommended.
        suffix = '_rtdetr'
    else:
        suffix = ''

    # Kick off training. Outputs go to runs/<task_name>/ — see
    # "What ends up in the work directory" below for details. If a previous
    # run left a weights/last.pt behind, training resumes automatically.
    train_object_detection(
        # Work directory. Becomes runs/<basename>/ on disk; the training run's
        # `name` is the basename and `project` is its parent directory.
        f'runs/your_training_task_{level}{suffix}',

        # Either a data.yaml path, a data.yml path, or a directory containing one.
        train_cfg=os.path.join(dataset_dir, 'data.yaml'),

        level=level,           # see LEVEL above
        yversion=yversion,     # see YVERSION above
        max_epochs=100,        # forwarded to Ultralytics as `epochs`
        patience=1000,         # early-stopping patience; high value ≈ disabled

        # Anything else here is forwarded straight to model.train(...).
        # Examples:
        #   batch=16,
        #   imgsz=640,
        #   device=0,
        #   workers=8,
        #   optimizer='AdamW',
        # Full reference:
        #   https://docs.ultralytics.com/modes/train/#train-settings
    )

Instance segmentation

The segmentation entry point mirrors detection. The only differences worth noting: the segmentation pretrained weights have a -seg suffix (yolov8s-seg.pt, yolo11s-seg.pt, etc.), and Ultralytics emits both Box* and Mask* curves in the work directory. The wrapper handles both sides automatically.

import os

from yolov8.train import train_segmentation

if __name__ == '__main__':
    dataset_dir = 'dir/to/your/segmentation/dataset'

    train_segmentation(
        'runs/your_seg_task_s',
        train_cfg=os.path.join(dataset_dir, 'data.yaml'),
        level='s',
        yversion=8,        # also supports 9/10/11/12
        max_epochs=100,
        patience=1000,
        # imgsz=640, batch=16, device=0, ...
    )

RT-DETR has no segmentation variant, so train_segmentation does not accept yversion='rtdetr'.

Choosing a model family / size

A few rules of thumb when picking (yversion, level):

  • Smaller models (n/t/s) train faster, run faster, need less VRAM, and tend to be enough when classes are visually distinct.
  • Larger models (m/l/x) help on cluttered scenes, fine-grained classes, or small objects, at the cost of throughput and memory.
  • Newer families (v10/v11/v12) generally have better accuracy/latency tradeoffs than v8 at the same level. v8 is still a reasonable default if you need the broadest tooling compatibility.
  • For the Roboflow publish path you must stay on YOLOv8; see the legacy Roboflow section.

What ends up in the work directory

After training, runs/your_training_task_xxx/ will contain (Ultralytics' own outputs plus a few files this wrapper writes):

runs/your_training_task_xxx/
├── weights/
│   ├── best.pt                    best checkpoint by validation metric
│   └── last.pt                    most recent checkpoint (resume marker)
├── results.csv                    per-epoch metrics
├── results.png                    metrics plot
├── F1_curve.png  P_curve.png  R_curve.png  PR_curve.png
│                                  detection-task curves (or Box*/Mask*
│                                  variants for segmentation)
├── confusion_matrix.png
├── confusion_matrix_normalized.png
├── labels.jpg                     dataset label distribution
├── labels_correlogram.jpg
├── events.out.tfevents.*          TensorBoard logs
├── threshold.json                 mean + per-class best F1 / threshold,
│                                  computed directly from the validator's
│                                  in-memory curves (no OCR involved)
└── (no model_type.json sidecar; model_type and problem_type are derived
    on demand from the embedded class on weights/best.pt — see
    `yolov8.utils.derive_model_meta`)

The previous 30-second-delayed model_type.json write was removed because Ultralytics wipes the work directory at training start and the same metadata lives unconditionally inside every best.pt / last.pt. Export and publish now derive model_type / problem_type from the checkpoint at upload time.

Resuming a run

If weights/last.pt exists when you call train_object_detection / train_segmentation again with the same work directory, the wrapper sets Ultralytics' resume=True for you. Just re-run the same script — no extra flags required. To start fresh instead, point the function at a new work directory or remove the existing one.

Exporting to ONNX

yolov8.export packages a finished training run into a tidy bundle and emits an ONNX file alongside the PyTorch checkpoint:

python -m yolov8.export -w runs/your_training_task_xxx
# Optional flags:
#   -n / --name           override the output basename (defaults to the workdir name)
#   --opset_version 14    ONNX opset (default 14)

What the export does, beyond just running yolo.export():

  • Anonymises the training-time host paths in train_args (data, project, model) using SHA3, so published checkpoints don't leak local directory layouts.
  • Ships the threshold.json produced at training time (mean and per-class best F1 + threshold, read directly from the validator's in-memory curves — see yolov8.utils.compute_threshold_data). Downstream inference tooling can read this to pick a sensible conf_threshold.
  • Anonymises the hostname in TensorBoard event filenames before bundling.
  • Produces a zip at runs/<task>/export/<task>.zip containing the checkpoint, ONNX, plots, csv, labels, and metadata.

You usually don't need to invoke this manually — python -m yolov8.publish huggingface runs the same export pipeline before uploading. Use it directly when you want a self-contained zip without publishing.

Quantizing to INT8 (Tier S PTQ)

yolov8.quantize produces a deployable INT8 ONNX from a finished training run. The recipe is fixed (no per-call knobs to tune): we ran a multi-week experimental sweep across YOLO families and converged on a single configuration that wins on every dataset + model cell we tested. See plans/YOLO-INT8-PTQ-CALIBRATION-RECIPE.md for the empirical write-up; the punchy version is:

  • Calibrator: ONNX Runtime Percentile with cutoff 99.999 + CalibTensorRangeSymmetric=True. Symmetric activations (QUInt8)
    • per-channel symmetric weights (QInt8).
  • Format: QDQ. reduce_range=True is mandatory on ORT 1.23 CPU EP — turning it off drops mAP retention from ~96 % to ~50 % regardless of every other knob.
  • Calibration data: N=128 images sampled uniformly at random from the training set, seed-locked. No embedding-coreset; "smart" selections (FPS / k-means / SelectQ / easy-examples) measurably underperform random under this calibrator.
  • Head exclusion: a unified tail-60 + extended op-type set that covers v8/v9/v10/v11 in one rule (the v10 NMS-free postproc op types are added; harmless on v8/v11).

Empirically across 7 (version, size) cells × COCO val2017 plus a private 4-class finetuned dataset, the recipe lands at 95.7 – 98.8 % mAP50 retention vs the FP32 ONNX of the same model (yolov9s + COCO leads at 98.8 %). Seed-stable within ±0.15 pp.

python -m yolov8.quantize -w runs/your_training_task_xxx
# Common flags:
#   --data                override the dataset yaml (otherwise read from args.yaml)
#   --train-images        override the training-image directory used as the calib pool
#   --calib-n / --calib-seed   defaults: 128 / 0
#   --imgsz               default: read from args.yaml, falls back to 640
#   --no-eval             skip the post-quantization val pass
#   --force               re-run quantization even if quant/int8/*_int8.onnx exists

Outputs land under <workdir>/quant/:

runs/your_training_task_xxx/quant/
├── int8/<task>_imgsz640_int8.onnx     ← deployable (carries metadata + threshold)
├── onnx/<task>_imgsz640_fp32.onnx     ← FP32 source (kept so re-quantization is cheap)
├── onnx/<task>_imgsz640_pre.onnx
├── calib_lists/random128_seed0.txt
├── eval.json                          ← INT8 mAP50 / mAP50-95 / P / R / speed
├── threshold.json                     ← INT8-specific F1 threshold (recomputed)
└── quant_args.json                    ← exact recipe knobs used

Re-running on the same workdir reuses the existing INT8 artifact — calibration is the slow step (3–10 minutes depending on model size) and is skipped silently. Use --force if you've changed the dataset or want a fresh run.

The deployable .int8.onnx is fully self-describing — its metadata_props carries every standard Ultralytics key plus a dghs.yolov8.quant.* namespace recording the recipe, the eval payload, and an INT8-specific dghs.yolov8.threshold (the optimal F1 confidence threshold for the quantized graph specifically, which generally differs slightly from the FP32 threshold).

Where the recipe came from. External resources surveyed during the calibration / sampling sweep:

The full Tier-S/Tier-A/Tier-B + rule-out + worth-trying tabulation, the per-(version, size) retention numbers, and the 10 figures documenting the experiment chain are in plans/YOLO-INT8-PTQ-CALIBRATION-RECIPE.md. An earlier cross-family fixed-recipe sweep is in plans/QUANTIZATION-EXPERIMENTS.md.

Publishing to HuggingFace

# Your HuggingFace access token (must have write access to the target repo).
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

python -m yolov8.publish huggingface \
  -w runs/your_training_task_xxx_xxx \
  -r your_namespace/your_hf_repository
# Other flags:
#   -n / --name             override the per-model directory inside the repo
#   -R / --revision         target branch (default: main)
#   --opset_version 14      forwarded to ONNX export
#   --with-int8             also publish the Tier S INT8 ONNX (reuses
#                           <workdir>/quant/ if already populated; otherwise
#                           runs the quantize pipeline first)
#   --dry-run               report the manifest without uploading; HF_TOKEN
#                           is not required in this mode

This will, in a single commit:

  1. Run the export pipeline above (with the same anonymisation guarantees).
  2. Create the HuggingFace model repo if it does not exist.
  3. Upload everything under <name>/... inside the repo:
    • <name>/model.pt — anonymised PyTorch checkpoint
    • <name>/model.onnx — exported ONNX
    • <name>/labels.json — class index → label name
    • <name>/threshold.json{f1_score, threshold, per_class}
    • <name>/model_type.json{model_type, problem_type}
    • Plots, results.csv, anonymised TensorBoard event files
    • With --with-int8: <name>/model.int8.onnx (deployable INT8) + <name>/eval_int8.json + <name>/threshold_int8.json (INT8-specific) + <name>/quant_args.json (recipe knobs).

You can publish many models into the same repository — each lives in its own subdirectory, and the next section explains how to keep an index of them in the repo's README.

Aggregating a model table on a HuggingFace repo

Once a few models are uploaded, you can regenerate the comparison table at the top of the HuggingFace repository's README.md:

export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

python -m yolov8.list -r your_namespace/your_hf_repository
# Optional:
#   -R / --revision         target branch (default: main)

The command:

  • Walks every */model.pt in the repo and recomputes FLOPS / parameters, pulls the recorded F1 score / threshold, links to F1 and confusion-matrix plots, and lists the labels (with a labels.json link if there are many).
  • Sorts rows by recency (newest first).
  • Writes the result back into README.md. If the existing README already contains a markdown table whose header has Model / FLOPS / Params / Labels, that one block is replaced in place; everything else in the README (descriptions, badges, prose) is preserved. If no such table is present, the table is written at the top.

This makes it cheap to keep a public model "zoo" page up to date as you publish new variants.

Publishing to Roboflow (legacy / optional)

Roboflow integration is no longer a primary feature of this project and is kept here for legacy users. It supports YOLOv8 models only and requires the optional requirements-roboflow.txt extra, which pins ultralytics==8.0.196 for SDK compatibility — installing it will downgrade ultralytics from the main range. The roboflow import is done lazily inside the subcommand, so you can use the rest of the CLI without ever installing the extra.

# Install the optional extra first (downgrades ultralytics to 8.0.196).
pip install -r requirements-roboflow.txt

# Roboflow API key.
export ROBOFLOW_APIKEY=raxxxxxxxxxxxxxxxxxC

python -m yolov8.publish roboflow \
  -w runs/your_training_task_xxx_xxx \
  -p your_workspace/your_project \
  -v 233

Pass --help to either subcommand to see the full option list.

Using a published model

Once a model is on HuggingFace, you can load and run it directly with dghs-imgutils — no extra setup required.

# pip install dghs-imgutils>=0.6.0
# export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
import matplotlib.pyplot as plt

from imgutils.detect import detection_visualize
from imgutils.generic import yolo_predict

detection = yolo_predict(
    image='your/image.jpg',

    # Where to fetch the model from.
    repo_id='your_namespace/your_hf_repository',
    model_name='your_training_task_xxx_xxx',

    # Reasonable defaults; if the repo has a threshold.json for this model,
    # consider using its recommended threshold instead.
    conf_threshold=0.25,
    iou_threshold=0.7,
)

print(f'Detections:\n{detection!r}')

plt.imshow(detection_visualize(
    image='your/image.jpg',
    detection=detection,
))

Or launch a one-line Gradio demo for a whole repo, with a model picker:

# pip install dghs-imgutils[demo]>=0.6.0
# export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
from imgutils.generic import YOLOModel

YOLOModel(repo_id='your_namespace/your_hf_repository').launch_demo(
    default_model_name='your_training_task_xxx_xxx',

    default_conf_threshold=0.25,
    default_iou_threshold=0.7,

    # Standard Gradio server kwargs.
    server_name=None,
    server_port=7860,
)

Repository layout

yolov8/                   the actual Python package
├── train/
│   ├── object_detection.py    train_object_detection(...)
│   └── segmentation.py        train_segmentation(...)
├── export.py                  workdir → bundle (with anonymisation; --with-int8 packs INT8)
├── onnx/                      YOLO/RTDETR → ONNX; predict-only and dual-head (+ embedding)
├── quantize.py                workdir → INT8 ONNX (Tier S PTQ); CLI + library
├── publish.py                 `python -m yolov8.publish {huggingface,roboflow}`
├── list.py                    `python -m yolov8.list` — refresh HF README table
├── config/meta.py             single source of truth for version/author
└── utils/                     small utilities (CLI helpers, threshold extractor, …)

plans/                         design docs and experimental write-ups
├── QUANTIZATION-EXPERIMENTS.md           cross-family fixed-recipe sweep
├── YOLO-INT8-PTQ-CALIBRATION-RECIPE.md   Tier S calibration / sampling study
└── YOLO-INT8-PTQ-CALIBRATION-RECIPE.assets/  10 figures + make_plots.py

requirements.txt               main runtime deps (ultralytics<=8.3.105)
requirements-onnx.txt          ONNX export + quantization deps (always recommended)
requirements-roboflow.txt      optional, for the legacy Roboflow publish path
requirements-doc.txt           docs build deps
requirements-test.txt          test deps

For an architectural deep dive, hidden constraints, and AI-agent operating rules, see CLAUDE.md (also exposed as AGENTS.md).

Acknowledgements

License

Apache License 2.0 — see LICENSE.

About

YOLOv8 wrapper for quickly train model

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages