Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / distributed / attention_swap.py: 28%
81 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"""Attention activation swap support for Hugging Face models."""
17__all__ = [
18 "apply_attention_swap",
19 "attention_swap_policy",
20 "validate_attention_swap",
21]
23import logging
24import weakref
25from dataclasses import dataclass, field
26from typing import Optional
28import torch
29from torch import nn
31from hyper_parallel.core.activation_memory import CheckpointPolicy, SwapManager, swap_wrapper
32from hyper_parallel.core.activation_memory.swap import _teardown_wired_swap_layers
34logger = logging.getLogger(__name__)
36_MIN_SWAP_TENSOR_BYTES = 1024 * 1024
39@dataclass
40class _AttentionTarget:
41 """One attention module and every parent attribute that references it."""
43 module: nn.Module
44 references: list[tuple[nn.Module, str]] = field(default_factory=list)
47def attention_swap_policy(tensor: torch.Tensor) -> CheckpointPolicy:
48 """Select large, independently-owned attention activations for swapping.
50 Args:
51 tensor: Tensor saved by autograd inside an attention forward pass.
53 Returns:
54 ``MUST_SWAP`` for tensors that are safe and worthwhile to transfer;
55 otherwise ``MUST_SAVE``.
56 """
57 if not tensor.requires_grad or tensor.dim() < 2:
58 return CheckpointPolicy.MUST_SAVE
59 storage_bytes = tensor.untyped_storage().size()
60 tensor_bytes = tensor.numel() * tensor.element_size()
61 if storage_bytes != tensor_bytes or tensor_bytes < _MIN_SWAP_TENSOR_BYTES:
62 return CheckpointPolicy.MUST_SAVE
63 return CheckpointPolicy.MUST_SWAP
66def validate_attention_swap(
67 activation_swap: str,
68 *,
69 activation_checkpoint: Optional[str] = None,
70 enable_compile: bool = False,
71 pp_size: int = 1,
72) -> None:
73 """Validate attention swap mode and incompatible model-build features.
75 Args:
76 activation_swap: Requested activation swap mode.
77 activation_checkpoint: Activation recomputation mode.
78 enable_compile: Whether graph compilation is enabled.
79 pp_size: Configured pipeline-parallel world size.
81 Raises:
82 ValueError: If the mode is invalid or an unsupported combination is enabled.
83 """
84 if activation_swap not in ("none", "attention"):
85 raise ValueError(
86 "activation_swap must be one of ('none', 'attention'), "
87 f"got {activation_swap!r}"
88 )
89 if activation_swap == "none":
90 return
91 if enable_compile:
92 raise ValueError("activation_swap='attention' is incompatible with torch.compile")
93 if activation_checkpoint not in (None, "off"):
94 raise ValueError("activation_swap='attention' is incompatible with activation checkpointing")
95 if pp_size != 1:
96 raise ValueError("activation_swap='attention' does not support pipeline parallelism")
99def _record_attention_target(
100 targets_by_id: dict[int, _AttentionTarget],
101 parent: nn.Module,
102 child_name: str,
103 child: nn.Module,
104) -> None:
105 """Record a replaceable reference while deduplicating shared modules."""
106 target = targets_by_id.setdefault(id(child), _AttentionTarget(child))
107 target.references.append((parent, child_name))
110def _find_attention_targets(model: nn.Module) -> list[_AttentionTarget]:
111 """Find replaceable ``self_attn`` modules without assuming a layer path."""
112 targets_by_id: dict[int, _AttentionTarget] = {}
113 visited_modules: set[int] = set()
115 def visit(parent: nn.Module) -> None:
116 """Recursively record ``self_attn`` children, deduplicating shared modules.
118 Args:
119 parent: Module whose children are inspected.
120 """
121 parent_id = id(parent)
122 if parent_id in visited_modules:
123 return
124 visited_modules.add(parent_id)
126 for child_name, child in parent.named_children():
127 if child_name == "self_attn":
128 _record_attention_target(targets_by_id, parent, child_name, child)
129 continue
130 visit(child)
132 visit(model)
133 targets = list(targets_by_id.values())
134 if not targets:
135 raise ValueError(
136 f"activation_swap='attention' found no self_attn modules in {type(model).__name__}; "
137 "expected attention modules registered as self_attn"
138 )
139 return targets
142def apply_attention_swap(model: nn.Module, activation_swap: str) -> nn.Module:
143 """Wrap attention modules and install model-order swap scheduling.
145 Args:
146 model: Model being prepared, before sharding and FSDP wrapping.
147 activation_swap: Requested activation swap mode.
149 Returns:
150 The input model, patched in place when attention swapping is enabled.
151 """
152 if activation_swap != "attention":
153 raise ValueError(
154 "activation_swap must be one of ('none', 'attention'), "
155 f"got {activation_swap!r}"
156 )
157 attention_targets = _find_attention_targets(model)
158 wrapped_attentions: list[nn.Module] = []
159 saved_refs: list[tuple[nn.Module, str, nn.Module]] = []
160 try:
161 for target in attention_targets:
162 attention = target.module
163 wrapped_attention = swap_wrapper(
164 attention,
165 policy_fn=attention_swap_policy,
166 group_swap=True,
167 )
168 for parent, child_name in target.references:
169 saved_refs.append((parent, child_name, getattr(parent, child_name)))
170 setattr(parent, child_name, wrapped_attention)
171 wrapped_attentions.append(wrapped_attention)
173 swap_manager = SwapManager()
174 for current_attention, next_attention in zip(wrapped_attentions, wrapped_attentions[1:]):
175 swap_manager.set_forward_prefetch_layer(current_attention, next_attention)
176 if wrapped_attentions:
177 weakref.finalize(
178 model,
179 _teardown_wired_swap_layers,
180 wrapped_attentions,
181 )
182 except Exception:
183 # Partial failure: restore the original attention modules on every
184 # reference already re-pointed, and release any swap wiring registered
185 # by the chain loop before it raised (idempotent when none was).
186 _teardown_wired_swap_layers(wrapped_attentions)
187 for parent, child_name, original in saved_refs:
188 setattr(parent, child_name, original)
189 raise
191 logger.info(
192 "Enabled attention activation swap for %d module(s) in %s",
193 len(wrapped_attentions),
194 type(model).__name__,
195 )
196 return model