Abstract CUDA hardcodes into configurable te_device_type / te_platform - #3113
Abstract CUDA hardcodes into configurable te_device_type / te_platform#3113lxd-cumt wants to merge 8 commits into
Conversation
Greptile SummaryThis PR introduces a device-agnostic abstraction layer over the ~200 hardcoded
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant TE as transformer_engine.__init__
participant Plugin as vendor_plugin.patches
participant PYINIT as pytorch.__init__
participant Module as TE Module (e.g. backends.py)
User->>TE: import transformer_engine
TE->>TE: read NVTE_PLUGIN env var
alt NVTE_PLUGIN set
TE->>Plugin: import_module(plugin).apply_patches()
Plugin->>TE: "set TE_DEVICE_TYPE = "musa""
Plugin->>TE: "set TE_PLATFORM = torch.musa"
Plugin-->>TE: "patch torch.cuda.* → torch.musa.*"
end
TE->>PYINIT: from . import pytorch
PYINIT->>TE: "TE_DEVICE_TYPE = getattr(te, "TE_DEVICE_TYPE", "cuda")"
PYINIT->>PYINIT: define te_device_type() / te_platform()
PYINIT->>TE: "te.te_device_type = te_device_type"
PYINIT->>Module: import submodules
Module->>TE: from transformer_engine import te_device_type
Module->>Module: use te_device_type() / te_platform() at runtime
Reviews (7): Last reviewed commit: "Simplify plugin device/platform interfac..." | Re-trigger Greptile |
| dpa_utils._original_get_attention_backend = dpa_utils.get_attention_backend | ||
| # Replace dpa_utils.get_attention_backend with tex.get_attention_backend | ||
| # This allows each backend (FlagOS, CUDA, Reference) to control its own backend selection | ||
| dpa_utils.get_attention_backend = tex.get_attention_backend |
There was a problem hiding this comment.
AttributeError at import time breaks the PyTorch module
tex is transformer_engine_torch (the C++ extension), which does not expose a get_attention_backend attribute. Accessing tex.get_attention_backend directly on line 75 (without a getattr guard) raises AttributeError the moment transformer_engine.pytorch is imported, making the entire PyTorch backend unusable on any standard CUDA installation. The previous line (69) correctly uses getattr(tex, "flash_attention", _FlashAttentionNative) with a fallback — the same pattern must be applied here, or the unconditional attribute access must be removed.
| try: | ||
| from transformer_engine_torch import bulk_overlap_ag_with_external_gemm | ||
| except ImportError: | ||
| bulk_overlap_ag_with_external_gemm = None |
There was a problem hiding this comment.
NoneType call crash in backward pass when bulk_overlap_ag_with_external_gemm is unavailable
The import is now guarded (= None on failure), but line 435 calls bulk_overlap_ag_with_external_gemm(ub_obj_overlap_wgrad, dgrad_send_stream, dgrad_recv_stream) unconditionally. Whenever this code path is hit in a tensor-parallel row-overlap backward pass on a system where this symbol is absent, a TypeError: 'NoneType' object is not callable is raised at runtime rather than at import time. A guard like if bulk_overlap_ag_with_external_gemm is not None: (or raising a descriptive error earlier) is needed at the call site.
| try: | ||
| from .plugin.core.backends.vendor.musa.patches import apply_patch as _musa_apply_patch | ||
|
|
||
| _musa_apply_patch() | ||
| except Exception as e: | ||
| pass |
There was a problem hiding this comment.
Silent
except Exception: pass hides all MUSA patch failures
The bare except Exception as e: pass swallows every failure during the MUSA patch import — including AttributeError raised when torch.musa.* attributes referenced in _PATCH_CALLS don't exist on standard CUDA systems. The variable e is never logged or inspected. On CUDA systems this is the common path (no torch.musa), so every import of transformer_engine silently triggers and discards an exception. At minimum, emit a logging.debug or use a narrower exception type (e.g., ImportError) and let other errors propagate.
| # Patches: (parent_object, attribute_name, replacement_callable) | ||
| _PATCH_CALLS: list[tuple[object, str, Callable[..., object]]] = [ | ||
| # We do not recommend replace is_available, due to its device-related behavior. | ||
| # (torch.cuda, "is_available", torch.musa.is_available), | ||
| (torch.cuda, "get_device_properties", torch.musa.get_device_properties), | ||
| (torch.cuda, "device", torch.musa.device), | ||
| (torch.cuda, "current_device", torch.musa.current_device), | ||
| (torch.cuda, "synchronize", torch.musa.synchronize), | ||
| (torch.cuda, "is_current_stream_capturing", torch.musa.is_current_stream_capturing), | ||
| # TODO: Add NVTX patches for MUSA. | ||
| # NVTX is CUDA-specific; make it a no-op on MUSA. | ||
| (torch.cuda.nvtx, "range_push", _noop), | ||
| (torch.cuda.nvtx, "range_pop", _noop), |
There was a problem hiding this comment.
_PATCH_CALLS accesses torch.musa.* at module load time
_PATCH_CALLS is a module-level list that dereferences torch.musa.get_device_properties, torch.musa.device, etc. when patches.py is imported. On any system without torch_musa, this raises AttributeError the moment the import is attempted. The caller in __init__.py wraps this in a blanket except Exception: pass, so it fails silently, but it means the module is broken by construction on non-MUSA hosts. Deferring the attribute lookups to inside apply_patch() (where hasattr(torch, "musa") is already checked) would make the module safe to import on all platforms.
| # Mark TE global device type for Python-side callers. | ||
| # IMPORTANT: do not import `transformer_engine` here, because TE's `__init__.py` | ||
| # imports this module to run patches and that would cause a circular import. | ||
| try: | ||
| import transformer_engine | ||
|
|
||
| transformer_engine.TE_DEVICE_TYPE = "musa" | ||
| transformer_engine.TE_PLATFORM = torch.musa |
There was a problem hiding this comment.
The comment warns against importing
transformer_engine here due to a circular-import risk, but the very next lines do exactly that. During transformer_engine/__init__.py execution Python's import cache returns the partially-initialized module, so TE_DEVICE_TYPE (set before the patch call) is reachable and the assignment works — but the comment creates a false sense of safety and the approach is still fragile if the import order ever changes.
| # Mark TE global device type for Python-side callers. | |
| # IMPORTANT: do not import `transformer_engine` here, because TE's `__init__.py` | |
| # imports this module to run patches and that would cause a circular import. | |
| try: | |
| import transformer_engine | |
| transformer_engine.TE_DEVICE_TYPE = "musa" | |
| transformer_engine.TE_PLATFORM = torch.musa | |
| # Mark TE global device type for Python-side callers. | |
| # NOTE: importing `transformer_engine` here re-enters a partially-initialised module | |
| # (its __init__.py is still running), but Python's import cache makes this safe as long | |
| # as TE_DEVICE_TYPE is assigned before apply_patch() is called. | |
| try: | |
| import transformer_engine | |
| transformer_engine.TE_DEVICE_TYPE = "musa" | |
| transformer_engine.TE_PLATFORM = torch.musa |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
Thanks for the review! I've addressed the above Greptile comments. |
Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
…th explicit raise Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
|
Should this PR be merged into the |
ptrendx
left a comment
There was a problem hiding this comment.
Left some comments. Could we use this PR also as an opportunity to take a look at each of the cases where we hardcoded the device type and see if we can instead use a device from e.g. the input tensor instead?
…edundant code - Move TE_DEVICE_TYPE, te_device_type, TE_PLATFORM, te_platform from top-level __init__.py to transformer_engine/pytorch/__init__.py since they depend on torch and the top-level package is shared with JAX. - Remove unnecessary rng_state patch in context_parallel.py - Remove unnecessary fused_attention thd check in utils.py - Remove extra blank line in linear.py - Remove redundant device-is-None check in float8_blockwise_tensor.py Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
Replace NVTE_ENABLE_PLUGIN=1 with NVTE_PLUGIN=<module_name> so the plugin module is not hardcoded. Any plugin implementing <module>.patches.apply_patches() can be loaded via this env var. Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
Thanks for the review, I have addressed the comments. |
| # Plugin system: set NVTE_PLUGIN to the plugin module name to enable. | ||
| # e.g. NVTE_PLUGIN=transformer_engine_plugin_fl | ||
| _nvte_plugin = os.environ.get("NVTE_PLUGIN") | ||
| if _nvte_plugin: | ||
| try: | ||
| from importlib import import_module | ||
|
|
||
| _patches = import_module(f"{_nvte_plugin}.patches") | ||
| _patches.apply_patches() | ||
| except Exception as e: | ||
| import warnings | ||
|
|
||
| warnings.warn( | ||
| f"NVTE_PLUGIN={_nvte_plugin} but plugin patch apply failed: {e}", | ||
| RuntimeWarning, | ||
| stacklevel=1, | ||
| ) |
There was a problem hiding this comment.
Is this needed in addition to #3107 or should it be removed once that PR is merged?
There was a problem hiding this comment.
Previous pr #3401 replaces ops within transformer_engine_torch, whereas this patch targets torch.cuda-related APIs repo-wide. Despite the similar plugin load mechanism, they address orthogonal concerns. I suggest retaining this.
| try: | ||
| return transformer_engine.TE_DEVICE_TYPE | ||
| except Exception: | ||
| return default |
There was a problem hiding this comment.
Considering that we set this value just above, why do we need try/except here? And if there was some
chance that this value got deleted somehow, then it should probably be an error anyway rather than
a quiet ignore.
| try: | ||
| return transformer_engine.TE_PLATFORM | ||
| except Exception: | ||
| return default |
| if not hasattr(transformer_engine, "TE_DEVICE_TYPE"): | ||
| transformer_engine.TE_DEVICE_TYPE = "cuda" | ||
| TE_DEVICE_TYPE = transformer_engine.TE_DEVICE_TYPE |
There was a problem hiding this comment.
Actually I'm not sure why this code lives here. We should be able to just set TE_DEVICE_TYPE (and
platform) inside transformer_engine/__init__.py before loading of the patches. Also, do we actually
need this very generic patches interface or can we just set those values directly (plugin would just
There was a problem hiding this comment.
TE_PLATFORM defaults to torch.cuda, which requires torch to be importable. The top-level transformer_engine/__init__.py is shared with
JAX and does not import torch, so both TE_DEVICE_TYPE and TE_PLATFORM defaults are set here to keep them co-located. The functions
te_device_type() and te_platform() are then registered onto the top-level package for use by other submodules.
Regarding the generic patches interface: apply_patches() does more than setting these two values — it also monkey-patches torch.cuda.*
APIs (e.g. current_device, synchronize, get_device_properties) for non-CUDA backends. A direct-set approach would not cover those.
| if is_training and device_compute_capability >= (10, 0): | ||
| logger.debug("Disabling FusedAttention for determinism reasons on Blackwell") | ||
| use_fused_attention = False | ||
| fused_attention_backend = None |
| _alibi_cache["_alibi_bias"] = bias.contiguous().to( | ||
| dtype=bias_dtype, device=te_device_type() | ||
| ) |
There was a problem hiding this comment.
Here the bias should already be on the device, so probably we could just omit the device parameter
here entirely.
| """ | ||
| row_id_map = torch.empty((num_tokens, num_experts * 2 + 1), dtype=torch.int32, device="cuda") | ||
| row_id_map = torch.empty( | ||
| (num_tokens, num_experts * 2 + 1), dtype=torch.int32, device=te_device_type() |
| if device.type != "cuda": | ||
| device = torch.device("cuda", torch.cuda.current_device()) | ||
| if device.type != te_device_type(): | ||
| device = torch.device(te_device_type(),te_platform().current_device()) |
- Remove unnecessary try/except in te_device_type() and te_platform() - Simplify TE_DEVICE_TYPE/TE_PLATFORM defaults using getattr - Remove unrelated Blackwell FusedAttention change - Use routing_map.device instead of te_device_type() in permutation - Fix missing space after comma in utils.py Signed-off-by: Xianduo Li <lixianduo@mail.nankai.edu.cn>
|
A few questions for discussion:
|
FlagOS Proposal: Plugin Architecture & Device-Agnostic Abstraction for TransformerEngine
Device-Type Abstraction: Replacing Hardcoded
"cuda"ReferencesThe current TE PyTorch layer contains ~100 hardcoded
"cuda"string literals and ~165torch.cuda.*API calls. These span device placement (device="cuda"), autocast context (device_type="cuda"), device-type guards (device.type == "cuda"), and RNG state management (torch.cuda.CUDAGraph,torch.cuda._lazy_call). This makes TE non-functional on alternative accelerator platforms without invasive patching.Proposed Design
Soft abstraction – A global
te_device_type()/te_platform()accessor replaces ~200 literal"cuda"strings across the Python codebase.Platform monkey-patch – A vendor-provided
apply_patch()hook runs at import time to directly remaptorch.cuda.*APIs (e.g.torch.cuda.device,torch.cuda.current_device,torch.cuda.current_stream) to the vendor equivalents (e.g.torch.other_vendor.*).