Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / pipeline_parallel / mpipe / schedule.py: 99%

169 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"""The MPipe Transpose schedule (an Interleaved-1F1B variant). 

16 

17Layers the ``MPIPE_*`` transpose steps around the inherited Interleaved-1F1B 

18body order and registers their handlers via the generic custom-function 

19registry, so the core ``scheduler`` module carries no MPipe-specific code. 

20""" 

21import logging 

22from typing import Callable, Optional, TYPE_CHECKING 

23 

24from torch.nn import Module 

25 

26from hyper_parallel.core.pipeline_parallel.mpipe.executor import MPipeTransposeExecutor 

27 

28from hyper_parallel.core.pipeline_parallel.scheduler import ScheduleInterleaved1F1B 

29from hyper_parallel.core.pipeline_parallel.mpipe.sampler import mpipe_owned_micros 

30from hyper_parallel.core.pipeline_parallel.mpipe.step_types import MpipeStepType 

31from hyper_parallel.core.pipeline_parallel.utils import MetaStep, MetaStepType 

32 

33if TYPE_CHECKING: 

34 from hyper_parallel.core.pipeline_parallel.utils import BatchDimSpec 

35 

36logger = logging.getLogger(__name__) 

37 

38# (data, features, graph input) step types of the transpose-prefix transfers. 

39_PREFIX_SEND_TYPES = (MetaStepType.DATA_SEND, MpipeStepType.MPIPE_FWD_SEND, MpipeStepType.MPIPE_GRAPH_SEND) 

40_PREFIX_RECV_TYPES = (MetaStepType.DATA_RECV, MpipeStepType.MPIPE_FWD_RECV, MpipeStepType.MPIPE_GRAPH_RECV) 

41 

42 

43def _prefix_owned_micros(rank, num_transpose_micro_batches, micro_batch_num, overflow_mode): 

44 """Micros ``rank`` transposes in its prefix (under ``"min"`` only ``rank`` 

45 itself; rank 0's overflow micros are loaded inline in its body order).""" 

46 nt = num_transpose_micro_batches 

47 if rank >= nt: 

48 return [] 

49 if overflow_mode == "min": 

50 return [rank] 

51 return [m for m in range(micro_batch_num) if m % nt == rank] 

52 

53 

54def _prefix_transfer_steps(micros, step_types, is_dataload_only, ship_graph): 

55 """Type-major transfer block: all data, then all features, then all graph inputs. 

56 

57 The sending ranks and rank 0 both build their block here, so the send and 

58 receive orders match; otherwise the meta recv drifts and decodes garbage 

59 shapes. Dataload-only ships the data alone; ``ship_graph`` adds the input 

60 ship-back for the centralized stage-0 backward. 

61 """ 

62 data_type, fwd_type, graph_type = step_types 

63 steps = [MetaStep(m, data_type, -1) for m in micros] 

64 if not is_dataload_only: 

65 steps += [MetaStep(m, fwd_type, -1) for m in micros] 

66 if ship_graph: 

67 steps += [MetaStep(m, graph_type, -1) for m in micros] 

68 return steps 

69 

70 

71class ScheduleMPipeTranspose(ScheduleInterleaved1F1B): 

72 """The MPipe Transpose schedule. 

73 

74 A variant of Interleaved 1F1B that shrinks the warmup pipeline bubble by 

75 **transposing** the forward of a model's first ``T`` layers (the 

76 *preprocess* block, which logically belongs to stage 0's first chunk). 

77 

78 Instead of stage 0 computing the preprocess forward serially for every 

79 micro-batch, the preprocess parameters are broadcast to all ``PP`` ranks 

80 and, for the first ``NT = min(PP, micro_batch_num)`` micro-batches, each 

81 rank ``i`` computes the preprocess forward of micro-batch ``i`` in parallel 

82 during what would otherwise be its warmup idle time. Each rank ``i > 0`` 

83 then ships to stage 0: 

84 

85 * the preprocess **output** (``MPIPE_FWD_SEND`` / ``MPIPE_FWD_RECV``) so 

86 stage 0 can run its body forward, and 

87 * the preprocess **input** (``MPIPE_GRAPH_SEND`` / ``MPIPE_GRAPH_RECV``) 

88 so the preprocess backward can run centrally on stage 0 

89 (gradients accumulate on stage 0 only). 

90 

91 The remaining ``micro_batch_num - NT`` micro-batches run the preprocess 

92 forward inline on stage 0 (graph-connected to the body, so their backward 

93 is automatic), exactly as ordinary Interleaved 1F1B. 

94 

95 The body model (stage 0 = the layers after the preprocess block, all other 

96 stages unchanged) is scheduled by the inherited Interleaved 1F1B logic; the 

97 preprocess steps are layered around it: a transpose-phase prefix per rank, 

98 plus inline preprocess forward / stage-0 backward steps. 

99 

100 Args: 

101 stages (list[PipelineStage], PipelineStage): The body pipeline stages, 

102 given **after** stage 0's first chunk has been partitioned into the 

103 preprocess block and the body. Stage 0 here must wrap only the 

104 layers that remain *after* the preprocess block; the transposed 

105 preprocess layers are passed separately as ``preprocess_module`` and 

106 must not appear in ``stages``. 

107 micro_batch_num (int): The number of micro-batches. 

108 preprocess_module (Optional[Module]): The preprocess block (first ``T`` 

109 layers of stage 0). Following Option A, it must exist on **every** rank: on rank 0 

110 it holds the trained parameters; on other ranks it is a structural 

111 copy whose parameters are overwritten each step by the broadcast. 

112 num_transpose_layers (int): ``T`` — the number of preprocess layers, 

113 must be smaller than the layer count of stage 0's first chunk. 

114 ``0`` is allowed and means *only the data loading is transposed*: 

115 each rank loads its micro-batch and ships the raw input to stage 0, 

116 with no parameter broadcast, no preprocess compute, and no 

117 stage-0 backward. 

118 args_batch_dim (list, optional): See ``PipelineScheduleRuntime``. 

119 kwargs_batch_dim (dict, optional): See ``PipelineScheduleRuntime``. 

120 output_concat_dim (int, optional): See ``PipelineScheduleRuntime``. 

121 overlap_p2p (bool, optional): See ``ScheduleInterleaved1F1B``. 

122 Default ``False``. 

123 swap (bool, optional): Reserved for API compatibility. MPipe activation 

124 swap is not supported and ``True`` raises ``ValueError``. 

125 

126 Note: 

127 This class builds the schedule ordering and registers the ``MPIPE_*`` 

128 execution handlers implemented by :class:`MPipeTransposeExecutor`. 

129 """ 

130 

131 # Each rank runs one transposed preprocess forward, so every rank needs its 

132 # own micro-batch input — drivers (the trainer) check this marker to feed all 

133 # ranks rather than only the first stage. 

134 requires_all_rank_input = True 

135 

136 def __init__(self, 

137 stages: list, 

138 micro_batch_num: int, 

139 preprocess_module: "Optional[Module]", 

140 num_transpose_layers: int, 

141 num_visual_layers: Optional[int] = None, 

142 args_batch_dim: "Optional[BatchDimSpec]" = None, 

143 kwargs_batch_dim: "Optional[BatchDimSpec]" = None, 

144 output_concat_dim: Optional[int] = None, 

145 overlap_p2p: bool = False, 

146 swap: bool = False, 

147 output_consumer: Optional[Callable] = None, 

148 owner_backward: bool = False, 

149 less_memory: bool = False, 

150 overflow_mode: str = "full") -> None: 

151 """Build an interleaved-1F1B schedule that transposes the preprocess block. 

152 

153 Args: 

154 stages (list): The local pipeline stages (as for :class:`ScheduleInterleaved1F1B`). 

155 micro_batch_num (int): Number of micro-batches per optimizer step. 

156 preprocess_module (Optional[Module]): The block transposed to every 

157 rank — the first ``num_transpose_layers`` layers, a visual tower, 

158 or a param-free identity for the dataload-only (``T = 0``) mode. 

159 num_transpose_layers (int): ``T`` — the (informational) transposed-layer count. 

160 num_visual_layers (int): total number of visual layers. 

161 args_batch_dim (Optional[BatchDimSpec]): Positional-arg batch-dim spec (forwarded to the base). 

162 kwargs_batch_dim (Optional[BatchDimSpec]): Keyword-arg batch-dim spec (forwarded to the base). 

163 output_concat_dim (Optional[int]): Output concatenation dim (forwarded to the base). 

164 overlap_p2p (bool): Whether to overlap P2P (forwarded to the base). 

165 swap (bool): Reserved for API compatibility. Must be ``False``. 

166 output_consumer: Optional ``(ctx, micro, out_tuple) -> None`` callback 

167 routing a transposed micro-batch's preprocess output (``None`` = 

168 default, the output becomes stage 0's body input). 

169 less_memory (bool): Shallow-warmup interleaved body (forwarded to 

170 the base) — see :class:`ScheduleInterleaved1F1B`. The transpose 

171 prefix is unaffected. 

172 owner_backward (bool): Opt-in owner-does-backward for a TRAINABLE 

173 preprocess on torch. When effective, the owner rank retains the 

174 forward graph, stage 0 ships the feature gradient back, and the 

175 owner runs the tower backward in its cooldown (instead of the 

176 centralized stage-0 backward); tower grads are SUM all-reduced 

177 across the pp replicas. Ignored (stage-0 backward kept) for a frozen / param-free 

178 preprocess. 

179 overflow_mode (str): How to distribute the ``M > NT`` overflow micros. 

180 ``"full"`` (default) — round-robin: each owner takes ``M/NT`` 

181 micros, ViT phase balanced. ``"min"`` — rank 0 absorbs the 

182 overflow (saves inter-rank P2P at the cost of a longer rank-0 

183 ViT phase). 

184 """ 

185 if swap: 

186 raise ValueError( 

187 "MPipe Transpose with pipeline activation swap is not yet supported." 

188 ) 

189 if not isinstance(num_transpose_layers, int) or num_transpose_layers < 0: 

190 raise ValueError( 

191 f"Argument 'num_transpose_layers' must be a non-negative int, " 

192 f"but got {num_transpose_layers!r}." 

193 ) 

194 if overflow_mode not in ("full", "min"): 

195 raise ValueError( 

196 f"overflow_mode must be 'full' or 'min', got {overflow_mode!r}" 

197 ) 

198 self._overflow_mode = overflow_mode 

199 # ``preprocess_module`` is the resolved block to transpose (first T text 

200 # layers, the visual tower, or a param-free identity for dataload-only). 

201 self._preprocess_module = preprocess_module 

202 self._num_transpose_layers = num_transpose_layers 

203 self._num_visual_layers = num_visual_layers 

204 # ``None`` = the preprocess output is stage 0's body input (arg_mbs); 

205 # VL passes a consumer that routes it into an injection kwarg instead. 

206 self._output_consumer = output_consumer 

207 # Trainable preprocess -> broadcast + stage-0 backward; frozen or 

208 # param-free -> ship the output only (no broadcast, no recompute). 

209 self._has_trainable_preprocess = self._module_has_trainable_params(preprocess_module) 

210 self._owner_backward = bool(owner_backward) and self._has_trainable_preprocess 

211 super().__init__(stages, 

212 micro_batch_num, 

213 args_batch_dim=args_batch_dim, 

214 kwargs_batch_dim=kwargs_batch_dim, 

215 output_concat_dim=output_concat_dim, 

216 overlap_p2p=overlap_p2p, 

217 overlap_b_f=False, 

218 swap=swap, 

219 less_memory=less_memory) 

220 self._executor = None 

221 self._setup_mpipe_execution() 

222 

223 @staticmethod 

224 def _module_has_trainable_params(module) -> bool: 

225 """Whether ``module`` has any trainable (grad-requiring) parameter.""" 

226 if module is None: 

227 return False 

228 return any(p.requires_grad for p in module.parameters()) 

229 

230 @property 

231 def preprocess_module(self) -> "Optional[Module]": 

232 """The preprocess block transposed to every rank (present on every rank).""" 

233 return self._preprocess_module 

234 

235 @property 

236 def output_consumer(self) -> Optional[Callable]: 

237 """Optional ``(ctx, micro, out_tuple)`` placement callback (None = body input).""" 

238 return self._output_consumer 

239 

240 @property 

241 def has_trainable_preprocess(self) -> bool: 

242 """Whether the preprocess has trainable params (drives broadcast / stage-0 backward).""" 

243 return self._has_trainable_preprocess 

244 

245 @property 

246 def owner_backward(self) -> bool: 

247 """Whether owner-does-backward is effective (trainable preprocess + torch).""" 

248 return self._owner_backward 

249 

250 @property 

251 def num_transpose_layers(self) -> int: 

252 """The informational transposed-layer count ``T`` (``0`` = dataload only).""" 

253 return self._num_transpose_layers 

254 

255 @property 

256 def num_transpose_micro_batches(self) -> int: 

257 """``NT = min(PP, micro_batch_num)`` — count of owner ranks per cycle.""" 

258 return min(self.real_stage_num, self.micro_batch_num) 

259 

260 def owned_micros(self, pp_rank: int) -> frozenset: 

261 """Owner set for ``pp_rank`` under :attr:`overflow_mode` (see ``mpipe_owned_micros``).""" 

262 return mpipe_owned_micros(self.real_stage_num, self.micro_batch_num, 

263 pp_rank, mode=self._overflow_mode) 

264 

265 def owner_of(self, micro: int) -> int: 

266 """Rank that owns micro ``micro`` under :attr:`overflow_mode`.""" 

267 nt = self.num_transpose_micro_batches 

268 if self._overflow_mode == "min": 

269 return 0 if micro >= nt else micro 

270 return micro % nt 

271 

272 def _setup_mpipe_execution(self) -> None: 

273 """Build the Torch executor and register the MPIPE_* handlers.""" 

274 self._executor = MPipeTransposeExecutor(self) 

275 self._apply_backward_retain_flag() 

276 handlers = { 

277 MpipeStepType.MPIPE_PARAM_BROADCAST: self._executor.broadcast_params, 

278 MpipeStepType.MPIPE_TRANSPOSE_FWD: self._executor.transpose_forward, 

279 MpipeStepType.MPIPE_FWD_SEND: self._executor.fwd_send, 

280 MpipeStepType.MPIPE_FWD_RECV: self._executor.fwd_recv, 

281 MpipeStepType.MPIPE_GRAPH_SEND: self._executor.fwd_bwd_graph_input_send, 

282 MpipeStepType.MPIPE_GRAPH_RECV: self._executor.fwd_bwd_graph_input_recv, 

283 MpipeStepType.MPIPE_TRANSPOSE_BWD: self._executor.transpose_backward, 

284 MpipeStepType.MPIPE_GRAD_SEND: self._executor.grad_send, 

285 MpipeStepType.MPIPE_GRAD_RECV_WITH_BACKWARD: self._executor.grad_recv_with_backward, 

286 MpipeStepType.MPIPE_GRAD_REDUCE: self._executor.reduce_tower_grads, 

287 } 

288 for step_type, handler in handlers.items(): 

289 self.register_custom_function(step_type, handler) 

290 

291 def _apply_backward_retain_flag(self) -> None: 

292 """Flag the stages to retain their body-backward graph for a TRAINABLE tower. 

293 

294 A trainable transposed tower + FSDP shares one all-gather node across the 

295 micro graphs that back-propagate into it (the connected micro-0 body graph 

296 plus the deferred owner/stage-0 tower backwards). The first backward to 

297 traverse that shared node frees it, so a later one raises "backward through 

298 the graph a second time" (surfaces at ``vpp >= 2``). Retaining the body 

299 graph keeps the shared node alive. A frozen tower (no shared trainable 

300 node) and plain PP (no flag) are unaffected. 

301 """ 

302 if self._has_trainable_preprocess: 

303 for stage in self.stages: 

304 stage.retain_backward_graph = True 

305 

306 def run_microbatches(self, arg_mbs: list, kwarg_mbs: list, losses: list) -> None: 

307 """Reset the executor's per-step caches, then run the schedule. 

308 

309 A step raising mid-run leaves its micro-batches in those caches, so the 

310 error path drops them (:meth:`MPipeTransposeExecutor.abort`) -- the 

311 counterpart of the scheduler's in-flight P2P drain, which covers only 

312 the comm handles. The normal path leaves the caches empty, which the 

313 next run's reset still verifies. 

314 

315 Args: 

316 arg_mbs (list): Per-micro-batch positional args. 

317 kwarg_mbs (list): Per-micro-batch keyword args. 

318 losses (list): Mutable list collecting per-step losses. 

319 """ 

320 if self._executor is not None: 

321 self._executor.reset() 

322 try: 

323 super().run_microbatches(arg_mbs, kwarg_mbs, losses) 

324 except BaseException: 

325 if self._executor is not None: 

326 self._executor.abort() 

327 raise 

328 

329 def construct_exec_order(self) -> None: 

330 """Build the body Interleaved 1F1B order, then layer the preprocess 

331 transpose phase and the centralized preprocess backward on top. 

332 

333 The parameter broadcast, the stage-0-backward input transport, and the 

334 stage-0 backward are emitted only for a **trainable** preprocess; a 

335 frozen (visual tower) or param-free (``T == 0``) one only transposes 

336 the forward and ships its output. 

337 

338 When owner-does-backward is effective (trainable + torch + opt-in) the 

339 stage-0-backward input transport and stage-0 backward are replaced by a 

340 feature-gradient ship-back (``MPIPE_GRAD_SEND`` on stage 0, 

341 ``MPIPE_GRAD_RECV_WITH_BACKWARD`` cooldown suffix on the owners) and a final 

342 ``MPIPE_GRAD_REDUCE`` collective on every rank. 

343 """ 

344 super().construct_exec_order() 

345 body_order = self.exec_order 

346 nt = self.num_transpose_micro_batches 

347 micro_batch_num = self.micro_batch_num 

348 has_trainable = self._has_trainable_preprocess 

349 owner_backward = self._owner_backward 

350 mode = self._overflow_mode 

351 # Body DATA_LOAD steps are rescheduled into the mpipe prefix (and, under 

352 # "min", inline onto rank 0's body for the overflow micros). 

353 for i, order in body_order.items(): 

354 body_order[i] = [step for step in order 

355 if step is not None and step.type != MetaStepType.DATA_LOAD] 

356 body_order[0] = self._insert_rank0_preprocess_steps( 

357 body_order[0], nt, has_trainable, mode, owner_backward=owner_backward, 

358 num_transpose_layers=self._num_transpose_layers) 

359 exec_order = {} 

360 for rank in range(self.real_stage_num): 

361 order = ( 

362 self._build_transpose_prefix( 

363 rank, nt, micro_batch_num, has_trainable, mode, 

364 owner_backward=owner_backward, 

365 num_transpose_layers=self._num_transpose_layers) 

366 + body_order[rank] 

367 ) 

368 if owner_backward: 

369 # Cooldown suffix: stage 0 ships dL/dfeatures, owners run the 

370 # tower backward, then all ranks SUM-all-reduce the tower grads. 

371 order = ( 

372 order 

373 + self._build_owner_backward_suffix(rank, nt, micro_batch_num, mode) 

374 + [MetaStep(None, MpipeStepType.MPIPE_GRAD_REDUCE, 0)] 

375 ) 

376 exec_order[rank] = order 

377 self.exec_order = exec_order 

378 # frozen + all visual layers transposed, so stage 0 needs no pixels 

379 if (not self._has_trainable_preprocess and 

380 self._num_visual_layers is not None and 

381 self._num_transpose_layers >= self._num_visual_layers): 

382 self._data_keys = ("input_ids",) + tuple( 

383 k for k in self.kwargs_batch_dim if k != "pixel_values" 

384 ) 

385 else: 

386 self._data_keys = ("input_ids",) + tuple(self.kwargs_batch_dim) 

387 

388 # Length-M so overflow micros and VPP chunks index in bounds; the 

389 # prefix key -1 ships owner to rank 0, body stages chain via stages[0]. 

390 self._data_dst = {-1: [0 for _ in range(self.micro_batch_num)]} 

391 self._data_src = {-1: [self.owner_of(m) for m in range(self.micro_batch_num)]} 

392 for i in range(self._stage_num): 

393 self._data_dst[i] = [self.stages[0].dst_stage for _ in range(self.micro_batch_num)] 

394 self._data_src[i] = [self.stages[0].src_stage for _ in range(self.micro_batch_num)] 

395 

396 @staticmethod 

397 def _build_transpose_prefix(rank, num_transpose_micro_batches, micro_batch_num, 

398 has_trainable, overflow_mode, 

399 owner_backward=False, num_transpose_layers=None): 

400 """Build the transpose-phase prefix prepended to ``rank``'s body order. 

401 

402 Under ``overflow_mode="full"`` (round-robin) rank ``rank`` (``< NT``) 

403 owns ``{m : m % NT == rank}`` — emits DATA_LOAD → MPIPE_TRANSPOSE_FWD 

404 per owned micro, and (when not rank 0) ships each to rank 0. Rank 0 

405 receives every non-owned micro. For a **trainable** preprocess every 

406 rank also broadcasts its (trainable) parameters and ranks ``> 0`` 

407 additionally ship the input for the centralized stage-0 backward; for 

408 a frozen (visual tower) preprocess only the transpose forward and its 

409 output send/recv remain. 

410 

411 Under ``"min"`` each owner (including rank 0) emits exactly one 

412 transposed micro (``{rank}``). Rank 0 receives only ``1..NT-1``; the 

413 overflow micros ``NT..M-1`` are loaded inline on rank 0 by 

414 :meth:`_insert_rank0_preprocess_steps`. 

415 

416 Dataload-only mode (``num_transpose_layers == 0``): every emission 

417 of MPIPE_PARAM_BROADCAST, MPIPE_TRANSPOSE_FWD, MPIPE_FWD_SEND/RECV, 

418 MPIPE_GRAPH_SEND/RECV is suppressed. Only DATA_LOAD + DATA_SEND/RECV 

419 remain; stage 0 runs the visual tower itself during body forward. 

420 Ownership (round-robin vs single-owner) still follows 

421 ``overflow_mode`` so the dataload I/O sharding stays balanced. 

422 

423 Under owner-does-backward the input ship-back (``MPIPE_GRAPH_SEND`` / 

424 ``MPIPE_GRAPH_RECV``) is dropped -- the owner keeps its forward graph and 

425 receives only the feature gradient later (``MPIPE_GRAD_RECV_WITH_BACKWARD``). 

426 """ 

427 nt = num_transpose_micro_batches 

428 owned = _prefix_owned_micros(rank, nt, micro_batch_num, overflow_mode) 

429 is_dataload_only = num_transpose_layers == 0 

430 prefix = [] 

431 if has_trainable and not is_dataload_only: 

432 prefix.append(MetaStep(rank, MpipeStepType.MPIPE_PARAM_BROADCAST, -1)) 

433 # DATA_LOAD + MPIPE_TRANSPOSE_FWD are local (no comm) — emit per-micro. 

434 for m in owned: 

435 prefix.append(MetaStep(m, MetaStepType.DATA_LOAD, -1)) 

436 if not is_dataload_only: 

437 prefix.append(MetaStep(m, MpipeStepType.MPIPE_TRANSPOSE_FWD, -1)) 

438 ship_graph = has_trainable and not owner_backward 

439 if rank != 0: 

440 return prefix + _prefix_transfer_steps(owned, _PREFIX_SEND_TYPES, is_dataload_only, ship_graph) 

441 # Non-owned = micros rank 0 didn't transpose locally ("min": 

442 # overflow micros load inline, so they are not received). 

443 if overflow_mode == "min": 

444 non_owned = list(range(1, nt)) 

445 else: 

446 non_owned = [m for m in range(micro_batch_num) if m not in owned] 

447 return prefix + _prefix_transfer_steps(non_owned, _PREFIX_RECV_TYPES, is_dataload_only, ship_graph) 

448 

449 @staticmethod 

450 def _build_owner_backward_suffix(rank, num_transpose_micro_batches, micro_batch_num, overflow_mode): 

451 """Cooldown suffix (owner-backward), appended AFTER all body steps so the 

452 feature-grad transport never interleaves with the body's P2P. 

453 

454 Stage 0 ships ``dL/dfeatures`` for every micro it does not own itself 

455 (``MPIPE_GRAD_SEND``, one per micro whose owner != 0). Each owner rank 

456 receives a gradient for **every** micro it owns (``MPIPE_GRAD_RECV_WITH_BACKWARD``, 

457 one step per owned micro) and backprops its retained tower graph for each. 

458 Ranks ``>= NT`` own no transposed micro-batch. 

459 

460 Under ``"min"`` an owner rank owns exactly one micro (``{rank}``), so this 

461 degenerates to the original one-gradient-per-rank exchange. Under ``"full"`` 

462 (round-robin) with ``M > NT`` an owner rank can own several micros 

463 (``{m : m % NT == rank}``); each gets its own SEND/RECV pair. Both sides 

464 iterate ``range(micro_batch_num)`` in ascending order, so for any fixed 

465 (rank 0, owner) pair the send order matches the receive order -- required 

466 since HCCL serializes P2P per rank-pair on a single channel. 

467 """ 

468 nt = num_transpose_micro_batches 

469 

470 def owner_of(micro: int) -> int: 

471 """Rank that owns ``micro`` under the active overflow mode.""" 

472 if overflow_mode == "min": 

473 return 0 if micro >= nt else micro 

474 return micro % nt 

475 

476 if rank == 0: 

477 return [MetaStep(micro, MpipeStepType.MPIPE_GRAD_SEND, 0) 

478 for micro in range(micro_batch_num) if owner_of(micro) != 0] 

479 if rank < nt: 

480 return [MetaStep(micro, MpipeStepType.MPIPE_GRAD_RECV_WITH_BACKWARD, 0) 

481 for micro in range(micro_batch_num) if owner_of(micro) == rank] 

482 return [] 

483 

484 @staticmethod 

485 def _insert_rank0_preprocess_steps(order, num_transpose_micro_batches, 

486 has_trainable, overflow_mode, owner_backward=False, 

487 num_transpose_layers=None): 

488 """Patch stage 0's body order for the overflow / recompute paths. 

489 

490 Under ``"full"``: every micro is transposed (somewhere). No inline 

491 preprocess on rank 0; under trainable, MPIPE_TRANSPOSE_BWD fires after 

492 every stage-0 BWD (the preprocess output is detached either way). 

493 

494 Under ``"min"``: rank 0 absorbs the overflow micros ``NT..M-1``. Before 

495 each stage-0 FWD on an overflow micro we insert DATA_LOAD and (under 

496 trainable or frozen) MPIPE_TRANSPOSE_FWD — running the preprocess 

497 locally with a detached graph. For frozen this is just "rank 0 

498 runs the ViT here so the body forward has the encoded features / 

499 visual-injection kwarg"; for trainable the body BWD also needs an 

500 MPIPE_TRANSPOSE_BWD stage-0 backward for the preprocess gradient. (The 

501 previous design used a graph-connected forward to avoid the 

502 stage-0 backward on overflow micros; that optimization isn't restored 

503 here — `"min"` saves comm for slightly more rank-0 compute on the 

504 trainable path.) 

505 

506 Under owner-does-backward (``owner_backward``) the centralized stage-0 

507 backward is replaced: the body backward deposits ``dL/dfeatures`` on the 

508 placed feature tensors, and a stage-0 cooldown SUFFIX 

509 (:meth:`_build_owner_backward_suffix`) ships it back to the owners -- NOT 

510 inline here, since the blocking grad meta-exchange would deadlock against 

511 the body's P2P. So no per-BWD step is inserted for any micro; every 

512 owned/inline forward on stage 0 already runs graph-connected 

513 (``transpose_forward``'s owner-backward branch), so the body backward 

514 flows into the tower automatically. 

515 

516 Dataload-only (``num_transpose_layers == 0``) suppresses the inline 

517 MPIPE_TRANSPOSE_FWD, matching the contract documented on 

518 :meth:`_build_transpose_prefix`: with an identity preprocess there is 

519 nothing to run ahead of the body, and stage 0 consumes the raw loaded 

520 data directly. 

521 """ 

522 if not has_trainable and overflow_mode == "full": 

523 return order 

524 nt = num_transpose_micro_batches 

525 is_dataload_only = num_transpose_layers == 0 

526 patched = [] 

527 for step in order: 

528 is_stage0_fwd = ( 

529 step is not None 

530 and step.type == MetaStepType.FWD 

531 and step.stage_index == 0 

532 ) 

533 if (overflow_mode == "min" and is_stage0_fwd 

534 and step.micro_index >= nt): 

535 patched.append(MetaStep(step.micro_index, MetaStepType.DATA_LOAD, -1)) 

536 if not is_dataload_only: 

537 # Populates the visual-injection kwarg via output_consumer; 

538 # without it rank 0's body forward crashes. 

539 patched.append(MetaStep(step.micro_index, MpipeStepType.MPIPE_TRANSPOSE_FWD, -1)) 

540 patched.append(step) 

541 is_stage0_bwd = ( 

542 step is not None 

543 and step.type == MetaStepType.BWD 

544 and step.stage_index == 0 

545 ) 

546 if is_stage0_bwd and has_trainable and not owner_backward: 

547 patched.append(MetaStep(step.micro_index, MpipeStepType.MPIPE_TRANSPOSE_BWD, -1)) 

548 return patched