Skip to content

Commit fd5f657

Browse files
lihongyang1990panpy
andauthored
Multi-Backend Architecture Implementation for TransformerEngine-FL (#4)
# TransformerEngine-FL Plugin System ## Overview This PR implements a comprehensive multi-backend plugin system for TransformerEngine-FL, enabling support for multiple hardware vendors (NVIDIA, AMD, Hygon, etc.) while maintaining full API compatibility with the original `transformer_engine_torch`. **Core Philosophy**: A plugin-based backend system that allows hardware vendors to easily implement their own operator optimizations while preserving complete compatibility with the original TransformerEngine API. ## Key Features ### Full API Compatibility - Drop-in replacement for `transformer_engine_torch` - Switch backends via environment variables - Zero changes required to existing user code ### Multi-Backend Support | Backend | Description | Implementation | |---------|-------------|----------------| | **FlagOS (default)** | Triton-based cross-platform implementation | `backends/flagos/` | | **CUDA (vendor)** | Wraps original TransformerEngine C++ extensions | `backends/vendor/cuda/` | | **Reference** | Pure PyTorch fallback implementation | `backends/reference/` | ### Three-Tier Backend Selection ``` ┌─────────────────────────────────────────────────────────┐ │ 1. TE_FL_PER_OP (Per-operator override) [Highest] │ │ Example: TE_FL_PER_OP="rmsnorm_fwd=vendor:cuda" │ ├─────────────────────────────────────────────────────────┤ │ 2. TE_FL_PREFER (Global preference) │ │ Values: flagos / vendor / reference │ ├─────────────────────────────────────────────────────────┤ │ 3. Backend Priority (Intrinsic) [Lowest] │ │ Each implementation has a priority value │ └─────────────────────────────────────────────────────────┘ ``` ## Architecture ### Directory Structure ``` transformer_engine/plugin/core/ ├── __init__.py # Public API exports ├── types.py # Core types: BackendImplKind, OpImpl ├── registry.py # OpRegistry: stores all implementations ├── manager.py # OpManager: selects and calls implementations ├── policy.py # SelectionPolicy: backend selection rules ├── discovery.py # Plugin auto-discovery (entry_points, env) ├── builtin_ops.py # Registers all built-in backends ├── ops.py # TEFLModule: transformer_engine_torch compatible API ├── logger_manager.py # Logging utilities ├── _module_setup.py # Module aliasing setup ├── _build_config.py # Build-time configuration │ └── backends/ ├── flagos/ # FlagOS backend (Triton-based) │ ├── flagos.py # FlagOSBackend class │ ├── register_ops.py # Operator registration │ └── impl/ # Operator implementations │ ├── rmsnorm.py │ ├── gemm.py │ └── ... │ ├── vendor/ # Vendor backends │ └── cuda/ # NVIDIA CUDA backend │ ├── cuda.py # CUDABackend class │ └── register_ops.py │ └── reference/ # Reference backend (PyTorch) ├── reference.py # ReferenceBackend class ├── register_ops.py └── impl/ # Pure PyTorch implementations ``` ### Core Components | File | Description | |------|-------------| | `types.py` | Defines `BackendImplKind` (DEFAULT/VENDOR/REFERENCE) and `OpImpl` dataclass | | `registry.py` | `OpRegistry` - Central storage for all operator implementations | | `manager.py` | `OpManager` - Handles implementation selection, fallback, and execution | | `policy.py` | `SelectionPolicy` - Configurable rules for backend selection | | `discovery.py` | Auto-discovers plugins via `entry_points` or `TE_FL_PLUGIN_MODULES` | | `ops.py` | `TEFLModule` - Provides `transformer_engine_torch` compatible interface | ## Installation ### Build with CUDA support ```bash pip install --no-build-isolation -e . ``` ### Build without CUDA (FlagOS only) ```bash TE_FL_SKIP_CUDA=1 pip install --no-build-isolation -e . ``` ## Environment Variables ### Backend Selection | Variable | Description | Values | Default | |----------|-------------|--------|---------| | `TE_FL_PREFER` | Preferred backend type | `flagos` / `vendor` / `reference` | `flagos` | | `TE_FL_PREFER_VENDOR` | Prefer vendor (legacy) | `1` / `0` | `0` | | `TE_FL_STRICT` | Strict mode (no fallback) | `1` / `0` | `0` | ### Vendor Filtering | Variable | Description | Example | |----------|-------------|---------| | `TE_FL_ALLOW_VENDORS` | Allowed vendors (whitelist) | `nvidia,amd` | | `TE_FL_DENY_VENDORS` | Denied vendors (blacklist) | `vendor_a` | ### Per-Operator Configuration | Variable | Description | Example | |----------|-------------|---------| | `TE_FL_PER_OP` | Per-operator backend ordering | `rmsnorm_fwd=vendor:cuda\|default` | ### Plugin Discovery | Variable | Description | Example | |----------|-------------|---------| | `TE_FL_PLUGIN_MODULES` | Plugin modules to load | `my_plugin,another_plugin` | ### Build Configuration | Variable | Description | Values | Default | |----------|-------------|--------|---------| | `TE_FL_SKIP_CUDA` | Skip CUDA backend | `1` / `0` | `0` | | `CUDA_HOME` | CUDA installation path | `/usr/local/cuda` | Auto-detected | ### Logging | Variable | Description | Values | Default | |----------|-------------|--------|---------| | `TEFL_LOG_LEVEL` | Log level | `DEBUG` / `INFO` / `WARNING` / `ERROR` | `INFO` | ## Usage Examples ### Basic Usage (No Code Changes Required) ```python # Existing code works as-is import transformer_engine.pytorch as te # or import transformer_engine_torch as te ``` ### Register Custom Backend (In-tree) ```python from transformer_engine.plugin.core import ( OpRegistry, OpManager, OpImpl, BackendImplKind ) # 1. Define implementation def my_rmsnorm(input, weight, eps=1e-5, **kwargs): variance = input.pow(2).mean(-1, keepdim=True) return input * torch.rsqrt(variance + eps) * weight, torch.rsqrt(variance + eps) # 2. Register registry = OpRegistry() registry.register_impl(OpImpl( op_name="rmsnorm_fwd", impl_id="vendor.mybackend", kind=BackendImplKind.VENDOR, vendor="mybackend", fn=my_rmsnorm, priority=200, )) # 3. Call manager = OpManager(registry) output, rsigma = manager.call("rmsnorm_fwd", input, weight) ``` ### Register Custom Backend (Out-of-tree Plugin) Create a plugin package with `register(registry)` function: ```python # my_vendor_plugin/__init__.py from transformer_engine.plugin.core import OpImpl, BackendImplKind def my_rmsnorm(input, weight, eps=1e-5, **kwargs): # Your implementation ... def register(registry): """Called automatically by TE-FL""" registry.register_impl(OpImpl( op_name="rmsnorm_fwd", impl_id="vendor.myvendor", kind=BackendImplKind.VENDOR, vendor="myvendor", fn=my_rmsnorm, priority=200, )) ``` Load via environment variable: ```bash export TE_FL_PLUGIN_MODULES=my_vendor_plugin python your_script.py ``` ## Runtime Logs When running, you'll see logs indicating which backend is used: ``` [TE-FL manager.py:133 INFO] Registered impl_ids: ['default.flagos', 'reference.torch', 'vendor.cuda'] [TE-FL manager.py:390 INFO] Op 'rmsnorm_fwd' using 'default.flagos' (kind=default, vendor=None) [TE-FL manager.py:395 INFO] Op 'rmsnorm_fwd' switched from 'default.flagos' to 'vendor.cuda' (kind=vendor, vendor=CUDA) ``` ## Examples See `transformer_engine/plugins/examples/` for complete working examples: - `example_intree.py` - In-tree backend registration - `example_outtree.py` - Out-of-tree plugin registration Fixes # (issue) ## Type of change - [ ] Documentation change (change only to the documentation, either a fix or a new content) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Infra/Build change - [ ] Code refactoring ## Changes Please list the changes introduced in this PR: - Change A - Change B # Checklist: - [ ] I have read and followed the [contributing guidelines](https://github.com/NVIDIA/TransformerEngine/blob/main/CONTRIBUTING.rst) - [ ] The functionality is complete - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes --------- Co-authored-by: panpy <panpy@sugon.com>
1 parent ef41367 commit fd5f657

77 files changed

Lines changed: 10395 additions & 1051 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ compile_commands.json
4040
.nfs
4141
tensor_dumps/
4242
artifacts/
43+
# Auto-generated build configuration (specific to each environment)
44+
transformer_engine/plugin/core/_build_config.py

build_tools/pytorch.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ def setup_pytorch_extension(
8585
include_dirs = [str(path) for path in include_dirs]
8686
from torch.utils.cpp_extension import CppExtension
8787

88+
# Use transformer_engine_torch_nv as the native NVIDIA module name
89+
# This allows the plugin system to use transformer_engine_torch as the unified interface
8890
return CppExtension(
89-
name="transformer_engine_torch",
91+
name="transformer_engine_torch_nv",
9092
sources=[str(src) for src in sources],
9193
include_dirs=[str(inc) for inc in include_dirs],
9294
extra_compile_args={"cxx": cxx_flags},

build_tools/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,16 @@ def get_cuda_include_dirs() -> Tuple[str, str]:
251251
]
252252

253253

254+
@functools.lru_cache(maxsize=None)
255+
def skip_cuda_build() -> bool:
256+
"""Check if CUDA build should be skipped (for AMD/ROCm or pure FL backend)"""
257+
return bool(int(os.getenv("TE_FL_SKIP_CUDA", "0")))
258+
259+
254260
@functools.lru_cache(maxsize=None)
255261
def cuda_archs() -> str:
262+
if skip_cuda_build():
263+
return "" # Return empty string when skipping CUDA build
256264
archs = os.getenv("NVTE_CUDA_ARCHS")
257265
if archs is None:
258266
version = cuda_version()

setup.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929

3030
from setuptools.command.build_ext import build_ext as BuildExtension
31+
from setuptools.command.install import install as InstallCommand
32+
from datetime import datetime
33+
import platform
3134

3235
os.environ["NVTE_PROJECT_BUILDING"] = "1"
3336

@@ -41,6 +44,63 @@
4144
archs = cuda_archs()
4245

4346

47+
def generate_build_config(skip_cuda_build):
48+
"""Generate build-time configuration file."""
49+
config_template_path = (
50+
current_file_path / "transformer_engine" / "plugin" /
51+
"core" / "_build_config.py.template"
52+
)
53+
config_output_path = (
54+
current_file_path / "transformer_engine" / "plugin" /
55+
"core" / "_build_config.py"
56+
)
57+
58+
if config_template_path.exists():
59+
with open(config_template_path, 'r') as f:
60+
template = f.read()
61+
62+
config_content = template.format(
63+
skip_cuda=skip_cuda_build,
64+
build_time=datetime.now().isoformat(),
65+
platform=platform.platform(),
66+
)
67+
68+
with open(config_output_path, 'w') as f:
69+
f.write(config_content)
70+
71+
print(f"Generated build config: {config_output_path}")
72+
print(f" SKIP_CUDA_BUILD = {skip_cuda_build}")
73+
else:
74+
# Fallback: create minimal config if template doesn't exist
75+
config_content = f"""# Auto-generated build configuration
76+
SKIP_CUDA_BUILD = {skip_cuda_build}
77+
BUILD_TIME = "{datetime.now().isoformat()}"
78+
BUILD_PLATFORM = "{platform.platform()}"
79+
"""
80+
with open(config_output_path, 'w') as f:
81+
f.write(config_content)
82+
print(f"Generated minimal build config: {config_output_path}")
83+
84+
85+
class CustomInstall(InstallCommand):
86+
"""Custom install command to generate build config."""
87+
88+
user_options = InstallCommand.user_options + [
89+
('skip-cuda-build', None, 'Skip CUDA build'),
90+
]
91+
92+
def initialize_options(self):
93+
super().initialize_options()
94+
self.skip_cuda_build = bool(int(os.getenv("TE_FL_SKIP_CUDA", "0")))
95+
96+
def run(self):
97+
# Run the standard install
98+
super().run()
99+
100+
# Generate build config after installation
101+
generate_build_config(self.skip_cuda_build)
102+
103+
44104
class TimedBdist(bdist_wheel):
45105
"""Helper class to measure build time"""
46106

@@ -132,6 +192,14 @@ def setup_requirements() -> Tuple[List[str], List[str]]:
132192
with open("README.rst", encoding="utf-8") as f:
133193
long_description = f.read()
134194

195+
# Check if we should skip CUDA build (for AMD/ROCm or pure FL backend usage)
196+
skip_cuda_build = bool(int(os.getenv("TE_FL_SKIP_CUDA", "0")))
197+
if skip_cuda_build:
198+
print("=" * 60)
199+
print("TE_FL_SKIP_CUDA=1: Skipping CUDA/native backend compilation")
200+
print("Only FL (Flag-Gems/Triton) backend will be available")
201+
print("=" * 60)
202+
135203
# Settings for building top level empty package for dependency management.
136204
if bool(int(os.getenv("NVTE_BUILD_METAPACKAGE", "0"))):
137205
assert bool(
@@ -148,6 +216,13 @@ def setup_requirements() -> Tuple[List[str], List[str]]:
148216
"pytorch": [f"transformer_engine_torch=={__version__}"],
149217
"jax": [f"transformer_engine_jax=={__version__}"],
150218
}
219+
elif skip_cuda_build:
220+
# Skip CUDA build - only install Python packages for FL backend
221+
install_requires, test_requires = setup_requirements()
222+
ext_modules = [] # No CUDA extensions
223+
package_data = {"": ["VERSION.txt"]}
224+
include_package_data = True
225+
extras_require = {"test": test_requires}
151226
else:
152227
install_requires, test_requires = setup_requirements()
153228
ext_modules = [setup_common_extension()]
@@ -177,6 +252,9 @@ def setup_requirements() -> Tuple[List[str], List[str]]:
177252
)
178253
)
179254

255+
# Generate build config before setup
256+
generate_build_config(skip_cuda_build)
257+
180258
# Configure package
181259
setuptools.setup(
182260
name="transformer_engine",
@@ -193,7 +271,11 @@ def setup_requirements() -> Tuple[List[str], List[str]]:
193271
long_description=long_description,
194272
long_description_content_type="text/x-rst",
195273
ext_modules=ext_modules,
196-
cmdclass={"build_ext": CMakeBuildExtension, "bdist_wheel": TimedBdist},
274+
cmdclass={
275+
"build_ext": CMakeBuildExtension,
276+
"bdist_wheel": TimedBdist,
277+
"install": CustomInstall,
278+
},
197279
python_requires=f">={min_python_version_str()}",
198280
classifiers=["Programming Language :: Python :: 3"],
199281
install_requires=install_requires,

transformer_engine/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import os
1010
from importlib import metadata
11+
1112
import transformer_engine.common
1213

1314
try:

transformer_engine/common/__init__.py

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@
1818
from typing import Optional, Tuple
1919

2020

21+
def skip_cuda_build() -> bool:
22+
"""Check if CUDA build was skipped (FL-only mode).
23+
24+
First checks environment variable (for runtime override),
25+
then falls back to build-time configuration.
26+
"""
27+
# Environment variable takes precedence (allows runtime override)
28+
if os.environ.get("TE_FL_SKIP_CUDA"):
29+
return bool(int(os.environ.get("TE_FL_SKIP_CUDA", "0")))
30+
31+
# Fall back to build-time configuration
32+
try:
33+
from transformer_engine.plugin.core._build_config import SKIP_CUDA_BUILD
34+
return SKIP_CUDA_BUILD
35+
except ImportError:
36+
# If build config doesn't exist, default to False
37+
return False
38+
39+
# Load plugin system - this handles module registration and backend initialization
40+
# The _module_setup inside core will:
41+
# 1. Register modules under both full and short names for relative imports
42+
# 2. Load all available backends (flagos, reference, vendor/cuda, etc.)
43+
# 3. Register transformer_engine_torch module from the selected backend
44+
import transformer_engine.plugin.core # noqa: F401
45+
2146
@functools.lru_cache(maxsize=None)
2247
def _is_package_installed(package) -> bool:
2348
"""Check if the given package is installed via pip."""
@@ -146,46 +171,36 @@ def get_te_core_package_info() -> Tuple[bool, str, str]:
146171
@functools.lru_cache(maxsize=None)
147172
def load_framework_extension(framework: str) -> None:
148173
"""
149-
Load shared library with Transformer Engine framework bindings
150-
and check verify correctness if installed via PyPI.
174+
Load shared library with Transformer Engine framework bindings.
175+
176+
For PyTorch: The native module is now named transformer_engine_torch_nv,
177+
and transformer_engine_torch is provided by the plugin system.
178+
This function is kept for backward compatibility but does nothing for torch.
151179
"""
152180

181+
# Skip loading native extensions if CUDA build was skipped (FL-only mode)
182+
if skip_cuda_build():
183+
return
184+
153185
# Supported frameworks.
154186
assert framework in ("jax", "torch"), f"Unsupported framework {framework}"
155187

156-
# Name of the framework extension library.
188+
# For torch: plugin system already handles transformer_engine_torch
189+
# The native module is transformer_engine_torch_nv (imported by NVIDIA backend)
190+
if framework == "torch":
191+
return # Nothing to do, plugin system handles this
192+
193+
# For jax: load the native module as before
157194
module_name = f"transformer_engine_{framework}"
158195

159-
# Name of the pip extra dependency for framework extensions from PyPI.
160-
extra_dep_name = module_name
161-
if framework == "torch":
162-
extra_dep_name = "pytorch"
196+
# Skip if already loaded
197+
if module_name in sys.modules:
198+
return
163199

164-
# Find the TE packages. The core and framework packages can only be installed via PyPI.
165-
# For the `transformer-engine` package, we need to check explicity.
166-
te_core_installed, te_core_package_name, te_core_version = get_te_core_package_info()
167-
te_framework_installed = _is_package_installed(module_name)
168200
te_installed = _is_package_installed("transformer_engine")
169-
te_installed_via_pypi = _is_package_installed_from_wheel("transformer_engine")
170-
171201
assert te_installed, "Could not find `transformer_engine`."
172202

173-
# If the framework extension pip package is installed, it means that TE is installed via
174-
# PyPI. For this case we need to make sure that the metapackage, the core lib, and framework
175-
# extension are all installed via PyPI and have matching versions.
176-
if te_framework_installed:
177-
assert te_installed_via_pypi, "Could not find `transformer-engine` PyPI package."
178-
assert te_core_installed, "Could not find TE core package `transformer-engine-cu*`."
179-
180-
assert version(module_name) == version("transformer-engine") == te_core_version, (
181-
"Transformer Engine package version mismatch. Found"
182-
f" {module_name} v{version(module_name)}, transformer-engine"
183-
f" v{version('transformer-engine')}, and {te_core_package_name}"
184-
f" v{te_core_version}. Install transformer-engine using "
185-
f"'pip3 install --no-build-isolation transformer-engine[{extra_dep_name}]==VERSION'"
186-
)
187-
188-
# After all checks are completed, load the shared object file.
203+
# Load the shared object file for jax
189204
spec = importlib.util.spec_from_file_location(module_name, _get_shared_object_file(framework))
190205
solib = importlib.util.module_from_spec(spec)
191206
sys.modules[module_name] = solib
@@ -195,6 +210,10 @@ def load_framework_extension(framework: str) -> None:
195210
def sanity_checks_for_pypi_installation() -> None:
196211
"""Ensure that package is installed correctly if using PyPI."""
197212

213+
# Skip sanity checks if CUDA build was skipped (FL-only mode)
214+
if skip_cuda_build():
215+
return
216+
198217
te_core_installed, te_core_package_name, te_core_version = get_te_core_package_info()
199218
te_installed = _is_package_installed("transformer_engine")
200219
te_installed_via_pypi = _is_package_installed_from_wheel("transformer_engine")
@@ -390,13 +409,16 @@ def _load_core_library():
390409

391410
if "NVTE_PROJECT_BUILDING" not in os.environ or bool(int(os.getenv("NVTE_RELEASE_BUILD", "0"))):
392411
sanity_checks_for_pypi_installation()
393-
_CUDNN_LIB_CTYPES = _load_cudnn()
394-
_NVRTC_LIB_CTYPES = _load_nvrtc()
395-
_CURAND_LIB_CTYPES = _load_curand()
396-
_CUBLAS_LIB_CTYPES = _load_nvidia_cuda_library("cublas")
397-
_CUDART_LIB_CTYPES = _load_nvidia_cuda_library("cuda_runtime")
398-
_TE_LIB_CTYPES = _load_core_library()
399-
400-
# Needed to find the correct headers for NVRTC kernels.
401-
if not os.getenv("NVTE_CUDA_INCLUDE_DIR") and _nvidia_cudart_include_dir():
402-
os.environ["NVTE_CUDA_INCLUDE_DIR"] = _nvidia_cudart_include_dir()
412+
413+
# Skip loading CUDA libraries if CUDA build was skipped (FL-only mode)
414+
if not skip_cuda_build():
415+
_CUDNN_LIB_CTYPES = _load_cudnn()
416+
_NVRTC_LIB_CTYPES = _load_nvrtc()
417+
_CURAND_LIB_CTYPES = _load_curand()
418+
_CUBLAS_LIB_CTYPES = _load_nvidia_cuda_library("cublas")
419+
_CUDART_LIB_CTYPES = _load_nvidia_cuda_library("cuda_runtime")
420+
_TE_LIB_CTYPES = _load_core_library()
421+
422+
# Needed to find the correct headers for NVRTC kernels.
423+
if not os.getenv("NVTE_CUDA_INCLUDE_DIR") and _nvidia_cudart_include_dir():
424+
os.environ["NVTE_CUDA_INCLUDE_DIR"] = _nvidia_cudart_include_dir()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (c) 2025, BAAI. All rights reserved.
2+
#
3+
# See LICENSE for license information.
4+
5+
from .core import (
6+
TEFLBackendBase,
7+
TEFLModule,
8+
get_tefl_module as _get_tefl_module,
9+
get_registry,
10+
)
11+
12+
def __getattr__(name):
13+
if name == "tefl":
14+
return _get_tefl_module()
15+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
16+
17+
__all__ = [
18+
"TEFLBackendBase",
19+
"TEFLModule",
20+
"get_tefl_module",
21+
"get_registry",
22+
"tefl",
23+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) 2025, BAAI. All rights reserved.
2+
#
3+
# See LICENSE for license information.
4+
5+
__all__ = []

0 commit comments

Comments
 (0)