Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / context_parallel / async_dsa_context_parallel.py: 87%
111 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +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"""Async context parallel styles for DeepSeek Sparse Attention (DSA)."""
16from typing import Any, Optional
18from hyper_parallel.core.context_parallel.async_context_parallel import (
19 _allgather_reconstruct,
20 _launch_async_allgather_seq,
21 _make_async_cp_slot,
22 _slot_comm,
23 _wrap_async_cp_result,
24)
25from hyper_parallel.core.context_parallel.context_parallel import _ensure_1d, _localize_foreign_dtensor
26from hyper_parallel.core.context_parallel.dsa_context_parallel import (
27 DSAIndexerContextParallel,
28 DSAIndexerLossContextParallel,
29 DSASparseAttentionContextParallel,
30 _apply_sparse_attention_boundary,
31 _is_tensor_or_dtensor,
32 _to_sequence_replicate,
33)
34from hyper_parallel.core.dtensor.device_mesh import DeviceMesh
35from hyper_parallel.core.dtensor.dtensor import DTensor
36from hyper_parallel.core.dtensor.placement_types import Replicate
37from hyper_parallel.platform import get_platform
39platform = get_platform()
40Module = platform.Module
43class _AsyncSequenceReplicateSlot:
44 """Per-boundary async sequence-replicate state.
46 Producer hooks append pre-launched all-gather handles here. Consumer
47 pre-hooks pop and wait on them. If no producer hook ran, the consumer falls
48 back to the synchronous sequence-gather path so async DSA styles remain
49 compatible with the synchronous DSA module shape.
50 """
52 def __init__(self, device_mesh: DeviceMesh, seq_dim: int) -> None:
53 self.device_mesh = device_mesh
54 self.seq_dim = seq_dim
55 self.world_size = device_mesh.mesh.numel()
56 self.group = device_mesh.get_group() if self.world_size > 1 else None
57 self._slots = {}
58 self._bwd_slots = {}
60 @staticmethod
61 def _extract_tensor_output(output: Any) -> Any:
62 if _is_tensor_or_dtensor(output):
63 return output
64 if isinstance(output, (tuple, list)) and len(output) == 1 and _is_tensor_or_dtensor(output[0]):
65 return output[0]
66 return None
68 def _local_tensor(self, value: Any) -> Any:
69 value = _localize_foreign_dtensor(value, self.device_mesh, self.seq_dim)
70 return value.to_local() if isinstance(value, DTensor) else value
72 def _make_slot(self, value: Any, local: Any, work, out_perm) -> dict:
73 """Create an async sequence-replicate slot with layout metadata."""
74 slot = _make_async_cp_slot(
75 work,
76 out_perm,
77 value,
78 self.device_mesh,
79 (Replicate(),),
80 self.seq_dim,
81 )
82 slot["local"] = local
83 return slot
85 def _wrap_gathered(self, gathered: Any, slot) -> Any:
86 return _wrap_async_cp_result(gathered, slot, self.device_mesh, (Replicate(),))
88 def register_launch_hook(self, module: Optional[Module], slot_name: str) -> None:
89 """Register a producer-side launch hook when a handoff module exists."""
90 if module is None:
91 return
92 bwd_slot = self._bwd_slots.setdefault(slot_name, [])
94 def _post_hook(hook_module, hook_inputs, output):
95 del hook_module, hook_inputs
96 tensor = self._extract_tensor_output(output)
97 if tensor is not None:
98 self.launch(slot_name, tensor)
99 return output
101 def _backward_pre_hook(hook_module, grad_output):
102 del hook_module
103 return self._producer_bwd_pre_hook(grad_output, bwd_slot)
105 module.register_forward_hook(_post_hook)
106 platform.register_full_backward_pre_hook(module, _backward_pre_hook)
108 def _producer_bwd_pre_hook(self, grad_output: Any, bwd_slot: list) -> Any:
109 """Wait deferred reduce-scatter before gradients cross the producer boundary."""
110 if not bwd_slot:
111 return grad_output
112 work, out_perm, gather_dim = bwd_slot.pop()
113 work.wait()
114 d_local = _allgather_reconstruct(out_perm, gather_dim)
115 if isinstance(grad_output, tuple):
116 return (d_local,) + grad_output[1:]
117 return (d_local,)
119 def launch(self, slot_name: str, value: Any) -> None:
120 """Launch all-gather for ``value`` and enqueue its handle."""
121 if not _is_tensor_or_dtensor(value):
122 return
123 local = self._local_tensor(value)
124 if not platform.is_tensor(local):
125 return
126 if self.world_size <= 1:
127 self._slots.setdefault(slot_name, []).append(self._make_slot(value, local, None, None))
128 return
129 work, out_perm = _launch_async_allgather_seq(local, self.group, self.world_size, self.seq_dim)
130 self._slots.setdefault(slot_name, []).append(self._make_slot(value, local, work, out_perm))
132 def wait(self, slot_name: str, value: Any) -> Any:
133 """Wait on a pre-launched gather, or fall back to consumer-local launch."""
134 if not _is_tensor_or_dtensor(value):
135 return value
136 slot = self._slots.get(slot_name)
137 if slot:
138 item = slot.pop(0)
139 if isinstance(item, dict):
140 local = item["local"]
141 work, out_perm = _slot_comm(item)
142 else:
143 local, work, out_perm = item
144 if work is None:
145 return self._wrap_gathered(local, item)
146 graph_local = self._local_tensor(value)
147 if not platform.is_tensor(graph_local):
148 graph_local = local
149 gathered = platform.differentiable_async_allgather_wait(
150 graph_local,
151 work,
152 out_perm,
153 self.group,
154 self.world_size,
155 self.seq_dim,
156 self._bwd_slots.setdefault(slot_name, []),
157 )
158 return self._wrap_gathered(gathered, item)
159 return _to_sequence_replicate(value, self.device_mesh, self.seq_dim)
162class AsyncDSAIndexerContextParallel(DSAIndexerContextParallel):
163 """Async-first DSA indexer CP.
165 When ``key_handoff`` is provided, its forward hook launches key-side
166 all-gather and the indexer boundary pre-hook waits on it. Without a
167 handoff, this style falls back to the synchronous key-side gather.
168 """
170 def apply(self, module: Module, device_mesh: DeviceMesh, *, key_handoff: Optional[Module] = None) -> Module:
171 """Register async DSA indexer CP hooks on ``module`` and optional producer."""
172 cp_mesh = _ensure_1d(device_mesh)
173 async_state = _AsyncSequenceReplicateSlot(cp_mesh, self.seq_dim)
174 async_state.register_launch_hook(key_handoff, "key")
175 specs = self._build_specs(
176 cp_mesh,
177 key_fn=lambda value: async_state.wait("key", value),
178 )
179 return self._apply_with_specs(module, specs, cp_mesh)
182class AsyncDSASparseAttentionContextParallel(DSASparseAttentionContextParallel):
183 """Async-first DSA sparse-attention CP."""
185 def apply( # pylint: disable=arguments-differ
186 self,
187 module: Module,
188 device_mesh: DeviceMesh,
189 *,
190 key_handoff: Optional[Module] = None,
191 value_handoff: Optional[Module] = None,
192 key_rope_handoff: Optional[Module] = None,
193 ) -> Module:
194 """Register async DSA sparse-attention CP hooks on ``module`` and producers."""
195 cp_mesh = _ensure_1d(device_mesh)
196 async_state = _AsyncSequenceReplicateSlot(cp_mesh, self.seq_dim)
197 async_state.register_launch_hook(key_handoff, "key")
198 async_state.register_launch_hook(value_handoff, "value")
199 async_state.register_launch_hook(key_rope_handoff, "key_rope")
200 return _apply_sparse_attention_boundary(self, module, device_mesh, async_state=async_state)
203class AsyncDSAIndexerLossContextParallel(DSAIndexerLossContextParallel):
204 """Async-first DSA indexer-loss CP."""
206 def apply( # pylint: disable=arguments-differ,too-many-arguments
207 self,
208 module: Module,
209 device_mesh: DeviceMesh,
210 *,
211 key_handoff: Optional[Module] = None,
212 key_indexer_handoff: Optional[Module] = None,
213 key_rope_handoff: Optional[Module] = None,
214 ) -> Module:
215 """Register async DSA indexer-loss CP hooks on ``module`` and producers."""
216 cp_mesh = _ensure_1d(device_mesh)
217 async_state = _AsyncSequenceReplicateSlot(cp_mesh, self.seq_dim)
218 async_state.register_launch_hook(key_handoff, "key")
219 async_state.register_launch_hook(key_indexer_handoff, "key_indexer")
220 async_state.register_launch_hook(key_rope_handoff, "key_rope")
221 specs = self._build_loss_specs(
222 cp_mesh,
223 replicate_fn_map={
224 "key": lambda value: async_state.wait("key", value),
225 "key_indexer": lambda value: async_state.wait("key_indexer", value),
226 "key_rope": lambda value: async_state.wait("key_rope", value),
227 },
228 )
229 return self._apply_with_loss_specs(module, specs, self._get_local_idx(cp_mesh), cp_mesh)