Skip to content

[Examples] Yolo26 Sample for OpenVINO/XNNPACK#18583

Open
daniil-lyakhov wants to merge 4 commits intopytorch:mainfrom
daniil-lyakhov:dl/yolo26_rebase
Open

[Examples] Yolo26 Sample for OpenVINO/XNNPACK#18583
daniil-lyakhov wants to merge 4 commits intopytorch:mainfrom
daniil-lyakhov:dl/yolo26_rebase

Conversation

@daniil-lyakhov
Copy link
Copy Markdown
Contributor

@daniil-lyakhov daniil-lyakhov commented Mar 30, 2026

Summary

Yolo12 is replaced by the Yolo26 model in the examples

Test plan

Local sanity check

CPU Backend Model Precision Average detection time, ms 1920x1080
Intel(R) Core(TM) i9-10980XE CPU @ 3.00GHz OpenVINO Yolo26s FP32 159.48
Intel(R) Core(TM) i9-10980XE CPU @ 3.00GHz OpenVINO Yolo26s INT8 73
Intel(R) Core(TM) i9-10980XE CPU @ 3.00GHz XNNPACK Yolo26s FP32 312.403
Intel(R) Core(TM) i9-10980XE CPU @ 3.00GHz XNNPACK Yolo26s INT8 267.609

test_yolo26.sh is running ok on my local machine, please help me to validate the .ci/scripts/test_yolo26.sh
CC: @mergennachin @lucylq

Copilot AI review requested due to automatic review settings March 30, 2026 16:24
@pytorch-bot
Copy link
Copy Markdown

pytorch-bot bot commented Mar 30, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/18583

Note: Links to docs will display an error until the docs builds have been completed.

❌ 2 New Failures, 1 Cancelled Job, 3 Unrelated Failures

As of commit 5696739 with merge base 4e2ae9c (image):

NEW FAILURES - The following jobs have failed:

CANCELLED JOB - The following job was cancelled. Please retry:

BROKEN TRUNK - The following jobs failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Mar 30, 2026
@github-actions
Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the ExecuTorch YOLO example to use the Ultralytics YOLO26 family (replacing YOLO12), including C++ demo code, export/validation tooling, and CI coverage for OpenVINO/XNNPACK.

Changes:

  • Add/refresh YOLO26 example (C++ runner, export+validate script, docs, CMake target).
  • Update OpenVINO quantizer ignored-scope handling to support subgraph tuples.
  • Update CI script and top-level README to reference and test the YOLO26 example.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
examples/models/yolo26/requirements.txt Adds Python deps for the YOLO26 example export/validation.
examples/models/yolo26/main.cpp New YOLO26 C++ demo runner (video in/out + timing + drawing).
examples/models/yolo26/inference.h Updates YOLO post-processing to end-to-end post-NMS [1, N, 6] output parsing.
examples/models/yolo26/export_and_validate.py Updates export/quantization/validation flow and YOLO26 defaults.
examples/models/yolo26/README.md Renames docs and commands from YOLO12 to YOLO26.
examples/models/yolo26/CMakeLists.txt Renames the demo target to Yolo26DetectionDemo.
examples/models/yolo12/requirements.txt Removes YOLO12 example requirements.
backends/openvino/quantizer/quantizer.py Converts tuple-based subgraphs into nncf.Subgraph objects.
README.md Adds YOLO26 to the list of vision/speech examples.
.ci/scripts/test_yolo26.sh Updates CI to install/run YOLO26 export and C++ runner; fixes OpenVINO checkout command.
Comments suppressed due to low confidence (6)

examples/models/yolo26/inference.h:114

  • pad_x, pad_y, and scale are used here for coordinate mapping, but scale_with_padding() returns early without initializing them when the input already matches img_dims. That can make these computations undefined in the no-resize case. Ensure scale_with_padding() sets *scale = 1.0f and *pad_x = *pad_y = 0 before returning early.
    examples/models/yolo26/inference.h:120
  • class_id is read from the model output and then used to index yolo_config.classes without any bounds checking. If the model emits -1/NaN/out-of-range ids (or if the class list doesn't match), this will crash. Add a range check (0 <= class_id < classes.size()) and skip invalid detections (or clamp/log).
    examples/models/yolo26/export_and_validate.py:84
  • nncf (and nncf.experimental.torch.fx.quantize_pt2e) is imported unconditionally, even when quantize=False. That makes the non-quantized OpenVINO export path depend on NNCF being installed. Consider moving these imports inside the if quantize: block so FP32 export works without NNCF.
    examples/models/yolo26/export_and_validate.py:274
  • The error message has a typo ("implmenented") and the suggested flag formatting is inconsistent ([640,640] vs [640, 640] elsewhere). Fixing the typo and using consistent spacing will make the validation guidance clearer.
    examples/models/yolo26/export_and_validate.py:297
  • validator.device is assigned to CPU twice here. This duplication is easy to miss and can confuse future edits (e.g., if one of the assignments is changed). Remove the redundant assignment and keep a single, intentional device configuration.
    examples/models/yolo26/inference.h:94
  • num_classes is computed but never used, which will trigger an unused-variable warning under stricter builds. Either remove it or use it for validating class_id before indexing into yolo_config.classes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

in the model graph.
"""
if subgraphs:
subgraphs = [nncf.Subgraph(inputs=subgraph[0], outputs=subgraph[1]) for subgraph in subgraphs]
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subgraphs conversion is written as a long one-liner and assumes each element is a 2-tuple/list ((inputs, outputs)). For readability (and to avoid formatting/lint issues), consider expanding this into a small loop or multi-line comprehension, and optionally handle the case where callers pass nncf.Subgraph objects directly.

Suggested change
subgraphs = [nncf.Subgraph(inputs=subgraph[0], outputs=subgraph[1]) for subgraph in subgraphs]
processed_subgraphs: List[nncf.Subgraph] = []
for subgraph in subgraphs:
if isinstance(subgraph, nncf.Subgraph):
processed_subgraphs.append(subgraph)
else:
inputs, outputs = subgraph
processed_subgraphs.append(
nncf.Subgraph(inputs=inputs, outputs=outputs)
)
subgraphs = processed_subgraphs

Copilot uses AI. Check for mistakes.
@digantdesai
Copy link
Copy Markdown
Contributor

@claude Can you review this PR?

@claude
Copy link
Copy Markdown

claude bot commented Mar 30, 2026

Claude finished @digantdesai's task in 2m 0s —— View job


PR Review: [Examples] Yolo26 Sample for OpenVINO/XNNPACK

  • Gather context and read changed files
  • Review CI script changes
  • Review backend changes
  • Review example code (C++, Python, CMake)
  • Review documentation changes
  • Post review summary

Overall this is a clean rename+upgrade from YOLO12 to YOLO26 with a nice simplification on the C++ side (the model now outputs post-NMS results, removing the client-side NMS). A few issues to address:


Bugs / Correctness

1. Uninitialized pad_x, pad_y, scale when input matches target dimensionsinference.h:44-46

scale_with_padding() returns early when col == m_inputWidth && row == m_inputHeight without writing to the pad_x, pad_y, or scale out-parameters. Back in infer_yolo_once() (line 67-68), these are declared as uninitialized local variables:

int pad_x, pad_y;
float scale;

When the early return path is taken, lines 110-113 use these uninitialized values for coordinate mapping, which is undefined behavior. Fix by either initializing them at the declaration site (int pad_x = 0, pad_y = 0; float scale = 1.0f;) or having the early return path set them explicitly.

Fix this →

2. No bounds check on class_id before indexing into yolo_config.classesinference.h:118

result.className = yolo_config.classes[class_id];

If the model emits a class_id outside [0, classes.size()), this is an out-of-bounds access. A bounds check (or at least ET_CHECK_MSG) would prevent a crash from unexpected model output.

3. Unused variable num_classesinference.h:93

const int num_classes = static_cast<int>(yolo_config.classes.size());

This is computed but never used. It will produce a compiler warning with -Wunused-variable. Either remove it or use it for the bounds check suggested above.

4. Duplicate validator.device assignmentexport_and_validate.py:291,296

validator.device = torch.device("cpu")  # line 291
...
validator.device = torch.device("cpu")  # line 296

The device is set twice. One of these should be removed.


CI Script Issues

5. VIDEO_PATH is now required but no default or fallbacktest_yolo26.sh:67-70

The old script didn't require VIDEO_PATH. The new script exits if it's missing, but the export command on line 177 doesn't pass --video_path (it only passes --model_name and --backend). This seems fine for the FP32 (non-quantized) path, but the VIDEO_PATH validation at the top will cause the script to fail unconditionally even for FP32 runs. The CI caller must always pass -video_path even though it's not needed for FP32 export.

Consider making this check conditional on whether quantization is enabled, or moving it to just before it's used.

6. EXPORTED_MODEL_NAME assumes FP32test_yolo26.sh:170

EXPORTED_MODEL_NAME="${MODEL_NAME}_fp32_${MODE}.pte"

This hardcodes fp32 in the filename, but if quantization were enabled via PT2E_QUANTIZE, the exported filename (set in export_and_validate.py:264) would contain int8, causing the runner to fail to find the file. This was inherited from the old script, but worth noting.


Backend Change

7. set_ignored_scope subgraph conversionquantizer.py:195-196

if subgraphs:
    subgraphs = [nncf.Subgraph(inputs=subgraph[0], outputs=subgraph[1]) for subgraph in subgraphs]

This works but has two minor concerns:

  • It reassigns subgraphs which shadows the parameter — the type changes from List[Tuple[List[str], List[str]]] to List[nncf.Subgraph]. This is fine functionally since it's immediately consumed, but makes the code less clear.
  • If someone passes nncf.Subgraph objects directly (since the type hint accepts tuples, not Subgraph), indexing with [0]/[1] would fail. Copilot's suggestion to add an isinstance check is reasonable for robustness but not strictly necessary if callers always pass tuples.
  • The line is long. Consider splitting for readability per the project's style.

Nits / Minor

8. Typoexport_and_validate.py:272: "implmenented" should be "implemented". (Inherited from the old code, but could be fixed while here.)

9. Stale reference in docs/source/success-stories.md:159: Still references yolo12 and the old path examples/models/yolo12. Not in the diff, but should be updated as a follow-up.

10. nncf imported unconditionallyexport_and_validate.py:82-83: nncf and nncf.experimental.torch.fx are imported at the top of lower_to_openvino() regardless of whether quantization is requested. Moving these inside if quantize: would let FP32 OpenVINO export work without NNCF installed.

11. README typoREADME.md:49: "refere" should be "refer" (inherited).


Summary

The main functional concern is the uninitialized variables bug in inference.h (item 1) — that should be fixed before merge. Items 2-3 are low-risk but easy to fix. The rest are minor/cosmetic.

The overall change is well-structured: the YOLO26 end-to-end output format simplifies the C++ inference code substantially by removing client-side NMS, and the CI script improvement (using a pre-installed OpenVINO instead of building from source) is a good cleanup.


@@ -1,2 +0,0 @@
ultralytics==8.3.196
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not want to keep Yolo12?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to remove the Yolo12 model as the Yolo26 has simpler architecture, should run faster and has same/better accuracy results.

Besides, our colleagues from Ultralytics recommend to avoid the yolo12 altogether

https://docs.ultralytics.com/compare/#community-models-a-note-on-yolo12-and-yolo13
image

Copy link
Copy Markdown
Contributor

@digantdesai digantdesai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the yolo26.

  • Please fix issues Claude commented.
  • CI is not clean, see if anything relevant needs fixing.
  • Left a comment about Yolo12.

Copilot AI review requested due to automatic review settings March 31, 2026 10:30
@daniil-lyakhov
Copy link
Copy Markdown
Contributor Author

@digantdesai, Thank you for a quick response!

I fixed the claude code comments (double checked with my local cloude code and applied his comments as well)
Regarding the CI Script Issues comment 5 - I believe the script should always require the video file input

Found actual issue with the precommit - lintrunner, should be fixed

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings April 1, 2026 00:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

.ci/scripts/test_yolo26.sh:85

  • The script sources openvino/setupvars.sh and installs backends/openvino/requirements.txt using paths relative to the current working directory, and uses pip instead of $PYTHON_EXECUTABLE -m pip. This makes the script fragile if invoked from a different cwd or with a non-default Python; prefer resolving paths relative to the repo root/script directory and use the same Python executable for installs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +69
cv::Mat scale_with_padding(
const cv::Mat& source,
int* pad_x,
int* pad_y,
float* scale,
cv::Size img_dims) {
int col = source.cols;
int row = source.rows;
int m_inputWidth = img_dims.width;
int m_inputHeight = img_dims.height;
if (col == m_inputWidth && row == m_inputHeight) {
*pad_x = 0;
*pad_y = 0;
*scale = 1.f;
return source;
}

*scale = std::min(m_inputWidth / static_cast<float>(col), m_inputHeight / static_cast<float>(row));
int resized_w = static_cast<int>(col * *scale);
int resized_h = static_cast<int>(row * *scale);
*pad_x = (m_inputWidth - resized_w) / 2;
*pad_y = (m_inputHeight - resized_h) / 2;

cv::Mat resized;
cv::resize(source, resized, cv::Size(resized_w, resized_h));
cv::Mat result = cv::Mat::zeros(m_inputHeight, m_inputWidth, source.type());
resized.copyTo(result(cv::Rect(*pad_x, *pad_y, resized_w, resized_h)));
resized.release();
return result;
}

std::vector<Detection> infer_yolo_once(
Module& module,
cv::Mat input,
cv::Size img_dims,
const DetectionConfig& yolo_config) {
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scale_with_padding and infer_yolo_once are non-inline function definitions in a header. If this header is included by more than one translation unit, it will cause multiple-definition linker errors. Mark these functions inline (or move implementations to a .cpp file).

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +13
#include <iostream>
#include <vector>

#include <executorch/extension/module/module.h>
#include <executorch/extension/tensor/tensor.h>
#include <executorch/runtime/core/error.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/result.h>
#include <executorch/runtime/platform/runtime.h>
#include <opencv2/opencv.hpp>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header uses standard library facilities/macros that require their own headers (std::min and PRIx32), but it doesn’t include <algorithm> and <cinttypes>/<inttypes.h>. Add the required standard headers explicitly to avoid relying on transitive includes.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants