Comparing YOLO26 Semantic Segmentation with PyTorch and ONNX
Hello, everyone.
Sometimes it is not enough to know that an image contains a car. We also need the pixel-level boundaries of the road and sidewalk.
Today, I ran a YOLO26n semantic segmentation model on an Apple Silicon CPU and compared its PyTorch and ONNX Runtime outputs and speed.
To give the result first, the two class maps agreed on 99.3129% of all pixels. Mean end-to-end processing time was 27.55 ms with ONNX Runtime and 266.00 ms with PyTorch. Most of the difference came from post-processing rather than inference itself.
What we are verifying
Semantic segmentation assigns every pixel in an image to a class. Unlike object detection, which draws boxes, it produces regions that follow the shapes of roads, sky, cars, and other classes.
I used the official Cityscapes-trained yolo26n-sem.pt model to check:
- Whether PyTorch inference and ONNX export work on an Apple M1 Max CPU
- How closely the two class maps agree under the same input conditions
- The time spent on preprocessing, inference, post-processing, and the full call
- How the model divides a generated urban street image into 19 classes
This is the original test image.
Target lab: kiarina/labs/2026/07/14/yolo26-semantic-segmentation
Reproducing the environment
You need mise, uv, and an internet connection for the initial model and shared-image downloads.
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml 2026/07/14/yolo26-semantic-segmentation
mise -C 2026/07/14/yolo26-semantic-segmentation run
On the first run, the task downloads the checkpoint, verifies its SHA-256 hash, and exports it to ONNX opset 18. It creates four images: the PyTorch output, ONNX output, disagreement view, and an annotated comparison.
Model and license
This test uses one pretrained model. PyTorch and ONNX are not two different models here; they are two execution paths for the same checkpoint.
| Model | Role | Input and output |
|---|---|---|
yolo26n-sem.pt |
Assign each pixel to one of 19 Cityscapes classes | 1x3x640x640 image → class logits |
Exported yolo26n-sem.onnx
|
Run the same weights with ONNX Runtime | 1x3x640x640 image → 1x640x640 class IDs |
Class logits are raw scores indicating how likely each class is at each pixel. A class ID is the number of the selected class.
The data flow is:
1774x887 JPEG
-> Fit into 640x640 while preserving aspect ratio (letterbox)
-> Run the same checkpoint through two paths
PyTorch: class logits -> resize -> class IDs
ONNX: 640x640 class IDs -> resize
-> 1774x887 class maps
-> Compare pixel agreement, per-class IoU, and timing
Letterboxing adds padding to fit an image into a target size without stretching it. IoU measures the overlap between two regions; values closer to 100% mean better agreement.
- Checkpoint: Ultralytics assets v8.4.0
- Model size: 3,487,283 bytes
- SHA-256:
f3f293cca764de1f93044030d8d5612de9c5ffbf37c9c8ea1b69418b73038999 - Ultralytics code and trained models: AGPL-3.0 or Ultralytics Enterprise License
The Ultralytics licensing page states that trained models are covered by AGPL-3.0 by default. Proprietary or commercial integration may require an Enterprise License. Check the official terms for your use case at the time of use. The lab does not commit the checkpoint or exported ONNX file; it downloads and generates them at runtime.
Method
The input is one fixed image containing a road, sidewalks, buildings, traffic signals, cars, people, and bicycles.
file: tests/assets/jpg/street_scene_1774x887_287kb.jpg
resolution: 1774x887
SHA-256: d5c865f452599311fbbfd0c132bb4f8b7ade4dd88f0c8ac14ce136490ea53a2e
Both paths use imgsz=640, rect=False, and the CPU. I measured 10 runs after three warm-up runs. Warm-up reduces the effect of setup work that happens only during the first inference. Model initialization, image loading, and image saving are excluded from the benchmark.
The model can output these 19 classes:
road, sidewalk, building, wall, fence, pole, traffic light, traffic sign,
vegetation, terrain, sky, person, rider, car, truck, bus, train,
motorcycle, bicycle
Results
The left side is the original image. The right side overlays the ONNX class map at 50% opacity. The classes that appeared and their pixel shares are shown below the image.
Both PyTorch and ONNX Runtime ran successfully on the Apple M1 Max CPU.
| backend / stage | mean | min | max | std dev |
|---|---|---|---|---|
| PyTorch preprocessing | 0.93 ms | 0.89 ms | 1.04 ms | 0.04 ms |
| PyTorch inference | 28.83 ms | 26.39 ms | 30.31 ms | 1.02 ms |
| PyTorch post-processing | 236.07 ms | 233.19 ms | 239.70 ms | 1.67 ms |
| PyTorch wall time | 266.00 ms | 264.44 ms | 270.02 ms | 1.69 ms |
| ONNX preprocessing | 1.01 ms | 0.91 ms | 1.24 ms | 0.10 ms |
| ONNX inference | 25.67 ms | 24.32 ms | 26.91 ms | 0.88 ms |
| ONNX post-processing | 0.72 ms | 0.60 ms | 1.11 ms | 0.15 ms |
| ONNX wall time | 27.55 ms | 26.06 ms | 28.82 ms | 0.95 ms |
ONNX inference itself took about 0.89 times the PyTorch time, while its full call took about 0.10 times as long. The main difference was post-processing. PyTorch resizes class logits to the original resolution before choosing class IDs. This exported ONNX model directly returns class IDs, making its post-processing much shorter.
I ran the lab again while writing this article. Mean wall time was 290.79 ms for PyTorch and 29.76 ms for ONNX. Timings vary between runs, but the roughly 10x gap and 99.3129% pixel agreement were reproduced.
Pixel and per-class agreement
The two paths produced the same class ID for 99.3129% of all pixels and different IDs for 0.6871%. The detailed data for the 16 predicted classes is below.
| class | PyTorch area | ONNX area | IoU |
|---|---|---|---|
| road | 21.27% | 21.22% | 99.52% |
| sidewalk | 10.38% | 10.39% | 98.48% |
| building | 16.28% | 16.30% | 98.99% |
| wall | 3.05% | 3.05% | 97.16% |
| fence | 4.18% | 4.17% | 98.32% |
| pole | 1.62% | 1.61% | 88.15% |
| traffic light | 0.03% | 0.04% | 88.03% |
| traffic sign | 0.29% | 0.29% | 93.30% |
| vegetation | 26.52% | 26.54% | 98.85% |
| terrain | 0.02% | 0.02% | 82.92% |
| sky | 8.94% | 8.97% | 99.15% |
| person | 0.90% | 0.90% | 96.20% |
| rider | 0.21% | 0.21% | 94.61% |
| car | 5.11% | 5.12% | 98.67% |
| motorcycle | 0.74% | 0.74% | 95.68% |
| bicycle | 0.44% | 0.44% | 94.24% |
Red pixels show where the two paths produced different class IDs. They are concentrated around object boundaries rather than inside large regions.
The first failed comparison
In the first attempt, I did not fix the rect option, and pixel agreement was 98.9648%. PyTorch used minimal padding while the fixed-shape ONNX model used 640x640 padding. The actual inputs therefore differed even with the same imgsz=640 setting.
Setting rect=False for both paths raised agreement to 99.3129%. A backend comparison must align preprocessing as well as the model.
The verification environment was:
machine: MacBook Pro (Apple M1 Max, arm64)
OS: macOS 26.5.2
Python: 3.12.10
Ultralytics: 8.4.95
PyTorch: 2.13.0
ONNX: 1.22.0
ONNX Runtime: 1.27.0
OpenCV: 5.0.0
NumPy: 2.5.1
provider: CPUExecutionProvider
Interpretation
The detailed data is above, but the simpler reading has three main points.
- Large regions agreed closely
Road, building, vegetation, and sky all had IoU above 98%. The overall visual results were also nearly identical. Relative differences were larger for thin poles, traffic lights, terrain, and region boundaries.
- The output format was the main reason ONNX was faster
Inference differed by only about 3 ms, while post-processing differed by about 235 ms. That is because this ONNX export returns a class ID map directly. The result does not mean that ONNX is always 10 times faster; it applies to this export and the full processing paths tested here.
- The street was segmented, but a distant bus was missed
Road, sidewalk, buildings, sky, people, cars, and bicycles were assigned to visually reasonable locations. However, a small bus in the distance was not labeled as bus. Small and distant objects remain difficult.
This test used one generated image without a ground-truth mask. Therefore, 99.3129% is agreement between PyTorch and ONNX, not prediction accuracy. I did not test Cityscapes validation data, real photos, other images, different resolutions, MPS, CoreML, quantization, or memory usage.
Thoughts after verification
Exporting to ONNX preserved almost the same visual result while reducing CPU processing to about 30 ms per image. That looks useful for locally processing road and sidewalk regions in sequence.
The important caveat is that most of the speedup came from different post-processing. Performance should not be judged from the backend name alone; preprocessing and output format matter too. Next, I would like to test temporal stability on real road video and compare CoreML performance.



Top comments (0)