dwehr's picture
Migrate action viewer to local Cosmos generation
9f818c5
Raw
History Blame Contribute Delete
5.99 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: OpenMDW-1.1
from __future__ import annotations
from dataclasses import dataclass
from typing import Tuple
import torch
import torch.distributed as dist
import torch.utils.data
import wandb
from cosmos_framework.model._base import ImaginaireModel
from cosmos_framework.utils import distributed, log
from cosmos_framework.utils.callback import Callback
from cosmos_framework.utils.easy_io import easy_io
@dataclass
class _LossRecord:
loss: float = 0
iter_count: int = 0
name: str = None
def reset(self) -> None:
self.loss = 0
self.iter_count = 0
def get_stat(self, return_valid_mask_sum: bool = False) -> Tuple[float, float]:
if self.iter_count == 0:
self.loss = torch.tensor([float("nan")], device="cuda") # [1]
self.iter_count = 1
msg_str = f"{self.name}: sum_loss={self.loss.item()}/iter_count={self.iter_count}="
avg_loss_tensor = self.loss / self.iter_count
# Create a mask (1 if valid, 0 if NaN or Inf)
valid_mask = torch.tensor([torch.isfinite(avg_loss_tensor).float()], device="cuda") # [1]
msg_str += f"avg_loss={avg_loss_tensor.item()}, valid_mask={valid_mask.item()}, "
# Replace NaN/Inf with 0 to avoid affecting sum
avg_loss_tensor = torch.where(
torch.isfinite(avg_loss_tensor),
avg_loss_tensor,
torch.tensor([0.0], device="cuda"), # [1]
)
# Reduce across all ranks
dist.all_reduce(avg_loss_tensor, op=dist.ReduceOp.SUM) # Sum of valid losses
dist.all_reduce(valid_mask, op=dist.ReduceOp.SUM) # Count of valid losses
msg_str += f" | all_reduce: avg_loss={avg_loss_tensor.item()}, valid_mask={valid_mask.item()}"
# Compute final average, avoiding division by zero
if valid_mask.item() > 0:
final_avg_loss = (avg_loss_tensor / valid_mask).item()
valid_mask_sum = valid_mask.item()
else:
final_avg_loss = 0.0 # Default to zero if all values were invalid
valid_mask_sum = 0
avg_loss = final_avg_loss
msg_str += f" | final: avg_loss={final_avg_loss}"
if self.name is not None:
log.debug(msg_str, rank0_only=False)
self.reset()
if return_valid_mask_sum:
return avg_loss, valid_mask_sum
else:
return avg_loss
class WandbCallback(Callback):
def __init__(
self,
logging_iter_multipler: int = 1,
save_logging_iter_multipler: int = 1,
save_s3: bool = False,
) -> None:
super().__init__()
self.final_loss_log = _LossRecord()
self.final_all_loss_log = {}
self.logging_iter_multipler = logging_iter_multipler
self.save_logging_iter_multipler = save_logging_iter_multipler
assert self.logging_iter_multipler > 0, "logging_iter_multipler should be greater than 0"
self.save_s3 = save_s3
self.wandb_extra_tag = f"@{logging_iter_multipler}" if logging_iter_multipler > 1 else ""
self.name = "wandb_loss_log" + self.wandb_extra_tag
self.unstable_count = torch.zeros(1, device="cuda") # [1]
def on_training_step_end(
self,
model: ImaginaireModel,
data_batch: dict[str, torch.Tensor],
output_batch: dict[str, torch.Tensor],
loss: torch.Tensor,
iteration: int = 0,
) -> None:
if torch.isnan(loss) or torch.isinf(loss):
log.critical(
f"Unstable loss {loss} at iteration {iteration}",
rank0_only=False,
)
self.unstable_count += 1
self.final_loss_log.loss += loss.detach().float()
self.final_loss_log.iter_count += 1
for key in output_batch.keys():
if "loss" in key:
if key not in self.final_all_loss_log:
self.final_all_loss_log[key] = _LossRecord()
self.final_all_loss_log[key].loss += output_batch[key].detach().float()
self.final_all_loss_log[key].iter_count += 1
if iteration % (self.config.trainer.logging_iter * self.logging_iter_multipler) == 0:
avg_final_loss = self.final_loss_log.get_stat()
avg_final_all_loss = {}
for key in self.final_all_loss_log.keys():
avg_final_all_loss[key] = self.final_all_loss_log[key].get_stat()
dist.all_reduce(self.unstable_count, op=dist.ReduceOp.SUM)
if distributed.is_rank0() and wandb.run is not None:
info = {}
info.update(
{
f"train{self.wandb_extra_tag}/loss": avg_final_loss,
f"train{self.wandb_extra_tag}/unstable_count": self.unstable_count.item(),
"iteration": iteration,
}
)
for key, loss in avg_final_all_loss.items():
info.update(
{
f"train{self.wandb_extra_tag}_detail/{key}": loss,
}
)
if self.save_s3:
if (
iteration
% (
self.config.trainer.logging_iter
* self.logging_iter_multipler
* self.save_logging_iter_multipler
)
== 0
):
easy_io.dump(
info,
f"s3://rundir/{self.name}/Train_Iter{iteration:09d}.json",
)
if wandb:
wandb.log(info, step=iteration)
# reset unstable count
self.unstable_count.zero_()