"""Helper utilities for transformer models."""
import copy
from typing import Any
import torch
from torch import nn
[docs]
def clone_module(module: nn.Module, n: int) -> nn.ModuleList:
"""
Create n deep copies of a module.
Args:
module: Module to clone
n: Number of copies
Returns:
ModuleList of n independent copies
"""
return nn.ModuleList([copy.deepcopy(module) for _ in range(n)])
[docs]
def count_parameters(model: nn.Module, trainable_only: bool = True) -> int:
"""
Count model parameters.
Args:
model: PyTorch model
trainable_only: If True, count only trainable parameters
Returns:
Total number of parameters
"""
if trainable_only:
return sum(p.numel() for p in model.parameters() if p.requires_grad)
return sum(p.numel() for p in model.parameters())
[docs]
def get_parameter_groups(
model: nn.Module,
weight_decay: float = 0.0,
no_decay_bias: bool = True,
no_decay_norm: bool = True,
) -> list[dict[str, Any]]:
"""
Create parameter groups with different weight decay settings.
Typically, biases and normalization parameters should not have weight decay.
Args:
model: PyTorch model
weight_decay: Weight decay value for regularized parameters
no_decay_bias: If True, exclude bias from weight decay
no_decay_norm: If True, exclude normalization layers from weight decay
Returns:
List of parameter group dicts for optimizer
"""
decay = set()
no_decay = set()
for name, param in model.named_parameters():
if not param.requires_grad:
continue
# Check if this is a bias parameter
if (no_decay_bias and 'bias' in name) or (
no_decay_norm
and any(norm_type in name.lower() for norm_type in ['norm', 'ln', 'bn', 'gn'])
):
no_decay.add(name)
else:
decay.add(name)
# Verify all parameters are assigned
param_dict = dict(model.named_parameters())
assert len(decay) + len(no_decay) == len(param_dict)
return [
{
'params': [param_dict[name] for name in sorted(decay)],
'weight_decay': weight_decay,
},
{
'params': [param_dict[name] for name in sorted(no_decay)],
'weight_decay': 0.0,
},
]
[docs]
def get_device(device: str | torch.device | None = None) -> torch.device:
"""
Get torch device.
Args:
device: Device string or object. If None, auto-detect (cuda if available)
Returns:
torch.device object
"""
if device is None:
device = 'cuda' if torch.cuda.is_available() else 'cpu'
if isinstance(device, str):
return torch.device(device)
return device
[docs]
def move_to_device(batch: Any, device: torch.device) -> Any:
"""
Recursively move batch to device.
Handles dict, list, tuple, and tensor structures.
Args:
batch: Batch data (tensor, dict, list, or tuple)
device: Target device
Returns:
Batch moved to device
"""
if isinstance(batch, torch.Tensor):
return batch.to(device)
elif isinstance(batch, dict):
return {k: move_to_device(v, device) for k, v in batch.items()}
elif isinstance(batch, list):
return [move_to_device(v, device) for v in batch]
elif isinstance(batch, tuple):
return tuple(move_to_device(v, device) for v in batch)
else:
return batch
[docs]
def set_seed(seed: int) -> None:
"""
Set random seed for reproducibility.
Args:
seed: Random seed
"""
import random
import numpy as np
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
# For deterministic behavior (may impact performance)
# torch.backends.cudnn.deterministic = True
# torch.backends.cudnn.benchmark = False