-
Notifications
You must be signed in to change notification settings - Fork 810
Use dlopen to load NCCL EP
#3434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| """Verify that packaged NCCL EP JIT headers contain all local quoted includes.""" | ||
|
|
||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| _INCLUDE_PATTERN = re.compile(r'^\s*#\s*include\s*"([^"]+)"') | ||
| _EXTERNAL_HEADERS = {"nccl.h", "nccl_device.h"} | ||
| _HEADER_SUFFIXES = {".cuh", ".h", ".hh", ".hpp", ".inc", ".inl"} | ||
|
|
||
|
|
||
| def main() -> None: | ||
| include_root = Path(sys.argv[1]) | ||
| jit_root = include_root / "nccl_ep" | ||
| public_header = include_root / "nccl_ep.h" | ||
| if not public_header.is_file(): | ||
| raise RuntimeError(f"Missing NCCL EP public header: {public_header}") | ||
| if not jit_root.is_dir(): | ||
| raise RuntimeError(f"Missing NCCL EP JIT header directory: {jit_root}") | ||
|
|
||
| failures = [] | ||
|
|
||
| headers = [public_header, *jit_root.rglob("*")] | ||
| for header in headers: | ||
| if not header.is_file() or header.suffix not in _HEADER_SUFFIXES: | ||
| continue | ||
| for line_number, line in enumerate(header.read_text().splitlines(), 1): | ||
| match = _INCLUDE_PATTERN.match(line) | ||
| if match is None: | ||
| continue | ||
| include = match.group(1) | ||
| if include in _EXTERNAL_HEADERS: | ||
| continue | ||
| candidates = ( | ||
| header.parent / include, | ||
| include_root / include, | ||
| jit_root / include, | ||
| jit_root / "device" / include, | ||
| ) | ||
| if not any(candidate.is_file() for candidate in candidates): | ||
| failures.append(f"{header.relative_to(include_root)}:{line_number}: {include}") | ||
|
|
||
| if failures: | ||
| raise RuntimeError("Missing local NCCL EP JIT headers:\n" + "\n".join(failures)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| import transformer_engine | ||
|
|
||
|
|
||
| def _hide_packaged_library(monkeypatch): | ||
| def _missing_library(_): | ||
| raise FileNotFoundError | ||
|
|
||
| monkeypatch.setattr( | ||
| transformer_engine.common, | ||
| "_get_shared_object_file", | ||
| _missing_library, | ||
| ) | ||
|
|
||
|
|
||
| def test_nccl_ep_library_found_from_home(monkeypatch, tmp_path): | ||
| home = tmp_path / "nccl_ep" | ||
| library_dir = home / "lib" | ||
| library_dir.mkdir(parents=True) | ||
| (library_dir / "libnccl_ep.so.0").touch() | ||
|
|
||
| monkeypatch.setenv("NCCL_EP_HOME", str(home)) | ||
| _hide_packaged_library(monkeypatch) | ||
| monkeypatch.setattr(transformer_engine, "find_library", lambda _: None) | ||
|
|
||
| assert transformer_engine._nccl_ep_library_installed() | ||
|
|
||
|
|
||
| def test_nccl_ep_library_found_in_package(monkeypatch, tmp_path): | ||
| plugin = tmp_path / "libnccl_ep.so" | ||
| plugin.touch() | ||
|
|
||
| monkeypatch.delenv("NCCL_EP_HOME", raising=False) | ||
| monkeypatch.setattr( | ||
| transformer_engine.common, | ||
| "_get_shared_object_file", | ||
| lambda _: plugin, | ||
| ) | ||
| monkeypatch.setattr(transformer_engine, "find_library", lambda _: None) | ||
|
|
||
| assert transformer_engine._nccl_ep_library_installed() | ||
|
|
||
|
|
||
| def test_nccl_ep_library_found_by_dynamic_loader(monkeypatch): | ||
| monkeypatch.delenv("NCCL_EP_HOME", raising=False) | ||
| _hide_packaged_library(monkeypatch) | ||
| monkeypatch.setattr(transformer_engine, "find_library", lambda _: "libnccl_ep.so.0") | ||
|
|
||
| assert transformer_engine._nccl_ep_library_installed() | ||
|
|
||
|
|
||
| def test_nccl_ep_library_not_found(monkeypatch): | ||
| monkeypatch.delenv("NCCL_EP_HOME", raising=False) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it mean NCCL_EP_HOME needs to be set at runtime? |
||
| _hide_packaged_library(monkeypatch) | ||
| monkeypatch.setattr(transformer_engine, "find_library", lambda _: None) | ||
|
|
||
| assert not transformer_engine._nccl_ep_library_installed() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,13 @@ | |
| import ctypes | ||
| import functools | ||
| import os | ||
| from ctypes.util import find_library | ||
| from importlib import metadata | ||
| from pathlib import Path | ||
| from typing import Optional, Tuple | ||
| import transformer_engine.common | ||
|
|
||
| # Minimum NCCL version for the statically-linked NCCL EP backend. | ||
| # Minimum NCCL version for the runtime-loaded NCCL EP backend. | ||
| _NCCL_EP_MIN_VERSION = (2, 30, 4) | ||
|
|
||
|
|
||
|
|
@@ -32,14 +34,39 @@ def _nccl_runtime_version() -> Optional[Tuple[int, int, int]]: | |
| return (v // 10000, (v // 100) % 100, v % 100) | ||
|
|
||
|
|
||
| def _nccl_ep_library_installed() -> bool: | ||
| if nccl_ep_home := os.getenv("NCCL_EP_HOME"): | ||
| home = Path(nccl_ep_home) | ||
| if any( | ||
| library.is_file() | ||
| for lib_dir in ("lib", "lib64") | ||
| for library in (home / lib_dir).glob("libnccl_ep.so*") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When Knowledge Base Used: Build, extensions, and packaging |
||
| ): | ||
| return True | ||
|
|
||
| try: | ||
| transformer_engine.common._get_shared_object_file("nccl_ep") | ||
| except FileNotFoundError: | ||
| return find_library("nccl_ep") is not None | ||
| else: | ||
| return True | ||
|
|
||
|
|
||
| def is_nccl_ep_available() -> bool: | ||
| """Return True if the runtime libnccl.so meets the NCCL EP minimum.""" | ||
| """Return True if the NCCL EP library and a compatible NCCL runtime are available.""" | ||
| if not _nccl_ep_library_installed(): | ||
| return False | ||
| cur = _nccl_runtime_version() | ||
| return cur is not None and cur >= _NCCL_EP_MIN_VERSION | ||
|
|
||
|
|
||
| def require_nccl_ep() -> None: | ||
| """Raise RuntimeError if NCCL EP cannot run on the current libnccl.""" | ||
| if not _nccl_ep_library_installed(): | ||
| raise RuntimeError( | ||
| "NCCL EP library libnccl_ep.so is not installed. Build Transformer Engine " | ||
| "with NVTE_WITH_NCCL_EP=1." | ||
| ) | ||
| mn = ".".join(str(x) for x in _NCCL_EP_MIN_VERSION) | ||
| cur = _nccl_runtime_version() | ||
| if cur is None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,16 +100,18 @@ def _get_shared_object_file(library: str) -> Path: | |
| """ | ||
| Path to shared object file for a Transformer Engine library. | ||
|
|
||
| TE libraries are 'core', 'torch', or 'jax'. This function first | ||
| TE libraries are 'core', 'torch', 'jax', or 'nccl_ep'. This function first | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if |
||
| searches in the imported TE directory, and then in the | ||
| site-packages directory. | ||
|
|
||
| """ | ||
|
|
||
| # Check provided input and determine the correct prefix for .so. | ||
| assert library in ("core", "torch", "jax"), f"Unsupported TE library {library}." | ||
| assert library in ("core", "torch", "jax", "nccl_ep"), f"Unsupported TE library {library}." | ||
| if library == "core": | ||
| so_prefix = "libtransformer_engine" | ||
| elif library == "nccl_ep": | ||
| so_prefix = "libnccl_ep" | ||
| else: | ||
| so_prefix = f"transformer_engine_{library}" | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we copy the whole nccl_ep dir instead of header files only?