Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / pipeline_parallel / _sync_hook.py: 87%
39 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« 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"""Autograd synchronization hooks for pipeline overlap."""
16from typing import Any
18import torch
20from hyper_parallel.core.pipeline_parallel.hook_coordinator import HookCoordinator, HookRole
23class _SyncHookFunction(torch.autograd.Function):
24 """Autograd identity that fires HookCoordinator rendezvous on fwd/bwd.
26 Uses a **4-hook** design (``A``, ``B``, ``C``, ``D``) with pure
27 COMM / COMPUTE roles — no NONE role. Every rendezvous is a strict
28 COMM + COMPUTE pair, guaranteeing NCCL-first dispatch ordering at
29 **all** points including layer boundaries.
31 Hook placement per MoE layer::
33 [A] → dispatch → [B] → module → [C] → combine → [D] → (Attention) → [A_next]
35 At layer boundaries (D / A hooks), the Attention that runs between
36 layers is treated as COMPUTE, and the combine / combine.bwd is treated
37 as COMM, so the coordinator enforces comm-first ordering even across
38 layer transitions.
39 """
41 # 4-hook role tables: (prev_role_idx, next_role_idx).
42 # Index encoding: 1 = COMM, 2 = COMPUTE.
43 #
44 _FWD_ROLES = {
45 # (prev, next) prev op next op
46 "A": (2, 1), # COMPUTE, COMM Attention | dispatch
47 "B": (1, 2), # COMM, COMPUTE dispatch | module
48 "C": (2, 1), # COMPUTE, COMM module | combine
49 "D": (1, 2), # COMM, COMPUTE combine | Attention
50 }
51 _BWD_ROLES = {
52 "D": (2, 1), # COMPUTE, COMM Attn.bwd | combine.bwd
53 "C": (1, 2), # COMM, COMPUTE combine.bwd | module.bwd
54 "B": (2, 1), # COMPUTE, COMM module.bwd | dispatch.bwd
55 "A": (1, 2), # COMM, COMPUTE dispatch.bwd| Attn.bwd
56 }
58 _ROLE_CACHE = (None, HookRole.COMM, HookRole.COMPUTE)
60 @staticmethod
61 def _role_enum(idx: int):
62 return _SyncHookFunction._ROLE_CACHE[idx]
64 @staticmethod
65 def forward(ctx: Any, x: torch.Tensor, hook_name: str, coordinator: HookCoordinator) -> torch.Tensor: # pylint: disable=arguments-differ
66 """Identity forward that fires a HookCoordinator rendezvous.
68 Notifies the previous op's role and rendezvouses for the next op's
69 role per the ``_FWD_ROLES`` table. ``"D_LAST"`` is a sentinel
70 meaning "skip this rendezvous" (last layer's closing D — no
71 Attention follows).
73 Args:
74 ctx: Autograd context, stores ``hook_name`` and
75 ``coordinator`` for the backward pass.
76 x: Input tensor, returned unchanged.
77 hook_name: One of ``"A"``, ``"B"``, ``"C"``, ``"D"``,
78 ``"D_LAST"``.
79 coordinator: The :class:`HookCoordinator` driving the rendezvous.
81 Returns:
82 ``x`` unchanged.
83 """
84 ctx.hook_name = hook_name
85 ctx.coordinator = coordinator
87 if not coordinator.is_enabled():
88 return x
90 if hook_name == "D_LAST":
91 # ``D_LAST`` marks the last layer's closing D hook — no
92 # Attention follows in this chunk, so the rendezvous is
93 # meaningless and is skipped. We still
94 # ``notify_dispatched(COMM)`` so the COMPUTE side of the
95 # preceding ``C`` rendezvous unblocks early, letting
96 # BWD's Attn.bwd_last overlap with FWD's post-combine
97 # work — Torch autograd is thread-safe so this concurrent
98 # FWD-record + BWD-replay is fine.
99 prev_idx, _ = _SyncHookFunction._FWD_ROLES["D"]
100 role_of = _SyncHookFunction._role_enum
101 coordinator.notify_dispatched(role_of(prev_idx))
102 return x
104 prev_idx, next_idx = _SyncHookFunction._FWD_ROLES[hook_name]
105 role_of = _SyncHookFunction._role_enum
106 coordinator.notify_dispatched(role_of(prev_idx))
107 coordinator.rendezvous(role_of(next_idx))
108 return x
110 @staticmethod
111 def backward(ctx: Any, grad_output: torch.Tensor) -> tuple[torch.Tensor, None, None]:
112 """Identity backward that fires a HookCoordinator rendezvous.
114 Mirror of :meth:`forward` using the ``_BWD_ROLES`` table.
115 ``"D_LAST"`` skips the rendezvous because this is the first BWD
116 hook to fire and ``combine.bwd`` has already dispatched freely
117 before any rendezvous can happen.
119 Args:
120 ctx: Autograd context with ``hook_name`` and
121 ``coordinator`` saved during forward.
122 grad_output: Gradient w.r.t. the forward output, returned
123 unchanged.
125 Returns:
126 ``(grad_output, None, None)`` — gradients only flow back to
127 the tensor input, ``hook_name`` and ``coordinator`` are
128 non-tensor inputs.
129 """
130 hook_name = ctx.hook_name
131 coordinator = ctx.coordinator
133 if not coordinator.is_enabled():
134 return grad_output, None, None
136 if hook_name == "D_LAST":
137 # First BWD hook to fire; combine.bwd has already
138 # dispatched freely before any rendezvous can happen.
139 # Skipping here is safe on Torch because CUDA streams
140 # are process-wide and the NCCL FIFO order is consistent
141 # across ranks regardless of which thread launched
142 # combine.bwd.
143 return grad_output, None, None
145 prev_idx, next_idx = _SyncHookFunction._BWD_ROLES[hook_name]
146 role_of = _SyncHookFunction._role_enum
147 coordinator.notify_dispatched(role_of(prev_idx))
148 coordinator.rendezvous(role_of(next_idx))
149 return grad_output, None, None