Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / context_parallel / dsa_context_parallel.py: 83%
379 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"""Context parallel style for DeepSeek Sparse Attention (DSA).
17These PyNative module-level styles prepare DTensor placements at DSA indexer,
18sparse-attention, and indexer-loss module boundaries. Boundary implementations
19are responsible for consuming the resulting DTensor or local-tensor inputs.
21The first implementation intentionally supports only Colossal-style CP:
22query-side tensors are sharded on sequence, while key-side tensors are gathered
23to CP-replicated layouts. Ulysses/head sharding is rejected because the current
24DSA kernels require attention head, index head, head dim and sparse top-k dims to
25stay replicated.
26"""
27from dataclasses import dataclass
28from typing import Any, Callable, Optional
30import torch.distributed as dist
32from hyper_parallel.core.context_parallel.context_parallel import (
33 _OUTPUT_NON_CP,
34 _drop_cp_from_output,
35 _ensure_1d,
36 _non_cp_dtensor_layout,
37 _pop_output_layout,
38 _push_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 Replicate, Shard
44from hyper_parallel.core.tensor_parallel.style import ParallelStyle
45from hyper_parallel.core.context_parallel import utils
46from hyper_parallel.core.context_parallel.utils import Module
50_SUPPORTED_LAYOUTS = ("BSND", "TND")
51_SUPPORTED_LOSS_VARIANTS = ("sparse", "dense")
52_DEFAULT_ARG_INDEX = object()
55def _move_dim_to_front(value: Any, dim: int) -> tuple[Any, list[int]]:
56 """Move ``dim`` to the leading position and return the inverse permutation."""
57 order = [dim] + [index for index in range(len(value.shape)) if index != dim]
58 inverse = [0] * len(order)
59 for new_index, old_index in enumerate(order):
60 inverse[old_index] = new_index
61 return value.permute(order).contiguous(), inverse
64class _DSASequenceReplicateGradientBridge(utils.Function):
65 """Reuse one gathered forward value while preserving per-consumer backward."""
67 @staticmethod
68 def forward(
69 ctx: Any,
70 local_value: Any,
71 replicated_value: Any,
72 group: Any,
73 world_size: int,
74 seq_dim: int,
75 ) -> Any:
76 """Return the detached shared value and retain only collective metadata."""
77 del local_value
78 ctx.group = group
79 ctx.world_size = world_size
80 ctx.seq_dim = seq_dim
81 return replicated_value
83 @staticmethod
84 def backward( # pylint: disable=arguments-differ
85 ctx: Any, grad_output: Any
86 ) -> tuple[Any, None, None, None, None]:
87 """Reduce-scatter this consumer's gradient independently to its local input."""
88 if ctx.world_size == 1:
89 return grad_output, None, None, None, None
90 grad_front, inverse = _move_dim_to_front(grad_output, ctx.seq_dim)
91 output_shape = list(grad_front.shape)
92 if output_shape[0] % ctx.world_size != 0:
93 raise ValueError(
94 "DSA shared replicate backward requires a divisible sequence dimension, "
95 f"got {output_shape[0]} and CP size {ctx.world_size}."
96 )
97 output_shape[0] //= ctx.world_size
98 local_grad, work = utils.reduce_scatter_single(
99 grad_front, output_shape, ctx.group, async_op=False
100 )
101 if work is not None:
102 work.wait()
103 local_grad = local_grad.permute(inverse).contiguous()
104 return local_grad, None, None, None, None
107class DSASequenceReplicateCache:
108 """Share semantically identical CP AllGather results within one DSA layer.
110 Callers assign stable semantic slot names only to local tensors that are
111 mathematically identical views of the same activation. The first consumer
112 performs the sequence AllGather and later consumers reuse that storage.
113 Each consumer receives an independent gradient bridge so its reverse
114 ReduceScatter remains separate, matching the pre-cache accumulation order.
116 The cache is deliberately scoped to one sparse-attention/indexer-loss
117 forward interval. :meth:`begin` drops stale entries before sparse attention
118 and :meth:`clear` releases references after indexer-loss inputs are built.
119 """
121 def __init__(self) -> None:
122 """Initialize an empty per-layer communication cache."""
123 self._values: dict[str, Any] = {}
124 self._signatures: dict[str, tuple] = {}
126 @staticmethod
127 def _signature(value: Any, device_mesh: DeviceMesh, seq_dim: int) -> tuple:
128 """Build a metadata-only signature without synchronizing the device."""
129 if isinstance(value, DTensor):
130 shape = tuple(value.local_shape)
131 dtype = value.dtype
132 else:
133 shape = tuple(value.shape)
134 dtype = value.dtype
135 return shape, dtype, tuple(device_mesh.rank_list), seq_dim
137 def begin(self) -> None:
138 """Start a new sparse-attention interval and discard stale references."""
139 self.clear()
141 def replicate(self, slot_name: str, value: Any, device_mesh: DeviceMesh, seq_dim: int) -> Any:
142 """Return the shared replicated DTensor for one semantic activation."""
143 if not _is_tensor_or_dtensor(value):
144 return value
145 signature = self._signature(value, device_mesh, seq_dim)
146 if slot_name in self._values:
147 if self._signatures[slot_name] != signature:
148 raise ValueError(
149 f"DSA shared replicate slot {slot_name!r} received incompatible "
150 f"metadata: expected {self._signatures[slot_name]}, got {signature}."
151 )
152 replicated = self._values[slot_name]
153 else:
154 replicated = _to_sequence_replicate(value, device_mesh, seq_dim)
155 if isinstance(replicated, DTensor):
156 replicated = DTensor.from_local_with_layout(
157 replicated.to_local().detach(), replicated.layout
158 )
159 else:
160 replicated = replicated.detach()
161 self._values[slot_name] = replicated
162 self._signatures[slot_name] = signature
164 local_value = value.to_local() if isinstance(value, DTensor) else value
165 replicated_value = replicated.to_local() if isinstance(replicated, DTensor) else replicated
166 bridged_value = _DSASequenceReplicateGradientBridge.apply(
167 local_value,
168 replicated_value,
169 device_mesh.get_group(),
170 device_mesh.size(),
171 seq_dim,
172 )
173 if isinstance(replicated, DTensor):
174 return DTensor.from_local_with_layout(bridged_value, replicated.layout)
175 return bridged_value
177 def clear(self) -> None:
178 """Release cache-owned references after all forward consumers are wired."""
179 self._values.clear()
180 self._signatures.clear()
183def _is_tensor_or_dtensor(value: Any) -> bool:
184 """Return True for framework tensors and HyperParallel DTensors."""
185 return isinstance(value, DTensor) or utils.is_tensor(value)
188def _to_sequence_shard(value: Any, device_mesh: DeviceMesh, seq_dim: int) -> Any:
189 """Annotate ``value`` as sequence-sharded on ``device_mesh``."""
190 if not _is_tensor_or_dtensor(value):
191 return value
192 return _to_cp_dtensor(value, device_mesh, (Shard(seq_dim),), (Shard(seq_dim),), seq_dim)
195def _to_sequence_replicate(value: Any, device_mesh: DeviceMesh, seq_dim: int) -> Any:
196 """Annotate ``value`` as local sequence shard, then all-gather on CP."""
197 if not _is_tensor_or_dtensor(value):
198 return value
199 return _to_cp_dtensor(value, device_mesh, (Shard(seq_dim),), (Replicate(),), seq_dim)
202def _to_query_stats_shard(value: Any, device_mesh: DeviceMesh, stats_seq_dim: int) -> Any:
203 """Annotate query-side softmax stats as sharded on their sequence dimension."""
204 if not _is_tensor_or_dtensor(value):
205 return value
206 return _to_cp_dtensor(
207 value,
208 device_mesh,
209 (Shard(stats_seq_dim),),
210 (Shard(stats_seq_dim),),
211 stats_seq_dim,
212 )
215def _maybe_replace_arg(args: list, index: Optional[int], fn) -> None:
216 """Apply ``fn`` to ``args[index]`` when the index points to an existing arg."""
217 if index is None or index >= len(args):
218 return
219 args[index] = fn(args[index])
222def _maybe_replace_kwarg(kwargs: dict, name: Optional[str], fn) -> None:
223 """Apply ``fn`` to ``kwargs[name]`` when the key exists."""
224 if name is None or name not in kwargs:
225 return
226 kwargs[name] = fn(kwargs[name])
229@dataclass
230class _ParamSpec:
231 """Describe one tensor argument location and transform."""
233 index: Optional[int]
234 kwarg_name: Optional[str]
235 fn: Callable[[Any], Any]
238def _apply_param_specs(args: list, kwargs: dict, specs: list[_ParamSpec]) -> None:
239 """Apply each spec's transform to the matching positional or keyword argument."""
240 for spec in specs:
241 _maybe_replace_arg(args, spec.index, spec.fn)
242 _maybe_replace_kwarg(kwargs, spec.kwarg_name, spec.fn)
245def _validate_layout_and_mode(style_name: str, layout: str, mode: str) -> tuple[str, int]:
246 """Return normalized layout and sequence dim for the DSA CP style."""
247 layout = layout.upper()
248 if layout not in _SUPPORTED_LAYOUTS:
249 raise ValueError(f"layout must be one of {_SUPPORTED_LAYOUTS}, but got {layout!r}.")
250 if mode != "colossal":
251 raise ValueError(f"{style_name} currently supports only mode='colossal'.")
252 return layout, 1 if layout == "BSND" else 0
255def _validate_loss_variant(loss_variant: str) -> str:
256 """Return normalized DSA indexer-loss variant."""
257 loss_variant = loss_variant.lower()
258 if loss_variant not in _SUPPORTED_LOSS_VARIANTS:
259 raise ValueError(f"loss_variant must be one of {_SUPPORTED_LOSS_VARIANTS}, but got {loss_variant!r}.")
260 return loss_variant
263def _query_stats_seq_dim(layout: str) -> int:
264 """Return the sequence dimension used by query-side softmax stats."""
265 return 2 if layout == "BSND" else 1
268def _default_arg_index(index: Any, default: Optional[int]) -> Optional[int]:
269 """Resolve optional positional index defaults while preserving explicit None."""
270 return default if index is _DEFAULT_ARG_INDEX else index
273def _finalize_output(value: Any, use_local_output: bool, output_layout=None, seq_dim: Optional[int] = None) -> Any:
274 """Convert direct DTensor outputs, or one-level tuple/list outputs, to local tensors."""
275 output_kind, layout = output_layout if output_layout is not None else (None, None)
277 def finalize_one(item):
278 if use_local_output:
279 return item.to_local() if isinstance(item, DTensor) else item
280 if output_kind == _OUTPUT_NON_CP and seq_dim is not None:
281 return _drop_cp_from_output(item, layout, (Shard(seq_dim),))
282 return item
284 if isinstance(value, DTensor):
285 return finalize_one(value)
286 if isinstance(value, tuple):
287 return tuple(finalize_one(v) for v in value)
288 if isinstance(value, list):
289 return [finalize_one(v) for v in value]
290 return value
293def _dtensor_has_partial(value: DTensor) -> bool:
294 """Return whether ``value`` has any Partial placement."""
295 return any(placement.is_partial() for placement in value.placements)
298def _dtensor_to_local_reducing_partial(value: Any) -> Any:
299 """Convert a DTensor to local, reducing Partial only when communication is needed."""
300 if not isinstance(value, DTensor):
301 return value
302 if _dtensor_has_partial(value) and value.device_mesh.mesh.numel() > 1:
303 value = value.reduce_partial()
304 return value.to_local()
307def _register_boundary_hooks(module: Module, pre_hook, use_local_output: bool, seq_dim: int) -> None:
308 """Register a DSA boundary pre-hook and its public output conversion hook."""
309 utils.register_forward_pre_hook(module, pre_hook, with_kwargs=True)
311 def _finalize_output_hook(hook_module, hook_args, outputs):
312 del hook_args
313 return _finalize_output(
314 outputs,
315 use_local_output,
316 _pop_output_layout(hook_module),
317 seq_dim,
318 )
320 module.register_forward_hook(_finalize_output_hook)
323def _record_query_output_layout(module: Module, value: Any, cp_mesh: DeviceMesh, seq_dim: int) -> None:
324 """Remember the non-CP query layout so outputs can drop CP on exit."""
325 layout = _non_cp_dtensor_layout(value, cp_mesh, seq_dim)
326 _push_output_layout(module, (_OUTPUT_NON_CP, layout) if layout is not None else None)
329def _read_value(args: list, kwargs: dict, index: Optional[int], kwarg_name: Optional[str]) -> Any:
330 """Read a positional or keyword value from a hook argument pair."""
331 if index is not None and index < len(args):
332 return args[index]
333 if kwarg_name is not None and kwarg_name in kwargs:
334 return kwargs[kwarg_name]
335 return None
338def _configure_sparse_attention_boundary( # pylint: disable=too-many-arguments
339 style,
340 *,
341 layout: str,
342 mode: str,
343 query_index: Optional[int],
344 key_index: Optional[int],
345 value_index: Optional[int],
346 topk_index: Optional[int],
347 query_kwarg_name: Optional[str],
348 key_kwarg_name: Optional[str],
349 value_kwarg_name: Optional[str],
350 topk_kwarg_name: Optional[str],
351 query_rope_index: Optional[int],
352 key_rope_index: Optional[int],
353 query_rope_kwarg_name: Optional[str],
354 key_rope_kwarg_name: Optional[str],
355 use_local_output: bool,
356 shared_replicate_cache: Optional[DSASequenceReplicateCache],
357 share_key_value: bool,
358) -> None:
359 """Store sparse-attention boundary configuration on ``style``."""
360 layout, seq_dim = _validate_layout_and_mode(style.__class__.__name__, layout, mode)
361 style.layout = layout
362 style.mode = mode
363 style.seq_dim = seq_dim
364 style.query_index = query_index
365 style.key_index = key_index
366 style.value_index = value_index
367 style.topk_index = topk_index
368 style.query_kwarg_name = query_kwarg_name
369 style.key_kwarg_name = key_kwarg_name
370 style.value_kwarg_name = value_kwarg_name
371 style.topk_kwarg_name = topk_kwarg_name
372 style.query_rope_index = query_rope_index
373 style.key_rope_index = key_rope_index
374 style.query_rope_kwarg_name = query_rope_kwarg_name
375 style.key_rope_kwarg_name = key_rope_kwarg_name
376 style.use_local_output = use_local_output
377 style.shared_replicate_cache = shared_replicate_cache
378 style.share_key_value = share_key_value
381def _apply_sparse_attention_boundary(
382 style,
383 module: Module,
384 device_mesh: DeviceMesh,
385 *,
386 async_state: Optional[Any] = None,
387) -> Module:
388 """Register low-level DSA sparse-attention boundary hooks for ``style``."""
389 cp_mesh = _ensure_1d(device_mesh)
391 def _shard(value: Any) -> Any:
392 return _to_sequence_shard(value, cp_mesh, style.seq_dim)
394 def _replicate(slot_name: str):
395 if async_state is not None:
396 def _wait_for_replicate(value):
397 return async_state.wait(slot_name, value)
399 return _wait_for_replicate
400 if style.shared_replicate_cache is not None:
401 def _replicate_from_cache(value):
402 return style.shared_replicate_cache.replicate(
403 slot_name, value, cp_mesh, style.seq_dim
404 )
406 return _replicate_from_cache
408 def _replicate_sequence(value):
409 return _to_sequence_replicate(value, cp_mesh, style.seq_dim)
411 return _replicate_sequence
413 key_slot = "main_kv" if style.share_key_value else "key"
414 value_slot = "main_kv" if style.share_key_value else "value"
416 specs = [
417 _ParamSpec(style.query_index, style.query_kwarg_name, _shard),
418 _ParamSpec(style.key_index, style.key_kwarg_name, _replicate(key_slot)),
419 _ParamSpec(style.value_index, style.value_kwarg_name, _replicate(value_slot)),
420 _ParamSpec(style.topk_index, style.topk_kwarg_name, _shard),
421 _ParamSpec(style.query_rope_index, style.query_rope_kwarg_name, _shard),
422 _ParamSpec(style.key_rope_index, style.key_rope_kwarg_name, _replicate("key_rope")),
423 ]
425 def _pre_hook(hook_module, args, kwargs):
426 new_args = list(args)
427 new_kwargs = dict(kwargs)
428 if style.shared_replicate_cache is not None:
429 style.shared_replicate_cache.begin()
430 _record_query_output_layout(
431 hook_module,
432 _read_value(new_args, new_kwargs, style.query_index, style.query_kwarg_name),
433 cp_mesh,
434 style.seq_dim,
435 )
436 _apply_param_specs(new_args, new_kwargs, specs)
437 return tuple(new_args), new_kwargs
439 _register_boundary_hooks(module, _pre_hook, style.use_local_output, style.seq_dim)
440 return module
443class DSAIndexerContextParallel(ParallelStyle):
444 """Colossal-style CP hook for a DSA indexer boundary.
446 This style targets a hookable module/cell whose forward signature is shaped
447 like ``(query, key, weights, ...)`` and rewrites only that boundary:
449 - ``query`` and ``weights`` are annotated as ``Shard(seq)``;
450 - ``key`` is all-gathered to ``Replicate()``.
451 """
453 def __init__( # pylint: disable=too-many-arguments
454 self,
455 *,
456 layout: str = "BSND",
457 mode: str = "colossal",
458 query_index: Optional[int] = 0,
459 key_index: Optional[int] = 1,
460 weights_index: Optional[int] = 2,
461 query_kwarg_name: Optional[str] = None,
462 key_kwarg_name: Optional[str] = None,
463 weights_kwarg_name: Optional[str] = None,
464 use_local_output: bool = False,
465 ) -> None:
466 super().__init__()
467 layout, seq_dim = _validate_layout_and_mode(self.__class__.__name__, layout, mode)
468 self.layout = layout
469 self.mode = mode
470 self.seq_dim = seq_dim
471 self.query_index = query_index
472 self.key_index = key_index
473 self.weights_index = weights_index
474 self.query_kwarg_name = query_kwarg_name
475 self.key_kwarg_name = key_kwarg_name
476 self.weights_kwarg_name = weights_kwarg_name
477 self.use_local_output = use_local_output
479 def __repr__(self) -> str:
480 return (
481 f"{self.__class__.__name__}("
482 f"layout={self.layout!r}, mode={self.mode!r}, "
483 f"use_local_output={self.use_local_output})"
484 )
486 def _shard_query_side(self, value: Any, device_mesh: DeviceMesh) -> Any:
487 return _to_sequence_shard(value, device_mesh, self.seq_dim)
489 def _replicate_key_side(self, value: Any, device_mesh: DeviceMesh) -> Any:
490 return _to_sequence_replicate(value, device_mesh, self.seq_dim)
492 def _build_specs(self, cp_mesh: DeviceMesh, key_fn: Callable[[Any], Any]) -> list[_ParamSpec]:
493 """Build table-driven transforms for the indexer boundary."""
494 def shard(value: Any) -> Any:
495 return self._shard_query_side(value, cp_mesh)
497 return [
498 _ParamSpec(self.query_index, self.query_kwarg_name, shard),
499 _ParamSpec(self.key_index, self.key_kwarg_name, key_fn),
500 _ParamSpec(self.weights_index, self.weights_kwarg_name, shard),
501 ]
503 def _apply_with_specs(self, module: Module, specs: list[_ParamSpec], cp_mesh: DeviceMesh) -> Module:
504 """Register indexer hooks driven by parameter specs."""
505 def _pre_hook(hook_module, args, kwargs):
506 new_args = list(args)
507 new_kwargs = dict(kwargs)
508 _record_query_output_layout(
509 hook_module,
510 _read_value(new_args, new_kwargs, self.query_index, self.query_kwarg_name),
511 cp_mesh,
512 self.seq_dim,
513 )
514 _apply_param_specs(new_args, new_kwargs, specs)
515 return tuple(new_args), new_kwargs
517 _register_boundary_hooks(module, _pre_hook, self.use_local_output, self.seq_dim)
518 return module
520 def apply(self, module: Module, device_mesh: DeviceMesh) -> Module:
521 """Register DSA indexer CP hooks on ``module`` and return it."""
522 cp_mesh = _ensure_1d(device_mesh)
523 specs = self._build_specs(
524 cp_mesh,
525 key_fn=lambda value: self._replicate_key_side(value, cp_mesh),
526 )
527 return self._apply_with_specs(module, specs, cp_mesh)
530class DSASparseAttentionContextParallel(ParallelStyle):
531 """Colossal-style CP hook for a DSA sparse-attention boundary.
533 This style targets a hookable module/cell whose forward signature is shaped
534 like ``(query, key, value, topk_indices, query_rope, key_rope, ...)`` and
535 rewrites only that boundary:
537 - ``query``, ``topk_indices`` and ``query_rope`` are annotated as ``Shard(seq)``;
538 - ``key``, ``value`` and ``key_rope`` are all-gathered to ``Replicate()``.
539 """
541 def __init__( # pylint: disable=too-many-arguments
542 self,
543 *,
544 layout: str = "BSND",
545 mode: str = "colossal",
546 query_index: Optional[int] = 0,
547 key_index: Optional[int] = 1,
548 value_index: Optional[int] = 2,
549 topk_index: Optional[int] = 3,
550 query_kwarg_name: Optional[str] = None,
551 key_kwarg_name: Optional[str] = None,
552 value_kwarg_name: Optional[str] = None,
553 topk_kwarg_name: Optional[str] = None,
554 query_rope_index: Optional[int] = 4,
555 key_rope_index: Optional[int] = 5,
556 query_rope_kwarg_name: Optional[str] = "query_rope",
557 key_rope_kwarg_name: Optional[str] = "key_rope",
558 use_local_output: bool = False,
559 shared_replicate_cache: Optional[DSASequenceReplicateCache] = None,
560 share_key_value: bool = False,
561 ) -> None:
562 super().__init__()
563 _configure_sparse_attention_boundary(
564 self,
565 layout=layout,
566 mode=mode,
567 query_index=query_index,
568 key_index=key_index,
569 value_index=value_index,
570 topk_index=topk_index,
571 query_kwarg_name=query_kwarg_name,
572 key_kwarg_name=key_kwarg_name,
573 value_kwarg_name=value_kwarg_name,
574 topk_kwarg_name=topk_kwarg_name,
575 query_rope_index=query_rope_index,
576 key_rope_index=key_rope_index,
577 query_rope_kwarg_name=query_rope_kwarg_name,
578 key_rope_kwarg_name=key_rope_kwarg_name,
579 use_local_output=use_local_output,
580 shared_replicate_cache=shared_replicate_cache,
581 share_key_value=share_key_value,
582 )
584 def __repr__(self) -> str:
585 return (
586 f"{self.__class__.__name__}("
587 f"layout={self.layout!r}, mode={self.mode!r}, "
588 f"use_local_output={self.use_local_output})"
589 )
591 def apply(self, module: Module, device_mesh: DeviceMesh) -> Module:
592 """Register DSA sparse-attention CP hooks on ``module`` and return it."""
593 return _apply_sparse_attention_boundary(self, module, device_mesh)
596class DSAIndexerLossContextParallel(ParallelStyle):
597 """Colossal-style CP hook for a DSA indexer-loss kernel boundary.
599 This style targets a hookable module/cell whose forward signature is shaped
600 like the sparse indexer-loss variant by default:
601 ``(query, key, query_index, key_index, weights, topk_indices,
602 softmax_max, softmax_sum, query_rope, key_rope, ...)``. Set
603 ``loss_variant="dense"`` for the dense loss signature:
604 ``(query, key, query_index, key_index, weights, softmax_max, softmax_sum,
605 softmax_max_index, softmax_sum_index, scale_value, query_rope, key_rope,
606 ...)``. The boundary is expected to start after MF has already done
607 local-only bookkeeping such as ``stop_gradient`` and ``split``.
609 Placements:
610 - ``query``, ``query_index``, ``weights``, ``topk_indices`` and
611 ``query_rope`` are annotated as ``Shard(seq)``;
612 - ``softmax_max``, ``softmax_sum`` and dense index softmax stats are
613 annotated as query-side stats sharded on their stats sequence dimension;
614 - ``key``, ``key_index`` and ``key_rope`` are all-gathered to
615 ``Replicate()``.
616 """
618 def __init__( # pylint: disable=too-many-arguments
619 self,
620 *,
621 layout: str = "BSND",
622 mode: str = "colossal",
623 loss_variant: str = "sparse",
624 query_index: Optional[int] = 0,
625 key_index: Optional[int] = 1,
626 query_indexer_index: Optional[int] = 2,
627 key_indexer_index: Optional[int] = 3,
628 weights_index: Optional[int] = 4,
629 topk_index: Optional[int] = _DEFAULT_ARG_INDEX,
630 softmax_max_index: Optional[int] = _DEFAULT_ARG_INDEX,
631 softmax_sum_index: Optional[int] = _DEFAULT_ARG_INDEX,
632 softmax_max_indexer_index: Optional[int] = _DEFAULT_ARG_INDEX,
633 softmax_sum_indexer_index: Optional[int] = _DEFAULT_ARG_INDEX,
634 query_rope_index: Optional[int] = _DEFAULT_ARG_INDEX,
635 key_rope_index: Optional[int] = _DEFAULT_ARG_INDEX,
636 query_kwarg_name: Optional[str] = None,
637 key_kwarg_name: Optional[str] = None,
638 query_indexer_kwarg_name: Optional[str] = None,
639 key_indexer_kwarg_name: Optional[str] = None,
640 weights_kwarg_name: Optional[str] = None,
641 topk_kwarg_name: Optional[str] = None,
642 softmax_max_kwarg_name: Optional[str] = None,
643 softmax_sum_kwarg_name: Optional[str] = None,
644 softmax_max_indexer_kwarg_name: Optional[str] = None,
645 softmax_sum_indexer_kwarg_name: Optional[str] = None,
646 query_rope_kwarg_name: Optional[str] = None,
647 key_rope_kwarg_name: Optional[str] = None,
648 use_local_output: bool = False,
649 shared_replicate_cache: Optional[DSASequenceReplicateCache] = None,
650 ) -> None:
651 super().__init__()
652 layout, seq_dim = _validate_layout_and_mode(self.__class__.__name__, layout, mode)
653 loss_variant = _validate_loss_variant(loss_variant)
654 is_dense = loss_variant == "dense"
655 self.layout = layout
656 self.mode = mode
657 self.loss_variant = loss_variant
658 self.seq_dim = seq_dim
659 self.stats_seq_dim = _query_stats_seq_dim(layout)
660 self.query_index = query_index
661 self.key_index = key_index
662 self.query_indexer_index = query_indexer_index
663 self.key_indexer_index = key_indexer_index
664 self.weights_index = weights_index
665 self.topk_index = _default_arg_index(topk_index, None if is_dense else 5)
666 self.softmax_max_index = _default_arg_index(softmax_max_index, 5 if is_dense else 6)
667 self.softmax_sum_index = _default_arg_index(softmax_sum_index, 6 if is_dense else 7)
668 self.softmax_max_indexer_index = _default_arg_index(softmax_max_indexer_index, 7 if is_dense else None)
669 self.softmax_sum_indexer_index = _default_arg_index(softmax_sum_indexer_index, 8 if is_dense else None)
670 self.query_rope_index = _default_arg_index(query_rope_index, 10 if is_dense else 8)
671 self.key_rope_index = _default_arg_index(key_rope_index, 11 if is_dense else 9)
672 self.query_kwarg_name = query_kwarg_name
673 self.key_kwarg_name = key_kwarg_name
674 self.query_indexer_kwarg_name = query_indexer_kwarg_name
675 self.key_indexer_kwarg_name = key_indexer_kwarg_name
676 self.weights_kwarg_name = weights_kwarg_name
677 self.topk_kwarg_name = topk_kwarg_name
678 self.softmax_max_kwarg_name = softmax_max_kwarg_name
679 self.softmax_sum_kwarg_name = softmax_sum_kwarg_name
680 self.softmax_max_indexer_kwarg_name = softmax_max_indexer_kwarg_name
681 self.softmax_sum_indexer_kwarg_name = softmax_sum_indexer_kwarg_name
682 self.query_rope_kwarg_name = query_rope_kwarg_name
683 self.key_rope_kwarg_name = key_rope_kwarg_name
684 self.use_local_output = use_local_output
685 self.shared_replicate_cache = shared_replicate_cache
687 def __repr__(self) -> str:
688 return (
689 f"{self.__class__.__name__}("
690 f"layout={self.layout!r}, mode={self.mode!r}, "
691 f"loss_variant={self.loss_variant!r}, "
692 f"use_local_output={self.use_local_output})"
693 )
695 def _shard_query_side(self, value: Any, device_mesh: DeviceMesh) -> Any:
696 return _to_sequence_shard(value, device_mesh, self.seq_dim)
698 def _replicate_key_side(self, value: Any, device_mesh: DeviceMesh) -> Any:
699 return _to_sequence_replicate(value, device_mesh, self.seq_dim)
701 @staticmethod
702 def _local_shape(value: Any) -> Optional[tuple]:
703 if isinstance(value, DTensor):
704 return value.local_shape
705 if utils.is_tensor(value):
706 return value.shape
707 return None
709 def _slice_local_key_grad(self, value: Any, module: Module) -> Any:
710 """Convert d_key_index to the original local key-index shard shape."""
711 if not self.use_local_output:
712 return value
713 target_shape = getattr(module, "_hp_dsa_loss_key_index_local_shape", None)
714 if target_shape is None:
715 return _finalize_output(value, use_local_output=True)
717 if isinstance(value, DTensor):
718 value = _dtensor_to_local_reducing_partial(value)
719 if not utils.is_tensor(value):
720 return value
722 target_len = target_shape[self.seq_dim]
723 if value.shape[self.seq_dim] == target_len:
724 return value
726 local_idx = getattr(module, "_hp_dsa_loss_local_idx", 0)
727 start = local_idx * target_len
728 return value.narrow(self.seq_dim, start, target_len)
730 def _process_outputs(self, module: Module, outputs: Any) -> Any:
731 """Finalize indexer-loss outputs, reducing Partial values when needed."""
732 output_layout = _pop_output_layout(module)
733 if not self.use_local_output:
734 return _finalize_output(outputs, False, output_layout, self.seq_dim)
735 if not isinstance(outputs, (tuple, list)) or len(outputs) < 4:
736 return _finalize_output(outputs, use_local_output=True)
738 processed = list(outputs)
739 processed[0] = _dtensor_to_local_reducing_partial(processed[0])
740 processed[1] = self._slice_local_key_grad(processed[1], module)
741 processed[2] = _dtensor_to_local_reducing_partial(processed[2])
742 processed[3] = _dtensor_to_local_reducing_partial(processed[3])
743 return type(outputs)(processed)
745 def _build_loss_specs(
746 self,
747 cp_mesh: DeviceMesh,
748 replicate_fn_map: dict[str, Callable[[Any], Any]],
749 ) -> list[_ParamSpec]:
750 """Build table-driven transforms for the indexer-loss boundary."""
751 def shard(value: Any) -> Any:
752 return self._shard_query_side(value, cp_mesh)
754 def stats_shard(value: Any) -> Any:
755 return _to_query_stats_shard(value, cp_mesh, self.stats_seq_dim)
757 return [
758 _ParamSpec(self.query_index, self.query_kwarg_name, shard),
759 _ParamSpec(self.key_index, self.key_kwarg_name, replicate_fn_map["key"]),
760 _ParamSpec(self.query_indexer_index, self.query_indexer_kwarg_name, shard),
761 _ParamSpec(self.key_indexer_index, self.key_indexer_kwarg_name, replicate_fn_map["key_indexer"]),
762 _ParamSpec(self.weights_index, self.weights_kwarg_name, shard),
763 _ParamSpec(self.topk_index, self.topk_kwarg_name, shard),
764 _ParamSpec(self.softmax_max_index, self.softmax_max_kwarg_name, stats_shard),
765 _ParamSpec(self.softmax_sum_index, self.softmax_sum_kwarg_name, stats_shard),
766 _ParamSpec(self.softmax_max_indexer_index, self.softmax_max_indexer_kwarg_name, stats_shard),
767 _ParamSpec(self.softmax_sum_indexer_index, self.softmax_sum_indexer_kwarg_name, stats_shard),
768 _ParamSpec(self.query_rope_index, self.query_rope_kwarg_name, shard),
769 _ParamSpec(self.key_rope_index, self.key_rope_kwarg_name, replicate_fn_map["key_rope"]),
770 ]
772 def _read_key_indexer_shape(self, args: list, kwargs: dict) -> Optional[tuple]:
773 """Read the original local key-indexer shape before hook transforms."""
774 if self.key_indexer_index is not None and self.key_indexer_index < len(args):
775 return self._local_shape(args[self.key_indexer_index])
776 if self.key_indexer_kwarg_name and self.key_indexer_kwarg_name in kwargs:
777 return self._local_shape(kwargs[self.key_indexer_kwarg_name])
778 return None
780 @staticmethod
781 def _get_local_idx(cp_mesh: DeviceMesh) -> int:
782 """Return current rank's index in the CP mesh rank list."""
783 rank_list = list(cp_mesh.rank_list)
784 rank = dist.get_rank()
785 return rank_list.index(rank) if rank in rank_list else 0
787 def _apply_with_loss_specs(
788 self,
789 module: Module,
790 specs: list[_ParamSpec],
791 local_idx: int,
792 cp_mesh: DeviceMesh,
793 completion_fn: Optional[Callable[[], None]] = None,
794 ) -> Module:
795 """Register indexer-loss hooks driven by parameter specs."""
796 def _pre_hook(hook_module, args, kwargs):
797 new_args = list(args)
798 new_kwargs = dict(kwargs)
799 _record_query_output_layout(
800 hook_module,
801 _read_value(new_args, new_kwargs, self.query_index, self.query_kwarg_name),
802 cp_mesh,
803 self.seq_dim,
804 )
805 key_shape = self._read_key_indexer_shape(new_args, new_kwargs)
806 setattr(hook_module, "_hp_dsa_loss_key_index_local_shape", key_shape)
807 setattr(hook_module, "_hp_dsa_loss_local_idx", local_idx)
808 try:
809 _apply_param_specs(new_args, new_kwargs, specs)
810 finally:
811 if completion_fn is not None:
812 completion_fn()
813 return tuple(new_args), new_kwargs
815 utils.register_forward_pre_hook(module, _pre_hook, with_kwargs=True)
816 module.register_forward_hook(lambda _module, _args, outputs: self._process_outputs(_module, outputs))
817 return module
819 def apply(self, module: Module, device_mesh: DeviceMesh) -> Module:
820 """Register DSA indexer-loss CP hooks on ``module`` and return it."""
821 cp_mesh = _ensure_1d(device_mesh)
823 def replicate(value: Any) -> Any:
824 """Replicate a key tensor without sharing its gradient path."""
825 return self._replicate_key_side(value, cp_mesh)
827 def shared_replicate(slot_name: str, value: Any) -> Any:
828 """Reuse a gradient-safe main-attention activation when enabled."""
829 if self.shared_replicate_cache is None:
830 return replicate(value)
831 return self.shared_replicate_cache.replicate(slot_name, value, cp_mesh, self.seq_dim)
833 specs = self._build_loss_specs(
834 cp_mesh,
835 replicate_fn_map={
836 "key": lambda value: shared_replicate("main_kv", value),
837 "key_indexer": replicate,
838 "key_rope": lambda value: shared_replicate("key_rope", value),
839 },
840 )
841 completion_fn = self.shared_replicate_cache.clear if self.shared_replicate_cache is not None else None
842 return self._apply_with_loss_specs(
843 module,
844 specs,
845 self._get_local_idx(cp_mesh),
846 cp_mesh,
847 completion_fn=completion_fn,
848 )