Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / functional / aux_loss.py: 98%
58 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Reusable auxiliary-loss functions."""
17from __future__ import annotations
19from contextlib import contextmanager
20from contextvars import ContextVar
21from dataclasses import dataclass, field
22from typing import Any, Iterator
24import torch # pylint: disable=forbidden-backend-import
26from hyper_parallel.core.dtensor.dtensor import DTensor, SkipDTensorDispatch
29@dataclass
30class _AuxLossScaleState:
31 """Per-forward scale, shared with its backward and recomputation."""
33 scale: torch.Tensor | float = 1.0
34 active: bool = False
35 handles: list[Any] = field(default_factory=list)
38_AUX_LOSS_STATE: ContextVar[_AuxLossScaleState | None] = ContextVar("aux_loss_state", default=None)
41@contextmanager
42def aux_loss_scale_context() -> Iterator[None]:
43 """Isolate auxiliary gradient scaling for one forward/backward microstep.
45 Call :func:`bind_aux_loss_scale` on the local scalar task loss before
46 weighting it. Keep this context active through backward so checkpoint
47 recomputation observes the same scale. Nested contexts are independent.
48 """
49 state = _AuxLossScaleState()
50 token = _AUX_LOSS_STATE.set(state)
51 try:
52 yield
53 finally:
54 for handle in state.handles:
55 handle.remove()
56 _AUX_LOSS_STATE.reset(token)
59def bind_aux_loss_scale(loss: torch.Tensor) -> None:
60 """Make injected losses follow the scalar task loss's backward multiplier.
62 This is a no-op outside :func:`aux_loss_scale_context` or when no auxiliary
63 loss was attached. The hook runs before upstream model backward, including
64 checkpoint recomputation, and never changes the task-loss gradient.
66 Args:
67 loss: Unweighted scalar foundation loss connected to the model output.
68 """
69 state = _AUX_LOSS_STATE.get()
70 if state is None or not state.active or not loss.requires_grad:
71 return
72 if loss.numel() != 1:
73 raise ValueError("Auxiliary gradient scaling requires a scalar task loss")
75 def save_scale(gradient: torch.Tensor) -> None:
76 """Capture the actual multiplier after token and parallel weighting.
78 Args:
79 gradient: Upstream scalar loss multiplier, possibly a DTensor.
80 """
81 with SkipDTensorDispatch():
82 local_gradient = gradient.to_local() if isinstance(gradient, DTensor) else gradient
83 state.scale = local_gradient.detach()
85 state.handles.append(loss.register_hook(save_scale))
88class _AuxLossAutoScaler(torch.autograd.Function):
89 """Inject auxiliary-loss gradients without changing the forward loss value."""
91 main_loss_backward_scale = torch.tensor(1.0)
93 @staticmethod
94 def forward(ctx: Any, output: torch.Tensor, aux_loss: torch.Tensor) -> torch.Tensor:
95 """Save the auxiliary loss and return the output unchanged.
97 Args:
98 ctx: Autograd context for the auxiliary loss and scale state.
99 output: Forward tensor carrying the injected gradient.
100 aux_loss: Scalar objective whose gradient is injected in backward.
101 """
102 ctx.save_for_backward(aux_loss)
103 ctx.scale_state = _AUX_LOSS_STATE.get()
104 return output
106 @staticmethod
107 def backward(ctx: Any, grad_output: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
108 """Pass through the output gradient and inject the auxiliary-loss gradient.
110 Args:
111 ctx: Context saved by forward.
112 grad_output: Unchanged gradient returned to the forward tensor.
113 """
114 (aux_loss,) = ctx.saved_tensors
115 scale = (
116 ctx.scale_state.scale if ctx.scale_state is not None
117 else _AuxLossAutoScaler.main_loss_backward_scale
118 )
119 if isinstance(scale, torch.Tensor):
120 scale = scale.to(device=aux_loss.device, dtype=aux_loss.dtype)
121 aux_loss_grad = torch.ones_like(aux_loss) * scale
122 return grad_output, aux_loss_grad
124 @staticmethod
125 def set_loss_scale(scale: torch.Tensor) -> None:
126 """Set the auxiliary-loss gradient scale."""
127 _AuxLossAutoScaler.main_loss_backward_scale = scale
130def aux_loss_auto_scale(output: torch.Tensor, aux_loss: torch.Tensor) -> torch.Tensor:
131 """Attach an auxiliary-loss gradient to an unchanged forward tensor.
133 Args:
134 output: Main forward output.
135 aux_loss: Scalar auxiliary loss whose gradient should be injected.
137 Returns:
138 ``output`` unchanged in value, with an autograd edge to ``aux_loss``.
139 """
140 state = _AUX_LOSS_STATE.get()
141 if state is not None:
142 state.active = True
143 return _AuxLossAutoScaler.apply(output, aux_loss)
146def set_aux_loss_scale(scale: torch.Tensor) -> None:
147 """Set the gradient multiplier used by :func:`aux_loss_auto_scale`.
149 Args:
150 scale: Tensor used to scale the injected auxiliary-loss gradient.
151 """
152 _AuxLossAutoScaler.set_loss_scale(scale)