Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / dtensor / dtensor_base.py: 69%

109 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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 ``DTensorBase``: the platform-agnostic base of :class:`DTensor`.""" 

16# pylint: disable=C9006,C9007 

17from typing import Tuple, Dict, Any, Optional 

18import torch 

19from torch import Tensor 

20 

21 

22class DTensorBase(Tensor): 

23 """Base class of :class:`DTensor`: a ``torch.Tensor`` subclass that owns a local shard. 

24 

25 Holds the local shard plus the ``DeviceMesh``/``Placements`` describing how that shard 

26 maps onto the global tensor, and routes every op through the distributed dispatcher. 

27 """ 

28 

29 def __new__(cls, local_tensor, device_mesh=None, placements=None, layout=None, shape=None): 

30 """ 

31 Create a new DTensorBase instance. 

32 

33 Args: 

34 local_tensor: The local tensor shard or another DTensorBase instance. 

35 device_mesh: The device mesh describing the device topology. 

36 placements: The placement strategy for each mesh dimension. 

37 layout: Optional pre-built Layout reused directly by ``__init_data__`` 

38 (skips ``_build_layout``; see ``DTensor.from_local_with_layout``). 

39 shape: Optional logical global tensor shape. 

40 """ 

41 if isinstance(local_tensor, DTensorBase): 

42 # Copy from existing DTensorBase — use alias_placements to preserve multi-axis ordering 

43 t = Tensor._make_subclass(cls, local_tensor._local_tensor, local_tensor._local_tensor.requires_grad) 

44 copy_placements = local_tensor.layout.alias_placements if local_tensor.layout else local_tensor.placements 

45 t.__init_data__( 

46 local_tensor._local_tensor, 

47 local_tensor.device_mesh, 

48 copy_placements, 

49 shape=getattr(local_tensor, "_global_shape", None), 

50 ) 

51 return t 

52 

53 if device_mesh is None: 

54 raise ValueError("device_mesh is None, must provide a DeviceMesh instance") 

55 if placements is None: 

56 raise ValueError("placements is None, must provide placements") 

57 

58 # Create Tensor subclass instance, sharing local_tensor's underlying storage 

59 t = Tensor._make_subclass(cls, local_tensor, local_tensor.requires_grad) 

60 t.__init_data__(local_tensor, device_mesh, placements, layout, shape) 

61 return t 

62 

63 # pylint: disable=W0613, G.NAM.05 

64 @classmethod 

65 def __torch_function__( 

66 cls, 

67 func: torch._C._FunctionBase, 

68 types: Tuple[type, ...], 

69 args: Tuple[Any, ...] = (), 

70 kwargs: Optional[Dict[str, Any]] = None 

71 ) -> Any: 

72 """ 

73 Override PyTorch's __torch_function__ to intercept tensor operations. 

74 

75 This method dispatches operations through the distributed operator dispatcher 

76 to handle DTensor-specific layout inference and redistribution. 

77 

78 Args: 

79 func (torch._C._FunctionBase): The PyTorch function being called. 

80 types (Tuple[type, ...]): The types of tensors involved in the operation. 

81 args (Tuple[Any, ...]): Positional arguments passed to the function. 

82 kwargs (Optional[Dict[str, Any]]): Keyword arguments passed to the function. 

83 

84 Returns: 

85 Any: The result of the dispatched operation, typically a DTensor or tuple of DTensors. 

86 """ 

87 kwargs = kwargs or {} 

88 # pylint: disable=C0415 

89 from hyper_parallel.core.shard._op_dispatch import _OP_DISPATCHER 

90 out = _OP_DISPATCHER.dispatch(func, args, kwargs) 

91 return out 

92 

93 @property 

94 def grad(self) -> Optional[Tensor]: 

95 """ 

96 Get the gradient tensor of the local tensor. 

97 

98 Returns: 

99 Optional[Tensor]: The gradient tensor, or None if no gradient is set. 

100 """ 

101 return self._local_tensor.grad 

102 

103 @grad.setter 

104 def grad(self, value: Optional[Tensor]) -> None: 

105 """ 

106 Set the gradient tensor for the local tensor. 

107 

108 Args: 

109 value (Optional[Tensor]): The gradient tensor to set, or None to clear. 

110 """ 

111 self._local_tensor.grad = value 

112 

113 @property 

114 def requires_grad(self) -> bool: 

115 """ 

116 Check if gradient computation is enabled for this tensor. 

117 

118 Returns: 

119 bool: True if gradients should be computed for this tensor. 

120 """ 

121 return self._local_tensor.requires_grad 

122 

123 @requires_grad.setter 

124 def requires_grad(self, value: bool) -> None: 

125 """ 

126 Enable or disable gradient computation for this tensor. 

127 

128 Args: 

129 value (bool): True to enable gradient computation, False to disable. 

130 """ 

131 self._local_tensor.requires_grad_(value) 

132 # Sync DTensor wrapper's requires_grad 

133 super().requires_grad_(value) 

134 

135 def requires_grad_(self, requires_grad: bool = True): 

136 """ 

137 Enable or disable gradient computation in-place. 

138 

139 Args: 

140 requires_grad (bool): True to enable gradient computation. Default: True. 

141 

142 Returns: 

143 DTensorBase: Self for method chaining. 

144 """ 

145 self._local_tensor.requires_grad_(requires_grad) 

146 super().requires_grad_(requires_grad) 

147 return self 

148 

149 @property 

150 def grad_fn(self) -> Optional[torch.autograd.Function]: 

151 """ 

152 Get the gradient function that created this tensor. 

153 

154 Returns: 

155 Optional[torch.autograd.Function]: The gradient function, or None if not applicable. 

156 """ 

157 return self._local_tensor.grad_fn 

158 

159 def grad_zero_(self): 

160 """ 

161 Zero out the gradient tensor in-place. 

162 

163 Returns: 

164 DTensorBase: Self for method chaining. 

165 """ 

166 if self._local_tensor.grad is not None: 

167 self._local_tensor.grad.zero_() 

168 return self 

169 

170 def detach(self): 

171 """ 

172 Create a detached DTensor that does not require gradient. 

173 

174 Returns: 

175 DTensorBase: A new DTensor with the same data but detached from the computation graph. 

176 """ 

177 detached_local = self._local_tensor.detach() 

178 return self.__class__( 

179 detached_local, 

180 device_mesh=self._device_mesh, 

181 placements=self._alias_placements(), 

182 shape=getattr(self, "_global_shape", None), 

183 ) 

184 

185 def detach_(self): 

186 """ 

187 Detach this tensor from the computation graph in-place. 

188 

189 Returns: 

190 DTensorBase: Self for method chaining. 

191 """ 

192 self._local_tensor.detach_() 

193 super().detach_() 

194 return self 

195 

196 # ====================== Computation graph related overrides ====================== 

197 @property 

198 def is_leaf(self) -> bool: 

199 """ 

200 Check if this tensor is a leaf node in the computation graph. 

201 

202 Returns: 

203 bool: True if this is a leaf tensor (created by user, not by any operation). 

204 """ 

205 return self._local_tensor.is_leaf 

206 

207 @property 

208 def retains_grad(self) -> bool: 

209 """ 

210 Check if this tensor retains its gradient during backward pass. 

211 

212 Returns: 

213 bool: True if gradients are retained for non-leaf tensors. 

214 """ 

215 return self._local_tensor.retains_grad 

216 

217 @retains_grad.setter 

218 def retains_grad(self, value: bool) -> None: 

219 """ 

220 Enable or disable gradient retention for this tensor. 

221 

222 Args: 

223 value (bool): True to enable gradient retention. 

224 """ 

225 self._local_tensor.retains_grad_(value) 

226 

227 def backward(self, gradient=None, retain_graph=None, create_graph=False) -> None: 

228 """ 

229 Compute the gradients for this tensor. 

230 

231 Args: 

232 gradient (Optional[Tensor]): The gradient of the loss w.r.t. this tensor. 

233 retain_graph (Optional[bool]): Whether to retain the computation graph. 

234 create_graph (bool): Whether to create a graph of the gradient computation. 

235 """ 

236 self._local_tensor.backward(gradient, retain_graph, create_graph) 

237 

238 # ====================== Metadata related overrides (sync with local_tensor) ====================== 

239 @property 

240 def device(self) -> torch.device: 

241 """ 

242 Get the device on which this tensor is stored. 

243 

244 Returns: 

245 torch.device: The device object (e.g., 'cuda:0', 'cpu'). 

246 """ 

247 return self._local_tensor.device 

248 

249 @property 

250 # pylint: disable=C2801 

251 def data(self): 

252 """ 

253 Directory get Tensor.data relative storage. 

254 After DTensor object created, there are two reference on underlying storage. (.data and ._local_tensor) 

255 If not using DisableTorchFunctionSubclass, Tensor.__get__ will goto '__torch_function__', 

256 and finally return '._local_tensor' storage. 

257 """ 

258 with getattr(torch, "_C").DisableTorchFunctionSubclass(): 

259 # Directory get Tensor.data relative storage. 

260 # If not using DisableTorchFunctionSubclass, Tensor.__get__ will goto __torch_funtin 

261 return Tensor.data.__get__(self, type(self)) 

262 

263 @data.setter 

264 # pylint: disable=C2801 

265 def data(self, value): 

266 """Set the underlying tensor data, extracting the local shard if a DTensor is given.""" 

267 local_value = value.to_local() if isinstance(value, DTensorBase) else value 

268 # Tensor.data.__set__ on a Tensor subclass otherwise enters __torch_function__ 

269 # and only rebinds _local_tensor through DTensor dispatch. 

270 with getattr(torch, "_C").DisableTorchFunctionSubclass(): 

271 Tensor.data.__set__(self, local_value) 

272 Tensor.data.__set__(self._local_tensor, local_value) 

273 

274 @property 

275 def dtype(self) -> torch.dtype: 

276 """ 

277 Get the data type of this tensor. 

278 

279 Returns: 

280 torch.dtype: The data type (e.g., torch.float32, torch.int64). 

281 """ 

282 return self._local_tensor.dtype 

283 

284 @property 

285 def shape(self) -> torch.Size: 

286 """ 

287 Get the shape of this tensor. 

288 

289 Returns: 

290 torch.Size: The shape of the tensor. 

291 """ 

292 return self._local_tensor.shape 

293 

294 def type(self, dtype=None, non_blocking=False): 

295 """ 

296 Convert this tensor to the specified dtype. 

297 

298 Args: 

299 dtype (Optional[torch.dtype]): The target dtype. If None, returns the current type string. 

300 non_blocking (bool): Whether to perform the operation asynchronously. Default: False. 

301 

302 Returns: 

303 Union[str, DTensorBase]: The type string if dtype is None, otherwise a new DTensor. 

304 """ 

305 if dtype is None: 

306 return self._local_tensor.type() 

307 new_local = self._local_tensor.to(dtype=dtype, non_blocking=non_blocking) 

308 return self.__class__( 

309 new_local, 

310 device_mesh=self._device_mesh, 

311 placements=self._alias_placements(), 

312 shape=getattr(self, "_global_shape", None), 

313 ) 

314 

315 def size(self, dim: Optional[int] = None): 

316 """ 

317 Get the size of this tensor. 

318 

319 Args: 

320 dim (Optional[int]): The dimension to query. If None, returns the full shape. 

321 

322 Returns: 

323 Union[torch.Size, int]: The shape or size along a specific dimension. 

324 """ 

325 return self._local_tensor.size(dim) 

326 

327 @property 

328 def ndim(self) -> int: 

329 """ 

330 Get the number of dimensions of this tensor. 

331 

332 Returns: 

333 int: The number of dimensions. 

334 """ 

335 return self._local_tensor.ndim 

336 

337 def data_ptr(self) -> int: 

338 """ 

339 Get the pointer to the data storage of the local tensor. 

340 

341 Returns: 

342 int: The memory address of the tensor's data. 

343 """ 

344 # Force return local_tensor's data pointer (ensure address consistency) 

345 return self._local_tensor.data_ptr() 

346 

347 def numel(self) -> int: 

348 """ 

349 Get the total number of elements in this tensor. 

350 

351 Returns: 

352 int: The total number of elements. 

353 """ 

354 return self._local_tensor.numel() 

355 

356 # ====================== Auxiliary print ====================== 

357 def _alias_placements(self): 

358 """Return alias_placements from layout, falling back to _placements.""" 

359 if hasattr(self, '_layout') and self._layout is not None: 

360 return self._layout.alias_placements 

361 return self._placements 

362 

363 def to(self, *args, **kwargs): 

364 """Move the DTensor to a different device or dtype. 

365 

366 This method overrides the base Tensor.to() to properly reconstruct 

367 a DTensor with device_mesh and placements preserved. Uses _make_subclass 

368 to avoid issues with Parameter subclasses that don't accept extra kwargs. 

369 

370 Args: 

371 *args: Arguments passed to the underlying tensor's to() method. 

372 **kwargs: Keyword arguments for the tensor conversion. 

373 

374 Returns: 

375 DTensorBase: A new DTensor with the converted local tensor. 

376 """ 

377 new_local = self._local_tensor.to(*args, **kwargs) 

378 new_dt = Tensor._make_subclass(type(self), new_local, new_local.requires_grad) 

379 new_dt.__init_data__( 

380 new_local, 

381 self._device_mesh, 

382 self._alias_placements(), 

383 shape=getattr(self, "_global_shape", None), 

384 ) 

385 return new_dt 

386 

387 def __repr__(self) -> str: 

388 return ( 

389 f"DTensor(\n" 

390 f" local_tensor={self._local_tensor},\n" 

391 f" device_mesh={self._device_mesh},\n" 

392 f" placements={self._placements},\n" 

393 f" layout={getattr(self, '_layout', None)},\n" 

394 f" device={self.device},\n" 

395 f" dtype={self.dtype},\n" 

396 f" requires_grad={self.requires_grad},\n" 

397 f" grad={self.grad},\n" 

398 f" is_leaf={self.is_leaf},\n" 

399 f" data_ptr={self.data_ptr()}\n" 

400 f")" 

401 )