Download tcod/trinity/algorithm/utils.py from SeanWang0027/ftb-sciworld-repro: direct link, hf CLI and curl.
- Browser
- Download file 4.32 kB
-
https://huggingface.co/SeanWang0027/ftb-sciworld-repro/resolve/main/tcod/trinity/algorithm/utils.py
- Command line
-
hf download hf://SeanWang0027/ftb-sciworld-repro/tcod/trinity/algorithm/utils.py
-
curl -L -o utils.py https://huggingface.co/SeanWang0027/ftb-sciworld-repro/resolve/main/tcod/trinity/algorithm/utils.py
4.32 kB
| """Common utils for algorithm module. | |
| Modified from https://github.com/volcengine/verl/blob/main/verl/utils/torch_functional.py | |
| """ | |
| import torch | |
| def aggregate_loss(values, mask, loss_agg_mode="token-mean", normalizer=None): | |
| """ | |
| Compute loss from values and mask with various aggregation modes. | |
| Modified from: https://github.com/volcengine/verl/blob/main/verl/trainer/ppo/core_algos.py | |
| Args: | |
| values (torch.Tensor): Arbitrary shape tensor of values to aggregate. | |
| mask (torch.BoolTensor or torch.FloatTensor): Same shape as values, 1/True = include, 0 = ignore. | |
| loss_agg_mode (str): One of the following: | |
| - "token-mean": mean over all unmasked elements. | |
| - "seq-mean-token-sum": average over sequences, where each sequence's loss is sum of unmasked values. | |
| - "seq-mean-token-mean": average over sequences, where each sequence's loss is mean of unmasked values. | |
| - "seq-mean-token-sum-norm": total sum of unmasked values divided by a fixed normalizer (e.g., seq length). | |
| normalizer (float or None): Only used in 'seq-mean-token-sum-norm'. If None, uses mask.shape[-1]. | |
| Returns: | |
| torch.Tensor: Scalar loss value. | |
| """ | |
| if loss_agg_mode == "token-mean": | |
| return masked_mean(values, mask) | |
| elif loss_agg_mode == "seq-mean-token-sum": | |
| # Sum over last dimension (token-level), then take mean across batch (sequence-level) | |
| seq_losses = masked_sum(values, mask, axis=-1) # [batch] | |
| return seq_losses.mean() | |
| elif loss_agg_mode == "seq-mean-token-mean": | |
| # Mean over tokens per sequence, then mean over sequences | |
| seq_losses = masked_mean(values, mask, axis=-1) # [batch] | |
| return seq_losses.mean() | |
| elif loss_agg_mode == "seq-mean-token-sum-norm": | |
| total_token_sum = masked_sum(values, mask) # scalar | |
| norm = normalizer if normalizer is not None else mask.shape[-1] | |
| return total_token_sum / (norm + 1e-8) | |
| else: | |
| raise ValueError( | |
| f"Invalid loss_agg_mode: {loss_agg_mode}. " | |
| f"Choose from ['token-mean', 'seq-mean-token-sum', 'seq-mean-token-mean', 'seq-mean-token-sum-norm']" | |
| ) | |
| def masked_sum(values, mask, axis=None): | |
| """Compute mean of tensor with a masked values.""" | |
| valid_values = torch.where(mask.bool(), values, 0.0) | |
| return (valid_values * mask).sum(axis=axis) | |
| def masked_mean(values, mask, axis=None): | |
| """Compute mean of tensor with a masked values.""" | |
| return masked_sum(values, mask, axis=axis) / (mask.sum(axis=axis) + 1e-8) | |
| def masked_var(values, mask, unbiased=True): | |
| """Compute variance of tensor with masked values.""" | |
| mean = masked_mean(values, mask) | |
| centered_values = values - mean | |
| variance = masked_mean(centered_values**2, mask) | |
| if unbiased: | |
| mask_sum = mask.sum() | |
| if mask_sum == 0: | |
| raise ValueError("At least one element in the mask has to be 1.") | |
| # note that if mask_sum == 1, then there is a division by zero issue | |
| # to avoid it you just need to use a larger minibatch_size | |
| if mask_sum == 1: | |
| raise ValueError("The sum of the mask is one, which can cause a division by zero.") | |
| bessel_correction = mask_sum / (mask_sum - 1) | |
| variance = variance * bessel_correction | |
| return variance | |
| def masked_whiten(values, mask, shift_mean=True): | |
| """ | |
| Whiten `values` by normalizing with mean and variance computed over `mask`. | |
| Args: | |
| values (torch.Tensor): Input tensor. | |
| mask (torch.Tensor): Boolean tensor of same shape, selects elements for stats. | |
| shift_mean (bool): If True (default), output is zero-mean; | |
| if False, the original mean is re-added after scaling. | |
| Returns: | |
| torch.Tensor: Whitened tensor of same shape as `values`. | |
| """ | |
| mean, var = masked_mean(values, mask), masked_var(values, mask) | |
| whitened = (values - mean) * torch.rsqrt(var + 1e-8) | |
| if not shift_mean: | |
| whitened += mean | |
| return whitened | |
| def prefix_metrics(src_metrics: dict, prefix: str, dst_metrics: dict = None) -> dict: | |
| if dst_metrics is None: | |
| dst_metrics = {} | |
| for k, v in src_metrics.items(): | |
| dst_metrics[f"{prefix}/{k}"] = v | |
| return dst_metrics | |