Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / mindspore / autograd_compat.py: 84%
118 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +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 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# ============================================================================
16"""MindSpore backward-style autograd compatibility helpers."""
17# pylint: disable=protected-access,import-outside-toplevel
19from __future__ import annotations
21import warnings
23from mindspore import ops
24from mindspore._c_expression import TensorPy, pyboost_detach, run_backward
25from mindspore._c_expression import typing
26from mindspore.graph.api import _pynative_executor
28_BACKWARD_COMPAT_ENABLED = False
31@property
32def requires_grad(self):
33 """Return whether the tensor requires gradient."""
34 return self._requires_grad
37@requires_grad.setter
38def requires_grad(self, value=True):
39 if not isinstance(value, bool):
40 raise TypeError("The argument `requires_grad` must be bool type")
41 self._requires_grad = value
44@property
45def grad(self):
46 """Return the current accumulated gradient."""
47 if not self.is_leaf and self.requires_grad:
48 warnings.warn(
49 "The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. "
50 "Its .grad attribute won't be populated during autograd.backward(). "
51 "If you indeed want the .grad field to be populated for a non-leaf Tensor, "
52 "use .retain_grad() on the non-leaf Tensor.",
53 stacklevel=2,
54 )
55 dtensor_grad = getattr(self, "_dtensor_grad", None)
56 if dtensor_grad is not None:
57 return dtensor_grad
58 return self._grad
61@grad.setter
62def grad(self, value):
63 try:
64 from hyper_parallel.core.dtensor.dtensor import DTensor
65 except ImportError:
66 DTensor = ()
68 if value is None:
69 self._dtensor_grad = None
70 self._grad = None
71 return
73 if DTensor and isinstance(value, DTensor):
74 self._dtensor_grad = value
75 self._grad = value._local_tensor
76 return
78 self._dtensor_grad = None
79 self._grad = value
82@property
83def is_leaf(self):
84 """Return whether the tensor is a leaf."""
85 return self._is_leaf
88@property
89def retains_grad(self):
90 """Return whether the tensor retains gradient."""
91 return self._retains_grad
94@property
95def grad_fn(self):
96 """Return the gradient function node, or None for leaf tensors."""
97 if self._grad_node and self._grad_node.is_leaf():
98 return None
99 return self._grad_node
102@property
103def output_nr(self):
104 """Return the output index of this tensor in the autograd graph."""
105 return self._output_index
108def retain_grad(self):
109 """Set the tensor retains gradient."""
110 return self._retain_grad()
113def detach(self):
114 """Detach the tensor."""
115 detached = pyboost_detach(self)
116 detached._dtensor_grad = None
117 return detached
120def _is_same_size(output, grad_tensor):
121 return tuple(output.shape) == tuple(grad_tensor.shape)
124def _calculate_shape(output, grad_tensor):
125 return output.shape, grad_tensor.shape
128def _tensor_or_tensors_to_tuple(tensors, length):
129 if tensors is None:
130 return (None,) * length
131 if isinstance(tensors, TensorPy):
132 return (tensors,)
133 return tuple(tensors)
136def _make_grads(outputs, grads):
137 """Validate backward gradients and materialize implicit scalar grads."""
138 new_grads = []
139 for index, (out, grad_tensor) in enumerate(zip(outputs, grads)):
140 if isinstance(grad_tensor, TensorPy):
141 if not _is_same_size(out, grad_tensor):
142 out_shape, grad_shape = _calculate_shape(out, grad_tensor)
143 raise RuntimeError(
144 "Mismatch in shape: grad_output["
145 + str(index)
146 + "] has a shape of "
147 + str(grad_shape)
148 + " and output["
149 + str(index)
150 + "] has a shape of "
151 + str(out_shape)
152 + "."
153 )
154 if out.dtype.is_complex != grad_tensor.dtype.is_complex:
155 raise RuntimeError(
156 "For complex Tensors, both grad_output and output"
157 " are required to have the same dtype."
158 " Mismatch in dtype: grad_output["
159 + str(index)
160 + "] has a dtype of "
161 + str(grad_tensor.dtype)
162 + " and output["
163 + str(index)
164 + "] has a dtype of "
165 + str(out.dtype)
166 + "."
167 )
168 new_grads.append(grad_tensor)
169 elif grad_tensor is None:
170 if out.numel() != 1:
171 raise RuntimeError("grad can be implicitly created only for scalar outputs")
172 if not isinstance(out.dtype, (typing.Float, typing.BFloat)):
173 raise RuntimeError(
174 f"grad can be implicitly created only for real scalar outputs but got {out.dtype}"
175 )
176 new_grads.append(ops.ones_like(out))
177 else:
178 raise TypeError(
179 "gradients can be either Tensors or None, but got " + type(grad_tensor).__name__
180 )
181 return tuple(new_grads)
184def backward(self, gradient=None, retain_graph=None, create_graph=False, inputs=None):
185 """Run torch-style backward on a MindSpore tensor."""
186 outputs = (self,)
187 has_explicit_inputs = inputs is not None
188 if isinstance(inputs, list):
189 inputs = tuple(inputs)
190 elif isinstance(inputs, TensorPy):
191 inputs = (inputs,)
192 elif inputs is None:
193 inputs = ()
194 else:
195 inputs = tuple(inputs)
196 if has_explicit_inputs and len(inputs) == 0:
197 raise RuntimeError("'inputs' argument to backward() cannot be empty.")
199 grad_tensors = _tensor_or_tensors_to_tuple(gradient, len(outputs))
200 grad_tensors = _make_grads(outputs, grad_tensors)
201 if retain_graph is None:
202 retain_graph = create_graph
204 return run_backward(
205 outputs,
206 grad_tensors,
207 retain_graph,
208 create_graph,
209 inputs,
210 allow_unreachable=True,
211 accumulate_grad=True,
212 )
215def enable_mindspore_backward_compat() -> None:
216 """Enable torch-like ``Tensor.backward()`` semantics for MindSpore PyNative."""
217 global _BACKWARD_COMPAT_ENABLED
218 if _BACKWARD_COMPAT_ENABLED:
219 return
221 _pynative_executor.set_grad_flag(True)
222 TensorPy.requires_grad = requires_grad
223 TensorPy.grad = grad
224 TensorPy.backward = backward
225 TensorPy.is_leaf = is_leaf
226 TensorPy.retains_grad = retains_grad
227 TensorPy.retain_grad = retain_grad
228 TensorPy.grad_fn = grad_fn
229 TensorPy.output_nr = output_nr
230 TensorPy.detach = detach
231 _BACKWARD_COMPAT_ENABLED = True