Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / tensor_parallel / loss_parallel.py: 96%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-06 05:41 +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"""loss_parallel context manager. 

16 

17Context manager and utilities for distributed cross-entropy computation: 

18 - loss_parallel(): Context manager to enable distributed CE semantics 

19 - is_loss_parallel_active(): Debug/assert function 

20 - Internal state management via ContextVar 

21""" 

22 

23from __future__ import annotations 

24 

25import contextlib 

26import contextvars 

27import threading 

28from typing import Optional, ContextManager, TYPE_CHECKING 

29 

30if TYPE_CHECKING: 

31 from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

32 

33__all__ = [ 

34 "loss_parallel", 

35 "is_loss_parallel_active", 

36] 

37 

38 

39# ======================================================================== 

40# ContextVar state management 

41# ======================================================================== 

42 

43_loss_parallel_active: contextvars.ContextVar[bool] = contextvars.ContextVar( 

44 "_loss_parallel_active", default=False 

45) 

46 

47_loss_parallel_count: contextvars.ContextVar[int] = contextvars.ContextVar( 

48 "_loss_parallel_count", default=0 

49) 

50 

51_loss_parallel_context_id: contextvars.ContextVar[Optional[int]] = contextvars.ContextVar( 

52 "_loss_parallel_context_id", default=None 

53) 

54 

55_global_context_id_counter: int = 0 

56_global_context_id_lock: threading.Lock = threading.Lock() 

57 

58_loss_parallel_mesh: contextvars.ContextVar[Optional["DeviceMesh"]] = contextvars.ContextVar( 

59 "_loss_parallel_mesh", default=None 

60) 

61 

62_loss_parallel_strict: contextvars.ContextVar[bool] = contextvars.ContextVar( 

63 "_loss_parallel_strict", default=True 

64) 

65 

66 

67# ======================================================================== 

68# loss_parallel context manager 

69# ======================================================================== 

70 

71@contextlib.contextmanager 

72def loss_parallel( 

73 *, 

74 mesh: Optional["DeviceMesh"] = None, 

75 strict: bool = True, 

76) -> ContextManager[None]: 

77 """Context manager to enable distributed CE semantics. 

78 

79 Args: 

80 mesh: Explicitly specify TP sub-mesh; None infers from participating DTensors; 

81 inference failure with strict=True raises error. 

82 strict: When True, layout contract violation raises ValueError or dedicated exception; 

83 False only warns or takes optional fallback path (if fallback_gather implemented). 

84 

85 Nesting semantics: 

86 Reentrant counting; count must return to zero on exit. Supports nested usage. 

87 

88 Example: 

89 >>> with loss_parallel(): 

90 ... logits = model(input) # logits is DTensor, Shard(-1) 

91 ... loss = F.cross_entropy(logits, target) 

92 ... loss.backward() 

93 

94 Note: 

95 PyTorch upstream loss_parallel() is often parameterless. 

96 HyperParallel adds parameters; see docs/compatibility/pytorch_diff.md. 

97 """ 

98 global _global_context_id_counter 

99 

100 old_count = _loss_parallel_count.get() 

101 old_active = _loss_parallel_active.get() 

102 old_mesh = _loss_parallel_mesh.get() 

103 old_strict = _loss_parallel_strict.get() 

104 old_context_id = _loss_parallel_context_id.get() 

105 

106 _loss_parallel_count.set(old_count + 1) 

107 _loss_parallel_active.set(True) 

108 _loss_parallel_mesh.set(mesh) 

109 _loss_parallel_strict.set(strict) 

110 

111 if old_count == 0: 

112 with _global_context_id_lock: 

113 _global_context_id_counter += 1 

114 current_context_id = _global_context_id_counter 

115 _loss_parallel_context_id.set(current_context_id) 

116 else: 

117 current_context_id = old_context_id 

118 

119 try: 

120 yield 

121 finally: 

122 _loss_parallel_count.set(old_count) 

123 _loss_parallel_active.set(old_active) 

124 _loss_parallel_mesh.set(old_mesh) 

125 _loss_parallel_strict.set(old_strict) 

126 _loss_parallel_context_id.set(old_context_id) 

127 

128 

129# ======================================================================== 

130# is_loss_parallel_active (optional debug API) 

131# ======================================================================== 

132 

133def is_loss_parallel_active() -> bool: 

134 """Debug or assert: whether current thread is in loss_parallel context. 

135 

136 Returns: 

137 True: currently in loss_parallel context 

138 False: not in context 

139 """ 

140 return _loss_parallel_active.get() 

141 

142 

143def get_loss_parallel_count() -> int: 

144 """Get current nesting count. 

145 

146 Returns: 

147 int: Nesting depth (0 means not in context). 

148 """ 

149 return _loss_parallel_count.get() 

150 

151 

152# ======================================================================== 

153# Internal functions (for use by other modules) 

154# ======================================================================== 

155 

156def _get_loss_parallel_token() -> Optional[int]: 

157 """Get current context token for cache key differentiation. 

158 

159 Returns: 

160 Optional[int]: Current context unique ID for differentiating cache keys. 

161 Returns None if not in context, returns positive integer for context ID. 

162 """ 

163 return _loss_parallel_context_id.get() 

164 

165 

166def _get_loss_parallel_mesh() -> Optional["DeviceMesh"]: 

167 """Get the mesh set in current context. 

168 

169 Returns: 

170 Optional[DeviceMesh]: Explicitly specified mesh, or None (infer from DTensor). 

171 """ 

172 return _loss_parallel_mesh.get() 

173 

174 

175def _get_loss_parallel_strict() -> bool: 

176 """Get the strict setting in current context. 

177 

178 Returns: 

179 bool: Whether in strict mode. 

180 """ 

181 return _loss_parallel_strict.get()