dwehr's picture
Migrate action viewer to local Cosmos generation
9f818c5
Raw
History Blame Contribute Delete
4.37 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: OpenMDW-1.1
import matplotlib.pyplot as plt
import torch
import wandb
from torch.distributed.tensor import DTensor, Partial
from cosmos_framework.callbacks.every_n import EveryN
from cosmos_framework.model._base import ImaginaireModel
from cosmos_framework.trainer import ImaginaireTrainer
from cosmos_framework.utils import distributed
from cosmos_framework.model.vfm.vlm.qwen3_vl_moe.qwen3_vl_moe import Qwen3VLMoeTextSparseMoeBlock
def compute_expert_heatmap(vfm: torch.nn.Module) -> dict[str, torch.Tensor]:
"""
Compute the heatmap for the MoE blocks in the language model.
The heatmap is a dictionary with keys set to ["und", "gen"] and values set to
a tensor of shape (num_layers, num_experts).
Each element of the tensor is the average number of tokens routed to each expert for a
given layer. The sum of the elements in each row should be equal to the average number
of experts per token for the MoE model (config.num_experts_per_tok).
For dense models, the heatmap is an empty dictionary.
"""
with torch.no_grad():
num_layers = len(vfm.language_model.model.layers)
example_dtensor = vfm.language_model.model.layers[0].self_attn.q_proj.weight
if isinstance(example_dtensor, DTensor):
assert hasattr(example_dtensor, "device_mesh")
device_mesh = example_dtensor.device_mesh
else:
device_mesh = None
expert_heatmaps = {}
for tower in ["und", "gen"]:
expert_heatmaps_per_layer = []
for layer_idx in range(num_layers):
layer_module = vfm.language_model.model.layers[layer_idx]
mlp_module = layer_module.mlp if tower == "und" else layer_module.mlp_moe_gen
if isinstance(mlp_module, Qwen3VLMoeTextSparseMoeBlock):
# This is accumulated across all iterations.
total_tokens_per_expert = mlp_module.get_total_tokens_per_expert()
total_tokens = mlp_module.get_total_tokens()
# Compute the average across all ranks.
assert device_mesh is not None, "MoE models require multiple GPUs."
total_tokens_per_expert = DTensor.from_local(
total_tokens_per_expert,
device_mesh=device_mesh,
placements=[Partial()] * device_mesh.ndim,
).full_tensor()
total_tokens = DTensor.from_local(
total_tokens,
device_mesh=device_mesh,
placements=[Partial()] * device_mesh.ndim,
).full_tensor()
mean_tokens_per_expert = total_tokens_per_expert.float() / total_tokens.float() # [num_experts]
expert_heatmaps_per_layer.append(mean_tokens_per_expert)
if len(expert_heatmaps_per_layer) > 0:
expert_heatmaps[tower] = torch.stack(expert_heatmaps_per_layer, dim=0) # [num_layers,num_experts]
return expert_heatmaps
class ExpertHeatmap(EveryN):
"""
Plots the expert heatmap for the MoE blocks in the language model.
Args:
every_n (int): Number of iterations to log the expert heatmap.
"""
def __init__(self, every_n: int = 1000):
super().__init__(every_n=every_n)
def every_n_impl(
self,
trainer: ImaginaireTrainer,
model: ImaginaireModel,
data_batch: dict[str, torch.Tensor],
output_batch: dict[str, torch.Tensor],
loss: torch.Tensor,
iteration: int,
) -> None:
expert_heatmaps = compute_expert_heatmap(model.net)
if distributed.is_rank0() and wandb.run:
for tower, heatmap in expert_heatmaps.items():
fig, ax = plt.subplots()
im = ax.imshow(heatmap.cpu().numpy())
ax.set_xlabel("Experts")
ax.set_ylabel("Layers")
plt.colorbar(im, ax=ax)
wandb.log(
{
f"expert_heatmap/{tower}": fig,
},
step=iteration,
)
plt.close(fig)