Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / activation_memory / recompute_state.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07: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 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"""Dynamic execution state shared by activation recomputation features.""" 

16import contextvars 

17from typing import Any, Callable, Dict, Optional, Tuple 

18 

19 

20class _RecomputeInvocation: 

21 """Own resources whose lifetime is one checkpoint invocation.""" 

22 

23 def __init__(self) -> None: 

24 """Initialize an empty invocation resource registry.""" 

25 self.identity = object() 

26 self.resources: Dict[object, Any] = {} 

27 

28 def get_resource(self, key: object, factory: Callable[[], Any]) -> Any: 

29 """Get or create one invocation-local resource.""" 

30 if key not in self.resources: 

31 self.resources[key] = factory() 

32 return self.resources[key] 

33 

34 def clear(self) -> None: 

35 """Release invocation resources, including partially consumed caches.""" 

36 for resource in self.resources.values(): 

37 clear = getattr(resource, "clear", None) 

38 if clear is not None: 

39 clear() 

40 self.resources.clear() 

41 

42 

43class RecomputeState: 

44 """Describe the current checkpoint invocation and execution phase.""" 

45 

46 def __init__( 

47 self, 

48 invocation: _RecomputeInvocation, 

49 recomputing: bool, 

50 preserve_rng_state: bool = True, 

51 ) -> None: 

52 """Initialize state for one phase of a checkpoint invocation.""" 

53 self._invocation = invocation 

54 self.is_recomputing = recomputing 

55 self.preserve_rng_state = preserve_rng_state 

56 

57 @property 

58 def invocation_id(self) -> object: 

59 """Return an identity that is stable across forward and recomputation.""" 

60 return self._invocation.identity 

61 

62 def get_resource(self, key: object, factory: Callable[[], Any]) -> Any: 

63 """Get an invocation-local resource shared by both execution phases.""" 

64 return self._invocation.get_resource(key, factory) 

65 

66 def _clear_resources(self) -> None: 

67 """Release all resources owned by this checkpoint invocation.""" 

68 self._invocation.clear() 

69 

70 

71_CURRENT_RECOMPUTE_STATE: contextvars.ContextVar[Optional[RecomputeState]] = contextvars.ContextVar( 

72 "hyper_parallel_recompute_state", 

73 default=None, 

74) 

75 

76 

77class _RecomputeContext: 

78 """Install one checkpoint execution phase in the current dynamic scope.""" 

79 

80 def __init__(self, state: RecomputeState) -> None: 

81 """Initialize a context for the supplied execution state.""" 

82 self._state = state 

83 self._token = None 

84 

85 def __enter__(self) -> "_RecomputeContext": 

86 """Expose this phase as the current recompute state.""" 

87 self._token = _CURRENT_RECOMPUTE_STATE.set(self._state) 

88 return self 

89 

90 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> bool: 

91 """Restore the outer state and clear resources after recomputation.""" 

92 if self._token is not None: 

93 _CURRENT_RECOMPUTE_STATE.reset(self._token) 

94 if self._state.is_recomputing or exc_type is not None: 

95 self._state._clear_resources() # pylint: disable=protected-access 

96 return False 

97 

98 

99def get_recompute_state() -> Optional[RecomputeState]: 

100 """Return the current checkpoint execution state, if one is active.""" 

101 return _CURRENT_RECOMPUTE_STATE.get() 

102 

103 

104def is_recomputing() -> bool: 

105 """Return whether the current dynamic scope is replaying a checkpoint.""" 

106 state = get_recompute_state() 

107 return state is not None and state.is_recomputing 

108 

109 

110def create_recompute_contexts( 

111 preserve_rng_state: bool = True, 

112) -> Tuple[_RecomputeContext, _RecomputeContext]: 

113 """Create forward and recompute contexts for one checkpoint invocation.""" 

114 invocation = _RecomputeInvocation() 

115 return ( 

116 _RecomputeContext( 

117 RecomputeState(invocation, recomputing=False, preserve_rng_state=preserve_rng_state) 

118 ), 

119 _RecomputeContext( 

120 RecomputeState(invocation, recomputing=True, preserve_rng_state=preserve_rng_state) 

121 ), 

122 )