Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / trainer / runtime / metrics.py: 78%
54 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
1# Copyright 2025-2026 Bytedance Ltd. and/or its affiliates
2# Copyright 2026 Huawei Technologies Co., Ltd
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ============================================================================
16"""Global loss metrics reduction across data/context parallel ranks."""
18from __future__ import annotations
20__all__ = ["mean_global_loss"]
22from typing import TYPE_CHECKING, Union
24import torch # pylint: disable=forbidden-backend-import
25import torch.distributed as dist # pylint: disable=forbidden-backend-import
27from hyper_parallel.core.utils.communication import (
28 _is_tracing_or_compiling,
29 differentiable_all_reduce,
30)
31from hyper_parallel.trainer.runtime.distributed import all_reduce
33if TYPE_CHECKING:
34 from hyper_parallel.distributed.mesh import MeshContext
37def _reduce_weighted_loss(cur_loss, cur_token_len, device_mesh, dp_cp_group):
38 """Reduce a loss numerator while preserving the local backward graph."""
39 local_weighted_loss = cur_loss.float() * cur_token_len
40 backward_loss = local_weighted_loss * device_mesh.dp_size * device_mesh.cp_size
41 global_weighted_loss = local_weighted_loss.detach().item()
42 if device_mesh.dp_cp_mesh is not None and device_mesh.dp_cp_mesh.size() > 1:
43 global_weighted_loss = all_reduce(
44 global_weighted_loss,
45 op="sum",
46 group=dp_cp_group,
47 )
48 global_sum = local_weighted_loss.new_tensor(global_weighted_loss)
49 return global_sum + (backward_loss - backward_loss.detach())
52def mean_global_loss(
53 losses: Union[dict[str, torch.Tensor], torch.Tensor],
54 current_token_counts: dict[str, torch.Tensor],
55 device_mesh: "MeshContext",
56) -> dict[str, torch.Tensor]:
57 """Calculate global loss numerators using explicit mesh information.
59 FSDP divides gradients over its flattened DP+CP domain, so each local loss
60 is weighted by valid tokens and multiplied by ``dp_size * cp_size``.
61 Trainer divides accumulated gradients and reported losses by the actual
62 optimizer-step token count afterwards. Empty shards must return finite losses.
64 Args:
65 losses: A loss tensor or mapping of named loss tensors.
66 current_token_counts: Token counts for the current micro batch.
67 device_mesh: Trainer mesh context containing parallel sizes and groups.
69 Returns:
70 Token-weighted loss numerators keyed by loss name.
72 Raises:
73 ValueError: If sequence parallelism is enabled without a TP device mesh.
74 """
75 loss_dict = {}
76 dp_cp_mesh = device_mesh.dp_cp_mesh
77 dp_cp_group = dp_cp_mesh.get_group() if dp_cp_mesh is not None else None
78 sequence_parallel = device_mesh.sequence_parallel
79 sequence_parallel_group = None
80 sequence_parallel_size = 1
81 if sequence_parallel:
82 if device_mesh.device_mesh is None or "tp" not in device_mesh.device_mesh.mesh_dim_names:
83 raise ValueError("Sequence parallelism requires a DeviceMesh with a 'tp' dimension.")
84 sequence_parallel_group = device_mesh.device_mesh["tp"].get_group()
85 sequence_parallel_size = dist.get_world_size(group=sequence_parallel_group)
87 if isinstance(losses, torch.Tensor): # text loss only
88 losses = {"foundation_loss": losses}
90 if len({key.split("_loss", maxsplit=1)[0] for key in losses}) != 1:
91 raise ValueError("Step gradient normalization requires losses sharing one token-count family")
93 for key, cur_loss in losses.items():
94 loss_name = key.split("_loss", maxsplit=1)[0] # foundation/image_decoder/**
96 cur_token_len = current_token_counts[f"{loss_name}_tokens"]
97 if sequence_parallel:
98 if _is_tracing_or_compiling():
99 # Graph tracing: ``.item()`` is data-dependent and unsupported
100 # by the static-shape tracer; aggregate on tensors instead.
101 cur_token_len = differentiable_all_reduce(
102 cur_token_len, "sum", sequence_parallel_group
103 )
104 else:
105 cur_token_len = all_reduce(cur_token_len.item(), op="sum", group=sequence_parallel_group)
107 if _is_tracing_or_compiling():
108 # Graph-traced variant of the aggregation below: identical math
109 # expressed with tensor collectives so the static-shape tracer can
110 # capture it (no ``.item()``, no data-dependent zero-token guard).
111 # ``differentiable_all_reduce`` dispatches to functional collectives
112 # under tracing. BaseTrainer passes token-count tensors as explicit
113 # graph inputs so packed/ignored-token counts remain live.
114 local_weighted_loss = cur_loss.float() * cur_token_len
115 backward_loss = local_weighted_loss * device_mesh.dp_size * device_mesh.cp_size
116 global_weighted_loss = local_weighted_loss.detach()
117 if dp_cp_mesh is not None and dp_cp_mesh.size() > 1:
118 global_weighted_loss = differentiable_all_reduce(
119 global_weighted_loss, "sum", dp_cp_group
120 )
121 cur_loss = global_weighted_loss + (backward_loss - backward_loss.detach())
122 if sequence_parallel:
123 cur_loss = cur_loss / sequence_parallel_size
124 loss_dict[key] = cur_loss
125 continue
127 cur_loss = _reduce_weighted_loss(
128 cur_loss,
129 cur_token_len,
130 device_mesh,
131 dp_cp_group,
132 )
134 if sequence_parallel:
135 cur_loss = cur_loss / sequence_parallel_size
137 loss_dict[key] = cur_loss
139 return loss_dict