Instructions to use a-ml/yolo26-face with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- ultralytics
How to use a-ml/yolo26-face with ultralytics:
# Couldn't find a valid YOLO version tag. # Replace XX with the correct version. from ultralytics import YOLOvXX model = YOLOvXX.from_pretrained("a-ml/yolo26-face") source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
YOLO26 Face Parsing β 19-class, realtime, Core ML
Two YOLO26 semantic segmentation models fine-tuned on CelebAMask-HQ for 19-class face parsing, plus Core ML exports that run in ~1.1 ms on the Apple Neural Engine.
| params | val mIoU | test mIoU | pixel acc | ANE latency @512 | Core ML size | |
|---|---|---|---|---|---|---|
yolo26n-sem (nano) |
1.63 M | 0.747 | 0.746 | 0.940 | 1.14 ms | 3.2 MB (fp16) |
yolo26l-sem (large) |
17.87 M | 0.776 | 0.773 | 0.948 | 4.22 ms | 17.0 MB (int8) |
Test = the 2 824-image held-out split, never seen during training or model selection. Test β val for both models, so neither overfits. The nano reaches 96.3 % of the large model's mIoU with 11Γ fewer parameters β the gap is concentrated almost entirely in the rarest classes.
Latency measured on an Apple M5 Max ANE with computeUnits = .cpuAndNeuralEngine.
Expect an iPhone to be slower; both still leave large headroom against a 33 ms/30 fps budget.
Demo
Live front-camera overlay on iPhone, nano model, ~30 fps.
Contents
checkpoints/ yolo26{n,l}-sem-celebamaskhq.pt Ultralytics checkpoints (resume/fine-tune/val)
coreml/ yolo26n-sem-face-fp16.mlpackage the shipping realtime model
yolo26l-sem-face-int8.mlpackage the accuracy variant
scripts/ the full pipeline, dataset prep -> training -> export -> evaluation
assets/ per-epoch metrics + qualitative contact sheets
demo.mp4 iPhone screen recording
Classes
0 background Β· 1 skin Β· 2 nose Β· 3 eyeglasses Β· 4 left_eye Β· 5 right_eye Β· 6 left_eyebrow Β· 7 right_eyebrow Β· 8 left_ear Β· 9 right_ear Β· 10 mouth Β· 11 upper_lip Β· 12 lower_lip Β· 13 hair Β· 14 hat Β· 15 earring Β· 16 necklace Β· 17 neck Β· 18 cloth
Index order matches the canonical CelebAMask-HQ g_mask.py ordering, so later classes
overwrite earlier ones where the source masks overlap.
Results
Per-class mIoU on the CelebAMask-HQ validation split:
| class | nano | large |
|---|---|---|
| mean (all) | 0.747 | 0.776 |
| background | 0.920 | 0.932 |
| skin | 0.914 | 0.925 |
| hair | 0.901 | 0.916 |
| nose | 0.856 | 0.870 |
| mouth | 0.842 | 0.858 |
| neck | 0.816 | 0.832 |
| eyeglasses | 0.811 | 0.840 |
| lower_lip | 0.791 | 0.812 |
| upper_lip | 0.785 | 0.810 |
| right_eye | 0.777 | 0.800 |
| left_eye | 0.774 | 0.797 |
| cloth | 0.759 | 0.788 |
| hat | 0.756 | 0.782 |
| right_ear | 0.743 | 0.774 |
| left_ear | 0.742 | 0.776 |
| left_eyebrow | 0.704 | 0.731 |
| right_eyebrow | 0.693 | 0.719 |
| earring | 0.467 | 0.535 |
| necklace | 0.131 | 0.256 |
On the classes that dominate a face overlay β skin, hair, background β the two models are within 0.015 of each other. Choose the nano unless you specifically need jewellery.
Cross-dataset generalisation
Neither model saw these images. No ground truth, so this reports mean pixel confidence
(qualitative sheets in assets/):
| dataset | nano | large |
|---|---|---|
| Celebrity Faces | 0.959 | 0.965 |
| Human Faces (real photos) | 0.946 | 0.957 |
| Human Faces (AI-generated) | 0.975 | 0.976 |
| img_align_celeba | 0.944 | 0.958 |
Confidence is consistently highest on AI-generated faces (unusually prototypical) and
lowest on the low-resolution img_align_celeba crops.
Fine-tuning methodology
Initialised from the official Cityscapes-pretrained yolo26{n,l}-sem.pt and fine-tuned
for 12 epochs at 512 px on the canonical CelebA partition (24 183 train / 2 993 val /
2 824 test), batch 32, AdamW (optimizer=auto, lr β 4.3e-4), on a single Apple M5 Max
via MPS. Large: 7.77 h. Nano: 5.55 h.
python scripts/prepare_celebamaskhq_semantic.py --mask-size 512 --img-mode resize
python scripts/train.py --model yolo26l-sem.pt --epochs 12 --imgsz 512 --batch 32 \
--cls-pw 1.0 --loss-size 256 --workers 12 --mosaic 0.5 --no-deterministic --amp
Four choices did most of the work. They are documented because three of them are silent failures β the defaults produce a plausible-looking model that is quietly wrong.
1. cls_pw=1.0 β the single biggest quality win. Ultralytics computes ENet
inverse-log class weights as (1/ln(1.02+p))**cls_pw but defaults the exponent to
0.0, which makes every weight exactly 1.0; set_class_weights then returns early,
so class weighting is silently disabled. On a dataset this imbalanced (necklace is
0.02 % of pixels, hair is 32 %) that is fatal for rare classes. Setting cls_pw=1.0
(the maximum an internal assert allows) gives rare classes ~14Γ the weight of hair/skin:
cls_pw=0.0 (default) |
cls_pw=1.0 |
|
|---|---|---|
| earring | 0.000 | 0.535 |
| necklace | 0.000 | 0.256 |
| hat | 0.187 | 0.782 |
| eyeglasses | 0.352 | 0.840 |
| ears | 0.10 β 0.17 | ~0.775 |
| mean mIoU | 0.438 | 0.776 |
(Left column is an early short run, so it is indicative rather than a controlled ablation β but classes scoring exactly zero are attributable to the missing weighting.)
2. fliplr=0.0 β mandatory for this label set. Ultralytics' RandomFlip mirrors
geometry without swapping left/right class labels, so a horizontally flipped
left_eye is still labelled left_eye. Nine of the 19 classes are laterally paired
(eyes, brows, ears). Any fliplr > 0 teaches the model to confuse them. Rotation
(degrees=10), translation, scale and mosaic are used instead.
3. --loss-size 256 β ~5Γ faster training for β0.002 mIoU. The stock semantic loss
upsamples the stride-8 logits to full mask resolution before CE/Dice β a 159 M-element
tensor at 512 px / batch 32 β then softmaxes it and boolean-index-gathers it (a
data-dependent shape that forces a device sync), twice counting the auxiliary head. The
loss ends up costing more than the entire forward+backward pass. scripts/loss_patch.py
evaluates it at a fixed lower resolution instead. A/B at 320 px:
| stock | --loss-size 256 |
|
|---|---|---|
| large @512 | 14.2 s/iter | 2.7 s/iter |
| mean mIoU | 0.644 | 0.642 |
| thin-class (eyes/brows/lips) mean Ξ | β | β0.0016 |
Profiling first mattered: the dataloader was not the bottleneck (it sustains
1900β4000 img/s). See scripts/isolate_bottleneck.py.
4. Apple-Silicon throughput. deterministic=True (a default) more than doubles loss
cost on MPS by forcing slow scatter kernels. AMP helps on MPS (8.1 vs 12.6 s/iter).
Batch 64 is worse per-image than batch 32. Ultralytics also hard-forces workers=0 on
MPS (trainer.py:162, a CPU-era assumption); scripts/fast_trainer.py restores them and
adds validation throttling, since the 2 993-image val split otherwise costs ~18 min/epoch.
Core ML export
yolo export format=coreml and a naive manual conversion both fail with
coremltools 9 + numpy β₯ 2:
TypeError: only 0-dimensional arrays can be converted to Python scalars
coremltools' _cast op handler does mb.const(val=int(x.val)), and numpy 2 forbids
int() on a size-1, >0-d array. YOLO26's attention block emits exactly that cast.
scripts/coreml_patch.py re-registers _cast with .item() coercion β import it before
ct.convert. scripts/export_coreml.py then traces the raw module (fp16 logits, no
baked argmax) and compares quantization variants.
Quantize-then-convert vs convert-then-quantize, measured on the large model:
| argmax agreement | mIoU vs PyTorch | latency | size | |
|---|---|---|---|---|
| fp16 (convert only) | 99.854 % | 0.990 | 5.39 ms | 33.5 MB |
| convert β int8 | 99.727 % | 0.974 | 4.33 ms | 17.0 MB |
| convert β 6-bit palettize | 99.089 % | 0.924 | 4.06 ms | 12.8 MB |
| int8 β convert | 99.630 % | 0.975 | 4.49 ms | 17.2 MB |
Convert-then-quantize wins β better fidelity, smaller and faster than the reverse. Judge palettization by mIoU rather than argmax agreement: 99.1 % agreement hides a drop to 0.924 mIoU, because the disagreements land on small classes.
Reproduce with scripts/compare_quant_order.py.
Using the Core ML models
- input
imageβ 512Γ512 RGB (CVPixelBuffer; Core ML handles BGRAβRGB) - output
logitsβMLMultiArrayfloat16, (1, 19, 64, 64), NCHW
The output is a stride-8 logit grid, not an argmax map β bilinearly upsample and argmax over the 19 channels on the GPU. Doing it in a fragment shader keeps the whole network on the ANE and costs nothing measurable:
half best = logitsTex.sample(bilinear, modelUV, 0).r;
ushort cls = 0;
for (ushort c = 1; c < 19; c++) {
half v = logitsTex.sample(bilinear, modelUV, c).r;
if (v > best) { best = v; cls = c; }
}
Set configuration.computeUnits = .cpuAndNeuralEngine. Avoid .all: it aborts in
MPSGraph (MLIR pass manager failed) for these shapes on some machines, and keeping the
model off the GPU leaves it free for rendering.
β οΈ Feed the model an upright face. It was trained exclusively on upright, roughly
centred faces, so a rotated input degrades output badly enough to look like a broken
model. AVCaptureConnection.videoRotationAngle is silently ignored on some
device/format combinations β verify the buffer dimensions rather than trusting it.
Letterbox the whole frame, don't centre-crop it. The input is square while camera
frames are not. Cropping an inscribed square (min(w, h)) is tempting because it keeps
the subject at full resolution, but it produces no logits at all outside that square β
a face near the top or bottom of a portrait frame simply gets no mask. Scale the whole
frame to fit and pad the short axis (max(w, h)) instead. A 9:16 frame then fills ~56 %
of the input width, which costs some fine detail but covers every visible pixel.
Using the PyTorch checkpoints
from ultralytics import YOLO
model = YOLO("checkpoints/yolo26n-sem-celebamaskhq.pt")
result = model("face.jpg")[0]
class_map = result.semantic_mask.data # HxW int class indices
Validate against your own copy of the dataset (edit path in scripts/data.yaml):
yolo semantic val model=checkpoints/yolo26l-sem-celebamaskhq.pt data=scripts/data.yaml \
imgsz=512 split=test
Limitations
necklaceis weak (0.131 nano / 0.256 large) andearringis moderate. Only 143 validation images contain a necklace, totalling ~163 k pixels. This is a dataset limit, not a training one.- Upright, roughly centred faces only β see the warning above.
- CelebAMask-HQ demographic bias carries over; the training distribution is celebrity photography and is not representative. Validate on your own population before relying on it, and do not use it for identification, surveillance, or any consequential decision.
- The 64Γ64 logit grid is coarse for very thin structures; boundaries are smoothed by the bilinear upsample.
- Latency figures are from an M5 Max ANE, not a phone.
Licensing β read before use
- Ultralytics is AGPL-3.0. These weights are derived from Ultralytics code and pretrained checkpoints, so the AGPL's copyleft terms apply. Shipping them inside a closed-source application may require a commercial licence from Ultralytics.
- CelebAMask-HQ is non-commercial: research and educational use only. The dataset authors do not own the underlying image copyrights.
Both apply to anything you build from these artifacts. This repository is published for research and educational purposes.
Citation
@article{CelebAMask-HQ,
title = {MaskGAN: Towards Diverse and Interactive Facial Image Manipulation},
author = {Lee, Cheng-Han and Liu, Ziwei and Wu, Lingyun and Luo, Ping},
journal = {Technical Report},
year = {2019}
}
- Downloads last month
- 97