Skip to content

Commit 55b158f

Browse files
improved additional layer filtering
1 parent a0a0212 commit 55b158f

3 files changed

Lines changed: 66 additions & 19 deletions

File tree

docs/utils.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,40 @@ title: Utilities
88

99
`param_groups_weight_decay` is adapted from [timm's optimizer factory methods](https://huggingface.co/docs/timm/reference/optimizers#timm.optim.create_optimizer).
1010

11-
### Example
11+
### Examples
1212

1313
`param_groups_weight_decay` takes a model and returns two optimizer parameter group dictionaries. One with bias and normalization terms without weight decay and another dictionary with the rest of the model parameters with weight decay. The `weight_decay` passed to `param_groups_weight_decay` will override the optimizer's default weight decay.
1414

1515
```python
16-
params = param_groups_weight_decay(model, weigh_decay=1e-5)
16+
params = param_groups_weight_decay(model, weight_decay=1e-5)
1717
optimizer = StableAdamW(params, decouple_lr=True)
1818

1919
```
2020

21+
`additional_layers` parameter allows you to specify additional layer names or name substrings that should be excluded from weight decay. This is useful for excluding specific layers like token embeddings which also benefit from not having weight decay applied.
22+
23+
The parameter accepts an iterable of strings, where each string is matched as a substring against the full parameter name (as returned by `model.named_parameters()`).
24+
25+
```python
26+
class MiniLM(nn.Module):
27+
def __init__(self):
28+
super().__init__()
29+
self.tok_embeddings = nn.Embedding(1000, 20)
30+
self.pos_embeddings = nn.Embedding(100, 20)
31+
self.norm = nn.LayerNorm(20)
32+
self.layer1 = nn.Linear(20, 30)
33+
self.layer2 = nn.Linear(30, 1000)
34+
35+
model = MiniLM()
36+
37+
# Exclude token embeddings from weight decay in addition to bias and normalization layers
38+
params = param_groups_weight_decay(
39+
model,
40+
weight_decay=1e-5,
41+
additional_layers=["tok_embeddings"]
42+
)
43+
```
44+
2145
::: optimi.gradientrelease.prepare_for_gradient_release
2246

2347
For details on using `prepare_for_gradient_release`, please see the [gradient release docs](gradient_release.md).

optimi/utils.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,17 @@ def debias_beta(beta: float, step: int) -> float:
3939
def param_groups_weight_decay(
4040
model: nn.Module, weight_decay: float = 1e-2, additional_layers: Iterable[str] | None = None
4141
) -> list[dict[str, Any]]:
42-
"""Creates parameter groups, excluding bias and normalization layers from weight decay.
42+
"""Creates parameter groups excluding bias and normalization layers from weight decay.
4343
4444
Parameters:
45-
model: Model to optimize
46-
weight_decay: Weight decay coefficient (default: 1e-2)
47-
additional_layers: Additional layer names to exclude from weight decay (default: None)
45+
model: PyTorch model to create parameter groups for
46+
weight_decay: Weight decay coefficient applied to eligible parameters (default: 1e-2)
47+
additional_layers: Iterable of layer name substrings to exclude from weight decay.
48+
Any parameter whose name contains one of these substrings will be excluded from
49+
weight decay.
4850
4951
Returns:
50-
List of parameter groups with and without weight decay.
52+
List of two parameter group dictionaries, one with and one without weight decay.
5153
"""
5254
additional_layers = set(additional_layers) if additional_layers is not None else set()
5355
decay = []
@@ -56,7 +58,7 @@ def param_groups_weight_decay(
5658
if not param.requires_grad:
5759
continue
5860

59-
if param.ndim <= 1 or name.endswith(".bias") or name in additional_layers:
61+
if param.ndim <= 1 or name.endswith(".bias") or any(n in name for n in additional_layers):
6062
no_decay.append(param)
6163
else:
6264
decay.append(param)

tests/weight_decay_test.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import pytest
22
import torch
33
import optimi
4+
import inspect
5+
from optimi.optimizer import OptimiOptimizer
46

7+
# Dynamically collect all optimizer class names in the optimi module
8+
OPTIMIZERS = sorted([
9+
name for name, cls in inspect.getmembers(optimi)
10+
if inspect.isclass(cls)
11+
and issubclass(cls, OptimiOptimizer)
12+
])
513

614
class MLP(torch.nn.Module):
715
def __init__(self, input_size, hidden_size, device, dtype):
@@ -16,24 +24,31 @@ def forward(self, x):
1624

1725

1826
@pytest.mark.cpu
19-
@pytest.mark.parametrize("optimizer", ['Adam', 'Adan', 'Lion', 'RAdam', 'Ranger', 'SGD', 'StableAdamW'])
20-
def test_param_groups_weight_decay(optimizer):
27+
@pytest.mark.parametrize("optimizer", OPTIMIZERS)
28+
@pytest.mark.parametrize("additional_layer", [None, 'fc2'])
29+
def test_param_groups_weight_decay(optimizer, additional_layer):
2130
model = MLP(10, 20, torch.device('cpu'), torch.float16)
2231

23-
# first test that we have two groups, one with weight decay and one without
24-
25-
params = optimi.param_groups_weight_decay(model, weight_decay=1e-2)
32+
additional_layers = [additional_layer] if additional_layer is not None else None
33+
params = optimi.param_groups_weight_decay(model, weight_decay=1e-2, additional_layers=additional_layers)
2634

2735
filtered_wd_pg = False
2836
wd_pg = False
2937

38+
# first test that we have two groups, one with weight decay and one without
3039
for param_group in params:
3140
if param_group["weight_decay"] != 0:
3241
assert param_group["weight_decay"] == 1e-2, "Expected weight decay to be 1e-2"
33-
assert len(param_group["params"]) == 2, "Expected fc1.weight & fc2.weight in the group with weight decay"
42+
if additional_layer is not None:
43+
assert len(param_group["params"]) == 1, "Expected only fc1.weight in the group with weight decay"
44+
else:
45+
assert len(param_group["params"]) == 2, "Expected fc1.weight & fc2.weight in the group with weight decay"
3446
wd_pg = True
3547
if param_group["weight_decay"] == 0:
36-
assert len(param_group["params"]) == 3, "Expected fc1.bias, norm.weight, & norm.bias in the group without weight decay"
48+
if additional_layer is not None:
49+
assert len(param_group["params"]) == 4, "Expected fc1.bias, norm.weight, norm.biasm & fc2.weight in the group without weight decay"
50+
else:
51+
assert len(param_group["params"]) == 3, "Expected fc1.bias, norm.weight, & norm.bias in the group without weight decay"
3752
filtered_wd_pg = True
3853

3954
assert filtered_wd_pg, "Expected a parameter group without weight decay"
@@ -49,11 +64,17 @@ def test_param_groups_weight_decay(optimizer):
4964
for param_group in opt.param_groups:
5065
if param_group["weight_decay"] != 0:
5166
assert param_group["weight_decay"] == 1e-2, "Expected weight decay to be 1e-2"
52-
assert len(param_group["params"]) == 2, "Expected fc1.weight & fc2.weight in the group with weight decay"
67+
if additional_layer is not None:
68+
assert len(param_group["params"]) == 1, "Expected only fc1.weight in the group with weight decay"
69+
else:
70+
assert len(param_group["params"]) == 2, "Expected fc1.weight & fc2.weight in the group with weight decay"
5371
wd_pg = True
5472
if param_group["weight_decay"] == 0:
55-
assert len(param_group["params"]) == 3, "Expected fc1.bias, norm.weight, & norm.bias in the group without weight decay"
73+
if additional_layer is not None:
74+
assert len(param_group["params"]) == 4, "Expected fc1.bias, norm.weight, norm.biasm & fc2.weight in the group without weight decay"
75+
else:
76+
assert len(param_group["params"]) == 3, "Expected fc1.bias, norm.weight, & norm.bias in the group without weight decay"
5677
filtered_wd_pg = True
5778

58-
assert filtered_wd_pg, "Expected a parameter group without weight decay"
59-
assert wd_pg, "Expected a parameter group with weight decay"
79+
assert filtered_wd_pg, "Expected an optimizer parameter group without weight decay"
80+
assert wd_pg, "Expected an optimizer parameter group with weight decay"

0 commit comments

Comments
 (0)