Skip to content

Commit d7924d3

Browse files
wujingyueyaox12
andauthored
[Refactor] Select wide-residual operation at construction (#7952)
Signed-off-by: Jingyue Wu <jingyuew@nvidia.com> Co-authored-by: Xin Yao <xiny@nvidia.com>
1 parent ee6baa2 commit d7924d3

18 files changed

Lines changed: 458 additions & 346 deletions

‎megatron/core/models/hybrid/shortcut_block.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,14 @@ def __init__(
193193
layer_number=moe_layer.layer_number,
194194
branch_name="shortcut_routed",
195195
)
196-
outer_connection = moe_layer._get_mlp_residual_connection()
197-
if outer_connection is None:
196+
outer_read = moe_layer._get_mlp_residual_connection(operation="read")
197+
if outer_read is None:
198198
raise ValueError(
199199
"A wide-residual shortcut read requires an outer MoE residual connection."
200200
)
201201
if (
202202
self.shortcut_residual_read.residual_stream_hidden_size
203-
!= outer_connection.residual_stream_hidden_size
203+
!= outer_read.residual_stream_hidden_size
204204
):
205205
raise ValueError(
206206
"The shortcut read and outer MoE residual connection must consume the "

‎megatron/core/ssm/wide_residual_mamba_layer.py‎

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
checkpoint_residual_write,
1414
)
1515
from megatron.core.transformer.transformer_config import TransformerConfig
16-
from megatron.core.transformer.wide_residual_layer import StreamwiseSigmoidWideResidualConnection
16+
from megatron.core.transformer.wide_residual_layer import (
17+
StreamwiseSigmoidWideResidualConnection,
18+
load_residual_state,
19+
)
1720
from megatron.core.typed_torch import apply_module
1821

1922

@@ -55,33 +58,33 @@ def __init__(
5558
"returns its own residual."
5659
)
5760

58-
self.residual_connection = StreamwiseSigmoidWideResidualConnection(
59-
config=self.config,
60-
layer_number=self.layer_number,
61-
branch_name="mamba",
62-
pg_collection=pg_collection,
63-
name=(name + ".residual_connection") if name is not None else None,
61+
self.residual_read = StreamwiseSigmoidWideResidualConnection(
62+
config, self.layer_number, "mamba", operation="read"
63+
)
64+
self.residual_write = StreamwiseSigmoidWideResidualConnection(
65+
config, self.layer_number, "mamba", operation="write"
6466
)
67+
self.register_load_state_dict_pre_hook(load_residual_state)
6568
self.residual_stream_hidden_size = (
6669
config.wide_residual.num_streams * self.config.hidden_size
6770
)
68-
if self.residual_connection.residual_stream_hidden_size != self.residual_stream_hidden_size:
71+
if self.residual_read.residual_stream_hidden_size != self.residual_stream_hidden_size:
6972
raise ValueError(
7073
"The wide-residual Mamba connection must carry num_streams * hidden_size "
7174
f"features, expected {self.residual_stream_hidden_size}, got "
72-
f"{self.residual_connection.residual_stream_hidden_size}."
75+
f"{self.residual_read.residual_stream_hidden_size}."
7376
)
74-
if self.residual_connection.branch_hidden_size != self.config.hidden_size:
77+
if self.residual_read.branch_hidden_size != self.config.hidden_size:
7578
raise ValueError(
7679
"The wide-residual Mamba connection must produce "
7780
f"hidden_size={self.config.hidden_size}, got "
78-
f"{self.residual_connection.branch_hidden_size}."
81+
f"{self.residual_read.branch_hidden_size}."
7982
)
8083

8184
def _get_residual_connection(self):
8285
"""Return the connection surrounding the Mamba mixer."""
8386

84-
return self.residual_connection
87+
return self.residual_read, self.residual_write
8588

8689
def _prepare_mixer_state(
8790
self,
@@ -92,21 +95,20 @@ def _prepare_mixer_state(
9295

9396
recompute_context = residual_stream_recompute_context
9497
if recompute_context is None:
95-
hidden_states, connection_state = apply_module(self.residual_connection)(
98+
hidden_states, connection_state = apply_module(self.residual_read)(
9699
hidden_states,
97-
operation="read",
98100
fp32_residual_connection=self.config.fp32_residual_connection,
99101
branch_input_dtype=self.config.params_dtype,
100102
)
101103
else:
102104
hidden_states, connection_state = checkpoint_residual_read(
103-
self.residual_connection,
105+
self.residual_read,
104106
hidden_states,
105107
recompute_context,
106108
fp32_residual_connection=self.config.fp32_residual_connection,
107109
branch_input_dtype=self.config.params_dtype,
108110
)
109-
residual = self.residual_connection.residual_stream(connection_state)
111+
residual = self.residual_read.residual_stream(connection_state)
110112

111113
hidden_states = hidden_states.to(dtype=self.config.params_dtype)
112114
if recompute_context is not None and not isinstance(self.norm, IdentityOp):
@@ -128,17 +130,16 @@ def _apply_mixer_update(
128130
raise RuntimeError("Missing state for the Mamba residual connection.")
129131
if recompute_context is not None and not recompute_context.is_block_end:
130132
return checkpoint_residual_write(
131-
self.residual_connection,
133+
self.residual_write,
132134
mixer_out_with_bias,
133135
connection_state,
134136
recompute_context,
135137
dropout_probability=self.hidden_dropout,
136138
training=self.training,
137139
)
138140
with self.bias_dropout_add_exec_handler():
139-
return apply_module(self.residual_connection)(
141+
return apply_module(self.residual_write)(
140142
mixer_out_with_bias,
141-
operation="write",
142143
state=connection_state,
143144
dropout_probability=self.hidden_dropout,
144145
training=self.training,

‎megatron/core/transformer/residual_connection.py‎

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,21 @@ class ResidualConnection(nn.Module, ABC):
2121
``forward`` validates the common contract and retains the incoming residual
2222
stream as the first tensor in ``ResidualConnectionState``. Concrete connections
2323
own bias, dropout, mapping, and residual-update semantics.
24+
25+
``operation`` fixes an instance to one operation at construction.
2426
"""
2527

26-
def __init__(self, residual_stream_hidden_size: int, branch_hidden_size: int):
28+
def __init__(
29+
self,
30+
residual_stream_hidden_size: int,
31+
branch_hidden_size: int,
32+
*,
33+
operation: ResidualConnectionOperation,
34+
):
2735
super().__init__()
36+
if operation not in ("read", "write"):
37+
raise ValueError(f"Unsupported residual connection operation: {operation}.")
38+
self.operation = operation
2839
if residual_stream_hidden_size <= 0:
2940
raise ValueError("Residual-stream hidden size must be positive.")
3041
if branch_hidden_size <= 0:
@@ -49,7 +60,6 @@ def forward(
4960
self,
5061
value: Tensor,
5162
*,
52-
operation: Literal["read"],
5363
fp32_residual_connection: bool = False,
5464
branch_input_dtype: torch.dtype | None = None,
5565
) -> tuple[Tensor, ResidualConnectionState]: ...
@@ -59,7 +69,6 @@ def forward(
5969
self,
6070
value: ResidualBranchOutput,
6171
*,
62-
operation: Literal["write"],
6372
state: ResidualConnectionState,
6473
dropout_probability: float,
6574
training: bool,
@@ -69,7 +78,6 @@ def forward(
6978
self,
7079
value: ResidualBranchOutput,
7180
*,
72-
operation: ResidualConnectionOperation,
7381
state: ResidualConnectionState | None = None,
7482
fp32_residual_connection: bool = False,
7583
branch_input_dtype: torch.dtype | None = None,
@@ -82,37 +90,39 @@ def forward(
8290
dtype; otherwise compatible connections may produce that dtype directly in a fused read.
8391
"""
8492

85-
if operation == "read":
86-
if not torch.is_tensor(value):
87-
raise TypeError("Residual connection read expects a tensor.")
88-
if state is not None or dropout_probability is not None or training is not None:
89-
raise TypeError("Residual connection read received write-only arguments.")
90-
if branch_input_dtype is not None and (
91-
not isinstance(branch_input_dtype, torch.dtype)
92-
or not branch_input_dtype.is_floating_point
93-
):
94-
raise TypeError(
95-
"branch_input_dtype must be a floating-point torch.dtype or None, got "
96-
f"{branch_input_dtype!r}."
93+
match self.operation:
94+
case "read":
95+
if not torch.is_tensor(value):
96+
raise TypeError("Residual connection read expects a tensor.")
97+
if state is not None or dropout_probability is not None or training is not None:
98+
raise TypeError("Residual connection read received write-only arguments.")
99+
if branch_input_dtype is not None and (
100+
not isinstance(branch_input_dtype, torch.dtype)
101+
or not branch_input_dtype.is_floating_point
102+
):
103+
raise TypeError(
104+
"branch_input_dtype must be a floating-point torch.dtype or None, got "
105+
f"{branch_input_dtype!r}."
106+
)
107+
return self._read_with_validation(
108+
value,
109+
fp32_residual_connection=fp32_residual_connection,
110+
branch_input_dtype=branch_input_dtype,
97111
)
98-
return self._read_with_validation(
99-
value,
100-
fp32_residual_connection=fp32_residual_connection,
101-
branch_input_dtype=branch_input_dtype,
102-
)
103-
if operation == "write":
104-
if fp32_residual_connection or branch_input_dtype is not None:
105-
raise TypeError("Residual connection write received read-only arguments.")
106-
if state is None:
107-
raise TypeError("Residual connection write requires connection state.")
108-
if dropout_probability is None or training is None:
109-
raise TypeError(
110-
"Residual connection write requires dropout_probability and training."
112+
case "write":
113+
if fp32_residual_connection or branch_input_dtype is not None:
114+
raise TypeError("Residual connection write received read-only arguments.")
115+
if state is None:
116+
raise TypeError("Residual connection write requires connection state.")
117+
if dropout_probability is None or training is None:
118+
raise TypeError(
119+
"Residual connection write requires dropout_probability and training."
120+
)
121+
return self._write_with_validation(
122+
value, state, dropout_probability=dropout_probability, training=training
111123
)
112-
return self._write_with_validation(
113-
value, state, dropout_probability=dropout_probability, training=training
114-
)
115-
raise ValueError(f"Unsupported residual connection operation: {operation}.")
124+
case _:
125+
raise ValueError(f"Unsupported residual connection operation: {self.operation}.")
116126

117127
def _read_with_validation(
118128
self,

‎megatron/core/transformer/residual_recompute.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,7 @@ def checkpoint_residual_read(
119119

120120
def run_read(stream: Tensor) -> tuple[Tensor, ...]:
121121
branch_input, state = apply_module(connection)(
122-
stream,
123-
operation="read",
124-
fp32_residual_connection=False,
125-
branch_input_dtype=branch_input_dtype,
122+
stream, fp32_residual_connection=False, branch_input_dtype=branch_input_dtype
126123
)
127124
if state[0].shape != stream.shape:
128125
raise ValueError("Residual connection read returned an incompatible carried stream.")
@@ -167,7 +164,6 @@ def run_write(
167164
value = (branch_update, branch_bias) if return_tuple else branch_update
168165
return apply_module(connection)(
169166
value,
170-
operation="write",
171167
state=connection_state,
172168
dropout_probability=dropout_probability,
173169
training=training,

‎megatron/core/transformer/transformer_layer.py‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
from megatron.core.transformer.identity_op import IdentityFuncOp, IdentityOp
3434
from megatron.core.transformer.mlp import MLP
3535
from megatron.core.transformer.module import GraphableMegatronModule, TwoStageAttentionLayer
36+
from megatron.core.transformer.residual_connection import (
37+
ResidualConnection,
38+
ResidualConnectionOperation,
39+
)
3640
from megatron.core.transformer.residual_recompute import (
3741
ResidualStreamRecomputeContext,
3842
checkpoint_residual_read,
@@ -918,13 +922,17 @@ def forward_post_core_attn(
918922
residual_stream_recompute_context=residual_stream_recompute_context,
919923
)
920924

921-
def _get_self_attention_residual_connection(self):
922-
"""Return an optional architecture-owned self-attention connection."""
925+
def _get_self_attention_residual_connection(
926+
self, operation: ResidualConnectionOperation
927+
) -> ResidualConnection | None:
928+
"""Return the optional architecture-owned self-attention reader or writer."""
923929

924930
return None
925931

926-
def _get_mlp_residual_connection(self):
927-
"""Return an optional architecture-owned MLP connection."""
932+
def _get_mlp_residual_connection(
933+
self, operation: ResidualConnectionOperation
934+
) -> ResidualConnection | None:
935+
"""Return the optional architecture-owned MLP reader or writer."""
928936

929937
return None
930938

@@ -948,7 +956,7 @@ def _run_input_layernorm(
948956
``_apply_self_attn_bda_step``. A configured residual connection uses
949957
it for the corresponding write state; otherwise base returns ``()``.
950958
"""
951-
residual_connection = self._get_self_attention_residual_connection()
959+
residual_connection = self._get_self_attention_residual_connection(operation="read")
952960
recompute_context = (
953961
residual_stream_recompute_context if residual_connection is not None else None
954962
)
@@ -957,7 +965,6 @@ def _run_input_layernorm(
957965
if recompute_context is None:
958966
hidden_states, connection_state = apply_module(residual_connection)(
959967
hidden_states,
960-
operation="read",
961968
fp32_residual_connection=self.config.fp32_residual_connection,
962969
branch_input_dtype=self.config.params_dtype,
963970
)
@@ -1033,7 +1040,7 @@ def _apply_self_attn_bda_step(
10331040
using_fused_tp_inference_kernel = (
10341041
InferenceMode.is_active() and self.config.inference_fuse_tp_communication
10351042
)
1036-
residual_connection = self._get_self_attention_residual_connection()
1043+
residual_connection = self._get_self_attention_residual_connection(operation="write")
10371044
recompute_context = (
10381045
residual_stream_recompute_context if residual_connection is not None else None
10391046
)
@@ -1051,7 +1058,7 @@ def _apply_self_attn_bda_step(
10511058
is_terminal_write = bool(
10521059
recompute_context is not None
10531060
and recompute_context.is_block_end
1054-
and self._get_mlp_residual_connection() is None
1061+
and self._get_mlp_residual_connection(operation="write") is None
10551062
)
10561063
if recompute_context is not None and not is_terminal_write:
10571064
hidden_states = checkpoint_residual_write(
@@ -1066,7 +1073,6 @@ def _apply_self_attn_bda_step(
10661073
with self.bias_dropout_add_exec_handler():
10671074
hidden_states = apply_module(residual_connection)(
10681075
attention_output_with_bias,
1069-
operation="write",
10701076
state=attn_state,
10711077
dropout_probability=self.hidden_dropout,
10721078
training=self.training,
@@ -1254,7 +1260,7 @@ def _pre_mlp_layernorm_and_residual(
12541260
through to ``_apply_mlp_bda_step``. A configured residual connection
12551261
uses it for the corresponding write state; otherwise base returns ``()``.
12561262
"""
1257-
residual_connection = self._get_mlp_residual_connection()
1263+
residual_connection = self._get_mlp_residual_connection(operation="read")
12581264
recompute_context = (
12591265
residual_stream_recompute_context if residual_connection is not None else None
12601266
)
@@ -1263,7 +1269,6 @@ def _pre_mlp_layernorm_and_residual(
12631269
if recompute_context is None:
12641270
hidden_states, connection_state = apply_module(residual_connection)(
12651271
hidden_states,
1266-
operation="read",
12671272
fp32_residual_connection=self.config.fp32_residual_connection,
12681273
branch_input_dtype=self.config.params_dtype,
12691274
)
@@ -1578,7 +1583,7 @@ def _apply_mlp_bda_step(
15781583
InferenceMode.is_active() and self.config.inference_fuse_tp_communication
15791584
)
15801585

1581-
residual_connection = self._get_mlp_residual_connection()
1586+
residual_connection = self._get_mlp_residual_connection(operation="write")
15821587
recompute_context = (
15831588
residual_stream_recompute_context if residual_connection is not None else None
15841589
)
@@ -1613,7 +1618,6 @@ def _apply_mlp_bda_step(
16131618
with self.bias_dropout_add_exec_handler():
16141619
hidden_states = apply_module(residual_connection)(
16151620
mlp_output_with_bias,
1616-
operation="write",
16171621
state=mlp_state,
16181622
dropout_probability=self.hidden_dropout,
16191623
training=self.training,

0 commit comments

Comments
 (0)