Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / mindspore / activation_checkpoint / sac.py: 98%
96 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-22 04:23 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-22 04:23 +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# ============================================================================
15"""enhanced with selective checkpoint support swap"""
16# pylint: disable=W0212, W0613, C0115, C0116, C0103, R1705
17from collections import defaultdict
18from typing import Any, Dict, List, Optional, Union
20import mindspore as ms
21from mindspore import MsDispatchMode
22from hyper_parallel.core.activation_checkpoint.swap import (
23 SwapManager,
24 Storage,
25 SwapTensor,
26)
27from hyper_parallel.core.activation_checkpoint.activation_checkpoint import CheckpointPolicy
28from hyper_parallel.platform import get_platform
30platform = get_platform()
33class _VersionWrapper:
34 # Check that cached tensors are not mutated.
35 def __init__(self, val):
36 self.val: Union[ms.Tensor, Any] = val
37 self.version: Optional[int] = (
38 val._version if isinstance(val, ms.Tensor) else None
39 )
41 def get_val(self, allow_cache_entry_mutation):
42 if self.version is not None and not allow_cache_entry_mutation:
43 if self.val._version != self.version:
44 # Can we give user a stack trace of where the mutation happened?
45 raise RuntimeError(
46 "Tensor cached during selective activation checkpoint has been mutated"
47 )
48 return self.val
51class _SwapCacheEntry:
52 """Pair the recompute cache and swap record around the same tensor object."""
53 def __init__(self, val, funcname, group_swap=False):
54 self.save = _VersionWrapper(val)
55 self.swap = SwapTensor(val, funcname, group_swap=group_swap)
58def _maybe_detach(x):
59 if isinstance(x, ms.Tensor) and (x.is_floating_point() or x.is_complex()):
60 x = x.detach()
61 return x
64class SelectiveCheckpointContext:
65 def __init__(self, *, is_recompute):
66 self.is_recompute = is_recompute
68SAC_IGNORED_OPS = {"StopGradient"}
71def ignore_sac_ops(ignore_ops: List[Optional[object]]) -> None:
72 """Add available operator names to the selective-checkpoint ignore set.
74 Args:
75 ops (List[Optional[object]]): MindSpore operator names to execute without selective-AC replay
76 accounting. ``None`` entries are ignored.
77 """
78 SAC_IGNORED_OPS.update(op for op in ignore_ops if op is not None)
81class _CachingMindSporeDispatchMode(MsDispatchMode):
82 def __init__(self, policy_fn, swap_storage, storage, group_swap=False):
83 self.policy_fn = policy_fn
84 self.swap_storage = swap_storage
85 self.storage = storage
86 self.add_to_storage = False
87 self.group_swap = group_swap
88 # Cache context and singleton to avoid per-dispatch allocation / lookup.
89 self._swap_manager = SwapManager()
90 self._group_prefix = ""
92 def __ms_dispatch__(self, func, args=(), kwargs=None):
93 kwargs = {} if kwargs is None else kwargs
94 if func.name in SAC_IGNORED_OPS:
95 return func(*args, **kwargs)
96 policy = self.policy_fn(SelectiveCheckpointContext(is_recompute=False),
97 func, *args, **kwargs)
99 out = func(*args, **kwargs)
101 if policy in (CheckpointPolicy.MUST_SAVE, CheckpointPolicy.PREFER_SAVE):
102 self.storage[func.name].append(
103 platform.tree_map(
104 lambda x: _VersionWrapper(_maybe_detach(x)), out
105 )
106 )
107 elif policy == CheckpointPolicy.MUST_SWAP:
108 if not self.add_to_storage:
109 group_name = self._swap_manager.get_current_group_name()
110 self._group_prefix = f"{group_name}::"
111 self._swap_manager.add_storage(group_name, self.swap_storage)
112 self.add_to_storage = True
113 funcname = f"{self._group_prefix}{func.name}"
114 group_swap = self.group_swap
115 entries = platform.tree_map(
116 lambda x: _SwapCacheEntry(_maybe_detach(x), funcname, group_swap=group_swap), out
117 )
118 self.storage[func.name].append(
119 platform.tree_map(lambda x: x.save, entries)
120 )
121 self.swap_storage[func.name].append(
122 platform.tree_map(lambda x: x.swap, entries)
123 )
124 elif policy != CheckpointPolicy.MUST_RECOMPUTE:
125 raise RuntimeError(f"Checkpoint Activation: {func.name} encountered an invalid policy {policy}")
126 return out
129class _CachedMindSporeDispatchMode(MsDispatchMode):
130 def __init__(self, policy_fn, swap_storage, storage, allow_cache_entry_mutation):
131 self.policy_fn = policy_fn
132 self.swap_storage = swap_storage
133 self.storage = storage
134 self.allow_cache_entry_mutation = allow_cache_entry_mutation
135 self._swap_cleared = False
137 def __ms_dispatch__(self, func, args=(), kwargs=None):
138 kwargs = {} if kwargs is None else kwargs
139 if func.name in SAC_IGNORED_OPS:
140 return func(*args, **kwargs)
142 policy = self.policy_fn(SelectiveCheckpointContext(is_recompute=True),
143 func, *args, **kwargs)
145 if not self._swap_cleared:
146 self.swap_storage.clear()
147 self._swap_cleared = True
149 # MUST_SAVE and MUST_SWAP both restore from storage identically.
150 if policy in (CheckpointPolicy.MUST_SAVE, CheckpointPolicy.PREFER_SAVE, CheckpointPolicy.MUST_SWAP):
151 storage = self.storage.get(func.name)
152 if storage is None:
153 raise RuntimeError(f"{func} encountered during backward, but not found in storage")
154 if len(storage) == 0:
155 raise RuntimeError(
156 "Trying to backward an extra time. You are only allowed to backward once "
157 "on any region computed under selective activation checkpoint."
158 )
159 out = platform.tree_map(lambda x: x.get_val(self.allow_cache_entry_mutation), storage.pop(0))
160 else:
161 out = func(*args, **kwargs)
162 return out
165def create_selective_checkpoint_contexts(policy_fn_or_list, allow_cache_entry_mutation=False, group_swap=False):
166 if policy_fn_or_list is None:
167 def policy_fn(_ctx, _op, *_args, **_kwargs):
168 return CheckpointPolicy.PREFER_RECOMPUTE
169 elif callable(policy_fn_or_list):
170 policy_fn = policy_fn_or_list
171 else:
172 raise TypeError("policy_fn_or_list must be either a function or a list of ops.")
174 swap_storage = Storage()
175 storage: Dict[Any, List[Any]] = defaultdict(list)
176 return (
177 _CachingMindSporeDispatchMode(policy_fn, swap_storage, storage, group_swap=group_swap),
178 _CachedMindSporeDispatchMode(policy_fn, swap_storage, storage, allow_cache_entry_mutation)
179 )