Skip to content

Commit 1c63968

Browse files
authored
[0.5/4] Add streamwise wide-residual connection modules (#7599)
Signed-off-by: Kwangjun Ahn <kwangjuna@nvidia.com>
1 parent 7de072a commit 1c63968

10 files changed

Lines changed: 703 additions & 7 deletions

File tree

‎megatron/core/optimizer/__init__.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,23 @@ def get_standard_config_overrides(config: OptimizerConfig) -> Dict[ParamKey, Par
101101
Dict[ParamKey, ParamGroupOverride]: standard config overrides.
102102
"""
103103
config_overrides: Optional[Dict[ParamKey, ParamGroupOverride]] = {}
104-
# First, figure out how we are going to do wd skipping. The two main approaches are:
105-
# 1. The classic megatron approach of skipping all len 1 and bias parameters.
106-
# 2. The Qwen3-Next approach of doing 1, other than qk layernorm parameters.
104+
# Select the model-family convention for zero weight decay on vector-like parameters:
105+
# the classic rule skips all 1-D parameters and biases, while the Qwen3-Next rule keeps
106+
# weight decay on Q/K layernorm parameters. Wide-residual retention controllers intentionally
107+
# follow the run's ordinary weight-decay policy rather than this generic vector exemption.
107108
if config.apply_wd_to_qk_layernorm:
108109
shape_1_not_qkln_param = ParamWithNamePredicate(
109110
name="s1_not_qkln",
110111
fn=lambda param, name: (len(param.shape) == 1 or name.endswith(".bias"))
111-
and not ("q_layernorm." in name or "k_layernorm." in name),
112+
and not ("q_layernorm." in name or "k_layernorm." in name)
113+
and not getattr(param, "is_wide_residual_retention_parameter", False),
112114
)
113115
param_wd_mult_key = ParamKey(with_name_predicate=shape_1_not_qkln_param)
114116
else:
115117
param_length_1_match = ParamPredicate(
116-
name="param_len_1", fn=lambda param: len(param.shape) == 1
118+
name="param_len_1_except_wide_residual_retention",
119+
fn=lambda param: len(param.shape) == 1
120+
and not getattr(param, "is_wide_residual_retention_parameter", False),
117121
)
118122
param_wd_mult_key = ParamKey(name="*.bias", predicate=param_length_1_match)
119123

‎megatron/core/transformer/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
TransformerLayer,
99
TransformerLayerSubmodules,
1010
)
11+
from .wide_residual_config import WideResidualConfig

‎megatron/core/transformer/transformer_config.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
LayerType,
3333
)
3434
from megatron.core.transformer.pipeline_parallel_layer_layout import PipelineParallelLayerLayout
35+
from megatron.core.transformer.wide_residual_config import WideResidualConfig
3536

3637
from .._rank_utils import log_single_rank
3738
from ..fusions.fused_bias_geglu import quick_gelu
@@ -1290,6 +1291,16 @@ class TransformerConfig(ModelParallelConfig):
12901291
12911292
Must be a positive integer when set."""
12921293

1294+
####################
1295+
# Wide Residual Configuration
1296+
####################
1297+
wide_residual: Optional[WideResidualConfig] = None
1298+
"""Optional streamwise wide-residual architecture configuration.
1299+
1300+
When set, the model carries ``num_streams * hidden_size`` features between
1301+
layers while attention and MLP branches continue to operate at ``hidden_size``.
1302+
"""
1303+
12931304
####################
12941305
# miscellaneous
12951306
####################
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
3+
from dataclasses import dataclass
4+
5+
6+
@dataclass
7+
class WideResidualConfig:
8+
"""Configuration for streamwise wide residuals around ordinary-width branches.
9+
10+
The model carries ``num_streams`` contiguous residual streams, each with
11+
``TransformerConfig.hidden_size`` features. Attention, MLP, and MoE branches
12+
continue to operate at the ordinary hidden size.
13+
"""
14+
15+
num_streams: int
16+
"""Number of ordinary-width streams carried by the model; must be greater than one."""
17+
18+
streamwise_sigmoid_init_scale: float = 0.01
19+
"""Symmetric initialization spread for streamwise write logits."""
20+
21+
learned_retention: bool = False
22+
"""Apply one bounded learned carry factor to each residual stream."""
23+
24+
retention_init: float = 0.999
25+
"""Initial retention factor; values near one preserve the initial function."""
26+
27+
retention_max_forget: float = 0.10
28+
"""Maximum forget rate in ``1 - max_forget * sigmoid(-logit)``."""
29+
30+
def __post_init__(self) -> None:
31+
if isinstance(self.num_streams, bool) or not isinstance(self.num_streams, int):
32+
raise TypeError("wide residual num_streams must be an integer.")
33+
if self.num_streams <= 1:
34+
raise ValueError(
35+
f"wide residual num_streams must be greater than one, got {self.num_streams}."
36+
)
37+
if self.streamwise_sigmoid_init_scale < 0.0:
38+
raise ValueError(
39+
"streamwise_sigmoid_init_scale must be non-negative, got "
40+
f"{self.streamwise_sigmoid_init_scale}."
41+
)
42+
if self.learned_retention:
43+
if not 0.0 < self.retention_max_forget < 1.0:
44+
raise ValueError(
45+
f"retention_max_forget must be in (0, 1), got {self.retention_max_forget}."
46+
)
47+
if not 1.0 - self.retention_max_forget < self.retention_init < 1.0:
48+
raise ValueError(
49+
"retention_init must satisfy 1 - retention_max_forget < "
50+
f"retention_init < 1, got {self.retention_init}."
51+
)
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Copyright (c) 2025-2026, NVIDIA CORPORATION. All rights reserved.
2+
"""Construction-time wide-residual connections and model-boundary helpers."""
3+
4+
from __future__ import annotations
5+
6+
import math
7+
from typing import Literal
8+
9+
import torch
10+
import torch.nn.functional as F
11+
from torch import Tensor, nn
12+
13+
from megatron.core.process_groups_config import ProcessGroupCollection
14+
from megatron.core.transformer.residual_connection import (
15+
ResidualBranchOutput,
16+
ResidualConnection,
17+
ResidualConnectionState,
18+
ResidualConnectionWriteState,
19+
)
20+
from megatron.core.transformer.streamwise_residual_ops import (
21+
streamwise_sigmoid_read,
22+
streamwise_sigmoid_writeback,
23+
)
24+
from megatron.core.transformer.transformer_config import TransformerConfig
25+
26+
_MIN_CONTROLLER_NUMEL = 128
27+
28+
29+
def _make_padded_vector(values: Tensor) -> nn.Parameter:
30+
"""Create a parameter vector padded for small distributed-optimizer shards."""
31+
32+
if values.ndim != 1:
33+
raise ValueError(f"Expected one-dimensional initial values, got {values.shape}.")
34+
if values.numel() >= _MIN_CONTROLLER_NUMEL:
35+
return nn.Parameter(values.clone())
36+
padded = torch.zeros(_MIN_CONTROLLER_NUMEL, dtype=values.dtype)
37+
padded[: values.numel()] = values
38+
return nn.Parameter(padded)
39+
40+
41+
def _active_vector(param: Tensor, length: int) -> Tensor:
42+
"""Return the active prefix of a potentially padded vector parameter."""
43+
44+
return param[:length]
45+
46+
47+
def _mark_residual_map_parameter(param: nn.Parameter, config: TransformerConfig) -> None:
48+
"""Attach optimizer and TP-gradient metadata to a replicated residual map."""
49+
50+
setattr(param, "is_residual_map_parameter", True)
51+
setattr(param, "use_muon", False)
52+
setattr(param, "sequence_parallel", config.sequence_parallel)
53+
54+
55+
def _mark_replicated_tp_parameter(param: nn.Parameter, config: TransformerConfig) -> None:
56+
"""Mark a replicated controller for the correct TP gradient reduction."""
57+
58+
setattr(param, "allreduce", True)
59+
setattr(param, "tensor_model_parallel", False)
60+
setattr(param, "average_gradients_across_tp_domain", not config.sequence_parallel)
61+
62+
63+
def _mark_retention_parameter(param: nn.Parameter, config: TransformerConfig) -> None:
64+
"""Attach optimizer and TP-gradient metadata to a replicated retention parameter."""
65+
66+
setattr(param, "is_wide_residual_retention_parameter", True)
67+
setattr(param, "sequence_parallel", config.sequence_parallel)
68+
_mark_replicated_tp_parameter(param, config)
69+
70+
71+
class LearnedWideResidualRetention(nn.Module):
72+
"""Bounded learned retention with one controller per full-width stream."""
73+
74+
def __init__(
75+
self, config: TransformerConfig, layer_number: int, branch_name: str, *, num_streams: int
76+
) -> None:
77+
super().__init__()
78+
if config.wide_residual is None:
79+
raise ValueError("LearnedWideResidualRetention requires wide_residual config.")
80+
wr = config.wide_residual
81+
if num_streams != wr.num_streams:
82+
raise ValueError(
83+
"Retention must define one controller per full-width stream, got "
84+
f"{num_streams} controllers for {wr.num_streams} streams."
85+
)
86+
self.layer_number = layer_number
87+
self.branch_name = branch_name
88+
self.num_streams = num_streams
89+
self.max_forget = wr.retention_max_forget
90+
forget_ratio = (1.0 - wr.retention_init) / self.max_forget
91+
initial_logit = math.log((1.0 - forget_ratio) / forget_ratio)
92+
self.retention_logit = _make_padded_vector(
93+
torch.full((num_streams,), initial_logit, dtype=torch.float32)
94+
)
95+
_mark_retention_parameter(self.retention_logit, config)
96+
97+
def factors(self) -> Tensor:
98+
"""Return one FP32 retention factor for every full-width stream."""
99+
100+
logits = _active_vector(self.retention_logit, self.num_streams).float()
101+
return 1.0 - self.max_forget * torch.sigmoid(-logits)
102+
103+
def forward(self, *, return_logits: bool = False) -> Tensor:
104+
"""Return padded logits or materialized retention factors."""
105+
106+
return self.retention_logit if return_logits else self.factors()
107+
108+
109+
class StreamwiseSigmoidMap(nn.Module):
110+
"""Positive scalar controller for each contiguous full-width stream."""
111+
112+
def __init__(self, config: TransformerConfig, *, map_kind: Literal["read", "write"]) -> None:
113+
super().__init__()
114+
if config.wide_residual is None:
115+
raise ValueError("StreamwiseSigmoidMap requires wide_residual config.")
116+
wr = config.wide_residual
117+
118+
self.map_kind = map_kind
119+
self.num_streams = wr.num_streams
120+
121+
if map_kind == "read":
122+
initial_factor = 1.0 / self.num_streams
123+
initial_logit = math.log(initial_factor / (1.0 - initial_factor))
124+
initial_logits = torch.full((self.num_streams,), initial_logit, dtype=torch.float32)
125+
elif map_kind == "write":
126+
initial_logits = torch.linspace(
127+
-wr.streamwise_sigmoid_init_scale,
128+
wr.streamwise_sigmoid_init_scale,
129+
self.num_streams,
130+
dtype=torch.float32,
131+
)
132+
else:
133+
raise ValueError(f"Unsupported streamwise sigmoid map kind: {map_kind!r}.")
134+
135+
self.logit = _make_padded_vector(initial_logits)
136+
_mark_residual_map_parameter(self.logit, config)
137+
_mark_replicated_tp_parameter(self.logit, config)
138+
139+
def factors(self) -> Tensor:
140+
"""Return one positive factor for every full-width residual stream."""
141+
142+
factors = torch.sigmoid(_active_vector(self.logit, self.num_streams).float())
143+
if self.map_kind == "write":
144+
factors = 2.0 * factors
145+
return factors
146+
147+
def forward(self, *, return_logits: bool = False) -> Tensor:
148+
"""Return padded logits or one positive factor per full-width stream."""
149+
150+
return self.logit if return_logits else self.factors()
151+
152+
153+
class StreamwiseSigmoidWideResidualConnection(ResidualConnection):
154+
"""Positive streamwise maps around one ordinary-width residual branch."""
155+
156+
def __init__(
157+
self,
158+
config: TransformerConfig,
159+
layer_number: int,
160+
branch_name: str,
161+
pg_collection: ProcessGroupCollection,
162+
name: str | None = None,
163+
) -> None:
164+
del pg_collection, name
165+
if config.wide_residual is None:
166+
raise ValueError(
167+
"StreamwiseSigmoidWideResidualConnection requires wide_residual config."
168+
)
169+
wr = config.wide_residual
170+
super().__init__(
171+
residual_stream_hidden_size=wr.num_streams * config.hidden_size,
172+
branch_hidden_size=config.hidden_size,
173+
)
174+
self.layer_number = layer_number
175+
self.branch_name = branch_name
176+
self.num_streams = wr.num_streams
177+
self.read_map = StreamwiseSigmoidMap(config, map_kind="read")
178+
self.write_map = StreamwiseSigmoidMap(config, map_kind="write")
179+
self.retention = (
180+
LearnedWideResidualRetention(
181+
config, layer_number, branch_name, num_streams=self.num_streams
182+
)
183+
if wr.learned_retention
184+
else None
185+
)
186+
187+
def _read(self, hidden_states: Tensor) -> tuple[Tensor, ResidualConnectionWriteState]:
188+
return (
189+
streamwise_sigmoid_read(
190+
hidden_states, self.read_map(return_logits=True), self.num_streams
191+
),
192+
(),
193+
)
194+
195+
def _write(
196+
self,
197+
branch_output: ResidualBranchOutput,
198+
state: ResidualConnectionState,
199+
*,
200+
dropout_probability: float,
201+
training: bool,
202+
) -> Tensor:
203+
residual_stream = state[0]
204+
if isinstance(branch_output, tuple):
205+
branch_update, bias = branch_output
206+
else:
207+
branch_update, bias = branch_output, None
208+
209+
branch_update = branch_update.to(dtype=residual_stream.dtype)
210+
if bias is not None:
211+
branch_update = branch_update + bias.to(
212+
device=branch_update.device, dtype=branch_update.dtype
213+
)
214+
branch_update = F.dropout(branch_update, p=dropout_probability, training=training)
215+
retention_logits = (
216+
self.retention(return_logits=True) if self.retention is not None else None
217+
)
218+
return streamwise_sigmoid_writeback(
219+
residual_stream,
220+
branch_update,
221+
self.write_map(return_logits=True),
222+
self.num_streams,
223+
retention_logits=retention_logits,
224+
retention_max_forget=(self.retention.max_forget if self.retention is not None else 0.0),
225+
)
226+
227+
228+
class StreamwiseSigmoidResidualReadout(nn.Module):
229+
"""Learned streamwise readout from D' to D before final normalization."""
230+
231+
def __init__(self, config: TransformerConfig) -> None:
232+
super().__init__()
233+
if config.wide_residual is None:
234+
raise ValueError("StreamwiseSigmoidResidualReadout requires wide_residual config.")
235+
wr = config.wide_residual
236+
self.input_hidden_size = wr.num_streams * config.hidden_size
237+
self.output_hidden_size = config.hidden_size
238+
self.exit_map = StreamwiseSigmoidMap(config, map_kind="read")
239+
240+
def forward(self, hidden_states: Tensor) -> Tensor:
241+
"""Mix the full-width streams into one backbone-width activation."""
242+
243+
if hidden_states.shape[-1] != self.input_hidden_size:
244+
raise ValueError(
245+
"Streamwise sigmoid readout expected hidden size "
246+
f"{self.input_hidden_size}, got {hidden_states.shape[-1]}."
247+
)
248+
return streamwise_sigmoid_read(
249+
hidden_states, self.exit_map(return_logits=True), self.exit_map.num_streams
250+
)
251+
252+
253+
def build_wide_residual_readout(
254+
config: TransformerConfig,
255+
) -> StreamwiseSigmoidResidualReadout | None:
256+
"""Build the streamwise model-boundary readout when wide residuals are enabled."""
257+
258+
if config.wide_residual is None:
259+
return None
260+
return StreamwiseSigmoidResidualReadout(config)
261+
262+
263+
def expand_wide_residual_stream(hidden_states: Tensor, num_streams: int) -> Tensor:
264+
"""Replicate an ordinary-width activation into contiguous residual streams."""
265+
266+
if num_streams <= 1:
267+
raise ValueError(f"num_streams must be greater than one, got {num_streams}.")
268+
repeat_shape = [1] * hidden_states.ndim
269+
repeat_shape[-1] = num_streams
270+
return hidden_states.repeat(*repeat_shape)

0 commit comments

Comments
 (0)