Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / torch / dtensor.py: 65%
108 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 2025-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 IS" 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# ============================================================================
15"""torch dtensor base"""
16from typing import Tuple, Dict, Any, Optional
17import torch
18from torch import Tensor
21class DTensorBase(Tensor):
22 """torch dtensor base"""
24 def __new__(cls, local_tensor, device_mesh=None, placements=None, layout=None):
25 """
26 Create a new DTensorBase instance.
28 Args:
29 local_tensor: The local tensor shard or another DTensorBase instance.
30 device_mesh: The device mesh describing the device topology.
31 placements: The placement strategy for each mesh dimension.
32 layout: Optional pre-built Layout reused directly by ``__init_data__``
33 (skips ``_build_layout``; see ``DTensor.from_local_with_layout``).
34 """
35 if isinstance(local_tensor, DTensorBase):
36 # Copy from existing DTensorBase — use alias_placements to preserve multi-axis ordering
37 t = Tensor._make_subclass(cls, local_tensor._local_tensor, local_tensor._local_tensor.requires_grad)
38 copy_placements = local_tensor.layout.alias_placements if local_tensor.layout else local_tensor.placements
39 t.__init_data__(local_tensor._local_tensor, local_tensor.device_mesh, copy_placements)
40 return t
42 if device_mesh is None:
43 raise ValueError("device_mesh is None, must provide a DeviceMesh instance")
44 if placements is None:
45 raise ValueError("placements is None, must provide placements")
47 # Create Tensor subclass instance, sharing local_tensor's underlying storage
48 t = Tensor._make_subclass(cls, local_tensor, local_tensor.requires_grad)
49 t.__init_data__(local_tensor, device_mesh, placements, layout)
50 return t
52 # pylint: disable=W0613, G.NAM.05
53 @classmethod
54 def __torch_function__(
55 cls,
56 func: torch._C._FunctionBase,
57 types: Tuple[type, ...],
58 args: Tuple[Any, ...] = (),
59 kwargs: Optional[Dict[str, Any]] = None
60 ) -> Any:
61 """
62 Override PyTorch's __torch_function__ to intercept tensor operations.
64 This method dispatches operations through the distributed operator dispatcher
65 to handle DTensor-specific layout inference and redistribution.
67 Args:
68 func (torch._C._FunctionBase): The PyTorch function being called.
69 types (Tuple[type, ...]): The types of tensors involved in the operation.
70 args (Tuple[Any, ...]): Positional arguments passed to the function.
71 kwargs (Optional[Dict[str, Any]]): Keyword arguments passed to the function.
73 Returns:
74 Any: The result of the dispatched operation, typically a DTensor or tuple of DTensors.
75 """
76 kwargs = kwargs or {}
77 # pylint: disable=C0415
78 from hyper_parallel.core.shard._op_dispatch import _OP_DISPATCHER
79 out = _OP_DISPATCHER.dispatch(func, args, kwargs)
80 return out
82 @property
83 def grad(self) -> Optional[Tensor]:
84 """
85 Get the gradient tensor of the local tensor.
87 Returns:
88 Optional[Tensor]: The gradient tensor, or None if no gradient is set.
89 """
90 return self._local_tensor.grad
92 @grad.setter
93 def grad(self, value: Optional[Tensor]) -> None:
94 """
95 Set the gradient tensor for the local tensor.
97 Args:
98 value (Optional[Tensor]): The gradient tensor to set, or None to clear.
99 """
100 self._local_tensor.grad = value
102 @property
103 def requires_grad(self) -> bool:
104 """
105 Check if gradient computation is enabled for this tensor.
107 Returns:
108 bool: True if gradients should be computed for this tensor.
109 """
110 return self._local_tensor.requires_grad
112 @requires_grad.setter
113 def requires_grad(self, value: bool) -> None:
114 """
115 Enable or disable gradient computation for this tensor.
117 Args:
118 value (bool): True to enable gradient computation, False to disable.
119 """
120 self._local_tensor.requires_grad_(value)
121 # Sync DTensor wrapper's requires_grad
122 super().requires_grad_(value)
124 def requires_grad_(self, requires_grad: bool = True):
125 """
126 Enable or disable gradient computation in-place.
128 Args:
129 requires_grad (bool): True to enable gradient computation. Default: True.
131 Returns:
132 DTensorBase: Self for method chaining.
133 """
134 self._local_tensor.requires_grad_(requires_grad)
135 super().requires_grad_(requires_grad)
136 return self
138 @property
139 def grad_fn(self) -> Optional[torch.autograd.Function]:
140 """
141 Get the gradient function that created this tensor.
143 Returns:
144 Optional[torch.autograd.Function]: The gradient function, or None if not applicable.
145 """
146 return self._local_tensor.grad_fn
148 def grad_zero_(self):
149 """
150 Zero out the gradient tensor in-place.
152 Returns:
153 DTensorBase: Self for method chaining.
154 """
155 if self._local_tensor.grad is not None:
156 self._local_tensor.grad.zero_()
157 return self
159 def detach(self):
160 """
161 Create a detached DTensor that does not require gradient.
163 Returns:
164 DTensorBase: A new DTensor with the same data but detached from the computation graph.
165 """
166 detached_local = self._local_tensor.detach()
167 return self.__class__(detached_local, device_mesh=self._device_mesh, placements=self._alias_placements())
169 def detach_(self):
170 """
171 Detach this tensor from the computation graph in-place.
173 Returns:
174 DTensorBase: Self for method chaining.
175 """
176 self._local_tensor.detach_()
177 super().detach_()
178 return self
180 # ====================== Computation graph related overrides ======================
181 @property
182 def is_leaf(self) -> bool:
183 """
184 Check if this tensor is a leaf node in the computation graph.
186 Returns:
187 bool: True if this is a leaf tensor (created by user, not by any operation).
188 """
189 return self._local_tensor.is_leaf
191 @property
192 def retains_grad(self) -> bool:
193 """
194 Check if this tensor retains its gradient during backward pass.
196 Returns:
197 bool: True if gradients are retained for non-leaf tensors.
198 """
199 return self._local_tensor.retains_grad
201 @retains_grad.setter
202 def retains_grad(self, value: bool) -> None:
203 """
204 Enable or disable gradient retention for this tensor.
206 Args:
207 value (bool): True to enable gradient retention.
208 """
209 self._local_tensor.retains_grad_(value)
211 def backward(self, gradient=None, retain_graph=None, create_graph=False) -> None:
212 """
213 Compute the gradients for this tensor.
215 Args:
216 gradient (Optional[Tensor]): The gradient of the loss w.r.t. this tensor.
217 retain_graph (Optional[bool]): Whether to retain the computation graph.
218 create_graph (bool): Whether to create a graph of the gradient computation.
219 """
220 self._local_tensor.backward(gradient, retain_graph, create_graph)
222 # ====================== Metadata related overrides (sync with local_tensor) ======================
223 @property
224 def device(self) -> torch.device:
225 """
226 Get the device on which this tensor is stored.
228 Returns:
229 torch.device: The device object (e.g., 'cuda:0', 'cpu').
230 """
231 return self._local_tensor.device
233 @property
234 # pylint: disable=C2801
235 def data(self):
236 """Return the underlying Tensor's data view, bypassing DTensor wrappers."""
237 return Tensor.data.__get__(self, type(self))
239 @data.setter
240 # pylint: disable=C2801
241 def data(self, value):
242 """Set the underlying tensor data, extracting the local shard if a DTensor is given."""
243 local_value = value.to_local() if isinstance(value, DTensorBase) else value
244 # Tensor.data.__set__ on a Tensor subclass otherwise enters __torch_function__
245 # and only rebinds _local_tensor through DTensor dispatch.
246 with getattr(torch, "_C").DisableTorchFunctionSubclass():
247 Tensor.data.__set__(self, local_value)
248 Tensor.data.__set__(self._local_tensor, local_value)
250 @property
251 def dtype(self) -> torch.dtype:
252 """
253 Get the data type of this tensor.
255 Returns:
256 torch.dtype: The data type (e.g., torch.float32, torch.int64).
257 """
258 return self._local_tensor.dtype
260 @property
261 def shape(self) -> torch.Size:
262 """
263 Get the shape of this tensor.
265 Returns:
266 torch.Size: The shape of the tensor.
267 """
268 return self._local_tensor.shape
270 def type(self, dtype=None, non_blocking=False):
271 """
272 Convert this tensor to the specified dtype.
274 Args:
275 dtype (Optional[torch.dtype]): The target dtype. If None, returns the current type string.
276 non_blocking (bool): Whether to perform the operation asynchronously. Default: False.
278 Returns:
279 Union[str, DTensorBase]: The type string if dtype is None, otherwise a new DTensor.
280 """
281 if dtype is None:
282 return self._local_tensor.type()
283 new_local = self._local_tensor.to(dtype=dtype, non_blocking=non_blocking)
284 return self.__class__(new_local, device_mesh=self._device_mesh, placements=self._alias_placements())
286 def size(self, dim: Optional[int] = None):
287 """
288 Get the size of this tensor.
290 Args:
291 dim (Optional[int]): The dimension to query. If None, returns the full shape.
293 Returns:
294 Union[torch.Size, int]: The shape or size along a specific dimension.
295 """
296 return self._local_tensor.size(dim)
298 @property
299 def ndim(self) -> int:
300 """
301 Get the number of dimensions of this tensor.
303 Returns:
304 int: The number of dimensions.
305 """
306 return self._local_tensor.ndim
308 def data_ptr(self) -> int:
309 """
310 Get the pointer to the data storage of the local tensor.
312 Returns:
313 int: The memory address of the tensor's data.
314 """
315 # Force return local_tensor's data pointer (ensure address consistency)
316 return self._local_tensor.data_ptr()
318 def numel(self) -> int:
319 """
320 Get the total number of elements in this tensor.
322 Returns:
323 int: The total number of elements.
324 """
325 return self._local_tensor.numel()
327 # ====================== Auxiliary print ======================
328 def _alias_placements(self):
329 """Return alias_placements from layout, falling back to _placements."""
330 if hasattr(self, '_layout') and self._layout is not None:
331 return self._layout.alias_placements
332 return self._placements
334 def to(self, *args, **kwargs):
335 """Move the DTensor to a different device or dtype.
337 This method overrides the base Tensor.to() to properly reconstruct
338 a DTensor with device_mesh and placements preserved. Uses _make_subclass
339 to avoid issues with Parameter subclasses that don't accept extra kwargs.
341 Args:
342 *args: Arguments passed to the underlying tensor's to() method.
343 **kwargs: Keyword arguments for the tensor conversion.
345 Returns:
346 DTensorBase: A new DTensor with the converted local tensor.
347 """
348 new_local = self._local_tensor.to(*args, **kwargs)
349 new_dt = Tensor._make_subclass(type(self), new_local, new_local.requires_grad)
350 new_dt.__init_data__(new_local, self._device_mesh, self._alias_placements())
351 return new_dt
353 def __repr__(self) -> str:
354 return (
355 f"DTensor(\n"
356 f" local_tensor={self._local_tensor},\n"
357 f" device_mesh={self._device_mesh},\n"
358 f" placements={self._placements},\n"
359 f" layout={getattr(self, '_layout', None)},\n"
360 f" device={self.device},\n"
361 f" dtype={self.dtype},\n"
362 f" requires_grad={self.requires_grad},\n"
363 f" grad={self.grad},\n"
364 f" is_leaf={self.is_leaf},\n"
365 f" data_ptr={self.data_ptr()}\n"
366 f")"
367 )