DEV Community

Nariaki Wada
Nariaki Wada

Posted on

Comparing YOLO26 Semantic Segmentation with PyTorch and ONNX

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.

Urban street image before YOLO26 semantic segmentation

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Urban street image segmented into pixel-level classes with YOLO26

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.

Pixels where PyTorch and ONNX class IDs differ, highlighted in red

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
Enter fullscreen mode Exit fullscreen mode

Interpretation

The detailed data is above, but the simpler reading has three main points.

  1. 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.

  1. 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.

  1. 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)