        config.build_dir = os.getenv("NVFUSER_BUILD_DIR")
    if "NVFUSER_BUILD_INSTALL_DIR" in os.environ:
        config.install_dir = os.getenv("NVFUSER_BUILD_INSTALL_DIR")
    if "NVFUSER_BUILD_INSTALL_REQUIRES" in os.environ:
        config.install_requires = os.getenv("NVFUSER_BUILD_INSTALL_REQUIRES").split(",")
    if "NVFUSER_BUILD_EXTRAS_REQUIRE" in os.environ:
        config.extras_require = eval(os.getenv("NVFUSER_BUILD_EXTRAS_REQUIRE"))
    if "NVFUSER_BUILD_CPP_STANDARD" in os.environ:
        config.cpp_standard = int(os.getenv("NVFUSER_BUILD_CPP_STANDARD"))
    if "NVFUSER_BUILD_VERSION_TAG" in os.environ:
        config.overwrite_version = True
        config.version_tag = os.getenv("NVFUSER_BUILD_VERSION_TAG")
    if "NVFUSER_CUTLASS_MAX_JOBS" in os.environ:
        config.cutlass_max_jobs = int(os.getenv("NVFUSER_CUTLASS_MAX_JOBS"))
    if "NVFUSER_BUILD_NVMMH_INCLUDE_DIR" in os.environ:
        config.nvmmh_include_dir = os.getenv("NVFUSER_BUILD_NVMMH_INCLUDE_DIR")
    if "NVFUSER_BUILD_ENABLE_PCH" in os.environ:
        config.enable_pch = get_env_flag_bool("NVFUSER_BUILD_ENABLE_PCH")

def get_default_install_prefix():
    cwd = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(cwd, "nvfuser_common")

class build_ext(setuptools.command.build_ext.build_ext):
    def __init__(self, *args, **kwargs):
        install_dir = kwargs.pop("install_dir", "")
        self.install_dir = install_dir if install_dir else get_default_install_prefix()
        super().__init__(*args, **kwargs)

    def copy_library(self, ext, library_name):
        # Copy files on necessity.
        filename = self.get_ext_filename(self.get_ext_fullname(ext.name))
        fileext = os.path.splitext(filename)[1]

        libnvfuser_path = os.path.join(
            os.path.join(self.install_dir, "lib"), f"{library_name}{fileext}"
        )
        assert os.path.exists(libnvfuser_path)
        install_dst = os.path.join(self.build_lib, filename)
        if not os.path.exists(os.path.dirname(install_dst)):
            os.makedirs(os.path.dirname(install_dst))


    if "NVFUSER_BUILD_EXTRAS_REQUIRE" in os.environ:
        config.extras_require = eval(os.getenv("NVFUSER_BUILD_EXTRAS_REQUIRE"))
    if "NVFUSER_BUILD_CPP_STANDARD" in os.environ:
        config.cpp_standard = int(os.getenv("NVFUSER_BUILD_CPP_STANDARD"))
    if "NVFUSER_BUILD_VERSION_TAG" in os.environ:
        config.overwrite_version = True
        config.version_tag = os.getenv("NVFUSER_BUILD_VERSION_TAG")
    if "NVFUSER_CUTLASS_MAX_JOBS" in os.environ:
        config.cutlass_max_jobs = int(os.getenv("NVFUSER_CUTLASS_MAX_JOBS"))
    if "NVFUSER_BUILD_NVMMH_INCLUDE_DIR" in os.environ:
        config.nvmmh_include_dir = os.getenv("NVFUSER_BUILD_NVMMH_INCLUDE_DIR")
    if "NVFUSER_BUILD_ENABLE_PCH" in os.environ:
        config.enable_pch = get_env_flag_bool("NVFUSER_BUILD_ENABLE_PCH")

def get_default_install_prefix():
    cwd = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(cwd, "nvfuser_common")

class build_ext(setuptools.command.build_ext.build_ext):
    def __init__(self, *args, **kwargs):
        install_dir = kwargs.pop("install_dir", "")
        self.install_dir = install_dir if install_dir else get_default_install_prefix()
        super().__init__(*args, **kwargs)

    def copy_library(self, ext, library_name):
        # Copy files on necessity.
        filename = self.get_ext_filename(self.get_ext_fullname(ext.name))
        fileext = os.path.splitext(filename)[1]

        libnvfuser_path = os.path.join(
            os.path.join(self.install_dir, "lib"), f"{library_name}{fileext}"
        )
        assert os.path.exists(libnvfuser_path)
        install_dst = os.path.join(self.build_lib, filename)
        if not os.path.exists(os.path.dirname(install_dst)):
            os.makedirs(os.path.dirname(install_dst))
        self.copy_file(libnvfuser_path, install_dst)

    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:
            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]


        self.copy_file(libnvfuser_path, install_dst)

    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:
            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]
                # let's worry about multiple license when we see it.
                assert len(license) == 1
                license_entry = os.path.join(root, license[0])
                if project_name in aggregated_license:
                    # Only add it if the license is different
                    aggregated_license[project_name].append(license_entry)
                else:
                    aggregated_license[project_name] = [license_entry]
        return aggregated_license

    def __exit__(self, exception_type, exception_value, traceback):
        # restore original license file
        with open(self.license_file, "w") as f:
            f.write(self.nvfuser_license_txt)

try:
    from wheel.bdist_wheel import bdist_wheel
except ImportError:
    build_whl = None
else:

    class build_whl(bdist_wheel):
        def run(self):
            with concat_third_party_license() as tp_licenses:
                if len(tp_licenses) != 0:
                    with open("LICENSE", "a") as f:
                        f.write("\n\n")
                        f.write(
                            "NVIDIA/fuser depends on libraries with license listed below:"
                        )

                for project_name, license_files in tp_licenses.items():
                    # check all license files are identical


                    identical_flag = all(map(check_file, license_files[1:]))
                    if not identical_flag:
                        raise RuntimeError(
                            "inconsistent license found for project: ",
                            project_name,
                            " check its license files under: ",
                            license_files,
                        )

                    with open("LICENSE", "a") as f:
                        f.write("\n\nProject Name: " + project_name)
                        f.write("\nLicense Files:\n")
                        for file_name in license_files:
                            f.write("\t" + file_name)
                        f.write("\n" + license_ref)

                # generate whl before we restore LICENSE
                super().run()

def get_cmake_bin():
    # TODO: double check cmake version here and retrieve later version if necessary
    return "cmake"

def cmake(config, relative_path):
    from tools.memory import get_available_memory_gb

    # make build directories
    cwd = os.path.dirname(os.path.abspath(__file__))
    cmake_build_dir = (
        os.path.join(cwd, "build") if not config.build_dir else config.build_dir
    )
    if not os.path.exists(cmake_build_dir):
        os.makedirs(cmake_build_dir)

    install_prefix = (
        config.install_dir if config.install_dir else get_default_install_prefix()
    )

    from tools.gen_nvfuser_version import (
        get_pytorch_use_distributed,
    )


cmake

                            " check its license files under: ",
                            license_files,
                        )

                    with open("LICENSE", "a") as f:
                        f.write("\n\nProject Name: " + project_name)
                        f.write("\nLicense Files:\n")
                        for file_name in license_files:
                            f.write("\t" + file_name)
                        f.write("\n" + license_ref)

                # generate whl before we restore LICENSE
                super().run()

def get_cmake_bin():
    # TODO: double check cmake version here and retrieve later version if necessary
    return "cmake"

def cmake(config, relative_path):
    from tools.memory import get_available_memory_gb

    # make build directories
    cwd = os.path.dirname(os.path.abspath(__file__))
    cmake_build_dir = (
        os.path.join(cwd, "build") if not config.build_dir else config.build_dir
    )
    if not os.path.exists(cmake_build_dir):
        os.makedirs(cmake_build_dir)

    install_prefix = (
        config.install_dir if config.install_dir else get_default_install_prefix()
    )

    from tools.gen_nvfuser_version import (
        get_pytorch_use_distributed,
    )

    pytorch_use_distributed = get_pytorch_use_distributed()

    def on_or_off(flag: bool) -> str:
        return "ON" if flag else "OFF"

    # generate cmake directory
    #
    # cmake options are sticky: when -DFOO=... isn't specified, cmake's option
    # FOO prefers the cached value over the default value. This behavior
    # confused me several times (e.g.
    # https://github.com/NVIDIA/Fuser/pull/4319) when I ran `pip install -e`,
    # so I chose to always pass these options even for default values. Doing so
    # explicitly overrides cached values and ensures consistent behavior across
    # clean and dirty builds.
    cmd_str = [
        get_cmake_bin(),
        f"-DCMAKE_BUILD_TYPE={config.build_type}",
        f"-DCMAKE_INSTALL_PREFIX={install_prefix}",
        f"-DNVFUSER_CPP_STANDARD={config.cpp_standard}",
        f"-DUSE_DISTRIBUTED={pytorch_use_distributed}",
        f"-DNVFUSER_BUILD_WITH_ASAN={on_or_off(config.build_with_asan)}",
        f"-DNVFUSER_STANDALONE_BUILD_WITH_UCC={on_or_off(config.build_with_ucc)}",
        f"-DNVFUSER_EXPLICIT_ERROR_CHECK={on_or_off(config.explicit_error_check)}",
        f"-DBUILD_TEST={on_or_off(not config.no_test)}",
        f"-DBUILD_PYTHON={on_or_off(not config.no_python)}",
        f"-DNVFUSER_DISABLE_CUTLASS={on_or_off(config.no_cutlass)}",
        f"-DPython_EXECUTABLE={sys.executable}",
        f"-DBUILD_NVFUSER_BENCHMARK={on_or_off(not config.no_benchmark)}",
        f"-DNVFUSER_DISTRIBUTED={on_or_off(not config.build_without_distributed)}",
        f"-DNVFUSER_USE_PCH={on_or_off(config.enable_pch)}",
        "-B",
        cmake_build_dir,
    ]
    if config.cutlass_max_jobs:
        cmd_str.append(f"-DCUTLASS_MAX_JOBS={config.cutlass_max_jobs}")
    if config.nvmmh_include_dir:
        cmd_str.append(f"-DNVMMH_INCLUDE_DIR={config.nvmmh_include_dir}")
    if not config.no_ninja:
        cmd_str.append("-G")
        cmd_str.append("Ninja")
    cmd_str.append(relative_path)

    print(f"Configuring CMake with {' '.join(cmd_str)}")
    subprocess.check_call(cmd_str)

    max_jobs = multiprocessing.cpu_count()
    mem_gb_per_task = 3  # Currently compilation of nvFuser souce code takes ~3GB of memory per task, we should adjust this value if it changes in the future.
    available_mem = get_available_memory_gb()
    if available_mem > 0:
        max_jobs_mem = int(available_mem / mem_gb_per_task)
        max_jobs = min(max_jobs, max_jobs_mem)

    if not config.cmake_only:
        # build binary
        max_jobs = os.getenv("MAX_JOBS", str(max_jobs))
        print(f"Using {max_jobs} jobs for compilation")
        cmd_str = [
            get_cmake_bin(),
            "--build",
            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):


        max_jobs_mem = int(available_mem / mem_gb_per_task)
        max_jobs = min(max_jobs, max_jobs_mem)

    if not config.cmake_only:
        # build binary
        max_jobs = os.getenv("MAX_JOBS", str(max_jobs))
        print(f"Using {max_jobs} jobs for compilation")
        cmd_str = [
            get_cmake_bin(),
            "--build",
            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):
                    # ignore comment in .gitignore
                    if len(entry) >= 1 and entry[0] != "#":
                        for filename in glob.glob(entry):
                            print("removing: ", filename)
                            try:
                                os.remove(filename)
                            except OSError:
                                shutil.rmtree(filename, ignore_errors=True)

    return clean

def run(config, version_tag, relative_path):
    from setuptools import Extension, setup, find_packages

    # NOTE(crcrpar): Deliberately build basically two dynamic libraries here so that they can
    # be treated as "nvfuser_package_data".
    if config.build_setup:
        cmake(config, relative_path)
    if not config.cmake_only:
        # NOTE: package include files for cmake
        # TODO(crcrpar): Better avoid hardcoding `libnvfuser_codegen.so`
        # might can be treated by using `exclude_package_data`.
        nvfuser_common_package_data = [
            "lib/libnvfuser_codegen.so",
            "lib/libnvf_cutlass.so",
            "include/nvfuser/*.h",
            "include/nvfuser/struct.inl",
            "include/nvfuser/C++20/type_traits",
            "include/nvfuser/device_lower/*.h",


run

            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):
                    # ignore comment in .gitignore
                    if len(entry) >= 1 and entry[0] != "#":
                        for filename in glob.glob(entry):
                            print("removing: ", filename)
                            try:
                                os.remove(filename)
                            except OSError:
                                shutil.rmtree(filename, ignore_errors=True)

    return clean

def run(config, version_tag, relative_path):
    from setuptools import Extension, setup, find_packages

    # NOTE(crcrpar): Deliberately build basically two dynamic libraries here so that they can
    # be treated as "nvfuser_package_data".
    if config.build_setup:
        cmake(config, relative_path)
    if not config.cmake_only:
        # NOTE: package include files for cmake
        # TODO(crcrpar): Better avoid hardcoding `libnvfuser_codegen.so`
        # might can be treated by using `exclude_package_data`.
        nvfuser_common_package_data = [
            "lib/libnvfuser_codegen.so",
            "lib/libnvf_cutlass.so",
            "include/nvfuser/*.h",
            "include/nvfuser/struct.inl",


    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:
            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]
                # let's worry about multiple license when we see it.
                assert len(license) == 1
                license_entry = os.path.join(root, license[0])
                if project_name in aggregated_license:
                    # Only add it if the license is different
                    aggregated_license[project_name].append(license_entry)


        config.version_tag = os.getenv("NVFUSER_BUILD_VERSION_TAG")
    if "NVFUSER_CUTLASS_MAX_JOBS" in os.environ:
        config.cutlass_max_jobs = int(os.getenv("NVFUSER_CUTLASS_MAX_JOBS"))
    if "NVFUSER_BUILD_NVMMH_INCLUDE_DIR" in os.environ:
        config.nvmmh_include_dir = os.getenv("NVFUSER_BUILD_NVMMH_INCLUDE_DIR")
    if "NVFUSER_BUILD_ENABLE_PCH" in os.environ:
        config.enable_pch = get_env_flag_bool("NVFUSER_BUILD_ENABLE_PCH")

def get_default_install_prefix():
    cwd = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(cwd, "nvfuser_common")

class build_ext(setuptools.command.build_ext.build_ext):
    def __init__(self, *args, **kwargs):
        install_dir = kwargs.pop("install_dir", "")
        self.install_dir = install_dir if install_dir else get_default_install_prefix()
        super().__init__(*args, **kwargs)

    def copy_library(self, ext, library_name):
        # Copy files on necessity.
        filename = self.get_ext_filename(self.get_ext_fullname(ext.name))
        fileext = os.path.splitext(filename)[1]

        libnvfuser_path = os.path.join(
            os.path.join(self.install_dir, "lib"), f"{library_name}{fileext}"
        )
        assert os.path.exists(libnvfuser_path)
        install_dst = os.path.join(self.build_lib, filename)
        if not os.path.exists(os.path.dirname(install_dst)):
            os.makedirs(os.path.dirname(install_dst))
        self.copy_file(libnvfuser_path, install_dst)

    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:


class build_ext(setuptools.command.build_ext.build_ext):
    def __init__(self, *args, **kwargs):
        install_dir = kwargs.pop("install_dir", "")
        self.install_dir = install_dir if install_dir else get_default_install_prefix()
        super().__init__(*args, **kwargs)

    def copy_library(self, ext, library_name):
        # Copy files on necessity.
        filename = self.get_ext_filename(self.get_ext_fullname(ext.name))
        fileext = os.path.splitext(filename)[1]

        libnvfuser_path = os.path.join(
            os.path.join(self.install_dir, "lib"), f"{library_name}{fileext}"
        )
        assert os.path.exists(libnvfuser_path)
        install_dst = os.path.join(self.build_lib, filename)
        if not os.path.exists(os.path.dirname(install_dst)):
            os.makedirs(os.path.dirname(install_dst))
        self.copy_file(libnvfuser_path, install_dst)

    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:


        # Copy files on necessity.
        filename = self.get_ext_filename(self.get_ext_fullname(ext.name))
        fileext = os.path.splitext(filename)[1]

        libnvfuser_path = os.path.join(
            os.path.join(self.install_dir, "lib"), f"{library_name}{fileext}"
        )
        assert os.path.exists(libnvfuser_path)
        install_dst = os.path.join(self.build_lib, filename)
        if not os.path.exists(os.path.dirname(install_dst)):
            os.makedirs(os.path.dirname(install_dst))
        self.copy_file(libnvfuser_path, install_dst)

    def build_extension(self, ext):
        if ext.name == "nvfuser_direct._C_DIRECT":
            self.copy_library(ext, "libnvfuser_direct")
            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:
            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]


            self.copy_shared_library("libnvfuser_codegen.so")
        else:
            super().build_extension(ext)

    def copy_shared_library(self, lib_name):
        # Copy shared library to lib/ subdirectory for package data
        src_path = os.path.join(self.install_dir, "lib", lib_name)
        if os.path.exists(src_path):
            dst_dir = os.path.join(self.build_lib, "nvfuser_common", "lib")
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)
            dst_path = os.path.join(dst_dir, lib_name)
            self.copy_file(src_path, dst_path)

class concat_third_party_license:
    def __init__(self, directory="third_party"):
        self.license_file = "LICENSE"
        self.directory = directory

    def __enter__(self):
        # read original license file
        with open(self.license_file, "r") as f:
            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]
                # let's worry about multiple license when we see it.
                assert len(license) == 1
                license_entry = os.path.join(root, license[0])
                if project_name in aggregated_license:
                    # Only add it if the license is different
                    aggregated_license[project_name].append(license_entry)
                else:
                    aggregated_license[project_name] = [license_entry]
        return aggregated_license

    def __exit__(self, exception_type, exception_value, traceback):
        # restore original license file
        with open(self.license_file, "w") as f:
            f.write(self.nvfuser_license_txt)

try:
    from wheel.bdist_wheel import bdist_wheel
except ImportError:
    build_whl = None
else:

    class build_whl(bdist_wheel):
        def run(self):
            with concat_third_party_license() as tp_licenses:
                if len(tp_licenses) != 0:
                    with open("LICENSE", "a") as f:
                        f.write("\n\n")
                        f.write(


            self.nvfuser_license_txt = f.read()

        licenses = {"LICENSE", "LICENSE.txt", "LICENSE.rst", "COPYING.BSD"}

        # aggregated license, we key on project name
        aggregated_license = {}
        for root, dirs, files in os.walk(self.directory):
            license = list(licenses & set(files))
            if license:
                project_name = root.split("/")[-1]
                # let's worry about multiple license when we see it.
                assert len(license) == 1
                license_entry = os.path.join(root, license[0])
                if project_name in aggregated_license:
                    # Only add it if the license is different
                    aggregated_license[project_name].append(license_entry)
                else:
                    aggregated_license[project_name] = [license_entry]
        return aggregated_license

    def __exit__(self, exception_type, exception_value, traceback):
        # restore original license file
        with open(self.license_file, "w") as f:
            f.write(self.nvfuser_license_txt)

try:
    from wheel.bdist_wheel import bdist_wheel
except ImportError:
    build_whl = None
else:

    class build_whl(bdist_wheel):
        def run(self):
            with concat_third_party_license() as tp_licenses:
                if len(tp_licenses) != 0:
                    with open("LICENSE", "a") as f:
                        f.write("\n\n")
                        f.write(
                            "NVIDIA/fuser depends on libraries with license listed below:"
                        )

                for project_name, license_files in tp_licenses.items():
                    # check all license files are identical


                license_entry = os.path.join(root, license[0])
                if project_name in aggregated_license:
                    # Only add it if the license is different
                    aggregated_license[project_name].append(license_entry)
                else:
                    aggregated_license[project_name] = [license_entry]
        return aggregated_license

    def __exit__(self, exception_type, exception_value, traceback):
        # restore original license file
        with open(self.license_file, "w") as f:
            f.write(self.nvfuser_license_txt)

try:
    from wheel.bdist_wheel import bdist_wheel
except ImportError:
    build_whl = None
else:

    class build_whl(bdist_wheel):
        def run(self):
            with concat_third_party_license() as tp_licenses:
                if len(tp_licenses) != 0:
                    with open("LICENSE", "a") as f:
                        f.write("\n\n")
                        f.write(
                            "NVIDIA/fuser depends on libraries with license listed below:"
                        )

                for project_name, license_files in tp_licenses.items():
                    # check all license files are identical
                    with open(license_files[0], "r") as f:
                        license_ref = f.read()

                    def check_file(file_name):
                        with open(file_name, "r") as f:
                            return f.read() == license_ref

                    identical_flag = all(map(check_file, license_files[1:]))
                    if not identical_flag:
                        raise RuntimeError(
                            "inconsistent license found for project: ",
                            project_name,
                            " check its license files under: ",
                            license_files,
                        )

                    with open("LICENSE", "a") as f:
                        f.write("\n\nProject Name: " + project_name)
                        f.write("\nLicense Files:\n")
                        for file_name in license_files:
                            f.write("\t" + file_name)
                        f.write("\n" + license_ref)

                # generate whl before we restore LICENSE
                super().run()

def get_cmake_bin():
    # TODO: double check cmake version here and retrieve later version if necessary
    return "cmake"

def cmake(config, relative_path):
    from tools.memory import get_available_memory_gb

    # make build directories
    cwd = os.path.dirname(os.path.abspath(__file__))
    cmake_build_dir = (
        os.path.join(cwd, "build") if not config.build_dir else config.build_dir
    )
    if not os.path.exists(cmake_build_dir):
        os.makedirs(cmake_build_dir)

    install_prefix = (
        config.install_dir if config.install_dir else get_default_install_prefix()


    from tools.memory import get_available_memory_gb

    # make build directories
    cwd = os.path.dirname(os.path.abspath(__file__))
    cmake_build_dir = (
        os.path.join(cwd, "build") if not config.build_dir else config.build_dir
    )
    if not os.path.exists(cmake_build_dir):
        os.makedirs(cmake_build_dir)

    install_prefix = (
        config.install_dir if config.install_dir else get_default_install_prefix()
    )

    from tools.gen_nvfuser_version import (
        get_pytorch_use_distributed,
    )

    pytorch_use_distributed = get_pytorch_use_distributed()

    def on_or_off(flag: bool) -> str:
        return "ON" if flag else "OFF"

    # generate cmake directory
    #
    # cmake options are sticky: when -DFOO=... isn't specified, cmake's option
    # FOO prefers the cached value over the default value. This behavior
    # confused me several times (e.g.
    # https://github.com/NVIDIA/Fuser/pull/4319) when I ran `pip install -e`,
    # so I chose to always pass these options even for default values. Doing so
    # explicitly overrides cached values and ensures consistent behavior across
    # clean and dirty builds.
    cmd_str = [
        get_cmake_bin(),
        f"-DCMAKE_BUILD_TYPE={config.build_type}",
        f"-DCMAKE_INSTALL_PREFIX={install_prefix}",
        f"-DNVFUSER_CPP_STANDARD={config.cpp_standard}",
        f"-DUSE_DISTRIBUTED={pytorch_use_distributed}",
        f"-DNVFUSER_BUILD_WITH_ASAN={on_or_off(config.build_with_asan)}",
        f"-DNVFUSER_STANDALONE_BUILD_WITH_UCC={on_or_off(config.build_with_ucc)}",
        f"-DNVFUSER_EXPLICIT_ERROR_CHECK={on_or_off(config.explicit_error_check)}",
        f"-DBUILD_TEST={on_or_off(not config.no_test)}",


clean

        max_jobs = min(max_jobs, max_jobs_mem)

    if not config.cmake_only:
        # build binary
        max_jobs = os.getenv("MAX_JOBS", str(max_jobs))
        print(f"Using {max_jobs} jobs for compilation")
        cmd_str = [
            get_cmake_bin(),
            "--build",
            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):
                    # ignore comment in .gitignore
                    if len(entry) >= 1 and entry[0] != "#":
                        for filename in glob.glob(entry):
                            print("removing: ", filename)
                            try:
                                os.remove(filename)
                            except OSError:
                                shutil.rmtree(filename, ignore_errors=True)

    return clean

def run(config, version_tag, relative_path):
    from setuptools import Extension, setup, find_packages

    # NOTE(crcrpar): Deliberately build basically two dynamic libraries here so that they can
    # be treated as "nvfuser_package_data".
    if config.build_setup:
        cmake(config, relative_path)
    if not config.cmake_only:
        # NOTE: package include files for cmake
        # TODO(crcrpar): Better avoid hardcoding `libnvfuser_codegen.so`
        # might can be treated by using `exclude_package_data`.
        nvfuser_common_package_data = [
            "lib/libnvfuser_codegen.so",
            "lib/libnvf_cutlass.so",
            "include/nvfuser/*.h",
            "include/nvfuser/struct.inl",


        # build binary
        max_jobs = os.getenv("MAX_JOBS", str(max_jobs))
        print(f"Using {max_jobs} jobs for compilation")
        cmd_str = [
            get_cmake_bin(),
            "--build",
            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):
                    # ignore comment in .gitignore
                    if len(entry) >= 1 and entry[0] != "#":
                        for filename in glob.glob(entry):
                            print("removing: ", filename)
                            try:
                                os.remove(filename)
                            except OSError:
                                shutil.rmtree(filename, ignore_errors=True)


        cmd_str = [
            get_cmake_bin(),
            "--build",
            cmake_build_dir,
            "--target",
            "install",
            "--",
            "-j",
            max_jobs,
        ]
        subprocess.check_call(cmd_str)

def create_clean(relative_path):
    class clean(setuptools.Command):
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            import glob

            gitignore_path = os.path.join(relative_path, ".gitignore")
            assert os.path.exists(gitignore_path)
            with open(gitignore_path, "r") as f:
                ignores = f.read()
                for entry in ignores.split("\n"):
                    # ignore comment in .gitignore
                    if len(entry) >= 1 and entry[0] != "#":
                        for filename in glob.glob(entry):
                            print("removing: ", filename)
                            try:
                                os.remove(filename)
                            except OSError:
                                shutil.rmtree(filename, ignore_errors=True)

    return clean


    from wheel.bdist_wheel import bdist_wheel
except ImportError:
    build_whl = None
else:

    class build_whl(bdist_wheel):
        def run(self):
            with concat_third_party_license() as tp_licenses:
                if len(tp_licenses) != 0:
                    with open("LICENSE", "a") as f:
                        f.write("\n\n")
                        f.write(
                            "NVIDIA/fuser depends on libraries with license listed below:"
                        )

                for project_name, license_files in tp_licenses.items():
                    # check all license files are identical
                    with open(license_files[0], "r") as f:
                        license_ref = f.read()

                    def check_file(file_name):
                        with open(file_name, "r") as f:
                            return f.read() == license_ref

                    identical_flag = all(map(check_file, license_files[1:]))
                    if not identical_flag:
                        raise RuntimeError(
                            "inconsistent license found for project: ",
                            project_name,
                            " check its license files under: ",
                            license_files,
                        )

                    with open("LICENSE", "a") as f:
                        f.write("\n\nProject Name: " + project_name)
                        f.write("\nLicense Files:\n")
                        for file_name in license_files:
                            f.write("\t" + file_name)
                        f.write("\n" + license_ref)

                # generate whl before we restore LICENSE
                super().run()


# SPDX-FileCopyrightText: Copyright (c) 2025-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
import torch

from ._C_DIRECT import DataType

import ctypes
from typing import Type, Union, Tuple
import functools

NumberTypeType = Union[Type[bool], Type[int], Type[float], Type[complex]]

_torch_dtype_to_nvfuser_dtype_map = {
    torch.cdouble: DataType.ComplexDouble,
    torch.cfloat: DataType.ComplexFloat,
    torch.double: DataType.Double,
    torch.float: DataType.Float,
    torch.half: DataType.Half,
    torch.bfloat16: DataType.BFloat16,
    torch.float8_e4m3fn: DataType.Float8_e4m3fn,
    torch.float8_e5m2: DataType.Float8_e5m2,
    torch.float8_e8m0fnu: DataType.Float8_e8m0fnu,
    torch.long: DataType.Int,
    torch.int: DataType.Int32,
    torch.bool: DataType.Bool,
    # Python scalars
    complex: DataType.ComplexDouble,
    float: DataType.Double,
    int: DataType.Int,
    bool: DataType.Bool,
}

if hasattr(torch, "float4_e2m1fn_x2"):
    _torch_dtype_to_nvfuser_dtype_map[
        torch.float4_e2m1fn_x2
    ] = DataType.Float4_e2m1fn_x2

def python_scalar_to_nvfuser_dtype(a: Union[int, float, complex, bool]):
    return _torch_dtype_to_nvfuser_dtype_map[type(a)]

def torch_dtype_to_nvfuser_dtype(dtype: Union[torch.dtype, NumberTypeType]):
    """
    Translates from torch.dtype to nvFuser's DataType enum
    """
    return _torch_dtype_to_nvfuser_dtype_map[dtype]

def retry_on_oom_or_skip_test(func):
    """Decorator: upon torch.OutOfMemoryError clear the cache and retry test"""

    @functools.wraps(func)
    def retried_func(*args, **kwargs):
        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError:
            pass
        else:
            return output

        # We have hit an OOM error, so clear the cache and retry
        gc.collect()
        torch.cuda.empty_cache()

        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError as e:
            # If we hit an OOM this time, then skip the test
            import pytest

            pytest.skip(f"Test failed due to OutOfMemoryError: {e}")
            return

        return output

    return retried_func

def get_device_properties() -> Tuple[int, float]:
    """
    Computes device properties using ctypes and cuda.
    Note: Consider using CUDA-Python when CUDA support >= 12.0.
    """
    libnames = ("libcuda.so", "libcuda.dylib", "nvcuda.dll", "cuda.dll")
    for libname in libnames:
        try:
            cuda = ctypes.CDLL(libname)
        except OSError:
            continue
        else:
            break
    else:
        raise OSError("could not load any of: " + " ".join(libnames))

    # Device attribute enums (taken from cuda.h)
    # https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1ge12b8a782bebe21b1ac0091bf9f4e2a3

    CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK = 1
    CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK = 8
    CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK = 12
    CU_DEVICE_ATTRIBUTE_CLOCK_RATE = 13
    CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE = 36
    CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH = 37
    CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE = 38
    CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR = 39

    device_properties = {}
    device = torch.cuda.current_device()
    cuda_properties = torch.cuda.get_device_properties(device)

    device_properties["gpu_name"] = cuda_properties.name
    device_properties["gpu_compute_capability_major"] = cuda_properties.major
    device_properties["gpu_compute_capability_minor"] = cuda_properties.minor
    device_properties["gpu_gmem_bytes"] = cuda_properties.total_memory
    device_properties["gpu_sm_count"] = cuda_properties.multi_processor_count

    max_threads_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_threads_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK,
        device,
    )
    device_properties["gpu_max_threads_per_block"] = max_threads_per_block.value

    smem_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(smem_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK,
        device,
    )
    device_properties["gpu_smem_bytes_per_block"] = smem_per_block.value

    max_reg_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_reg_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK,
        device,
    )
    device_properties["gpu_regs_per_block"] = max_reg_per_block.value

    max_clock_khz = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_clock_khz),
        CU_DEVICE_ATTRIBUTE_CLOCK_RATE,
        device,
    )
    device_properties["gpu_clock_rate_khz"] = max_clock_khz.value

    l2_cache_size = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(l2_cache_size), CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, device
    )
    device_properties["gpu_l2_bytes"] = l2_cache_size.value

    memory_clock_rate = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(memory_clock_rate), CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, device
    )
    device_properties["gpu_mem_clock_khz"] = memory_clock_rate.value

    memory_bus_width = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(memory_bus_width),
        CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH,
        device,
    )
    device_properties["gpu_mem_bus_width"] = memory_bus_width.value

    max_threads_per_sm = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_threads_per_sm),
        CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR,
        device,
    )
    device_properties["gpu_max_threads_per_sm"] = max_threads_per_sm.value

    # Compute peak bandwidth in GBps
    peak_bandwidth = (2 * memory_bus_width.value * memory_clock_rate.value) / (1e6 * 8)
    device_properties["gpu_peak_bandwidth_gbps"] = peak_bandwidth

    return device_properties

DEVICE_PROPERTIES = None
if torch.cuda.is_available():
    # Loading libraries will raise errors on non-CUDA machines.
    DEVICE_PROPERTIES = get_device_properties()


    torch.bfloat16: DataType.BFloat16,
    torch.float8_e4m3fn: DataType.Float8_e4m3fn,
    torch.float8_e5m2: DataType.Float8_e5m2,
    torch.float8_e8m0fnu: DataType.Float8_e8m0fnu,
    torch.long: DataType.Int,
    torch.int: DataType.Int32,
    torch.bool: DataType.Bool,
    # Python scalars
    complex: DataType.ComplexDouble,
    float: DataType.Double,
    int: DataType.Int,
    bool: DataType.Bool,
}

if hasattr(torch, "float4_e2m1fn_x2"):
    _torch_dtype_to_nvfuser_dtype_map[
        torch.float4_e2m1fn_x2
    ] = DataType.Float4_e2m1fn_x2

def python_scalar_to_nvfuser_dtype(a: Union[int, float, complex, bool]):
    return _torch_dtype_to_nvfuser_dtype_map[type(a)]

def torch_dtype_to_nvfuser_dtype(dtype: Union[torch.dtype, NumberTypeType]):
    """
    Translates from torch.dtype to nvFuser's DataType enum
    """
    return _torch_dtype_to_nvfuser_dtype_map[dtype]

def retry_on_oom_or_skip_test(func):
    """Decorator: upon torch.OutOfMemoryError clear the cache and retry test"""

    @functools.wraps(func)
    def retried_func(*args, **kwargs):
        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError:
            pass
        else:
            return output


    torch.long: DataType.Int,
    torch.int: DataType.Int32,
    torch.bool: DataType.Bool,
    # Python scalars
    complex: DataType.ComplexDouble,
    float: DataType.Double,
    int: DataType.Int,
    bool: DataType.Bool,
}

if hasattr(torch, "float4_e2m1fn_x2"):
    _torch_dtype_to_nvfuser_dtype_map[
        torch.float4_e2m1fn_x2
    ] = DataType.Float4_e2m1fn_x2

def python_scalar_to_nvfuser_dtype(a: Union[int, float, complex, bool]):
    return _torch_dtype_to_nvfuser_dtype_map[type(a)]

def torch_dtype_to_nvfuser_dtype(dtype: Union[torch.dtype, NumberTypeType]):
    """
    Translates from torch.dtype to nvFuser's DataType enum
    """
    return _torch_dtype_to_nvfuser_dtype_map[dtype]

def retry_on_oom_or_skip_test(func):
    """Decorator: upon torch.OutOfMemoryError clear the cache and retry test"""

    @functools.wraps(func)
    def retried_func(*args, **kwargs):
        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError:
            pass
        else:
            return output

        # We have hit an OOM error, so clear the cache and retry
        gc.collect()
        torch.cuda.empty_cache()

        try:
            output = func(*args, **kwargs)


    bool: DataType.Bool,
}

if hasattr(torch, "float4_e2m1fn_x2"):
    _torch_dtype_to_nvfuser_dtype_map[
        torch.float4_e2m1fn_x2
    ] = DataType.Float4_e2m1fn_x2

def python_scalar_to_nvfuser_dtype(a: Union[int, float, complex, bool]):
    return _torch_dtype_to_nvfuser_dtype_map[type(a)]

def torch_dtype_to_nvfuser_dtype(dtype: Union[torch.dtype, NumberTypeType]):
    """
    Translates from torch.dtype to nvFuser's DataType enum
    """
    return _torch_dtype_to_nvfuser_dtype_map[dtype]

def retry_on_oom_or_skip_test(func):
    """Decorator: upon torch.OutOfMemoryError clear the cache and retry test"""

    @functools.wraps(func)
    def retried_func(*args, **kwargs):
        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError:
            pass
        else:
            return output

        # We have hit an OOM error, so clear the cache and retry
        gc.collect()
        torch.cuda.empty_cache()

        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError as e:
            # If we hit an OOM this time, then skip the test
            import pytest

            pytest.skip(f"Test failed due to OutOfMemoryError: {e}")
            return

        return output

    return retried_func

def get_device_properties() -> Tuple[int, float]:
    """
    Computes device properties using ctypes and cuda.
    Note: Consider using CUDA-Python when CUDA support >= 12.0.
    """
    libnames = ("libcuda.so", "libcuda.dylib", "nvcuda.dll", "cuda.dll")
    for libname in libnames:
        try:
            cuda = ctypes.CDLL(libname)
        except OSError:
            continue
        else:
            break
    else:
        raise OSError("could not load any of: " + " ".join(libnames))

    # Device attribute enums (taken from cuda.h)
    # https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1ge12b8a782bebe21b1ac0091bf9f4e2a3


            return output

        # We have hit an OOM error, so clear the cache and retry
        gc.collect()
        torch.cuda.empty_cache()

        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError as e:
            # If we hit an OOM this time, then skip the test
            import pytest

            pytest.skip(f"Test failed due to OutOfMemoryError: {e}")
            return

        return output

    return retried_func

def get_device_properties() -> Tuple[int, float]:
    """
    Computes device properties using ctypes and cuda.
    Note: Consider using CUDA-Python when CUDA support >= 12.0.
    """
    libnames = ("libcuda.so", "libcuda.dylib", "nvcuda.dll", "cuda.dll")
    for libname in libnames:
        try:
            cuda = ctypes.CDLL(libname)
        except OSError:
            continue
        else:
            break
    else:
        raise OSError("could not load any of: " + " ".join(libnames))

    # Device attribute enums (taken from cuda.h)
    # https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1ge12b8a782bebe21b1ac0091bf9f4e2a3

    CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK = 1
    CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK = 8
    CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK = 12
    CU_DEVICE_ATTRIBUTE_CLOCK_RATE = 13
    CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE = 36
    CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH = 37
    CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE = 38
    CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR = 39

    device_properties = {}
    device = torch.cuda.current_device()
    cuda_properties = torch.cuda.get_device_properties(device)

    device_properties["gpu_name"] = cuda_properties.name
    device_properties["gpu_compute_capability_major"] = cuda_properties.major
    device_properties["gpu_compute_capability_minor"] = cuda_properties.minor
    device_properties["gpu_gmem_bytes"] = cuda_properties.total_memory
    device_properties["gpu_sm_count"] = cuda_properties.multi_processor_count

    max_threads_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_threads_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK,
        device,
    )
    device_properties["gpu_max_threads_per_block"] = max_threads_per_block.value

    smem_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(smem_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK,
        device,
    )
    device_properties["gpu_smem_bytes_per_block"] = smem_per_block.value

    max_reg_per_block = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_reg_per_block),
        CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK,
        device,
    )
    device_properties["gpu_regs_per_block"] = max_reg_per_block.value

    max_clock_khz = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_clock_khz),
        CU_DEVICE_ATTRIBUTE_CLOCK_RATE,
        device,
    )
    device_properties["gpu_clock_rate_khz"] = max_clock_khz.value

    l2_cache_size = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(l2_cache_size), CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, device
    )
    device_properties["gpu_l2_bytes"] = l2_cache_size.value

    memory_clock_rate = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(memory_clock_rate), CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, device
    )
    device_properties["gpu_mem_clock_khz"] = memory_clock_rate.value

    memory_bus_width = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(memory_bus_width),
        CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH,
        device,
    )
    device_properties["gpu_mem_bus_width"] = memory_bus_width.value

    max_threads_per_sm = ctypes.c_int()
    cuda.cuDeviceGetAttribute(
        ctypes.byref(max_threads_per_sm),
        CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR,
        device,
    )
    device_properties["gpu_max_threads_per_sm"] = max_threads_per_sm.value

    # Compute peak bandwidth in GBps
    peak_bandwidth = (2 * memory_bus_width.value * memory_clock_rate.value) / (1e6 * 8)
    device_properties["gpu_peak_bandwidth_gbps"] = peak_bandwidth

    return device_properties

DEVICE_PROPERTIES = None
if torch.cuda.is_available():
    # Loading libraries will raise errors on non-CUDA machines.
    DEVICE_PROPERTIES = get_device_properties()


    _torch_dtype_to_nvfuser_dtype_map[
        torch.float4_e2m1fn_x2
    ] = DataType.Float4_e2m1fn_x2

def python_scalar_to_nvfuser_dtype(a: Union[int, float, complex, bool]):
    return _torch_dtype_to_nvfuser_dtype_map[type(a)]

def torch_dtype_to_nvfuser_dtype(dtype: Union[torch.dtype, NumberTypeType]):
    """
    Translates from torch.dtype to nvFuser's DataType enum
    """
    return _torch_dtype_to_nvfuser_dtype_map[dtype]

def retry_on_oom_or_skip_test(func):
    """Decorator: upon torch.OutOfMemoryError clear the cache and retry test"""

    @functools.wraps(func)
    def retried_func(*args, **kwargs):
        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError:
            pass
        else:
            return output

        # We have hit an OOM error, so clear the cache and retry
        gc.collect()
        torch.cuda.empty_cache()

        try:
            output = func(*args, **kwargs)
        except torch.OutOfMemoryError as e:
            # If we hit an OOM this time, then skip the test
            import pytest

            pytest.skip(f"Test failed due to OutOfMemoryError: {e}")
            return

        return output

    return retried_func

def get_device_properties() -> Tuple[int, float]:
    """
    Computes device properties using ctypes and cuda.
    Note: Consider using CUDA-Python when CUDA support >= 12.0.
    """
    libnames = ("libcuda.so", "libcuda.dylib", "nvcuda.dll", "cuda.dll")
    for libname in libnames:
        try:
            cuda = ctypes.CDLL(libname)
        except OSError:
            continue
        else:
            break
    else:
        raise OSError("could not load any of: " + " ".join(libnames))


# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
from typing import Any
from .version import _version_str

__all__ = ["NvfuserVersion", "Version"]

class _LazyImport:
    """Wraps around classes lazy imported from packaging.version
    Output of the function v in following snippets are identical:
       from packaging.version import Version
       def v():
           return Version('1.2.3')
    and
       Version = _LazyImport('Version')
       def v():
           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )

for cmp_method in ["__gt__", "__lt__", "__eq__", "__ge__", "__le__"]:
    setattr(
        NvfuserVersion,
        cmp_method,
        lambda x, y, method=cmp_method: x._cmp_version(y, method),
    )

__version__ = NvfuserVersion(_version_str)


# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
from typing import Any
from .version import _version_str

__all__ = ["NvfuserVersion", "Version"]

class _LazyImport:
    """Wraps around classes lazy imported from packaging.version
    Output of the function v in following snippets are identical:
       from packaging.version import Version
       def v():
           return Version('1.2.3')
    and
       Version = _LazyImport('Version')
       def v():
           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )


NvfuserVersion

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )

for cmp_method in ["__gt__", "__lt__", "__eq__", "__ge__", "__le__"]:
    setattr(
        NvfuserVersion,
        cmp_method,
        lambda x, y, method=cmp_method: x._cmp_version(y, method),
    )

__version__ = NvfuserVersion(_version_str)


from typing import Any
from .version import _version_str

__all__ = ["NvfuserVersion", "Version"]

class _LazyImport:
    """Wraps around classes lazy imported from packaging.version
    Output of the function v in following snippets are identical:
       from packaging.version import Version
       def v():
           return Version('1.2.3')
    and
       Version = _LazyImport('Version')
       def v():
           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")


__all__ = ["NvfuserVersion", "Version"]

class _LazyImport:
    """Wraps around classes lazy imported from packaging.version
    Output of the function v in following snippets are identical:
       from packaging.version import Version
       def v():
           return Version('1.2.3')
    and
       Version = _LazyImport('Version')
       def v():
           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))


    and
       Version = _LazyImport('Version')
       def v():
           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(


           return Version('1.2.3')
    The difference here is that in later example imports
    do not happen until v is called
    """

    def __init__(self, cls_name: str) -> None:
        self._cls_name = cls_name

    def get_cls(self):
        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )


        try:
            import packaging.version  # type: ignore[import]
        except ImportError:
            # If packaging isn't installed, try and use the vendored copy
            # in pkg_resources
            from pkg_resources import packaging  # type: ignore[attr-defined, no-redef]
        return getattr(packaging.version, self._cls_name)

    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )

for cmp_method in ["__gt__", "__lt__", "__eq__", "__ge__", "__le__"]:
    setattr(
        NvfuserVersion,
        cmp_method,
        lambda x, y, method=cmp_method: x._cmp_version(y, method),
    )

__version__ = NvfuserVersion(_version_str)


    def __call__(self, *args, **kwargs):
        return self.get_cls()(*args, **kwargs)

    def __instancecheck__(self, obj):
        return isinstance(obj, self.get_cls())

Version = _LazyImport("Version")

class NvfuserVersion(str):
    @classmethod
    def _convert_to_version(cls, ver: Any) -> Version:
        if isinstance(ver, str):
            return Version(ver.split("+")[0])
        elif isinstance(ver, Version.get_cls()):
            return ver
        else:
            raise ValueError("can't convert {} to Version".format(ver))

    def _cmp_version(self, other: Any, method: str) -> Version:
        return getattr(NvfuserVersion._convert_to_version(self), method)(
            NvfuserVersion._convert_to_version(other)
        )

for cmp_method in ["__gt__", "__lt__", "__eq__", "__ge__", "__le__"]:
    setattr(
        NvfuserVersion,
        cmp_method,
        lambda x, y, method=cmp_method: x._cmp_version(y, method),
    )

__version__ = NvfuserVersion(_version_str)


# SPDX-FileCopyrightText: Copyright (c) 2025-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

import torch
from cupti import cupti
import cxxfilt
import pytest
from ._C_DIRECT import get_fusion_profile

# Base class for all timers used by pytest-benchmark.
class Timer:
    def __init__(self):
        self.current_time = 0.0

    def _increment_global_time(self, elapsed_time: float) -> None:
        self.current_time += elapsed_time

    def __call__(self):
        raise NotImplementedError("Subclass must implement this method")

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.
        buffer_size = 8 * 1024 * 1024
        max_num_records = 0
        return buffer_size, max_num_records

    def _func_buffer_completed(self, activities: list[cupti.ActivityAPI]) -> None:
        for activity in activities:
            # Activity.end and Activity.start are in nanoseconds.
            duration = (activity.end - activity.start) / 1e9
            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )

        self.profiler_output: list[tuple[str, float]] = []

        # Subscribe to CUPTI and register activity callbacks.
        CuptiProfiler.__subscriber_handle = cupti_call_safe(cupti.subscribe, None, None)
        cupti_call_safe(
            cupti.activity_register_callbacks,
            self._func_buffer_requested,
            self._func_buffer_completed,
        )
        self.is_valid = True

    def start(self) -> None:
        self._error_if_not_valid()
        cupti_call_safe(cupti.activity_flush_all, 1)
        self.profiler_output = []
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_enable, activity_kind)

    def stop(self) -> list[tuple[str, float]]:
        self._error_if_not_valid()
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_disable, activity_kind)
        cupti_call_safe(cupti.activity_flush_all, 0)
        return self.profiler_output

    def teardown_cupti(self) -> None:
        self._error_if_not_valid()
        if CuptiProfiler.__subscriber_handle is None:
            return
        cupti_call_safe(cupti.unsubscribe, CuptiProfiler.__subscriber_handle)
        cupti_call_safe(cupti.finalize)
        CuptiProfiler.__subscriber_handle = None
        # Invalidate the profiler so it cannot be used again.
        self.is_valid = False

class CuptiTimer(Timer):
    def __init__(self):
        super().__init__()
        self.cupti_profiler = CuptiProfiler()
        self.is_running = False

    def __call__(self):
        torch.cuda.synchronize()

        if not self.is_running:
            self.cupti_profiler.start()
            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded
        if len(profiler_output) == 0:
            self.cleanup()
            raise RuntimeError("No activities were recorded.")

        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


Timer

# SPDX-FileCopyrightText: Copyright (c) 2025-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

import torch
from cupti import cupti
import cxxfilt
import pytest
from ._C_DIRECT import get_fusion_profile

# Base class for all timers used by pytest-benchmark.
class Timer:
    def __init__(self):
        self.current_time = 0.0

    def _increment_global_time(self, elapsed_time: float) -> None:
        self.current_time += elapsed_time

    def __call__(self):
        raise NotImplementedError("Subclass must implement this method")

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.


import cxxfilt
import pytest
from ._C_DIRECT import get_fusion_profile

# Base class for all timers used by pytest-benchmark.
class Timer:
    def __init__(self):
        self.current_time = 0.0

    def _increment_global_time(self, elapsed_time: float) -> None:
        self.current_time += elapsed_time

    def __call__(self):
        raise NotImplementedError("Subclass must implement this method")

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None


    def __init__(self):
        self.current_time = 0.0

    def _increment_global_time(self, elapsed_time: float) -> None:
        self.current_time += elapsed_time

    def __call__(self):
        raise NotImplementedError("Subclass must implement this method")

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.


CuptiProfiler

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.
        buffer_size = 8 * 1024 * 1024
        max_num_records = 0
        return buffer_size, max_num_records

    def _func_buffer_completed(self, activities: list[cupti.ActivityAPI]) -> None:
        for activity in activities:
            # Activity.end and Activity.start are in nanoseconds.
            duration = (activity.end - activity.start) / 1e9
            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )

        self.profiler_output: list[tuple[str, float]] = []

        # Subscribe to CUPTI and register activity callbacks.
        CuptiProfiler.__subscriber_handle = cupti_call_safe(cupti.subscribe, None, None)
        cupti_call_safe(
            cupti.activity_register_callbacks,
            self._func_buffer_requested,
            self._func_buffer_completed,
        )
        self.is_valid = True

    def start(self) -> None:
        self._error_if_not_valid()
        cupti_call_safe(cupti.activity_flush_all, 1)
        self.profiler_output = []
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_enable, activity_kind)

    def stop(self) -> list[tuple[str, float]]:
        self._error_if_not_valid()
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_disable, activity_kind)
        cupti_call_safe(cupti.activity_flush_all, 0)
        return self.profiler_output

    def teardown_cupti(self) -> None:
        self._error_if_not_valid()
        if CuptiProfiler.__subscriber_handle is None:
            return
        cupti_call_safe(cupti.unsubscribe, CuptiProfiler.__subscriber_handle)
        cupti_call_safe(cupti.finalize)
        CuptiProfiler.__subscriber_handle = None
        # Invalidate the profiler so it cannot be used again.
        self.is_valid = False

class CuptiTimer(Timer):
    def __init__(self):
        super().__init__()
        self.cupti_profiler = CuptiProfiler()
        self.is_running = False

    def __call__(self):
        torch.cuda.synchronize()

        if not self.is_running:
            self.cupti_profiler.start()
            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded


CuptiTimer

            cupti_call_safe(cupti.activity_enable, activity_kind)

    def stop(self) -> list[tuple[str, float]]:
        self._error_if_not_valid()
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_disable, activity_kind)
        cupti_call_safe(cupti.activity_flush_all, 0)
        return self.profiler_output

    def teardown_cupti(self) -> None:
        self._error_if_not_valid()
        if CuptiProfiler.__subscriber_handle is None:
            return
        cupti_call_safe(cupti.unsubscribe, CuptiProfiler.__subscriber_handle)
        cupti_call_safe(cupti.finalize)
        CuptiProfiler.__subscriber_handle = None
        # Invalidate the profiler so it cannot be used again.
        self.is_valid = False

class CuptiTimer(Timer):
    def __init__(self):
        super().__init__()
        self.cupti_profiler = CuptiProfiler()
        self.is_running = False

    def __call__(self):
        torch.cuda.synchronize()

        if not self.is_running:
            self.cupti_profiler.start()
            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded
        if len(profiler_output) == 0:
            self.cleanup()
            raise RuntimeError("No activities were recorded.")

        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


FusionProfileTimer

            self.cupti_profiler.start()
            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded
        if len(profiler_output) == 0:
            self.cleanup()
            raise RuntimeError("No activities were recorded.")

        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded
        if len(profiler_output) == 0:
            self.cleanup()
            raise RuntimeError("No activities were recorded.")

        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


# SPDX-FileCopyrightText: Copyright (c) 2025-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

import torch
from cupti import cupti
import cxxfilt
import pytest
from ._C_DIRECT import get_fusion_profile

# Base class for all timers used by pytest-benchmark.
class Timer:
    def __init__(self):
        self.current_time = 0.0

    def _increment_global_time(self, elapsed_time: float) -> None:
        self.current_time += elapsed_time

    def __call__(self):
        raise NotImplementedError("Subclass must implement this method")

    def cleanup(self):
        pass

def demangle_kernel_name(mangled_name):
    try:
        return cxxfilt.demangle(mangled_name)
    except Exception:
        return mangled_name  # Return original if demangling fails

def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:


        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


cleanup

    def __call__(self):
        torch.cuda.synchronize()

        if not self.is_running:
            self.cupti_profiler.start()
            self.is_running = True
            return self.current_time

        profiler_output = self.cupti_profiler.stop()
        self.is_running = False

        # Check if any activities were recorded
        if len(profiler_output) == 0:
            self.cleanup()
            raise RuntimeError("No activities were recorded.")

        self._increment_global_time(sum(duration for _, duration in profiler_output))
        return self.current_time

    def cleanup(self):
        self.is_running = False
        self.cupti_profiler.teardown_cupti()

class FusionProfileTimer(Timer):
    def __init__(self):
        super().__init__()
        self.fd = None
        # Specifies if the timer in host measurement is called at the start/finish of execution.
        # Timings are measured at the end of execution.
        self.execution_start = True

    def set_fd(self, fd):
        self.fd = fd

    def __call__(self):
        if not self.execution_start:
            profile = get_fusion_profile()
            elapsed_host_time = profile.host_time_ms / 1e3
            self._increment_global_time(elapsed_host_time)
        self.execution_start = not self.execution_start
        return self.current_time


def cupti_call_safe(func, *args):
    """Wrapper for CUPTI calls. Failing CUPTI calls will exit the program."""
    try:
        return func(*args)
    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.
        buffer_size = 8 * 1024 * 1024
        max_num_records = 0
        return buffer_size, max_num_records

    def _func_buffer_completed(self, activities: list[cupti.ActivityAPI]) -> None:
        for activity in activities:
            # Activity.end and Activity.start are in nanoseconds.
            duration = (activity.end - activity.start) / 1e9
            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )


    except Exception as e:
        print(f"CUPTI call {func.__name__} failed: {e}")
        pytest.exit(1)

class CuptiProfiler:
    # List of activities to be recorded by CUPTI.
    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.
        buffer_size = 8 * 1024 * 1024
        max_num_records = 0
        return buffer_size, max_num_records

    def _func_buffer_completed(self, activities: list[cupti.ActivityAPI]) -> None:
        for activity in activities:
            # Activity.end and Activity.start are in nanoseconds.
            duration = (activity.end - activity.start) / 1e9
            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )

        self.profiler_output: list[tuple[str, float]] = []

        # Subscribe to CUPTI and register activity callbacks.
        CuptiProfiler.__subscriber_handle = cupti_call_safe(cupti.subscribe, None, None)
        cupti_call_safe(
            cupti.activity_register_callbacks,


    activity_kinds: list[cupti.ActivityKind] = [
        cupti.ActivityKind.CONCURRENT_KERNEL,
    ]

    # Private class variable to store the subscriber handle.
    __subscriber_handle = None

    def _error_if_not_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "CuptiProfiler is not valid. " "This instance has been torn down."
            )

    def _func_buffer_requested(self) -> tuple[int, int]:
        # 8MB buffer size as recommended by CUPTI samples.
        # max_num_records=0 indicates the buffer is filled with as many records as possible.
        buffer_size = 8 * 1024 * 1024
        max_num_records = 0
        return buffer_size, max_num_records

    def _func_buffer_completed(self, activities: list[cupti.ActivityAPI]) -> None:
        for activity in activities:
            # Activity.end and Activity.start are in nanoseconds.
            duration = (activity.end - activity.start) / 1e9
            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )

        self.profiler_output: list[tuple[str, float]] = []

        # Subscribe to CUPTI and register activity callbacks.
        CuptiProfiler.__subscriber_handle = cupti_call_safe(cupti.subscribe, None, None)
        cupti_call_safe(
            cupti.activity_register_callbacks,
            self._func_buffer_requested,
            self._func_buffer_completed,
        )
        self.is_valid = True

    def start(self) -> None:


start

            self.profiler_output.append((demangle_kernel_name(activity.name), duration))

    def __init__(self):
        if CuptiProfiler.__subscriber_handle is not None:
            raise RuntimeError(
                "Only one instance of CuptiProfiler can be created. "
                "CUPTI only supports one subscriber at a time."
            )

        self.profiler_output: list[tuple[str, float]] = []

        # Subscribe to CUPTI and register activity callbacks.
        CuptiProfiler.__subscriber_handle = cupti_call_safe(cupti.subscribe, None, None)
        cupti_call_safe(
            cupti.activity_register_callbacks,
            self._func_buffer_requested,
            self._func_buffer_completed,
        )
        self.is_valid = True

    def start(self) -> None:
        self._error_if_not_valid()
        cupti_call_safe(cupti.activity_flush_all, 1)
        self.profiler_output = []
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_enable, activity_kind)

    def stop(self) -> list[tuple[str, float]]:
        self._error_if_not_valid()
        for activity_kind in CuptiProfiler.activity_kinds:
            cupti_call_safe(cupti.activity_disable, activity_kind)
        cupti_call_safe(cupti.activity_flush_all, 0)
        return self.profiler_output

    def teardown_cupti(self) -> None:
        self._error_if_not_valid()
        if CuptiProfiler.__subscriber_handle is None:
            return
        cupti_call_safe(cupti.unsubscribe, CuptiProfiler.__subscriber_handle)
        cupti_call_safe(cupti.finalize)
        CuptiProfiler.__subscriber_handle = None
        # Invalidate the profiler so it cannot be used again.
        self.is_valid = False

class CuptiTimer(Timer):