Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / optimizer / dtensor_compat.py: 0%
86 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-04 05:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-04 05:18 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
16"""DTensor backend compatibility layer for the optimizer module.
18Provides lazy exports (PEP 562) for DTensor, DeviceMesh, Shard, Replicate,
19and StridedShard based on the detected backend ('torch' or 'hyper').
20"""
22from __future__ import annotations
24import logging
25from typing import Any, Dict, List
27import torch.distributed._tensor as torch_dt
29logger = logging.getLogger(__name__)
31# Global backend flag
32_DTENSOR_BACKEND: str = "hyper" # "hyper" or "torch"
35class _NeverMatch:
36 """Safe fallback class that always returns False for ``isinstance()``."""
37 __slots__ = ()
40# Lazy-export cache
41_LAZY_CACHE: Dict[str, Any] = {}
44def _invalidate_lazy_cache() -> None:
45 """Clear the lazy-export cache to rebuild on next access."""
46 _LAZY_CACHE.clear()
49def detect_dtensor_backend(
50 adamw_params: List[Any],
51 muon_params: List[Any],
52) -> str:
53 """Detect and set the DTensor backend ('torch' or 'hyper') from parameter lists."""
54 global _DTENSOR_BACKEND # pylint: disable=global-statement
56 sample_param = _extract_first_param(muon_params)
58 if sample_param is None:
59 sample_param = _extract_first_param(adamw_params)
61 if sample_param is None:
62 logger.info_rank0("No parameters found for backend detection; defaulting to 'hyper'.")
63 _DTENSOR_BACKEND = "hyper"
64 _invalidate_lazy_cache()
65 return _DTENSOR_BACKEND
67 param_cls_module = type(sample_param).__module__
68 if param_cls_module.startswith("torch.distributed"):
69 _DTENSOR_BACKEND = "torch"
70 else:
71 _DTENSOR_BACKEND = "hyper"
73 logger.info_rank0("Detected DTensor backend: '%s'.", _DTENSOR_BACKEND)
74 _invalidate_lazy_cache()
75 return _DTENSOR_BACKEND
78def _extract_first_param(param_groups: List[Any]) -> Any:
79 """Return the first parameter from a list of param groups, or None."""
80 for group in param_groups:
81 params = group.get("params", []) if isinstance(group, dict) else []
82 for p in params:
83 return p
85 for p in param_groups:
86 return p
88 return None
91# Accessor functions
92def get_dtensor_cls():
93 """Return the DTensor class for the active backend."""
94 if _DTENSOR_BACKEND == "torch":
95 return torch_dt.DTensor
96 from hyper_parallel.core.dtensor.dtensor import DTensor # pylint: disable=import-outside-toplevel
97 return DTensor
100def get_device_mesh_cls():
101 """Return the DeviceMesh class for the active backend."""
102 if _DTENSOR_BACKEND == "torch":
103 from torch.distributed.device_mesh import DeviceMesh # pylint: disable=import-outside-toplevel
104 return DeviceMesh
105 from hyper_parallel.core.dtensor.device_mesh import DeviceMesh # pylint: disable=import-outside-toplevel
106 return DeviceMesh
109def get_shard_cls():
110 """Return the Shard placement class for the active backend."""
111 if _DTENSOR_BACKEND == "torch":
112 from torch.distributed._tensor.placement_types import Shard # pylint: disable=import-outside-toplevel
113 return Shard
114 from hyper_parallel.core.dtensor.placement_types import Shard # pylint: disable=import-outside-toplevel
115 return Shard
118def get_replicate_cls():
119 """Return the Replicate placement class for the active backend."""
120 if _DTENSOR_BACKEND == "torch":
121 from torch.distributed._tensor.placement_types import Replicate # pylint: disable=import-outside-toplevel
122 return Replicate
123 from hyper_parallel.core.dtensor.placement_types import Replicate # pylint: disable=import-outside-toplevel
124 return Replicate
127def get_strided_shard_cls():
128 """Return the StridedShard placement class. Returns _NEVER_MATCH for 'torch'."""
129 if _DTENSOR_BACKEND == "torch":
130 return _NeverMatch
132 from hyper_parallel.core.dtensor.placement_types import StridedShard # pylint: disable=import-outside-toplevel
133 return StridedShard
136# DTensor union type resolver
137def _import_hyper_dtensor():
138 """Import hyper DTensor class; return torch DTensor as fallback."""
139 try:
140 from hyper_parallel.core.dtensor.dtensor import DTensor # pylint: disable=import-outside-toplevel
141 return DTensor
142 except ImportError:
143 return torch_dt.DTensor
146def _resolve_dtensor_union():
147 """Build ``torch_dt.DTensor | hyper_dt.DTensor`` on demand."""
148 return torch_dt.DTensor | _import_hyper_dtensor()
151def to_local_if_dtensor(tensor: Any) -> Any:
152 """Return the local shard if `tensor` is a DTensor, otherwise return as-is."""
153 # Use resolver directly for internal module lookups instead of lazy-loaded DTensor
154 dtensor_type = _LAZY_CACHE.get("DTensor") or _resolve_dtensor_union()
155 return tensor.to_local() if isinstance(tensor, dtensor_type) else tensor
158# lazy exports
159_LAZY_RESOLVERS = {
160 "DTensor": _resolve_dtensor_union,
161 "DeviceMesh": get_device_mesh_cls,
162 "Shard": get_shard_cls,
163 "Replicate": get_replicate_cls,
164 "StridedShard": get_strided_shard_cls,
165}
168def __getattr__(name): # type: ignore[no-untyped-def] # pylint: disable=invalid-name
169 """Resolve module attributes on first access."""
170 resolver = _LAZY_RESOLVERS.get(name)
171 if resolver is not None:
172 value = _LAZY_CACHE.get(name)
173 if value is None:
174 value = resolver()
175 _LAZY_CACHE[name] = value
176 return value
177 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
180def __dir__(): # type: ignore[no-untyped-def] # pylint: disable=invalid-name
181 """Include lazy-exported names in dir() for IDE autocomplete."""
182 return list(globals().keys()) + list(_LAZY_RESOLVERS.keys())