extension.py 7.39 KB
Newer Older
1
import distutils.sysconfig
2
import os
moto's avatar
moto committed
3
4
5
6
import platform
import subprocess
from pathlib import Path

7
import torch
moto's avatar
moto committed
8
9
from setuptools import Extension
from setuptools.command.build_ext import build_ext
10
from torch.utils.cpp_extension import CUDA_HOME
moto's avatar
moto committed
11
12

__all__ = [
13
14
    "get_ext_modules",
    "CMakeBuild",
moto's avatar
moto committed
15
16
17
18
]

_THIS_DIR = Path(__file__).parent.resolve()
_ROOT_DIR = _THIS_DIR.parent.parent.resolve()
19
_TORCHAUDIO_DIR = _ROOT_DIR / "torchaudio"
moto's avatar
moto committed
20

21

22
23
24
25
def _get_build(var, default=False):
    if var not in os.environ:
        return default

26
27
28
    val = os.environ.get(var, "0")
    trues = ["1", "true", "TRUE", "on", "ON", "yes", "YES"]
    falses = ["0", "false", "FALSE", "off", "OFF", "no", "NO"]
29
30
31
    if val in trues:
        return True
    if val not in falses:
32
        print(f"WARNING: Unexpected environment variable value `{var}={val}`. " f"Expected one of {trues + falses}")
33
34
35
    return False


36
_BUILD_SOX = False if platform.system() == "Windows" else _get_build("BUILD_SOX", True)
37
_BUILD_RIR = _get_build("BUILD_RIR", True)
38
_BUILD_RNNT = _get_build("BUILD_RNNT", True)
39
_USE_FFMPEG = _get_build("USE_FFMPEG", False)
moto's avatar
moto committed
40
_DLOPEN_FFMPEG = _get_build("DLOPEN_FFMPEG", False)
41
_USE_ROCM = _get_build("USE_ROCM", torch.backends.cuda.is_built() and torch.version.hip is not None)
42
_USE_CUDA = _get_build("USE_CUDA", torch.backends.cuda.is_built() and torch.version.hip is None)
43
_BUILD_ALIGN = _get_build("BUILD_ALIGN", True)
Yuekai Zhang's avatar
Yuekai Zhang committed
44
_BUILD_CUDA_CTC_DECODER = _get_build("BUILD_CUDA_CTC_DECODER", _USE_CUDA)
45
46
_USE_OPENMP = _get_build("USE_OPENMP", True) and "ATen parallel backend: OpenMP" in torch.__config__.parallel_info()
_TORCH_CUDA_ARCH_LIST = os.environ.get("TORCH_CUDA_ARCH_LIST", None)
moto's avatar
moto committed
47
48


moto's avatar
moto committed
49
def get_ext_modules():
50
    modules = [
51
        Extension(name="torchaudio.lib.libtorchaudio", sources=[]),
52
        Extension(name="torchaudio.lib._torchaudio", sources=[]),
53
    ]
54
55
56
57
58
59
60
    if _BUILD_SOX:
        modules.extend(
            [
                Extension(name="torchaudio.lib.libtorchaudio_sox", sources=[]),
                Extension(name="torchaudio.lib._torchaudio_sox", sources=[]),
            ]
        )
Yuekai Zhang's avatar
Yuekai Zhang committed
61
62
63
64
65
66
67
    if _BUILD_CUDA_CTC_DECODER:
        modules.extend(
            [
                Extension(name="torchaudio.lib.libctc_prefix_decoder", sources=[]),
                Extension(name="torchaudio.lib.pybind11_prefixctc", sources=[]),
            ]
        )
68
    if _USE_FFMPEG:
69
70
71
        modules.extend(
            [
                Extension(name="torchaudio.lib.libtorchaudio_ffmpeg", sources=[]),
72
                Extension(name="torchaudio.lib._torchaudio_ffmpeg", sources=[]),
73
74
            ]
        )
75
    return modules
moto's avatar
moto committed
76
77


moto's avatar
moto committed
78
79
80
81
82
# Based off of
# https://github.com/pybind/cmake_example/blob/580c5fd29d4651db99d8874714b07c0c49a53f8a/setup.py
class CMakeBuild(build_ext):
    def run(self):
        try:
83
            subprocess.check_output(["cmake", "--version"])
moto's avatar
moto committed
84
        except OSError:
85
            raise RuntimeError("CMake is not available.") from None
moto's avatar
moto committed
86
        super().run()
moto's avatar
moto committed
87
88

    def build_extension(self, ext):
89
90
91
92
93
94
        # Since two library files (libtorchaudio and _torchaudio) need to be
        # recognized by setuptools, we instantiate `Extension` twice. (see `get_ext_modules`)
        # This leads to the situation where this `build_extension` method is called twice.
        # However, the following `cmake` command will build all of them at the same time,
        # so, we do not need to perform `cmake` twice.
        # Therefore we call `cmake` only for `torchaudio._torchaudio`.
95
        if ext.name != "torchaudio.lib.libtorchaudio":
96
97
            return

98
99
100
101
102
103
104
        # Note:
        # the last part "lib" does not really matter. We want to get the full path of
        # the root build directory. Passing "torchaudio" will be interpreted as
        # `torchaudio.[so|dylib|pyd]`, so we need something `torchaudio.foo`, that is
        # interpreted as `torchaudio/foo.so` then use dirname to get the `torchaudio`
        # directory.
        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath("torchaudio.lib")))
moto's avatar
moto committed
105
106
107
108
109
110
111
112
113
114
115

        # required for auto-detection of auxiliary "native" libs
        if not extdir.endswith(os.path.sep):
            extdir += os.path.sep

        cfg = "Debug" if self.debug else "Release"

        cmake_args = [
            f"-DCMAKE_BUILD_TYPE={cfg}",
            f"-DCMAKE_PREFIX_PATH={torch.utils.cmake_prefix_path}",
            f"-DCMAKE_INSTALL_PREFIX={extdir}",
116
            "-DCMAKE_VERBOSE_MAKEFILE=ON",
moto's avatar
moto committed
117
118
            f"-DPython_INCLUDE_DIR={distutils.sysconfig.get_python_inc()}",
            f"-DBUILD_SOX:BOOL={'ON' if _BUILD_SOX else 'OFF'}",
119
            f"-DBUILD_RIR:BOOL={'ON' if _BUILD_RIR else 'OFF'}",
120
            f"-DBUILD_RNNT:BOOL={'ON' if _BUILD_RNNT else 'OFF'}",
121
            f"-DBUILD_ALIGN:BOOL={'ON' if _BUILD_ALIGN else 'OFF'}",
Yuekai Zhang's avatar
Yuekai Zhang committed
122
            f"-DBUILD_CUDA_CTC_DECODER:BOOL={'ON' if _BUILD_CUDA_CTC_DECODER else 'OFF'}",
moto's avatar
moto committed
123
            "-DBUILD_TORCHAUDIO_PYTHON_EXTENSION:BOOL=ON",
124
            f"-DUSE_ROCM:BOOL={'ON' if _USE_ROCM else 'OFF'}",
Caroline Chen's avatar
Caroline Chen committed
125
            f"-DUSE_CUDA:BOOL={'ON' if _USE_CUDA else 'OFF'}",
moto's avatar
moto committed
126
            f"-DUSE_OPENMP:BOOL={'ON' if _USE_OPENMP else 'OFF'}",
127
            f"-DUSE_FFMPEG:BOOL={'ON' if _USE_FFMPEG else 'OFF'}",
128
            f"-DDLOPEN_FFMPEG:BOOL={'ON' if _DLOPEN_FFMPEG else 'OFF'}",
moto's avatar
moto committed
129
        ]
130
        build_args = ["--target", "install"]
131
132
133
134
        # Pass CUDA architecture to cmake
        if _TORCH_CUDA_ARCH_LIST is not None:
            # Convert MAJOR.MINOR[+PTX] list to new style one
            # defined at https://cmake.org/cmake/help/latest/prop_tgt/CUDA_ARCHITECTURES.html
135
            _arches = _TORCH_CUDA_ARCH_LIST.replace(".", "").replace(" ", ";").split(";")
136
137
            _arches = [arch[:-4] if arch.endswith("+PTX") else f"{arch}-real" for arch in _arches]
            cmake_args += [f"-DCMAKE_CUDA_ARCHITECTURES={';'.join(_arches)}"]
moto's avatar
moto committed
138

139
140
141
142
        if platform.system() != "Windows" and CUDA_HOME is not None:
            cmake_args += [f"-DCMAKE_CUDA_COMPILER='{CUDA_HOME}/bin/nvcc'"]
            cmake_args += [f"-DCUDA_TOOLKIT_ROOT_DIR='{CUDA_HOME}'"]

moto's avatar
moto committed
143
        # Default to Ninja
144
        if "CMAKE_GENERATOR" not in os.environ or platform.system() == "Windows":
moto's avatar
moto committed
145
            cmake_args += ["-GNinja"]
146
        if platform.system() == "Windows":
147
            import sys
148

149
150
151
152
153
154
            python_version = sys.version_info
            cmake_args += [
                "-DCMAKE_C_COMPILER=cl",
                "-DCMAKE_CXX_COMPILER=cl",
                f"-DPYTHON_VERSION={python_version.major}.{python_version.minor}",
            ]
moto's avatar
moto committed
155
156
157
158
159
160
161
162
163
164
165
166
167

        # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
        # across all generators.
        if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
            # self.parallel is a Python 3 only way to set parallel jobs by hand
            # using -j in the build_ext call, not supported by pip or PyPA-build.
            if hasattr(self, "parallel") and self.parallel:
                # CMake 3.12+ only.
                build_args += ["-j{}".format(self.parallel)]

        if not os.path.exists(self.build_temp):
            os.makedirs(self.build_temp)

168
169
        subprocess.check_call(["cmake", str(_ROOT_DIR)] + cmake_args, cwd=self.build_temp)
        subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=self.build_temp)
moto's avatar
moto committed
170
171
172

    def get_ext_filename(self, fullname):
        ext_filename = super().get_ext_filename(fullname)
173
        ext_filename_parts = ext_filename.split(".")
moto's avatar
moto committed
174
        without_abi = ext_filename_parts[:-2] + ext_filename_parts[-1:]
175
        ext_filename = ".".join(without_abi)
moto's avatar
moto committed
176
        return ext_filename