Skip to content

Commit 0d342cb

Browse files
committed
add readme v2
1 parent bbe4957 commit 0d342cb

1 file changed

Lines changed: 60 additions & 38 deletions

File tree

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,69 @@
11
# Scalarization
22

3-
A `Scalarizer` reduces a tensor of values (typically a vector of per-task or per-instance losses)
4-
into a single scalar that can be optimized with a standard `loss.backward()`. Scalarizers are the
5-
simple baseline against which the Jacobian-descent [aggregators](../aggregation) are compared:
6-
instead of combining the per-loss gradients, a scalarizer combines the losses directly.
3+
This package implements the `Scalarizer`s: objects that reduce a tensor of values (typically a
4+
vector of losses) into a single scalar optimizable with a standard `loss.backward()`.
75

8-
Full documentation for every scalarizer is at
9-
[torchjd.org](https://torchjd.org/latest/docs/scalarization/).
6+
This file is for contributors working on scalarizers. For the list of available scalarizers and their
7+
full API, see [torchjd.org](https://torchjd.org/latest/docs/scalarization/).
108

11-
## Usage
9+
## The contract
10+
11+
A scalarizer subclasses `Scalarizer` (in [`_scalarizer_base.py`](_scalarizer_base.py)) and implements
12+
one method:
1213

1314
```python
14-
import torch
15-
from torch.nn import Linear
16-
from torchjd.scalarization import Mean
15+
def forward(self, values: Tensor, /) -> Tensor:
16+
...
17+
```
1718

18-
model = Linear(3, 2)
19-
scalarizer = Mean()
19+
- **Any shape in, scalar out:** it reduces over *all* elements of `values` (0-dim, vector, matrix,
20+
higher-dim) into a 0-dim scalar.
21+
- **`values`, not `losses`:** a scalarizer is generic and not tied to losses.
22+
- **Pure and differentiable:** the output depends only on `values` and the configured parameters, so
23+
that `scalarizer(values).backward()` produces the gradient.
2024

21-
features = torch.randn(8, 3)
22-
losses = model(features).pow(2).mean(dim=0) # one loss per output dimension
23-
loss = scalarizer(losses)
24-
loss.backward() # gradients flow to the model parameters
25-
```
25+
## Adding one
26+
27+
A new scalarizer is a class plus the files that register it. Mirror an existing scalarizer of the
28+
same kind:
29+
30+
- `_<name>.py` — the class.
31+
- `__init__.py` — the import and an `__all__` entry.
32+
- `docs/source/docs/scalarization/<name>.rst` — the docs page, added to the `index.rst` toctree.
33+
- `tests/unit/scalarization/test_<name>.py` — the tests.
34+
- `CHANGELOG.md` — an entry under `[Unreleased]`.
35+
36+
## State
37+
38+
Most scalarizers are stateless. Keep yours stateless unless the method genuinely needs state (learned
39+
weights, a loss history). When it does:
40+
41+
- **Subclass `Stateful`** (`from torchjd._mixins import Stateful`) and implement `reset()` to restore
42+
the initial state.
43+
- **Keep `forward` self-contained.** Do not hide cross-call state or side effects inside it. When the
44+
method must carry information between calls, expose it through an explicit, named method and
45+
document the protocol (e.g. a per-epoch `step()`, or an `update()` after the optimizer step).
46+
- **`nn.Parameter` vs buffer:** trainable state is an `nn.Parameter`; non-trained tensors that must
47+
move with `.to()` are registered with `register_buffer`.
48+
49+
## What is not a scalarizer
50+
51+
A scalarizer only ever sees the loss values.
52+
53+
Anything that needs the model, its parameters, or the per-task gradients belongs in the
54+
[aggregation](../aggregation) package as a `Weighting` / `Aggregator`, which operates on the Jacobian
55+
or its Gramian. If you reach for gradient norms or the network inside `forward`, you are writing an
56+
aggregator, not a scalarizer.
57+
58+
## Things to be careful about
2659

27-
## Available scalarizers
28-
29-
- **Constant**: combines the values with constant, pre-determined weights.
30-
- **COSMOS**: linear scalarization minus a cosine-similarity penalty toward a preference direction.
31-
- **DWA**: weights each value by the relative rate at which its loss decreased over the two previous
32-
epochs.
33-
- **FAMO**: decreases all task losses at an approximately equal rate, learning the task weights
34-
internally.
35-
- **GeometricMean**: geometric mean of the values (also known as GLS).
36-
- **IMTLL**: learns a per-task scale and combines the values as the sum of `exp(s_i) * L_i - s_i`.
37-
- **Mean**: mean of the values.
38-
- **PBI**: decomposes the values along a preference direction and penalizes the perpendicular
39-
distance.
40-
- **Random**: combines the values with positive random weights summing to one.
41-
- **STCH**: smooth approximation of the weighted, shifted maximum of the values.
42-
- **Sum**: sum of the values.
43-
- **UW**: weights the values using learned per-task uncertainties.
44-
45-
`UW`, `IMTLL`, and `FAMO` are trainable, and `DWA` and `FAMO` carry state between calls, so they
46-
need a little more than a single call (an optimizer, a per-epoch `step()`, or an `update()`). See
47-
the documentation for the exact usage.
60+
- **Determinism and side effects:** the output should depend only on `values` and the configured
61+
parameters. Any state change must be deliberate, explicit, and undone by `reset()`.
62+
- **Numerical stability:** keep the reduction finite on the edges of its domain (log-sum-exp
63+
centering, an eps under a norm or in a denominator, etc.), and explain any value shift in a comment
64+
and a `.. note::`.
65+
- **Hyperparameters:** when a coefficient has no single good value across problems, make it required
66+
rather than guessing a default, and validate it in `__init__`.
67+
- **Shape validation:** check parameter shapes against `values` at call time and raise `ValueError`.
68+
- **Preconditions:** if the method is undefined on some inputs, document it in a `.. note::` and lock
69+
it with a test (e.g. assert `nan` propagates rather than being silently clamped).

0 commit comments

Comments
 (0)