vision
/
setup.py
597 строк · 23.1 Кб
1import distutils.command.clean2import distutils.spawn3import glob4import os5import shutil6import subprocess7import sys8import warnings9from pathlib import Path10
11import torch12from pkg_resources import DistributionNotFound, get_distribution, parse_version13from setuptools import find_packages, setup14from torch.utils.cpp_extension import BuildExtension, CppExtension, CUDA_HOME, CUDAExtension, ROCM_HOME15
16FORCE_CUDA = os.getenv("FORCE_CUDA", "0") == "1"17FORCE_MPS = os.getenv("FORCE_MPS", "0") == "1"18DEBUG = os.getenv("DEBUG", "0") == "1"19USE_PNG = os.getenv("TORCHVISION_USE_PNG", "1") == "1"20USE_JPEG = os.getenv("TORCHVISION_USE_JPEG", "1") == "1"21USE_WEBP = os.getenv("TORCHVISION_USE_WEBP", "1") == "1"22USE_HEIC = os.getenv("TORCHVISION_USE_HEIC", "0") == "1" # TODO enable by default!23USE_AVIF = os.getenv("TORCHVISION_USE_AVIF", "0") == "1" # TODO enable by default!24USE_NVJPEG = os.getenv("TORCHVISION_USE_NVJPEG", "1") == "1"25NVCC_FLAGS = os.getenv("NVCC_FLAGS", None)26# Note: the GPU video decoding stuff used to be called "video codec", which
27# isn't an accurate or descriptive name considering there are at least 2 other
28# video deocding backends in torchvision. I'm renaming this to "gpu video
29# decoder" where possible, keeping user facing names (like the env var below) to
30# the old scheme for BC.
31USE_GPU_VIDEO_DECODER = os.getenv("TORCHVISION_USE_VIDEO_CODEC", "1") == "1"32# Same here: "use ffmpeg" was used to denote "use cpu video decoder".
33USE_CPU_VIDEO_DECODER = os.getenv("TORCHVISION_USE_FFMPEG", "1") == "1"34
35TORCHVISION_INCLUDE = os.environ.get("TORCHVISION_INCLUDE", "")36TORCHVISION_LIBRARY = os.environ.get("TORCHVISION_LIBRARY", "")37TORCHVISION_INCLUDE = TORCHVISION_INCLUDE.split(os.pathsep) if TORCHVISION_INCLUDE else []38TORCHVISION_LIBRARY = TORCHVISION_LIBRARY.split(os.pathsep) if TORCHVISION_LIBRARY else []39
40ROOT_DIR = Path(__file__).absolute().parent41CSRS_DIR = ROOT_DIR / "torchvision/csrc"42IS_ROCM = (torch.version.hip is not None) and (ROCM_HOME is not None)43BUILD_CUDA_SOURCES = (torch.cuda.is_available() and ((CUDA_HOME is not None) or IS_ROCM)) or FORCE_CUDA44
45PACKAGE_NAME = "torchvision"46
47print("Torchvision build configuration:")48print(f"{FORCE_CUDA = }")49print(f"{FORCE_MPS = }")50print(f"{DEBUG = }")51print(f"{USE_PNG = }")52print(f"{USE_JPEG = }")53print(f"{USE_WEBP = }")54print(f"{USE_HEIC = }")55print(f"{USE_AVIF = }")56print(f"{USE_NVJPEG = }")57print(f"{NVCC_FLAGS = }")58print(f"{USE_CPU_VIDEO_DECODER = }")59print(f"{USE_GPU_VIDEO_DECODER = }")60print(f"{TORCHVISION_INCLUDE = }")61print(f"{TORCHVISION_LIBRARY = }")62print(f"{IS_ROCM = }")63print(f"{BUILD_CUDA_SOURCES = }")64
65
66def get_version():67with open(ROOT_DIR / "version.txt") as f:68version = f.readline().strip()69sha = "Unknown"70
71try:72sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=str(ROOT_DIR)).decode("ascii").strip()73except Exception:74pass75
76if os.getenv("BUILD_VERSION"):77version = os.getenv("BUILD_VERSION")78elif sha != "Unknown":79version += "+" + sha[:7]80
81return version, sha82
83
84def write_version_file(version, sha):85# Exists for BC, probably completely useless.86with open(ROOT_DIR / "torchvision/version.py", "w") as f:87f.write(f"__version__ = '{version}'\n")88f.write(f"git_version = {repr(sha)}\n")89f.write("from torchvision.extension import _check_cuda_version\n")90f.write("if _check_cuda_version() > 0:\n")91f.write(" cuda = _check_cuda_version()\n")92
93
94def get_requirements():95def get_dist(pkgname):96try:97return get_distribution(pkgname)98except DistributionNotFound:99return None100
101pytorch_dep = "torch"102if os.getenv("PYTORCH_VERSION"):103pytorch_dep += "==" + os.getenv("PYTORCH_VERSION")104
105requirements = [106"numpy",107pytorch_dep,108]109
110# Excluding 8.3.* because of https://github.com/pytorch/vision/issues/4934111pillow_ver = " >= 5.3.0, !=8.3.*"112pillow_req = "pillow-simd" if get_dist("pillow-simd") is not None else "pillow"113requirements.append(pillow_req + pillow_ver)114
115return requirements116
117
118def get_macros_and_flags():119define_macros = []120extra_compile_args = {"cxx": []}121if BUILD_CUDA_SOURCES:122if IS_ROCM:123define_macros += [("WITH_HIP", None)]124nvcc_flags = []125else:126define_macros += [("WITH_CUDA", None)]127if NVCC_FLAGS is None:128nvcc_flags = []129else:130nvcc_flags = nvcc_flags.split(" ")131extra_compile_args["nvcc"] = nvcc_flags132
133if sys.platform == "win32":134define_macros += [("torchvision_EXPORTS", None)]135extra_compile_args["cxx"].append("/MP")136
137if DEBUG:138extra_compile_args["cxx"].append("-g")139extra_compile_args["cxx"].append("-O0")140if "nvcc" in extra_compile_args:141# we have to remove "-OX" and "-g" flag if exists and append142nvcc_flags = extra_compile_args["nvcc"]143extra_compile_args["nvcc"] = [f for f in nvcc_flags if not ("-O" in f or "-g" in f)]144extra_compile_args["nvcc"].append("-O0")145extra_compile_args["nvcc"].append("-g")146else:147extra_compile_args["cxx"].append("-g0")148
149return define_macros, extra_compile_args150
151
152def make_C_extension():153print("Building _C extension")154
155sources = (156list(CSRS_DIR.glob("*.cpp"))157+ list(CSRS_DIR.glob("ops/*.cpp"))158+ list(CSRS_DIR.glob("ops/autocast/*.cpp"))159+ list(CSRS_DIR.glob("ops/autograd/*.cpp"))160+ list(CSRS_DIR.glob("ops/cpu/*.cpp"))161+ list(CSRS_DIR.glob("ops/quantized/cpu/*.cpp"))162)163mps_sources = list(CSRS_DIR.glob("ops/mps/*.mm"))164
165if IS_ROCM:166from torch.utils.hipify import hipify_python167
168hipify_python.hipify(169project_directory=str(ROOT_DIR),170output_directory=str(ROOT_DIR),171includes="torchvision/csrc/ops/cuda/*",172show_detailed=True,173is_pytorch_extension=True,174)175cuda_sources = list(CSRS_DIR.glob("ops/hip/*.hip"))176for header in CSRS_DIR.glob("ops/cuda/*.h"):177shutil.copy(str(header), str(CSRS_DIR / "ops/hip"))178else:179cuda_sources = list(CSRS_DIR.glob("ops/cuda/*.cu"))180
181if BUILD_CUDA_SOURCES:182Extension = CUDAExtension183sources += cuda_sources184else:185Extension = CppExtension186if torch.backends.mps.is_available() or FORCE_MPS:187sources += mps_sources188
189define_macros, extra_compile_args = get_macros_and_flags()190return Extension(191name="torchvision._C",192sources=sorted(str(s) for s in sources),193include_dirs=[CSRS_DIR],194define_macros=define_macros,195extra_compile_args=extra_compile_args,196)197
198
199def find_libpng():200# Returns (found, include dir, library dir, library name)201if sys.platform in ("linux", "darwin"):202libpng_config = shutil.which("libpng-config")203if libpng_config is None:204warnings.warn("libpng-config not found")205return False, None, None, None206min_version = parse_version("1.6.0")207png_version = parse_version(208subprocess.run([libpng_config, "--version"], stdout=subprocess.PIPE).stdout.strip().decode("utf-8")209)210if png_version < min_version:211warnings.warn("libpng version {png_version} is less than minimum required version {min_version}")212return False, None, None, None213
214include_dir = (215subprocess.run([libpng_config, "--I_opts"], stdout=subprocess.PIPE)216.stdout.strip()217.decode("utf-8")218.split("-I")[1]219)220library_dir = subprocess.run([libpng_config, "--libdir"], stdout=subprocess.PIPE).stdout.strip().decode("utf-8")221library = "png"222else: # Windows223pngfix = shutil.which("pngfix")224if pngfix is None:225warnings.warn("pngfix not found")226return False, None, None, None227pngfix_dir = Path(pngfix).absolute().parent.parent228
229library_dir = str(pngfix_dir / "lib")230include_dir = str(pngfix_dir / "include/libpng16")231library = "libpng"232
233return True, include_dir, library_dir, library234
235
236def find_library(header):237# returns (found, include dir, library dir)238# if include dir or library dir is None, it means that the library is in239# standard paths and don't need to be added to compiler / linker search240# paths241
242searching_for = f"Searching for {header}"243
244for folder in TORCHVISION_INCLUDE:245if (Path(folder) / header).exists():246print(f"{searching_for} in {Path(folder) / header}. Found in TORCHVISION_INCLUDE.")247return True, None, None248print(f"{searching_for}. Didn't find in TORCHVISION_INCLUDE.")249
250# Try conda-related prefixes. If BUILD_PREFIX is set it means conda-build is251# being run. If CONDA_PREFIX is set then we're in a conda environment.252for prefix_env_var in ("BUILD_PREFIX", "CONDA_PREFIX"):253if (prefix := os.environ.get(prefix_env_var)) is not None:254prefix = Path(prefix)255if sys.platform == "win32":256prefix = prefix / "Library"257include_dir = prefix / "include"258library_dir = prefix / "lib"259if (include_dir / header).exists():260print(f"{searching_for}. Found in {prefix_env_var}.")261return True, str(include_dir), str(library_dir)262print(f"{searching_for}. Didn't find in {prefix_env_var}.")263
264if sys.platform == "linux":265for prefix in (Path("/usr/include"), Path("/usr/local/include")):266if (prefix / header).exists():267print(f"{searching_for}. Found in {prefix}.")268return True, None, None269print(f"{searching_for}. Didn't find in {prefix}")270
271return False, None, None272
273
274def make_image_extension():275print("Building image extension")276
277include_dirs = TORCHVISION_INCLUDE.copy()278library_dirs = TORCHVISION_LIBRARY.copy()279
280libraries = []281define_macros, extra_compile_args = get_macros_and_flags()282
283image_dir = CSRS_DIR / "io/image"284sources = list(image_dir.glob("*.cpp")) + list(image_dir.glob("cpu/*.cpp")) + list(image_dir.glob("cpu/giflib/*.c"))285
286if IS_ROCM:287sources += list(image_dir.glob("hip/*.cpp"))288# we need to exclude this in favor of the hipified source289sources.remove(image_dir / "image.cpp")290else:291sources += list(image_dir.glob("cuda/*.cpp"))292
293Extension = CppExtension294
295if USE_PNG:296png_found, png_include_dir, png_library_dir, png_library = find_libpng()297if png_found:298print("Building torchvision with PNG support")299print(f"{png_include_dir = }")300print(f"{png_library_dir = }")301include_dirs.append(png_include_dir)302library_dirs.append(png_library_dir)303libraries.append(png_library)304define_macros += [("PNG_FOUND", 1)]305else:306warnings.warn("Building torchvision without PNG support")307
308if USE_JPEG:309jpeg_found, jpeg_include_dir, jpeg_library_dir = find_library(header="jpeglib.h")310if jpeg_found:311print("Building torchvision with JPEG support")312print(f"{jpeg_include_dir = }")313print(f"{jpeg_library_dir = }")314if jpeg_include_dir is not None and jpeg_library_dir is not None:315# if those are None it means they come from standard paths that are already in the search paths, which we don't need to re-add.316include_dirs.append(jpeg_include_dir)317library_dirs.append(jpeg_library_dir)318libraries.append("jpeg")319define_macros += [("JPEG_FOUND", 1)]320else:321warnings.warn("Building torchvision without JPEG support")322
323if USE_WEBP:324webp_found, webp_include_dir, webp_library_dir = find_library(header="webp/decode.h")325if webp_found:326print("Building torchvision with WEBP support")327print(f"{webp_include_dir = }")328print(f"{webp_library_dir = }")329if webp_include_dir is not None and webp_library_dir is not None:330# if those are None it means they come from standard paths that are already in the search paths, which we don't need to re-add.331include_dirs.append(webp_include_dir)332library_dirs.append(webp_library_dir)333webp_library = "libwebp" if sys.platform == "win32" else "webp"334libraries.append(webp_library)335define_macros += [("WEBP_FOUND", 1)]336else:337warnings.warn("Building torchvision without WEBP support")338
339if USE_HEIC:340heic_found, heic_include_dir, heic_library_dir = find_library(header="libheif/heif.h")341if heic_found:342print("Building torchvision with HEIC support")343print(f"{heic_include_dir = }")344print(f"{heic_library_dir = }")345if heic_include_dir is not None and heic_library_dir is not None:346# if those are None it means they come from standard paths that are already in the search paths, which we don't need to re-add.347include_dirs.append(heic_include_dir)348library_dirs.append(heic_library_dir)349libraries.append("heif")350define_macros += [("HEIC_FOUND", 1)]351else:352warnings.warn("Building torchvision without HEIC support")353
354if USE_AVIF:355avif_found, avif_include_dir, avif_library_dir = find_library(header="avif/avif.h")356if avif_found:357print("Building torchvision with AVIF support")358print(f"{avif_include_dir = }")359print(f"{avif_library_dir = }")360if avif_include_dir is not None and avif_library_dir is not None:361# if those are None it means they come from standard paths that are already in the search paths, which we don't need to re-add.362include_dirs.append(avif_include_dir)363library_dirs.append(avif_library_dir)364libraries.append("avif")365define_macros += [("AVIF_FOUND", 1)]366else:367warnings.warn("Building torchvision without AVIF support")368
369if USE_NVJPEG and torch.cuda.is_available():370nvjpeg_found = CUDA_HOME is not None and (Path(CUDA_HOME) / "include/nvjpeg.h").exists()371
372if nvjpeg_found:373print("Building torchvision with NVJPEG image support")374libraries.append("nvjpeg")375define_macros += [("NVJPEG_FOUND", 1)]376Extension = CUDAExtension377else:378warnings.warn("Building torchvision without NVJPEG support")379
380return Extension(381name="torchvision.image",382sources=sorted(str(s) for s in sources),383include_dirs=include_dirs,384library_dirs=library_dirs,385define_macros=define_macros,386libraries=libraries,387extra_compile_args=extra_compile_args,388)389
390
391def make_video_decoders_extensions():392print("Building video decoder extensions")393
394build_without_extensions_msg = "Building without video decoders extensions."395if sys.platform != "linux" or (sys.version_info.major == 3 and sys.version_info.minor == 9):396# FIXME: Building torchvision with ffmpeg on MacOS or with Python 3.9397# FIXME: causes crash. See the following GitHub issues for more details.398# FIXME: https://github.com/pytorch/pytorch/issues/65000399# FIXME: https://github.com/pytorch/vision/issues/3367400print("Can only build video decoder extensions on linux and Python != 3.9")401return []402
403ffmpeg_exe = shutil.which("ffmpeg")404if ffmpeg_exe is None:405print(f"{build_without_extensions_msg} Couldn't find ffmpeg binary.")406return []407
408def find_ffmpeg_libraries():409ffmpeg_libraries = {"libavcodec", "libavformat", "libavutil", "libswresample", "libswscale"}410
411ffmpeg_bin = os.path.dirname(ffmpeg_exe)412ffmpeg_root = os.path.dirname(ffmpeg_bin)413ffmpeg_include_dir = os.path.join(ffmpeg_root, "include")414ffmpeg_library_dir = os.path.join(ffmpeg_root, "lib")415
416gcc = os.environ.get("CC", shutil.which("gcc"))417platform_tag = subprocess.run([gcc, "-print-multiarch"], stdout=subprocess.PIPE)418platform_tag = platform_tag.stdout.strip().decode("utf-8")419
420if platform_tag:421# Most probably a Debian-based distribution422ffmpeg_include_dir = [ffmpeg_include_dir, os.path.join(ffmpeg_include_dir, platform_tag)]423ffmpeg_library_dir = [ffmpeg_library_dir, os.path.join(ffmpeg_library_dir, platform_tag)]424else:425ffmpeg_include_dir = [ffmpeg_include_dir]426ffmpeg_library_dir = [ffmpeg_library_dir]427
428for library in ffmpeg_libraries:429library_found = False430for search_path in ffmpeg_include_dir + TORCHVISION_INCLUDE:431full_path = os.path.join(search_path, library, "*.h")432library_found |= len(glob.glob(full_path)) > 0433
434if not library_found:435print(f"{build_without_extensions_msg}")436print(f"{library} header files were not found.")437return None, None438
439return ffmpeg_include_dir, ffmpeg_library_dir440
441ffmpeg_include_dir, ffmpeg_library_dir = find_ffmpeg_libraries()442if ffmpeg_include_dir is None or ffmpeg_library_dir is None:443return []444
445print("Found ffmpeg:")446print(f" ffmpeg include path: {ffmpeg_include_dir}")447print(f" ffmpeg library_dir: {ffmpeg_library_dir}")448
449extensions = []450if USE_CPU_VIDEO_DECODER:451print("Building with CPU video decoder support")452
453# TorchVision base decoder + video reader454video_reader_src_dir = os.path.join(ROOT_DIR, "torchvision", "csrc", "io", "video_reader")455video_reader_src = glob.glob(os.path.join(video_reader_src_dir, "*.cpp"))456base_decoder_src_dir = os.path.join(ROOT_DIR, "torchvision", "csrc", "io", "decoder")457base_decoder_src = glob.glob(os.path.join(base_decoder_src_dir, "*.cpp"))458# Torchvision video API459videoapi_src_dir = os.path.join(ROOT_DIR, "torchvision", "csrc", "io", "video")460videoapi_src = glob.glob(os.path.join(videoapi_src_dir, "*.cpp"))461# exclude tests462base_decoder_src = [x for x in base_decoder_src if "_test.cpp" not in x]463
464combined_src = video_reader_src + base_decoder_src + videoapi_src465
466extensions.append(467CppExtension(468# This is an aweful name. It should be "cpu_video_decoder". Keeping for BC.469"torchvision.video_reader",470combined_src,471include_dirs=[472base_decoder_src_dir,473video_reader_src_dir,474videoapi_src_dir,475str(CSRS_DIR),476*ffmpeg_include_dir,477*TORCHVISION_INCLUDE,478],479library_dirs=ffmpeg_library_dir + TORCHVISION_LIBRARY,480libraries=[481"avcodec",482"avformat",483"avutil",484"swresample",485"swscale",486],487extra_compile_args=["-std=c++17"] if os.name != "nt" else ["/std:c++17", "/MP"],488extra_link_args=["-std=c++17" if os.name != "nt" else "/std:c++17"],489)490)491
492if USE_GPU_VIDEO_DECODER:493# Locating GPU video decoder headers and libraries494# CUDA_HOME should be set to the cuda root directory.495# TORCHVISION_INCLUDE and TORCHVISION_LIBRARY should include the locations496# to the headers and libraries below497if not (498BUILD_CUDA_SOURCES
499and CUDA_HOME is not None500and any([os.path.exists(os.path.join(folder, "cuviddec.h")) for folder in TORCHVISION_INCLUDE])501and any([os.path.exists(os.path.join(folder, "nvcuvid.h")) for folder in TORCHVISION_INCLUDE])502and any([os.path.exists(os.path.join(folder, "libnvcuvid.so")) for folder in TORCHVISION_LIBRARY])503and any([os.path.exists(os.path.join(folder, "libavcodec", "bsf.h")) for folder in ffmpeg_include_dir])504):505print("Could not find necessary dependencies. Refer the setup.py to check which ones are needed.")506print("Building without GPU video decoder support")507return extensions508print("Building torchvision with GPU video decoder support")509
510gpu_decoder_path = os.path.join(CSRS_DIR, "io", "decoder", "gpu")511gpu_decoder_src = glob.glob(os.path.join(gpu_decoder_path, "*.cpp"))512cuda_libs = os.path.join(CUDA_HOME, "lib64")513cuda_inc = os.path.join(CUDA_HOME, "include")514
515_, extra_compile_args = get_macros_and_flags()516extensions.append(517CUDAExtension(518"torchvision.gpu_decoder",519gpu_decoder_src,520include_dirs=[CSRS_DIR] + TORCHVISION_INCLUDE + [gpu_decoder_path] + [cuda_inc] + ffmpeg_include_dir,521library_dirs=ffmpeg_library_dir + TORCHVISION_LIBRARY + [cuda_libs],522libraries=[523"avcodec",524"avformat",525"avutil",526"swresample",527"swscale",528"nvcuvid",529"cuda",530"cudart",531"z",532"pthread",533"dl",534"nppicc",535],536extra_compile_args=extra_compile_args,537)538)539
540return extensions541
542
543class clean(distutils.command.clean.clean):544def run(self):545with open(".gitignore") as f:546ignores = f.read()547for wildcard in filter(None, ignores.split("\n")):548for filename in glob.glob(wildcard):549try:550os.remove(filename)551except OSError:552shutil.rmtree(filename, ignore_errors=True)553
554# It's an old-style class in Python 2.7...555distutils.command.clean.clean.run(self)556
557
558if __name__ == "__main__":559version, sha = get_version()560write_version_file(version, sha)561
562print(f"Building wheel {PACKAGE_NAME}-{version}")563
564with open("README.md") as f:565readme = f.read()566
567extensions = [568make_C_extension(),569make_image_extension(),570*make_video_decoders_extensions(),571]572
573setup(574name=PACKAGE_NAME,575version=version,576author="PyTorch Core Team",577author_email="soumith@pytorch.org",578url="https://github.com/pytorch/vision",579description="image and video datasets and models for torch deep learning",580long_description=readme,581long_description_content_type="text/markdown",582license="BSD",583packages=find_packages(exclude=("test",)),584package_data={PACKAGE_NAME: ["*.dll", "*.dylib", "*.so", "prototype/datasets/_builtin/*.categories"]},585zip_safe=False,586install_requires=get_requirements(),587extras_require={588"gdown": ["gdown>=4.7.3"],589"scipy": ["scipy"],590},591ext_modules=extensions,592python_requires=">=3.8",593cmdclass={594"build_ext": BuildExtension.with_options(no_python_abi_suffix=True),595"clean": clean,596},597)598