augmented_lagrangian
Augmented-Lagrangian controller for a single scalar inequality constraint.
Classes:
| Name | Description |
|---|---|
AugmentedLagrangianInequalityConstraint |
Powell-Hestenes-Rockafellar augmented-Lagrangian controller for a single scalar inequality constraint |
AugmentedLagrangianInequalityConstraint
¶
Bases: Module
Powell-Hestenes-Rockafellar augmented-Lagrangian controller for a single scalar inequality constraint g <= tolerance.
Maintains the dual state used to enforce one inequality constraint as a penalty added to a
training objective. The constraint violation is v = g - tolerance; the penalty is
``penalty = multiplier * relu(v) + (penalty_coefficient / 2) * relu(v) ** 2``
where multiplier (the dual variable, often written lambda) and penalty_coefficient (the
quadratic coefficient, often written rho) are registered buffers. Only the active part relu(v)
enters the penalty, so a satisfied constraint contributes nothing and never pushes the objective
negative. The multiplier is advanced by dual ascent from the mean accumulated violation:
``multiplier <- clamp(multiplier + penalty_coefficient * mean_violation, min=0)``.
The two public methods split across the training step because the penalty enters the backward pass while the multiplier update mutates it:
penaltyruns in the forward pass; its output is added to the loss and backpropagated.observeruns afterbackward(the multiplier is a saved-for-backward multiplicand, so mutating it earlier would trip autograd's in-place version check). It records the violation and applies the dual-ascent update on the configured cadence, or immediately withforce=True(e.g. at an epoch boundary).
All state — multiplier, penalty_coefficient, and the running violation/example accumulators
— is held in buffers, so a state_dict checkpoint captures a partially accumulated window and
resume is well defined. The module performs no cross-replica reduction: it updates its own
multiplier from the values it is given, so under data-parallel training each replica maintains its
own multiplier and accumulators unless the caller reduces constraint_value across replicas
before calling.
relax selects the dual-update regime. With relax=True (default) the raw violation is
accumulated, so a feasible window (mean violation below zero) drives the multiplier back down
toward zero — the textbook PHR inequality update — letting the constrained quantity settle at the
tolerance boundary rather than staying over-constrained after a transient violation. With
relax=False only active (positive) violations are accumulated, so the multiplier is a one-way
ratchet that never decreases — classic dual (sub)gradient ascent. Use relax=False when you want
to be strict about the constraint: once the penalty ramps up to enforce the bound it never backs
off, keeping the constrained quantity firmly within tolerance even after it becomes feasible.
The penalty coefficient is held constant (no escalation schedule).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
float
|
The constraint bound |
required |
|
float
|
Initial value of the dual multiplier. |
0.0
|
|
float
|
The (constant) quadratic penalty coefficient. Must be positive. |
0.5
|
|
bool
|
If |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import torch from stainedglass_core.loss import augmented_lagrangian constraint = augmented_lagrangian.AugmentedLagrangianInequalityConstraint( ... tolerance=0.2, penalty_coefficient=0.5 ... )
A value within tolerance incurs no penalty.¶
penalty, violation = constraint.penalty(torch.tensor(0.1)) float(penalty) 0.0
A value above tolerance incurs a quadratic penalty; add it to the loss, then, after¶
backward, record the violation and apply the dual-ascent update.¶penalty, violation = constraint.penalty(torch.tensor(0.6)) round(float(penalty), 4) 0.04 constraint.observe(violation, force=True) True round( ... float(constraint.multiplier), 4 ... ) # multiplier rose to enforce the constraint 0.2
Added in version v3.53.0.
Methods:
| Name | Description |
|---|---|
observe |
Record a violation and apply the dual-ascent update on cadence. |
penalty |
Compute the augmented-Lagrangian penalty and the signed violation. |
observe
¶
observe(
violation: Tensor | None = None,
*,
num_examples: int = 1,
update_examples: int = 0,
world_size: int = 1,
force: bool = False
) -> bool
Record a violation and apply the dual-ascent update on cadence.
Call after backward so the in-place multiplier update does not corrupt the penalty's
autograd graph. Typical use records each step's violation and updates every update_examples
examples; pass force=True (with or without a new violation) to flush the accumulator
immediately, e.g. at an epoch boundary.
With relax=False only the active (relu'd) part of violation is accumulated, so
satisfied-constraint windows cannot lower the multiplier; with relax=True the raw violation
is accumulated. The dual-ascent update averages the accumulated violations over the number of
recorded observations (not weighted by num_examples); num_examples only advances the
example counter that governs the update_examples cadence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Tensor | None
|
The signed violation to record, as returned by |
None
|
|
int
|
Number of examples represented by |
1
|
|
int
|
Global number of examples between cadence updates. |
0
|
|
int
|
Number of data-parallel replicas sharing the global example budget. Must be positive. |
1
|
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
penalty
¶
penalty(
constraint_value: Tensor,
) -> tuple[torch.Tensor, torch.Tensor]
Compute the augmented-Lagrangian penalty and the signed violation.
Call in the forward pass and add the returned penalty to the loss. The penalty is
differentiable with respect to constraint_value for active violations, providing the
gradient that drives the constrained quantity back toward the tolerance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Tensor
|
The current value of the constrained quantity |
required |
Returns:
| Type | Description |
|---|---|
tuple[torch.Tensor, torch.Tensor]
|
A tuple |
tuple[torch.Tensor, torch.Tensor]
|
objective and |
tuple[torch.Tensor, torch.Tensor]
|
|