Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / trainer / runtime / loss_aggregation.py: 67%
42 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 2025-2026 Bytedance Ltd. and/or its affiliates
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"""Loss token counting for trainer-side aggregation."""
16from typing import Union
18import torch
20from hyper_parallel.data.constants import IGNORE_INDEX
23def count_loss_token(
24 batches: Union[list[dict[str, torch.Tensor]], dict[str, torch.Tensor]],
25) -> dict[str, torch.Tensor]:
26 # FIXME: VeOmni version -> AutoModels version
27 """Calculate the total number of text_tokens/image_tokens/** for loss in a global batch, or one micro batch."""
28 if isinstance(batches, dict):
29 batches = [batches]
30 token_len: dict[str, torch.Tensor] = {}
32 def _count(obj):
33 if isinstance(obj, dict) and not obj.get("padding_flag", False):
34 # ``shift_labels`` already aligns one target with every local hidden
35 # position. Otherwise align both labels and mask with the causal
36 # next-token prediction before counting.
37 labels = obj.get("shift_labels")
38 loss_mask = obj.get("loss_mask")
39 if labels is None:
40 labels = obj["labels"][..., 1:]
41 if loss_mask is not None:
42 loss_mask = loss_mask[..., 1:]
43 # IGNORE_INDEX and loss_mask are independent exclusion mechanisms;
44 # global loss weighting must count the same intersection as CE.
45 valid_targets = labels != IGNORE_INDEX
46 if loss_mask is not None:
47 if loss_mask.shape != labels.shape:
48 raise ValueError(
49 "loss_mask must match the labels after causal alignment, "
50 f"got labels={tuple(labels.shape)} and "
51 f"aligned_mask={tuple(loss_mask.shape)}"
52 )
53 valid_targets = valid_targets & loss_mask.to(torch.bool)
54 foundation_tokens = torch.sum(valid_targets)
55 if "foundation_tokens" in token_len:
56 foundation_tokens = token_len["foundation_tokens"] + foundation_tokens
57 token_len["foundation_tokens"] = foundation_tokens # text tokens
59 for key in obj.keys():
60 if key.endswith("_labels") and key != "shift_labels":
61 token_name = key.split("_labels")[0]
62 token_len[f"{token_name}_tokens"] = torch.sum(obj[key] != IGNORE_INDEX) # image generation tokens
64 if "image_output_mask" in obj:
65 image_decoder_tokens = torch.sum(obj["image_output_mask"])
66 if "image_decoder_tokens" in token_len:
67 image_decoder_tokens = token_len["image_decoder_tokens"] + image_decoder_tokens
68 token_len["image_decoder_tokens"] = image_decoder_tokens # image generation tokens
69 elif isinstance(obj, (list, tuple)):
70 for item in obj:
71 _count(item)
72 else:
73 raise TypeError(f"Unsupported batch type: {type(obj)}")
75 _count(batches)
76 foundation_tokens = token_len.setdefault("foundation_tokens", torch.tensor(0))
77 token_len.setdefault("image_decoder_tokens", foundation_tokens.new_zeros(()))
78 return token_len
81__all__ = ["count_loss_token"]