diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b799f20..564f3c8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,6 +18,7 @@ jobs: strategy: fail-fast: false matrix: + # NOTE if newer versions are added, update sony_custom_layers.__init__ pinned_requirements!!! py_ver: ["3.8", "3.9", "3.10", "3.11"] tf_ver: ["2.10", "2.11", "2.12", "2.13", "2.14", "2.15"] exclude: @@ -38,7 +39,7 @@ jobs: python-version: ${{matrix.py_ver}} - name: Install dependencies run: | - pip install tensorflow==${{matrix.tf_ver}} + pip install tensorflow==${{matrix.tf_ver}}.* pip install -r requirements_test.txt pip list - name: Run pytest @@ -50,15 +51,25 @@ jobs: strategy: fail-fast: false matrix: + # NOTE if newer versions are added, update sony_custom_layers.__init__ pinned_requirements!!! py_ver: [ "3.8", "3.9", "3.10", "3.11" ] - torch_ver: ["2.2.*"] - torchvision_ver: ["0.17.*"] # <0.17 incompatible with torch2.2 - ort_ver: ["1.15.*", "1.16.*", "1.17.*"] - ort_ext_ver: ["0.8.*", "0.9.*", "0.10.*"] - onnx_ver: ["1.14.*", "1.15.*"] + torch_ver: ["2.0", "2.1", "2.2"] + ort_ver: ["1.15", "1.16", "1.17"] + ort_ext_ver: ["0.8", "0.9", "0.10"] + include: + - torch_ver: "2.2" + torchvision_ver: "0.17" + onnx_ver: "1.15" + - torch_ver: "2.1" + torchvision_ver: "0.16" + onnx_ver: "1.14" + - torch_ver: "2.0" + torchvision_ver: "0.15" + onnx_ver: "1.15" + exclude: - py_ver: "3.11" - ort_ext_ver: "0.8.*" + ort_ext_ver: "0.8" steps: - name: Checkout uses: actions/checkout@v4 @@ -68,11 +79,11 @@ jobs: python-version: ${{matrix.py_ver}} - name: Install dependencies run: | - pip install torch==${{matrix.torch_ver}} \ - torchvision==${{matrix.torchvision_ver}} \ - onnxruntime==${{matrix.ort_ver}} \ - onnxruntime_extensions==${{matrix.ort_ext_ver}} \ - onnx==${{matrix.onnx_ver}} \ + pip install torch==${{matrix.torch_ver}}.* \ + torchvision==${{matrix.torchvision_ver}}.* \ + onnxruntime==${{matrix.ort_ver}}.* \ + onnxruntime_extensions==${{matrix.ort_ext_ver}}.* \ + onnx==${{matrix.onnx_ver}}.* \ --index-url https://download.pytorch.org/whl/cpu \ --extra-index-url https://pypi.org/simple @@ -91,12 +102,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.10" - - name: Run pre-commit run: | ./install-pre-commit.sh pre-commit run --all - - name: get new dev tag shell: bash run : | @@ -128,7 +137,7 @@ jobs: echo "__version__ = '${{ env.new_ver }}'" > sony_custom_layers/version.py echo "print sony_custom_layers/version.py" cat sony_custom_layers/version.py - + sed -i 's/name = sony-custom-layers/name = sony-custom-layers-dev/' setup.cfg echo "print setup.cfg" cat setup.cfg @@ -148,5 +157,3 @@ jobs: git tag ${{ env.new_tag }} git push origin ${{ env.new_tag }} fi - - diff --git a/README.md b/README.md index 8455246..ca8b9d0 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ To install the latest stable release of SCL, run the following command: pip install sony-custom-layers ``` By default, no framework dependencies are installed. -To install SCL including the dependencies for TensorFlow: +To install SCL including the latest tested dependencies (up to patch version) for TensorFlow: ``` pip install sony-custom-layers[tf] ``` -To install SCL including the dependencies for PyTorch/ONNX/OnnxRuntime: +To install SCL including the latest tested dependencies (up to patch version) for PyTorch/ONNX/OnnxRuntime: ``` pip install sony-custom-layers[torch] ``` @@ -43,9 +43,9 @@ pip install sony-custom-layers[torch] #### PyTorch -| **Tested FW versions** | **Tested Python version** | **Serialization** | -|---------------------------------|---------------------------|------------------------------------------------------------------| -| torch 2.2
torchvision 0.17
onnxruntime 1.15-1.17
onnxruntime_extensions 0.8-0.10
onnx 1.14-1.15| 3.8-3.11 | .onnx (via torch.onnx.export)
.pt2 (via torch.export.export) | +| **Tested FW versions** | **Tested Python version** | **Serialization** | +|--------------------------------------------------------------------------------------------------------------------------|---------------------------|---------------------------------------------------------------------------------| +| torch 2.0-2.2
torchvision 0.15-0.17
onnxruntime 1.15-1.17
onnxruntime_extensions 0.8-0.10
onnx 1.14-1.15 | 3.8-3.11 | .onnx (via torch.onnx.export)
.pt2 (via torch.export.export, torch2.2 only) | ## Implemented Layers SCL currently includes implementations of the following layers: diff --git a/setup.py b/setup.py index 517d6d9..86b5301 100644 --- a/setup.py +++ b/setup.py @@ -23,11 +23,11 @@ """ from setuptools import setup -from sony_custom_layers import requirements +from sony_custom_layers import pinned_requirements extras_require = { - 'torch': requirements['torch'] + requirements['torch_ort'], - 'tf': requirements['tf'], + 'torch': pinned_requirements['torch'] + pinned_requirements['torch_ort'], + 'tf': pinned_requirements['tf'], } setup(extras_require=extras_require) diff --git a/sony_custom_layers/__init__.py b/sony_custom_layers/__init__.py index abd89cb..e0faecf 100644 --- a/sony_custom_layers/__init__.py +++ b/sony_custom_layers/__init__.py @@ -14,9 +14,16 @@ # limitations under the License. # ----------------------------------------------------------------------------- -# for use by setup.py and for dynamic validation in sony_custom_layers.{keras, pytorch}.__init__ +# minimal requirements for dynamic validation in sony_custom_layers.{keras, pytorch}.__init__ requirements = { - 'tf': ['tensorflow>=2.10,<2.16'], - 'torch': ['torch>=2.2.0', 'torchvision>=0.17.0'], - 'torch_ort': ['onnxruntime', 'onnxruntime_extensions>=0.8.0'], + 'tf': ['tensorflow>=2.10'], + 'torch': ['torch>=2.0', 'torchvision>=0.15'], + 'torch_ort': ['onnx', 'onnxruntime', 'onnxruntime_extensions>=0.8.0'], +} + +# pinned requirements of latest tested versions for extra_requires +pinned_requirements = { + 'tf': ['tensorflow==2.15.*'], + 'torch': ['torch==2.2.*', 'torchvision==0.17.*'], + 'torch_ort': ['onnx==1.15.*', 'onnxruntime==1.17.*', 'onnxruntime_extensions==0.10.*'] } diff --git a/sony_custom_layers/keras/__init__.py b/sony_custom_layers/keras/__init__.py index 4d7d25e..6ad534e 100644 --- a/sony_custom_layers/keras/__init__.py +++ b/sony_custom_layers/keras/__init__.py @@ -14,10 +14,10 @@ # limitations under the License. # ----------------------------------------------------------------------------- -from sony_custom_layers.util.import_util import check_pip_requirements +from sony_custom_layers.util.import_util import validate_pip_requirements from sony_custom_layers import requirements -check_pip_requirements(requirements['tf']) +validate_pip_requirements(requirements['tf']) from .object_detection import FasterRCNNBoxDecode, SSDPostProcess, ScoreConverter # noqa: E402 from .custom_objects import custom_layers_scope # noqa: E402 diff --git a/sony_custom_layers/pytorch/__init__.py b/sony_custom_layers/pytorch/__init__.py index e29a600..5e6e8db 100644 --- a/sony_custom_layers/pytorch/__init__.py +++ b/sony_custom_layers/pytorch/__init__.py @@ -15,7 +15,7 @@ # ----------------------------------------------------------------------------- from typing import Optional, TYPE_CHECKING -from sony_custom_layers.util.import_util import check_pip_requirements +from sony_custom_layers.util.import_util import validate_pip_requirements from sony_custom_layers import requirements if TYPE_CHECKING: @@ -23,7 +23,7 @@ __all__ = ['multiclass_nms', 'NMSResults', 'load_custom_ops'] -check_pip_requirements(requirements['torch']) +validate_pip_requirements(requirements['torch']) from .object_detection import multiclass_nms, NMSResults # noqa: E402 @@ -53,7 +53,7 @@ def load_custom_ops(load_ort: bool = False, SessionOptions object if ort registration was requested, otherwise None """ if load_ort or ort_session_ops: - check_pip_requirements(requirements['torch_ort']) + validate_pip_requirements(requirements['torch_ort']) # trigger onnxruntime op registration from .object_detection import nms_ort diff --git a/sony_custom_layers/pytorch/object_detection/nms.py b/sony_custom_layers/pytorch/object_detection/nms.py index 5dd0981..46073aa 100644 --- a/sony_custom_layers/pytorch/object_detection/nms.py +++ b/sony_custom_layers/pytorch/object_detection/nms.py @@ -20,7 +20,11 @@ from torch import Tensor import torchvision # noqa: F401 # needed for torch.ops.torchvision -MULTICLASS_NMS_TORCH_OP = 'sony::multiclass_nms' +from sony_custom_layers.util.import_util import is_compatible + +CUSTOM_LIB_NAME = 'sony' +MULTICLASS_NMS_TORCH_OP = 'multiclass_nms' +MULTICLASS_NMS_TORCH_OP_QUALNAME = CUSTOM_LIB_NAME + '::' + MULTICLASS_NMS_TORCH_OP __all__ = ['multiclass_nms', 'NMSResults'] @@ -57,13 +61,19 @@ def multiclass_nms(boxes, scores, score_threshold: float, iou_threshold: float, return NMSResults(*torch.ops.sony.multiclass_nms(boxes, scores, score_threshold, iou_threshold, max_detections)) -torch.library.define( - MULTICLASS_NMS_TORCH_OP, - "(Tensor boxes, Tensor scores, float score_threshold, float iou_threshold, SymInt max_detections) -> " - "(Tensor, Tensor, Tensor, Tensor)") +custom_lib = torch.library.Library(CUSTOM_LIB_NAME, "DEF") +schema = (MULTICLASS_NMS_TORCH_OP + + "(Tensor boxes, Tensor scores, float score_threshold, float iou_threshold, SymInt max_detections) " + "-> (Tensor, Tensor, Tensor, Tensor)") +op_name = custom_lib.define(schema) + +if is_compatible('torch>=2.2'): + register_impl = torch.library.impl(MULTICLASS_NMS_TORCH_OP_QUALNAME, 'default') +else: + register_impl = torch.library.impl(custom_lib, MULTICLASS_NMS_TORCH_OP) -@torch.library.impl(MULTICLASS_NMS_TORCH_OP, 'default') +@register_impl def _multiclass_nms_op(boxes: torch.Tensor, scores: torch.Tensor, score_threshold: float, iou_threshold: float, max_detections: int) -> NMSResults: """ Registers the torch op as torch.ops.sony.multiclass_nms """ @@ -74,19 +84,21 @@ def _multiclass_nms_op(boxes: torch.Tensor, scores: torch.Tensor, score_threshol max_detections=max_detections) -@torch.library.impl_abstract(MULTICLASS_NMS_TORCH_OP) -def _multiclass_nms_meta(boxes: torch.Tensor, scores: torch.Tensor, score_threshold: float, iou_threshold: float, - max_detections: int) -> NMSResults: - """ Registers torch op's abstract implementation. It specifies the properties of the output tensors. - Needed for torch.export """ - ctx = torch.library.get_ctx() - batch = ctx.new_dynamic_size() - return NMSResults( - torch.empty((batch, max_detections, 4)), - torch.empty((batch, max_detections)), - torch.empty((batch, max_detections), dtype=torch.int64), - torch.empty((batch, 1), dtype=torch.int64) - ) # yapf: disable +if is_compatible('torch>=2.2'): + + @torch.library.impl_abstract(MULTICLASS_NMS_TORCH_OP_QUALNAME) + def _multiclass_nms_meta(boxes: torch.Tensor, scores: torch.Tensor, score_threshold: float, iou_threshold: float, + max_detections: int) -> NMSResults: + """ Registers torch op's abstract implementation. It specifies the properties of the output tensors. + Needed for torch.export """ + ctx = torch.library.get_ctx() + batch = ctx.new_dynamic_size() + return NMSResults( + torch.empty((batch, max_detections, 4)), + torch.empty((batch, max_detections)), + torch.empty((batch, max_detections), dtype=torch.int64), + torch.empty((batch, 1), dtype=torch.int64) + ) # yapf: disable def _multiclass_nms_impl(boxes: Union[Tensor, np.ndarray], scores: Union[Tensor, np.ndarray], score_threshold: float, diff --git a/sony_custom_layers/pytorch/object_detection/nms_onnx.py b/sony_custom_layers/pytorch/object_detection/nms_onnx.py index d514a77..83e70a8 100644 --- a/sony_custom_layers/pytorch/object_detection/nms_onnx.py +++ b/sony_custom_layers/pytorch/object_detection/nms_onnx.py @@ -15,7 +15,7 @@ # ----------------------------------------------------------------------------- import torch -from .nms import MULTICLASS_NMS_TORCH_OP +from .nms import MULTICLASS_NMS_TORCH_OP_QUALNAME MULTICLASS_NMS_ONNX_OP = "Sony::MultiClassNMS" @@ -42,4 +42,4 @@ def multiclass_nms_onnx(g, boxes, scores, score_threshold, iou_threshold, max_de return outputs -torch.onnx.register_custom_op_symbolic(MULTICLASS_NMS_TORCH_OP, multiclass_nms_onnx, opset_version=1) +torch.onnx.register_custom_op_symbolic(MULTICLASS_NMS_TORCH_OP_QUALNAME, multiclass_nms_onnx, opset_version=1) diff --git a/sony_custom_layers/pytorch/tests/object_detection/test_multiclass_nms.py b/sony_custom_layers/pytorch/tests/object_detection/test_multiclass_nms.py index 5df1f76..d88e4d2 100644 --- a/sony_custom_layers/pytorch/tests/object_detection/test_multiclass_nms.py +++ b/sony_custom_layers/pytorch/tests/object_detection/test_multiclass_nms.py @@ -25,6 +25,7 @@ from sony_custom_layers.pytorch.object_detection import nms from sony_custom_layers.pytorch import load_custom_ops +from sony_custom_layers.util.import_util import is_compatible from sony_custom_layers.util.test_util import exec_in_clean_process @@ -261,6 +262,7 @@ def test_ort(self, dynamic_batch, tmpdir_factory): """ exec_in_clean_process(code, check=True) + @pytest.mark.skipif(not is_compatible('torch>=2.2'), reason='unsupported') def test_pt2_export(self, tmpdir_factory): def f(boxes, scores): diff --git a/sony_custom_layers/util/import_util.py b/sony_custom_layers/util/import_util.py index b6bf03b..3ad9d5e 100644 --- a/sony_custom_layers/util/import_util.py +++ b/sony_custom_layers/util/import_util.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ----------------------------------------------------------------------------- -from typing import List +from typing import List, Union from packaging.requirements import Requirement from packaging.version import parse @@ -24,9 +24,9 @@ class RequirementError(Exception): pass -def check_pip_requirements(requirements: List[str]): +def validate_pip_requirements(requirements: List[str]): """ - Check if all requirements are installed and meet the version specifications. + Validate that all requirements are installed and meet the version specifications. Args: requirements: a list of pip-style requirement strings @@ -47,3 +47,20 @@ def check_pip_requirements(requirements: List[str]): error += f'\nRequired {req_str}, installed version {installed_ver}' if error: raise RequirementError(error) + + +def is_compatible(requirements: Union[str, List]) -> bool: + """ + Non-raising requirement(s) check + Args: + requirements (str, List): requirement pip-style string or a list of requirement strings + + Returns: + (bool) whether requirement(s) are satisfied + """ + requirements = [requirements] if isinstance(requirements, str) else requirements + try: + validate_pip_requirements(requirements) + except RequirementError: + return False + return True