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

273 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"""AsyncContextParallel: overlap projection GEMM with CP collectives. 

16 

17Supports async Pure Ulysses, async Pure Colossal AI, and Hybrid CP modes. 

18Hybrid keeps the original half-async path: async Ulysses A2A + sync Colossal 

19AllGather. Falls back to sync ContextParallel when q/k/v_proj are not provided. 

20 

21Forward: proj hooks launch async A2A → attn pre-hook waits Q/K/V → attn hook gathers output 

22Backward: autograd backward launches async A2A → proj pre-hooks wait before GEMMs 

23""" 

24from functools import partial 

25from typing import Optional 

26 

27from hyper_parallel.core.context_parallel.context_parallel import ( 

28 ContextParallel, 

29 _build_hybrid_cp_mesh, 

30 _compose_cp_non_cp_placements, 

31 _ensure_1d, 

32 _finalize_ata_output, 

33 _gather_seq, 

34 _gather_head_to_seq, 

35 _is_cp_composed_dtensor, 

36 _localize_foreign_dtensor, 

37 _non_cp_dtensor_layout, 

38 _pop_output_layout, 

39 _to_cp_dtensor, 

40) 

41from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

42from hyper_parallel.core.dtensor.dtensor import DTensor 

43from hyper_parallel.core.dtensor.placement_types import Shard, Replicate 

44from hyper_parallel.platform import get_platform 

45 

46platform = get_platform() 

47Module = platform.Module 

48Tensor = platform.Tensor 

49 

50 

51# --------------------------------------------------------------------------- 

52# All-to-all helpers 

53# --------------------------------------------------------------------------- 

54 

55def _detach_if_available(tensor: Tensor) -> Tensor: 

56 """Detach the communication buffer when the backend tensor exposes ``detach``.""" 

57 detach = getattr(tensor, "detach", None) 

58 return detach() if detach is not None else tensor 

59 

60 

61def _launch_async_a2a_seq_to_head( 

62 tensor: Tensor, 

63 group, 

64 world_size: int, 

65 head_dim: int, 

66) -> tuple: 

67 """Launch async seq→head A2A (forward).""" 

68 x = tensor.contiguous() 

69 shape = list(x.shape) 

70 num_heads = shape[head_dim] 

71 if num_heads % world_size != 0: 

72 raise ValueError(f"num_heads ({num_heads}) must be divisible by world_size ({world_size}).") 

73 ndim = len(shape) + 1 

74 x_perm = x.reshape( 

75 shape[:head_dim] + [world_size, num_heads // world_size] + shape[head_dim + 1:] 

76 ).permute( 

77 [head_dim] + list(range(head_dim)) + list(range(head_dim + 1, ndim)) 

78 ).contiguous() 

79 out_perm, work = platform.all_to_all_single(_detach_if_available(x_perm), list(x_perm.shape), group, async_op=True) 

80 return work, out_perm 

81 

82 

83def _a2a_reconstruct(out_perm: Tensor, concat_dim: int) -> Tensor: 

84 """Reconstruct A2A result from raw out_perm.""" 

85 new_ndim = out_perm.dim() 

86 chunk_in_perm = concat_dim + 1 

87 recon_perm = list(range(1, chunk_in_perm)) + [0] + list(range(chunk_in_perm, new_ndim)) 

88 x_recon = out_perm.permute(recon_perm).contiguous() 

89 shape = list(x_recon.shape) 

90 merged = shape[concat_dim] * shape[concat_dim + 1] 

91 return x_recon.reshape(shape[:concat_dim] + [merged] + shape[concat_dim + 2:]) 

92 

93 

94def _normalize_dim(dim: int, ndim: int) -> int: 

95 """Normalize a possibly negative dimension index.""" 

96 return dim + ndim if dim < 0 else dim 

97 

98 

99def _move_dim_to_front(tensor: Tensor, dim: int) -> Tensor: 

100 """Move ``dim`` to the leading dimension before all-gather/reduce-scatter.""" 

101 dim = _normalize_dim(dim, tensor.dim()) 

102 if dim == 0: 

103 return tensor.contiguous() 

104 perm = [dim] + [i for i in range(tensor.dim()) if i != dim] 

105 return tensor.permute(perm).contiguous() 

106 

107 

108def _move_dim_from_front(tensor: Tensor, dim: int) -> Tensor: 

109 """Inverse of :func:`_move_dim_to_front`.""" 

110 dim = _normalize_dim(dim, tensor.dim()) 

111 if dim == 0: 

112 return tensor.contiguous() 

113 perm = [dim] + [i for i in range(tensor.dim()) if i != dim] 

114 inverse = [0] * len(perm) 

115 for idx, value in enumerate(perm): 

116 inverse[value] = idx 

117 return tensor.permute(inverse).contiguous() 

118 

119 

120def _launch_async_allgather_seq( 

121 tensor: Tensor, 

122 group, 

123 world_size: int, 

124 gather_dim: int, 

125) -> tuple: 

126 """Launch async all-gather along ``gather_dim``.""" 

127 x_perm = _move_dim_to_front(tensor.contiguous(), gather_dim) 

128 output_shape = list(x_perm.shape) 

129 output_shape[0] *= world_size 

130 out_perm, work = platform.all_gather_single(_detach_if_available(x_perm), output_shape, group, async_op=True) 

131 return work, out_perm 

132 

133 

134def _allgather_reconstruct(out_perm: Tensor, gather_dim: int) -> Tensor: 

135 """Move the leading communication buffer dimension back to ``gather_dim``. 

136 

137 The input may be a forward all-gather output buffer or a backward 

138 reduce-scatter local buffer, as both are stored with ``gather_dim`` moved 

139 to dimension 0 before communication. 

140 """ 

141 return _move_dim_from_front(out_perm, gather_dim) 

142 

143 

144# --------------------------------------------------------------------------- 

145# Tensor helpers 

146# --------------------------------------------------------------------------- 

147 

148def _to_local(tensor: Tensor) -> Tensor: 

149 """Return the local Tensor from a DTensor, or the tensor itself.""" 

150 return tensor.to_local() if isinstance(tensor, DTensor) else tensor 

151 

152 

153def _to_cp_local(tensor: Tensor, submesh: DeviceMesh, seq_dim: int) -> Tensor: 

154 """Return the local tensor CP async collectives should consume.""" 

155 tensor = _localize_foreign_dtensor(tensor, submesh, seq_dim) 

156 return tensor.to_local() if isinstance(tensor, DTensor) else tensor 

157 

158 

159def _cp_execution_layout_from_input(tensor: Tensor, cp_mesh: DeviceMesh, cp_placements, seq_dim: int) -> tuple: 

160 """Return the DTensor layout to use after a raw async CP collective.""" 

161 cp_placements = tuple(cp_placements) 

162 layout = _non_cp_dtensor_layout(tensor, cp_mesh, seq_dim) 

163 if layout is None: 

164 return cp_mesh, cp_placements 

165 non_cp_mesh, non_cp_placements, composed_mesh = layout 

166 return composed_mesh, _compose_cp_non_cp_placements( 

167 composed_mesh, 

168 cp_mesh, 

169 cp_placements, 

170 non_cp_mesh, 

171 non_cp_placements, 

172 ) 

173 

174 

175def _make_async_cp_slot(work, out_perm, tensor: Tensor, cp_mesh: DeviceMesh, cp_placements, seq_dim: int) -> dict: 

176 """Store an async handle together with the layout needed after wait.""" 

177 return { 

178 "work": work, 

179 "out_perm": out_perm, 

180 "layout": _cp_execution_layout_from_input(tensor, cp_mesh, cp_placements, seq_dim), 

181 } 

182 

183 

184def _slot_comm(slot): 

185 """Return ``(work, out_perm)`` from a new metadata slot or a legacy tuple.""" 

186 if isinstance(slot, dict): 

187 return slot["work"], slot["out_perm"] 

188 return slot 

189 

190 

191def _slot_layout(slot, fallback_mesh: DeviceMesh, fallback_placements) -> tuple: 

192 """Return the post-collective layout recorded in *slot*.""" 

193 if isinstance(slot, dict) and "layout" in slot: 

194 return slot["layout"] 

195 return fallback_mesh, tuple(fallback_placements) 

196 

197 

198def _wrap_async_cp_result(local: Tensor, slot, fallback_mesh: DeviceMesh, fallback_placements) -> "DTensor": 

199 """Wrap a waited async result with the layout captured before communication.""" 

200 mesh, placements = _slot_layout(slot, fallback_mesh, fallback_placements) 

201 return DTensor.from_local(local, mesh, placements) 

202 

203 

204# --------------------------------------------------------------------------- 

205# AsyncContextParallel 

206# --------------------------------------------------------------------------- 

207 

208class AsyncContextParallel(ContextParallel): 

209 """Context Parallel with projection–collective compute overlap. 

210 

211 Requires ``q_proj``, ``k_proj``, ``v_proj`` in :meth:`apply`; otherwise 

212 falls back to synchronous :class:`ContextParallel`. 

213 

214 Pure Colossal AI (``ulysses_degree=1``) overlaps K/V AllGather when the 

215 backend exposes async all-gather handles. Hybrid CP overlaps Ulysses A2A and 

216 keeps the Colossal K/V AllGather synchronous for backward-ordering safety. 

217 

218 Args: 

219 seq_dim: Sequence dimension (1=BSHD, 2=BNSD). 

220 head_dim: Head dimension (2=BSHD, 1=BNSD). 

221 ulysses_degree: Ulysses sub-mesh size (see :class:`ContextParallel`). 

222 qkv_indices: Positional indices of (Q, K, V) in attention forward. 

223 qkv_kwarg_names: Keyword names for (Q, K, V). 

224 use_local_output: Return local tensors after CP when True. When False, 

225 keep CP DTensor outputs, or drop the CP axis from 

226 composed CP+TP outputs and keep the non-CP layout. 

227 load_balance: Load-balance flag forwarded to base class. 

228 """ 

229 

230 def __init__( 

231 self, 

232 seq_dim: int = 1, 

233 head_dim: int = 2, 

234 ulysses_degree: Optional[int] = None, 

235 qkv_indices: tuple = (0, 1, 2), 

236 qkv_kwarg_names: tuple = (), 

237 use_local_output: bool = False, 

238 load_balance: bool = False, 

239 ): 

240 super().__init__( 

241 seq_dim=seq_dim, 

242 head_dim=head_dim, 

243 ulysses_degree=ulysses_degree, 

244 qkv_indices=qkv_indices, 

245 qkv_kwarg_names=qkv_kwarg_names, 

246 use_local_output=use_local_output, 

247 load_balance=load_balance, 

248 ) 

249 

250 # ------------------------------------------------------------------ 

251 # Public entry point 

252 # ------------------------------------------------------------------ 

253 

254 def apply( # pylint: disable=arguments-differ 

255 self, 

256 module: Module, 

257 device_mesh: DeviceMesh, 

258 q_proj: Optional[Module] = None, 

259 k_proj: Optional[Module] = None, 

260 v_proj: Optional[Module] = None, 

261 ) -> Module: 

262 """Register async-overlap hooks and return *module*. 

263 

264 Falls back to synchronous :class:`ContextParallel` if any of 

265 ``q/k/v_proj`` is ``None``. 

266 

267 Args: 

268 module: Core-attention submodule. 

269 device_mesh: CP device mesh (1-D or 2-D). 

270 q_proj: The last module in the Q path whose output is passed 

271 directly to the attention module as Q. Its forward 

272 post-hook launches the async Q all-to-all. There 

273 must be **no** intermediate ops (view, transpose, …) 

274 between this module and attention; such ops would be 

275 bypassed by the pre-hook substitution and could cause 

276 shape mismatches. For models with QK normalization 

277 applied right before attention, pass ``qk_norm_q`` 

278 here instead of the raw projection. 

279 k_proj: Same semantics as ``q_proj``, for the K path. Pass 

280 ``qk_norm_k`` when the model applies QK-Norm before 

281 attention. 

282 v_proj: Value projection module (no norm variant needed). 

283 """ 

284 if q_proj is None or k_proj is None or v_proj is None: 

285 return super().apply(module, device_mesh) 

286 

287 cp_size = device_mesh.mesh.numel() 

288 ds = self.ulysses_degree if self.ulysses_degree is not None else cp_size 

289 if cp_size % ds != 0: 

290 raise ValueError( 

291 f"cp_size ({cp_size}) must be divisible by ulysses_degree ({ds})." 

292 ) 

293 co = cp_size // ds 

294 

295 if ds == 1: 

296 if self.load_balance: 

297 return super().apply(module, device_mesh) 

298 return self._apply_colossal_async(module, device_mesh, cp_size, k_proj, v_proj) 

299 

300 return self._apply_a2a_async(module, device_mesh, ds, co, q_proj, k_proj, v_proj) 

301 

302 def _apply_colossal_async( 

303 self, 

304 module: Module, 

305 device_mesh: DeviceMesh, 

306 cp_size: int, 

307 k_proj: Module, 

308 v_proj: Module, 

309 ) -> Module: 

310 """Register Pure Colossal async K/V AllGather hooks.""" 

311 co_submesh = _ensure_1d(device_mesh) 

312 group = co_submesh.get_group() 

313 fwd_ag_slots = {"k": None, "v": None} 

314 bwd_ag_slots = {"k": [], "v": []} 

315 self._register_ag_proj_hooks( 

316 k_proj, 

317 v_proj, 

318 submesh=co_submesh, 

319 group=group, 

320 world_size=cp_size, 

321 fwd_slots=fwd_ag_slots, 

322 bwd_slots=bwd_ag_slots, 

323 ) 

324 platform.register_forward_pre_hook( 

325 module, 

326 partial( 

327 self._attn_pre_hook_colossal, 

328 co_submesh=co_submesh, 

329 group=group, 

330 world_size=cp_size, 

331 fwd_slots=fwd_ag_slots, 

332 bwd_slots=bwd_ag_slots, 

333 ), 

334 with_kwargs=True, 

335 ) 

336 module.register_forward_hook( 

337 partial(self._post_hook_colossal, co_submesh=co_submesh) 

338 ) 

339 return module 

340 

341 def _apply_a2a_async( # pylint: disable=too-many-arguments 

342 self, 

343 module: Module, 

344 device_mesh: DeviceMesh, 

345 ds: int, 

346 co: int, 

347 q_proj: Module, 

348 k_proj: Module, 

349 v_proj: Module, 

350 ) -> Module: 

351 """Register Pure Ulysses or Hybrid async A2A hooks.""" 

352 fwd_slots = {"q": None, "k": None, "v": None} 

353 bwd_slots = {"q": [], "k": [], "v": []} 

354 

355 if co == 1: 

356 ds_submesh = _ensure_1d(device_mesh) 

357 group = ds_submesh.get_group() 

358 a2a_layout_mesh = ds_submesh 

359 a2a_layout_placements = (Shard(self.head_dim),) 

360 pre_hook = partial( 

361 self._attn_pre_hook_ulysses, 

362 ds_submesh=ds_submesh, 

363 group=group, 

364 world_size=ds, 

365 fwd_slots=fwd_slots, 

366 bwd_slots=bwd_slots, 

367 ) 

368 post_hook = partial(self._attn_post_hook_ata, ds_submesh=ds_submesh) 

369 else: 

370 hybrid_cp_mesh = _build_hybrid_cp_mesh(device_mesh, ds, co) 

371 dim_names = hybrid_cp_mesh.mesh_dim_names 

372 if dim_names is None: 

373 raise ValueError("2-D mesh must have mesh_dim_names (guaranteed by _build_2d_mesh)") 

374 ds_submesh = hybrid_cp_mesh[dim_names[1]] 

375 group = ds_submesh.get_group() 

376 a2a_layout_mesh = hybrid_cp_mesh 

377 a2a_layout_placements = (Shard(self.seq_dim), Shard(self.head_dim)) 

378 pre_hook = partial( 

379 self._attn_pre_hook_hybrid, 

380 ds_submesh=ds_submesh, 

381 group=group, 

382 world_size=ds, 

383 hybrid_cp_mesh=hybrid_cp_mesh, 

384 fwd_slots=fwd_slots, 

385 bwd_slots=bwd_slots, 

386 ) 

387 post_hook = partial( 

388 self._post_hook_hybrid, 

389 hybrid_cp_mesh=hybrid_cp_mesh, 

390 ds_submesh=ds_submesh, 

391 ) 

392 

393 self._register_proj_hooks( 

394 q_proj, 

395 k_proj, 

396 v_proj, 

397 submesh=ds_submesh, 

398 group=group, 

399 world_size=ds, 

400 fwd_slots=fwd_slots, 

401 bwd_slots=bwd_slots, 

402 layout_mesh=a2a_layout_mesh, 

403 layout_placements=a2a_layout_placements, 

404 ) 

405 platform.register_forward_pre_hook( 

406 module, 

407 pre_hook, 

408 with_kwargs=True, 

409 ) 

410 module.register_forward_hook(post_hook) 

411 return module 

412 

413 # ------------------------------------------------------------------ 

414 # Shared: projection hooks registration 

415 # ------------------------------------------------------------------ 

416 

417 def _register_proj_hooks( 

418 self, 

419 q_proj, 

420 k_proj, 

421 v_proj, 

422 submesh, 

423 group, 

424 world_size, 

425 fwd_slots, 

426 bwd_slots, 

427 layout_mesh, 

428 layout_placements, 

429 ): 

430 """Register forward and backward hooks on all three projection modules.""" 

431 for key, proj in [("q", q_proj), ("k", k_proj), ("v", v_proj)]: 

432 proj.register_forward_hook( 

433 partial(self._proj_post_hook, key=key, submesh=submesh, group=group, world_size=world_size, 

434 fwd_slots=fwd_slots, layout_mesh=layout_mesh, layout_placements=layout_placements) 

435 ) 

436 platform.register_full_backward_pre_hook( 

437 proj, 

438 partial(self._proj_bwd_pre_hook, bwd_slot=bwd_slots[key]) 

439 ) 

440 

441 def _proj_post_hook( # pylint: disable=unused-argument,too-many-arguments 

442 self, module, inputs, output, key, submesh, group, world_size, fwd_slots, layout_mesh, layout_placements 

443 ): 

444 """Launch async seq→head A2A after projection; return original output unchanged.""" 

445 tensor = _to_cp_local(output, submesh, self.seq_dim) 

446 work, out_perm = _launch_async_a2a_seq_to_head( 

447 tensor, group, world_size, self.head_dim 

448 ) 

449 fwd_slots[key] = _make_async_cp_slot( 

450 work, 

451 out_perm, 

452 output, 

453 layout_mesh, 

454 layout_placements, 

455 self.seq_dim, 

456 ) 

457 return output 

458 

459 def _register_ag_proj_hooks(self, k_proj, v_proj, submesh, group, world_size, fwd_slots, bwd_slots): 

460 """Register async AllGather hooks for K/V projection modules.""" 

461 for key, proj in [("k", k_proj), ("v", v_proj)]: 

462 proj.register_forward_hook( 

463 partial(self._proj_ag_post_hook, key=key, submesh=submesh, group=group, world_size=world_size, 

464 fwd_slots=fwd_slots) 

465 ) 

466 platform.register_full_backward_pre_hook( 

467 proj, 

468 partial(self._proj_ag_bwd_pre_hook, bwd_slot=bwd_slots[key]) 

469 ) 

470 

471 def _proj_ag_post_hook( # pylint: disable=unused-argument,too-many-arguments 

472 self, module, inputs, output, key, submesh, group, world_size, fwd_slots 

473 ): 

474 """Launch async K/V AllGather after projection; return original output.""" 

475 tensor = _to_cp_local(output, submesh, self.seq_dim) 

476 work, out_perm = _launch_async_allgather_seq( 

477 tensor, group, world_size, self.seq_dim 

478 ) 

479 fwd_slots[key] = _make_async_cp_slot( 

480 work, 

481 out_perm, 

482 output, 

483 submesh, 

484 (Replicate(),), 

485 self.seq_dim, 

486 ) 

487 return output 

488 

489 def _get_qkv_value(self, args, kwargs, qkv_pos: int): 

490 """Return Q/K/V value from positional args or configured kwargs.""" 

491 idx = self.qkv_indices[qkv_pos] 

492 if idx < len(args): 

493 return args[idx] 

494 if qkv_pos < len(self.qkv_kwarg_names): 

495 name = self.qkv_kwarg_names[qkv_pos] 

496 if name in kwargs: 

497 return kwargs[name] 

498 return None 

499 

500 def _set_qkv_value(self, args, kwargs, qkv_pos: int, value): 

501 """Set Q/K/V value in positional args or configured kwargs.""" 

502 idx = self.qkv_indices[qkv_pos] 

503 if idx < len(args): 

504 args[idx] = value 

505 return 

506 if qkv_pos < len(self.qkv_kwarg_names): 

507 name = self.qkv_kwarg_names[qkv_pos] 

508 if name in kwargs: 

509 kwargs[name] = value 

510 

511 # ------------------------------------------------------------------ 

512 # Internal: wait for pre-launched communication handles 

513 # ------------------------------------------------------------------ 

514 

515 def _wait_a2a(self, tensor, group, world_size, fwd_slots, key, bwd_slot): 

516 """Wait for pre-launched A2A; returns head-scattered tensor (differentiable).""" 

517 slot = fwd_slots[key] 

518 work, out_perm = _slot_comm(slot) 

519 fwd_slots[key] = None 

520 return platform.differentiable_async_a2a_wait( 

521 tensor, work, out_perm, group, world_size, 

522 self.seq_dim, self.head_dim, # concat_dim=seq_dim, split_dim=head_dim 

523 bwd_slot, 

524 ) 

525 

526 def _wait_a2a_dtensor( 

527 self, 

528 tensor, 

529 group, 

530 world_size, 

531 fwd_slots, 

532 key, 

533 bwd_slot, 

534 fallback_mesh, 

535 fallback_placements, 

536 ): 

537 """Wait for pre-launched A2A and wrap the result with captured CP+non-CP layout.""" 

538 slot = fwd_slots[key] 

539 local = self._wait_a2a( 

540 tensor, 

541 group, 

542 world_size, 

543 fwd_slots, 

544 key, 

545 bwd_slot, 

546 ) 

547 return _wrap_async_cp_result(local, slot, fallback_mesh, fallback_placements) 

548 

549 def _wait_allgather(self, tensor, group, world_size, work, out_perm, bwd_slot=None): 

550 """Wait for pre-launched AllGather and return gathered tensor.""" 

551 return platform.differentiable_async_allgather_wait( 

552 tensor, 

553 work, 

554 out_perm, 

555 group, 

556 world_size, 

557 self.seq_dim, 

558 bwd_slot, 

559 ) 

560 

561 # ------------------------------------------------------------------ 

562 # QKV transform helpers 

563 # ------------------------------------------------------------------ 

564 

565 def _apply_qkv_transforms( 

566 self, 

567 args, 

568 kwargs, 

569 submesh, 

570 transform_q, 

571 transform_k, 

572 transform_v, 

573 ): 

574 """Apply transforms to Q/K/V and return modified ``(args, kwargs)``. 

575 

576 Each transform receives ``(original, local)``. The local tensor is the 

577 raw communication buffer, while the original value carries any non-CP 

578 DTensor layout that must be restored after the async wait. 

579 """ 

580 new_args = list(args) 

581 new_kwargs = dict(kwargs) 

582 

583 transforms = (transform_q, transform_k, transform_v) 

584 for pos, transform in enumerate(transforms): 

585 value = self._get_qkv_value(new_args, new_kwargs, pos) 

586 if value is None: 

587 continue 

588 self._set_qkv_value( 

589 new_args, 

590 new_kwargs, 

591 pos, 

592 transform(value, _to_cp_local(value, submesh, self.seq_dim)), 

593 ) 

594 return tuple(new_args), new_kwargs 

595 

596 # ------------------------------------------------------------------ 

597 # Attention pre-hooks 

598 # ------------------------------------------------------------------ 

599 

600 def _attn_pre_hook_ulysses( # pylint: disable=unused-argument,too-many-arguments 

601 self, module, args, kwargs, group, world_size, fwd_slots, bwd_slots, ds_submesh=None 

602 ): 

603 """Wait Q/K/V A2A and return head-scattered args/kwargs.""" 

604 self._record_q_output_tp_layout(module, args, kwargs, ds_submesh) 

605 

606 def make_a2a(key): 

607 def transform(value, local_value): # pylint: disable=unused-argument 

608 if ds_submesh is None: 

609 return self._wait_a2a( 

610 local_value, 

611 group, 

612 world_size, 

613 fwd_slots, 

614 key, 

615 bwd_slots[key], 

616 ) 

617 return self._wait_a2a_dtensor( 

618 local_value, 

619 group, 

620 world_size, 

621 fwd_slots, 

622 key, 

623 bwd_slots[key], 

624 ds_submesh, 

625 (Shard(self.head_dim),), 

626 ) 

627 return transform 

628 

629 return self._apply_qkv_transforms( 

630 args, 

631 kwargs, 

632 ds_submesh, 

633 transform_q=make_a2a("q"), 

634 transform_k=make_a2a("k"), 

635 transform_v=make_a2a("v"), 

636 ) 

637 

638 def _ulysses_cp_layout_from_input(self, t, ds_submesh): 

639 """Return the pure-Ulysses execution mesh and placements for one input tensor.""" 

640 cp_placements = (Shard(self.head_dim),) 

641 layout = _non_cp_dtensor_layout(t, ds_submesh, self.seq_dim) 

642 if layout is None: 

643 return ds_submesh, cp_placements 

644 non_cp_mesh, non_cp_placements, composed_mesh = layout 

645 return composed_mesh, _compose_cp_non_cp_placements( 

646 composed_mesh, 

647 ds_submesh, 

648 cp_placements, 

649 non_cp_mesh, 

650 non_cp_placements, 

651 ) 

652 

653 def _attn_pre_hook_colossal( # pylint: disable=unused-argument,too-many-arguments 

654 self, module, args, kwargs, co_submesh, group, world_size, fwd_slots, bwd_slots 

655 ): 

656 """Wait K/V AllGather, wrap Q as local shard and return CP-ready args/kwargs.""" 

657 new_args = list(args) 

658 new_kwargs = dict(kwargs) 

659 

660 q_value = self._get_qkv_value(new_args, new_kwargs, 0) 

661 self._record_q_output_tp_layout(module, new_args, new_kwargs, co_submesh) 

662 if q_value is not None: 

663 self._set_qkv_value( 

664 new_args, 

665 new_kwargs, 

666 0, 

667 _to_cp_dtensor( 

668 q_value, 

669 co_submesh, 

670 (Shard(self.seq_dim),), 

671 (Shard(self.seq_dim),), 

672 self.seq_dim, 

673 ), 

674 ) 

675 

676 for pos, key in enumerate(("k", "v"), start=1): 

677 value = self._get_qkv_value(new_args, new_kwargs, pos) 

678 if value is None: 

679 continue 

680 local = _to_cp_local(value, co_submesh, self.seq_dim) 

681 slot = fwd_slots[key] 

682 work, out_perm = _slot_comm(slot) 

683 fwd_slots[key] = None 

684 gathered = self._wait_allgather( 

685 local, 

686 group, 

687 world_size, 

688 work, 

689 out_perm, 

690 bwd_slots[key], 

691 ) 

692 if isinstance(value, DTensor): 

693 layout = _non_cp_dtensor_layout(value, co_submesh, self.seq_dim) 

694 if layout is not None: 

695 non_cp_mesh, non_cp_placements, composed_mesh = layout 

696 composed_placements = _compose_cp_non_cp_placements( 

697 composed_mesh, 

698 co_submesh, 

699 (Replicate(),), 

700 non_cp_mesh, 

701 non_cp_placements, 

702 ) 

703 self._set_qkv_value( 

704 new_args, 

705 new_kwargs, 

706 pos, 

707 DTensor.from_local( 

708 gathered.to_local() if isinstance(gathered, DTensor) else gathered, 

709 composed_mesh, 

710 composed_placements, 

711 ), 

712 ) 

713 continue 

714 self._set_qkv_value( 

715 new_args, 

716 new_kwargs, 

717 pos, 

718 DTensor.from_local( 

719 _to_local(gathered), 

720 co_submesh, 

721 (Replicate(),), 

722 ), 

723 ) 

724 

725 return tuple(new_args), new_kwargs 

726 

727 def _attn_pre_hook_hybrid( # pylint: disable=too-many-locals,too-many-arguments 

728 self, module, args, kwargs, group, world_size, hybrid_cp_mesh, # pylint: disable=unused-argument 

729 fwd_slots, bwd_slots, ds_submesh=None 

730 ): 

731 """Wait Ulysses A2A for Q/K/V, then synchronously gather K/V on the co-submesh.""" 

732 new_args = list(args) 

733 new_kwargs = dict(kwargs) 

734 

735 co_submesh = hybrid_cp_mesh[hybrid_cp_mesh.mesh_dim_names[0]] 

736 if ds_submesh is None: 

737 ds_submesh = hybrid_cp_mesh[hybrid_cp_mesh.mesh_dim_names[1]] 

738 self._record_q_output_tp_layout(module, new_args, new_kwargs, hybrid_cp_mesh) 

739 

740 q_value = self._get_qkv_value(new_args, new_kwargs, 0) 

741 if q_value is not None: 

742 self._set_qkv_value( 

743 new_args, 

744 new_kwargs, 

745 0, 

746 self._wait_a2a_dtensor( 

747 _to_cp_local(q_value, ds_submesh, self.seq_dim), 

748 group, 

749 world_size, 

750 fwd_slots, 

751 "q", 

752 bwd_slots["q"], 

753 hybrid_cp_mesh, 

754 (Shard(self.seq_dim), Shard(self.head_dim)), 

755 ), 

756 ) 

757 

758 k_value = self._get_qkv_value(new_args, new_kwargs, 1) 

759 if k_value is not None: 

760 k_ul = self._wait_a2a_dtensor( 

761 _to_cp_local(k_value, ds_submesh, self.seq_dim), 

762 group, 

763 world_size, 

764 fwd_slots, 

765 "k", 

766 bwd_slots["k"], 

767 hybrid_cp_mesh, 

768 (Shard(self.seq_dim), Shard(self.head_dim)), 

769 ) 

770 k_full = _gather_seq(k_ul, co_submesh, self.seq_dim) 

771 k_mesh, k_placements = self._hybrid_kv_gather_placements( 

772 k_value, 

773 hybrid_cp_mesh, 

774 ) 

775 self._set_qkv_value( 

776 new_args, 

777 new_kwargs, 

778 1, 

779 DTensor.from_local( 

780 _to_local(k_full), 

781 k_mesh, 

782 k_placements, 

783 ), 

784 ) 

785 

786 v_value = self._get_qkv_value(new_args, new_kwargs, 2) 

787 if v_value is not None: 

788 v_ul = self._wait_a2a_dtensor( 

789 _to_cp_local(v_value, ds_submesh, self.seq_dim), 

790 group, 

791 world_size, 

792 fwd_slots, 

793 "v", 

794 bwd_slots["v"], 

795 hybrid_cp_mesh, 

796 (Shard(self.seq_dim), Shard(self.head_dim)), 

797 ) 

798 v_full = _gather_seq(v_ul, co_submesh, self.seq_dim) 

799 v_mesh, v_placements = self._hybrid_kv_gather_placements( 

800 v_value, 

801 hybrid_cp_mesh, 

802 ) 

803 self._set_qkv_value( 

804 new_args, 

805 new_kwargs, 

806 2, 

807 DTensor.from_local( 

808 _to_local(v_full), 

809 v_mesh, 

810 v_placements, 

811 ), 

812 ) 

813 

814 return tuple(new_args), new_kwargs 

815 

816 # ------------------------------------------------------------------ 

817 # Attention post-hook (Ulysses and Hybrid share the same reverse ATA) 

818 # ------------------------------------------------------------------ 

819 

820 def _attn_post_hook_ata(self, module, args, output, ds_submesh): # pylint: disable=unused-argument 

821 """Reverse head→seq gather on ds_submesh; returns local tensor.""" 

822 output_layout = _pop_output_layout(module) 

823 

824 def _process(output_item): 

825 if isinstance(output_item, (Tensor, DTensor)): 

826 if isinstance(output_item, DTensor) and not _is_cp_composed_dtensor(output_item, ds_submesh): 

827 output_item = output_item.to_local() 

828 seq_dtensor = _gather_head_to_seq( 

829 output_item, ds_submesh, self.seq_dim, self.head_dim 

830 ) 

831 return _finalize_ata_output( 

832 seq_dtensor, output_layout, ds_submesh, self.seq_dim, self.use_local_output 

833 ) 

834 return output_item 

835 

836 if isinstance(output, (tuple, list)): 

837 return type(output)(_process(item) for item in output) 

838 return _process(output) 

839 

840 # ------------------------------------------------------------------ 

841 # Backward: wait A2A handle (launched by autograd) before proj GEMM 

842 # ------------------------------------------------------------------ 

843 

844 def _proj_bwd_pre_hook(self, module, grad_output, bwd_slot): # pylint: disable=unused-argument 

845 """Wait backward A2A just before proj GEMM; replace grad with seq-form. 

846 

847 The async head→seq A2A is launched inside _TorchAsyncA2AFunction.backward 

848 and appended to ``bwd_slot``. Waiting here lets the A2A overlap with the 

849 preceding proj GEMM. 

850 """ 

851 work, out_perm = bwd_slot.pop() 

852 work.wait() 

853 d_seq = _a2a_reconstruct(out_perm, self.head_dim) 

854 return (d_seq,) + grad_output[1:] if isinstance(grad_output, tuple) else (d_seq,) 

855 

856 def _proj_ag_bwd_pre_hook(self, module, grad_output, bwd_slot): # pylint: disable=unused-argument 

857 """Wait backward reduce-scatter just before K/V projection GEMM.""" 

858 work, out_perm, gather_dim = bwd_slot.pop() 

859 work.wait() 

860 d_local = _allgather_reconstruct(out_perm, gather_dim) 

861 return (d_local,) + grad_output[1:] if isinstance(grad_output, tuple) else (d_local,)