Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
aa3d06b
feat(edge): add auditable cross-platform issue51 validation
lsl-arch Sep 1, 2026
b215d1b
fix(edge): validate NCNN graph endpoints before decode
lsl-arch Sep 1, 2026
a9053c9
docs(edge): document NCNN endpoint validation
lsl-arch Sep 1, 2026
4495481
fix(edge): make NCNN metadata model-specific
lsl-arch Sep 1, 2026
0dcde17
docs(edge): clarify NCNN sidecar compatibility
lsl-arch Sep 1, 2026
98e0134
merge: sync latest YOLO-Master main
lsl-arch Sep 1, 2026
210d567
docs(edge): align Chinese NCNN validation notes
lsl-arch Sep 1, 2026
9c61714
docs(edge): distinguish upstream release metrics
lsl-arch Sep 1, 2026
0d76d8e
docs(edge): clarify validation evidence scope
lsl-arch Sep 1, 2026
4526372
docs(edge): correct README references
lsl-arch Sep 1, 2026
418c5be
style(edge): remove README trailing whitespace
lsl-arch Sep 1, 2026
58bdf40
docs(edge): label upstream references and fix paths
lsl-arch Sep 1, 2026
dbcce6f
docs(edge): tighten evidence wording and examples
lsl-arch Sep 1, 2026
8e81cf6
docs(edge): remove unverified release metrics
lsl-arch Sep 1, 2026
6805a21
fix(ci): keep multitask cache and sampler compatible
lsl-arch Sep 2, 2026
9b251cd
fix(ci): resolve multitask configs and legacy runtime compatibility
lsl-arch Sep 2, 2026
ca0075a
fix(compat): avoid tuple reductions on torch 1.8
lsl-arch Sep 2, 2026
b1eef17
fix(multitask): preserve dense anchor alignment during validation
lsl-arch Sep 2, 2026
6b5ddf2
fix(ddp): force portable gloo transport in Windows gates
lsl-arch Sep 2, 2026
c89e124
fix(ci): fall back to public PyPI for optional export indexes
lsl-arch Sep 2, 2026
27d1623
test(ddp): allow slower cross-platform runners
lsl-arch Sep 2, 2026
0c7caa0
fix(ddp): preserve portable transport defaults across runners
lsl-arch Sep 2, 2026
2d7f593
fix(ddp): cover Windows rendezvous and macOS xdist
lsl-arch Sep 2, 2026
c978d1b
fix(ddp): patch libuv in Windows smoke workers
lsl-arch Sep 2, 2026
93be305
fix(ddp): patch env rendezvous TCPStore on Windows
lsl-arch Sep 2, 2026
228419e
test(ddp): use non-degenerate routed gradient fixture
lsl-arch Sep 2, 2026
44de0bc
docs(edge): present auditable issue 51 evidence
lsl-arch Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 42 additions & 19 deletions .github/scripts/create-export-env.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ def isolated_env_ids():
)


def _public_pypi_command(command):
"""Remove the optional NVIDIA index arguments from an install command."""
skip_next = False
filtered = []
for token in command:
if skip_next:
skip_next = False
continue
if token in {"--extra-index-url", "--index-strategy"}:
skip_next = True
continue
if token in {"https://pypi.ngc.nvidia.com", "unsafe-best-match"}:
continue
filtered.append(token)
return filtered


def build_env(env_id, root):
"""Build one export environment and run its smoke export commands."""
recipe = EXPORT_ENVS[env_id]
Expand All @@ -35,25 +52,31 @@ def build_env(env_id, root):
indexes = [token for flag, url in recipe["indexes"] for token in (flag, url)]
index_strategy = ["--index-strategy", "unsafe-best-match"] if indexes else []
torch = [f"torch{recipe['torch']}"] if recipe["torch"] else []
subprocess.run(
[
"uv",
"pip",
"install",
"--python",
str(python),
"-e",
package,
"pytest",
*torch,
*recipe["requirements"],
*indexes,
*index_strategy,
"--torch-backend",
"cpu",
],
check=True,
)
install_command = [
"uv",
"pip",
"install",
"--python",
str(python),
"-e",
package,
"pytest",
*torch,
*recipe["requirements"],
*indexes,
*index_strategy,
"--torch-backend",
"cpu",
]
try:
subprocess.run(install_command, check=True)
except subprocess.CalledProcessError:
# NVIDIA's index is supplemental for TensorFlow/GraphSurgeon. If its DNS
# endpoint is unavailable, retry against public PyPI so CI remains usable.
if "https://pypi.ngc.nvidia.com" not in indexes:
raise
print("Supplemental NVIDIA PyPI index unavailable; retrying with public PyPI", flush=True)
subprocess.run(_public_pypi_command(install_command), check=True)

if recipe["env"]:
site_packages = next(venv.glob("lib/python*/site-packages"))
Expand Down
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
# Local dependency/bootstrap trees used by offline QA; never publish them.
/.qa-deps/
/.qa-cmake-*/
/.qa-tmp*/
/.qa-pytest-*/
**/.qa-cmake-*/
**/.qa-tmp*/
**/.qa-pytest-*/
/.qa-audit-note.txt
*.qa-tmp/
/work/pytest-*/
/.qa-run-*/
# Ad-hoc local verification directories
/.qa-env-*/
/qa_tmp_*/
mlruns/

# Translations
Expand Down
552 changes: 235 additions & 317 deletions examples/YOLO-Master-Cross-Platform-Edge-Deployment/README.md

Large diffs are not rendered by default.

671 changes: 380 additions & 291 deletions examples/YOLO-Master-Cross-Platform-Edge-Deployment/TECHNICAL_REPORT.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

234 changes: 183 additions & 51 deletions examples/YOLO-Master-Cross-Platform-Edge-Deployment/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,24 @@ endif()
set(ONNXRUNTIME_ROOT "${CMAKE_SOURCE_DIR}/../third_party/onnxruntime" CACHE PATH "ONNXRuntime root")
set(NCNN_ROOT "${CMAKE_SOURCE_DIR}/../third_party/ncnn" CACHE PATH "ncnn root")
set(MNN_ROOT "${CMAKE_SOURCE_DIR}/../third_party/mnn-src" CACHE PATH "MNN root (include/ + lib/ or build/)")
# Native TensorRT is normally supplied by JetPack under /usr, but keeping the
# roots explicit makes the same CMake invocation usable with an unpacked SDK
# or an aarch64 sysroot. Empty values preserve the platform defaults below.
set(TENSORRT_ROOT "" CACHE PATH "TensorRT SDK root (include/ and lib/)")
set(CUDA_ROOT "" CACHE PATH "CUDA SDK root (include/ and lib64/)")

option(USE_ORT "Build ONNXRuntime backend" ON)
option(USE_NCNN "Build ncnn backend" ON)
option(USE_MNN "Build MNN backend" ON)
option(USE_TRT "Build TensorRT backend (GPU .engine inference; Jetson/CUDA)" OFF)
option(USE_TRT "Build native TensorRT 10 backend (GPU .engine; Jetson/CUDA)" OFF)
# A dependency-light CLI build is useful for checking the argument/CSV
# contract, so it remains allowed by default. Acceptance and release builds
# should set the corresponding REQUIRE_* option(s) to make missing SDKs a
# configuration error instead of silently producing a partial binary.
option(ALLOW_NO_BACKENDS "Allow a diagnostic build without inference SDKs" ON)
option(REQUIRE_ORT "Fail configuration when ONNX Runtime is unavailable" OFF)
option(REQUIRE_NCNN "Fail configuration when NCNN is unavailable" OFF)
option(REQUIRE_MNN "Fail configuration when MNN is unavailable" OFF)

set(SRC src/common.cpp src/slicing.cpp src/annotate_writers.cpp src/annotate_export.cpp
src/main.cpp src/stb_impl.cpp)
Expand All @@ -38,96 +51,215 @@ set(DEFS "")
if(NOT PORTABLE)
list(APPEND DEFS HAVE_VIDEOIO) # cv::VideoCapture (--source video) needs opencv_videoio
endif()
if(WIN32)
# Keep Win32's min/max macros from colliding with std::min/std::max in the
# shared C++ runtime and third-party headers.
list(APPEND DEFS NOMINMAX WIN32_LEAN_AND_MEAN)
endif()
set(RPATH "")
set(RUNTIME_DLLS "") # Windows: DLLs to copy next to the .exe
set(ORT_ENABLED OFF)
set(NCNN_ENABLED OFF)
set(MNN_ENABLED OFF)

if(USE_ORT AND EXISTS "${ONNXRUNTIME_ROOT}/include/onnxruntime_cxx_api.h")
list(APPEND SRC src/ort_backend.cpp)
list(APPEND INCS ${ONNXRUNTIME_ROOT}/include)
set(ORT_LINK_LIB "")
if(WIN32)
list(APPEND LIBS ${ONNXRUNTIME_ROOT}/lib/onnxruntime.lib)
file(GLOB ORT_DLLS "${ONNXRUNTIME_ROOT}/lib/*.dll" "${ONNXRUNTIME_ROOT}/bin/*.dll")
list(APPEND RUNTIME_DLLS ${ORT_DLLS})
if(EXISTS "${ONNXRUNTIME_ROOT}/lib/onnxruntime.lib")
set(ORT_LINK_LIB "${ONNXRUNTIME_ROOT}/lib/onnxruntime.lib")
file(GLOB ORT_DLLS "${ONNXRUNTIME_ROOT}/lib/*.dll" "${ONNXRUNTIME_ROOT}/bin/*.dll")
list(APPEND RUNTIME_DLLS ${ORT_DLLS})
endif()
elseif(APPLE)
# macOS: ORT ships a .dylib with the CoreML EP built in; link the frameworks it pulls
# macOS: ORT ships a .dylib with the CoreML EP built in; link the frameworks it pulls.
find_library(ORT_DYLIB onnxruntime PATHS ${ONNXRUNTIME_ROOT}/lib NO_DEFAULT_PATH)
list(APPEND LIBS ${ORT_DYLIB} "-framework Foundation" "-framework CoreML")
list(APPEND RPATH ${ONNXRUNTIME_ROOT}/lib)
set(ORT_LINK_LIB "${ORT_DYLIB}")
else()
list(APPEND LIBS ${ONNXRUNTIME_ROOT}/lib/libonnxruntime.so)
# Release archives commonly ship only a versioned SONAME
# (libonnxruntime.so.1.18.1), without an unversioned development symlink.
file(GLOB ORT_SOS "${ONNXRUNTIME_ROOT}/lib/libonnxruntime.so*")
list(SORT ORT_SOS)
list(LENGTH ORT_SOS ORT_SO_COUNT)
if(ORT_SO_COUNT GREATER 0)
list(GET ORT_SOS 0 ORT_LINK_LIB)
endif()
endif()
if(ORT_LINK_LIB)
list(APPEND SRC src/ort_backend.cpp)
list(APPEND INCS ${ONNXRUNTIME_ROOT}/include)
list(APPEND LIBS ${ORT_LINK_LIB})
if(APPLE)
list(APPEND LIBS "-framework Foundation" "-framework CoreML")
endif()
list(APPEND RPATH ${ONNXRUNTIME_ROOT}/lib)
list(APPEND DEFS USE_ORT)
set(ORT_ENABLED ON)
message(STATUS "ONNXRuntime backend: ON (${ONNXRUNTIME_ROOT})")
elseif(REQUIRE_ORT)
message(FATAL_ERROR "REQUIRE_ORT=ON but ONNXRuntime library was not found under ${ONNXRUNTIME_ROOT}")
else()
message(WARNING "ONNXRuntime backend: OFF (headers found but library is missing under ${ONNXRUNTIME_ROOT})")
endif()
list(APPEND DEFS USE_ORT)
message(STATUS "ONNXRuntime backend: ON (${ONNXRUNTIME_ROOT})")
else()
if(REQUIRE_ORT)
message(FATAL_ERROR "REQUIRE_ORT=ON but ONNXRuntime was not found at ${ONNXRUNTIME_ROOT}")
endif()
message(WARNING "ONNXRuntime backend: OFF (not found at ${ONNXRUNTIME_ROOT})")
endif()

if(USE_NCNN AND EXISTS "${NCNN_ROOT}/include/ncnn/net.h")
list(APPEND SRC src/ncnn_backend.cpp)
list(APPEND INCS ${NCNN_ROOT}/include ${NCNN_ROOT}/include/ncnn)
find_library(NCNN_LIB ncnn PATHS ${NCNN_ROOT}/lib NO_DEFAULT_PATH)
list(APPEND LIBS ${NCNN_LIB})
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
list(APPEND LIBS OpenMP::OpenMP_CXX)
endif()
if(WIN32)
file(GLOB NCNN_DLLS "${NCNN_ROOT}/bin/*.dll" "${NCNN_ROOT}/lib/*.dll")
list(APPEND RUNTIME_DLLS ${NCNN_DLLS})
# Accept both an installed SDK (lib/) and an uninstalled source build
# (build/src/). The latter is the layout produced by the standard NCNN
# CMake instructions and is useful on an offline Ubuntu host.
find_library(NCNN_LIB ncnn PATHS
${NCNN_ROOT}/lib
${NCNN_ROOT}/lib64
${NCNN_ROOT}/build/src
${NCNN_ROOT}/build
NO_DEFAULT_PATH)
if(NOT NCNN_LIB)
if(REQUIRE_NCNN)
message(FATAL_ERROR "REQUIRE_NCNN=ON but libncnn was not found under ${NCNN_ROOT}/lib")
endif()
message(WARNING "ncnn headers found but libncnn is missing; backend disabled")
else()
list(APPEND RPATH ${NCNN_ROOT}/lib)
list(APPEND SRC src/ncnn_backend.cpp)
list(APPEND INCS ${NCNN_ROOT}/include ${NCNN_ROOT}/include/ncnn)
list(APPEND LIBS ${NCNN_LIB})
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
list(APPEND LIBS OpenMP::OpenMP_CXX)
endif()
if(UNIX AND NOT APPLE)
# Static NCNN archives do not carry their POSIX runtime dependencies.
# These are harmless for shared builds and avoid platform-specific
# undefined references (pthread/dlopen/libm) at link time.
find_package(Threads REQUIRED)
list(APPEND LIBS Threads::Threads ${CMAKE_DL_LIBS} m)
endif()
if(WIN32)
file(GLOB NCNN_DLLS "${NCNN_ROOT}/bin/*.dll" "${NCNN_ROOT}/lib/*.dll")
list(APPEND RUNTIME_DLLS ${NCNN_DLLS})
else()
get_filename_component(NCNN_LIB_DIR "${NCNN_LIB}" DIRECTORY)
list(APPEND RPATH ${NCNN_LIB_DIR} ${NCNN_ROOT}/lib ${NCNN_ROOT}/lib64
${NCNN_ROOT}/build/src ${NCNN_ROOT}/build)
endif()
list(APPEND DEFS USE_NCNN)
set(NCNN_ENABLED ON)
message(STATUS "ncnn backend: ON (${NCNN_ROOT})")
endif()
list(APPEND DEFS USE_NCNN)
message(STATUS "ncnn backend: ON (${NCNN_ROOT})")
else()
if(REQUIRE_NCNN)
message(FATAL_ERROR "REQUIRE_NCNN=ON but ncnn was not found at ${NCNN_ROOT}")
endif()
message(WARNING "ncnn backend: OFF (not found at ${NCNN_ROOT})")
endif()

if(USE_MNN AND EXISTS "${MNN_ROOT}/include/MNN/Interpreter.hpp")
list(APPEND SRC src/mnn_backend.cpp)
list(APPEND INCS ${MNN_ROOT}/include)
find_library(MNN_LIB MNN PATHS ${MNN_ROOT}/lib ${MNN_ROOT}/build ${MNN_ROOT}/build/Release NO_DEFAULT_PATH)
find_library(MNN_LIB MNN PATHS
${MNN_ROOT}/lib
${MNN_ROOT}/lib64
${MNN_ROOT}/build
${MNN_ROOT}/build/Release
${MNN_ROOT}/build/Debug
NO_DEFAULT_PATH)
if(NOT MNN_LIB)
message(FATAL_ERROR "USE_MNN=ON but libMNN not found under ${MNN_ROOT}/{lib,build}")
endif()
list(APPEND LIBS ${MNN_LIB})
if(UNIX AND NOT APPLE)
# Static MNN builds do not propagate their POSIX runtime dependencies.
# Keep shared-library builds unchanged while making libMNN.a linkable.
find_package(Threads REQUIRED)
list(APPEND LIBS Threads::Threads ${CMAKE_DL_LIBS})
endif()
if(WIN32)
file(GLOB MNN_DLLS "${MNN_ROOT}/lib/*.dll" "${MNN_ROOT}/bin/*.dll" "${MNN_ROOT}/build/*.dll" "${MNN_ROOT}/build/Release/*.dll")
list(APPEND RUNTIME_DLLS ${MNN_DLLS})
if(REQUIRE_MNN)
message(FATAL_ERROR "REQUIRE_MNN=ON but libMNN not found under ${MNN_ROOT}/{lib,build}")
endif()
message(WARNING "MNN headers found but libMNN is missing; backend disabled")
else()
list(APPEND RPATH ${MNN_ROOT}/lib ${MNN_ROOT}/build)
list(APPEND SRC src/mnn_backend.cpp)
list(APPEND INCS ${MNN_ROOT}/include)
list(APPEND LIBS ${MNN_LIB})
if(UNIX AND NOT APPLE)
# Static MNN builds do not propagate their POSIX runtime dependencies.
# Keep shared-library builds unchanged while making libMNN.a linkable.
find_package(Threads REQUIRED)
list(APPEND LIBS Threads::Threads ${CMAKE_DL_LIBS} m)
endif()
if(WIN32)
file(GLOB MNN_DLLS "${MNN_ROOT}/lib/*.dll" "${MNN_ROOT}/bin/*.dll" "${MNN_ROOT}/build/*.dll" "${MNN_ROOT}/build/Release/*.dll")
list(APPEND RUNTIME_DLLS ${MNN_DLLS})
else()
get_filename_component(MNN_LIB_DIR "${MNN_LIB}" DIRECTORY)
list(APPEND RPATH ${MNN_LIB_DIR} ${MNN_ROOT}/lib ${MNN_ROOT}/lib64
${MNN_ROOT}/build ${MNN_ROOT}/build/Release ${MNN_ROOT}/build/Debug)
endif()
list(APPEND DEFS USE_MNN)
set(MNN_ENABLED ON)
message(STATUS "MNN backend: ON (${MNN_ROOT})")
endif()
list(APPEND DEFS USE_MNN)
message(STATUS "MNN backend: ON (${MNN_ROOT})")
else()
if(REQUIRE_MNN)
message(FATAL_ERROR "REQUIRE_MNN=ON but MNN was not found at ${MNN_ROOT}")
endif()
message(WARNING "MNN backend: OFF (not found at ${MNN_ROOT})")
endif()

if(USE_TRT)
# TensorRT + CUDA from JetPack (headers under /usr/include/aarch64-linux-gnu, CUDA under /usr/local/cuda)
find_path(TRT_INC NvInfer.h PATHS /usr/include/aarch64-linux-gnu /usr/include ${TENSORRT_ROOT}/include)
find_library(TRT_LIB nvinfer PATHS /usr/lib/aarch64-linux-gnu ${TENSORRT_ROOT}/lib)
file(GLOB _CUDA_DIRS /usr/local/cuda /usr/local/cuda-*)
find_path(CUDA_INC cuda_runtime_api.h PATHS ${_CUDA_DIRS} PATH_SUFFIXES include)
find_library(CUDART_LIB cudart PATHS ${_CUDA_DIRS} PATH_SUFFIXES lib64 lib/aarch64-linux-gnu)
# The native runner uses TensorRT 10's named-I/O API. TensorRT 8 has a
# separate binding API and is intentionally routed through ORT + TRT-EP
# instead of being accepted here and failing later during compilation.
# HINTS are searched first so a caller-provided SDK/sysroot wins over a
# host installation. The explicit PATHS retain the JetPack defaults when
# both cache variables are left empty.
find_path(TRT_INC NvInfer.h
HINTS "${TENSORRT_ROOT}/include"
PATHS /usr/include/aarch64-linux-gnu /usr/include
PATH_SUFFIXES include)
find_library(TRT_LIB nvinfer
HINTS "${TENSORRT_ROOT}/lib" "${TENSORRT_ROOT}/lib64"
PATHS /usr/lib/aarch64-linux-gnu /usr/lib /usr/lib64
PATH_SUFFIXES lib lib64)
file(GLOB _CUDA_DIRS /usr/local/cuda /usr/local/cuda-* "${CUDA_ROOT}")
find_path(CUDA_INC cuda_runtime_api.h
HINTS "${CUDA_ROOT}/include"
PATHS ${_CUDA_DIRS}
PATH_SUFFIXES include)
find_library(CUDART_LIB cudart
HINTS "${CUDA_ROOT}/lib64" "${CUDA_ROOT}/lib"
PATHS ${_CUDA_DIRS}
PATH_SUFFIXES lib64 lib lib/aarch64-linux-gnu)
if(TRT_INC AND TRT_LIB AND CUDA_INC AND CUDART_LIB)
set(TRT_MAJOR "")
if(EXISTS "${TRT_INC}/NvInferVersion.h")
file(STRINGS "${TRT_INC}/NvInferVersion.h" _TRT_MAJOR_LINE
REGEX "^[ \t]*#define[ \t]+NV_TENSORRT_MAJOR[ \t]+[0-9]+")
if(_TRT_MAJOR_LINE)
string(REGEX REPLACE ".*NV_TENSORRT_MAJOR[ \t]+([0-9]+).*" "\\1"
TRT_MAJOR "${_TRT_MAJOR_LINE}")
endif()
endif()
if(NOT TRT_MAJOR)
message(FATAL_ERROR
"USE_TRT=ON requires NvInferVersion.h with NV_TENSORRT_MAJOR; "
"the native backend targets TensorRT 10.x. Set USE_TRT=OFF and use "
"the ORT TensorRT EP for older SDKs.")
endif()
if(TRT_MAJOR LESS 10)
message(FATAL_ERROR
"USE_TRT=ON found TensorRT ${TRT_MAJOR}; the native backend requires "
"TensorRT 10.x named-I/O APIs. Set USE_TRT=OFF and use ORT + TRT-EP "
"for TensorRT 8.x targets.")
endif()
list(APPEND SRC src/trt_backend.cpp)
list(APPEND INCS ${TRT_INC} ${CUDA_INC})
list(APPEND LIBS ${TRT_LIB} ${CUDART_LIB})
list(APPEND DEFS USE_TRT)
message(STATUS "TensorRT backend: ON (${TRT_LIB} + ${CUDART_LIB})")
message(STATUS "TensorRT backend: ON (v${TRT_MAJOR}; ${TRT_LIB} + ${CUDART_LIB})")
else()
message(FATAL_ERROR "USE_TRT=ON but TensorRT/CUDA not found (TRT_INC=${TRT_INC} TRT_LIB=${TRT_LIB} CUDA_INC=${CUDA_INC} CUDART_LIB=${CUDART_LIB})")
endif()
endif()

if(NOT ORT_ENABLED AND NOT NCNN_ENABLED AND NOT MNN_ENABLED AND NOT USE_TRT AND NOT ALLOW_NO_BACKENDS)
message(FATAL_ERROR
"No inference backend was configured. Install an SDK, enable one backend, or set "
"-DALLOW_NO_BACKENDS=ON for a diagnostic CLI build.")
endif()

add_executable(yolomaster_edge ${SRC})
target_include_directories(yolomaster_edge PRIVATE ${INCS})
target_link_libraries(yolomaster_edge PRIVATE ${LIBS})
Expand Down
Loading