Source code for nvalchemi.training.losses.base
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Base protocols and models for loss-function schedules.
This module also re-exports :class:`BaseLossFunction` and
:class:`ComposedLossFunction` from :mod:`.composition` for discoverability:
subclass authors can do ``from nvalchemi.training.losses.base import
BaseLossFunction`` without tracking the internal module layout. The
canonical home of the leaf base class, keyed composition aggregator, and
composition output type remains :mod:`.composition`.
"""
from __future__ import annotations
from typing import Annotated, Protocol, runtime_checkable
from pydantic import BaseModel, Field
from nvalchemi.training._spec import BaseSpec, create_model_spec
[docs]
@runtime_checkable
class LossWeightSchedule(Protocol):
"""Runtime-checkable protocol for loss-weight schedules.
Any object callable with signature ``(step: int, epoch: int) -> float``,
exposing a ``per_epoch`` attribute, and returning a rebuild recipe from
``to_spec()`` satisfies this protocol. Such objects are accepted inside
:class:`~nvalchemi.training.losses.ComposedLossFunction`'s ``weights``
sequence or as the right-hand side of ``schedule * leaf``. Concrete
Pydantic schedules live in
:mod:`~nvalchemi.training.losses.schedules`.
Attributes
----------
per_epoch
If ``True``, the schedule should advance by ``epoch`` instead of
by ``step``. This aligns loss-weight updates with training loops
that update learning-rate schedules once per epoch.
Parameters
----------
step
Current global training step (0-indexed).
epoch
Current epoch number (0-indexed).
Returns
-------
float
Scalar weight to apply to the associated loss term.
"""
per_epoch: Annotated[
bool,
"Whether the schedule steps per epoch; if False, schedule will update per step/batch.",
]
def __call__(self, step: int, epoch: int) -> float:
"""Evaluate the schedule at ``(step, epoch)``."""
...
[docs]
def to_spec(self) -> BaseSpec:
"""Return a serializable spec that rebuilds this schedule."""
...
class _BaseWeightSchedule(BaseModel):
"""Base Pydantic model for serializable loss-weight schedules.
Attributes
----------
per_epoch
If ``False``, schedule windows advance by global step. If
``True``, they advance by epoch.
"""
model_config = {"frozen": True}
per_epoch: Annotated[
bool,
Field(
default=False,
description=(
"Whether to advance this schedule by epoch instead of by global step."
),
),
] = False
def to_spec(self) -> BaseSpec:
"""Return a serializable spec that rebuilds this schedule."""
return create_model_spec(type(self), **self.model_dump())
def _map_schedule_index(self, step: int, epoch: int) -> int:
"""Return the counter used to advance this schedule.
This method is only intended to be used if your schedule is mutually
exclusive; if your schedule uses both step *and* epoch values, then
you do not need to use this function as it's only for routing.
"""
return epoch if self.per_epoch else step
# Re-exports for discoverability. Import at the bottom to avoid a circular
# import: ``composition`` imports ``_BaseWeightSchedule`` indirectly through
# ``schedules``, which imports this module.
from nvalchemi.training.losses.composition import ( # noqa: E402
BaseLossFunction,
ComposedLossFunction,
ComposedLossOutput,
DTypePolicy,
ReductionContext,
)
__all__ = [
"BaseLossFunction",
"ComposedLossFunction",
"ComposedLossOutput",
"DTypePolicy",
"LossWeightSchedule",
"ReductionContext",
]