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*.txtfiles described below.
- Features
- Installation
- Training
- Exporting to ONNX
- Quantizing to INT8 (Tier S PTQ)
- Publishing to HuggingFace
- Aggregating a model table on a HuggingFace repo
- Publishing to Roboflow (legacy / optional)
- Using a published model
- Repository layout
- Acknowledgements
- License
- 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.ptis present in the work directory — re-running the same training script picks up where it left off. - Captures
model_type.jsonandproblem_typemetadata reliably, surviving Ultralytics' early work-directory wipe. python -m yolov8.exportpackages a training run into a portable archive (best checkpoint, ONNX, training curves, confusion matrix, anonymised TensorBoard logs).python -m yolov8.quantizeruns 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.onnxcarries its own re-validated threshold. See Quantizing to INT8.python -m yolov8.publish huggingfaceuploads the archive to a HuggingFace model repo with one command, including a recommended F1 threshold computed directly from the validator metrics. Optional--with-int8also publishes the INT8 ONNX + its eval / threshold sidecars.python -m yolov8.listregenerates a comparison table in the HuggingFace repo'sREADME.md, in place, without disturbing the rest of the page.- Optional Roboflow publish path kept for legacy users (see the dedicated section below).
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.txtGPU is required for any meaningful training. ONNX export and publishing run fine on CPU.
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.
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
)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_segmentationdoes not acceptyversion='rtdetr'.
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.
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.
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.
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.jsonproduced at training time (mean and per-class best F1 + threshold, read directly from the validator's in-memory curves — seeyolov8.utils.compute_threshold_data). Downstream inference tooling can read this to pick a sensibleconf_threshold. - Anonymises the hostname in TensorBoard event filenames before bundling.
- Produces a zip at
runs/<task>/export/<task>.zipcontaining 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.
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
Percentilewith cutoff 99.999 +CalibTensorRangeSymmetric=True. Symmetric activations (QUInt8)- per-channel symmetric weights (
QInt8).
- per-channel symmetric weights (
- Format:
QDQ.reduce_range=Trueis 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=128images 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 existsOutputs 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:
- NVIDIA Integer Quantization Whitepaper (arXiv 2004.09602)
- ONNX Runtime quantization docs
- Ultralytics community: PTQ methods support for YOLO
- SqueezeBits OwLite — How to quantize YOLO models
- Q-YOLO / MPQ-YOLO
- SelectQ: Calibration Data Selection for PTQ (MIR 2025)
- CL-Calib: Enhancing PTQ Calibration via Contrastive Learning (CVPR 2024)
- Dataset Quantization (ICCV 2023)
- NVIDIA TensorRT issue #1114 (YOLOv5 SiLU collapse)
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.
# 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 modeThis will, in a single commit:
- Run the export pipeline above (with the same anonymisation guarantees).
- Create the HuggingFace model repo if it does not exist.
- 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.
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.ptin the repo and recomputes FLOPS / parameters, pulls the recorded F1 score / threshold, links to F1 and confusion-matrix plots, and lists the labels (with alabels.jsonlink 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 hasModel / 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.
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.txtextra, which pinsultralytics==8.0.196for SDK compatibility — installing it will downgradeultralyticsfrom the main range. Theroboflowimport 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 233Pass --help to either subcommand to see the full option list.
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,
)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).
- Built on top of Ultralytics.
- Designed to slot into the deepghs workflow alongside dghs-imgutils.
Apache License 2.0 — see LICENSE.