From 511e901334dd55274ebdb503e20fb9be10d1d117 Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Tue, 30 Jun 2026 22:22:22 -0700 Subject: [PATCH 1/6] Migrate pybind11 to nanobind (#1086) * Migrate Python bindings to nanobind and align abi3 packaging - replace pybind11 with nanobind v2.13.0 in CMake and dependency manifest - port pyop bindings and C API wrappers from pybind11 APIs to nanobind APIs - update wheel build matrices to build cp310/cp311/cp312 only for abi3 strategy - add py_limited_api metadata and wheel tagging for cp312+ stable ABI - add abi3audit checks (cp312 baseline) in cibuildwheel test scripts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align limited ABI gating with ONNX strategy - enable py_limited_api only on CPython >= 3.12 and non-free-threaded runtimes - define Py_LIMITED_API=0x030C0000 only when limited ABI is enabled - gate bdist_wheel cp312 abi3 tagging with the same runtime checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/linux_arm64_wheel.yaml | 2 +- .../onebranch-windows-build-stage.yml | 2 +- .pipelines/wheels_linux.yml | 4 +- .pipelines/wheels_macos.yml | 2 +- .pipelines/wheels_win32.yml | 2 +- .pyproject/cmdclass.py | 15 + cgmanifest.json | 6 +- cmake/ext_python.cmake | 45 +-- cmake/externals/nanobind.cmake | 11 + cmake/externals/pybind11.cmake | 10 - pyop/py_c_api.cc | 69 ++-- pyop/pyfunc.cc | 303 ++++++++---------- pyop/pykernel.h | 6 +- setup.py | 11 +- .../templates/windows-build-stage.yml | 2 +- .../github/azure-pipeline/wheels_linux.yml | 4 +- .../github/azure-pipeline/wheels_macos.yml | 2 +- tools/test_cibuildwheel.bat | 17 + tools/test_cibuildwheel.sh | 13 + 19 files changed, 284 insertions(+), 242 deletions(-) create mode 100644 cmake/externals/nanobind.cmake delete mode 100644 cmake/externals/pybind11.cmake diff --git a/.github/workflows/linux_arm64_wheel.yaml b/.github/workflows/linux_arm64_wheel.yaml index 69db8f549..b76682e89 100644 --- a/.github/workflows/linux_arm64_wheel.yaml +++ b/.github/workflows/linux_arm64_wheel.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python_version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_version: ['3.10', '3.11', '3.12'] steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.pipelines/templates/onebranch-windows-build-stage.yml b/.pipelines/templates/onebranch-windows-build-stage.yml index e98387e8a..a00e02e80 100644 --- a/.pipelines/templates/onebranch-windows-build-stage.yml +++ b/.pipelines/templates/onebranch-windows-build-stage.yml @@ -13,7 +13,7 @@ stages: type: windows variables: - CIBW_BUILD: cp3{8,9,10,11,12}-*amd64 + CIBW_BUILD: cp3{10,11,12}-*amd64 CIBW_ARCHS: AMD64 CIBW_ENVIRONMENT: "${{ parameters.CibwEnv }}" CIBW_BUILD_VERBOSITY: 1 diff --git a/.pipelines/wheels_linux.yml b/.pipelines/wheels_linux.yml index ee04d37d4..9d0d241fc 100644 --- a/.pipelines/wheels_linux.yml +++ b/.pipelines/wheels_linux.yml @@ -9,7 +9,7 @@ jobs: timeoutInMinutes: 180 pool: {name: 'Azure-Pipelines-EO-Ubuntu-2004-aiinfra'} variables: - CIBW_BUILD: "cp3{8,9,10,11,12}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_SKIP: "*musllinux*" CIBW_ENVIRONMENT: "${{ parameters.ExtraEnv }}" @@ -30,7 +30,7 @@ jobs: timeoutInMinutes: 180 pool: {name: 'Azure-Pipelines-EO-Ubuntu-2004-aiinfra'} variables: - CIBW_BUILD: "cp3{8,9,10,11,12}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_SKIP: "*musllinux_*" # AzureOp doesn't support aaarch64 yet. # CIBW_ENVIRONMENT: "${{ parameters.ExtraEnv }}" diff --git a/.pipelines/wheels_macos.yml b/.pipelines/wheels_macos.yml index e7bb6d8d6..316206eff 100644 --- a/.pipelines/wheels_macos.yml +++ b/.pipelines/wheels_macos.yml @@ -4,7 +4,7 @@ jobs: pool: vmImage: 'macOS-14' variables: - CIBW_BUILD: "cp3{8,9,10,11,12}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_ENVIRONMENT: "MACOSX_DEPLOYMENT_TARGET=11.0" # Skip trying to test arm64 builds on Intel Macs diff --git a/.pipelines/wheels_win32.yml b/.pipelines/wheels_win32.yml index 461e78eb7..f5216051a 100644 --- a/.pipelines/wheels_win32.yml +++ b/.pipelines/wheels_win32.yml @@ -9,7 +9,7 @@ jobs: timeoutInMinutes: 120 pool: {name: 'onnxruntime-Win-CPU-2022'} variables: - CIBW_BUILD: "cp3{8,9,10,11,12}-*amd64" + CIBW_BUILD: "cp3{10,11,12}-*amd64" CIBW_ENVIRONMENT: "${{ parameters.ExtraEnv }}" steps: diff --git a/.pyproject/cmdclass.py b/.pyproject/cmdclass.py index 3f5147380..9719b852a 100644 --- a/.pyproject/cmdclass.py +++ b/.pyproject/cmdclass.py @@ -14,6 +14,10 @@ from setuptools.command.build import build as _build from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.develop import develop as _develop +try: + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel +except ImportError: # pragma: no cover + _bdist_wheel = None VSINSTALLDIR_NAME = 'VSINSTALLDIR' ORTX_USER_OPTION = 'ortx-user-option' @@ -316,6 +320,17 @@ def build_cmake(self, extension): self.spawn([cmake_exe, '--build', str(build_temp)] + build_args) +if _bdist_wheel is not None: + class CmdBdistWheel(_bdist_wheel): + def finalize_options(self): + super().finalize_options() + no_gil = hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled() + if sys.version_info >= (3, 12) and not no_gil: + self.py_limited_api = "cp312" + + ortx_cmdclass = dict(build=CmdBuild, develop=CmdDevelop, build_ext=CmdBuildCMakeExt) +if _bdist_wheel is not None: + ortx_cmdclass["bdist_wheel"] = CmdBdistWheel diff --git a/cgmanifest.json b/cgmanifest.json index 93c874417..b58167bdf 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -176,10 +176,10 @@ "component": { "type": "git", "git": { - "commitHash": "3e9dfa2866941655c56877882565e7577de6fc7b", - "repositoryUrl": "https://github.com/pybind/pybind11.git" + "commitHash": "e2dc00f7a34f935c6cf91948776d59c4709e9fe6", + "repositoryUrl": "https://github.com/wjakob/nanobind.git" }, - "comments": "v2.10.1" + "comments": "v2.13.0" } }, { diff --git a/cmake/ext_python.cmake b/cmake/ext_python.cmake index 9d19d67fe..2eaae85d2 100644 --- a/cmake/ext_python.cmake +++ b/cmake/ext_python.cmake @@ -1,16 +1,19 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -block(PROPAGATE Python3_FOUND) - set(Python3_FIND_REGISTRY NEVER) - # if we don't set this to NEVER (or possibly LAST) the builds of the wheel for different python versions will fail - # as it will find the system python version first and not the correct python version for the wheel. - set(Python3_FIND_FRAMEWORK NEVER) - find_package(Python3 COMPONENTS Interpreter Development.Module) -endblock() +set(Python_FIND_REGISTRY NEVER) +# if we don't set this to NEVER (or possibly LAST) the builds of the wheel for different python versions will fail +# as it will find the system python version first and not the correct python version for the wheel. +set(Python_FIND_FRAMEWORK NEVER) +if(CMAKE_VERSION VERSION_LESS 3.18) + set(_PYTHON_DEV_COMPONENT Development) +else() + set(_PYTHON_DEV_COMPONENT Development.Module) +endif() +find_package(Python COMPONENTS Interpreter ${_PYTHON_DEV_COMPONENT} OPTIONAL_COMPONENTS Development.SABIModule) -if (NOT Python3_FOUND) - message(FATAL_ERROR "Python3 not found!") +if (NOT Python_FOUND) + message(FATAL_ERROR "Python not found!") endif() file(GLOB TARGET_SRC_PYOPS "pyop/pyfunc.cc" "pyop/*.h" "shared/*.cc") @@ -21,32 +24,18 @@ if (WIN32) list(APPEND TARGET_SRC_PYOPS "pyop/extensions_pydll.def") endif() -add_library(extensions_pydll SHARED ${TARGET_SRC_PYOPS} ${shared_TARGET_LIB_SRC}) +message(STATUS "Fetch nanobind") +include(nanobind) + +nanobind_add_module(extensions_pydll NB_STATIC STABLE_ABI ${TARGET_SRC_PYOPS} ${shared_TARGET_LIB_SRC}) standardize_output_folder(extensions_pydll) list(APPEND OCOS_COMPILE_DEFINITIONS PYTHON_OP_SUPPORT) target_compile_definitions(extensions_pydll PRIVATE ${OCOS_COMPILE_DEFINITIONS}) - -message(STATUS "Fetch pybind11") -include(pybind11) target_include_directories(extensions_pydll PRIVATE - ${pybind11_INCLUDE_DIRS} - $ $) - -target_compile_definitions(extensions_pydll PRIVATE - $) - -target_link_libraries(extensions_pydll PRIVATE Python3::Module ocos_operators) +target_link_libraries(extensions_pydll PRIVATE ocos_operators) if(OCOS_PYTHON_MODULE_PATH) - get_filename_component(OCOS_PYTHON_MODULE_NAME ${OCOS_PYTHON_MODULE_PATH} NAME) - if(NOT WIN32) - set_target_properties(extensions_pydll PROPERTIES - LIBRARY_OUTPUT_NAME ${OCOS_PYTHON_MODULE_NAME} - PREFIX "" - SUFFIX "") - endif() - add_custom_command(TARGET extensions_pydll POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${OCOS_PYTHON_MODULE_PATH} COMMENT "Copying $ to ${OCOS_PYTHON_MODULE_PATH}") diff --git a/cmake/externals/nanobind.cmake b/cmake/externals/nanobind.cmake new file mode 100644 index 000000000..d145840a3 --- /dev/null +++ b/cmake/externals/nanobind.cmake @@ -0,0 +1,11 @@ +FetchContent_Declare( + nanobind + GIT_REPOSITORY https://github.com/wjakob/nanobind.git + GIT_TAG v2.13.0 + GIT_SHALLOW TRUE + GIT_SUBMODULES_RECURSE TRUE +) + +set(NB_TEST OFF CACHE BOOL "" FORCE) +set(NB_USE_SUBMODULE_DEPS ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(nanobind) diff --git a/cmake/externals/pybind11.cmake b/cmake/externals/pybind11.cmake deleted file mode 100644 index 42b83a332..000000000 --- a/cmake/externals/pybind11.cmake +++ /dev/null @@ -1,10 +0,0 @@ -FetchContent_Declare( - pybind11 - URL https://github.com/pybind/pybind11/archive/refs/tags/v2.12.0.zip - URL_HASH SHA1=8482f57ed55c7b100672815a311d5450858723fb - SOURCE_SUBDIR not_set -) - -FetchContent_MakeAvailable(pybind11) - -set(pybind11_INCLUDE_DIRS ${pybind11_SOURCE_DIR}/include) diff --git a/pyop/py_c_api.cc b/pyop/py_c_api.cc index 27e10d196..66436161b 100644 --- a/pyop/py_c_api.cc +++ b/pyop/py_c_api.cc @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include "ortx_utils.h" @@ -14,7 +13,8 @@ #include "ortx_cpp_helper.h" #include "pykernel.h" -namespace py = pybind11; +namespace nb = nanobind; +using namespace nb::literals; template int64_t NumOfElement(const T& sp) { @@ -25,7 +25,39 @@ int64_t NumOfElement(const T& sp) { return c; } -void AddGlobalMethodsCApi(pybind11::module& m) { +struct ScopedPyBuffer { + explicit ScopedPyBuffer(PyObject* object, int flags) { + if (PyObject_GetBuffer(object, &view, flags) != 0) { + throw nb::python_error(); + } + } + + ~ScopedPyBuffer() { PyBuffer_Release(&view); } + + Py_buffer view{}; +}; + +static nb::module_& GetNumpyModule() { + static nb::module_ numpy = nb::module_::import_("numpy"); + return numpy; +} + +static const char* GetNumpyDTypeName(extDataType_t tensor_type) { + switch (tensor_type) { + case extDataType_t::kOrtxFloat: + return "float32"; + case extDataType_t::kOrtxInt64: + return "int64"; + case extDataType_t::kOrtxUint8: + return "uint8"; + case extDataType_t::kOrtxUint32: + return "uint32"; + default: + throw std::runtime_error("unsupported tensor type"); + } +} + +void AddGlobalMethodsCApi(nb::module_& m) { m.def( "create_processor", [](const char* processor_def_json) { @@ -72,7 +104,7 @@ void AddGlobalMethodsCApi(pybind11::module& m) { m.def( "tensor_result_get_at", - [](std::uintptr_t result_h, size_t index) -> py::object { + [](std::uintptr_t result_h, size_t index) -> nb::object { OrtxTensorResult* result = reinterpret_cast(result_h); OrtxTensor* tensor{}; auto err = OrtxTensorResultGetAt(result, index, &tensor); @@ -89,7 +121,7 @@ void AddGlobalMethodsCApi(pybind11::module& m) { if (tensor_type == extDataType_t::kOrtxString) { const char* str{}; OrtxGetTensorData(tensor, reinterpret_cast(&str), nullptr, nullptr); - return py::str(str); + return nb::str(str); } else if (tensor_type == extDataType_t::kOrtxInt64 || tensor_type == extDataType_t::kOrtxFloat || tensor_type == extDataType_t::kOrtxUint8 || tensor_type == extDataType_t::kOrtxUint32) { OrtxGetTensorData(tensor, reinterpret_cast(&data), &shape, &num_dims); @@ -102,22 +134,9 @@ void AddGlobalMethodsCApi(pybind11::module& m) { for (auto n = num_dims - num_dims; n < num_dims; ++n) { npy_dims.push_back(shape[n]); } - py::array obj{}; - - if (tensor_type == extDataType_t::kOrtxFloat) { - obj = py::array_t(npy_dims); - } else if (tensor_type == extDataType_t::kOrtxInt64) { - obj = py::array_t(npy_dims); - } else if (tensor_type == extDataType_t::kOrtxUint8) { - obj = py::array_t(npy_dims); - } else if (tensor_type == extDataType_t::kOrtxUint32) { - obj = py::array_t(npy_dims); - } else { - throw std::runtime_error("unsupported tensor type"); - } - - void* out_ptr = obj.mutable_data(); - memcpy(out_ptr, data, NumOfElement(npy_dims) * elem_size); + nb::object obj = GetNumpyModule().attr("empty")(nb::cast(npy_dims), "dtype"_a = GetNumpyDTypeName(tensor_type)); + ScopedPyBuffer buffer(obj.ptr(), PyBUF_WRITABLE); + memcpy(buffer.view.buf, data, NumOfElement(npy_dims) * elem_size); return obj; }, "Get tensor at index."); diff --git a/pyop/pyfunc.cc b/pyop/pyfunc.cc index 215a90209..2457c787b 100644 --- a/pyop/pyfunc.cc +++ b/pyop/pyfunc.cc @@ -9,17 +9,18 @@ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #define PY_ARRAY_UNIQUE_SYMBOL ocos_python_ARRAY_API -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "string_utils.h" #include "string_tensor.h" #include "pykernel.h" -namespace py = pybind11; +namespace nb = nanobind; +using namespace nb::literals; const int PyCustomOpDef::undefined = ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED; const int PyCustomOpDef::dt_float = ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; // maps to c type float @@ -80,6 +81,58 @@ static size_t element_size(ONNXTensorElementDataType dt) { } } +struct ScopedPyBuffer { + explicit ScopedPyBuffer(PyObject* object, int flags) { + if (PyObject_GetBuffer(object, &view, flags) != 0) { + throw nb::python_error(); + } + } + + ~ScopedPyBuffer() { PyBuffer_Release(&view); } + + Py_buffer view{}; +}; + +static nb::module_& GetNumpyModule() { + static nb::module_ numpy = nb::module_::import_("numpy"); + return numpy; +} + +static const char* GetNumpyDTypeName(ONNXTensorElementDataType dtype) { + switch (dtype) { + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: + return "float32"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: + return "uint8"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: + return "int8"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16: + return "uint16"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16: + return "int16"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: + return "int32"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: + return "int64"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: + return "bool_"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE: + return "float64"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: + return "uint32"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: + return "uint64"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64: + return "complex64"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128: + return "complex128"; + case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16: + throw std::runtime_error(MakeString("Type float16 not supported by python customops api")); + default: + throw std::runtime_error(MakeString("Unsupported ONNX Tensor type: ", dtype)); + } +} + struct PyCustomOpDefImpl : public PyCustomOpDef { typedef std::vector shape_t; static int64_t calc_size_from_shape(const shape_t& sp) { @@ -90,87 +143,44 @@ struct PyCustomOpDefImpl : public PyCustomOpDef { return c; } - static py::object BuildPyArrayFromTensor( + static nb::object BuildPyArrayFromTensor( const OrtApi& api, OrtW::CustomOpApi& ort, OrtKernelContext* context, const OrtValue* value, const shape_t& shape, ONNXTensorElementDataType dtype) { std::vector npy_dims; + npy_dims.reserve(shape.size()); for (auto n : shape) { - npy_dims.push_back(n); - } - py::array obj; - - switch (dtype) { - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING: - obj = py::array(py::dtype("O"), npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16: - throw std::runtime_error(MakeString( - "Type float16 not supported by python customops api")); - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: - obj = py::array_t(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64: - obj = py::array_t>(npy_dims); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128: - obj = py::array_t>(npy_dims); - break; - default: - throw std::runtime_error(MakeString("Unsupported ONNX Tensor type: ", dtype)); + npy_dims.push_back(static_cast(n)); } - void* out_ptr = obj.mutable_data(); + auto& numpy = GetNumpyModule(); if (dtype == ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING) { - py::object* outObj = static_cast(out_ptr); - auto size = calc_size_from_shape(shape); std::vector src; GetTensorMutableDataString(api, ort, context, value, src); - for (int i = 0; i < size; ++i) { - outObj[i] = py::cast(src[i]); + nb::list py_values; + for (const auto& item : src) { + py_values.append(nb::str(item.c_str())); } - } else { - const void* p = (const void*)ort.GetTensorData(value); - size_t size_type = element_size(dtype); - memcpy(out_ptr, p, size_type * calc_size_from_shape(shape)); + nb::object obj = numpy.attr("array")(py_values, "dtype"_a = numpy.attr("object_")); + obj = obj.attr("reshape")(nb::cast(npy_dims)); + return obj; + } + + nb::object obj = numpy.attr("empty")(nb::cast(npy_dims), "dtype"_a = GetNumpyDTypeName(dtype)); + const void* p = static_cast(ort.GetTensorData(value)); + size_t byte_size = element_size(dtype) * static_cast(calc_size_from_shape(shape)); + ScopedPyBuffer buffer(obj.ptr(), PyBUF_WRITABLE); + if (static_cast(buffer.view.len) < byte_size) { + throw std::runtime_error("Numpy buffer is smaller than tensor data."); } + memcpy(buffer.view.buf, p, byte_size); return obj; } - static py::object InvokePyFunction(uint64_t id, const py::object& feed, const py::object& attrs) { + static nb::object InvokePyFunction(uint64_t id, const nb::object& feed, const nb::object& attrs) { return (*op_invoker)(id, feed, attrs); } - using callback_t = std::function; + using callback_t = std::function; static std::unique_ptr op_invoker; }; @@ -260,92 +270,54 @@ void PyCustomOpKernel::Compute(OrtKernelContext* context) { { /* Acquire GIL before calling Python C API, due to it was released in sess.run */ - py::gil_scoped_acquire acquire; - py::list pyinputs; + nb::gil_scoped_acquire acquire; + nb::list pyinputs; for (auto it = inputs.begin(); it != inputs.end(); ++it) { - py::object input0 = PyCustomOpDefImpl::BuildPyArrayFromTensor( + nb::object input0 = PyCustomOpDefImpl::BuildPyArrayFromTensor( api_, ort_, context, it->input_X, it->dimensions, it->dtype); pyinputs.append(input0); } - py::dict pyattrs; + nb::dict pyattrs; for (auto it = attrs_values_.begin(); it != attrs_values_.end(); ++it) { - pyattrs[py::str(it->first)] = py::str(it->second); + pyattrs[nb::cast(it->first)] = nb::cast(it->second); } // Call python function id, shape, flat coefficient. - py::tuple fetch = PyCustomOpDefImpl::InvokePyFunction(obj_id_, pyinputs, pyattrs); - int64_t rid = fetch[0].cast(); + nb::tuple fetch = nb::cast(PyCustomOpDefImpl::InvokePyFunction(obj_id_, pyinputs, pyattrs)); + int64_t rid = nb::cast(fetch[0]); assert(rid == obj_id_); // Setup output. for (size_t no = 0; no < n_outputs; ++no) { - auto dims = fetch[1 + no * 2].cast>(); + auto dims = nb::cast>(fetch[1 + no * 2]); OrtValue* output = ort_.KernelContext_GetOutput(context, no, dims.data(), dims.size()); OrtTensorTypeAndShapeInfo* o_info = ort_.GetTensorTypeAndShape(output); ONNXTensorElementDataType o_dtype = ort_.GetTensorElementType(o_info); ort_.ReleaseTensorTypeAndShapeInfo(o_info); if (o_dtype == ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING) { - std::vector retval = fetch[2 + no * 2].cast>(); + std::vector retval = nb::cast>(fetch[2 + no * 2]); FillTensorDataString(api_, ort_, context, retval, output); } else { - const void* Y = (const void*)ort_.GetTensorData(output); void* out = (void*)ort_.GetTensorMutableData(output); - py::array retval = fetch[2 + no * 2].cast(); - if (element_size(o_dtype) != retval.itemsize()) { - switch (o_dtype) { - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16: - throw std::runtime_error(MakeString( - "Type float16 not supported by python customops api")); - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: - retval = fetch[2 + no * 2].cast>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64: - retval = fetch[2 + no * 2].cast>>(); - break; - case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128: - retval = fetch[2 + no * 2].cast>>(); - break; - default: - throw std::runtime_error(MakeString( - "Type mismatch between declared output element size (", - element_size(o_dtype), ") and python element size (", - retval.itemsize(), ")")); - } - } + nb::object retval = GetNumpyModule().attr("ascontiguousarray")(fetch[2 + no * 2], "dtype"_a = GetNumpyDTypeName(o_dtype)); + ScopedPyBuffer buffer(retval.ptr(), PyBUF_CONTIG_RO); size_t size = element_size(o_dtype); - memcpy(out, retval.data(), size * retval.size()); + size_t output_bytes = size * static_cast(PyCustomOpDefImpl::calc_size_from_shape(dims)); + if (static_cast(buffer.view.itemsize) != size) { + throw std::runtime_error(MakeString( + "Type mismatch between declared output element size (", + size, ") and python element size (", + buffer.view.itemsize, ")")); + } + if (static_cast(buffer.view.len) != output_bytes) { + throw std::runtime_error(MakeString( + "Type mismatch between expected output bytes (", + output_bytes, ") and python output bytes (", + buffer.view.len, ")")); + } + memcpy(out, buffer.view.buf, output_bytes); } } } @@ -438,7 +410,7 @@ uint64_t hash_64(const std::string& str, uint64_t num_buckets, bool fast) { return Hash64(str.c_str(), str.size()) % static_cast(num_buckets); } -void AddGlobalMethods(pybind11::module& m) { +void AddGlobalMethods(nb::module_& m) { m.def("hash_64", &hash_64, "Computes a uint64 hash for a string (from tensorflow)."); m.def("enable_py_op", &EnablePyCustomOps, "Enable or disable pyop functions."); m.def( @@ -447,44 +419,47 @@ void AddGlobalMethods(pybind11::module& m) { "default_opset_domain", [] { return std::string(c_OpDomain); }, "return the default opset domain name."); } -void AddObjectMethods(pybind11::module& m) { - pybind11::class_(m, "PyCustomOpDef") - .def(pybind11::init<>()) - .def_readwrite("op_type", &PyCustomOpDef::op_type) - .def_readwrite("obj_id", &PyCustomOpDef::obj_id) - .def_readwrite("input_types", &PyCustomOpDef::input_types) - .def_readwrite("output_types", &PyCustomOpDef::output_types) - .def_readwrite("attrs", &PyCustomOpDef::attrs) - .def_static("install_hooker", [](py::object obj) { PyCustomOpDefImpl::op_invoker = std::make_unique(obj); }) - .def_readonly_static("undefined", &PyCustomOpDef::undefined) - .def_readonly_static("dt_float", &PyCustomOpDef::dt_float) - .def_readonly_static("dt_uint8", &PyCustomOpDef::dt_uint8) - .def_readonly_static("dt_int8", &PyCustomOpDef::dt_int8) - .def_readonly_static("dt_uint16", &PyCustomOpDef::dt_uint16) - .def_readonly_static("dt_int16", &PyCustomOpDef::dt_int16) - .def_readonly_static("dt_int32", &PyCustomOpDef::dt_int32) - .def_readonly_static("dt_int64", &PyCustomOpDef::dt_int64) - .def_readonly_static("dt_string", &PyCustomOpDef::dt_string) - .def_readonly_static("dt_bool", &PyCustomOpDef::dt_bool) - .def_readonly_static("dt_float16", &PyCustomOpDef::dt_float16) - .def_readonly_static("dt_double", &PyCustomOpDef::dt_double) - .def_readonly_static("dt_uint32", &PyCustomOpDef::dt_uint32) - .def_readonly_static("dt_uint64", &PyCustomOpDef::dt_uint64) - .def_readonly_static("dt_complex64", &PyCustomOpDef::dt_complex64) - .def_readonly_static("dt_complex128", &PyCustomOpDef::dt_complex128) - .def_readonly_static("dt_bfloat16", &PyCustomOpDef::dt_bfloat16); +void AddObjectMethods(nb::module_& m) { + nb::class_(m, "PyCustomOpDef") + .def(nb::init<>()) + .def_rw("op_type", &PyCustomOpDef::op_type) + .def_rw("obj_id", &PyCustomOpDef::obj_id) + .def_rw("input_types", &PyCustomOpDef::input_types) + .def_rw("output_types", &PyCustomOpDef::output_types) + .def_rw("attrs", &PyCustomOpDef::attrs) + .def_static("install_hooker", [](nb::object obj) { + PyCustomOpDefImpl::op_invoker = std::make_unique( + nb::cast(obj)); + }) + .def_ro_static("undefined", &PyCustomOpDef::undefined) + .def_ro_static("dt_float", &PyCustomOpDef::dt_float) + .def_ro_static("dt_uint8", &PyCustomOpDef::dt_uint8) + .def_ro_static("dt_int8", &PyCustomOpDef::dt_int8) + .def_ro_static("dt_uint16", &PyCustomOpDef::dt_uint16) + .def_ro_static("dt_int16", &PyCustomOpDef::dt_int16) + .def_ro_static("dt_int32", &PyCustomOpDef::dt_int32) + .def_ro_static("dt_int64", &PyCustomOpDef::dt_int64) + .def_ro_static("dt_string", &PyCustomOpDef::dt_string) + .def_ro_static("dt_bool", &PyCustomOpDef::dt_bool) + .def_ro_static("dt_float16", &PyCustomOpDef::dt_float16) + .def_ro_static("dt_double", &PyCustomOpDef::dt_double) + .def_ro_static("dt_uint32", &PyCustomOpDef::dt_uint32) + .def_ro_static("dt_uint64", &PyCustomOpDef::dt_uint64) + .def_ro_static("dt_complex64", &PyCustomOpDef::dt_complex64) + .def_ro_static("dt_complex128", &PyCustomOpDef::dt_complex128) + .def_ro_static("dt_bfloat16", &PyCustomOpDef::dt_bfloat16); } -PYBIND11_MODULE(_extensions_pydll, m) { - m.doc() = "pybind11 stateful interface to ONNXRuntime-Extensions"; +NB_MODULE(_extensions_pydll, m) { + m.attr("__doc__") = "nanobind stateful interface to ONNXRuntime-Extensions"; AddGlobalMethods(m); #if defined(ENABLE_C_API) AddGlobalMethodsCApi(m); #endif AddObjectMethods(m); - auto atexit = py::module_::import("atexit"); - atexit.attr("register")(py::cpp_function([]() { + auto atexit = nb::module_::import_("atexit"); + atexit.attr("register")(nb::cpp_function([]() { PyCustomOpDefImpl::op_invoker.reset(); })); } diff --git a/pyop/pykernel.h b/pyop/pykernel.h index 0f41412a2..083e178c7 100644 --- a/pyop/pykernel.h +++ b/pyop/pykernel.h @@ -9,6 +9,10 @@ #include #include +namespace nanobind { +class module_; +} + struct PyCustomOpDef { std::string op_type; uint64_t obj_id = 0; @@ -129,5 +133,5 @@ struct PyCustomOpFactory : public OrtCustomOp { bool EnablePyCustomOps(bool enable = true); #if defined(ENABLE_C_API) -void AddGlobalMethodsCApi(pybind11::module& m); +void AddGlobalMethodsCApi(nanobind::module_& m); #endif diff --git a/setup.py b/setup.py index 3b2100e98..a9d160622 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,9 @@ TOP_DIR = os.path.dirname(__file__) or os.getcwd() PACKAGE_NAME = "onnxruntime_extensions" +NO_GIL = hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled() +PY_312_OR_NEWER = sys.version_info >= (3, 12) +USE_LIMITED_API = not NO_GIL and PY_312_OR_NEWER # setup.py cannot be debugged in pip command line, so the command classes are refactored into another file cmds_dir = pathlib.Path(TOP_DIR) / ".pyproject" @@ -53,7 +56,12 @@ def write_py_version(ext_version): _fver.writelines(text) -ext_modules = [setuptools.extension.Extension(name=str("onnxruntime_extensions._extensions_pydll"), sources=[])] +ext_modules = [setuptools.extension.Extension( + name=str("onnxruntime_extensions._extensions_pydll"), + sources=[], + py_limited_api=USE_LIMITED_API, + define_macros=[("Py_LIMITED_API", "0x030C0000")] if USE_LIMITED_API else [], +)] packages = find_packages() package_dir = {k: os.path.join(".", k.replace(".", "/")) for k in packages} @@ -88,6 +96,7 @@ def write_py_version(ext_version): cmdclass=_cmds.ortx_cmdclass, include_package_data=True, install_requires=[], + python_requires=">=3.10", classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", diff --git a/tools/ci_build/github/azure-pipeline/templates/windows-build-stage.yml b/tools/ci_build/github/azure-pipeline/templates/windows-build-stage.yml index f938670b5..a7fbe1b6c 100644 --- a/tools/ci_build/github/azure-pipeline/templates/windows-build-stage.yml +++ b/tools/ci_build/github/azure-pipeline/templates/windows-build-stage.yml @@ -20,7 +20,7 @@ stages: os: windows variables: - CIBW_BUILD: cp3{10,11,12,13}-*amd64 + CIBW_BUILD: cp3{10,11,12}-*amd64 CIBW_ARCHS: AMD64 CIBW_ENVIRONMENT: "${{ parameters.CibwEnv }}" CIBW_BUILD_VERBOSITY: 1 diff --git a/tools/ci_build/github/azure-pipeline/wheels_linux.yml b/tools/ci_build/github/azure-pipeline/wheels_linux.yml index 4d7940f2c..c3df1fe33 100644 --- a/tools/ci_build/github/azure-pipeline/wheels_linux.yml +++ b/tools/ci_build/github/azure-pipeline/wheels_linux.yml @@ -33,7 +33,7 @@ extends: - job: linux_x86_64 timeoutInMinutes: 180 variables: - CIBW_BUILD: "cp3{10,11,12,13}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_SKIP: "*musllinux*" CIBW_ENVIRONMENT: "${{ parameters.ExtraEnv }}" templateContext: @@ -56,7 +56,7 @@ extends: - job: manylinux_aarch64 timeoutInMinutes: 180 variables: - CIBW_BUILD: "cp3{10,11,12,13}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_SKIP: "*musllinux_*" # AzureOp doesn't support aaarch64 yet. # CIBW_ENVIRONMENT: "${{ parameters.ExtraEnv }}" diff --git a/tools/ci_build/github/azure-pipeline/wheels_macos.yml b/tools/ci_build/github/azure-pipeline/wheels_macos.yml index a9c765e48..360c36ad4 100644 --- a/tools/ci_build/github/azure-pipeline/wheels_macos.yml +++ b/tools/ci_build/github/azure-pipeline/wheels_macos.yml @@ -28,7 +28,7 @@ extends: - job: macos timeoutInMinutes: 180 variables: - CIBW_BUILD: "cp3{10,11,12,13}-*" + CIBW_BUILD: "cp3{10,11,12}-*" CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_ENVIRONMENT: "MACOSX_DEPLOYMENT_TARGET=11.0" # Skip trying to test arm64 builds on Intel Macs diff --git a/tools/test_cibuildwheel.bat b/tools/test_cibuildwheel.bat index 83cb140eb..2492b3d3d 100644 --- a/tools/test_cibuildwheel.bat +++ b/tools/test_cibuildwheel.bat @@ -1 +1,18 @@ @echo off +setlocal EnableDelayedExpansion + +python -c "import onnxruntime_extensions as _ortx; import onnxruntime_extensions._extensions_pydll as _ext; print(_ext.__file__)" +if errorlevel 1 exit /b 1 + +for /f "delims=" %%i in ('python -c "import onnxruntime_extensions._extensions_pydll as m; print(m.__file__)"') do ( + set EXT_PATH=%%i +) + +python -c "import sys; raise SystemExit(0 if sys.version_info >= (3,12) else 1)" +if errorlevel 1 exit /b 0 + +python -m pip install -q abi3audit +if errorlevel 1 exit /b 1 + +abi3audit --assume-minimum-abi3 3.12 "%EXT_PATH%" +if errorlevel 1 exit /b 1 diff --git a/tools/test_cibuildwheel.sh b/tools/test_cibuildwheel.sh index a9bf588e2..8c27ee6e0 100755 --- a/tools/test_cibuildwheel.sh +++ b/tools/test_cibuildwheel.sh @@ -1 +1,14 @@ #!/bin/bash +set -euo pipefail + +python - <<'PY' +import onnxruntime_extensions as _ortx # noqa: F401 +import onnxruntime_extensions._extensions_pydll as _ext +print(_ext.__file__) +PY + +if python -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)"; then + python -m pip install -q abi3audit + ext_path="$(python -c "import onnxruntime_extensions._extensions_pydll as m; print(m.__file__)")" + abi3audit --assume-minimum-abi3 3.12 "$ext_path" +fi From 25d7636534f1eb76430e37fba617ef5b182cc16b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:38:49 +0000 Subject: [PATCH 2/6] Export RegisterCustomOps for nanobind abi3 module --- include/onnxruntime_extensions.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/onnxruntime_extensions.h b/include/onnxruntime_extensions.h index 7853e8ad9..5644f330e 100644 --- a/include/onnxruntime_extensions.h +++ b/include/onnxruntime_extensions.h @@ -17,9 +17,17 @@ extern "C" { #endif +#if defined(_WIN32) +#define ORTX_EXPORT __declspec(dllexport) +#else +#define ORTX_EXPORT __attribute__((visibility("default"))) +#endif + +ORTX_EXPORT OrtStatus* ORT_API_CALL RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api); +ORTX_EXPORT int ORT_API_CALL GetActiveOrtAPIVersion(); From 2f29aa678eea1949ef148a63fd1f4b91cd24ee67 Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Wed, 1 Jul 2026 10:32:57 -0700 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pyop/pyfunc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyop/pyfunc.cc b/pyop/pyfunc.cc index 2457c787b..f96634dc8 100644 --- a/pyop/pyfunc.cc +++ b/pyop/pyfunc.cc @@ -158,7 +158,7 @@ struct PyCustomOpDefImpl : public PyCustomOpDef { GetTensorMutableDataString(api, ort, context, value, src); nb::list py_values; for (const auto& item : src) { - py_values.append(nb::str(item.c_str())); + py_values.append(nb::cast(item)); } nb::object obj = numpy.attr("array")(py_values, "dtype"_a = numpy.attr("object_")); obj = obj.attr("reshape")(nb::cast(npy_dims)); From b95ca643c48240733eb35e2b6fe1626e88933914 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:58:42 +0000 Subject: [PATCH 4/6] Update remaining CI Python 3.8 env to 3.10 --- .../linux/docker/Dockerfile.ubi8_cuda11_8_tensorrt8_6 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/ci_build/github/linux/docker/Dockerfile.ubi8_cuda11_8_tensorrt8_6 b/tools/ci_build/github/linux/docker/Dockerfile.ubi8_cuda11_8_tensorrt8_6 index fa175b234..2e1ef4ec1 100644 --- a/tools/ci_build/github/linux/docker/Dockerfile.ubi8_cuda11_8_tensorrt8_6 +++ b/tools/ci_build/github/linux/docker/Dockerfile.ubi8_cuda11_8_tensorrt8_6 @@ -14,12 +14,12 @@ RUN dnf install -y bash wget git&&\ # Install python3 RUN dnf install -y \ - python3.8 \ - python38-pip \ - python38-wheel &&\ + python3.10 \ + python3.10-pip \ + python3.10-wheel &&\ cd /usr/local/bin &&\ - ln -s /usr/bin/python3 python3.8 &&\ - ln -s /usr/bin/pip3 pip3.8; + ln -s /usr/bin/python3 python3.10 &&\ + ln -s /usr/bin/pip3 pip3.10; RUN pip3 install --upgrade pip RUN pip3 install setuptools>=68.2.2 From d823c8063bcb9ddb97a62a5f81fc7a684a8fbe7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:46:39 +0000 Subject: [PATCH 5/6] Fix LinuxGPU Docker Python 3.10 --- .../Dockerfile.ubuntu_cuda11_8_tensorrt8_6 | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 b/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 index dadfc3ab3..f36e75b54 100644 --- a/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 +++ b/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 @@ -12,18 +12,23 @@ ENV PATH /usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/src/tensorrt/bin:${ONNXR ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update &&\ - apt-get install -y git bash unattended-upgrades wget + apt-get install -y git bash unattended-upgrades wget software-properties-common RUN unattended-upgrade -# Install python3 -RUN apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - python3-dev \ - python3-wheel &&\ +# Install python3.10 +RUN add-apt-repository ppa:deadsnakes/ppa &&\ + apt-get update &&\ + apt-get install -y --no-install-recommends \ + python3.10 \ + python3.10-dev \ + python3.10-distutils \ + python3.10-venv &&\ + wget -q https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py &&\ + python3.10 /tmp/get-pip.py &&\ cd /usr/local/bin &&\ - ln -s /usr/bin/python3 python &&\ - ln -s /usr/bin/pip3 pip; + ln -sf /usr/bin/python3.10 python3 &&\ + ln -sf /usr/bin/python3.10 python &&\ + ln -sf /usr/local/bin/pip3 pip; RUN pip install --upgrade pip RUN pip install setuptools>=68.2.2 @@ -47,4 +52,3 @@ ARG BUILD_USER=onnxruntimedev ARG BUILD_UID=1000 RUN adduser --gecos 'onnxruntime Build User' --disabled-password $BUILD_USER --uid $BUILD_UID USER $BUILD_USER - From 51346c278c9f11bc1e39ebc52f6f314a03084a25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:47:28 +0000 Subject: [PATCH 6/6] Clean up LinuxGPU Python setup --- .../linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 b/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 index f36e75b54..75d18dd0d 100644 --- a/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 +++ b/tools/ci_build/github/linux/docker/Dockerfile.ubuntu_cuda11_8_tensorrt8_6 @@ -25,10 +25,10 @@ RUN add-apt-repository ppa:deadsnakes/ppa &&\ python3.10-venv &&\ wget -q https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py &&\ python3.10 /tmp/get-pip.py &&\ + rm /tmp/get-pip.py &&\ cd /usr/local/bin &&\ ln -sf /usr/bin/python3.10 python3 &&\ - ln -sf /usr/bin/python3.10 python &&\ - ln -sf /usr/local/bin/pip3 pip; + ln -sf /usr/bin/python3.10 python; RUN pip install --upgrade pip RUN pip install setuptools>=68.2.2