Files
VLA/wall_x/trainer/qwen_vl_act_trainer.py
T

934 lines
35 KiB
Python
Raw Normal View History

2025-09-07 14:59:17 +08:00
import os
import gc
import time
import torch
import random
import numpy as np
import torch.nn as nn
import torch.distributed as dist
2025-09-07 14:59:17 +08:00
from tqdm import tqdm
from functools import wraps
from datetime import datetime
from torch.optim import AdamW
from torch.distributed.tensor import distribute_tensor
2025-09-07 14:59:17 +08:00
from accelerate import Accelerator
from safetensors.torch import load_file
from transformers.optimization import get_cosine_with_min_lr_schedule_with_warmup
2025-10-16 10:53:51 +08:00
from transformers import AutoProcessor
2025-09-07 14:59:17 +08:00
from wall_x.utils.timers import Timers
2025-10-16 10:53:51 +08:00
from wall_x.model.qwen2_5_based import Qwen2_5_VLMoEForAction, Qwen2_5_VLConfig
2025-09-07 14:59:17 +08:00
from wall_x.data.config import ACTION_DATASET_NAMES, MULTIMODAL_DATASET_NAMES
2025-09-11 13:18:33 +08:00
from wall_x.data.load_lerobot_dataset import (
PreprocessedDataset,
get_data_configs,
load_lerobot_data,
)
2025-09-07 14:59:17 +08:00
def timer(func):
"""
Decorator to measure function execution time.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
func: Function to be timed
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Returns:
Wrapped function with timing functionality
"""
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(
f"\033[92m[current time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}] Function {func.__name__} took {end_time - start_time:.2f} seconds to execute\033[0m"
)
return result
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
return wrapper
def print_rank_last(message):
"""
Print message only on the last rank in distributed training.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
message (str): Message to print
"""
if torch.distributed.is_initialized():
if torch.distributed.get_rank() == (torch.distributed.get_world_size() - 1):
print(message, flush=True)
else:
print(message, flush=True)
def seed_all(seed):
"""
Set random seeds for reproducible training.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
seed (int): Random seed value
"""
np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed)
class QwenVlAct_Trainer:
"""
Vision-Language-Action trainer for Qwen-VL models with robotic action prediction.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
This trainer handles multi-modal learning combining vision, language, and action data
for robotic control applications. It supports distributed training, mixed precision,
gradient accumulation, and various optimization strategies including MoE (Mixture of Experts).
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Features:
- Multi-modal data processing (vision + language + actions)
- Distributed training with Accelerate
- Gradient accumulation and clipping
- Learning rate scheduling with warmup
- Checkpoint saving and resuming
- Comprehensive logging and monitoring
"""
@timer
2025-09-11 13:18:33 +08:00
def __init__(
self,
config,
logger,
accelerator: Accelerator = None,
seed=42,
data_config_path=None,
):
2025-09-07 14:59:17 +08:00
"""
Initialize the Vision-Language-Action trainer.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
config (dict): Training configuration dictionary containing:
- processor_path (str): Path to data preprocessing processor
- qwen_vl_act_config_path (str): Path to model configuration file
- learning_rate (float): Base learning rate for training
- num_epoch (int): Number of training epochs
2025-09-09 15:04:00 +08:00
- pretrained_wallx_path (str): Path to pretrained model
2025-09-07 14:59:17 +08:00
- And other training hyperparameters
logger: Logger instance for tracking metrics
accelerator (Accelerator, optional): Hugging Face Accelerate instance for distributed training
seed (int, optional): Random seed for reproducibility. Defaults to 42.
data_config_path (str, optional): Path to data configuration file
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Raises:
ValueError: If required configuration keys are missing
"""
# Validate required configuration keys
2025-09-08 21:38:24 +08:00
required_keys = ["learning_rate", "num_epoch"]
2025-09-07 14:59:17 +08:00
for key in required_keys:
if key not in config:
raise ValueError(f"Missing required configuration key: {key}")
self.config = config
self.logger = logger
self.accelerator = accelerator
self.seed = seed
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Initialize random seeds for reproducibility
seed_all(self.seed)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Training state variables
self.start_epoch = 0
self.global_step = 0
self.num_epoch = self.config["num_epoch"]
self.initial_step = 0
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Data and model configuration
self.dataload_config = get_data_configs(self.config["data"])
self.data_config_path = data_config_path
self.use_fast_tokenizer = self.config.get("use_fast_tokenizer", False)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Load model and initialize training components
self.load_model()
self.action_dim = sum(self.config["dof_config"].values())
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Distributed training setup
self.rank = self.accelerator.process_index
self.world_size = self.accelerator.num_processes
2025-09-11 13:18:33 +08:00
print(
f"rank {self.accelerator.process_index} after load model memory usage: {torch.cuda.memory_allocated() / 1024 ** 3:.2f} GB",
flush=True,
)
2025-09-07 14:59:17 +08:00
# Load training data
self.load_qact_data()
2025-09-11 13:18:33 +08:00
print(
f"rank {self.accelerator.process_index} after load qact data usage: {torch.cuda.memory_allocated() / 1024 ** 3:.2f} GB",
flush=True,
)
2025-09-07 14:59:17 +08:00
# Resume from checkpoint if specified
if "resume" in self.config:
self.resume_from_checkpoint()
# Initialize special token IDs
2025-09-11 13:18:33 +08:00
self.propri_token_id = self.processor.tokenizer.convert_tokens_to_ids(
"<|propri|>"
)
self.action_token_id = self.processor.tokenizer.convert_tokens_to_ids(
"<|action|>"
)
2025-09-07 14:59:17 +08:00
# Initialize evaluation metrics
self.base_l1_loss = None
self.base_l1_loss_detail = {}
# Performance monitoring
self.timers = Timers(log_level=0, log_option="minmax")
# Adjust global step if resuming from checkpoint
if self.initial_step != 0:
2025-09-11 13:18:33 +08:00
self.global_step = self.initial_step // self.config.get(
"gradient_accumulation_steps", 1
)
2025-09-07 14:59:17 +08:00
def print_rank0(self, msg, flush=True):
"""
Print message only on rank 0 (main process).
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
msg: Message to print
flush (bool): Whether to flush output buffer
"""
if self.accelerator.is_main_process:
print(msg, flush=flush)
def fit(self):
"""
Main training loop executing multiple epochs with validation.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Handles the complete training process including:
- Training loop execution
- Validation after each epoch
- Process synchronization
- Memory cleanup
"""
self.accelerator.wait_for_everyone()
# Optional validation before training starts
2025-09-11 13:18:33 +08:00
if self.config.get("resume", None) is not None and self.config["resume"].get(
"validate_first", False
):
2025-09-07 14:59:17 +08:00
self.val_loop()
self.accelerator.wait_for_everyone()
# Main training loop
for epoch in range(self.start_epoch, self.num_epoch):
self.train_loop(epoch)
self.accelerator.wait_for_everyone()
2025-09-11 13:18:33 +08:00
2025-10-16 10:53:51 +08:00
if (epoch + 1) % self.config.get("epoch_save_interval", 1) == 0:
2025-09-08 23:21:03 +08:00
self.save_checkpoint(epoch)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Validation after each epoch
self.val_loop()
self.accelerator.wait_for_everyone()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Memory cleanup
gc.collect()
def train_loop(self, epoch):
"""
Execute training for a single epoch.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
epoch (int): Current epoch number
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Handles:
- Data loading and batching
- Forward/backward passes
- Gradient accumulation and clipping
- Learning rate scheduling
- Loss logging and monitoring
- Performance profiling (optional)
"""
# Initialize training dataloader for current epoch
if isinstance(self.dataset, PreprocessedDataset):
if getattr(self, "train_dataloader", None) is not None:
2025-09-27 12:51:25 +08:00
self.dataset._train()
2025-09-07 14:59:17 +08:00
self.train_sampler.set_epoch(epoch)
else:
2025-09-11 13:18:33 +08:00
self.train_dataloader, self.train_sampler = (
self.dataset.get_train_dataloader()
)
2025-09-07 14:59:17 +08:00
self.train_sampler.set_epoch(epoch)
else:
self.train_dataloader = self.dataset.get_train_dataloader()
self.model.train()
grad_accum_steps = self.config.get("gradient_accumulation_steps", 1)
total = len(self.train_dataloader)
t0 = time.time()
2025-09-11 13:18:33 +08:00
enable_profiling = self.config["profile"]
2025-09-07 14:59:17 +08:00
# Optional PyTorch profiler for performance analysis
if enable_profiling:
profiler = torch.profiler.profile(
2025-09-11 13:18:33 +08:00
activities=[
torch.profiler.ProfilerActivity.CPU,
torch.profiler.ProfilerActivity.CUDA,
],
schedule=torch.profiler.schedule(
wait=self.config["profile_wait_iters"],
warmup=self.config["profile_warmup_iters"],
active=self.config["profile_active_iters"],
),
on_trace_ready=torch.profiler.tensorboard_trace_handler(
self.config["profile_save_path"], worker_name="worker0"
),
2025-09-07 14:59:17 +08:00
record_shapes=True,
profile_memory=True,
with_stack=True,
)
profiler.__enter__()
try:
2025-09-11 13:18:33 +08:00
2025-09-09 15:04:00 +08:00
# Setup timers for First iteration
self.timers("interval-time", log_level=0).start(barrier=False)
self.timers("data-load", log_level=0).start(barrier=False)
2025-09-07 14:59:17 +08:00
for i, batch in enumerate(self.train_dataloader, self.initial_step):
# Move batch to device
if isinstance(self.dataset, PreprocessedDataset):
2025-09-11 13:18:33 +08:00
batch = {
k: (
v.to(self.accelerator.device, non_blocking=True)
if isinstance(v, torch.Tensor)
else v
)
for k, v in batch.items()
}
2025-09-07 14:59:17 +08:00
self.timers("data-load").stop()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
with self.accelerator.accumulate(self.model):
# Forward pass
self.timers("forward-compute", log_level=0).start(barrier=False)
outputs = self.model(**batch, mode="train")
self.timers("forward-compute").stop()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
loss = outputs.loss
# Check for NaN loss
if torch.isnan(loss):
2025-09-11 13:18:33 +08:00
print(
f"Warning: NaN loss detected in epoch: {epoch}, step: {i}",
flush=True,
)
2025-09-07 14:59:17 +08:00
continue
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Backward pass
self.timers("backward-compute", log_level=0).start(barrier=False)
self.accelerator.backward(loss)
self.timers("backward-compute").stop()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Gradient clipping
total_norm = self.accelerator.clip_grad_norm_(
self.model.parameters(), self.config.get("max_grad_norm", 1.0)
)
# Optimizer step
self.timers("optimizer", log_level=0).start(barrier=False)
self.optimizer.step()
self.optimizer.zero_grad()
self.timers("optimizer").stop()
# Update global step and learning rate after gradient accumulation
if (i + 1) % grad_accum_steps == 0:
self.lr_scheduler.step()
self.global_step += 1
lr = self.lr_scheduler.get_last_lr()[0]
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Gather loss across all processes for logging
2025-09-11 13:18:33 +08:00
train_loss = (
self.accelerator.gather(loss.detach()).mean().item()
)
2025-09-07 14:59:17 +08:00
_log_dict = {
"lr": lr,
"train_loss": train_loss,
}
# Log component losses
2025-09-11 13:18:33 +08:00
if (
"cross_entropy_loss" in outputs
and outputs.cross_entropy_loss is not None
):
_log_dict["cross_entropy_loss"] = (
self.accelerator.gather(
outputs.cross_entropy_loss.detach()
)
.mean()
.item()
)
2025-09-07 14:59:17 +08:00
if "flow_loss" in outputs and outputs.flow_loss is not None:
2025-09-11 13:18:33 +08:00
_log_dict["flow_loss"] = (
self.accelerator.gather(outputs.flow_loss.detach())
.mean()
.item()
)
2025-09-07 14:59:17 +08:00
# Log per-dataset channel losses
2025-09-11 13:18:33 +08:00
if (
"channel_loss_dict" in outputs
and outputs.channel_loss_dict is not None
):
for dataset_name_i in (
ACTION_DATASET_NAMES + MULTIMODAL_DATASET_NAMES
):
count_sum = (
self.accelerator.gather(
outputs.channel_loss_count_dict[dataset_name_i]
)
.sum()
.item()
)
2025-09-07 14:59:17 +08:00
if count_sum > 0:
2025-09-11 13:18:33 +08:00
channel_loss = (
self.accelerator.gather(
outputs.channel_loss_dict[
dataset_name_i
].detach()
)
.sum()
.item()
/ count_sum
)
_log_dict[f"channel_loss_{dataset_name_i}"] = (
channel_loss
)
2025-09-07 14:59:17 +08:00
# Log action accuracy for fast tokenizer
2025-09-11 13:18:33 +08:00
if (
"action_accuracy" in outputs.channel_loss_dict
and self.use_fast_tokenizer
):
2025-09-07 14:59:17 +08:00
_log_dict["action_accuracy"] = (
2025-09-11 13:18:33 +08:00
self.accelerator.gather(
outputs.channel_loss_dict[
"action_accuracy"
].detach()
)
.mean()
.item()
2025-09-07 14:59:17 +08:00
)
# Log metrics
if self.logger is not None:
self.logger.log(_log_dict, step=self.global_step)
# Log gradient norm
if self.logger is not None and self.accelerator.sync_gradients:
2025-09-11 13:18:33 +08:00
self.logger.log(
{"total_norm": total_norm}, step=self.global_step
)
2025-09-07 14:59:17 +08:00
self.timers("interval-time").stop()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Setup timers for next iteration
if i < len(self.train_dataloader) - 1:
self.timers("interval-time", log_level=0).start(barrier=False)
self.timers("data-load", log_level=0).start(barrier=False)
# Periodic logging
t1 = time.time()
if i % 1 == 0:
lr = self.lr_scheduler.get_last_lr()[0]
2025-09-11 13:18:33 +08:00
self.training_log(
epoch, self.num_epoch, i, total, loss, lr, t1 - t0
)
2025-09-07 14:59:17 +08:00
t0 = time.time()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
if enable_profiling:
profiler.step()
finally:
if enable_profiling:
profiler.__exit__(None, None, None)
@torch.no_grad()
def val_loop(self):
"""
Execute validation loop with gradient computation disabled.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Evaluates model performance on validation set and logs validation loss.
"""
# Initialize validation dataloader
if getattr(self, "val_dataloader", None) is not None:
2025-09-27 12:51:25 +08:00
self.dataset._eval()
2025-09-07 14:59:17 +08:00
self.val_sampler.set_epoch(0)
else:
self.val_dataloader, self.val_sampler = self.dataset.get_val_dataloader()
self.val_sampler.set_epoch(0)
self.model.eval()
self.val_loss = 0
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Validation loop
for i, batch in enumerate(
2025-09-11 13:18:33 +08:00
tqdm(
self.val_dataloader,
desc="Validating",
total=len(self.val_dataloader),
disable=not self.accelerator.is_main_process,
)
2025-09-07 14:59:17 +08:00
):
if isinstance(self.dataset, PreprocessedDataset):
2025-09-11 13:18:33 +08:00
batch = {
k: (
v.to(self.accelerator.device, non_blocking=True)
if isinstance(v, torch.Tensor)
else v
)
for k, v in batch.items()
}
2025-09-07 14:59:17 +08:00
with torch.no_grad():
outputs = self.model(**batch, mode="train")
loss = outputs.loss
self.val_loss += self.accelerator.gather(loss.detach()).mean().item()
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Calculate average validation loss
self.val_loss /= len(self.val_dataloader)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Log validation metrics
if self.logger is not None:
self.logger.log({"val_loss": self.val_loss}, step=self.global_step)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
self.model.train()
@timer
def load_model(self):
"""
Load and configure the Vision-Language-Action model.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Handles:
- Model loading from pretrained weights
- Processor initialization
- Optimizer configuration (with support for different learning rates for different components)
- Learning rate scheduler setup
- Model preparation for distributed training
"""
# Load pretrained model
2025-10-16 10:53:51 +08:00
model_type = self.config.get("model_type", "qwen2_5")
assert model_type in ["wall-oss", "qwen2_5"]
if model_type == "wall-oss":
model = Qwen2_5_VLMoEForAction.from_pretrained(
self.config["pretrained_wallx_path"],
**{"use_fast_tokenizer": self.use_fast_tokenizer},
)
self.processor = model.processor
model = model.to(torch.bfloat16)
elif model_type == "qwen2_5":
config = Qwen2_5_VLConfig.from_pretrained(
self.config["qwen_vl_act_config_path"]
)
flow_loss_weight = self.config.get("flow_loss_weight", 1.0)
self.processor = AutoProcessor.from_pretrained(
self.config["pretrained_wallx_path"], use_fast=True
)
if self.config.get("use_fast_tokenizer", False):
action_tokenizer_path = self.config["action_tokenizer_path"]
action_tokenizer = AutoProcessor.from_pretrained(
action_tokenizer_path, trust_remote_code=True
)
# process for use fast
new_tokens = ["<|propri|>", "<|action|>"]
new_tokens += [
f"<|action_token_{i}|>" for i in range(action_tokenizer.vocab_size)
]
self.processor.tokenizer.add_tokens(new_tokens)
begin_idx_token = "<|action_token_0|>"
token_id = self.processor.tokenizer.convert_tokens_to_ids(
begin_idx_token
)
self.processor.tokenizer.init_kwargs["action_token_start_index"] = (
token_id
)
self.processor.tokenizer.init_kwargs["action_token_vocab_size"] = (
action_tokenizer.vocab_size
)
self.processor.action_processor = action_tokenizer
model = Qwen2_5_VLMoEForAction(
config,
self.use_fast_tokenizer,
self.processor,
flow_loss_weight=flow_loss_weight,
)
model = model.to(torch.bfloat16)
model = self.load_qwen_pretrain_weight(
model, self.config["pretrained_wallx_path"]
)
model.resize_token_embeddings(len(self.processor.tokenizer))
model = model.to(torch.bfloat16)
else:
raise NotImplementedError(f"Invalid model type: {model_type}")
2025-09-07 14:59:17 +08:00
# Configure optimizer based on training strategy
if "freeze_vlm" in self.config and self.config["freeze_vlm"]:
print("Freezing VLM parameters, training only MoE experts", flush=True)
moe_params = []
for name, param in model.named_parameters():
if "moe.experts.1." not in name:
param.requires_grad = False
else:
moe_params.append(param)
param_groups = [{"params": moe_params, "lr": self.config["learning_rate"]}]
self.optimizer = AdamW(param_groups, weight_decay=0.1)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
elif "action_expert_learning_rate" in self.config:
# Separate learning rates for VLM and action expert parameters
moe_params = []
vlm_params = []
for name, param in model.named_parameters():
if "moe.experts.1." in name:
moe_params.append(param)
else:
vlm_params.append(param)
# Configure parameter groups
if self.config.get("train_action_expert_only", False):
self.print_rank0("Training action expert only", flush=True)
2025-09-11 13:18:33 +08:00
param_groups = [
{
"params": moe_params,
"lr": self.config["action_expert_learning_rate"],
}
]
2025-09-07 14:59:17 +08:00
else:
param_groups = [
{"params": vlm_params, "lr": self.config["learning_rate"]},
2025-09-11 13:18:33 +08:00
{
"params": moe_params,
"lr": self.config["action_expert_learning_rate"],
},
2025-09-07 14:59:17 +08:00
]
self.optimizer = AdamW(param_groups, weight_decay=0.1)
self.print_rank0(
f"Setting MoE learning rate to {self.config['action_expert_learning_rate']}, "
2025-09-11 13:18:33 +08:00
f"VLM learning rate to {self.config['learning_rate']}",
flush=True,
2025-09-07 14:59:17 +08:00
)
else:
# Standard optimizer configuration
self.optimizer = AdamW(
model.parameters(),
lr=self.config["learning_rate"],
weight_decay=0.1,
)
# Configure learning rate scheduler
warmup_steps = self.config.get("num_warmup_steps", 0)
num_training_steps = self.config.get("num_training_steps", 1000000000)
min_lr = self.config.get("min_lr", 0.1 * self.config["learning_rate"])
self.lr_scheduler = get_cosine_with_min_lr_schedule_with_warmup(
optimizer=self.optimizer,
num_warmup_steps=warmup_steps,
num_training_steps=num_training_steps,
min_lr=min_lr,
)
self.model = model
# Enable gradient computation for embeddings
if hasattr(model, "enable_input_require_grads"):
self.model.enable_input_require_grads()
else:
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
def make_inputs_require_grad(module, input, output):
output.requires_grad_(True)
2025-09-11 13:18:33 +08:00
self.model.get_input_embeddings().register_forward_hook(
make_inputs_require_grad
)
2025-09-07 14:59:17 +08:00
# Prepare model, optimizer, and scheduler for distributed training
self.model, self.optimizer, self.lr_scheduler = self.accelerator.prepare(
self.model, self.optimizer, self.lr_scheduler
)
@timer
def load_qact_data(self):
"""
Load and configure training data for Vision-Language-Action learning.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Supports LeRobot dataset format and handles distributed data loading
across multiple processes.
"""
print(f"Loading Vision-Language-Action data from {__file__}")
self.accelerator.wait_for_everyone()
# Load LeRobot dataset
self.dataset, self.train_num = load_lerobot_data(
self.config,
self.dataload_config.get("lerobot_config", {}),
rank=self.rank,
world_size=self.world_size,
)
@timer
def load_qwen_pretrain_weight(self, model, pretrain_weight_path):
"""
Load pretrained Qwen weights with MoE adaptation.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
model: Model instance to load weights into
pretrain_weight_path (str): Path to pretrained weight files
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Returns:
Model with loaded pretrained weights
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Handles weight key renaming for MoE architecture compatibility.
"""
# Load all safetensors files
2025-09-11 13:18:33 +08:00
weight_files = sorted(
[f for f in os.listdir(pretrain_weight_path) if f.endswith(".safetensors")]
)
2025-09-07 14:59:17 +08:00
merged_weights = {}
# Merge weights from all files
for weight_file in weight_files:
file_path = os.path.join(pretrain_weight_path, weight_file)
weights = load_file(file_path)
merged_weights.update(weights)
# Rename weights for MoE compatibility
renamed_weights = {}
for key, value in merged_weights.items():
2025-09-11 13:18:33 +08:00
if (
key.startswith("model.layers")
and "mlp." in key
and model.config.mlp_moe
):
2025-09-07 14:59:17 +08:00
# Rename MLP weights for MoE structure
layer_num = key.split(".layers.")[1].split(".mlp")[0]
2025-09-11 13:18:33 +08:00
new_key = key.replace(
f"layers.{layer_num}.mlp.", f"layers.{layer_num}.moe.experts.0."
)
2025-09-07 14:59:17 +08:00
renamed_weights[new_key] = value
2025-09-11 13:18:33 +08:00
elif (
key.startswith("model.layers")
and "self_attn." in key
and model.config.attention_moe
):
2025-09-07 14:59:17 +08:00
# Rename attention weights for MoE structure
layer_num = key.split(".layers.")[1].split(".self_attn")[0]
proj_types = ["q_proj", "k_proj", "v_proj", "o_proj"]
for proj in proj_types:
if proj in key:
2025-09-11 13:18:33 +08:00
new_key = key.replace(
f"layers.{layer_num}.self_attn.{proj}",
f"layers.{layer_num}.self_attn.{proj}_experts.0",
)
2025-09-07 14:59:17 +08:00
renamed_weights[new_key] = value
break
else:
renamed_weights[key] = value
# Load weights into model
err = model.load_state_dict(renamed_weights, strict=False)
self.print_rank0(f"Weight loading report: {err}", flush=True)
if self.accelerator.is_main_process:
self.print_rank0(f"Loaded pretrained weights from: {pretrain_weight_path}")
return model
2025-09-11 13:18:33 +08:00
def training_log(
self,
current_epoch,
total_epoch,
current_train_iter,
total_train_iter,
loss,
lr,
time_per_step,
):
2025-09-07 14:59:17 +08:00
"""
Log training progress and performance metrics.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
current_epoch (int): Current epoch number
total_epoch (int): Total number of epochs
current_train_iter (int): Current training iteration
total_train_iter (int): Total iterations in epoch
loss (torch.Tensor): Current loss value
lr (float): Current learning rate
time_per_step (float): Time taken for current step
"""
2025-09-11 13:18:33 +08:00
timers_to_log = [
"interval-time",
"data-load",
"forward-compute",
"backward-compute",
"optimizer",
]
2025-09-07 14:59:17 +08:00
log_string = f" [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]"
log_string += " epoch {:3d}/{:3d} |".format(current_epoch, total_epoch)
log_string += " iter {:6d}/{:6d} |".format(current_train_iter, total_train_iter)
log_string += " loss {:.6f} |".format(loss)
log_string += " lr {:.6f} |".format(lr)
log_string += " time_per_step_avg {:.6f}s |".format(time_per_step)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
print_rank_last(log_string)
self.timers.log(timers_to_log, normalizer=1)
def save_checkpoint(self, epoch, step=0):
"""
Save training checkpoint.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
epoch (int): Current epoch number
step (int, optional): Current step number. Defaults to 0.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Saves model state, optimizer state, and training progress information.
"""
save_path = self.config["save_path"]
if step == 0:
ckpt_path = f"{save_path}/{epoch}"
else:
ckpt_path = f"{save_path}/{epoch}_{step}"
2025-09-11 13:18:33 +08:00
2025-10-16 10:53:51 +08:00
# If FSDP SHARDED_STATE_DICT is used, please refer to the wall-x/workspace/README.md
# merge checkpoint section to merge the weights into a single safetensors if needed.
2025-09-07 14:59:17 +08:00
self.accelerator.save_state(ckpt_path)
2025-10-16 10:53:51 +08:00
self.processor.save_pretrained(os.path.join(ckpt_path, "processor"))
2025-09-07 14:59:17 +08:00
# Save current iteration steps for dataset resuming
if step != 0:
_rank = self.accelerator.process_index
if isinstance(self.dataset, PreprocessedDataset):
torch.save(
2025-09-11 13:18:33 +08:00
{"epoch": epoch, "step": step},
os.path.join(
ckpt_path, f"epoch_{epoch}_step_{step}_rank_{_rank}.pth"
),
2025-09-07 14:59:17 +08:00
)
def resume_from_checkpoint(self):
"""
Resume training from a saved checkpoint.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Handles both full checkpoint loading and model-only loading based on configuration.
"""
checkpoint_path = self.config["resume"]["ckpt"]
2025-10-16 10:53:51 +08:00
if self.config.get("resume", {}).get("load_ckpt_only", False):
if self.config.get("FSDP2", False):
self._load_fsdp_state_dict_with_distribute_tensor()
2025-09-11 13:18:33 +08:00
2025-10-16 10:53:51 +08:00
else:
# Load only model weights
ckpt_path = self.config["resume"]["ckpt"] + "/model.safetensors"
state_dict = load_file(ckpt_path, device="cpu")
2025-09-11 13:18:33 +08:00
2025-10-16 10:53:51 +08:00
# Add module prefix if needed for distributed training
new_state_dict = {}
for key in state_dict:
if not key.startswith("module."):
new_key = "module." + key
new_state_dict[new_key] = state_dict[key]
self.model.load_state_dict(new_state_dict, strict=False)
2025-09-07 14:59:17 +08:00
else:
# Load full checkpoint including optimizer and scheduler states
self.accelerator.load_state(checkpoint_path)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
self.print_rank0(f"Resumed from checkpoint: {checkpoint_path}")
def _load_fsdp_state_dict_with_distribute_tensor(self):
rank = dist.get_rank() if dist.is_initialized() else 0
full_sd = load_file(
self.config["resume"]["ckpt"] + "/model.safetensors", device="cpu"
)
meta_sharded_sd = self.model.state_dict()
sharded_sd = {}
def find_matching_key(target_key, available_keys):
if target_key in available_keys:
return target_key
prefixed_key = f"_orig_mod.{target_key}"
if prefixed_key in available_keys:
return prefixed_key
if target_key.startswith("_orig_mod."):
unprefixed_key = target_key[len("_orig_mod.") :]
if unprefixed_key in available_keys:
return unprefixed_key
return None
for param_name, full_tensor in full_sd.items():
matching_key = find_matching_key(param_name, meta_sharded_sd.keys())
if matching_key is None:
if rank == 0:
print(
f"[Rank {rank}] Warning: Parameter not found:",
param_name,
flush=True,
)
continue
sharded_meta_param = meta_sharded_sd[matching_key]
sharded_tensor = distribute_tensor(
full_tensor,
sharded_meta_param.device_mesh,
sharded_meta_param.placements,
)
sharded_sd[matching_key] = nn.Parameter(sharded_tensor)
self.model.load_state_dict(sharded_sd, assign=True, strict=False)
2025-09-07 14:59:17 +08:00
def log_l1_details(self, all_label, all_pred, all_task, all_dof_mask):
"""
Log detailed L1 loss metrics by degrees of freedom.
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Args:
all_label (torch.Tensor): Ground truth action labels
all_pred (torch.Tensor): Predicted actions
all_task (list): Task identifiers
all_dof_mask (torch.Tensor): Degrees of freedom mask
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
Computes and logs L1 loss for each DOF component separately for detailed analysis.
"""
2025-09-11 13:18:33 +08:00
all_task = all_task[: len(all_label)]
2025-09-07 14:59:17 +08:00
# Apply DOF mask
all_label = all_label * all_dof_mask
all_pred = all_pred * all_dof_mask
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
# Compute baseline L1 loss (predict mean action)
if self.base_l1_loss is None:
mean_action = all_label.mean(dim=0)
self.base_l1_loss = nn.functional.l1_loss(all_label, mean_action)
2025-09-11 13:18:33 +08:00
self.logger.log(
{"base_l1_loss": self.base_l1_loss.item()}, step=self.global_step
)
2025-09-07 14:59:17 +08:00
# Log L1 loss for each DOF component
start_idx = 0
dof_config = self.config.get("dof_config", {})
for dof in dof_config:
end_idx = start_idx + dof_config[dof]
dof_label = all_label[:, :, start_idx:end_idx]
dof_pred = all_pred[:, :, start_idx:end_idx]
dof_l1 = nn.functional.l1_loss(dof_pred, dof_label)
2025-09-11 13:18:33 +08:00
2025-09-07 14:59:17 +08:00
self.print_rank0(f"DOF {dof}, L1 loss: {dof_l1.item()}", flush=True)
2025-09-11 13:18:33 +08:00
self.logger.log(
{f"detail/l1_loss_{dof}": dof_l1.item()}, step=self.global_step
)
start_idx = end_idx