Loss balancers
A balancer combines ordered, unweighted objective losses. It is independent of PhiModule, so the same module and
objective can use static, gradient-norm, exact-NTK, or custom weighting.
Shared state and update contract
BalancerState.weights is aligned with objective loss_names. The traces field stores the latest adaptive
diagnostic: parameter-gradient norms for GradNormBalancer, or mean pointwise diagonal NTKs for ExactNTKBalancer.
BalancerUpdatePlan contains a compiled functional update, its host-side cadence, and optional diagnostic batch
sizes. A None batch-size mapping reuses the current training batches.
Adaptive balancers accept update_every_n_steps and an optional update_start_step. By default, the first update
occurs after one complete interval. An explicit start anchors the cadence, so a start of 50 with an interval of 100
updates at steps 50, 150, and 250.
Logged metrics
A balancer's diagnostics() method returns names such as weight/pde/heat. PhiModule adds the train/ prefix before
the values reach callbacks, progress bars, or experiment loggers. For a loss named pde/heat, the built-in balancers
produce these metrics:
| Balancer | Metric | Meaning |
|---|---|---|
| All built-ins | train/weight/pde/heat |
Current multiplier applied to the unweighted pde/heat loss |
GradNormBalancer |
train/grad_norm/pde/heat |
L2 norm of the unweighted loss gradient over all model parameters |
ExactNTKBalancer |
train/ntk/pde/heat |
Mean pointwise diagonal empirical NTK for the configured residual or output stream |
Every scalar is sent to configured experiment loggers at the Trainer's logging cadence. Loss weights also appear in
the progress metrics by default. Gradient-norm and NTK diagnostics are logger-only unless a module explicitly routes
them to the progress bar with self.log(..., prog_bar=True).
StaticLossBalancer reports the configured weights throughout fitting. Adaptive diagnostics start at zero and change
only when their scheduled update runs. Their most recent values are reported between updates; they are not recomputed
on every optimizer step.
BalancerState
[source]
Store JIT-compatible loss weights and the most recent NTK diagnostics.
Attributes:
| Name | Type | Description |
|---|---|---|
weights |
Array
|
Vector aligned with the balancer's fixed loss-name ordering. |
traces |
Array
|
Most recently computed NTK statistic for each loss, or zeros before the first update. Exact-NTK balancing stores the mean pointwise diagonal empirical NTK in this field. |
BalancerUpdatePlan
[source]
dataclass
BalancerUpdatePlan(
update: BalancerUpdate,
every_n_steps: int,
update_start_step: int,
batch_sizes: Mapping[str, int] | None = None,
)
Describe one host-scheduled adaptive-balancer update.
Attributes:
| Name | Type | Description |
|---|---|---|
update |
BalancerUpdate
|
Compiled functional update accepting model state, named batches, and balancer state. |
every_n_steps |
int
|
Positive optimizer-step interval between updates. |
update_start_step |
int
|
Nonnegative absolute optimizer step anchoring the update cadence. |
batch_sizes |
Mapping[str, int] | None
|
Optional positive diagnostic batch size for each objective batch key. |
__post_init__
[source]
Validate host scheduling and fixed diagnostic-batch sizes.
Raises:
| Type | Description |
|---|---|
TypeError
|
If scheduling values or diagnostic batch names have invalid types. |
ValueError
|
If an interval or diagnostic batch size is zero or negative. |
LossBalancer
[source]
Bases: Protocol
Define the functional contract for combining named scalar losses.
initialize
[source]
Create the JAX-compatible initial balancer state.
Returns:
| Type | Description |
|---|---|
Any
|
Initial balancer-state PyTree. |
combine
[source]
Combine named unweighted losses into one scalar objective.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
losses
|
Mapping[str, Array]
|
Scalar losses keyed by every configured loss name. |
required |
state
|
Any
|
Current balancer-state PyTree. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Weighted scalar loss. |
diagnostics
[source]
Expose scalar diagnostics without interpreting state externally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Any
|
Current balancer-state PyTree. |
required |
Returns:
| Type | Description |
|---|---|
Mapping[str, Array]
|
Scalar diagnostics with stable names. |
AdaptiveBalancer
[source]
Bases: Protocol
Define the assembly contract for a periodically updated loss balancer.
build_update_plan
[source]
Build a reusable update and declare its diagnostic data policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
Any
|
Application module exposing the numerical streams required by the balancer. |
required |
batch_keys
|
Sequence[str]
|
Stable objective batch keys available from the training source. |
required |
Returns:
| Type | Description |
|---|---|
BalancerUpdatePlan
|
Functional update, scheduling, and diagnostic data plan consumed by training assembly. |
Static weighting
StaticLossBalancer
[source]
StaticLossBalancer(
loss_names: Sequence[str],
*,
weights: Mapping[str, float] | None = None,
default_weight: float = 1.0,
)
Apply immutable scalar weights to a fixed sequence of loss names.
Attributes:
| Name | Type | Description |
|---|---|---|
initial_weights |
Configured |
Initialize stable loss ordering and configured weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loss_names
|
Sequence[str]
|
Unique non-empty sequence defining loss ordering. |
required |
weights
|
Mapping[str, float] | None
|
Optional explicit weights keyed by loss name. |
None
|
default_weight
|
float
|
Weight assigned to names absent from |
1.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
initialize
[source]
Create the fixed balancer state.
Returns:
| Type | Description |
|---|---|
BalancerState
|
State containing configured weights and zero diagnostic placeholders. |
combine
[source]
Combine named scalar losses in stable order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
losses
|
Mapping[str, Array]
|
Scalar loss arrays keyed by every configured loss name. |
required |
state
|
BalancerState
|
Current balancer state containing the fixed weights. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Weighted scalar total. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a configured loss name is absent from |
diagnostics
[source]
Expose configured loss weights for logging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
BalancerState
|
Current fixed-weight state. |
required |
Returns:
| Type | Description |
|---|---|
Mapping[str, Array]
|
Scalar weights keyed by loss name. |
from phijax.balancers import StaticLossBalancer
balancer = StaticLossBalancer(
module.loss_names,
weights={"initial/u": 1.0, "pde/burgers": 1.0},
)
Pass the module's resolved loss_names; do not duplicate them in project settings.
Gradient-norm weighting
This policy balances inverse parameter-gradient magnitudes. It is not the training-rate-aware GradNorm multitask
algorithm. Losses are differentiated individually to limit retained intermediate state. Each
train/grad_norm/<loss_name> value is the L2 norm of one unweighted scalar loss gradient with respect to every model
parameter. The update uses the current training batches.
GradNormBalancer
[source]
GradNormBalancer(
loss_names: Sequence[str],
*,
update_every_n_steps: int,
update_start_step: int | None = None,
eps: float = 1e-05,
moving_average_coefficient: float = 0.9,
initial_weights: Mapping[str, float] | None = None,
)
Balance named losses with parameter-gradient norm ratios.
The balancer computes the Euclidean parameter-gradient norm g_i of every unweighted scalar loss. Its raw weights
are lambda_i = mean_j(g_j) / (g_i + eps * mean_j(g_j)), followed by an exponential moving average. Losses are
differentiated one at a time so the update does not retain every loss Jacobian at once.
This is a direct gradient-norm weighting policy rather than the training-rate-aware GradNorm algorithm for multitask networks.
Attributes:
| Name | Type | Description |
|---|---|---|
momentum |
Moving-average coefficient applied to previous weights. |
Initialize gradient-norm balancing configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loss_names
|
Sequence[str]
|
Unique non-empty sequence defining loss and diagnostic ordering. |
required |
update_every_n_steps
|
int
|
Positive optimizer-step interval between gradient-norm updates. |
required |
update_start_step
|
int | None
|
Nonnegative absolute optimizer step of the first update. |
None
|
eps
|
float
|
Nonnegative multiplier regularizing each gradient-norm denominator. |
1e-05
|
moving_average_coefficient
|
float
|
Previous-weight coefficient in |
0.9
|
initial_weights
|
Mapping[str, float] | None
|
Optional initial weights keyed by loss name; unspecified names use |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If update scheduling values have invalid types. |
ValueError
|
If names are invalid, the update interval is not positive, |
initialize
[source]
Create an initial balancer state.
Returns:
| Type | Description |
|---|---|
BalancerState
|
State with configured weights and zero gradient-norm diagnostics. |
combine
[source]
Apply the most recently computed gradient-norm weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
losses
|
Mapping[str, Array]
|
Scalar loss arrays keyed by every configured loss name. |
required |
state
|
BalancerState
|
Current weights and gradient-norm diagnostics. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Weighted scalar total. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a configured loss name is absent from |
diagnostics
[source]
Expose current loss weights and parameter-gradient norms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
BalancerState
|
Current gradient-norm balancer state. |
required |
Returns:
| Type | Description |
|---|---|
Mapping[str, Array]
|
Scalar weights and gradient norms keyed by loss name. |
update_from_grad_norms
[source]
Update smoothed weights from ordered loss-gradient norms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad_norms
|
Array
|
Gradient-norm vector aligned with :attr: |
required |
state
|
BalancerState
|
Previous weights and gradient-norm diagnostics. |
required |
Returns:
| Type | Description |
|---|---|
BalancerState
|
New stopped-gradient state. An all-zero norm vector leaves every weight unchanged. |
update
[source]
update(
module: BasePhiModule,
model_state: Any,
batches: NamedBatches,
state: BalancerState,
) -> BalancerState
Compute one loss-gradient norm at a time and update balancer state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module producing the named unweighted scalar losses. |
required |
model_state
|
Any
|
Differentiable model parameter PyTree. |
required |
batches
|
NamedBatches
|
Current fixed-structure training batches. |
required |
state
|
BalancerState
|
Previous balancer state. |
required |
Returns:
| Type | Description |
|---|---|
BalancerState
|
Updated weights and gradient norms in :attr: |
make_update
[source]
make_update(
module: BasePhiModule,
) -> Callable[
[Any, NamedBatches, BalancerState], BalancerState
]
Create a reusable compiled gradient-norm update.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module producing named unweighted scalar losses. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Any, NamedBatches, BalancerState], BalancerState]
|
JIT-compiled function accepting model state, current batches, and balancer state. |
build_update_plan
[source]
Describe a gradient-norm refresh using the current training batches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module producing named unweighted scalar losses. |
required |
batch_keys
|
Sequence[str]
|
Available objective batch keys, retained for the shared adaptive-balancer contract. |
required |
Returns:
| Type | Description |
|---|---|
BalancerUpdatePlan
|
Update plan that reuses the current training batches. |
Exact NTK weighting
Exact NTK balancing computes the squared parameter-Jacobian norm at each point. It averages these values for each
residual stream and normalizes the loss weights from those means. Named streams are evaluated one at a time.
kernel_chunk_size=1 minimizes peak memory; None vectorizes over all samples.
Each train/ntk/<loss_name> value is the resulting mean for one named residual or output stream. The Trainer samples
fixed diagnostic batches of kernel_size rows once for the fit, and each scheduled update evaluates the current model
on those batches.
The method is motivated by the empirical NTK analysis and adaptive PINN loss weighting in
Wang, Yu, and Perdikaris (2022). PhiJAX uses the mean-diagonal rule
lambda_i = mean_j(mu_j) / mu_i. It supports moving-average smoothing and processes one named stream at a time to
limit peak memory.
exact_ntk_trace
[source]
Compute an exact diagonal-NTK contribution without materializing a Jacobian.
The function differentiates one scalar stream entry at a time, flattens the parameter-gradient PyTree, and sums its
squared norm. A multi-feature stream therefore contributes the sum of its diagonal empirical NTK entries.
:class:ExactNTKBalancer maps this calculation over samples in bounded chunks before averaging the resulting
pointwise diagonal values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream_fn
|
Callable[[Any], Array]
|
Pure function mapping explicit model state to one residual array. |
required |
model_state
|
Any
|
Differentiable model parameter PyTree. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Scalar |
ExactNTKBalancer
[source]
ExactNTKBalancer(
loss_names: Sequence[str],
*,
update_every_n_steps: int,
kernel_size: int,
kernel_chunk_size: int | None = 1,
update_start_step: int | None = None,
eps: float = 1e-10,
moving_average_coefficient: float = 0.9,
initial_weights: Mapping[str, float] | None = None,
)
Balance named losses with exact diagonal-NTK ratios and optional smoothing.
For loss i, the balancer computes the exact diagonal empirical NTK independently at every sampled point and
stores its pointwise mean as mu_i. The raw rule is lambda_i = mean_j(mu_j) / mu_i. A moving average blends each
raw update with its previous value. Using empirical NTK statistics to diagnose and balance PINN loss terms follows
Wang et al. (2022); PhiJAX uses mean-diagonal normalization and evaluates named residual streams sequentially to
control peak memory.
Attributes:
| Name | Type | Description |
|---|---|---|
momentum |
Moving-average coefficient applied to previous weights. |
References
Wang, S., Yu, X., and Perdikaris, P. (2022). When and Why PINNs Fail to Train: A Neural Tangent Kernel Perspective. Journal of Computational Physics, 449, 110768.
Initialize exact-NTK balancing configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loss_names
|
Sequence[str]
|
Unique non-empty sequence defining loss and trace ordering. |
required |
update_every_n_steps
|
int
|
Positive optimizer-step interval between NTK updates. |
required |
kernel_size
|
int
|
Positive diagnostic sample count requested for every objective batch. |
required |
kernel_chunk_size
|
int | None
|
Positive number of diagnostic samples differentiated in parallel, or |
1
|
update_start_step
|
int | None
|
Nonnegative absolute optimizer step of the first update. |
None
|
eps
|
float
|
Positive mean-diagonal floor used to avoid division by zero. |
1e-10
|
moving_average_coefficient
|
float
|
Previous-weight coefficient in |
0.9
|
initial_weights
|
Mapping[str, float] | None
|
Optional initial weights keyed by loss name; unspecified names use |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If update scheduling or kernel-size values have invalid types. |
ValueError
|
If names are invalid, sizes are not positive, |
initialize
[source]
Create an initial balancer state.
Returns:
| Type | Description |
|---|---|
BalancerState
|
State with configured initial weights and zero mean-diagonal placeholders. |
combine
[source]
Apply the most recently computed NTK weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
losses
|
Mapping[str, Array]
|
Scalar loss arrays keyed by every configured loss name. |
required |
state
|
BalancerState
|
Current NTK weights and mean-diagonal diagnostics. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Weighted scalar total. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a configured loss name is absent from |
diagnostics
[source]
Expose current NTK weights and mean-diagonal estimates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
BalancerState
|
Current NTK balancer state. |
required |
Returns:
| Type | Description |
|---|---|
Mapping[str, Array]
|
Scalar weights and traces keyed by loss name. |
update_from_traces
[source]
Update smoothed weights from ordered mean diagonal-NTK values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traces
|
Array
|
Mean diagonal-NTK vector aligned with :attr: |
required |
state
|
BalancerState
|
Previous weights and mean-diagonal diagnostics. |
required |
Returns:
| Type | Description |
|---|---|
BalancerState
|
New stopped-gradient state containing nonnegative mean diagonals and smoothed weights. Zero entries |
BalancerState
|
preserve their previous weights; an all-zero vector leaves every weight unchanged. |
update
[source]
update(
module: BasePhiModule,
model_state: Any,
ntk_batches: NamedBatches,
state: BalancerState,
*,
kernel_chunk_size: int | None = 1,
) -> BalancerState
Compute pointwise diagonal NTKs one named stream at a time and update state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module exposing |
required |
model_state
|
Any
|
Differentiable model parameter PyTree. |
required |
ntk_batches
|
NamedBatches
|
Fixed-shape named batches with a shared leading sample size, used only for NTK estimation. |
required |
state
|
BalancerState
|
Previous balancer state. |
required |
kernel_chunk_size
|
int | None
|
Positive number of kernel samples differentiated in parallel. |
1
|
Returns:
| Type | Description |
|---|---|
BalancerState
|
Updated weights and mean diagonal-NTK values in :attr: |
make_update
[source]
make_update(
module: BasePhiModule,
*,
kernel_chunk_size: int | None = 1,
) -> Callable[
[Any, NamedBatches, BalancerState], BalancerState
]
Create the reusable compiled NTK update for fixed batch structures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module exposing one-at-a-time named residual streams. |
required |
kernel_chunk_size
|
int | None
|
Positive number of kernel samples differentiated in parallel. |
1
|
Returns:
| Type | Description |
|---|---|
Callable[[Any, NamedBatches, BalancerState], BalancerState]
|
JIT-compiled function accepting model state, NTK batches, and balancer state. New array values reuse the |
Callable[[Any, NamedBatches, BalancerState], BalancerState]
|
executable when shapes, dtypes, and PyTree structures stay fixed. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
build_update_plan
[source]
Describe an exact-NTK refresh with fixed diagnostic samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
BasePhiModule
|
Module exposing one-at-a-time named residual streams. |
required |
batch_keys
|
Sequence[str]
|
Stable objective batch keys sampled for every NTK refresh. |
required |
Returns:
| Type | Description |
|---|---|
BalancerUpdatePlan
|
Update plan containing scheduling, the compiled NTK update, and fixed diagnostic batch sizes. |
balancer = ExactNTKBalancer(
module.loss_names,
update_every_n_steps=100,
kernel_size=256,
kernel_chunk_size=1,
)
See Creating a custom loss balancer for implementation and testing requirements.