From 9806ba3ffc09cd69985d56f8ae03e05fd463bb48 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Tue, 9 Nov 2021 22:35:13 -0800 Subject: [PATCH 1/5] [Windows] Workaround for loading bundled DLLs Python-3.8+ adds `add_dll_directory` call, see https://docs.python.org/3/whatsnew/3.8.html#ctypes Simulate this behaviour on older versions of Python runtime by calling `LoadLibraryExW` with the appropriate flags Fixes https://github.com/pytorch/vision/issues/4787 --- torchvision/io/image.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/torchvision/io/image.py b/torchvision/io/image.py index f835565016c..b5cb3ee7f37 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -7,8 +7,27 @@ try: lib_path = _get_extension_path("image") + # On Windows Python-3.8+ has `os.add_dll_directory` call, + # which is called from _get_extension_path to configure dll search path + # Condition below adds a workaround for older versions by + # explicitly calling `LoadLibraryExW` with the following flags: + # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) + # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) + import os as _os, _sys as _sys + if _os.name == "nt" and sys.version_info < (3, 8): + + from ctypes.windll import kernel32 as _kernel32 + if hasattr(_kernel32, "LoadLibraryExW"): + _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, + None, + 0x00001100) + else: + import warnings + warnings.warn("LoadLibraryExW is missing in kernel32.dll") torch.ops.load_library(lib_path) -except (ImportError, OSError): +except (ImportError, OSError) as e: + import warnings + warnings.warn(f"Failed to load {global().get('lib_path','image.pyd')}: {e}") pass From 24ee97702c74a76890d8ed23c9a525a4b12b0d61 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Wed, 10 Nov 2021 08:23:36 -0800 Subject: [PATCH 2/5] Address review feedback Get rid of `pass`, import `os`, `sys` and `warnnings` globally, fix `global()`->`globals()` typo --- torchvision/_internally_replaced_utils.py | 2 +- torchvision/io/image.py | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/torchvision/_internally_replaced_utils.py b/torchvision/_internally_replaced_utils.py index 18afc3ed93a..73be76fd922 100644 --- a/torchvision/_internally_replaced_utils.py +++ b/torchvision/_internally_replaced_utils.py @@ -22,7 +22,7 @@ def _is_remote_location_available() -> bool: from torch.utils.model_zoo import load_url as load_state_dict_from_url # noqa: 401 -def _get_extension_path(lib_name): +def _get_extension_path(lib_name) -> str: lib_dir = os.path.dirname(__file__) if os.name == "nt": diff --git a/torchvision/io/image.py b/torchvision/io/image.py index b5cb3ee7f37..50af4002cd5 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -1,4 +1,8 @@ +import ctypes +import os +import sys from enum import Enum +from warnings import warn import torch @@ -13,22 +17,16 @@ # explicitly calling `LoadLibraryExW` with the following flags: # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) - import os as _os, _sys as _sys - if _os.name == "nt" and sys.version_info < (3, 8): + if os.name == "nt" and sys.version_info < (3, 8): - from ctypes.windll import kernel32 as _kernel32 + _kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) if hasattr(_kernel32, "LoadLibraryExW"): - _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, - None, - 0x00001100) + _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, None, 0x00001100) else: - import warnings - warnings.warn("LoadLibraryExW is missing in kernel32.dll") + warn("LoadLibraryExW is missing in kernel32.dll") torch.ops.load_library(lib_path) except (ImportError, OSError) as e: - import warnings - warnings.warn(f"Failed to load {global().get('lib_path','image.pyd')}: {e}") - pass + warn(f"Failed to load {globals().get('lib_path','image.pyd')}: {e}") class ImageReadMode(Enum): From b09f632ff2a8e262cae9a5db4ef570f02f3075c2 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 13 Dec 2021 09:13:08 -0800 Subject: [PATCH 3/5] More review feedback addressed --- torchvision/_internally_replaced_utils.py | 20 +++++++++++++++++++- torchvision/io/_video_opt.py | 5 ++--- torchvision/io/image.py | 18 ++---------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/torchvision/_internally_replaced_utils.py b/torchvision/_internally_replaced_utils.py index 73be76fd922..4c16c19174a 100644 --- a/torchvision/_internally_replaced_utils.py +++ b/torchvision/_internally_replaced_utils.py @@ -22,7 +22,7 @@ def _is_remote_location_available() -> bool: from torch.utils.model_zoo import load_url as load_state_dict_from_url # noqa: 401 -def _get_extension_path(lib_name) -> str: +def _get_extension_path(lib_name: str) -> str: lib_dir = os.path.dirname(__file__) if os.name == "nt": @@ -56,3 +56,21 @@ def _get_extension_path(lib_name) -> str: raise ImportError return ext_specs.origin + + +def _load_library(lib_name: str) -> None: + lib_path = _get_extension_path(lib_name) + # On Windows Python-3.8+ has `os.add_dll_directory` call, + # which is called from _get_extension_path to configure dll search path + # Condition below adds a workaround for older versions by + # explicitly calling `LoadLibraryExW` with the following flags: + # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) + # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) + if os.name == "nt" and sys.version_info < (3, 8): + + _kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) + if hasattr(_kernel32, "LoadLibraryExW"): + _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, None, 0x00001100) + else: + warn("LoadLibraryExW is missing in kernel32.dll") + torch.ops.load_library(lib_path) diff --git a/torchvision/io/_video_opt.py b/torchvision/io/_video_opt.py index 45bec44ec61..90126da8993 100644 --- a/torchvision/io/_video_opt.py +++ b/torchvision/io/_video_opt.py @@ -5,12 +5,11 @@ import torch -from .._internally_replaced_utils import _get_extension_path +from .._internally_replaced_utils import _load_library try: - lib_path = _get_extension_path("video_reader") - torch.ops.load_library(lib_path) + _load_library("video_reader") _HAS_VIDEO_OPT = True except (ImportError, OSError): _HAS_VIDEO_OPT = False diff --git a/torchvision/io/image.py b/torchvision/io/image.py index 50af4002cd5..6d92a9d8c32 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -6,25 +6,11 @@ import torch -from .._internally_replaced_utils import _get_extension_path +from .._internally_replaced_utils import _load_library try: - lib_path = _get_extension_path("image") - # On Windows Python-3.8+ has `os.add_dll_directory` call, - # which is called from _get_extension_path to configure dll search path - # Condition below adds a workaround for older versions by - # explicitly calling `LoadLibraryExW` with the following flags: - # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) - # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) - if os.name == "nt" and sys.version_info < (3, 8): - - _kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) - if hasattr(_kernel32, "LoadLibraryExW"): - _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, None, 0x00001100) - else: - warn("LoadLibraryExW is missing in kernel32.dll") - torch.ops.load_library(lib_path) + _load_library("image") except (ImportError, OSError) as e: warn(f"Failed to load {globals().get('lib_path','image.pyd')}: {e}") From 52a6e14f89d146bb76e3aef8d37438bbdeadc116 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 13 Dec 2021 10:00:36 -0800 Subject: [PATCH 4/5] Move `_load_library` to `torchvision.extension` --- torchvision/_internally_replaced_utils.py | 18 ------------------ torchvision/extension.py | 23 +++++++++++++++++++++++ torchvision/io/_video_opt.py | 2 +- torchvision/io/image.py | 7 ++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/torchvision/_internally_replaced_utils.py b/torchvision/_internally_replaced_utils.py index 4c16c19174a..7984ea34d38 100644 --- a/torchvision/_internally_replaced_utils.py +++ b/torchvision/_internally_replaced_utils.py @@ -56,21 +56,3 @@ def _get_extension_path(lib_name: str) -> str: raise ImportError return ext_specs.origin - - -def _load_library(lib_name: str) -> None: - lib_path = _get_extension_path(lib_name) - # On Windows Python-3.8+ has `os.add_dll_directory` call, - # which is called from _get_extension_path to configure dll search path - # Condition below adds a workaround for older versions by - # explicitly calling `LoadLibraryExW` with the following flags: - # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) - # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) - if os.name == "nt" and sys.version_info < (3, 8): - - _kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) - if hasattr(_kernel32, "LoadLibraryExW"): - _image_lib_handle = _kernel32.LoadLibraryExW(lib_path, None, 0x00001100) - else: - warn("LoadLibraryExW is missing in kernel32.dll") - torch.ops.load_library(lib_path) diff --git a/torchvision/extension.py b/torchvision/extension.py index 69f34891837..eaa0d50774a 100644 --- a/torchvision/extension.py +++ b/torchvision/extension.py @@ -1,3 +1,8 @@ +import ctypes +import os +import sys +from warnings import warn + import torch from ._internally_replaced_utils import _get_extension_path @@ -67,4 +72,22 @@ def _check_cuda_version(): return _version +def _load_library(lib_name: str) -> None: + lib_path = _get_extension_path(lib_name) + # On Windows Python-3.8+ has `os.add_dll_directory` call, + # which is called from _get_extension_path to configure dll search path + # Condition below adds a workaround for older versions by + # explicitly calling `LoadLibraryExW` with the following flags: + # - LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x1000) + # - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x100) + if os.name == "nt" and sys.version_info < (3, 8): + _kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) + if hasattr(_kernel32, "LoadLibraryExW"): + _kernel32.LoadLibraryExW(lib_path, None, 0x00001100) + else: + warn("LoadLibraryExW is missing in kernel32.dll") + + torch.ops.load_library(lib_path) + + _check_cuda_version() diff --git a/torchvision/io/_video_opt.py b/torchvision/io/_video_opt.py index 90126da8993..d75dd41534d 100644 --- a/torchvision/io/_video_opt.py +++ b/torchvision/io/_video_opt.py @@ -5,7 +5,7 @@ import torch -from .._internally_replaced_utils import _load_library +from ..extension import _load_library try: diff --git a/torchvision/io/image.py b/torchvision/io/image.py index 6d92a9d8c32..68117c3b7ef 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -1,18 +1,15 @@ -import ctypes -import os -import sys from enum import Enum from warnings import warn import torch -from .._internally_replaced_utils import _load_library +from ..extension import _load_library try: _load_library("image") except (ImportError, OSError) as e: - warn(f"Failed to load {globals().get('lib_path','image.pyd')}: {e}") + warn(f"Failed to load image Python extension: {e}") class ImageReadMode(Enum): From a5babfb428ed417184c5dad9090975cd54b7b60a Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 13 Dec 2021 12:55:16 -0800 Subject: [PATCH 5/5] Remove type annotations to pacify linter --- torchvision/_internally_replaced_utils.py | 2 +- torchvision/extension.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/_internally_replaced_utils.py b/torchvision/_internally_replaced_utils.py index 7984ea34d38..18afc3ed93a 100644 --- a/torchvision/_internally_replaced_utils.py +++ b/torchvision/_internally_replaced_utils.py @@ -22,7 +22,7 @@ def _is_remote_location_available() -> bool: from torch.utils.model_zoo import load_url as load_state_dict_from_url # noqa: 401 -def _get_extension_path(lib_name: str) -> str: +def _get_extension_path(lib_name): lib_dir = os.path.dirname(__file__) if os.name == "nt": diff --git a/torchvision/extension.py b/torchvision/extension.py index eaa0d50774a..ea837c234d3 100644 --- a/torchvision/extension.py +++ b/torchvision/extension.py @@ -72,7 +72,7 @@ def _check_cuda_version(): return _version -def _load_library(lib_name: str) -> None: +def _load_library(lib_name): lib_path = _get_extension_path(lib_name) # On Windows Python-3.8+ has `os.add_dll_directory` call, # which is called from _get_extension_path to configure dll search path