Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / compile / pass_config.py: 94%

51 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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""" 

16Parallel Configuration - Graph-mode parallel configuration. 

17 

18Pure configuration dataclass: no environment probes. Whether FSDP actually 

19runs is decided by ``fsdp_enabled`` here (the user's intent) AND the runtime 

20distributed guard inside ``FSDPPass`` (which still early-returns on 

21``world_size == 1``). The previous ``fsdp_enabled`` property returned 

22``dist.is_initialized() and world_size > 1`` — that turned on FSDP whenever 

23distributed was initialized, leaving no way to run pure-TP / pure-PP graph 

24mode. The explicit field fixes that. Pipeline-parallel (``pp_enabled``) 

25follows the same contract: intent here, runtime guard in ``PpPass``. 

26""" 

27 

28__all__ = ["PassConfig", "PP_SCHEDULES", "DP_MODES", "normalize_dp_mode"] 

29 

30from dataclasses import dataclass 

31from typing import Any, Optional 

32 

33 

34# Pipeline schedule names accepted by ``pp_schedule``. Kept torch-free here so 

35# the config dataclass stays importable anywhere; ``pp_schedule``'s registry 

36# (which does import torch) must expose exactly these keys. 

37PP_SCHEDULES = ("gpipe", "1f1b") 

38 

39# Data-parallel modes, mirroring simplefsdp's ``data_parallel`` modes: 

40# "fsdp" -> fully_shard (Shard(0) on the fsdp axis) 

41# "ddp" -> replicate (Replicate; grads all-reduced) 

42# "hsdp" -> hybrid_shard (Replicate x Shard(0); rs + all-reduce) 

43DP_MODES = ("fsdp", "ddp", "hsdp") 

44 

45# simplefsdp / fully_shard spellings accepted as aliases. 

46_DP_MODE_ALIASES = { 

47 "fully_shard": "fsdp", 

48 "replicate": "ddp", 

49 "hybrid_shard": "hsdp", 

50} 

51 

52 

53def normalize_dp_mode(mode: str) -> str: 

54 """Return the canonical DP mode for ``mode`` (accepts simplefsdp aliases). 

55 

56 Raises: 

57 ValueError: When ``mode`` is not a string or is unknown. 

58 """ 

59 if not isinstance(mode, str): 

60 raise ValueError(f"dp_mode must be a string, got {type(mode).__name__}") 

61 key = mode.strip().lower() 

62 return _DP_MODE_ALIASES.get(key, key) 

63 

64 

65@dataclass 

66class PassConfig: 

67 """Parallel configuration for graph-mode FSDP (+ optional TP / SP / PP) training. 

68 

69 Attributes: 

70 enable_overlap: Drive ``AutoOverlapPass`` to move ``wait_tensor`` for 

71 communication/compute overlap. 

72 fsdp_enabled: Drive ``FSDPPass`` (data-parallel partitioning: parameter 

73 all_gather + gradient reduction + live-model sharding, selected by 

74 ``dp_mode``). ``False`` skips data parallelism entirely — set this 

75 for pure-TP / pure-PP graph-mode runs. ``FSDPPass`` itself still 

76 early-returns when distributed is not initialized or 

77 ``world_size == 1``, so single-card runs are a no-op regardless. 

78 fsdp_reshard_after_forward: Drive the FSDP reshard optimization. 

79 When ``True`` (default, matching FSDP's ``reshard_after_forward``) 

80 ``FSDPPass`` sinks each param's all_gather to its first forward 

81 use, frees the replicated full parameter once its last forward 

82 reader is done, and — when the backward still needs it — inserts a 

83 fresh all_gather plus a rematerialization of the saved forward 

84 views just before the first backward consumer. Peak memory then 

85 tracks the forward working set instead of the sum of all 

86 replicated parameters. Set ``False`` to keep every gathered 

87 parameter resident for the whole joint graph (previous behavior). 

88 fsdp_degree: Size of the FSDP group. ``None`` (default) means 

89 "resolve at runtime": the trainer back-fills it from the 

90 automodel ``MeshContext`` (TP+FSDP hybrid, where the FSDP group 

91 is a proper sub-group of the world), and ``FSDPPass`` falls back 

92 to ``world_size`` for the FSDP-only path. Mutating this after 

93 construction is supported but discouraged — prefer passing the 

94 resolved degree at construction time (see ``GraphTrainer``). 

95 For ``dp_mode="hsdp"`` this is the *shard* axis degree. 

96 dp_mode: Data-parallel mode, one of ``DP_MODES``: ``"fsdp"`` 

97 (fully_shard, the default), ``"ddp"`` (replicate; no parameter 

98 sharding, gradients all-reduced), or ``"hsdp"`` (hybrid_shard; 

99 parameters replicated on the ``dp_replicate`` axis and sharded on 

100 the ``fsdp`` axis, gradients reduce-scattered then all-reduced). 

101 simplefsdp spellings ``"fully_shard"`` / ``"replicate"`` / 

102 ``"hybrid_shard"`` are accepted as aliases and normalized. 

103 dp_replicate_degree: Size of the replicate axis for ``"ddp"`` / 

104 ``"hsdp"``. ``None`` means "resolve at runtime" (from the mesh, 

105 or ``world_size // fsdp_degree`` for HSDP / ``world_size`` for 

106 DDP). Kept separate from ``fsdp_degree`` (the shard axis) so an 

107 HSDP mesh is unambiguous. 

108 tp_size: Tensor-parallel degree. Informational today (TP collectives 

109 live inside boundary forwards baked by automodel, not in the 

110 graph-mode passes); kept so a future TP-aware pass can read it 

111 without API churn. 

112 sequence_parallel: Enable sequence parallel (SP) on the TP axis. 

113 loss_parallel: Enable loss parallel (LP) on the TP axis. 

114 pp_enabled: Drive ``PpPass`` (pipeline-parallel graph split). When 

115 ``True`` the joint fwd+bwd graph is sliced to this rank's stage 

116 along module-FQN boundaries declared in ``GraphParallelPlan``, cross-stage 

117 activations are exchanged via P2P ``isend``/``irecv``, and a 

118 self-contained GPipe schedule is installed as a ``call_module`` 

119 stub inside the rewritten graph, so the trainer needs no PP 

120 wiring. ``PpPass`` early-returns when distributed is not 

121 initialized or ``world_size == 1``. 

122 pp_degree: Number of pipeline-parallel ranks (== number of stages 

123 for the single-virtual-stage schedule shipped here). ``None`` 

124 means "resolve at runtime" from ``world_size`` (the pure-PP 

125 path); for a PP+FSDP hybrid the FSDP group is a proper 

126 sub-group of the world and the trainer back-fills this from 

127 the mesh, exactly like ``fsdp_degree``. 

128 pp_microbatch_size: Number of samples per microbatch for the 

129 pipeline schedule. The per-step batch size (the leading dim of 

130 the trainer's input tensor) must be divisible by this. ``1`` means 

131 one sample per microbatch (maximum pipeline concurrency, most 

132 P2P traffic); larger values trade bubble size for fewer P2P 

133 round-trips. 

134 pp_schedule: Pipeline schedule name, one of ``PP_SCHEDULES``. 

135 ``"gpipe"`` (default) runs all forwards before all backwards 

136 (simplest ordering, largest bubble). ``"1f1b"`` interleaves one 

137 backward per forward in steady state, shrinking the pipeline 

138 bubble and freeing each microbatch's activations as soon as its 

139 backward runs (GPipe retains all of them until the sweep ends); 

140 it requires ``num_microbatches >= pp_degree``. 

141 ``PpPass`` resolves the name to a schedule class via 

142 ``pp_schedule.get_schedule_class``. 

143 

144 Note: 

145 ``fsdp_enabled`` / ``pp_enabled`` no longer probe 

146 ``torch.distributed``. The distributed-initialized check moved into 

147 the respective passes' ``run`` (their original location) so this 

148 dataclass stays torch-free and importable anywhere. 

149 """ 

150 

151 enable_overlap: bool = True 

152 fsdp_enabled: bool = True 

153 fsdp_reshard_after_forward: bool = True 

154 fsdp_degree: Optional[int] = None 

155 dp_mode: str = "fsdp" 

156 dp_replicate_degree: Optional[int] = None 

157 tp_size: int = 1 

158 sequence_parallel: bool = False 

159 loss_parallel: bool = False 

160 pp_enabled: bool = False 

161 pp_degree: Optional[int] = None 

162 pp_microbatch_size: int = 1 

163 pp_schedule: str = "gpipe" 

164 

165 def __post_init__(self) -> None: 

166 self.dp_mode = normalize_dp_mode(self.dp_mode) 

167 self.validate() 

168 

169 def validate(self) -> None: 

170 """Sanity-check invariants; also re-run after manual field mutation. 

171 

172 Raises: 

173 ValueError: On a negative ``tp_size``, a non-positive explicit 

174 ``fsdp_degree`` / ``pp_degree``, a non-positive 

175 ``pp_microbatch_size``, an unknown ``pp_schedule`` / 

176 ``dp_mode``, or a non-positive ``dp_replicate_degree``. 

177 """ 

178 if self.tp_size < 1: 

179 raise ValueError(f"tp_size must be >= 1, got {self.tp_size}") 

180 if self.fsdp_degree is not None and self.fsdp_degree < 1: 

181 raise ValueError( 

182 f"fsdp_degree must be None or a positive int, got {self.fsdp_degree}" 

183 ) 

184 if self.dp_mode not in DP_MODES: 

185 raise ValueError( 

186 f"dp_mode must be one of {DP_MODES} " 

187 f"(aliases: {tuple(_DP_MODE_ALIASES)}), got {self.dp_mode!r}" 

188 ) 

189 if self.dp_replicate_degree is not None and self.dp_replicate_degree < 1: 

190 raise ValueError( 

191 f"dp_replicate_degree must be None or a positive int, " 

192 f"got {self.dp_replicate_degree}" 

193 ) 

194 if self.pp_degree is not None and self.pp_degree < 1: 

195 raise ValueError( 

196 f"pp_degree must be None or a positive int, got {self.pp_degree}" 

197 ) 

198 if self.pp_microbatch_size < 1: 

199 raise ValueError( 

200 f"pp_microbatch_size must be >= 1, got {self.pp_microbatch_size}" 

201 ) 

202 if not isinstance(self.pp_schedule, str) or ( 

203 self.pp_schedule.lower() not in PP_SCHEDULES 

204 ): 

205 raise ValueError( 

206 f"pp_schedule must be one of {PP_SCHEDULES}, got {self.pp_schedule!r}" 

207 ) 

208 

209 

210def build_pass_config_from_trainer_config( 

211 config: Any, 

212 *, 

213 fsdp_enabled: Optional[bool] = None, 

214 enable_overlap: bool = False, 

215) -> PassConfig: 

216 """Project trainer topology intent onto graph-mode pass config. 

217 

218 This helper stays in the pass-config module because it computes the 

219 graph-pass surface from the trainer topology without altering runtime 

220 objects. Accepting ``Any`` keeps this module importable without the 

221 trainer package. 

222 """ 

223 accelerator = config.accelerator 

224 if fsdp_enabled is None: 

225 fsdp_enabled = ( 

226 config.fsdp_config.dp_shard_size > 1 

227 or config.fsdp_config.edp_shard_size > 1 

228 ) 

229 fsdp_degree = config.fsdp_config.dp_shard_size if fsdp_enabled else None 

230 return PassConfig( 

231 enable_overlap=enable_overlap, 

232 fsdp_enabled=fsdp_enabled, 

233 fsdp_degree=fsdp_degree, 

234 tp_size=accelerator.tp_size, 

235 sequence_parallel=accelerator.sequence_parallel, 

236 loss_parallel=accelerator.loss_parallel, 

237 ) 

238 

239 

240__all__ = ["PassConfig", "build_pass_config_from_trainer_config"]