From 1c1f917b6e76d775c347f619d327491ac0a66505 Mon Sep 17 00:00:00 2001 From: Masahiro Tanaka Date: Tue, 30 Dec 2025 17:22:38 -0800 Subject: [PATCH 1/2] add TP example Signed-off-by: Masahiro Tanaka --- train_tensor_parallel/README.md | 240 ++++++++ train_tensor_parallel/autotp_strategy.py | 412 ++++++++++++++ train_tensor_parallel/common.py | 527 ++++++++++++++++++ train_tensor_parallel/data.py | 168 ++++++ train_tensor_parallel/ddp_strategy.py | 220 ++++++++ train_tensor_parallel/fsdp_strategy.py | 285 ++++++++++ train_tensor_parallel/job_ddp.yaml | 49 ++ train_tensor_parallel/job_deepspeed.yaml | 54 ++ train_tensor_parallel/job_fsdp.yaml | 52 ++ train_tensor_parallel/loss_curves.png | Bin 0 -> 186835 bytes train_tensor_parallel/model_builder.py | 154 +++++ train_tensor_parallel/plot_loss_curves.py | 111 ++++ .../run_verification_main.sh | 122 ++++ train_tensor_parallel/train_ddp.py | 425 ++++++++++++++ train_tensor_parallel/train_deepspeed.py | 137 +++++ train_tensor_parallel/train_fsdp.py | 120 ++++ 16 files changed, 3076 insertions(+) create mode 100644 train_tensor_parallel/README.md create mode 100644 train_tensor_parallel/autotp_strategy.py create mode 100644 train_tensor_parallel/common.py create mode 100644 train_tensor_parallel/data.py create mode 100644 train_tensor_parallel/ddp_strategy.py create mode 100644 train_tensor_parallel/fsdp_strategy.py create mode 100644 train_tensor_parallel/job_ddp.yaml create mode 100644 train_tensor_parallel/job_deepspeed.yaml create mode 100644 train_tensor_parallel/job_fsdp.yaml create mode 100644 train_tensor_parallel/loss_curves.png create mode 100644 train_tensor_parallel/model_builder.py create mode 100644 train_tensor_parallel/plot_loss_curves.py create mode 100755 train_tensor_parallel/run_verification_main.sh create mode 100644 train_tensor_parallel/train_ddp.py create mode 100644 train_tensor_parallel/train_deepspeed.py create mode 100644 train_tensor_parallel/train_fsdp.py diff --git a/train_tensor_parallel/README.md b/train_tensor_parallel/README.md new file mode 100644 index 0000000..5b4a8cd --- /dev/null +++ b/train_tensor_parallel/README.md @@ -0,0 +1,240 @@ +# Ray Train + Tensor Parallelism + +Training examples combining Ray Train with tensor parallelism using either DeepSpeed AutoTP or PyTorch native FSDP2+DTensor. + +## Features + +- **2D Parallelism**: Combine tensor parallelism (TP) with data parallelism (DP) +- **Ray Train Integration**: Distributed execution, checkpointing, and fault tolerance +- **Two Implementations**: + - `train_deepspeed.py`: DeepSpeed AutoTP with ZeRO optimization + - `train_fsdp.py`: PyTorch native FSDP2 + DTensor + +## Quick Start + +```bash +# DeepSpeed AutoTP: 4 GPUs with 2-way TP, 2-way DP +python train_deepspeed.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 2 \ + --dp_size 2 \ + --num_workers 4 \ + --num_epochs 3 + +# FSDP+DTensor: 4 GPUs with 2-way TP, 2-way DP +python train_fsdp.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 2 \ + --dp_size 2 \ + --num_workers 4 \ + --num_epochs 3 +``` + +## Common Arguments + +| Argument | Description | Default | +|----------|-------------|---------| +| `--model_name` | HuggingFace model name | `Qwen/Qwen2-7B` | +| `--tp_size` | Tensor parallel degree | Required | +| `--dp_size` | Data parallel degree | `1` | +| `--num_workers` | Total workers (must equal tp_size * dp_size) | Required | +| `--batch_size` | Per-GPU micro batch size | `1` | +| `--seq_length` | Maximum sequence length | `2048` | +| `--num_epochs` | Number of training epochs | `3` | +| `--learning_rate` | Learning rate | `1e-5` | +| `--dataset_name` | HuggingFace dataset | `wikitext` | +| `--storage_path` | Checkpoint storage path | `/mnt/cluster_storage` | +| `--resume_from` | Experiment name to resume from | None | + +### DeepSpeed-specific + +| Argument | Description | Default | +|----------|-------------|---------| +| `--zero_stage` | ZeRO optimization stage (0-2) | `1` | + +Note: Autocast is always enabled for bf16/fp16 dtypes in the FSDP path. + +## Anyscale Job Configs + +Pre-configured job files are provided for running on Anyscale: + +```bash +# DeepSpeed AutoTP +anyscale job submit -f job_deepspeed.yaml + +# FSDP + DTensor +anyscale job submit -f job_fsdp.yaml +``` + +### Common Settings + +Both jobs share these configurations: + +| Setting | Value | Description | +|---------|-------|-------------| +| `model_name` | `Qwen/Qwen2.5-0.5B` | Small model for testing | +| `tp_size` | 2 | Tensor parallel degree (must divide num_key_value_heads) | +| `dp_size` | 2 | Data parallel degree | +| `num_workers` | 4 | Total GPUs (tp_size × dp_size) | +| `seq_length` | 1024 | Sequence length | +| `batch_size` | 2 | Per-GPU micro batch size | +| `dataset_percentage` | 1.0 | Use 1% of dataset for quick testing | +| `num_epochs` | 1 | Number of training epochs | +| `log_interval` | 1 | Log every step | +| `image_uri` | `anyscale/ray:2.53.0-slim-py312-cu128` | Ray image with CUDA 12.8 | +| `instance_type` | `g4dn.12xlarge` | 4x T4 GPUs per node | +| `num_nodes` | 1 | Single node | + +### DeepSpeed-specific Settings (`job_deepspeed.yaml`) + +| Setting | Value | Description | +|---------|-------|-------------| +| `zero_stage` | 1 | DeepSpeed ZeRO optimization stage (0-2) | + +Requirements: `torch>=2.9.1`, `deepspeed>=0.18.3`, `transformers>=4.45.0`, `datasets>=3.0.0`, `accelerate>=1.0.0` + +### FSDP-specific Settings (`job_fsdp.yaml`) + +Autocast is always enabled for bf16/fp16 dtypes. + +Requirements: `torch>=2.9.1`, `transformers>=4.45.0`, `datasets>=3.0.0`, `accelerate>=1.0.0` + +### Customizing Job Configs + +Override settings via command line: + +```bash +# Use larger model with more GPUs +anyscale job submit -f job_deepspeed.yaml \ + --entrypoint "python train_deepspeed.py --model_name Qwen/Qwen2-7B --tp_size 4 --dp_size 2 --num_workers 8" +``` + +**Important**: `tp_size` must evenly divide the model's `num_key_value_heads`. For Qwen2.5-0.5B (2 KV heads), valid values are 1 or 2. + +## File Structure + +``` +train_tensor_parallel/ +├── train_deepspeed.py # DeepSpeed AutoTP entry point +├── train_fsdp.py # FSDP+DTensor entry point +├── common.py # Shared utilities (training loop, checkpointing) +├── autotp_strategy.py # DeepSpeed AutoTP strategy +├── fsdp_strategy.py # FSDP2+DTensor strategy +├── model_builder.py # Model creation utilities +├── data.py # TP-aware data loading +├── job_deepspeed.yaml # Anyscale job config for DeepSpeed +└── job_fsdp.yaml # Anyscale job config for FSDP +``` + +## How 2D Parallelism Works + +With `tp_size=2` and `dp_size=2` on 4 GPUs: + +``` +Device Mesh (2x2): + TP Dim + [0] [1] +DP ┌───┬───┐ +Dim │ 0 │ 1 │ ← TP Group 0 (same data, sharded model) + ├───┼───┤ + │ 2 │ 3 │ ← TP Group 1 (same data, sharded model) + └───┴───┘ + ↑ ↑ + DP Groups (different data, gradient sync) +``` + +- **TP Groups** (rows): GPUs 0,1 and GPUs 2,3 share the same input data but have sharded model weights +- **DP Groups** (columns): GPUs 0,2 and GPUs 1,3 see different data and synchronize gradients + +## Key Implementation Details + +### TP-Aware Data Loading + +Standard data loaders shard by `world_rank`, giving each GPU different data. With tensor parallelism, all GPUs in a TP group must see identical data. The `data.py` module handles this by sharding based on `dp_rank` instead: + +```python +# All TP ranks in same DP group get identical batches +sampler = DistributedSampler( + dataset, + num_replicas=dp_size, # NOT world_size + rank=dp_rank, # NOT world_rank +) +``` + +### Checkpointing + +All workers save their model shards independently. Ray Train aggregates these into a single checkpoint that can be used for resuming training. + +## Verification + +This section describes how to verify the correctness of the three distributed training implementations (DDP, DeepSpeed AutoTP, FSDP+DTensor). + +### Overview + +For a fair comparison, all implementations must start from **identical initial weights**. The training scripts support this via: +- `--init_weights_path`: Path to load/save initial model weights +- `--save_init_weights`: Save initial weights before training (DDP only) + +### Prerequisites + +- 4 GPUs (for TP=2, DP=2 configuration) +- Model: `Qwen/Qwen2.5-0.5B` (or any compatible model) +- Python environment with PyTorch, DeepSpeed, and Ray Train installed + +### Quick Start + +```bash +# Create output directories +mkdir -p /tmp/shared_weights /tmp/loss_curves + +# Run all three implementations with shared weights +./run_verification_main.sh +``` + +### Expected Results + +When using shared initial weights, you should see: + +#### Initial Loss +All three implementations should have **nearly identical initial loss** (difference < 0.001): +``` +DDP: Initial=10.8732 +DeepSpeed: Initial=10.8736 +FSDP: Initial=10.8736 +``` + +#### Loss Curve Matching + +| Implementation | Max Diff from DDP | Mean Diff | Notes | +|----------------|-------------------|-----------|-------| +| FSDP+DTensor | < 0.02 | < 0.001 | Almost identical to DDP | +| DeepSpeed AutoTP | < 0.5 | < 0.15 | Slightly different due to gradient clipping | + +#### Example Output +``` +============================================================ +LOSS CURVE STATISTICS +============================================================ + +DDP (TP=1, DP=2): + Initial loss: 10.873175 + Final loss: 4.644855 + Min loss: 0.723590 + Mean loss: 7.341280 + +DeepSpeed AutoTP (TP=2, DP=2): + Initial loss: 10.873634 + Final loss: 4.424264 + Min loss: 0.475180 + Mean loss: 7.223806 + +FSDP+DTensor (TP=2, DP=2): + Initial loss: 10.873634 + Final loss: 4.644554 + Min loss: 0.723624 + Mean loss: 7.341585 +``` + +#### Verification Plot + +![Training Loss Curves](loss_curves.png) diff --git a/train_tensor_parallel/autotp_strategy.py b/train_tensor_parallel/autotp_strategy.py new file mode 100644 index 0000000..826c644 --- /dev/null +++ b/train_tensor_parallel/autotp_strategy.py @@ -0,0 +1,412 @@ +"""DeepSpeed AutoTP tensor parallelism strategy adapted for Ray Train.""" + +import os +from typing import Any, Dict, List, Optional + +import torch +import torch.distributed as dist +import torch.nn as nn + +from model_builder import ( + ModelConfig, + create_model, + get_model_config, +) + + +class RayAutoTPStrategy: + """ + DeepSpeed AutoTP tensor parallelism strategy adapted for Ray Train. + + Uses DeepSpeed's automatic tensor parallelism via: + - set_autotp_mode() for instrumentation + - deepspeed.tp_model_init() for automatic model sharding + - DeepSpeed engine for training with ZeRO optimization + + When dp_size > 1, creates a 2D parallelism configuration: + - TP groups (same row): processes that share tensor-parallel shards + - DP groups (same column): processes that handle different data batches + + For example, with 8 GPUs, dp_size=2, tp_size=4: + - Device arrangement: [[0,1,2,3], [4,5,6,7]] + - TP groups: {0,1,2,3}, {4,5,6,7} + - DP groups: {0,4}, {1,5}, {2,6}, {3,7} + """ + + def __init__(self, tp_size: int, dp_size: int = 1): + self.tp_size = tp_size + self.dp_size = dp_size + self.engine = None + self.optimizer = None + self.model = None + self.tp_group = None + self.dp_group = None + self.tp_rank = None + self.dp_rank = None + self.rank = None + self.world_size = None + self.device = None + self._model_config: Optional[ModelConfig] = None + + def _create_process_groups(self) -> None: + """ + Create TP and DP process groups for 2D parallelism. + + Organizes GPUs into a 2D grid where: + - Rows are TP groups (processes that shard model weights) + - Columns are DP groups (processes that handle different data) + + Example with 8 GPUs (dp_size=2, tp_size=4): + GPU layout: [[0, 1, 2, 3], + [4, 5, 6, 7]] + TP groups: {0,1,2,3}, {4,5,6,7} + DP groups: {0,4}, {1,5}, {2,6}, {3,7} + """ + # Calculate which TP and DP group this rank belongs to + self.tp_rank = self.rank % self.tp_size # Position within TP group + self.dp_rank = self.rank // self.tp_size # Which DP replica + + # Create TP groups (processes in the same row) + # Each TP group contains tp_size consecutive ranks + tp_groups: List[List[int]] = [] + for dp_idx in range(self.dp_size): + tp_group_ranks = list(range(dp_idx * self.tp_size, (dp_idx + 1) * self.tp_size)) + tp_groups.append(tp_group_ranks) + group = dist.new_group(tp_group_ranks) + if self.rank in tp_group_ranks: + self.tp_group = group + + # Create DP groups (processes in the same column) + # Each DP group contains ranks at the same position across TP groups + dp_groups: List[List[int]] = [] + for tp_idx in range(self.tp_size): + dp_group_ranks = [tp_idx + dp_idx * self.tp_size for dp_idx in range(self.dp_size)] + dp_groups.append(dp_group_ranks) + group = dist.new_group(dp_group_ranks) + if self.rank in dp_group_ranks: + self.dp_group = group + + if self.rank == 0: + print(f"[AutoTP] TP groups: {tp_groups}") + print(f"[AutoTP] DP groups: {dp_groups}") + print(f"[AutoTP] Rank {self.rank}: tp_rank={self.tp_rank}, dp_rank={self.dp_rank}") + + def _sync_model_weights(self, model: nn.Module) -> None: + """ + Synchronize all model weights within each TP group. + + This is necessary because each rank creates the model with different + random initializations, but DeepSpeed tp_model_init() expects all + ranks to have identical weights before sharding. + + With 2D parallelism (DP x TP), we have multiple TP groups: + - TP group 0: global ranks [0, 1, ..., tp_size-1] + - TP group 1: global ranks [tp_size, tp_size+1, ..., 2*tp_size-1] + - etc. + + Each TP group broadcasts from its first member (tp_rank=0). + + Args: + model: The model to synchronize + """ + if self.tp_group is None or self.tp_size <= 1: + return + + # The source rank for broadcast is the first rank in this TP group + # With 2D layout: TP group i contains ranks [i*tp_size, (i+1)*tp_size) + # So the first rank in this TP group is dp_rank * tp_size + tp_group_first_rank = self.dp_rank * self.tp_size + + if self.rank == 0: + print(f"[AutoTP] Synchronizing model weights within each TP group...") + + with torch.no_grad(): + for name, param in model.named_parameters(): + dist.broadcast(param.data, src=tp_group_first_rank, group=self.tp_group) + + if self.tp_rank == 0: + print(f"[AutoTP] Model weights synchronized in TP group (src_rank={tp_group_first_rank})") + + def _create_mpu(self) -> "ModelParallelUnit": + """ + Create a Model Parallel Unit (MPU) for DeepSpeed. + + DeepSpeed uses the MPU to understand the parallelism topology and + perform gradient all-reduce only across the data parallel group. + """ + return ModelParallelUnit( + tp_group=self.tp_group, + dp_group=self.dp_group, + tp_size=self.tp_size, + dp_size=self.dp_size, + tp_rank=self.tp_rank, + dp_rank=self.dp_rank, + ) + + def setup( + self, + model_name: str, + device: torch.device, + dtype: torch.dtype, + config: Dict[str, Any], + ) -> None: + """ + Set up DeepSpeed AutoTP with optional data parallelism. + + Args: + model_name: HuggingFace model name + device: Target device + dtype: Target dtype (bfloat16, float16, float32) + config: Training configuration dict + """ + import deepspeed + from deepspeed.module_inject.layers import set_autotp_mode + + self.device = device + self.rank = dist.get_rank() if dist.is_initialized() else 0 + self.world_size = dist.get_world_size() if dist.is_initialized() else 1 + self._model_config = get_model_config(model_name) + + # Validate 2D configuration + if self.dp_size * self.tp_size != self.world_size: + raise ValueError( + f"dp_size ({self.dp_size}) * tp_size ({self.tp_size}) must equal " + f"world_size ({self.world_size})" + ) + + # Validate model configuration + if self._model_config.num_key_value_heads % self.tp_size != 0: + raise ValueError( + f"TP size {self.tp_size} must divide num_key_value_heads " + f"{self._model_config.num_key_value_heads}" + ) + + if self.rank == 0: + print(f"[AutoTP] Setting up with dp_size={self.dp_size}, tp_size={self.tp_size}") + + # Create TP and DP process groups for 2D parallelism + self._create_process_groups() + + # Enable AutoTP instrumentation BEFORE model creation + set_autotp_mode(training=True) + + # Create model on actual device with proper initialization + model = create_model( + model_name=model_name, + device=device, + dtype=dtype, + num_layers=config.get("num_layers", 0), + attn_impl=config.get("attn_impl", "sdpa"), + ) + + # Apply activation checkpointing if requested + if config.get("activation_checkpointing", False): + model.gradient_checkpointing_enable() + if self.rank == 0: + print("[AutoTP] Enabled activation checkpointing") + + # Handle initial weights loading for verification + # This must happen BEFORE weight synchronization so all ranks load the same weights + init_weights_path = config.get("init_weights_path") + if init_weights_path and os.path.exists(init_weights_path): + state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) + model.load_state_dict(state_dict) + if self.rank == 0: + print(f"[AutoTP] Loaded initial weights from {init_weights_path}") + # Skip weight synchronization since all ranks now have identical weights + skip_sync = True + else: + skip_sync = False + + # CRITICAL: Broadcast ALL model weights from rank 0 to all TP ranks before sharding. + # DeepSpeed tp_model_init() shards weights but doesn't synchronize them across ranks. + # Without this, each rank would have different random initializations, leading to + # incorrect model behavior after sharding. + # Skip if we loaded weights from checkpoint (all ranks already have identical weights) + if not skip_sync: + self._sync_model_weights(model) + + # Apply TP sharding with deepspeed.tp_model_init() + # Pass the TP group so DeepSpeed knows which ranks share model shards + model = deepspeed.tp_model_init( + model, + tp_size=self.tp_size, + dtype=dtype, + tp_group=self.tp_group, + ) + + # Get all parameters + params = list(model.parameters()) + + # Build DeepSpeed config + zero_stage = config.get("zero_stage", 1) + batch_size = config.get("batch_size", 1) + gradient_accumulation_steps = config.get("gradient_accumulation_steps", 1) + + # train_batch_size calculation: + # When MPU is provided (tp_size > 1): DeepSpeed uses mpu.get_data_parallel_world_size() + effective_dp = self.dp_size if self.tp_size > 1 else self.world_size + ds_config = { + "train_batch_size": batch_size * effective_dp * gradient_accumulation_steps, + "train_micro_batch_size_per_gpu": batch_size, + "gradient_accumulation_steps": gradient_accumulation_steps, + "gradient_clipping": config.get("max_grad_norm", 1.0), + "zero_optimization": { + "stage": zero_stage, + "overlap_comm": True, + }, + "tensor_parallel": { + "autotp_size": self.tp_size, + }, + "data_parallel_size": self.dp_size, + "zero_allow_untested_optimizer": True, + "steps_per_print": 2000, + "wall_clock_breakdown": False, + } + + # Add precision config + if dtype == torch.bfloat16: + ds_config["bf16"] = { + "enabled": True, + "bf16_master_weights_and_grads": True, + "bf16_optimizer_states": True, + } + # Enable PyTorch native autocast for bfloat16 + ds_config["torch_autocast"] = { + "enabled": True, + "dtype": "bfloat16", + } + elif dtype == torch.float16: + ds_config["fp16"] = {"enabled": True, "initial_scale_power": 8} + # Enable PyTorch native autocast for float16 + ds_config["torch_autocast"] = { + "enabled": True, + "dtype": "float16", + } + + # Create optimizer + learning_rate = config.get("learning_rate", 1e-5) + weight_decay = config.get("weight_decay", 0.01) + optimizer = torch.optim.AdamW(params, lr=learning_rate, weight_decay=weight_decay) + + # Initialize DeepSpeed engine + # Pass the MPU so DeepSpeed knows the parallelism topology + self.engine, self.optimizer, _, _ = deepspeed.initialize( + model=model, + optimizer=optimizer, + config=ds_config, + mpu=self._create_mpu() if self.tp_size > 1 else None, + ) + + self.model = self.engine.module + + if self.rank == 0: + num_params = sum(p.numel() for p in params) + print(f"[AutoTP] Model initialized with {num_params:,} parameters") + print(f"[AutoTP] DeepSpeed engine created with ZeRO-{zero_stage}") + if self.dp_size > 1: + print(f"[AutoTP] 2D parallelism: {self.dp_size} DP x {self.tp_size} TP") + if "torch_autocast" in ds_config: + print(f"[AutoTP] torch.autocast enabled with dtype={ds_config['torch_autocast']['dtype']}") + + def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + """Run forward pass with AutoTP model.""" + outputs = self.engine( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + labels=batch["labels"], + use_cache=False, + ) + return outputs.loss + + def backward(self, loss: torch.Tensor) -> None: + """Run backward pass through DeepSpeed engine.""" + self.engine.backward(loss) + + def optimizer_step(self) -> None: + """Run optimizer step through DeepSpeed engine.""" + self.engine.step() + + def zero_grad(self) -> None: + """Zero gradients - handled internally by DeepSpeed.""" + pass + + def train(self) -> None: + """Set model to training mode.""" + self.engine.train() + + def eval(self) -> None: + """Set model to evaluation mode.""" + self.engine.eval() + + def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Save checkpoint using DeepSpeed. + + Args: + checkpoint_dir: Directory to save checkpoint + tag: Checkpoint tag/name + """ + self.engine.save_checkpoint(checkpoint_dir, tag=tag) + + def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Load checkpoint using DeepSpeed. + + Args: + checkpoint_dir: Directory containing checkpoint + tag: Checkpoint tag/name + """ + self.engine.load_checkpoint(checkpoint_dir, tag=tag) + + +class ModelParallelUnit: + """ + Model Parallel Unit (MPU) interface for DeepSpeed. + + This class implements the interface DeepSpeed expects for understanding + the parallelism topology when using custom TP+DP configurations. + + DeepSpeed calls methods like get_data_parallel_group() to know which + ranks should participate in gradient all-reduce operations. + """ + + def __init__( + self, + tp_group: dist.ProcessGroup, + dp_group: dist.ProcessGroup, + tp_size: int, + dp_size: int, + tp_rank: int, + dp_rank: int, + ): + self._tp_group = tp_group + self._dp_group = dp_group + self._tp_size = tp_size + self._dp_size = dp_size + self._tp_rank = tp_rank + self._dp_rank = dp_rank + + def get_data_parallel_group(self) -> dist.ProcessGroup: + """Return the data parallel process group.""" + return self._dp_group + + def get_model_parallel_group(self) -> dist.ProcessGroup: + """Return the model/tensor parallel process group.""" + return self._tp_group + + def get_data_parallel_world_size(self) -> int: + """Return the number of data parallel ranks.""" + return self._dp_size + + def get_model_parallel_world_size(self) -> int: + """Return the number of model/tensor parallel ranks.""" + return self._tp_size + + def get_data_parallel_rank(self) -> int: + """Return this rank's position in the data parallel group.""" + return self._dp_rank + + def get_model_parallel_rank(self) -> int: + """Return this rank's position in the model/tensor parallel group.""" + return self._tp_rank diff --git a/train_tensor_parallel/common.py b/train_tensor_parallel/common.py new file mode 100644 index 0000000..7e83a05 --- /dev/null +++ b/train_tensor_parallel/common.py @@ -0,0 +1,527 @@ +""" +Common utilities shared between DeepSpeed AutoTP and FSDP+DTensor training scripts. +""" + +import argparse +import json +import logging +import os +import tempfile +import uuid +from typing import Any, Callable, Dict, Protocol + +import torch + +import ray +import ray.train +import ray.train.torch +from ray.train import Checkpoint, RunConfig, ScalingConfig +from ray.train.torch import TorchTrainer + +from data import create_tp_aware_dataloader + +logger = logging.getLogger(__name__) + + +class TPStrategy(Protocol): + """Protocol defining the interface for tensor parallelism strategies.""" + + tp_rank: int + dp_rank: int + tp_size: int + dp_size: int + + def setup(self, model_name: str, device: torch.device, dtype: torch.dtype, config: Dict[str, Any]) -> None: + ... + + def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + ... + + def backward(self, loss: torch.Tensor) -> None: + ... + + def forward_backward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + """Optional: Combined forward+backward for strategies that need it (e.g., DTensor with loss_parallel).""" + ... + + def optimizer_step(self) -> None: + ... + + def zero_grad(self) -> None: + ... + + def train(self) -> None: + ... + + def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + ... + + def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + ... + + +def log_rank0(message: str) -> None: + """Log message only from rank 0.""" + if ray.train.get_context().get_world_rank() == 0: + logger.info(message) + + +def save_checkpoint( + strategy: TPStrategy, + epoch: int, + step: int, + metrics: Dict[str, Any], +) -> None: + """ + Save checkpoint and report to Ray Train. + + All workers save their checkpoint shards, then report to Ray Train. + Ray Train aggregates checkpoints from all workers properly. + + Args: + strategy: The TP strategy containing the model/engine + epoch: Current epoch number + step: Current step number + metrics: Training metrics to report + """ + world_rank = ray.train.get_context().get_world_rank() + + with tempfile.TemporaryDirectory() as tmp_dir: + checkpoint_dir = os.path.join(tmp_dir, "checkpoint") + os.makedirs(checkpoint_dir, exist_ok=True) + + # Save checkpoint (each rank saves its shard) + strategy.save_checkpoint(checkpoint_dir, tag="model") + + # Save metadata (from world rank 0 only) + if world_rank == 0: + metadata = {"epoch": epoch, "step": step} + with open(os.path.join(checkpoint_dir, "metadata.json"), "w") as f: + json.dump(metadata, f) + + # All workers must call report() with their checkpoint + checkpoint = Checkpoint.from_directory(tmp_dir) + ray.train.report(metrics, checkpoint=checkpoint) + + if world_rank == 0: + experiment_name = ray.train.get_context().get_experiment_name() + log_rank0(f"Checkpoint saved for experiment {experiment_name}. Metrics: {metrics}") + + +def load_checkpoint( + strategy: TPStrategy, + checkpoint: ray.train.Checkpoint, +) -> Dict[str, Any]: + """ + Load checkpoint for resuming training. + + Each rank loads its corresponding checkpoint shard. + + Args: + strategy: The TP strategy containing the model/engine + checkpoint: Ray Train checkpoint to load + + Returns: + Metadata dict with epoch and step info + """ + metadata = {"epoch": 0, "step": 0} + + try: + with checkpoint.as_directory() as checkpoint_dir: + log_rank0(f"Loading checkpoint from {checkpoint_dir}") + ckpt_dir = os.path.join(checkpoint_dir, "checkpoint") + if not os.path.isdir(ckpt_dir): + ckpt_dir = checkpoint_dir + + # Load checkpoint (each rank loads its shard) + strategy.load_checkpoint(ckpt_dir, tag="model") + + # Read metadata + metadata_file = os.path.join(ckpt_dir, "metadata.json") + if os.path.isfile(metadata_file): + with open(metadata_file, "r") as f: + metadata = json.load(f) + + # Synchronize across all workers + if torch.distributed.is_available() and torch.distributed.is_initialized(): + torch.distributed.barrier() + + log_rank0(f"Successfully loaded checkpoint. Epoch: {metadata.get('epoch', 0)}") + + except Exception as e: + logger.error(f"Failed to load checkpoint: {e}") + raise RuntimeError(f"Checkpoint loading failed: {e}") from e + + return metadata + + +def run_training_loop( + strategy: TPStrategy, + config: Dict[str, Any], +) -> None: + """ + Run the common training loop for any TP strategy. + + Args: + strategy: The TP strategy (already set up) + config: Training configuration dict + """ + world_rank = ray.train.get_context().get_world_rank() + device = ray.train.torch.get_device() + + # Create TP-aware dataloader + # Uses dp_rank/dp_size for sharding, not world_rank/world_size + dataloader = create_tp_aware_dataloader( + model_name=config["model_name"], + dataset_name=config["dataset_name"], + seq_length=config["seq_length"], + batch_size=config["batch_size"], + dp_rank=strategy.dp_rank, + dp_size=strategy.dp_size, + seed=config.get("seed", 42), + dataset_percentage=config.get("dataset_percentage", 10.0), + ) + + steps_per_epoch = len(dataloader) + log_rank0(f"Dataloader created: {steps_per_epoch} steps per epoch") + + # Load checkpoint if resuming + checkpoint = ray.train.get_checkpoint() + start_epoch = 0 + if checkpoint: + metadata = load_checkpoint(strategy, checkpoint) + start_epoch = metadata.get("epoch", 0) + 1 + log_rank0(f"Resuming training from epoch {start_epoch}") + + # Set model to training mode + strategy.train() + + # Loss history tracking for verification + loss_history = [] + + # Training loop + for epoch in range(start_epoch, config["num_epochs"]): + # Set sampler epoch for different shuffling each epoch + dataloader.sampler.set_epoch(epoch) + + running_loss = 0.0 + num_batches = 0 + + # Check if strategy has combined forward_backward (needed for DTensor with loss_parallel) + use_forward_backward = hasattr(strategy, "forward_backward") and callable( + getattr(strategy, "forward_backward", None) + ) + + for step, batch in enumerate(dataloader): + # Move batch to device + batch = {k: v.to(device) for k, v in batch.items()} + + # Zero gradients (for FSDP, DeepSpeed handles this internally) + strategy.zero_grad() + + if use_forward_backward: + # Combined forward+backward (required for DTensor with loss_parallel) + loss = strategy.forward_backward(batch) + else: + # Separate forward and backward passes + loss = strategy.forward(batch) + strategy.backward(loss) + + # Track per-step loss + loss_value = loss.item() + loss_history.append(loss_value) + + # Log progress + if world_rank == 0 and step % config.get("log_interval", 10) == 0: + log_rank0( + f"Epoch: {epoch} Step: {step + 1}/{steps_per_epoch} Loss: {loss_value:.4f}" + ) + + # Optimizer step + strategy.optimizer_step() + + running_loss += loss_value + num_batches += 1 + + # Debug mode: stop early for testing + if config.get("debug_steps", 0) > 0 and step + 1 >= config["debug_steps"]: + log_rank0(f"Debug steps finished. Stopping epoch {epoch}.") + break + + # Calculate average loss for epoch + avg_loss = running_loss / num_batches if num_batches > 0 else 0.0 + + # Save checkpoint at end of epoch + save_checkpoint( + strategy=strategy, + epoch=epoch, + step=step, + metrics={"loss": avg_loss, "epoch": epoch}, + ) + + log_rank0(f"Epoch {epoch} completed. Average loss: {avg_loss:.4f}") + + # Save loss history if output_dir is specified (rank 0 only) + output_dir = config.get("loss_output_dir") + impl_name = config.get("impl_name", "unknown") + if output_dir and world_rank == 0: + os.makedirs(output_dir, exist_ok=True) + loss_file = os.path.join(output_dir, f"loss_{impl_name}.json") + with open(loss_file, "w") as f: + json.dump({"implementation": impl_name, "loss_history": loss_history}, f) + log_rank0(f"Loss history saved to {loss_file}") + + +def add_common_args(parser: argparse.ArgumentParser) -> None: + """Add common arguments shared by all training scripts.""" + # Model configuration + parser.add_argument( + "--model_name", + type=str, + default="Qwen/Qwen2-7B", + help="HuggingFace model name or path", + ) + parser.add_argument( + "--num_layers", + type=int, + default=0, + help="Override number of layers (0 = use model default)", + ) + parser.add_argument( + "--attn_impl", + type=str, + default="sdpa", + choices=["sdpa", "flash_attention_2", "eager"], + help="Attention implementation", + ) + + # Parallelism configuration + parser.add_argument( + "--tp_size", + type=int, + required=True, + help="Tensor parallel degree", + ) + parser.add_argument( + "--dp_size", + type=int, + default=1, + help="Data parallel degree", + ) + parser.add_argument( + "--num_workers", + type=int, + required=True, + help="Total number of workers (must equal tp_size * dp_size)", + ) + + # Dataset configuration + parser.add_argument( + "--dataset_name", + type=str, + default="wikitext", + help="HuggingFace dataset name", + ) + parser.add_argument( + "--dataset_percentage", + type=float, + default=10.0, + help="Percentage of dataset to use (0-100)", + ) + + # Training configuration + parser.add_argument( + "--batch_size", + type=int, + default=1, + help="Per-GPU micro batch size", + ) + parser.add_argument( + "--seq_length", + type=int, + default=2048, + help="Maximum sequence length", + ) + parser.add_argument( + "--num_epochs", + type=int, + default=3, + help="Number of training epochs", + ) + parser.add_argument( + "--learning_rate", + type=float, + default=1e-5, + help="Learning rate", + ) + parser.add_argument( + "--weight_decay", + type=float, + default=0.01, + help="Weight decay", + ) + parser.add_argument( + "--max_grad_norm", + type=float, + default=1.0, + help="Gradient clipping norm", + ) + parser.add_argument( + "--gradient_accumulation_steps", + type=int, + default=1, + help="Gradient accumulation steps", + ) + parser.add_argument( + "--activation_checkpointing", + action="store_true", + help="Enable activation/gradient checkpointing", + ) + + # Checkpointing configuration + parser.add_argument( + "--storage_path", + type=str, + default="/mnt/cluster_storage", + help="Storage path for checkpoints", + ) + parser.add_argument( + "--experiment_name", + type=str, + default=None, + help="Experiment name (auto-generated if not provided)", + ) + parser.add_argument( + "--resume_from", + type=str, + default=None, + help="Experiment name to resume from", + ) + + # Logging and debugging + parser.add_argument( + "--log_interval", + type=int, + default=10, + help="Logging interval (steps)", + ) + parser.add_argument( + "--debug_steps", + type=int, + default=0, + help="Stop after this many steps per epoch (0 = run full epoch)", + ) + parser.add_argument( + "--seed", + type=int, + default=42, + help="Random seed", + ) + parser.add_argument( + "--loss_output_dir", + type=str, + default=None, + help="Directory to save per-step loss history JSON (for verification)", + ) + parser.add_argument( + "--init_weights_path", + type=str, + default=None, + help="Path to load initial model weights from (for verification across implementations)", + ) + parser.add_argument( + "--save_init_weights", + action="store_true", + help="Save initial model weights to --init_weights_path before training", + ) + + +def get_common_train_config(args: argparse.Namespace) -> Dict[str, Any]: + """Build common train_loop_config from parsed args.""" + return { + # Model + "model_name": args.model_name, + "num_layers": args.num_layers, + "attn_impl": args.attn_impl, + # Parallelism + "tp_size": args.tp_size, + "dp_size": args.dp_size, + # Dataset + "dataset_name": args.dataset_name, + "dataset_percentage": args.dataset_percentage, + # Training + "batch_size": args.batch_size, + "seq_length": args.seq_length, + "num_epochs": args.num_epochs, + "learning_rate": args.learning_rate, + "weight_decay": args.weight_decay, + "max_grad_norm": args.max_grad_norm, + "gradient_accumulation_steps": args.gradient_accumulation_steps, + "activation_checkpointing": args.activation_checkpointing, + # Logging/debugging + "log_interval": args.log_interval, + "debug_steps": args.debug_steps, + "seed": args.seed, + # Loss output for verification + "loss_output_dir": args.loss_output_dir, + # Init weights for verification + "init_weights_path": args.init_weights_path, + "save_init_weights": args.save_init_weights, + } + + +def run_trainer( + args: argparse.Namespace, + train_loop_per_worker: Callable[[Dict[str, Any]], None], + train_loop_config: Dict[str, Any], + experiment_prefix: str, +) -> None: + """ + Common trainer setup and execution. + + Args: + args: Parsed command line arguments + train_loop_per_worker: The training loop function + train_loop_config: Configuration dict for training + experiment_prefix: Prefix for auto-generated experiment names + """ + # Validate parallelism configuration + if args.tp_size * args.dp_size != args.num_workers: + raise ValueError( + f"tp_size ({args.tp_size}) * dp_size ({args.dp_size}) " + f"must equal num_workers ({args.num_workers})" + ) + + print(f"Configuration: {args}") + + # Configure Ray Train + scaling_config = ScalingConfig( + num_workers=args.num_workers, + use_gpu=True, + ) + + # Generate experiment name + name = args.experiment_name + if name is None: + if args.resume_from is not None: + name = args.resume_from + else: + name = f"{experiment_prefix}_tp{args.tp_size}_dp{args.dp_size}_{uuid.uuid4().hex[:8]}" + + print(f"Experiment name: {name}") + + run_config = RunConfig( + storage_path=args.storage_path, + name=name, + ) + + # Create and run trainer + trainer = TorchTrainer( + train_loop_per_worker=train_loop_per_worker, + scaling_config=scaling_config, + train_loop_config=train_loop_config, + run_config=run_config, + ) + + result = trainer.fit() + print(f"Training finished. Result: {result}") diff --git a/train_tensor_parallel/data.py b/train_tensor_parallel/data.py new file mode 100644 index 0000000..d690ff8 --- /dev/null +++ b/train_tensor_parallel/data.py @@ -0,0 +1,168 @@ +"""TP-aware data loading for tensor parallelism training.""" + +from typing import Any + +import torch.distributed as dist +from datasets import DownloadConfig, load_dataset +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler +from transformers import AutoTokenizer + +import ray.train + + +def _get_world_rank() -> int: + """Get world rank, handling both distributed and non-distributed cases.""" + try: + return ray.train.get_context().get_world_rank() + except Exception: + return 0 + + +def _barrier(): + """Synchronize all workers.""" + if dist.is_available() and dist.is_initialized(): + dist.barrier() + + +def get_tokenizer(model_name: str, trust_remote_code: bool = True) -> Any: + """ + Load and configure the tokenizer for the given model. + + Args: + model_name: Name of the model to load tokenizer for + trust_remote_code: Whether to trust remote code + + Returns: + Configured tokenizer + """ + tokenizer = AutoTokenizer.from_pretrained( + model_name, trust_remote_code=trust_remote_code + ) + + # Set pad token if not already set + if tokenizer.pad_token is None: + if tokenizer.eos_token is not None: + tokenizer.pad_token = tokenizer.eos_token + else: + # Fallback for models without eos_token + tokenizer.pad_token = tokenizer.unk_token + + return tokenizer + + +def create_tp_aware_dataloader( + model_name: str, + dataset_name: str, + seq_length: int, + batch_size: int, + dp_rank: int, + dp_size: int, + seed: int = 42, + dataset_percentage: float = 10.0, +) -> DataLoader: + """ + Create dataloader with TP-aware sharding. + + IMPORTANT: This function uses dp_rank and dp_size for data sharding, + NOT world_rank and world_size. This ensures that all TP ranks within + the same DP group see identical batches, which is required for + tensor parallelism to work correctly. + + Do NOT use ray.train.torch.prepare_data_loader() as it uses world_rank + for sharding, which would give different data to each TP rank. + + Args: + model_name: HuggingFace model name for tokenizer + dataset_name: HuggingFace dataset name + seq_length: Maximum sequence length + batch_size: Batch size per worker + dp_rank: Data parallel rank (NOT world rank) + dp_size: Data parallel size (NOT world size) + seed: Random seed for shuffling + dataset_percentage: Percentage of dataset to use (0-100) + + Returns: + DataLoader with TP-aware sharding + """ + world_rank = _get_world_rank() + + # Handle datasets that require a config name + dataset_config = None + if dataset_name == "wikitext": + dataset_config = "wikitext-2-raw-v1" + + split_spec = f"train[:{int(dataset_percentage)}%]" + + # Rank 0 downloads tokenizer and dataset first to avoid file handle conflicts + if world_rank == 0: + tokenizer = get_tokenizer(model_name) + dataset = load_dataset( + dataset_name, + dataset_config, + split=split_spec, + download_config=DownloadConfig(disable_tqdm=True), + ) + + # Wait for rank 0 to finish downloading + _barrier() + + # Other ranks load from cache + if world_rank != 0: + tokenizer = get_tokenizer(model_name) + dataset = load_dataset( + dataset_name, + dataset_config, + split=split_spec, + download_config=DownloadConfig(disable_tqdm=True), + ) + + def tokenize_function(examples): + return tokenizer( + examples["text"], + padding="max_length", + max_length=seq_length, + truncation=True, + ) + + # Tokenize the dataset + tokenized_dataset = dataset.map( + tokenize_function, + batched=True, + num_proc=1, + keep_in_memory=True, + remove_columns=dataset.column_names, + ) + + # Add labels column (same as input_ids for causal LM training) + def add_labels(examples): + examples["labels"] = examples["input_ids"].copy() + return examples + + tokenized_dataset = tokenized_dataset.map( + add_labels, + batched=True, + num_proc=1, + keep_in_memory=True, + ) + + tokenized_dataset.set_format( + type="torch", columns=["input_ids", "attention_mask", "labels"] + ) + + # Create DistributedSampler with DP rank/size (NOT world rank/size) + # This ensures all TP ranks in the same DP group get the same data + sampler = DistributedSampler( + tokenized_dataset, + num_replicas=dp_size, # Use DP size, not world size + rank=dp_rank, # Use DP rank, not world rank + shuffle=True, + seed=seed, + ) + + return DataLoader( + tokenized_dataset, + batch_size=batch_size, + sampler=sampler, + drop_last=True, + ) diff --git a/train_tensor_parallel/ddp_strategy.py b/train_tensor_parallel/ddp_strategy.py new file mode 100644 index 0000000..43b4046 --- /dev/null +++ b/train_tensor_parallel/ddp_strategy.py @@ -0,0 +1,220 @@ +"""DDP (Distributed Data Parallel) strategy for Ray Train as a baseline reference.""" + +import os +from typing import Any, Dict, Optional + +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.nn.parallel import DistributedDataParallel as DDP + +from model_builder import create_model, get_model_config + + +class RayDDPStrategy: + """ + Standard DDP strategy as a baseline reference for comparison. + + This is the simplest distributed training approach: + - Each worker has a full copy of the model + - Data is sharded across workers (data parallelism only) + - Gradients are all-reduced across workers + + No tensor parallelism is used - this serves as a correctness baseline + to verify that the TP implementations produce similar loss curves. + """ + + def __init__(self): + self.model = None + self.ddp_model = None + self.optimizer = None + self.rank = None + self.world_size = None + self.device = None + # For compatibility with TPStrategy interface + self.tp_size = 1 + self.dp_size = None # Will be set to world_size + self.tp_rank = 0 + self.dp_rank = None # Will be set to world_rank + self.use_autocast = False + self.autocast_dtype = None + + def setup( + self, + model_name: str, + device: torch.device, + dtype: torch.dtype, + config: Dict[str, Any], + ) -> None: + """ + Set up DDP training. + + Args: + model_name: HuggingFace model name + device: Target device + dtype: Target dtype (bfloat16, float16, float32) + config: Training configuration dict + """ + self.device = device + self.rank = dist.get_rank() if dist.is_initialized() else 0 + self.world_size = dist.get_world_size() if dist.is_initialized() else 1 + + # For DDP: dp_size = world_size, tp_size = 1 + self.dp_size = self.world_size + self.dp_rank = self.rank + + if self.rank == 0: + print(f"[DDP] Setting up with world_size={self.world_size}") + + # Create model + model = create_model( + model_name=model_name, + device=device, + dtype=dtype, + num_layers=config.get("num_layers", 0), + attn_impl=config.get("attn_impl", "sdpa"), + ) + + # Apply activation checkpointing if requested + if config.get("activation_checkpointing", False): + model.gradient_checkpointing_enable() + if self.rank == 0: + print("[DDP] Enabled activation checkpointing") + + # Handle initial weights loading/saving for verification + init_weights_path = config.get("init_weights_path") + save_init_weights = config.get("save_init_weights", False) + + if init_weights_path: + if save_init_weights: + # Save initial weights (rank 0 only) + if self.rank == 0: + os.makedirs(os.path.dirname(init_weights_path), exist_ok=True) + torch.save(model.state_dict(), init_weights_path) + print(f"[DDP] Saved initial weights to {init_weights_path}") + # Synchronize to ensure file is written before other processes read + if dist.is_available() and dist.is_initialized(): + dist.barrier() + elif os.path.exists(init_weights_path): + # Load initial weights + state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) + model.load_state_dict(state_dict) + if self.rank == 0: + print(f"[DDP] Loaded initial weights from {init_weights_path}") + + # Wrap with DDP + self.model = model + self.ddp_model = DDP( + model, + device_ids=[device.index] if device.type == "cuda" else None, + output_device=device.index if device.type == "cuda" else None, + find_unused_parameters=False, + ) + + # Create optimizer + learning_rate = config.get("learning_rate", 1e-5) + weight_decay = config.get("weight_decay", 0.01) + self.optimizer = torch.optim.AdamW( + self.ddp_model.parameters(), + lr=learning_rate, + weight_decay=weight_decay, + ) + + if self.rank == 0: + num_params = sum(p.numel() for p in model.parameters()) + print(f"[DDP] Model initialized with {num_params:,} parameters") + print(f"[DDP] Data parallel across {self.world_size} workers") + + # Configure autocast + self.use_autocast = config.get("autocast", dtype in (torch.bfloat16, torch.float16)) + if self.use_autocast: + self.autocast_dtype = dtype + if self.rank == 0: + dtype_name = "bfloat16" if dtype == torch.bfloat16 else "float16" + print(f"[DDP] torch.autocast enabled with dtype={dtype_name}") + + def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + """Run forward pass with DDP model.""" + from contextlib import nullcontext + + autocast_ctx = ( + torch.autocast(device_type="cuda", dtype=self.autocast_dtype) + if self.use_autocast + else nullcontext() + ) + + with autocast_ctx: + outputs = self.ddp_model( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + labels=batch["labels"], + use_cache=False, + ) + return outputs.loss + + def backward(self, loss: torch.Tensor) -> None: + """Run backward pass.""" + loss.backward() + + def optimizer_step(self) -> None: + """Run optimizer step.""" + self.optimizer.step() + + def zero_grad(self) -> None: + """Zero gradients.""" + self.optimizer.zero_grad(set_to_none=True) + + def train(self) -> None: + """Set model to training mode.""" + self.ddp_model.train() + + def eval(self) -> None: + """Set model to evaluation mode.""" + self.ddp_model.eval() + + def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Save checkpoint for DDP model. + + For DDP, only rank 0 needs to save since all ranks have identical weights. + """ + if self.rank != 0: + return + + # Create tag directory + tag_dir = os.path.join(checkpoint_dir, tag) + os.makedirs(tag_dir, exist_ok=True) + + # Save model state dict (unwrap DDP module) + model_path = os.path.join(tag_dir, "model.pt") + torch.save(self.model.state_dict(), model_path) + + # Save optimizer state dict + optim_path = os.path.join(tag_dir, "optimizer.pt") + torch.save(self.optimizer.state_dict(), optim_path) + + def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Load checkpoint for DDP model. + + All ranks load from the same checkpoint file. + """ + tag_dir = os.path.join(checkpoint_dir, tag) + + # Load model state dict + model_path = os.path.join(tag_dir, "model.pt") + if os.path.exists(model_path): + self.model.load_state_dict( + torch.load(model_path, map_location=self.device, weights_only=True) + ) + + # Load optimizer state dict + optim_path = os.path.join(tag_dir, "optimizer.pt") + if os.path.exists(optim_path): + self.optimizer.load_state_dict( + torch.load(optim_path, map_location=self.device, weights_only=True) + ) + + # Ensure all ranks are synchronized after loading + if dist.is_available() and dist.is_initialized(): + dist.barrier() diff --git a/train_tensor_parallel/fsdp_strategy.py b/train_tensor_parallel/fsdp_strategy.py new file mode 100644 index 0000000..bdac543 --- /dev/null +++ b/train_tensor_parallel/fsdp_strategy.py @@ -0,0 +1,285 @@ +"""FSDP2 + DTensor tensor parallelism strategy adapted for Ray Train.""" + +import os +from contextlib import nullcontext +from typing import Any, Dict, Optional + +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.distributed.device_mesh import init_device_mesh +from torch.distributed.tensor.parallel import parallelize_module + +from model_builder import ( + create_model, + get_model_config, + get_transformer_layers, +) + + +class RayFSDPStrategy: + """ + FSDP2 + DTensor tensor parallelism strategy adapted for Ray Train. + + Uses a 2D device mesh where: + - First dimension (dp) is for data parallelism via FSDP2 (fully_shard) + - Second dimension (tp) is for tensor parallelism via DTensor + + For example, with 8 GPUs, dp_size=2, tp_size=4: + - Device mesh shape: (2, 4) + - TP groups (same row): {0,1,2,3}, {4,5,6,7} + - DP groups (same column): {0,4}, {1,5}, {2,6}, {3,7} + """ + + def __init__(self, tp_size: int, dp_size: int = 1): + self.tp_size = tp_size + self.dp_size = dp_size + self.model = None + self.optimizer = None + self.device_mesh = None + self.tp_mesh = None + self.dp_mesh = None + self.tp_group = None + self.tp_rank = None + self.dp_rank = None + self.rank = None + self.world_size = None + self.device = None + self.use_autocast = False + self.autocast_dtype = None + + def setup( + self, + model_name: str, + device: torch.device, + dtype: torch.dtype, + config: Dict[str, Any], + ) -> None: + """ + Set up FSDP2 + DTensor with 2D device mesh. + + The mesh is organized as (dp, tp) where: + - dp dimension: FSDP2 shards optimizer states and gradients + - tp dimension: DTensor shards model weights for tensor parallelism + """ + from torch.distributed._composable.fsdp import MixedPrecisionPolicy, fully_shard + from torch.distributed.tensor.parallel import ColwiseParallel, RowwiseParallel + + self.device = device + self.rank = dist.get_rank() if dist.is_initialized() else 0 + self.world_size = dist.get_world_size() if dist.is_initialized() else 1 + + # Calculate TP and DP rank (same layout as AutoTP) + self.tp_rank = self.rank % self.tp_size + self.dp_rank = self.rank // self.tp_size + + # Validate configuration + if self.dp_size * self.tp_size != self.world_size: + raise ValueError( + f"dp_size ({self.dp_size}) * tp_size ({self.tp_size}) must equal " + f"world_size ({self.world_size})" + ) + + model_config = get_model_config(model_name) + if model_config.num_key_value_heads % self.tp_size != 0: + raise ValueError( + f"TP size {self.tp_size} must divide num_key_value_heads " + f"{model_config.num_key_value_heads}" + ) + + if self.rank == 0: + print( + f"[FSDP2+DTensor] Setting up 2D mesh: dp_size={self.dp_size}, tp_size={self.tp_size}" + ) + + # Create 2D device mesh: (dp, tp) + self.device_mesh = init_device_mesh( + "cuda", (self.dp_size, self.tp_size), mesh_dim_names=("dp", "tp") + ) + self.tp_mesh = self.device_mesh["tp"] + self.dp_mesh = self.device_mesh["dp"] + self.tp_group = self.tp_mesh.get_group() + + if self.rank == 0: + print(f"[FSDP2+DTensor] Device mesh created: {self.device_mesh}") + + # Create model + model = create_model( + model_name=model_name, + device=device, + dtype=dtype, + num_layers=config.get("num_layers", 0), + attn_impl=config.get("attn_impl", "sdpa"), + ) + + # Apply activation checkpointing if requested + if config.get("activation_checkpointing", False): + model.gradient_checkpointing_enable() + if self.rank == 0: + print("[FSDP2+DTensor] Enabled activation checkpointing") + + # Handle initial weights loading for verification + # This must happen BEFORE TP/FSDP sharding + init_weights_path = config.get("init_weights_path") + if init_weights_path and os.path.exists(init_weights_path): + state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) + model.load_state_dict(state_dict) + if self.rank == 0: + print(f"[FSDP2+DTensor] Loaded initial weights from {init_weights_path}") + + # Get transformer layers + layers = get_transformer_layers(model) + if layers is None: + raise ValueError("Could not find transformer layers in model") + + # TP mapping for transformer layers (Qwen/Llama-style models) + tp_mapping = { + # Attention projections + "self_attn.q_proj": ColwiseParallel(), + "self_attn.k_proj": ColwiseParallel(), + "self_attn.v_proj": ColwiseParallel(), + "self_attn.o_proj": RowwiseParallel(), + # MLP projections + "mlp.gate_proj": ColwiseParallel(), + "mlp.up_proj": ColwiseParallel(), + "mlp.down_proj": RowwiseParallel(), + } + + if self.rank == 0: + print(f"[FSDP2+DTensor] Applying DTensor TP to {len(layers)} layers") + + # Apply DTensor TP to transformer layers + for layer in layers: + parallelize_module(layer, self.tp_mesh, tp_mapping) + + # Apply FSDP2 (fully_shard) if dp_size > 1 + mp_policy = MixedPrecisionPolicy(param_dtype=dtype, reduce_dtype=dtype) + + if self.dp_size > 1: + if self.rank == 0: + print("[FSDP2+DTensor] Applying FSDP2 to transformer layers") + + for layer in layers: + fully_shard(layer, mesh=self.dp_mesh, mp_policy=mp_policy) + + # Apply to the whole model + fully_shard(model, mesh=self.dp_mesh, mp_policy=mp_policy) + else: + if self.rank == 0: + print("[FSDP2+DTensor] dp_size=1, skipping FSDP sharding (TP only)") + + self.model = model + + # Create optimizer + # Note: Use foreach=False because DTensor doesn't support fused optimizer ops + learning_rate = config.get("learning_rate", 1e-5) + weight_decay = config.get("weight_decay", 0.01) + self.optimizer = torch.optim.AdamW( + self.model.parameters(), + lr=learning_rate, + weight_decay=weight_decay, + foreach=False, # Required for DTensor compatibility + ) + + if self.rank == 0: + num_params = sum(p.numel() for p in model.parameters()) + print(f"[FSDP2+DTensor] Model initialized with {num_params:,} parameters") + if self.dp_size > 1: + print(f"[FSDP2+DTensor] 2D parallelism: {self.dp_size} DP x {self.tp_size} TP") + + # Always enable autocast for mixed precision dtypes (bf16/fp16) + self.use_autocast = dtype in (torch.bfloat16, torch.float16) + if self.use_autocast: + self.autocast_dtype = dtype + if self.rank == 0: + dtype_name = "bfloat16" if dtype == torch.bfloat16 else "float16" + print(f"[FSDP2+DTensor] torch.autocast enabled with dtype={dtype_name}") + + def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + """Run forward pass with FSDP2+DTensor model.""" + autocast_ctx = ( + torch.autocast(device_type="cuda", dtype=self.autocast_dtype) + if self.use_autocast + else nullcontext() + ) + + with autocast_ctx: + outputs = self.model( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + labels=batch["labels"], + use_cache=False, + ) + return outputs.loss + + def backward(self, loss: torch.Tensor) -> None: + """Run backward pass.""" + loss.backward() + + def forward_backward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: + """ + Run forward and backward pass together. + + Returns: + The loss value (for logging) + """ + loss = self.forward(batch) + loss.backward() + return loss + + def optimizer_step(self) -> None: + """Run optimizer step.""" + self.optimizer.step() + + def zero_grad(self) -> None: + """Zero gradients.""" + self.optimizer.zero_grad(set_to_none=True) + + def train(self) -> None: + """Set model to training mode.""" + self.model.train() + + def eval(self) -> None: + """Set model to evaluation mode.""" + self.model.eval() + + def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Save checkpoint for FSDP2+DTensor model. + + For FSDP2, we use torch.save on the model state dict. + Each rank saves its own shard. + """ + import os + + # Create tag directory + tag_dir = os.path.join(checkpoint_dir, tag) + os.makedirs(tag_dir, exist_ok=True) + + # Save model state dict (each rank saves its shard) + model_path = os.path.join(tag_dir, f"model_rank{self.rank}.pt") + torch.save(self.model.state_dict(), model_path) + + # Save optimizer state dict + optim_path = os.path.join(tag_dir, f"optimizer_rank{self.rank}.pt") + torch.save(self.optimizer.state_dict(), optim_path) + + def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: + """ + Load checkpoint for FSDP2+DTensor model. + + Each rank loads its corresponding shard. + """ + import os + + tag_dir = os.path.join(checkpoint_dir, tag) + + # Load model state dict + model_path = os.path.join(tag_dir, f"model_rank{self.rank}.pt") + if os.path.exists(model_path): + self.model.load_state_dict(torch.load(model_path, weights_only=True)) + + # Load optimizer state dict + optim_path = os.path.join(tag_dir, f"optimizer_rank{self.rank}.pt") + if os.path.exists(optim_path): + self.optimizer.load_state_dict(torch.load(optim_path, weights_only=True)) diff --git a/train_tensor_parallel/job_ddp.yaml b/train_tensor_parallel/job_ddp.yaml new file mode 100644 index 0000000..0d7f68e --- /dev/null +++ b/train_tensor_parallel/job_ddp.yaml @@ -0,0 +1,49 @@ +# Anyscale Job: Ray Train + DDP (Baseline) +# This job runs DDP training as a baseline and saves initial weights for verification. +# Run this FIRST before DeepSpeed/FSDP jobs to create shared initial weights. +# +# Submit with: anyscale job submit -f job_ddp.yaml + +name: train-ddp-baseline + +entrypoint: > + python train_ddp.py + --model_name Qwen/Qwen2.5-0.5B + --num_workers 2 + --dataset_name wikitext + --dataset_percentage 1.0 + --batch_size 2 + --seq_length 1024 + --num_epochs 1 + --learning_rate 1e-5 + --log_interval 1 + --debug_steps 100 + +image_uri: anyscale/ray:2.53.0-py312-cu128 + +working_dir: . + +requirements: + - torch>=2.9.1 + - transformers>=4.45.0 + - datasets>=3.0.0 + - accelerate>=1.0.0 + +compute_config: + head_node: + instance_type: m5.xlarge + worker_nodes: + - instance_type: g4dn.12xlarge + min_nodes: 1 + max_nodes: 1 + +env_vars: + RAY_TRAIN_V2_ENABLED: "1" + HF_HOME: /mnt/cluster_storage/huggingface + +max_retries: 0 + +tags: + project: tensor-parallelism + framework: ddp + role: baseline diff --git a/train_tensor_parallel/job_deepspeed.yaml b/train_tensor_parallel/job_deepspeed.yaml new file mode 100644 index 0000000..fbbc60e --- /dev/null +++ b/train_tensor_parallel/job_deepspeed.yaml @@ -0,0 +1,54 @@ +# Anyscale Job: Ray Train + DeepSpeed AutoTP Tensor Parallelism +# This job runs training with DeepSpeed AutoTP for tensor parallelism +# +# For verification, run job_ddp.yaml FIRST to create shared initial weights. +# +# Submit with: anyscale job submit -f job_deepspeed.yaml +# Or with custom args: anyscale job submit -f job_deepspeed.yaml --entrypoint "python train_deepspeed.py --tp_size 4 --dp_size 2 --num_workers 8" + +name: train-deepspeed-autotp + +entrypoint: > + python train_deepspeed.py + --model_name Qwen/Qwen2.5-0.5B + --tp_size 2 + --dp_size 2 + --num_workers 4 + --dataset_name wikitext + --dataset_percentage 1.0 + --batch_size 2 + --seq_length 1024 + --num_epochs 1 + --learning_rate 1e-5 + --zero_stage 1 + --log_interval 1 + --debug_steps 100 + +image_uri: anyscale/ray:2.53.0-py312-cu128 + +working_dir: . + +requirements: + - torch>=2.9.1 + - deepspeed>=0.18.3 + - transformers>=4.45.0 + - datasets>=3.0.0 + - accelerate>=1.0.0 + +compute_config: + head_node: + instance_type: m5.xlarge + worker_nodes: + - instance_type: g4dn.12xlarge + min_nodes: 1 + max_nodes: 1 + +env_vars: + RAY_TRAIN_V2_ENABLED: "1" + HF_HOME: /mnt/cluster_storage/huggingface + +max_retries: 0 + +tags: + project: tensor-parallelism + framework: deepspeed diff --git a/train_tensor_parallel/job_fsdp.yaml b/train_tensor_parallel/job_fsdp.yaml new file mode 100644 index 0000000..4348257 --- /dev/null +++ b/train_tensor_parallel/job_fsdp.yaml @@ -0,0 +1,52 @@ +# Anyscale Job: Ray Train + FSDP2 + DTensor Tensor Parallelism +# This job runs training with PyTorch native FSDP2 + DTensor for tensor parallelism +# +# For verification, run job_ddp.yaml FIRST to create shared initial weights. +# +# Submit with: anyscale job submit -f job_fsdp.yaml +# Or with custom args: anyscale job submit -f job_fsdp.yaml --entrypoint "python train_fsdp.py --tp_size 4 --dp_size 2 --num_workers 8" + +name: train-fsdp-dtensor + +entrypoint: > + python train_fsdp.py + --model_name Qwen/Qwen2.5-0.5B + --tp_size 2 + --dp_size 2 + --num_workers 4 + --dataset_name wikitext + --dataset_percentage 1.0 + --batch_size 2 + --seq_length 1024 + --num_epochs 1 + --learning_rate 1e-5 + --log_interval 1 + --debug_steps 100 + +image_uri: anyscale/ray:2.53.0-py312-cu128 + +working_dir: . + +requirements: + - torch>=2.9.1 + - transformers>=4.45.0 + - datasets>=3.0.0 + - accelerate>=1.0.0 + +compute_config: + head_node: + instance_type: m5.xlarge + worker_nodes: + - instance_type: g4dn.12xlarge + min_nodes: 1 + max_nodes: 1 + +env_vars: + RAY_TRAIN_V2_ENABLED: "1" + HF_HOME: /mnt/cluster_storage/huggingface + +max_retries: 0 + +tags: + project: tensor-parallelism + framework: fsdp diff --git a/train_tensor_parallel/loss_curves.png b/train_tensor_parallel/loss_curves.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d6fd57f39852ab8ec1249c31608e7388acae9b GIT binary patch literal 186835 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�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 literal 0 HcmV?d00001 diff --git a/train_tensor_parallel/model_builder.py b/train_tensor_parallel/model_builder.py new file mode 100644 index 0000000..3e9fc50 --- /dev/null +++ b/train_tensor_parallel/model_builder.py @@ -0,0 +1,154 @@ +"""Simplified model builder for tensor parallelism training.""" + +from contextlib import contextmanager +from dataclasses import dataclass + +import torch +import torch.nn as nn +from transformers import AutoConfig, AutoModelForCausalLM + + +@dataclass +class ModelConfig: + """Configuration dataclass for model specifications.""" + + name: str + hidden_size: int + num_attention_heads: int + num_key_value_heads: int + num_hidden_layers: int + intermediate_size: int + vocab_size: int + + +@contextmanager +def use_default_device(device): + """Context manager to temporarily set default device.""" + prev_device = torch.get_default_device() + torch.set_default_device(device) + try: + yield + finally: + torch.set_default_device(prev_device) + + +def get_model_config(model_name: str) -> ModelConfig: + """ + Get model configuration from HuggingFace. + + Args: + model_name: HuggingFace model name or local path + + Returns: + ModelConfig with model specifications + """ + hf_config = AutoConfig.from_pretrained(model_name, trust_remote_code=True) + + # Handle models with nested text_config (e.g., Qwen2.5-VL) + if hasattr(hf_config, "text_config"): + text_config = hf_config.text_config + else: + text_config = hf_config + + # Get hidden_size (GPT-2 uses n_embd) + hidden_size = getattr(text_config, "hidden_size", None) + if hidden_size is None: + hidden_size = getattr(text_config, "n_embd", 768) + + # Get num_attention_heads (GPT-2 uses n_head) + num_attention_heads = getattr(text_config, "num_attention_heads", None) + if num_attention_heads is None: + num_attention_heads = getattr(text_config, "n_head", 12) + + # Get num_hidden_layers (GPT-2 uses n_layer) + num_hidden_layers = getattr(text_config, "num_hidden_layers", None) + if num_hidden_layers is None: + num_hidden_layers = getattr(text_config, "n_layer", 12) + + # Get intermediate_size (GPT-2 uses n_inner, defaults to 4 * hidden_size) + intermediate_size = getattr(text_config, "intermediate_size", None) + if intermediate_size is None: + intermediate_size = getattr(text_config, "n_inner", None) + if intermediate_size is None: + intermediate_size = 4 * hidden_size + + return ModelConfig( + name=model_name, + hidden_size=hidden_size, + num_attention_heads=num_attention_heads, + num_key_value_heads=getattr( + text_config, "num_key_value_heads", num_attention_heads + ), + num_hidden_layers=num_hidden_layers, + intermediate_size=intermediate_size, + vocab_size=text_config.vocab_size, + ) + + +def create_model( + model_name: str, + device: torch.device, + dtype: torch.dtype, + num_layers: int = 0, + attn_impl: str = "sdpa", +) -> nn.Module: + """ + Create model with random initialization. + + Args: + model_name: HuggingFace model name or local path + device: Device to place the model on + dtype: Data type for model parameters + num_layers: Override number of layers (0 = use model default) + attn_impl: Attention implementation to use + + Returns: + Initialized model + """ + hf_config = AutoConfig.from_pretrained( + model_name, + trust_remote_code=True, + attn_implementation=attn_impl, + ) + + # Handle models with nested text_config (e.g., Qwen2.5-VL) + if hasattr(hf_config, "text_config"): + config_to_modify = hf_config.text_config + else: + config_to_modify = hf_config + + # Override number of layers if specified + if num_layers > 0: + original_layers = config_to_modify.num_hidden_layers + config_to_modify.num_hidden_layers = num_layers + print(f"Overriding num_hidden_layers: {original_layers} -> {num_layers}") + + # Create model with random initialization + with use_default_device(device): + model = AutoModelForCausalLM.from_config(hf_config, trust_remote_code=True) + + # Move to target dtype + model = model.to(dtype=dtype) + + return model + + +def get_transformer_layers(model: nn.Module): + """ + Get the list of transformer layers from the model. + + Args: + model: The model to get layers from + + Returns: + List or ModuleList of transformer layers, or None if not found + """ + # Qwen/Llama/Mistral structure: model.model.layers + if hasattr(model, "model") and hasattr(model.model, "layers"): + return model.model.layers + + # GPT-2 structure: model.transformer.h + if hasattr(model, "transformer") and hasattr(model.transformer, "h"): + return model.transformer.h + + return None diff --git a/train_tensor_parallel/plot_loss_curves.py b/train_tensor_parallel/plot_loss_curves.py new file mode 100644 index 0000000..ade68cc --- /dev/null +++ b/train_tensor_parallel/plot_loss_curves.py @@ -0,0 +1,111 @@ +""" +Plot loss curves from training runs. + +Usage: + python plot_loss_curves.py --input_dir /tmp/loss_curves --output loss_curves.png +""" + +import argparse +import json +import os + +import matplotlib.pyplot as plt +import numpy as np + + +def load_loss_history(filepath: str) -> dict: + """Load loss history from JSON file.""" + with open(filepath, "r") as f: + return json.load(f) + + +def plot_loss_curves(input_dir: str, output_path: str): + """Plot loss curves for all implementations.""" + # Load all loss histories + implementations = {} + + files = { + "DDP (TP=1, DP=2)": "loss_ddp.json", + "DeepSpeed AutoTP (TP=2, DP=2)": "loss_deepspeed.json", + "FSDP+DTensor (TP=2, DP=2)": "loss_fsdp.json", + } + + colors = { + "DDP (TP=1, DP=2)": "#1f77b4", # blue + "DeepSpeed AutoTP (TP=2, DP=2)": "#ff7f0e", # orange + "FSDP+DTensor (TP=2, DP=2)": "#2ca02c", # green + } + + for name, filename in files.items(): + filepath = os.path.join(input_dir, filename) + if os.path.exists(filepath): + data = load_loss_history(filepath) + implementations[name] = data["loss_history"] + print(f"Loaded {name}: {len(data['loss_history'])} steps") + else: + print(f"Warning: {filepath} not found") + + if not implementations: + print("No loss histories found!") + return + + # Create figure + fig, ax = plt.subplots(figsize=(10, 6)) + + # Plot loss curves + for name, losses in implementations.items(): + steps = list(range(len(losses))) + ax.plot(steps, losses, label=name, color=colors[name], linewidth=1.5) + + ax.set_xlabel("Step", fontsize=12) + ax.set_ylabel("Loss", fontsize=12) + ax.set_title("Training Loss Curves", fontsize=14) + ax.legend(loc="upper right", fontsize=10) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig(output_path, dpi=150, bbox_inches="tight") + print(f"\nSaved plot to {output_path}") + + # Print statistics + print("\n" + "=" * 60) + print("LOSS CURVE STATISTICS") + print("=" * 60) + + for name, losses in implementations.items(): + losses_arr = np.array(losses) + print(f"\n{name}:") + print(f" Initial loss: {losses_arr[0]:.6f}") + print(f" Final loss: {losses_arr[-1]:.6f}") + print(f" Min loss: {losses_arr.min():.6f}") + print(f" Mean loss: {losses_arr.mean():.6f}") + + if "DDP (TP=1, DP=2)" in implementations: + ddp_losses = np.array(implementations["DDP (TP=1, DP=2)"]) + print("\n" + "-" * 60) + print("Differences from DDP baseline:") + for name, losses in implementations.items(): + if name == "DDP (TP=1, DP=2)": + continue + losses_arr = np.array(losses) + min_len = min(len(ddp_losses), len(losses_arr)) + diff = losses_arr[:min_len] - ddp_losses[:min_len] + print(f"\n{name}:") + print(f" Max abs diff: {np.abs(diff).max():.6f}") + print(f" Mean abs diff: {np.abs(diff).mean():.6f}") + print(f" Final diff: {diff[-1]:.6f}") + + plt.show() + + +def main(): + parser = argparse.ArgumentParser(description="Plot loss curves") + parser.add_argument("--input_dir", type=str, default="/tmp/loss_curves") + parser.add_argument("--output", type=str, default="loss_curves.png") + + args = parser.parse_args() + plot_loss_curves(args.input_dir, args.output) + + +if __name__ == "__main__": + main() diff --git a/train_tensor_parallel/run_verification_main.sh b/train_tensor_parallel/run_verification_main.sh new file mode 100755 index 0000000..b6b64e2 --- /dev/null +++ b/train_tensor_parallel/run_verification_main.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# Verification script using main training scripts +# This script runs all three implementations with shared initial weights +# and generates a comparison plot. + +set -e + +# Configuration +MODEL_NAME="${MODEL_NAME:-Qwen/Qwen2.5-0.5B}" +NUM_LAYERS="${NUM_LAYERS:-2}" +BATCH_SIZE="${BATCH_SIZE:-2}" +SEQ_LENGTH="${SEQ_LENGTH:-1024}" +LEARNING_RATE="${LEARNING_RATE:-1e-5}" +DEBUG_STEPS="${DEBUG_STEPS:-100}" +LOG_INTERVAL="${LOG_INTERVAL:-10}" + +# Output directories +OUTPUT_DIR="${OUTPUT_DIR:-/tmp/loss_curves_verify}" +WEIGHTS_DIR="${WEIGHTS_DIR:-/tmp/shared_weights_verify}" +CHECKPOINT_DIR="${CHECKPOINT_DIR:-/tmp/ray_checkpoints_verify}" + +# Create directories +mkdir -p "$OUTPUT_DIR" "$WEIGHTS_DIR" + +echo "============================================================" +echo "Verification Using Main Training Scripts" +echo "============================================================" +echo "Model: $MODEL_NAME" +echo "Layers: $NUM_LAYERS" +echo "Batch size: $BATCH_SIZE" +echo "Sequence length: $SEQ_LENGTH" +echo "Learning rate: $LEARNING_RATE" +echo "Debug steps: $DEBUG_STEPS" +echo "Output dir: $OUTPUT_DIR" +echo "============================================================" + +# Step 1: Run DDP (baseline) and save initial weights +echo "" +echo "[Step 1/4] Running DDP (baseline) and saving initial weights..." +echo "------------------------------------------------------------" +python train_ddp.py \ + --model_name "$MODEL_NAME" \ + --num_workers 2 \ + --batch_size "$BATCH_SIZE" \ + --seq_length "$SEQ_LENGTH" \ + --num_layers "$NUM_LAYERS" \ + --num_epochs 1 \ + --learning_rate "$LEARNING_RATE" \ + --debug_steps "$DEBUG_STEPS" \ + --log_interval "$LOG_INTERVAL" \ + --loss_output_dir "$OUTPUT_DIR" \ + --storage_path "$CHECKPOINT_DIR" \ + --init_weights_path "$WEIGHTS_DIR/init_weights.pt" \ + --save_init_weights + +echo "" +echo "DDP completed. Initial weights saved to $WEIGHTS_DIR/init_weights.pt" + +# Step 2: Run DeepSpeed AutoTP with same weights +echo "" +echo "[Step 2/4] Running DeepSpeed AutoTP with shared weights..." +echo "------------------------------------------------------------" +python train_deepspeed.py \ + --model_name "$MODEL_NAME" \ + --tp_size 2 \ + --dp_size 2 \ + --num_workers 4 \ + --batch_size "$BATCH_SIZE" \ + --seq_length "$SEQ_LENGTH" \ + --num_layers "$NUM_LAYERS" \ + --num_epochs 1 \ + --learning_rate "$LEARNING_RATE" \ + --debug_steps "$DEBUG_STEPS" \ + --log_interval "$LOG_INTERVAL" \ + --loss_output_dir "$OUTPUT_DIR" \ + --storage_path "$CHECKPOINT_DIR" \ + --init_weights_path "$WEIGHTS_DIR/init_weights.pt" + +echo "" +echo "DeepSpeed AutoTP completed." + +# Step 3: Run FSDP+DTensor with same weights +echo "" +echo "[Step 3/4] Running FSDP+DTensor with shared weights..." +echo "------------------------------------------------------------" +python train_fsdp.py \ + --model_name "$MODEL_NAME" \ + --tp_size 2 \ + --dp_size 2 \ + --num_workers 4 \ + --batch_size "$BATCH_SIZE" \ + --seq_length "$SEQ_LENGTH" \ + --num_layers "$NUM_LAYERS" \ + --num_epochs 1 \ + --learning_rate "$LEARNING_RATE" \ + --debug_steps "$DEBUG_STEPS" \ + --log_interval "$LOG_INTERVAL" \ + --loss_output_dir "$OUTPUT_DIR" \ + --storage_path "$CHECKPOINT_DIR" \ + --init_weights_path "$WEIGHTS_DIR/init_weights.pt" \ + --autocast + +echo "" +echo "FSDP+DTensor completed." + +# Step 4: Plot and compare loss curves +echo "" +echo "[Step 4/4] Plotting loss curves..." +echo "------------------------------------------------------------" +python plot_loss_curves.py \ + --input_dir "$OUTPUT_DIR" \ + --output "$OUTPUT_DIR/loss_curves.png" + +echo "" +echo "============================================================" +echo "Verification Complete!" +echo "============================================================" +echo "Loss history files:" +ls -la "$OUTPUT_DIR"/*.json +echo "" +echo "Plot saved to: $OUTPUT_DIR/loss_curves.png" +echo "============================================================" diff --git a/train_tensor_parallel/train_ddp.py b/train_tensor_parallel/train_ddp.py new file mode 100644 index 0000000..6494319 --- /dev/null +++ b/train_tensor_parallel/train_ddp.py @@ -0,0 +1,425 @@ +""" +Ray Train + DDP (Distributed Data Parallel) Training. + +This script serves as a baseline reference to verify the correctness of +tensor parallelism implementations. It uses standard PyTorch DDP for +data parallelism only (no tensor parallelism). + +Example usage: + # 4 GPUs: 4-way data parallelism + python train_ddp.py \ + --model_name Qwen/Qwen2-7B \ + --num_workers 4 \ + --dataset_name wikitext \ + --batch_size 2 \ + --seq_length 2048 \ + --num_epochs 3 + + # 8 GPUs: 8-way data parallelism + python train_ddp.py \ + --model_name Qwen/Qwen2-7B \ + --num_workers 8 \ + --dataset_name wikitext \ + --batch_size 1 \ + --seq_length 2048 \ + --num_epochs 3 +""" + +import argparse +import os +import uuid +from typing import Any, Dict + +os.environ["RAY_TRAIN_V2_ENABLED"] = "1" + +import torch + +import ray.train +import ray.train.torch +from ray.train import RunConfig, ScalingConfig +from ray.train.torch import TorchTrainer + +from common import ( + log_rank0, + save_checkpoint, + load_checkpoint, +) +from data import create_tp_aware_dataloader +from ddp_strategy import RayDDPStrategy + + +def train_loop_per_worker(config: Dict[str, Any]) -> None: + """ + Main training loop executed by each Ray Train worker. + + Args: + config: Training configuration dict + """ + # Get Ray Train context + ctx = ray.train.get_context() + world_rank = ctx.get_world_rank() + world_size = ctx.get_world_size() + device = ray.train.torch.get_device() + + log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") + + # Create and setup the DDP strategy + strategy = RayDDPStrategy() + + strategy.setup( + model_name=config["model_name"], + device=device, + dtype=torch.bfloat16, + config={ + "learning_rate": config["learning_rate"], + "weight_decay": config.get("weight_decay", 0.01), + "num_layers": config.get("num_layers", 0), + "attn_impl": config.get("attn_impl", "sdpa"), + "activation_checkpointing": config.get("activation_checkpointing", False), + "autocast": config.get("autocast", True), + "init_weights_path": config.get("init_weights_path"), + "save_init_weights": config.get("save_init_weights", False), + }, + ) + + # Run training loop + run_ddp_training_loop(strategy, config) + + +def run_ddp_training_loop( + strategy: RayDDPStrategy, + config: Dict[str, Any], +) -> None: + """ + Run the training loop for DDP. + + This is similar to run_training_loop in common.py but adapted for DDP + where all workers have the full model and use standard data sharding. + + Args: + strategy: The DDP strategy (already set up) + config: Training configuration dict + """ + world_rank = ray.train.get_context().get_world_rank() + device = ray.train.torch.get_device() + + # Create TP-aware dataloader (uses dp_rank/dp_size) + # For DDP: dp_rank = world_rank, dp_size = world_size + dataloader = create_tp_aware_dataloader( + model_name=config["model_name"], + dataset_name=config["dataset_name"], + seq_length=config["seq_length"], + batch_size=config["batch_size"], + dp_rank=strategy.dp_rank, + dp_size=strategy.dp_size, + seed=config.get("seed", 42), + dataset_percentage=config.get("dataset_percentage", 10.0), + ) + + steps_per_epoch = len(dataloader) + log_rank0(f"Dataloader created: {steps_per_epoch} steps per epoch") + + # Load checkpoint if resuming + checkpoint = ray.train.get_checkpoint() + start_epoch = 0 + if checkpoint: + metadata = load_checkpoint(strategy, checkpoint) + start_epoch = metadata.get("epoch", 0) + 1 + log_rank0(f"Resuming training from epoch {start_epoch}") + + # Set model to training mode + strategy.train() + + # Loss history tracking for verification + loss_history = [] + + # Training loop + for epoch in range(start_epoch, config["num_epochs"]): + # Set sampler epoch for different shuffling each epoch + dataloader.sampler.set_epoch(epoch) + + running_loss = 0.0 + num_batches = 0 + + for step, batch in enumerate(dataloader): + # Move batch to device + batch = {k: v.to(device) for k, v in batch.items()} + + # Zero gradients + strategy.zero_grad() + + # Forward pass + loss = strategy.forward(batch) + + # Backward pass + strategy.backward(loss) + + # Track per-step loss + loss_value = loss.item() + loss_history.append(loss_value) + + # Log progress + if world_rank == 0 and step % config.get("log_interval", 10) == 0: + log_rank0( + f"Epoch: {epoch} Step: {step + 1}/{steps_per_epoch} Loss: {loss_value:.4f}" + ) + + # Optimizer step + strategy.optimizer_step() + + running_loss += loss_value + num_batches += 1 + + # Debug mode: stop early for testing + if config.get("debug_steps", 0) > 0 and step + 1 >= config["debug_steps"]: + log_rank0(f"Debug steps finished. Stopping epoch {epoch}.") + break + + # Calculate average loss for epoch + avg_loss = running_loss / num_batches if num_batches > 0 else 0.0 + + # Save checkpoint at end of epoch + save_checkpoint( + strategy=strategy, + epoch=epoch, + step=step, + metrics={"loss": avg_loss, "epoch": epoch}, + ) + + log_rank0(f"Epoch {epoch} completed. Average loss: {avg_loss:.4f}") + + # Save loss history if output_dir is specified (rank 0 only) + output_dir = config.get("loss_output_dir") + if output_dir and world_rank == 0: + import json + os.makedirs(output_dir, exist_ok=True) + loss_file = os.path.join(output_dir, "loss_ddp.json") + with open(loss_file, "w") as f: + json.dump({"implementation": "ddp", "loss_history": loss_history}, f) + log_rank0(f"Loss history saved to {loss_file}") + + +def get_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Ray Train + DDP Distributed Data Parallel Training (Baseline)" + ) + + # Model configuration + parser.add_argument( + "--model_name", + type=str, + default="Qwen/Qwen2-7B", + help="HuggingFace model name or path", + ) + parser.add_argument( + "--num_layers", + type=int, + default=0, + help="Override number of layers (0 = use model default)", + ) + parser.add_argument( + "--attn_impl", + type=str, + default="sdpa", + choices=["sdpa", "flash_attention_2", "eager"], + help="Attention implementation", + ) + + # Parallelism configuration + parser.add_argument( + "--num_workers", + type=int, + required=True, + help="Number of workers (data parallelism degree)", + ) + + # Dataset configuration + parser.add_argument( + "--dataset_name", + type=str, + default="wikitext", + help="HuggingFace dataset name", + ) + parser.add_argument( + "--dataset_percentage", + type=float, + default=10.0, + help="Percentage of dataset to use (0-100)", + ) + + # Training configuration + parser.add_argument( + "--batch_size", + type=int, + default=1, + help="Per-GPU micro batch size", + ) + parser.add_argument( + "--seq_length", + type=int, + default=2048, + help="Maximum sequence length", + ) + parser.add_argument( + "--num_epochs", + type=int, + default=3, + help="Number of training epochs", + ) + parser.add_argument( + "--learning_rate", + type=float, + default=1e-5, + help="Learning rate", + ) + parser.add_argument( + "--weight_decay", + type=float, + default=0.01, + help="Weight decay", + ) + parser.add_argument( + "--activation_checkpointing", + action="store_true", + help="Enable activation/gradient checkpointing", + ) + parser.add_argument( + "--autocast", + action="store_true", + default=True, + help="Enable torch.autocast for mixed precision (default: True)", + ) + + # Checkpointing configuration + parser.add_argument( + "--storage_path", + type=str, + default="/mnt/cluster_storage", + help="Storage path for checkpoints", + ) + parser.add_argument( + "--experiment_name", + type=str, + default=None, + help="Experiment name (auto-generated if not provided)", + ) + parser.add_argument( + "--resume_from", + type=str, + default=None, + help="Experiment name to resume from", + ) + + # Logging and debugging + parser.add_argument( + "--log_interval", + type=int, + default=10, + help="Logging interval (steps)", + ) + parser.add_argument( + "--debug_steps", + type=int, + default=0, + help="Stop after this many steps per epoch (0 = run full epoch)", + ) + parser.add_argument( + "--seed", + type=int, + default=42, + help="Random seed", + ) + parser.add_argument( + "--loss_output_dir", + type=str, + default=None, + help="Directory to save per-step loss history JSON (for verification)", + ) + parser.add_argument( + "--init_weights_path", + type=str, + default=None, + help="Path to load initial model weights from (for verification across implementations)", + ) + parser.add_argument( + "--save_init_weights", + action="store_true", + help="Save initial model weights to --init_weights_path before training", + ) + + return parser.parse_args() + + +def main(): + """Main entry point.""" + args = get_args() + + print(f"Configuration: {args}") + + # Build train_loop_config + train_loop_config = { + # Model + "model_name": args.model_name, + "num_layers": args.num_layers, + "attn_impl": args.attn_impl, + # Parallelism - DDP has no TP, only DP + "tp_size": 1, + "dp_size": args.num_workers, + # Dataset + "dataset_name": args.dataset_name, + "dataset_percentage": args.dataset_percentage, + # Training + "batch_size": args.batch_size, + "seq_length": args.seq_length, + "num_epochs": args.num_epochs, + "learning_rate": args.learning_rate, + "weight_decay": args.weight_decay, + "activation_checkpointing": args.activation_checkpointing, + "autocast": args.autocast, + # Logging/debugging + "log_interval": args.log_interval, + "debug_steps": args.debug_steps, + "seed": args.seed, + # Loss output for verification + "loss_output_dir": args.loss_output_dir, + # Init weights for verification + "init_weights_path": args.init_weights_path, + "save_init_weights": args.save_init_weights, + } + + # Configure Ray Train + scaling_config = ScalingConfig( + num_workers=args.num_workers, + use_gpu=True, + ) + + # Generate experiment name + name = args.experiment_name + if name is None: + if args.resume_from is not None: + name = args.resume_from + else: + name = f"ddp_dp{args.num_workers}_{uuid.uuid4().hex[:8]}" + + print(f"Experiment name: {name}") + + run_config = RunConfig( + storage_path=args.storage_path, + name=name, + ) + + # Create and run trainer + trainer = TorchTrainer( + train_loop_per_worker=train_loop_per_worker, + scaling_config=scaling_config, + train_loop_config=train_loop_config, + run_config=run_config, + ) + + result = trainer.fit() + print(f"Training finished. Result: {result}") + + +if __name__ == "__main__": + main() diff --git a/train_tensor_parallel/train_deepspeed.py b/train_tensor_parallel/train_deepspeed.py new file mode 100644 index 0000000..30cc807 --- /dev/null +++ b/train_tensor_parallel/train_deepspeed.py @@ -0,0 +1,137 @@ +""" +Ray Train + DeepSpeed AutoTP Tensor Parallelism Training. + +This script demonstrates training with DeepSpeed AutoTP tensor parallelism +using Ray Train's TorchTrainer for distributed execution and checkpointing. + +Example usage: + # 8 GPUs: 4-way tensor parallelism, 2-way data parallelism + python train_deepspeed.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 2 \ + --num_workers 8 \ + --dataset_name wikitext \ + --batch_size 2 \ + --seq_length 2048 \ + --num_epochs 3 + + # 4 GPUs: 4-way tensor parallelism only + python train_deepspeed.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 1 \ + --num_workers 4 \ + --dataset_name wikitext \ + --num_epochs 3 +""" + +import argparse +import os +from typing import Any, Dict + +os.environ["RAY_TRAIN_V2_ENABLED"] = "1" + +import deepspeed +import torch + +import ray.train +import ray.train.torch + +from autotp_strategy import RayAutoTPStrategy +from common import ( + add_common_args, + get_common_train_config, + log_rank0, + run_trainer, + run_training_loop, +) + + +def train_loop_per_worker(config: Dict[str, Any]) -> None: + """ + Main training loop executed by each Ray Train worker. + + Args: + config: Training configuration dict + """ + # Get Ray Train context + ctx = ray.train.get_context() + world_rank = ctx.get_world_rank() + world_size = ctx.get_world_size() + device = ray.train.torch.get_device() + + log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") + + # Initialize DeepSpeed distributed (will detect and use existing process group from Ray Train) + deepspeed.init_distributed() + + # Create and setup the AutoTP strategy + strategy = RayAutoTPStrategy( + tp_size=config["tp_size"], + dp_size=config["dp_size"], + ) + + strategy.setup( + model_name=config["model_name"], + device=device, + dtype=torch.bfloat16, + config={ + "batch_size": config["batch_size"], + "learning_rate": config["learning_rate"], + "weight_decay": config.get("weight_decay", 0.01), + "max_grad_norm": config.get("max_grad_norm", 1.0), + "zero_stage": config["zero_stage"], + "gradient_accumulation_steps": config.get("gradient_accumulation_steps", 1), + "num_layers": config.get("num_layers", 0), + "attn_impl": config.get("attn_impl", "sdpa"), + "activation_checkpointing": config.get("activation_checkpointing", False), + "vocab_parallel": config.get("vocab_parallel", False), + "init_weights_path": config.get("init_weights_path"), + }, + ) + + # Run common training loop + run_training_loop(strategy, config) + + +def get_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Ray Train + DeepSpeed AutoTP Tensor Parallelism Training" + ) + + # Add common arguments + add_common_args(parser) + + # DeepSpeed-specific arguments + parser.add_argument( + "--zero_stage", + type=int, + default=1, + choices=[0, 1, 2], + help="DeepSpeed ZeRO stage (0-2, ZeRO-3 not supported with AutoTP)", + ) + return parser.parse_args() + + +def main(): + """Main entry point.""" + args = get_args() + + # Build train_loop_config + train_loop_config = get_common_train_config(args) + train_loop_config["zero_stage"] = args.zero_stage + train_loop_config["impl_name"] = "deepspeed" + + # Run trainer + run_trainer( + args=args, + train_loop_per_worker=train_loop_per_worker, + train_loop_config=train_loop_config, + experiment_prefix="deepspeed_autotp", + ) + + +if __name__ == "__main__": + main() diff --git a/train_tensor_parallel/train_fsdp.py b/train_tensor_parallel/train_fsdp.py new file mode 100644 index 0000000..fb9ed1e --- /dev/null +++ b/train_tensor_parallel/train_fsdp.py @@ -0,0 +1,120 @@ +""" +Ray Train + FSDP2 + DTensor Tensor Parallelism Training. + +This script demonstrates training with PyTorch native FSDP2 + DTensor tensor parallelism +using Ray Train's TorchTrainer for distributed execution and checkpointing. + +Example usage: + # 8 GPUs: 4-way tensor parallelism, 2-way data parallelism + python train_fsdp.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 2 \ + --num_workers 8 \ + --dataset_name wikitext \ + --batch_size 2 \ + --seq_length 2048 \ + --num_epochs 3 + + # 4 GPUs: 4-way tensor parallelism only + python train_fsdp.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 1 \ + --num_workers 4 \ + --dataset_name wikitext \ + --num_epochs 3 +""" + +import argparse +import os +from typing import Any, Dict + +os.environ["RAY_TRAIN_V2_ENABLED"] = "1" + +import torch + +import ray.train +import ray.train.torch + +from common import ( + add_common_args, + get_common_train_config, + log_rank0, + run_trainer, + run_training_loop, +) +from fsdp_strategy import RayFSDPStrategy + + +def train_loop_per_worker(config: Dict[str, Any]) -> None: + """ + Main training loop executed by each Ray Train worker. + + Args: + config: Training configuration dict + """ + # Get Ray Train context + ctx = ray.train.get_context() + world_rank = ctx.get_world_rank() + world_size = ctx.get_world_size() + device = ray.train.torch.get_device() + + log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") + + # Create and setup the FSDP+DTensor strategy + strategy = RayFSDPStrategy( + tp_size=config["tp_size"], + dp_size=config["dp_size"], + ) + + strategy.setup( + model_name=config["model_name"], + device=device, + dtype=torch.bfloat16, + config={ + "learning_rate": config["learning_rate"], + "weight_decay": config.get("weight_decay", 0.01), + "num_layers": config.get("num_layers", 0), + "attn_impl": config.get("attn_impl", "sdpa"), + "activation_checkpointing": config.get("activation_checkpointing", False), + "vocab_parallel": config.get("vocab_parallel", False), + "init_weights_path": config.get("init_weights_path"), + }, + ) + + # Run common training loop + run_training_loop(strategy, config) + + +def get_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Ray Train + FSDP2 + DTensor Tensor Parallelism Training" + ) + + # Add common arguments + add_common_args(parser) + + return parser.parse_args() + + +def main(): + """Main entry point.""" + args = get_args() + + # Build train_loop_config + train_loop_config = get_common_train_config(args) + train_loop_config["impl_name"] = "fsdp" + + # Run trainer + run_trainer( + args=args, + train_loop_per_worker=train_loop_per_worker, + train_loop_config=train_loop_config, + experiment_prefix="fsdp_dtensor", + ) + + +if __name__ == "__main__": + main() From 8424617cc4a585ebe7012b62098f2a35ac4222d9 Mon Sep 17 00:00:00 2001 From: Masahiro Tanaka Date: Thu, 1 Jan 2026 19:28:15 -0800 Subject: [PATCH 2/2] simplify tp example Signed-off-by: Masahiro Tanaka --- train_tensor_parallel/README.md | 207 ++----- train_tensor_parallel/args.py | 120 ++++ train_tensor_parallel/autotp_strategy.py | 412 -------------- train_tensor_parallel/common.py | 527 ------------------ train_tensor_parallel/data.py | 168 ------ train_tensor_parallel/ddp_strategy.py | 220 -------- train_tensor_parallel/fsdp_strategy.py | 285 ---------- .../{job_fsdp.yaml => job.yaml} | 10 +- train_tensor_parallel/job_ddp.yaml | 49 -- train_tensor_parallel/job_deepspeed.yaml | 54 -- train_tensor_parallel/loss_curves.png | Bin 186835 -> 0 bytes train_tensor_parallel/model_builder.py | 154 ----- train_tensor_parallel/plot_loss_curves.py | 111 ---- .../run_verification_main.sh | 122 ---- train_tensor_parallel/train.py | 457 +++++++++++++++ train_tensor_parallel/train_ddp.py | 425 -------------- train_tensor_parallel/train_deepspeed.py | 137 ----- train_tensor_parallel/train_fsdp.py | 120 ---- 18 files changed, 618 insertions(+), 2960 deletions(-) create mode 100644 train_tensor_parallel/args.py delete mode 100644 train_tensor_parallel/autotp_strategy.py delete mode 100644 train_tensor_parallel/common.py delete mode 100644 train_tensor_parallel/data.py delete mode 100644 train_tensor_parallel/ddp_strategy.py delete mode 100644 train_tensor_parallel/fsdp_strategy.py rename train_tensor_parallel/{job_fsdp.yaml => job.yaml} (73%) delete mode 100644 train_tensor_parallel/job_ddp.yaml delete mode 100644 train_tensor_parallel/job_deepspeed.yaml delete mode 100644 train_tensor_parallel/loss_curves.png delete mode 100644 train_tensor_parallel/model_builder.py delete mode 100644 train_tensor_parallel/plot_loss_curves.py delete mode 100755 train_tensor_parallel/run_verification_main.sh create mode 100644 train_tensor_parallel/train.py delete mode 100644 train_tensor_parallel/train_ddp.py delete mode 100644 train_tensor_parallel/train_deepspeed.py delete mode 100644 train_tensor_parallel/train_fsdp.py diff --git a/train_tensor_parallel/README.md b/train_tensor_parallel/README.md index 5b4a8cd..5057dad 100644 --- a/train_tensor_parallel/README.md +++ b/train_tensor_parallel/README.md @@ -1,36 +1,36 @@ -# Ray Train + Tensor Parallelism +# Ray Train + Tensor Parallelism Tutorial -Training examples combining Ray Train with tensor parallelism using either DeepSpeed AutoTP or PyTorch native FSDP2+DTensor. +A simple tutorial demonstrating how to train large language models with tensor parallelism using PyTorch native FSDP2+DTensor and Ray Train. -## Features +## Key Concepts -- **2D Parallelism**: Combine tensor parallelism (TP) with data parallelism (DP) -- **Ray Train Integration**: Distributed execution, checkpointing, and fault tolerance -- **Two Implementations**: - - `train_deepspeed.py`: DeepSpeed AutoTP with ZeRO optimization - - `train_fsdp.py`: PyTorch native FSDP2 + DTensor +- **Tensor Parallelism (TP)**: Shards model weights across GPUs within a TP group +- **Data Parallelism (DP)**: Replicates the model across DP groups, each processing different data +- **2D Parallelism**: Combines TP and DP for scaling to many GPUs ## Quick Start ```bash -# DeepSpeed AutoTP: 4 GPUs with 2-way TP, 2-way DP -python train_deepspeed.py \ +# 4 GPUs: 2-way tensor parallelism, 2-way data parallelism +python train.py \ --model_name Qwen/Qwen2-7B \ --tp_size 2 \ --dp_size 2 \ --num_workers 4 \ --num_epochs 3 -# FSDP+DTensor: 4 GPUs with 2-way TP, 2-way DP -python train_fsdp.py \ +# 8 GPUs: 4-way tensor parallelism, 2-way data parallelism +python train.py \ --model_name Qwen/Qwen2-7B \ - --tp_size 2 \ + --tp_size 4 \ --dp_size 2 \ - --num_workers 4 \ + --num_workers 8 \ + --batch_size 2 \ + --seq_length 2048 \ --num_epochs 3 ``` -## Common Arguments +## Arguments | Argument | Description | Default | |----------|-------------|---------| @@ -38,92 +38,33 @@ python train_fsdp.py \ | `--tp_size` | Tensor parallel degree | Required | | `--dp_size` | Data parallel degree | `1` | | `--num_workers` | Total workers (must equal tp_size * dp_size) | Required | +| `--dataset_name` | HuggingFace dataset | `wikitext` | +| `--dataset_percentage` | Percentage of dataset to use (0-100) | `10.0` | | `--batch_size` | Per-GPU micro batch size | `1` | | `--seq_length` | Maximum sequence length | `2048` | | `--num_epochs` | Number of training epochs | `3` | | `--learning_rate` | Learning rate | `1e-5` | -| `--dataset_name` | HuggingFace dataset | `wikitext` | +| `--weight_decay` | Weight decay | `0.01` | | `--storage_path` | Checkpoint storage path | `/mnt/cluster_storage` | -| `--resume_from` | Experiment name to resume from | None | - -### DeepSpeed-specific - -| Argument | Description | Default | -|----------|-------------|---------| -| `--zero_stage` | ZeRO optimization stage (0-2) | `1` | - -Note: Autocast is always enabled for bf16/fp16 dtypes in the FSDP path. - -## Anyscale Job Configs - -Pre-configured job files are provided for running on Anyscale: - -```bash -# DeepSpeed AutoTP -anyscale job submit -f job_deepspeed.yaml - -# FSDP + DTensor -anyscale job submit -f job_fsdp.yaml -``` - -### Common Settings - -Both jobs share these configurations: - -| Setting | Value | Description | -|---------|-------|-------------| -| `model_name` | `Qwen/Qwen2.5-0.5B` | Small model for testing | -| `tp_size` | 2 | Tensor parallel degree (must divide num_key_value_heads) | -| `dp_size` | 2 | Data parallel degree | -| `num_workers` | 4 | Total GPUs (tp_size × dp_size) | -| `seq_length` | 1024 | Sequence length | -| `batch_size` | 2 | Per-GPU micro batch size | -| `dataset_percentage` | 1.0 | Use 1% of dataset for quick testing | -| `num_epochs` | 1 | Number of training epochs | -| `log_interval` | 1 | Log every step | -| `image_uri` | `anyscale/ray:2.53.0-slim-py312-cu128` | Ray image with CUDA 12.8 | -| `instance_type` | `g4dn.12xlarge` | 4x T4 GPUs per node | -| `num_nodes` | 1 | Single node | - -### DeepSpeed-specific Settings (`job_deepspeed.yaml`) - -| Setting | Value | Description | -|---------|-------|-------------| -| `zero_stage` | 1 | DeepSpeed ZeRO optimization stage (0-2) | - -Requirements: `torch>=2.9.1`, `deepspeed>=0.18.3`, `transformers>=4.45.0`, `datasets>=3.0.0`, `accelerate>=1.0.0` - -### FSDP-specific Settings (`job_fsdp.yaml`) - -Autocast is always enabled for bf16/fp16 dtypes. - -Requirements: `torch>=2.9.1`, `transformers>=4.45.0`, `datasets>=3.0.0`, `accelerate>=1.0.0` - -### Customizing Job Configs +| `--experiment_name` | Experiment name (auto-generated if not provided) | None | +| `--log_interval` | Logging interval (steps) | `10` | +| `--debug_steps` | Stop after N steps per epoch (0 = full epoch) | `0` | +| `--seed` | Random seed | `42` | -Override settings via command line: +## Anyscale Job ```bash -# Use larger model with more GPUs -anyscale job submit -f job_deepspeed.yaml \ - --entrypoint "python train_deepspeed.py --model_name Qwen/Qwen2-7B --tp_size 4 --dp_size 2 --num_workers 8" +anyscale job submit -f job.yaml ``` -**Important**: `tp_size` must evenly divide the model's `num_key_value_heads`. For Qwen2.5-0.5B (2 KV heads), valid values are 1 or 2. - ## File Structure ``` -train_tensor_parallel/ -├── train_deepspeed.py # DeepSpeed AutoTP entry point -├── train_fsdp.py # FSDP+DTensor entry point -├── common.py # Shared utilities (training loop, checkpointing) -├── autotp_strategy.py # DeepSpeed AutoTP strategy -├── fsdp_strategy.py # FSDP2+DTensor strategy -├── model_builder.py # Model creation utilities -├── data.py # TP-aware data loading -├── job_deepspeed.yaml # Anyscale job config for DeepSpeed -└── job_fsdp.yaml # Anyscale job config for FSDP +train_tensor_parallel_simple/ +├── train.py # Main training script +├── args.py # Command line arguments +├── job.yaml # Anyscale job config +└── README.md # This file ``` ## How 2D Parallelism Works @@ -134,13 +75,13 @@ With `tp_size=2` and `dp_size=2` on 4 GPUs: Device Mesh (2x2): TP Dim [0] [1] -DP ┌───┬───┐ -Dim │ 0 │ 1 │ ← TP Group 0 (same data, sharded model) - ├───┼───┤ - │ 2 │ 3 │ ← TP Group 1 (same data, sharded model) - └───┴───┘ - ↑ ↑ - DP Groups (different data, gradient sync) + DP +---+---+ + Dim | 0 | 1 | <- TP Group 0 (same data, sharded model) + +---+---+ + | 2 | 3 | <- TP Group 1 (same data, sharded model) + +---+---+ + ^ ^ + DP Groups (different data, gradient sync) ``` - **TP Groups** (rows): GPUs 0,1 and GPUs 2,3 share the same input data but have sharded model weights @@ -150,7 +91,7 @@ Dim │ 0 │ 1 │ ← TP Group 0 (same data, sharded model) ### TP-Aware Data Loading -Standard data loaders shard by `world_rank`, giving each GPU different data. With tensor parallelism, all GPUs in a TP group must see identical data. The `data.py` module handles this by sharding based on `dp_rank` instead: +Standard data loaders shard by `world_rank`, giving each GPU different data. With tensor parallelism, all GPUs in a TP group must see identical data. This is handled by sharding based on `dp_rank` instead: ```python # All TP ranks in same DP group get identical batches @@ -164,77 +105,3 @@ sampler = DistributedSampler( ### Checkpointing All workers save their model shards independently. Ray Train aggregates these into a single checkpoint that can be used for resuming training. - -## Verification - -This section describes how to verify the correctness of the three distributed training implementations (DDP, DeepSpeed AutoTP, FSDP+DTensor). - -### Overview - -For a fair comparison, all implementations must start from **identical initial weights**. The training scripts support this via: -- `--init_weights_path`: Path to load/save initial model weights -- `--save_init_weights`: Save initial weights before training (DDP only) - -### Prerequisites - -- 4 GPUs (for TP=2, DP=2 configuration) -- Model: `Qwen/Qwen2.5-0.5B` (or any compatible model) -- Python environment with PyTorch, DeepSpeed, and Ray Train installed - -### Quick Start - -```bash -# Create output directories -mkdir -p /tmp/shared_weights /tmp/loss_curves - -# Run all three implementations with shared weights -./run_verification_main.sh -``` - -### Expected Results - -When using shared initial weights, you should see: - -#### Initial Loss -All three implementations should have **nearly identical initial loss** (difference < 0.001): -``` -DDP: Initial=10.8732 -DeepSpeed: Initial=10.8736 -FSDP: Initial=10.8736 -``` - -#### Loss Curve Matching - -| Implementation | Max Diff from DDP | Mean Diff | Notes | -|----------------|-------------------|-----------|-------| -| FSDP+DTensor | < 0.02 | < 0.001 | Almost identical to DDP | -| DeepSpeed AutoTP | < 0.5 | < 0.15 | Slightly different due to gradient clipping | - -#### Example Output -``` -============================================================ -LOSS CURVE STATISTICS -============================================================ - -DDP (TP=1, DP=2): - Initial loss: 10.873175 - Final loss: 4.644855 - Min loss: 0.723590 - Mean loss: 7.341280 - -DeepSpeed AutoTP (TP=2, DP=2): - Initial loss: 10.873634 - Final loss: 4.424264 - Min loss: 0.475180 - Mean loss: 7.223806 - -FSDP+DTensor (TP=2, DP=2): - Initial loss: 10.873634 - Final loss: 4.644554 - Min loss: 0.723624 - Mean loss: 7.341585 -``` - -#### Verification Plot - -![Training Loss Curves](loss_curves.png) diff --git a/train_tensor_parallel/args.py b/train_tensor_parallel/args.py new file mode 100644 index 0000000..bd73d1c --- /dev/null +++ b/train_tensor_parallel/args.py @@ -0,0 +1,120 @@ +"""Command line argument parsing for tensor parallelism training.""" + +import argparse + + +def get_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Ray Train + FSDP2 + DTensor Tensor Parallelism Training" + ) + + # Model configuration + parser.add_argument( + "--model_name", + type=str, + default="Qwen/Qwen2-7B", + help="HuggingFace model name or path", + ) + + # Parallelism configuration + parser.add_argument( + "--tp_size", + type=int, + required=True, + help="Tensor parallel degree", + ) + parser.add_argument( + "--dp_size", + type=int, + default=1, + help="Data parallel degree", + ) + parser.add_argument( + "--num_workers", + type=int, + required=True, + help="Total number of workers (must equal tp_size * dp_size)", + ) + + # Dataset configuration + parser.add_argument( + "--dataset_name", + type=str, + default="wikitext", + help="HuggingFace dataset name", + ) + parser.add_argument( + "--dataset_percentage", + type=float, + default=10.0, + help="Percentage of dataset to use (0-100)", + ) + + # Training configuration + parser.add_argument( + "--batch_size", + type=int, + default=1, + help="Per-GPU micro batch size", + ) + parser.add_argument( + "--seq_length", + type=int, + default=2048, + help="Maximum sequence length", + ) + parser.add_argument( + "--num_epochs", + type=int, + default=3, + help="Number of training epochs", + ) + parser.add_argument( + "--learning_rate", + type=float, + default=1e-5, + help="Learning rate", + ) + parser.add_argument( + "--weight_decay", + type=float, + default=0.01, + help="Weight decay", + ) + + # Checkpointing configuration + parser.add_argument( + "--storage_path", + type=str, + default="/mnt/cluster_storage", + help="Storage path for checkpoints", + ) + parser.add_argument( + "--experiment_name", + type=str, + default=None, + help="Experiment name (auto-generated if not provided)", + ) + + # Logging and debugging + parser.add_argument( + "--log_interval", + type=int, + default=10, + help="Logging interval (steps)", + ) + parser.add_argument( + "--debug_steps", + type=int, + default=0, + help="Stop after this many steps per epoch (0 = run full epoch)", + ) + parser.add_argument( + "--seed", + type=int, + default=42, + help="Random seed", + ) + + return parser.parse_args() diff --git a/train_tensor_parallel/autotp_strategy.py b/train_tensor_parallel/autotp_strategy.py deleted file mode 100644 index 826c644..0000000 --- a/train_tensor_parallel/autotp_strategy.py +++ /dev/null @@ -1,412 +0,0 @@ -"""DeepSpeed AutoTP tensor parallelism strategy adapted for Ray Train.""" - -import os -from typing import Any, Dict, List, Optional - -import torch -import torch.distributed as dist -import torch.nn as nn - -from model_builder import ( - ModelConfig, - create_model, - get_model_config, -) - - -class RayAutoTPStrategy: - """ - DeepSpeed AutoTP tensor parallelism strategy adapted for Ray Train. - - Uses DeepSpeed's automatic tensor parallelism via: - - set_autotp_mode() for instrumentation - - deepspeed.tp_model_init() for automatic model sharding - - DeepSpeed engine for training with ZeRO optimization - - When dp_size > 1, creates a 2D parallelism configuration: - - TP groups (same row): processes that share tensor-parallel shards - - DP groups (same column): processes that handle different data batches - - For example, with 8 GPUs, dp_size=2, tp_size=4: - - Device arrangement: [[0,1,2,3], [4,5,6,7]] - - TP groups: {0,1,2,3}, {4,5,6,7} - - DP groups: {0,4}, {1,5}, {2,6}, {3,7} - """ - - def __init__(self, tp_size: int, dp_size: int = 1): - self.tp_size = tp_size - self.dp_size = dp_size - self.engine = None - self.optimizer = None - self.model = None - self.tp_group = None - self.dp_group = None - self.tp_rank = None - self.dp_rank = None - self.rank = None - self.world_size = None - self.device = None - self._model_config: Optional[ModelConfig] = None - - def _create_process_groups(self) -> None: - """ - Create TP and DP process groups for 2D parallelism. - - Organizes GPUs into a 2D grid where: - - Rows are TP groups (processes that shard model weights) - - Columns are DP groups (processes that handle different data) - - Example with 8 GPUs (dp_size=2, tp_size=4): - GPU layout: [[0, 1, 2, 3], - [4, 5, 6, 7]] - TP groups: {0,1,2,3}, {4,5,6,7} - DP groups: {0,4}, {1,5}, {2,6}, {3,7} - """ - # Calculate which TP and DP group this rank belongs to - self.tp_rank = self.rank % self.tp_size # Position within TP group - self.dp_rank = self.rank // self.tp_size # Which DP replica - - # Create TP groups (processes in the same row) - # Each TP group contains tp_size consecutive ranks - tp_groups: List[List[int]] = [] - for dp_idx in range(self.dp_size): - tp_group_ranks = list(range(dp_idx * self.tp_size, (dp_idx + 1) * self.tp_size)) - tp_groups.append(tp_group_ranks) - group = dist.new_group(tp_group_ranks) - if self.rank in tp_group_ranks: - self.tp_group = group - - # Create DP groups (processes in the same column) - # Each DP group contains ranks at the same position across TP groups - dp_groups: List[List[int]] = [] - for tp_idx in range(self.tp_size): - dp_group_ranks = [tp_idx + dp_idx * self.tp_size for dp_idx in range(self.dp_size)] - dp_groups.append(dp_group_ranks) - group = dist.new_group(dp_group_ranks) - if self.rank in dp_group_ranks: - self.dp_group = group - - if self.rank == 0: - print(f"[AutoTP] TP groups: {tp_groups}") - print(f"[AutoTP] DP groups: {dp_groups}") - print(f"[AutoTP] Rank {self.rank}: tp_rank={self.tp_rank}, dp_rank={self.dp_rank}") - - def _sync_model_weights(self, model: nn.Module) -> None: - """ - Synchronize all model weights within each TP group. - - This is necessary because each rank creates the model with different - random initializations, but DeepSpeed tp_model_init() expects all - ranks to have identical weights before sharding. - - With 2D parallelism (DP x TP), we have multiple TP groups: - - TP group 0: global ranks [0, 1, ..., tp_size-1] - - TP group 1: global ranks [tp_size, tp_size+1, ..., 2*tp_size-1] - - etc. - - Each TP group broadcasts from its first member (tp_rank=0). - - Args: - model: The model to synchronize - """ - if self.tp_group is None or self.tp_size <= 1: - return - - # The source rank for broadcast is the first rank in this TP group - # With 2D layout: TP group i contains ranks [i*tp_size, (i+1)*tp_size) - # So the first rank in this TP group is dp_rank * tp_size - tp_group_first_rank = self.dp_rank * self.tp_size - - if self.rank == 0: - print(f"[AutoTP] Synchronizing model weights within each TP group...") - - with torch.no_grad(): - for name, param in model.named_parameters(): - dist.broadcast(param.data, src=tp_group_first_rank, group=self.tp_group) - - if self.tp_rank == 0: - print(f"[AutoTP] Model weights synchronized in TP group (src_rank={tp_group_first_rank})") - - def _create_mpu(self) -> "ModelParallelUnit": - """ - Create a Model Parallel Unit (MPU) for DeepSpeed. - - DeepSpeed uses the MPU to understand the parallelism topology and - perform gradient all-reduce only across the data parallel group. - """ - return ModelParallelUnit( - tp_group=self.tp_group, - dp_group=self.dp_group, - tp_size=self.tp_size, - dp_size=self.dp_size, - tp_rank=self.tp_rank, - dp_rank=self.dp_rank, - ) - - def setup( - self, - model_name: str, - device: torch.device, - dtype: torch.dtype, - config: Dict[str, Any], - ) -> None: - """ - Set up DeepSpeed AutoTP with optional data parallelism. - - Args: - model_name: HuggingFace model name - device: Target device - dtype: Target dtype (bfloat16, float16, float32) - config: Training configuration dict - """ - import deepspeed - from deepspeed.module_inject.layers import set_autotp_mode - - self.device = device - self.rank = dist.get_rank() if dist.is_initialized() else 0 - self.world_size = dist.get_world_size() if dist.is_initialized() else 1 - self._model_config = get_model_config(model_name) - - # Validate 2D configuration - if self.dp_size * self.tp_size != self.world_size: - raise ValueError( - f"dp_size ({self.dp_size}) * tp_size ({self.tp_size}) must equal " - f"world_size ({self.world_size})" - ) - - # Validate model configuration - if self._model_config.num_key_value_heads % self.tp_size != 0: - raise ValueError( - f"TP size {self.tp_size} must divide num_key_value_heads " - f"{self._model_config.num_key_value_heads}" - ) - - if self.rank == 0: - print(f"[AutoTP] Setting up with dp_size={self.dp_size}, tp_size={self.tp_size}") - - # Create TP and DP process groups for 2D parallelism - self._create_process_groups() - - # Enable AutoTP instrumentation BEFORE model creation - set_autotp_mode(training=True) - - # Create model on actual device with proper initialization - model = create_model( - model_name=model_name, - device=device, - dtype=dtype, - num_layers=config.get("num_layers", 0), - attn_impl=config.get("attn_impl", "sdpa"), - ) - - # Apply activation checkpointing if requested - if config.get("activation_checkpointing", False): - model.gradient_checkpointing_enable() - if self.rank == 0: - print("[AutoTP] Enabled activation checkpointing") - - # Handle initial weights loading for verification - # This must happen BEFORE weight synchronization so all ranks load the same weights - init_weights_path = config.get("init_weights_path") - if init_weights_path and os.path.exists(init_weights_path): - state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) - model.load_state_dict(state_dict) - if self.rank == 0: - print(f"[AutoTP] Loaded initial weights from {init_weights_path}") - # Skip weight synchronization since all ranks now have identical weights - skip_sync = True - else: - skip_sync = False - - # CRITICAL: Broadcast ALL model weights from rank 0 to all TP ranks before sharding. - # DeepSpeed tp_model_init() shards weights but doesn't synchronize them across ranks. - # Without this, each rank would have different random initializations, leading to - # incorrect model behavior after sharding. - # Skip if we loaded weights from checkpoint (all ranks already have identical weights) - if not skip_sync: - self._sync_model_weights(model) - - # Apply TP sharding with deepspeed.tp_model_init() - # Pass the TP group so DeepSpeed knows which ranks share model shards - model = deepspeed.tp_model_init( - model, - tp_size=self.tp_size, - dtype=dtype, - tp_group=self.tp_group, - ) - - # Get all parameters - params = list(model.parameters()) - - # Build DeepSpeed config - zero_stage = config.get("zero_stage", 1) - batch_size = config.get("batch_size", 1) - gradient_accumulation_steps = config.get("gradient_accumulation_steps", 1) - - # train_batch_size calculation: - # When MPU is provided (tp_size > 1): DeepSpeed uses mpu.get_data_parallel_world_size() - effective_dp = self.dp_size if self.tp_size > 1 else self.world_size - ds_config = { - "train_batch_size": batch_size * effective_dp * gradient_accumulation_steps, - "train_micro_batch_size_per_gpu": batch_size, - "gradient_accumulation_steps": gradient_accumulation_steps, - "gradient_clipping": config.get("max_grad_norm", 1.0), - "zero_optimization": { - "stage": zero_stage, - "overlap_comm": True, - }, - "tensor_parallel": { - "autotp_size": self.tp_size, - }, - "data_parallel_size": self.dp_size, - "zero_allow_untested_optimizer": True, - "steps_per_print": 2000, - "wall_clock_breakdown": False, - } - - # Add precision config - if dtype == torch.bfloat16: - ds_config["bf16"] = { - "enabled": True, - "bf16_master_weights_and_grads": True, - "bf16_optimizer_states": True, - } - # Enable PyTorch native autocast for bfloat16 - ds_config["torch_autocast"] = { - "enabled": True, - "dtype": "bfloat16", - } - elif dtype == torch.float16: - ds_config["fp16"] = {"enabled": True, "initial_scale_power": 8} - # Enable PyTorch native autocast for float16 - ds_config["torch_autocast"] = { - "enabled": True, - "dtype": "float16", - } - - # Create optimizer - learning_rate = config.get("learning_rate", 1e-5) - weight_decay = config.get("weight_decay", 0.01) - optimizer = torch.optim.AdamW(params, lr=learning_rate, weight_decay=weight_decay) - - # Initialize DeepSpeed engine - # Pass the MPU so DeepSpeed knows the parallelism topology - self.engine, self.optimizer, _, _ = deepspeed.initialize( - model=model, - optimizer=optimizer, - config=ds_config, - mpu=self._create_mpu() if self.tp_size > 1 else None, - ) - - self.model = self.engine.module - - if self.rank == 0: - num_params = sum(p.numel() for p in params) - print(f"[AutoTP] Model initialized with {num_params:,} parameters") - print(f"[AutoTP] DeepSpeed engine created with ZeRO-{zero_stage}") - if self.dp_size > 1: - print(f"[AutoTP] 2D parallelism: {self.dp_size} DP x {self.tp_size} TP") - if "torch_autocast" in ds_config: - print(f"[AutoTP] torch.autocast enabled with dtype={ds_config['torch_autocast']['dtype']}") - - def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - """Run forward pass with AutoTP model.""" - outputs = self.engine( - input_ids=batch["input_ids"], - attention_mask=batch["attention_mask"], - labels=batch["labels"], - use_cache=False, - ) - return outputs.loss - - def backward(self, loss: torch.Tensor) -> None: - """Run backward pass through DeepSpeed engine.""" - self.engine.backward(loss) - - def optimizer_step(self) -> None: - """Run optimizer step through DeepSpeed engine.""" - self.engine.step() - - def zero_grad(self) -> None: - """Zero gradients - handled internally by DeepSpeed.""" - pass - - def train(self) -> None: - """Set model to training mode.""" - self.engine.train() - - def eval(self) -> None: - """Set model to evaluation mode.""" - self.engine.eval() - - def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Save checkpoint using DeepSpeed. - - Args: - checkpoint_dir: Directory to save checkpoint - tag: Checkpoint tag/name - """ - self.engine.save_checkpoint(checkpoint_dir, tag=tag) - - def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Load checkpoint using DeepSpeed. - - Args: - checkpoint_dir: Directory containing checkpoint - tag: Checkpoint tag/name - """ - self.engine.load_checkpoint(checkpoint_dir, tag=tag) - - -class ModelParallelUnit: - """ - Model Parallel Unit (MPU) interface for DeepSpeed. - - This class implements the interface DeepSpeed expects for understanding - the parallelism topology when using custom TP+DP configurations. - - DeepSpeed calls methods like get_data_parallel_group() to know which - ranks should participate in gradient all-reduce operations. - """ - - def __init__( - self, - tp_group: dist.ProcessGroup, - dp_group: dist.ProcessGroup, - tp_size: int, - dp_size: int, - tp_rank: int, - dp_rank: int, - ): - self._tp_group = tp_group - self._dp_group = dp_group - self._tp_size = tp_size - self._dp_size = dp_size - self._tp_rank = tp_rank - self._dp_rank = dp_rank - - def get_data_parallel_group(self) -> dist.ProcessGroup: - """Return the data parallel process group.""" - return self._dp_group - - def get_model_parallel_group(self) -> dist.ProcessGroup: - """Return the model/tensor parallel process group.""" - return self._tp_group - - def get_data_parallel_world_size(self) -> int: - """Return the number of data parallel ranks.""" - return self._dp_size - - def get_model_parallel_world_size(self) -> int: - """Return the number of model/tensor parallel ranks.""" - return self._tp_size - - def get_data_parallel_rank(self) -> int: - """Return this rank's position in the data parallel group.""" - return self._dp_rank - - def get_model_parallel_rank(self) -> int: - """Return this rank's position in the model/tensor parallel group.""" - return self._tp_rank diff --git a/train_tensor_parallel/common.py b/train_tensor_parallel/common.py deleted file mode 100644 index 7e83a05..0000000 --- a/train_tensor_parallel/common.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -Common utilities shared between DeepSpeed AutoTP and FSDP+DTensor training scripts. -""" - -import argparse -import json -import logging -import os -import tempfile -import uuid -from typing import Any, Callable, Dict, Protocol - -import torch - -import ray -import ray.train -import ray.train.torch -from ray.train import Checkpoint, RunConfig, ScalingConfig -from ray.train.torch import TorchTrainer - -from data import create_tp_aware_dataloader - -logger = logging.getLogger(__name__) - - -class TPStrategy(Protocol): - """Protocol defining the interface for tensor parallelism strategies.""" - - tp_rank: int - dp_rank: int - tp_size: int - dp_size: int - - def setup(self, model_name: str, device: torch.device, dtype: torch.dtype, config: Dict[str, Any]) -> None: - ... - - def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - ... - - def backward(self, loss: torch.Tensor) -> None: - ... - - def forward_backward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - """Optional: Combined forward+backward for strategies that need it (e.g., DTensor with loss_parallel).""" - ... - - def optimizer_step(self) -> None: - ... - - def zero_grad(self) -> None: - ... - - def train(self) -> None: - ... - - def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - ... - - def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - ... - - -def log_rank0(message: str) -> None: - """Log message only from rank 0.""" - if ray.train.get_context().get_world_rank() == 0: - logger.info(message) - - -def save_checkpoint( - strategy: TPStrategy, - epoch: int, - step: int, - metrics: Dict[str, Any], -) -> None: - """ - Save checkpoint and report to Ray Train. - - All workers save their checkpoint shards, then report to Ray Train. - Ray Train aggregates checkpoints from all workers properly. - - Args: - strategy: The TP strategy containing the model/engine - epoch: Current epoch number - step: Current step number - metrics: Training metrics to report - """ - world_rank = ray.train.get_context().get_world_rank() - - with tempfile.TemporaryDirectory() as tmp_dir: - checkpoint_dir = os.path.join(tmp_dir, "checkpoint") - os.makedirs(checkpoint_dir, exist_ok=True) - - # Save checkpoint (each rank saves its shard) - strategy.save_checkpoint(checkpoint_dir, tag="model") - - # Save metadata (from world rank 0 only) - if world_rank == 0: - metadata = {"epoch": epoch, "step": step} - with open(os.path.join(checkpoint_dir, "metadata.json"), "w") as f: - json.dump(metadata, f) - - # All workers must call report() with their checkpoint - checkpoint = Checkpoint.from_directory(tmp_dir) - ray.train.report(metrics, checkpoint=checkpoint) - - if world_rank == 0: - experiment_name = ray.train.get_context().get_experiment_name() - log_rank0(f"Checkpoint saved for experiment {experiment_name}. Metrics: {metrics}") - - -def load_checkpoint( - strategy: TPStrategy, - checkpoint: ray.train.Checkpoint, -) -> Dict[str, Any]: - """ - Load checkpoint for resuming training. - - Each rank loads its corresponding checkpoint shard. - - Args: - strategy: The TP strategy containing the model/engine - checkpoint: Ray Train checkpoint to load - - Returns: - Metadata dict with epoch and step info - """ - metadata = {"epoch": 0, "step": 0} - - try: - with checkpoint.as_directory() as checkpoint_dir: - log_rank0(f"Loading checkpoint from {checkpoint_dir}") - ckpt_dir = os.path.join(checkpoint_dir, "checkpoint") - if not os.path.isdir(ckpt_dir): - ckpt_dir = checkpoint_dir - - # Load checkpoint (each rank loads its shard) - strategy.load_checkpoint(ckpt_dir, tag="model") - - # Read metadata - metadata_file = os.path.join(ckpt_dir, "metadata.json") - if os.path.isfile(metadata_file): - with open(metadata_file, "r") as f: - metadata = json.load(f) - - # Synchronize across all workers - if torch.distributed.is_available() and torch.distributed.is_initialized(): - torch.distributed.barrier() - - log_rank0(f"Successfully loaded checkpoint. Epoch: {metadata.get('epoch', 0)}") - - except Exception as e: - logger.error(f"Failed to load checkpoint: {e}") - raise RuntimeError(f"Checkpoint loading failed: {e}") from e - - return metadata - - -def run_training_loop( - strategy: TPStrategy, - config: Dict[str, Any], -) -> None: - """ - Run the common training loop for any TP strategy. - - Args: - strategy: The TP strategy (already set up) - config: Training configuration dict - """ - world_rank = ray.train.get_context().get_world_rank() - device = ray.train.torch.get_device() - - # Create TP-aware dataloader - # Uses dp_rank/dp_size for sharding, not world_rank/world_size - dataloader = create_tp_aware_dataloader( - model_name=config["model_name"], - dataset_name=config["dataset_name"], - seq_length=config["seq_length"], - batch_size=config["batch_size"], - dp_rank=strategy.dp_rank, - dp_size=strategy.dp_size, - seed=config.get("seed", 42), - dataset_percentage=config.get("dataset_percentage", 10.0), - ) - - steps_per_epoch = len(dataloader) - log_rank0(f"Dataloader created: {steps_per_epoch} steps per epoch") - - # Load checkpoint if resuming - checkpoint = ray.train.get_checkpoint() - start_epoch = 0 - if checkpoint: - metadata = load_checkpoint(strategy, checkpoint) - start_epoch = metadata.get("epoch", 0) + 1 - log_rank0(f"Resuming training from epoch {start_epoch}") - - # Set model to training mode - strategy.train() - - # Loss history tracking for verification - loss_history = [] - - # Training loop - for epoch in range(start_epoch, config["num_epochs"]): - # Set sampler epoch for different shuffling each epoch - dataloader.sampler.set_epoch(epoch) - - running_loss = 0.0 - num_batches = 0 - - # Check if strategy has combined forward_backward (needed for DTensor with loss_parallel) - use_forward_backward = hasattr(strategy, "forward_backward") and callable( - getattr(strategy, "forward_backward", None) - ) - - for step, batch in enumerate(dataloader): - # Move batch to device - batch = {k: v.to(device) for k, v in batch.items()} - - # Zero gradients (for FSDP, DeepSpeed handles this internally) - strategy.zero_grad() - - if use_forward_backward: - # Combined forward+backward (required for DTensor with loss_parallel) - loss = strategy.forward_backward(batch) - else: - # Separate forward and backward passes - loss = strategy.forward(batch) - strategy.backward(loss) - - # Track per-step loss - loss_value = loss.item() - loss_history.append(loss_value) - - # Log progress - if world_rank == 0 and step % config.get("log_interval", 10) == 0: - log_rank0( - f"Epoch: {epoch} Step: {step + 1}/{steps_per_epoch} Loss: {loss_value:.4f}" - ) - - # Optimizer step - strategy.optimizer_step() - - running_loss += loss_value - num_batches += 1 - - # Debug mode: stop early for testing - if config.get("debug_steps", 0) > 0 and step + 1 >= config["debug_steps"]: - log_rank0(f"Debug steps finished. Stopping epoch {epoch}.") - break - - # Calculate average loss for epoch - avg_loss = running_loss / num_batches if num_batches > 0 else 0.0 - - # Save checkpoint at end of epoch - save_checkpoint( - strategy=strategy, - epoch=epoch, - step=step, - metrics={"loss": avg_loss, "epoch": epoch}, - ) - - log_rank0(f"Epoch {epoch} completed. Average loss: {avg_loss:.4f}") - - # Save loss history if output_dir is specified (rank 0 only) - output_dir = config.get("loss_output_dir") - impl_name = config.get("impl_name", "unknown") - if output_dir and world_rank == 0: - os.makedirs(output_dir, exist_ok=True) - loss_file = os.path.join(output_dir, f"loss_{impl_name}.json") - with open(loss_file, "w") as f: - json.dump({"implementation": impl_name, "loss_history": loss_history}, f) - log_rank0(f"Loss history saved to {loss_file}") - - -def add_common_args(parser: argparse.ArgumentParser) -> None: - """Add common arguments shared by all training scripts.""" - # Model configuration - parser.add_argument( - "--model_name", - type=str, - default="Qwen/Qwen2-7B", - help="HuggingFace model name or path", - ) - parser.add_argument( - "--num_layers", - type=int, - default=0, - help="Override number of layers (0 = use model default)", - ) - parser.add_argument( - "--attn_impl", - type=str, - default="sdpa", - choices=["sdpa", "flash_attention_2", "eager"], - help="Attention implementation", - ) - - # Parallelism configuration - parser.add_argument( - "--tp_size", - type=int, - required=True, - help="Tensor parallel degree", - ) - parser.add_argument( - "--dp_size", - type=int, - default=1, - help="Data parallel degree", - ) - parser.add_argument( - "--num_workers", - type=int, - required=True, - help="Total number of workers (must equal tp_size * dp_size)", - ) - - # Dataset configuration - parser.add_argument( - "--dataset_name", - type=str, - default="wikitext", - help="HuggingFace dataset name", - ) - parser.add_argument( - "--dataset_percentage", - type=float, - default=10.0, - help="Percentage of dataset to use (0-100)", - ) - - # Training configuration - parser.add_argument( - "--batch_size", - type=int, - default=1, - help="Per-GPU micro batch size", - ) - parser.add_argument( - "--seq_length", - type=int, - default=2048, - help="Maximum sequence length", - ) - parser.add_argument( - "--num_epochs", - type=int, - default=3, - help="Number of training epochs", - ) - parser.add_argument( - "--learning_rate", - type=float, - default=1e-5, - help="Learning rate", - ) - parser.add_argument( - "--weight_decay", - type=float, - default=0.01, - help="Weight decay", - ) - parser.add_argument( - "--max_grad_norm", - type=float, - default=1.0, - help="Gradient clipping norm", - ) - parser.add_argument( - "--gradient_accumulation_steps", - type=int, - default=1, - help="Gradient accumulation steps", - ) - parser.add_argument( - "--activation_checkpointing", - action="store_true", - help="Enable activation/gradient checkpointing", - ) - - # Checkpointing configuration - parser.add_argument( - "--storage_path", - type=str, - default="/mnt/cluster_storage", - help="Storage path for checkpoints", - ) - parser.add_argument( - "--experiment_name", - type=str, - default=None, - help="Experiment name (auto-generated if not provided)", - ) - parser.add_argument( - "--resume_from", - type=str, - default=None, - help="Experiment name to resume from", - ) - - # Logging and debugging - parser.add_argument( - "--log_interval", - type=int, - default=10, - help="Logging interval (steps)", - ) - parser.add_argument( - "--debug_steps", - type=int, - default=0, - help="Stop after this many steps per epoch (0 = run full epoch)", - ) - parser.add_argument( - "--seed", - type=int, - default=42, - help="Random seed", - ) - parser.add_argument( - "--loss_output_dir", - type=str, - default=None, - help="Directory to save per-step loss history JSON (for verification)", - ) - parser.add_argument( - "--init_weights_path", - type=str, - default=None, - help="Path to load initial model weights from (for verification across implementations)", - ) - parser.add_argument( - "--save_init_weights", - action="store_true", - help="Save initial model weights to --init_weights_path before training", - ) - - -def get_common_train_config(args: argparse.Namespace) -> Dict[str, Any]: - """Build common train_loop_config from parsed args.""" - return { - # Model - "model_name": args.model_name, - "num_layers": args.num_layers, - "attn_impl": args.attn_impl, - # Parallelism - "tp_size": args.tp_size, - "dp_size": args.dp_size, - # Dataset - "dataset_name": args.dataset_name, - "dataset_percentage": args.dataset_percentage, - # Training - "batch_size": args.batch_size, - "seq_length": args.seq_length, - "num_epochs": args.num_epochs, - "learning_rate": args.learning_rate, - "weight_decay": args.weight_decay, - "max_grad_norm": args.max_grad_norm, - "gradient_accumulation_steps": args.gradient_accumulation_steps, - "activation_checkpointing": args.activation_checkpointing, - # Logging/debugging - "log_interval": args.log_interval, - "debug_steps": args.debug_steps, - "seed": args.seed, - # Loss output for verification - "loss_output_dir": args.loss_output_dir, - # Init weights for verification - "init_weights_path": args.init_weights_path, - "save_init_weights": args.save_init_weights, - } - - -def run_trainer( - args: argparse.Namespace, - train_loop_per_worker: Callable[[Dict[str, Any]], None], - train_loop_config: Dict[str, Any], - experiment_prefix: str, -) -> None: - """ - Common trainer setup and execution. - - Args: - args: Parsed command line arguments - train_loop_per_worker: The training loop function - train_loop_config: Configuration dict for training - experiment_prefix: Prefix for auto-generated experiment names - """ - # Validate parallelism configuration - if args.tp_size * args.dp_size != args.num_workers: - raise ValueError( - f"tp_size ({args.tp_size}) * dp_size ({args.dp_size}) " - f"must equal num_workers ({args.num_workers})" - ) - - print(f"Configuration: {args}") - - # Configure Ray Train - scaling_config = ScalingConfig( - num_workers=args.num_workers, - use_gpu=True, - ) - - # Generate experiment name - name = args.experiment_name - if name is None: - if args.resume_from is not None: - name = args.resume_from - else: - name = f"{experiment_prefix}_tp{args.tp_size}_dp{args.dp_size}_{uuid.uuid4().hex[:8]}" - - print(f"Experiment name: {name}") - - run_config = RunConfig( - storage_path=args.storage_path, - name=name, - ) - - # Create and run trainer - trainer = TorchTrainer( - train_loop_per_worker=train_loop_per_worker, - scaling_config=scaling_config, - train_loop_config=train_loop_config, - run_config=run_config, - ) - - result = trainer.fit() - print(f"Training finished. Result: {result}") diff --git a/train_tensor_parallel/data.py b/train_tensor_parallel/data.py deleted file mode 100644 index d690ff8..0000000 --- a/train_tensor_parallel/data.py +++ /dev/null @@ -1,168 +0,0 @@ -"""TP-aware data loading for tensor parallelism training.""" - -from typing import Any - -import torch.distributed as dist -from datasets import DownloadConfig, load_dataset -from torch.utils.data import DataLoader -from torch.utils.data.distributed import DistributedSampler -from transformers import AutoTokenizer - -import ray.train - - -def _get_world_rank() -> int: - """Get world rank, handling both distributed and non-distributed cases.""" - try: - return ray.train.get_context().get_world_rank() - except Exception: - return 0 - - -def _barrier(): - """Synchronize all workers.""" - if dist.is_available() and dist.is_initialized(): - dist.barrier() - - -def get_tokenizer(model_name: str, trust_remote_code: bool = True) -> Any: - """ - Load and configure the tokenizer for the given model. - - Args: - model_name: Name of the model to load tokenizer for - trust_remote_code: Whether to trust remote code - - Returns: - Configured tokenizer - """ - tokenizer = AutoTokenizer.from_pretrained( - model_name, trust_remote_code=trust_remote_code - ) - - # Set pad token if not already set - if tokenizer.pad_token is None: - if tokenizer.eos_token is not None: - tokenizer.pad_token = tokenizer.eos_token - else: - # Fallback for models without eos_token - tokenizer.pad_token = tokenizer.unk_token - - return tokenizer - - -def create_tp_aware_dataloader( - model_name: str, - dataset_name: str, - seq_length: int, - batch_size: int, - dp_rank: int, - dp_size: int, - seed: int = 42, - dataset_percentage: float = 10.0, -) -> DataLoader: - """ - Create dataloader with TP-aware sharding. - - IMPORTANT: This function uses dp_rank and dp_size for data sharding, - NOT world_rank and world_size. This ensures that all TP ranks within - the same DP group see identical batches, which is required for - tensor parallelism to work correctly. - - Do NOT use ray.train.torch.prepare_data_loader() as it uses world_rank - for sharding, which would give different data to each TP rank. - - Args: - model_name: HuggingFace model name for tokenizer - dataset_name: HuggingFace dataset name - seq_length: Maximum sequence length - batch_size: Batch size per worker - dp_rank: Data parallel rank (NOT world rank) - dp_size: Data parallel size (NOT world size) - seed: Random seed for shuffling - dataset_percentage: Percentage of dataset to use (0-100) - - Returns: - DataLoader with TP-aware sharding - """ - world_rank = _get_world_rank() - - # Handle datasets that require a config name - dataset_config = None - if dataset_name == "wikitext": - dataset_config = "wikitext-2-raw-v1" - - split_spec = f"train[:{int(dataset_percentage)}%]" - - # Rank 0 downloads tokenizer and dataset first to avoid file handle conflicts - if world_rank == 0: - tokenizer = get_tokenizer(model_name) - dataset = load_dataset( - dataset_name, - dataset_config, - split=split_spec, - download_config=DownloadConfig(disable_tqdm=True), - ) - - # Wait for rank 0 to finish downloading - _barrier() - - # Other ranks load from cache - if world_rank != 0: - tokenizer = get_tokenizer(model_name) - dataset = load_dataset( - dataset_name, - dataset_config, - split=split_spec, - download_config=DownloadConfig(disable_tqdm=True), - ) - - def tokenize_function(examples): - return tokenizer( - examples["text"], - padding="max_length", - max_length=seq_length, - truncation=True, - ) - - # Tokenize the dataset - tokenized_dataset = dataset.map( - tokenize_function, - batched=True, - num_proc=1, - keep_in_memory=True, - remove_columns=dataset.column_names, - ) - - # Add labels column (same as input_ids for causal LM training) - def add_labels(examples): - examples["labels"] = examples["input_ids"].copy() - return examples - - tokenized_dataset = tokenized_dataset.map( - add_labels, - batched=True, - num_proc=1, - keep_in_memory=True, - ) - - tokenized_dataset.set_format( - type="torch", columns=["input_ids", "attention_mask", "labels"] - ) - - # Create DistributedSampler with DP rank/size (NOT world rank/size) - # This ensures all TP ranks in the same DP group get the same data - sampler = DistributedSampler( - tokenized_dataset, - num_replicas=dp_size, # Use DP size, not world size - rank=dp_rank, # Use DP rank, not world rank - shuffle=True, - seed=seed, - ) - - return DataLoader( - tokenized_dataset, - batch_size=batch_size, - sampler=sampler, - drop_last=True, - ) diff --git a/train_tensor_parallel/ddp_strategy.py b/train_tensor_parallel/ddp_strategy.py deleted file mode 100644 index 43b4046..0000000 --- a/train_tensor_parallel/ddp_strategy.py +++ /dev/null @@ -1,220 +0,0 @@ -"""DDP (Distributed Data Parallel) strategy for Ray Train as a baseline reference.""" - -import os -from typing import Any, Dict, Optional - -import torch -import torch.distributed as dist -import torch.nn as nn -from torch.nn.parallel import DistributedDataParallel as DDP - -from model_builder import create_model, get_model_config - - -class RayDDPStrategy: - """ - Standard DDP strategy as a baseline reference for comparison. - - This is the simplest distributed training approach: - - Each worker has a full copy of the model - - Data is sharded across workers (data parallelism only) - - Gradients are all-reduced across workers - - No tensor parallelism is used - this serves as a correctness baseline - to verify that the TP implementations produce similar loss curves. - """ - - def __init__(self): - self.model = None - self.ddp_model = None - self.optimizer = None - self.rank = None - self.world_size = None - self.device = None - # For compatibility with TPStrategy interface - self.tp_size = 1 - self.dp_size = None # Will be set to world_size - self.tp_rank = 0 - self.dp_rank = None # Will be set to world_rank - self.use_autocast = False - self.autocast_dtype = None - - def setup( - self, - model_name: str, - device: torch.device, - dtype: torch.dtype, - config: Dict[str, Any], - ) -> None: - """ - Set up DDP training. - - Args: - model_name: HuggingFace model name - device: Target device - dtype: Target dtype (bfloat16, float16, float32) - config: Training configuration dict - """ - self.device = device - self.rank = dist.get_rank() if dist.is_initialized() else 0 - self.world_size = dist.get_world_size() if dist.is_initialized() else 1 - - # For DDP: dp_size = world_size, tp_size = 1 - self.dp_size = self.world_size - self.dp_rank = self.rank - - if self.rank == 0: - print(f"[DDP] Setting up with world_size={self.world_size}") - - # Create model - model = create_model( - model_name=model_name, - device=device, - dtype=dtype, - num_layers=config.get("num_layers", 0), - attn_impl=config.get("attn_impl", "sdpa"), - ) - - # Apply activation checkpointing if requested - if config.get("activation_checkpointing", False): - model.gradient_checkpointing_enable() - if self.rank == 0: - print("[DDP] Enabled activation checkpointing") - - # Handle initial weights loading/saving for verification - init_weights_path = config.get("init_weights_path") - save_init_weights = config.get("save_init_weights", False) - - if init_weights_path: - if save_init_weights: - # Save initial weights (rank 0 only) - if self.rank == 0: - os.makedirs(os.path.dirname(init_weights_path), exist_ok=True) - torch.save(model.state_dict(), init_weights_path) - print(f"[DDP] Saved initial weights to {init_weights_path}") - # Synchronize to ensure file is written before other processes read - if dist.is_available() and dist.is_initialized(): - dist.barrier() - elif os.path.exists(init_weights_path): - # Load initial weights - state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) - model.load_state_dict(state_dict) - if self.rank == 0: - print(f"[DDP] Loaded initial weights from {init_weights_path}") - - # Wrap with DDP - self.model = model - self.ddp_model = DDP( - model, - device_ids=[device.index] if device.type == "cuda" else None, - output_device=device.index if device.type == "cuda" else None, - find_unused_parameters=False, - ) - - # Create optimizer - learning_rate = config.get("learning_rate", 1e-5) - weight_decay = config.get("weight_decay", 0.01) - self.optimizer = torch.optim.AdamW( - self.ddp_model.parameters(), - lr=learning_rate, - weight_decay=weight_decay, - ) - - if self.rank == 0: - num_params = sum(p.numel() for p in model.parameters()) - print(f"[DDP] Model initialized with {num_params:,} parameters") - print(f"[DDP] Data parallel across {self.world_size} workers") - - # Configure autocast - self.use_autocast = config.get("autocast", dtype in (torch.bfloat16, torch.float16)) - if self.use_autocast: - self.autocast_dtype = dtype - if self.rank == 0: - dtype_name = "bfloat16" if dtype == torch.bfloat16 else "float16" - print(f"[DDP] torch.autocast enabled with dtype={dtype_name}") - - def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - """Run forward pass with DDP model.""" - from contextlib import nullcontext - - autocast_ctx = ( - torch.autocast(device_type="cuda", dtype=self.autocast_dtype) - if self.use_autocast - else nullcontext() - ) - - with autocast_ctx: - outputs = self.ddp_model( - input_ids=batch["input_ids"], - attention_mask=batch["attention_mask"], - labels=batch["labels"], - use_cache=False, - ) - return outputs.loss - - def backward(self, loss: torch.Tensor) -> None: - """Run backward pass.""" - loss.backward() - - def optimizer_step(self) -> None: - """Run optimizer step.""" - self.optimizer.step() - - def zero_grad(self) -> None: - """Zero gradients.""" - self.optimizer.zero_grad(set_to_none=True) - - def train(self) -> None: - """Set model to training mode.""" - self.ddp_model.train() - - def eval(self) -> None: - """Set model to evaluation mode.""" - self.ddp_model.eval() - - def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Save checkpoint for DDP model. - - For DDP, only rank 0 needs to save since all ranks have identical weights. - """ - if self.rank != 0: - return - - # Create tag directory - tag_dir = os.path.join(checkpoint_dir, tag) - os.makedirs(tag_dir, exist_ok=True) - - # Save model state dict (unwrap DDP module) - model_path = os.path.join(tag_dir, "model.pt") - torch.save(self.model.state_dict(), model_path) - - # Save optimizer state dict - optim_path = os.path.join(tag_dir, "optimizer.pt") - torch.save(self.optimizer.state_dict(), optim_path) - - def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Load checkpoint for DDP model. - - All ranks load from the same checkpoint file. - """ - tag_dir = os.path.join(checkpoint_dir, tag) - - # Load model state dict - model_path = os.path.join(tag_dir, "model.pt") - if os.path.exists(model_path): - self.model.load_state_dict( - torch.load(model_path, map_location=self.device, weights_only=True) - ) - - # Load optimizer state dict - optim_path = os.path.join(tag_dir, "optimizer.pt") - if os.path.exists(optim_path): - self.optimizer.load_state_dict( - torch.load(optim_path, map_location=self.device, weights_only=True) - ) - - # Ensure all ranks are synchronized after loading - if dist.is_available() and dist.is_initialized(): - dist.barrier() diff --git a/train_tensor_parallel/fsdp_strategy.py b/train_tensor_parallel/fsdp_strategy.py deleted file mode 100644 index bdac543..0000000 --- a/train_tensor_parallel/fsdp_strategy.py +++ /dev/null @@ -1,285 +0,0 @@ -"""FSDP2 + DTensor tensor parallelism strategy adapted for Ray Train.""" - -import os -from contextlib import nullcontext -from typing import Any, Dict, Optional - -import torch -import torch.distributed as dist -import torch.nn as nn -from torch.distributed.device_mesh import init_device_mesh -from torch.distributed.tensor.parallel import parallelize_module - -from model_builder import ( - create_model, - get_model_config, - get_transformer_layers, -) - - -class RayFSDPStrategy: - """ - FSDP2 + DTensor tensor parallelism strategy adapted for Ray Train. - - Uses a 2D device mesh where: - - First dimension (dp) is for data parallelism via FSDP2 (fully_shard) - - Second dimension (tp) is for tensor parallelism via DTensor - - For example, with 8 GPUs, dp_size=2, tp_size=4: - - Device mesh shape: (2, 4) - - TP groups (same row): {0,1,2,3}, {4,5,6,7} - - DP groups (same column): {0,4}, {1,5}, {2,6}, {3,7} - """ - - def __init__(self, tp_size: int, dp_size: int = 1): - self.tp_size = tp_size - self.dp_size = dp_size - self.model = None - self.optimizer = None - self.device_mesh = None - self.tp_mesh = None - self.dp_mesh = None - self.tp_group = None - self.tp_rank = None - self.dp_rank = None - self.rank = None - self.world_size = None - self.device = None - self.use_autocast = False - self.autocast_dtype = None - - def setup( - self, - model_name: str, - device: torch.device, - dtype: torch.dtype, - config: Dict[str, Any], - ) -> None: - """ - Set up FSDP2 + DTensor with 2D device mesh. - - The mesh is organized as (dp, tp) where: - - dp dimension: FSDP2 shards optimizer states and gradients - - tp dimension: DTensor shards model weights for tensor parallelism - """ - from torch.distributed._composable.fsdp import MixedPrecisionPolicy, fully_shard - from torch.distributed.tensor.parallel import ColwiseParallel, RowwiseParallel - - self.device = device - self.rank = dist.get_rank() if dist.is_initialized() else 0 - self.world_size = dist.get_world_size() if dist.is_initialized() else 1 - - # Calculate TP and DP rank (same layout as AutoTP) - self.tp_rank = self.rank % self.tp_size - self.dp_rank = self.rank // self.tp_size - - # Validate configuration - if self.dp_size * self.tp_size != self.world_size: - raise ValueError( - f"dp_size ({self.dp_size}) * tp_size ({self.tp_size}) must equal " - f"world_size ({self.world_size})" - ) - - model_config = get_model_config(model_name) - if model_config.num_key_value_heads % self.tp_size != 0: - raise ValueError( - f"TP size {self.tp_size} must divide num_key_value_heads " - f"{model_config.num_key_value_heads}" - ) - - if self.rank == 0: - print( - f"[FSDP2+DTensor] Setting up 2D mesh: dp_size={self.dp_size}, tp_size={self.tp_size}" - ) - - # Create 2D device mesh: (dp, tp) - self.device_mesh = init_device_mesh( - "cuda", (self.dp_size, self.tp_size), mesh_dim_names=("dp", "tp") - ) - self.tp_mesh = self.device_mesh["tp"] - self.dp_mesh = self.device_mesh["dp"] - self.tp_group = self.tp_mesh.get_group() - - if self.rank == 0: - print(f"[FSDP2+DTensor] Device mesh created: {self.device_mesh}") - - # Create model - model = create_model( - model_name=model_name, - device=device, - dtype=dtype, - num_layers=config.get("num_layers", 0), - attn_impl=config.get("attn_impl", "sdpa"), - ) - - # Apply activation checkpointing if requested - if config.get("activation_checkpointing", False): - model.gradient_checkpointing_enable() - if self.rank == 0: - print("[FSDP2+DTensor] Enabled activation checkpointing") - - # Handle initial weights loading for verification - # This must happen BEFORE TP/FSDP sharding - init_weights_path = config.get("init_weights_path") - if init_weights_path and os.path.exists(init_weights_path): - state_dict = torch.load(init_weights_path, map_location=device, weights_only=True) - model.load_state_dict(state_dict) - if self.rank == 0: - print(f"[FSDP2+DTensor] Loaded initial weights from {init_weights_path}") - - # Get transformer layers - layers = get_transformer_layers(model) - if layers is None: - raise ValueError("Could not find transformer layers in model") - - # TP mapping for transformer layers (Qwen/Llama-style models) - tp_mapping = { - # Attention projections - "self_attn.q_proj": ColwiseParallel(), - "self_attn.k_proj": ColwiseParallel(), - "self_attn.v_proj": ColwiseParallel(), - "self_attn.o_proj": RowwiseParallel(), - # MLP projections - "mlp.gate_proj": ColwiseParallel(), - "mlp.up_proj": ColwiseParallel(), - "mlp.down_proj": RowwiseParallel(), - } - - if self.rank == 0: - print(f"[FSDP2+DTensor] Applying DTensor TP to {len(layers)} layers") - - # Apply DTensor TP to transformer layers - for layer in layers: - parallelize_module(layer, self.tp_mesh, tp_mapping) - - # Apply FSDP2 (fully_shard) if dp_size > 1 - mp_policy = MixedPrecisionPolicy(param_dtype=dtype, reduce_dtype=dtype) - - if self.dp_size > 1: - if self.rank == 0: - print("[FSDP2+DTensor] Applying FSDP2 to transformer layers") - - for layer in layers: - fully_shard(layer, mesh=self.dp_mesh, mp_policy=mp_policy) - - # Apply to the whole model - fully_shard(model, mesh=self.dp_mesh, mp_policy=mp_policy) - else: - if self.rank == 0: - print("[FSDP2+DTensor] dp_size=1, skipping FSDP sharding (TP only)") - - self.model = model - - # Create optimizer - # Note: Use foreach=False because DTensor doesn't support fused optimizer ops - learning_rate = config.get("learning_rate", 1e-5) - weight_decay = config.get("weight_decay", 0.01) - self.optimizer = torch.optim.AdamW( - self.model.parameters(), - lr=learning_rate, - weight_decay=weight_decay, - foreach=False, # Required for DTensor compatibility - ) - - if self.rank == 0: - num_params = sum(p.numel() for p in model.parameters()) - print(f"[FSDP2+DTensor] Model initialized with {num_params:,} parameters") - if self.dp_size > 1: - print(f"[FSDP2+DTensor] 2D parallelism: {self.dp_size} DP x {self.tp_size} TP") - - # Always enable autocast for mixed precision dtypes (bf16/fp16) - self.use_autocast = dtype in (torch.bfloat16, torch.float16) - if self.use_autocast: - self.autocast_dtype = dtype - if self.rank == 0: - dtype_name = "bfloat16" if dtype == torch.bfloat16 else "float16" - print(f"[FSDP2+DTensor] torch.autocast enabled with dtype={dtype_name}") - - def forward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - """Run forward pass with FSDP2+DTensor model.""" - autocast_ctx = ( - torch.autocast(device_type="cuda", dtype=self.autocast_dtype) - if self.use_autocast - else nullcontext() - ) - - with autocast_ctx: - outputs = self.model( - input_ids=batch["input_ids"], - attention_mask=batch["attention_mask"], - labels=batch["labels"], - use_cache=False, - ) - return outputs.loss - - def backward(self, loss: torch.Tensor) -> None: - """Run backward pass.""" - loss.backward() - - def forward_backward(self, batch: Dict[str, torch.Tensor]) -> torch.Tensor: - """ - Run forward and backward pass together. - - Returns: - The loss value (for logging) - """ - loss = self.forward(batch) - loss.backward() - return loss - - def optimizer_step(self) -> None: - """Run optimizer step.""" - self.optimizer.step() - - def zero_grad(self) -> None: - """Zero gradients.""" - self.optimizer.zero_grad(set_to_none=True) - - def train(self) -> None: - """Set model to training mode.""" - self.model.train() - - def eval(self) -> None: - """Set model to evaluation mode.""" - self.model.eval() - - def save_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Save checkpoint for FSDP2+DTensor model. - - For FSDP2, we use torch.save on the model state dict. - Each rank saves its own shard. - """ - import os - - # Create tag directory - tag_dir = os.path.join(checkpoint_dir, tag) - os.makedirs(tag_dir, exist_ok=True) - - # Save model state dict (each rank saves its shard) - model_path = os.path.join(tag_dir, f"model_rank{self.rank}.pt") - torch.save(self.model.state_dict(), model_path) - - # Save optimizer state dict - optim_path = os.path.join(tag_dir, f"optimizer_rank{self.rank}.pt") - torch.save(self.optimizer.state_dict(), optim_path) - - def load_checkpoint(self, checkpoint_dir: str, tag: str = "model") -> None: - """ - Load checkpoint for FSDP2+DTensor model. - - Each rank loads its corresponding shard. - """ - import os - - tag_dir = os.path.join(checkpoint_dir, tag) - - # Load model state dict - model_path = os.path.join(tag_dir, f"model_rank{self.rank}.pt") - if os.path.exists(model_path): - self.model.load_state_dict(torch.load(model_path, weights_only=True)) - - # Load optimizer state dict - optim_path = os.path.join(tag_dir, f"optimizer_rank{self.rank}.pt") - if os.path.exists(optim_path): - self.optimizer.load_state_dict(torch.load(optim_path, weights_only=True)) diff --git a/train_tensor_parallel/job_fsdp.yaml b/train_tensor_parallel/job.yaml similarity index 73% rename from train_tensor_parallel/job_fsdp.yaml rename to train_tensor_parallel/job.yaml index 4348257..0c8c12c 100644 --- a/train_tensor_parallel/job_fsdp.yaml +++ b/train_tensor_parallel/job.yaml @@ -1,15 +1,13 @@ # Anyscale Job: Ray Train + FSDP2 + DTensor Tensor Parallelism # This job runs training with PyTorch native FSDP2 + DTensor for tensor parallelism # -# For verification, run job_ddp.yaml FIRST to create shared initial weights. -# -# Submit with: anyscale job submit -f job_fsdp.yaml -# Or with custom args: anyscale job submit -f job_fsdp.yaml --entrypoint "python train_fsdp.py --tp_size 4 --dp_size 2 --num_workers 8" +# Submit with: anyscale job submit -f job.yaml +# Or with custom args: anyscale job submit -f job.yaml --entrypoint "python train.py --tp_size 4 --dp_size 2 --num_workers 8" -name: train-fsdp-dtensor +name: train-tp-fsdp-dtensor entrypoint: > - python train_fsdp.py + python train.py --model_name Qwen/Qwen2.5-0.5B --tp_size 2 --dp_size 2 diff --git a/train_tensor_parallel/job_ddp.yaml b/train_tensor_parallel/job_ddp.yaml deleted file mode 100644 index 0d7f68e..0000000 --- a/train_tensor_parallel/job_ddp.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Anyscale Job: Ray Train + DDP (Baseline) -# This job runs DDP training as a baseline and saves initial weights for verification. -# Run this FIRST before DeepSpeed/FSDP jobs to create shared initial weights. -# -# Submit with: anyscale job submit -f job_ddp.yaml - -name: train-ddp-baseline - -entrypoint: > - python train_ddp.py - --model_name Qwen/Qwen2.5-0.5B - --num_workers 2 - --dataset_name wikitext - --dataset_percentage 1.0 - --batch_size 2 - --seq_length 1024 - --num_epochs 1 - --learning_rate 1e-5 - --log_interval 1 - --debug_steps 100 - -image_uri: anyscale/ray:2.53.0-py312-cu128 - -working_dir: . - -requirements: - - torch>=2.9.1 - - transformers>=4.45.0 - - datasets>=3.0.0 - - accelerate>=1.0.0 - -compute_config: - head_node: - instance_type: m5.xlarge - worker_nodes: - - instance_type: g4dn.12xlarge - min_nodes: 1 - max_nodes: 1 - -env_vars: - RAY_TRAIN_V2_ENABLED: "1" - HF_HOME: /mnt/cluster_storage/huggingface - -max_retries: 0 - -tags: - project: tensor-parallelism - framework: ddp - role: baseline diff --git a/train_tensor_parallel/job_deepspeed.yaml b/train_tensor_parallel/job_deepspeed.yaml deleted file mode 100644 index fbbc60e..0000000 --- a/train_tensor_parallel/job_deepspeed.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# Anyscale Job: Ray Train + DeepSpeed AutoTP Tensor Parallelism -# This job runs training with DeepSpeed AutoTP for tensor parallelism -# -# For verification, run job_ddp.yaml FIRST to create shared initial weights. -# -# Submit with: anyscale job submit -f job_deepspeed.yaml -# Or with custom args: anyscale job submit -f job_deepspeed.yaml --entrypoint "python train_deepspeed.py --tp_size 4 --dp_size 2 --num_workers 8" - -name: train-deepspeed-autotp - -entrypoint: > - python train_deepspeed.py - --model_name Qwen/Qwen2.5-0.5B - --tp_size 2 - --dp_size 2 - --num_workers 4 - --dataset_name wikitext - --dataset_percentage 1.0 - --batch_size 2 - --seq_length 1024 - --num_epochs 1 - --learning_rate 1e-5 - --zero_stage 1 - --log_interval 1 - --debug_steps 100 - -image_uri: anyscale/ray:2.53.0-py312-cu128 - -working_dir: . - -requirements: - - torch>=2.9.1 - - deepspeed>=0.18.3 - - transformers>=4.45.0 - - datasets>=3.0.0 - - accelerate>=1.0.0 - -compute_config: - head_node: - instance_type: m5.xlarge - worker_nodes: - - instance_type: g4dn.12xlarge - min_nodes: 1 - max_nodes: 1 - -env_vars: - RAY_TRAIN_V2_ENABLED: "1" - HF_HOME: /mnt/cluster_storage/huggingface - -max_retries: 0 - -tags: - project: tensor-parallelism - framework: deepspeed diff --git a/train_tensor_parallel/loss_curves.png b/train_tensor_parallel/loss_curves.png deleted file mode 100644 index d0d6fd57f39852ab8ec1249c31608e7388acae9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 186835 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�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 diff --git a/train_tensor_parallel/model_builder.py b/train_tensor_parallel/model_builder.py deleted file mode 100644 index 3e9fc50..0000000 --- a/train_tensor_parallel/model_builder.py +++ /dev/null @@ -1,154 +0,0 @@ -"""Simplified model builder for tensor parallelism training.""" - -from contextlib import contextmanager -from dataclasses import dataclass - -import torch -import torch.nn as nn -from transformers import AutoConfig, AutoModelForCausalLM - - -@dataclass -class ModelConfig: - """Configuration dataclass for model specifications.""" - - name: str - hidden_size: int - num_attention_heads: int - num_key_value_heads: int - num_hidden_layers: int - intermediate_size: int - vocab_size: int - - -@contextmanager -def use_default_device(device): - """Context manager to temporarily set default device.""" - prev_device = torch.get_default_device() - torch.set_default_device(device) - try: - yield - finally: - torch.set_default_device(prev_device) - - -def get_model_config(model_name: str) -> ModelConfig: - """ - Get model configuration from HuggingFace. - - Args: - model_name: HuggingFace model name or local path - - Returns: - ModelConfig with model specifications - """ - hf_config = AutoConfig.from_pretrained(model_name, trust_remote_code=True) - - # Handle models with nested text_config (e.g., Qwen2.5-VL) - if hasattr(hf_config, "text_config"): - text_config = hf_config.text_config - else: - text_config = hf_config - - # Get hidden_size (GPT-2 uses n_embd) - hidden_size = getattr(text_config, "hidden_size", None) - if hidden_size is None: - hidden_size = getattr(text_config, "n_embd", 768) - - # Get num_attention_heads (GPT-2 uses n_head) - num_attention_heads = getattr(text_config, "num_attention_heads", None) - if num_attention_heads is None: - num_attention_heads = getattr(text_config, "n_head", 12) - - # Get num_hidden_layers (GPT-2 uses n_layer) - num_hidden_layers = getattr(text_config, "num_hidden_layers", None) - if num_hidden_layers is None: - num_hidden_layers = getattr(text_config, "n_layer", 12) - - # Get intermediate_size (GPT-2 uses n_inner, defaults to 4 * hidden_size) - intermediate_size = getattr(text_config, "intermediate_size", None) - if intermediate_size is None: - intermediate_size = getattr(text_config, "n_inner", None) - if intermediate_size is None: - intermediate_size = 4 * hidden_size - - return ModelConfig( - name=model_name, - hidden_size=hidden_size, - num_attention_heads=num_attention_heads, - num_key_value_heads=getattr( - text_config, "num_key_value_heads", num_attention_heads - ), - num_hidden_layers=num_hidden_layers, - intermediate_size=intermediate_size, - vocab_size=text_config.vocab_size, - ) - - -def create_model( - model_name: str, - device: torch.device, - dtype: torch.dtype, - num_layers: int = 0, - attn_impl: str = "sdpa", -) -> nn.Module: - """ - Create model with random initialization. - - Args: - model_name: HuggingFace model name or local path - device: Device to place the model on - dtype: Data type for model parameters - num_layers: Override number of layers (0 = use model default) - attn_impl: Attention implementation to use - - Returns: - Initialized model - """ - hf_config = AutoConfig.from_pretrained( - model_name, - trust_remote_code=True, - attn_implementation=attn_impl, - ) - - # Handle models with nested text_config (e.g., Qwen2.5-VL) - if hasattr(hf_config, "text_config"): - config_to_modify = hf_config.text_config - else: - config_to_modify = hf_config - - # Override number of layers if specified - if num_layers > 0: - original_layers = config_to_modify.num_hidden_layers - config_to_modify.num_hidden_layers = num_layers - print(f"Overriding num_hidden_layers: {original_layers} -> {num_layers}") - - # Create model with random initialization - with use_default_device(device): - model = AutoModelForCausalLM.from_config(hf_config, trust_remote_code=True) - - # Move to target dtype - model = model.to(dtype=dtype) - - return model - - -def get_transformer_layers(model: nn.Module): - """ - Get the list of transformer layers from the model. - - Args: - model: The model to get layers from - - Returns: - List or ModuleList of transformer layers, or None if not found - """ - # Qwen/Llama/Mistral structure: model.model.layers - if hasattr(model, "model") and hasattr(model.model, "layers"): - return model.model.layers - - # GPT-2 structure: model.transformer.h - if hasattr(model, "transformer") and hasattr(model.transformer, "h"): - return model.transformer.h - - return None diff --git a/train_tensor_parallel/plot_loss_curves.py b/train_tensor_parallel/plot_loss_curves.py deleted file mode 100644 index ade68cc..0000000 --- a/train_tensor_parallel/plot_loss_curves.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Plot loss curves from training runs. - -Usage: - python plot_loss_curves.py --input_dir /tmp/loss_curves --output loss_curves.png -""" - -import argparse -import json -import os - -import matplotlib.pyplot as plt -import numpy as np - - -def load_loss_history(filepath: str) -> dict: - """Load loss history from JSON file.""" - with open(filepath, "r") as f: - return json.load(f) - - -def plot_loss_curves(input_dir: str, output_path: str): - """Plot loss curves for all implementations.""" - # Load all loss histories - implementations = {} - - files = { - "DDP (TP=1, DP=2)": "loss_ddp.json", - "DeepSpeed AutoTP (TP=2, DP=2)": "loss_deepspeed.json", - "FSDP+DTensor (TP=2, DP=2)": "loss_fsdp.json", - } - - colors = { - "DDP (TP=1, DP=2)": "#1f77b4", # blue - "DeepSpeed AutoTP (TP=2, DP=2)": "#ff7f0e", # orange - "FSDP+DTensor (TP=2, DP=2)": "#2ca02c", # green - } - - for name, filename in files.items(): - filepath = os.path.join(input_dir, filename) - if os.path.exists(filepath): - data = load_loss_history(filepath) - implementations[name] = data["loss_history"] - print(f"Loaded {name}: {len(data['loss_history'])} steps") - else: - print(f"Warning: {filepath} not found") - - if not implementations: - print("No loss histories found!") - return - - # Create figure - fig, ax = plt.subplots(figsize=(10, 6)) - - # Plot loss curves - for name, losses in implementations.items(): - steps = list(range(len(losses))) - ax.plot(steps, losses, label=name, color=colors[name], linewidth=1.5) - - ax.set_xlabel("Step", fontsize=12) - ax.set_ylabel("Loss", fontsize=12) - ax.set_title("Training Loss Curves", fontsize=14) - ax.legend(loc="upper right", fontsize=10) - ax.grid(True, alpha=0.3) - - plt.tight_layout() - plt.savefig(output_path, dpi=150, bbox_inches="tight") - print(f"\nSaved plot to {output_path}") - - # Print statistics - print("\n" + "=" * 60) - print("LOSS CURVE STATISTICS") - print("=" * 60) - - for name, losses in implementations.items(): - losses_arr = np.array(losses) - print(f"\n{name}:") - print(f" Initial loss: {losses_arr[0]:.6f}") - print(f" Final loss: {losses_arr[-1]:.6f}") - print(f" Min loss: {losses_arr.min():.6f}") - print(f" Mean loss: {losses_arr.mean():.6f}") - - if "DDP (TP=1, DP=2)" in implementations: - ddp_losses = np.array(implementations["DDP (TP=1, DP=2)"]) - print("\n" + "-" * 60) - print("Differences from DDP baseline:") - for name, losses in implementations.items(): - if name == "DDP (TP=1, DP=2)": - continue - losses_arr = np.array(losses) - min_len = min(len(ddp_losses), len(losses_arr)) - diff = losses_arr[:min_len] - ddp_losses[:min_len] - print(f"\n{name}:") - print(f" Max abs diff: {np.abs(diff).max():.6f}") - print(f" Mean abs diff: {np.abs(diff).mean():.6f}") - print(f" Final diff: {diff[-1]:.6f}") - - plt.show() - - -def main(): - parser = argparse.ArgumentParser(description="Plot loss curves") - parser.add_argument("--input_dir", type=str, default="/tmp/loss_curves") - parser.add_argument("--output", type=str, default="loss_curves.png") - - args = parser.parse_args() - plot_loss_curves(args.input_dir, args.output) - - -if __name__ == "__main__": - main() diff --git a/train_tensor_parallel/run_verification_main.sh b/train_tensor_parallel/run_verification_main.sh deleted file mode 100755 index b6b64e2..0000000 --- a/train_tensor_parallel/run_verification_main.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash -# Verification script using main training scripts -# This script runs all three implementations with shared initial weights -# and generates a comparison plot. - -set -e - -# Configuration -MODEL_NAME="${MODEL_NAME:-Qwen/Qwen2.5-0.5B}" -NUM_LAYERS="${NUM_LAYERS:-2}" -BATCH_SIZE="${BATCH_SIZE:-2}" -SEQ_LENGTH="${SEQ_LENGTH:-1024}" -LEARNING_RATE="${LEARNING_RATE:-1e-5}" -DEBUG_STEPS="${DEBUG_STEPS:-100}" -LOG_INTERVAL="${LOG_INTERVAL:-10}" - -# Output directories -OUTPUT_DIR="${OUTPUT_DIR:-/tmp/loss_curves_verify}" -WEIGHTS_DIR="${WEIGHTS_DIR:-/tmp/shared_weights_verify}" -CHECKPOINT_DIR="${CHECKPOINT_DIR:-/tmp/ray_checkpoints_verify}" - -# Create directories -mkdir -p "$OUTPUT_DIR" "$WEIGHTS_DIR" - -echo "============================================================" -echo "Verification Using Main Training Scripts" -echo "============================================================" -echo "Model: $MODEL_NAME" -echo "Layers: $NUM_LAYERS" -echo "Batch size: $BATCH_SIZE" -echo "Sequence length: $SEQ_LENGTH" -echo "Learning rate: $LEARNING_RATE" -echo "Debug steps: $DEBUG_STEPS" -echo "Output dir: $OUTPUT_DIR" -echo "============================================================" - -# Step 1: Run DDP (baseline) and save initial weights -echo "" -echo "[Step 1/4] Running DDP (baseline) and saving initial weights..." -echo "------------------------------------------------------------" -python train_ddp.py \ - --model_name "$MODEL_NAME" \ - --num_workers 2 \ - --batch_size "$BATCH_SIZE" \ - --seq_length "$SEQ_LENGTH" \ - --num_layers "$NUM_LAYERS" \ - --num_epochs 1 \ - --learning_rate "$LEARNING_RATE" \ - --debug_steps "$DEBUG_STEPS" \ - --log_interval "$LOG_INTERVAL" \ - --loss_output_dir "$OUTPUT_DIR" \ - --storage_path "$CHECKPOINT_DIR" \ - --init_weights_path "$WEIGHTS_DIR/init_weights.pt" \ - --save_init_weights - -echo "" -echo "DDP completed. Initial weights saved to $WEIGHTS_DIR/init_weights.pt" - -# Step 2: Run DeepSpeed AutoTP with same weights -echo "" -echo "[Step 2/4] Running DeepSpeed AutoTP with shared weights..." -echo "------------------------------------------------------------" -python train_deepspeed.py \ - --model_name "$MODEL_NAME" \ - --tp_size 2 \ - --dp_size 2 \ - --num_workers 4 \ - --batch_size "$BATCH_SIZE" \ - --seq_length "$SEQ_LENGTH" \ - --num_layers "$NUM_LAYERS" \ - --num_epochs 1 \ - --learning_rate "$LEARNING_RATE" \ - --debug_steps "$DEBUG_STEPS" \ - --log_interval "$LOG_INTERVAL" \ - --loss_output_dir "$OUTPUT_DIR" \ - --storage_path "$CHECKPOINT_DIR" \ - --init_weights_path "$WEIGHTS_DIR/init_weights.pt" - -echo "" -echo "DeepSpeed AutoTP completed." - -# Step 3: Run FSDP+DTensor with same weights -echo "" -echo "[Step 3/4] Running FSDP+DTensor with shared weights..." -echo "------------------------------------------------------------" -python train_fsdp.py \ - --model_name "$MODEL_NAME" \ - --tp_size 2 \ - --dp_size 2 \ - --num_workers 4 \ - --batch_size "$BATCH_SIZE" \ - --seq_length "$SEQ_LENGTH" \ - --num_layers "$NUM_LAYERS" \ - --num_epochs 1 \ - --learning_rate "$LEARNING_RATE" \ - --debug_steps "$DEBUG_STEPS" \ - --log_interval "$LOG_INTERVAL" \ - --loss_output_dir "$OUTPUT_DIR" \ - --storage_path "$CHECKPOINT_DIR" \ - --init_weights_path "$WEIGHTS_DIR/init_weights.pt" \ - --autocast - -echo "" -echo "FSDP+DTensor completed." - -# Step 4: Plot and compare loss curves -echo "" -echo "[Step 4/4] Plotting loss curves..." -echo "------------------------------------------------------------" -python plot_loss_curves.py \ - --input_dir "$OUTPUT_DIR" \ - --output "$OUTPUT_DIR/loss_curves.png" - -echo "" -echo "============================================================" -echo "Verification Complete!" -echo "============================================================" -echo "Loss history files:" -ls -la "$OUTPUT_DIR"/*.json -echo "" -echo "Plot saved to: $OUTPUT_DIR/loss_curves.png" -echo "============================================================" diff --git a/train_tensor_parallel/train.py b/train_tensor_parallel/train.py new file mode 100644 index 0000000..434bc07 --- /dev/null +++ b/train_tensor_parallel/train.py @@ -0,0 +1,457 @@ +""" +Ray Train + FSDP2 + DTensor Tensor Parallelism Training Tutorial. + +This script demonstrates how to train large language models with tensor parallelism +using PyTorch native FSDP2 + DTensor and Ray Train for distributed execution. + +Key concepts: +- Tensor Parallelism (TP): Shards model weights across GPUs within a TP group +- Data Parallelism (DP): Replicates the model across DP groups, each processing different data +- 2D Parallelism: Combines TP and DP for scaling to many GPUs + +Example usage: + # 8 GPUs: 4-way tensor parallelism, 2-way data parallelism + python train_fsdp.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 2 \ + --num_workers 8 \ + --dataset_name wikitext \ + --batch_size 2 \ + --seq_length 2048 \ + --num_epochs 3 + + # 4 GPUs: 4-way tensor parallelism only + python train_fsdp.py \ + --model_name Qwen/Qwen2-7B \ + --tp_size 4 \ + --dp_size 1 \ + --num_workers 4 \ + --dataset_name wikitext \ + --num_epochs 3 +""" + +import json +import logging +import os +import tempfile +import uuid +from typing import Any, Dict + +os.environ["RAY_TRAIN_V2_ENABLED"] = "1" + +import torch +import torch.distributed as dist +from datasets import DownloadConfig, load_dataset +from torch.distributed._composable.fsdp import MixedPrecisionPolicy, fully_shard +from torch.distributed.device_mesh import init_device_mesh +from torch.distributed.tensor.parallel import ( + ColwiseParallel, + RowwiseParallel, + parallelize_module, +) +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler +from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer + +import ray.train +import ray.train.torch +from ray.train import Checkpoint, RunConfig, ScalingConfig +from ray.train.torch import TorchTrainer + +from args import get_args + +logger = logging.getLogger(__name__) + + +# ============================================================================= +# Data Loading +# ============================================================================= + + +def create_dataloader( + model_name: str, + dataset_name: str, + seq_length: int, + batch_size: int, + dp_rank: int, + dp_size: int, + seed: int = 42, + dataset_percentage: float = 10.0, +) -> DataLoader: + """ + Create dataloader with TP-aware sharding. + + IMPORTANT: Uses dp_rank/dp_size for sharding (NOT world_rank/world_size). + This ensures all TP ranks in the same DP group see identical batches. + """ + world_rank = ray.train.get_context().get_world_rank() + + # Handle datasets that require a config name + dataset_config = "wikitext-2-raw-v1" if dataset_name == "wikitext" else None + split_spec = f"train[:{int(dataset_percentage)}%]" + + # Rank 0 downloads first to avoid conflicts + if world_rank == 0: + tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) + dataset = load_dataset( + dataset_name, dataset_config, split=split_spec, + download_config=DownloadConfig(disable_tqdm=True), + ) + dist.barrier() + + # Other ranks load from cache + if world_rank != 0: + tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) + dataset = load_dataset( + dataset_name, dataset_config, split=split_spec, + download_config=DownloadConfig(disable_tqdm=True), + ) + + # Set pad token if needed + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + # Tokenize dataset + def tokenize_fn(examples): + return tokenizer( + examples["text"], padding="max_length", max_length=seq_length, truncation=True + ) + + tokenized = dataset.map( + tokenize_fn, batched=True, num_proc=1, keep_in_memory=True, + remove_columns=dataset.column_names, + ) + + # Add labels (same as input_ids for causal LM) + def add_labels(examples): + examples["labels"] = examples["input_ids"].copy() + return examples + + tokenized = tokenized.map(add_labels, batched=True, num_proc=1, keep_in_memory=True) + tokenized.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"]) + + # Use DP rank/size for sharding (ensures TP ranks get same data) + sampler = DistributedSampler( + tokenized, num_replicas=dp_size, rank=dp_rank, shuffle=True, seed=seed + ) + + return DataLoader(tokenized, batch_size=batch_size, sampler=sampler, drop_last=True) + + +# ============================================================================= +# Training Loop +# ============================================================================= + + +def train_loop_per_worker(config: Dict[str, Any]) -> None: + """ + Main training loop executed by each Ray Train worker. + + This function: + 1. Sets up the 2D device mesh for TP + DP + 2. Creates and shards the model with DTensor (TP) and FSDP2 (DP) + 3. Runs the training loop with checkpointing + """ + # Get Ray Train context + world_rank = ray.train.get_context().get_world_rank() + world_size = ray.train.get_context().get_world_size() + device = ray.train.torch.get_device() + + tp_size = config["tp_size"] + dp_size = config["dp_size"] + + if world_rank == 0: + logger.info(f"Worker started: world_rank={world_rank}, world_size={world_size}") + + # ------------------------------------------------------------------------- + # Step 1: Create 2D Device Mesh + # ------------------------------------------------------------------------- + # The mesh is organized as (dp, tp) where: + # - dp dimension: FSDP2 shards optimizer states and gradients + # - tp dimension: DTensor shards model weights for tensor parallelism + + # Calculate TP and DP rank + tp_rank = world_rank % tp_size + dp_rank = world_rank // tp_size + + # Validate configuration + if dp_size * tp_size != world_size: + raise ValueError( + f"dp_size ({dp_size}) * tp_size ({tp_size}) must equal " + f"world_size ({world_size})" + ) + + # Validate TP size divides num_key_value_heads (required for Qwen/Llama models) + hf_config = AutoConfig.from_pretrained(config["model_name"], trust_remote_code=True) + if hf_config.num_key_value_heads % tp_size != 0: + raise ValueError( + f"TP size {tp_size} must divide num_key_value_heads " + f"{hf_config.num_key_value_heads}" + ) + + if world_rank == 0: + logger.info(f"Setting up 2D mesh: dp_size={dp_size}, tp_size={tp_size}") + + # Create 2D device mesh: (dp, tp) + device_mesh = init_device_mesh( + "cuda", (dp_size, tp_size), mesh_dim_names=("dp", "tp") + ) + tp_mesh = device_mesh["tp"] + dp_mesh = device_mesh["dp"] + + if world_rank == 0: + logger.info(f"Device mesh created: {device_mesh}") + + # ------------------------------------------------------------------------- + # Step 2: Create and Shard Model + # ------------------------------------------------------------------------- + + dtype = torch.bfloat16 + + # Create model with random initialization on the target device + prev_device = torch.get_default_device() + torch.set_default_device(device) + model = AutoModelForCausalLM.from_config(hf_config).to(dtype=dtype) + torch.set_default_device(prev_device) + + # Get transformer layers for parallelization (Qwen model structure) + layers = model.model.layers + + # TP mapping for transformer layers (Qwen/Llama-style models) + # ColwiseParallel: splits output features across TP ranks + # RowwiseParallel: splits input features across TP ranks + tp_mapping = { + # Attention projections + "self_attn.q_proj": ColwiseParallel(), + "self_attn.k_proj": ColwiseParallel(), + "self_attn.v_proj": ColwiseParallel(), + "self_attn.o_proj": RowwiseParallel(), + # MLP projections + "mlp.gate_proj": ColwiseParallel(), + "mlp.up_proj": ColwiseParallel(), + "mlp.down_proj": RowwiseParallel(), + } + + if world_rank == 0: + logger.info(f"Applying DTensor TP to {len(layers)} layers") + + # Apply DTensor TP to transformer layers + for layer in layers: + parallelize_module(layer, tp_mesh, tp_mapping) + + # Apply FSDP2 (fully_shard) for data parallelism + mp_policy = MixedPrecisionPolicy(param_dtype=dtype, reduce_dtype=dtype) + + if dp_size > 1: + if world_rank == 0: + logger.info("Applying FSDP2 to transformer layers") + + for layer in layers: + fully_shard(layer, mesh=dp_mesh, mp_policy=mp_policy) + + # Apply to the whole model + fully_shard(model, mesh=dp_mesh, mp_policy=mp_policy) + else: + if world_rank == 0: + logger.info("dp_size=1, skipping FSDP sharding (TP only)") + + # Create optimizer + # Note: Use foreach=False because DTensor doesn't support fused optimizer ops + optimizer = torch.optim.AdamW( + model.parameters(), + lr=config.get("learning_rate", 1e-5), + weight_decay=config.get("weight_decay", 0.01), + foreach=False, + ) + + if world_rank == 0: + num_params = sum(p.numel() for p in model.parameters()) + logger.info(f"Model initialized with {num_params:,} parameters") + if dp_size > 1: + logger.info(f"2D parallelism: {dp_size} DP x {tp_size} TP") + logger.info("torch.autocast enabled with dtype=bfloat16") + + # ------------------------------------------------------------------------- + # Step 3: Create Dataloader + # ------------------------------------------------------------------------- + # IMPORTANT: Use dp_rank/dp_size for sharding, NOT world_rank/world_size + # This ensures all TP ranks in the same DP group see identical batches + + dataloader = create_dataloader( + model_name=config["model_name"], + dataset_name=config["dataset_name"], + seq_length=config["seq_length"], + batch_size=config["batch_size"], + dp_rank=dp_rank, + dp_size=dp_size, + seed=config.get("seed", 42), + dataset_percentage=config.get("dataset_percentage", 10.0), + ) + + steps_per_epoch = len(dataloader) + if world_rank == 0: + logger.info(f"Dataloader created: {steps_per_epoch} steps per epoch") + + # ------------------------------------------------------------------------- + # Step 4: Training Loop + # ------------------------------------------------------------------------- + + model.train() + + for epoch in range(config["num_epochs"]): + dataloader.sampler.set_epoch(epoch) + + running_loss = 0.0 + num_batches = 0 + + for step, batch in enumerate(dataloader): + # Move batch to device + batch = {k: v.to(device) for k, v in batch.items()} + + # Zero gradients + optimizer.zero_grad(set_to_none=True) + + # Forward pass with autocast + with torch.autocast(device_type="cuda", dtype=dtype): + outputs = model( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + labels=batch["labels"], + use_cache=False, + ) + loss = outputs.loss + + # Backward pass + loss.backward() + + # Optimizer step + optimizer.step() + + # Track loss + loss_value = loss.item() + running_loss += loss_value + num_batches += 1 + + # Log progress + if world_rank == 0 and step % config.get("log_interval", 10) == 0: + logger.info( + f"Epoch: {epoch} Step: {step + 1}/{steps_per_epoch} Loss: {loss_value:.4f}" + ) + + # Debug mode: stop early for testing + if config.get("debug_steps", 0) > 0 and step + 1 >= config["debug_steps"]: + if world_rank == 0: + logger.info(f"Debug steps finished. Stopping epoch {epoch}.") + break + + # Calculate average loss for epoch + avg_loss = running_loss / num_batches if num_batches > 0 else 0.0 + + # Save checkpoint at end of epoch + _save_checkpoint(model, optimizer, world_rank, epoch, step, avg_loss) + + if world_rank == 0: + logger.info(f"Epoch {epoch} completed. Average loss: {avg_loss:.4f}") + + +def _save_checkpoint( + model: torch.nn.Module, + optimizer: torch.optim.Optimizer, + world_rank: int, + epoch: int, + step: int, + avg_loss: float, +) -> None: + """Save checkpoint and report to Ray Train.""" + with tempfile.TemporaryDirectory() as tmp_dir: + checkpoint_dir = os.path.join(tmp_dir, "checkpoint") + os.makedirs(checkpoint_dir, exist_ok=True) + + # Each rank saves its model/optimizer shard + torch.save( + model.state_dict(), + os.path.join(checkpoint_dir, f"model_rank{world_rank}.pt"), + ) + torch.save( + optimizer.state_dict(), + os.path.join(checkpoint_dir, f"optimizer_rank{world_rank}.pt"), + ) + + # Save metadata (from rank 0 only) + if world_rank == 0: + with open(os.path.join(checkpoint_dir, "metadata.json"), "w") as f: + json.dump({"epoch": epoch, "step": step}, f) + + # All workers must call report() with their checkpoint + checkpoint = Checkpoint.from_directory(tmp_dir) + ray.train.report({"loss": avg_loss, "epoch": epoch}, checkpoint=checkpoint) + + +# ============================================================================= +# Main Entry Point +# ============================================================================= + + +def main(): + """Main entry point.""" + args = get_args() + + # Validate parallelism configuration + if args.tp_size * args.dp_size != args.num_workers: + raise ValueError( + f"tp_size ({args.tp_size}) * dp_size ({args.dp_size}) " + f"must equal num_workers ({args.num_workers})" + ) + + print(f"Configuration: {args}") + + # Build train_loop_config + train_loop_config = { + "model_name": args.model_name, + "tp_size": args.tp_size, + "dp_size": args.dp_size, + "dataset_name": args.dataset_name, + "dataset_percentage": args.dataset_percentage, + "batch_size": args.batch_size, + "seq_length": args.seq_length, + "num_epochs": args.num_epochs, + "learning_rate": args.learning_rate, + "weight_decay": args.weight_decay, + "log_interval": args.log_interval, + "debug_steps": args.debug_steps, + "seed": args.seed, + } + + # Configure Ray Train + scaling_config = ScalingConfig( + num_workers=args.num_workers, + use_gpu=True, + ) + + # Generate experiment name + name = args.experiment_name + if name is None: + name = f"fsdp_tp{args.tp_size}_dp{args.dp_size}_{uuid.uuid4().hex[:8]}" + + print(f"Experiment name: {name}") + + run_config = RunConfig( + storage_path=args.storage_path, + name=name, + ) + + # Create and run trainer + trainer = TorchTrainer( + train_loop_per_worker=train_loop_per_worker, + scaling_config=scaling_config, + train_loop_config=train_loop_config, + run_config=run_config, + ) + + result = trainer.fit() + print(f"Training finished. Result: {result}") + + +if __name__ == "__main__": + main() diff --git a/train_tensor_parallel/train_ddp.py b/train_tensor_parallel/train_ddp.py deleted file mode 100644 index 6494319..0000000 --- a/train_tensor_parallel/train_ddp.py +++ /dev/null @@ -1,425 +0,0 @@ -""" -Ray Train + DDP (Distributed Data Parallel) Training. - -This script serves as a baseline reference to verify the correctness of -tensor parallelism implementations. It uses standard PyTorch DDP for -data parallelism only (no tensor parallelism). - -Example usage: - # 4 GPUs: 4-way data parallelism - python train_ddp.py \ - --model_name Qwen/Qwen2-7B \ - --num_workers 4 \ - --dataset_name wikitext \ - --batch_size 2 \ - --seq_length 2048 \ - --num_epochs 3 - - # 8 GPUs: 8-way data parallelism - python train_ddp.py \ - --model_name Qwen/Qwen2-7B \ - --num_workers 8 \ - --dataset_name wikitext \ - --batch_size 1 \ - --seq_length 2048 \ - --num_epochs 3 -""" - -import argparse -import os -import uuid -from typing import Any, Dict - -os.environ["RAY_TRAIN_V2_ENABLED"] = "1" - -import torch - -import ray.train -import ray.train.torch -from ray.train import RunConfig, ScalingConfig -from ray.train.torch import TorchTrainer - -from common import ( - log_rank0, - save_checkpoint, - load_checkpoint, -) -from data import create_tp_aware_dataloader -from ddp_strategy import RayDDPStrategy - - -def train_loop_per_worker(config: Dict[str, Any]) -> None: - """ - Main training loop executed by each Ray Train worker. - - Args: - config: Training configuration dict - """ - # Get Ray Train context - ctx = ray.train.get_context() - world_rank = ctx.get_world_rank() - world_size = ctx.get_world_size() - device = ray.train.torch.get_device() - - log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") - - # Create and setup the DDP strategy - strategy = RayDDPStrategy() - - strategy.setup( - model_name=config["model_name"], - device=device, - dtype=torch.bfloat16, - config={ - "learning_rate": config["learning_rate"], - "weight_decay": config.get("weight_decay", 0.01), - "num_layers": config.get("num_layers", 0), - "attn_impl": config.get("attn_impl", "sdpa"), - "activation_checkpointing": config.get("activation_checkpointing", False), - "autocast": config.get("autocast", True), - "init_weights_path": config.get("init_weights_path"), - "save_init_weights": config.get("save_init_weights", False), - }, - ) - - # Run training loop - run_ddp_training_loop(strategy, config) - - -def run_ddp_training_loop( - strategy: RayDDPStrategy, - config: Dict[str, Any], -) -> None: - """ - Run the training loop for DDP. - - This is similar to run_training_loop in common.py but adapted for DDP - where all workers have the full model and use standard data sharding. - - Args: - strategy: The DDP strategy (already set up) - config: Training configuration dict - """ - world_rank = ray.train.get_context().get_world_rank() - device = ray.train.torch.get_device() - - # Create TP-aware dataloader (uses dp_rank/dp_size) - # For DDP: dp_rank = world_rank, dp_size = world_size - dataloader = create_tp_aware_dataloader( - model_name=config["model_name"], - dataset_name=config["dataset_name"], - seq_length=config["seq_length"], - batch_size=config["batch_size"], - dp_rank=strategy.dp_rank, - dp_size=strategy.dp_size, - seed=config.get("seed", 42), - dataset_percentage=config.get("dataset_percentage", 10.0), - ) - - steps_per_epoch = len(dataloader) - log_rank0(f"Dataloader created: {steps_per_epoch} steps per epoch") - - # Load checkpoint if resuming - checkpoint = ray.train.get_checkpoint() - start_epoch = 0 - if checkpoint: - metadata = load_checkpoint(strategy, checkpoint) - start_epoch = metadata.get("epoch", 0) + 1 - log_rank0(f"Resuming training from epoch {start_epoch}") - - # Set model to training mode - strategy.train() - - # Loss history tracking for verification - loss_history = [] - - # Training loop - for epoch in range(start_epoch, config["num_epochs"]): - # Set sampler epoch for different shuffling each epoch - dataloader.sampler.set_epoch(epoch) - - running_loss = 0.0 - num_batches = 0 - - for step, batch in enumerate(dataloader): - # Move batch to device - batch = {k: v.to(device) for k, v in batch.items()} - - # Zero gradients - strategy.zero_grad() - - # Forward pass - loss = strategy.forward(batch) - - # Backward pass - strategy.backward(loss) - - # Track per-step loss - loss_value = loss.item() - loss_history.append(loss_value) - - # Log progress - if world_rank == 0 and step % config.get("log_interval", 10) == 0: - log_rank0( - f"Epoch: {epoch} Step: {step + 1}/{steps_per_epoch} Loss: {loss_value:.4f}" - ) - - # Optimizer step - strategy.optimizer_step() - - running_loss += loss_value - num_batches += 1 - - # Debug mode: stop early for testing - if config.get("debug_steps", 0) > 0 and step + 1 >= config["debug_steps"]: - log_rank0(f"Debug steps finished. Stopping epoch {epoch}.") - break - - # Calculate average loss for epoch - avg_loss = running_loss / num_batches if num_batches > 0 else 0.0 - - # Save checkpoint at end of epoch - save_checkpoint( - strategy=strategy, - epoch=epoch, - step=step, - metrics={"loss": avg_loss, "epoch": epoch}, - ) - - log_rank0(f"Epoch {epoch} completed. Average loss: {avg_loss:.4f}") - - # Save loss history if output_dir is specified (rank 0 only) - output_dir = config.get("loss_output_dir") - if output_dir and world_rank == 0: - import json - os.makedirs(output_dir, exist_ok=True) - loss_file = os.path.join(output_dir, "loss_ddp.json") - with open(loss_file, "w") as f: - json.dump({"implementation": "ddp", "loss_history": loss_history}, f) - log_rank0(f"Loss history saved to {loss_file}") - - -def get_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Ray Train + DDP Distributed Data Parallel Training (Baseline)" - ) - - # Model configuration - parser.add_argument( - "--model_name", - type=str, - default="Qwen/Qwen2-7B", - help="HuggingFace model name or path", - ) - parser.add_argument( - "--num_layers", - type=int, - default=0, - help="Override number of layers (0 = use model default)", - ) - parser.add_argument( - "--attn_impl", - type=str, - default="sdpa", - choices=["sdpa", "flash_attention_2", "eager"], - help="Attention implementation", - ) - - # Parallelism configuration - parser.add_argument( - "--num_workers", - type=int, - required=True, - help="Number of workers (data parallelism degree)", - ) - - # Dataset configuration - parser.add_argument( - "--dataset_name", - type=str, - default="wikitext", - help="HuggingFace dataset name", - ) - parser.add_argument( - "--dataset_percentage", - type=float, - default=10.0, - help="Percentage of dataset to use (0-100)", - ) - - # Training configuration - parser.add_argument( - "--batch_size", - type=int, - default=1, - help="Per-GPU micro batch size", - ) - parser.add_argument( - "--seq_length", - type=int, - default=2048, - help="Maximum sequence length", - ) - parser.add_argument( - "--num_epochs", - type=int, - default=3, - help="Number of training epochs", - ) - parser.add_argument( - "--learning_rate", - type=float, - default=1e-5, - help="Learning rate", - ) - parser.add_argument( - "--weight_decay", - type=float, - default=0.01, - help="Weight decay", - ) - parser.add_argument( - "--activation_checkpointing", - action="store_true", - help="Enable activation/gradient checkpointing", - ) - parser.add_argument( - "--autocast", - action="store_true", - default=True, - help="Enable torch.autocast for mixed precision (default: True)", - ) - - # Checkpointing configuration - parser.add_argument( - "--storage_path", - type=str, - default="/mnt/cluster_storage", - help="Storage path for checkpoints", - ) - parser.add_argument( - "--experiment_name", - type=str, - default=None, - help="Experiment name (auto-generated if not provided)", - ) - parser.add_argument( - "--resume_from", - type=str, - default=None, - help="Experiment name to resume from", - ) - - # Logging and debugging - parser.add_argument( - "--log_interval", - type=int, - default=10, - help="Logging interval (steps)", - ) - parser.add_argument( - "--debug_steps", - type=int, - default=0, - help="Stop after this many steps per epoch (0 = run full epoch)", - ) - parser.add_argument( - "--seed", - type=int, - default=42, - help="Random seed", - ) - parser.add_argument( - "--loss_output_dir", - type=str, - default=None, - help="Directory to save per-step loss history JSON (for verification)", - ) - parser.add_argument( - "--init_weights_path", - type=str, - default=None, - help="Path to load initial model weights from (for verification across implementations)", - ) - parser.add_argument( - "--save_init_weights", - action="store_true", - help="Save initial model weights to --init_weights_path before training", - ) - - return parser.parse_args() - - -def main(): - """Main entry point.""" - args = get_args() - - print(f"Configuration: {args}") - - # Build train_loop_config - train_loop_config = { - # Model - "model_name": args.model_name, - "num_layers": args.num_layers, - "attn_impl": args.attn_impl, - # Parallelism - DDP has no TP, only DP - "tp_size": 1, - "dp_size": args.num_workers, - # Dataset - "dataset_name": args.dataset_name, - "dataset_percentage": args.dataset_percentage, - # Training - "batch_size": args.batch_size, - "seq_length": args.seq_length, - "num_epochs": args.num_epochs, - "learning_rate": args.learning_rate, - "weight_decay": args.weight_decay, - "activation_checkpointing": args.activation_checkpointing, - "autocast": args.autocast, - # Logging/debugging - "log_interval": args.log_interval, - "debug_steps": args.debug_steps, - "seed": args.seed, - # Loss output for verification - "loss_output_dir": args.loss_output_dir, - # Init weights for verification - "init_weights_path": args.init_weights_path, - "save_init_weights": args.save_init_weights, - } - - # Configure Ray Train - scaling_config = ScalingConfig( - num_workers=args.num_workers, - use_gpu=True, - ) - - # Generate experiment name - name = args.experiment_name - if name is None: - if args.resume_from is not None: - name = args.resume_from - else: - name = f"ddp_dp{args.num_workers}_{uuid.uuid4().hex[:8]}" - - print(f"Experiment name: {name}") - - run_config = RunConfig( - storage_path=args.storage_path, - name=name, - ) - - # Create and run trainer - trainer = TorchTrainer( - train_loop_per_worker=train_loop_per_worker, - scaling_config=scaling_config, - train_loop_config=train_loop_config, - run_config=run_config, - ) - - result = trainer.fit() - print(f"Training finished. Result: {result}") - - -if __name__ == "__main__": - main() diff --git a/train_tensor_parallel/train_deepspeed.py b/train_tensor_parallel/train_deepspeed.py deleted file mode 100644 index 30cc807..0000000 --- a/train_tensor_parallel/train_deepspeed.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -Ray Train + DeepSpeed AutoTP Tensor Parallelism Training. - -This script demonstrates training with DeepSpeed AutoTP tensor parallelism -using Ray Train's TorchTrainer for distributed execution and checkpointing. - -Example usage: - # 8 GPUs: 4-way tensor parallelism, 2-way data parallelism - python train_deepspeed.py \ - --model_name Qwen/Qwen2-7B \ - --tp_size 4 \ - --dp_size 2 \ - --num_workers 8 \ - --dataset_name wikitext \ - --batch_size 2 \ - --seq_length 2048 \ - --num_epochs 3 - - # 4 GPUs: 4-way tensor parallelism only - python train_deepspeed.py \ - --model_name Qwen/Qwen2-7B \ - --tp_size 4 \ - --dp_size 1 \ - --num_workers 4 \ - --dataset_name wikitext \ - --num_epochs 3 -""" - -import argparse -import os -from typing import Any, Dict - -os.environ["RAY_TRAIN_V2_ENABLED"] = "1" - -import deepspeed -import torch - -import ray.train -import ray.train.torch - -from autotp_strategy import RayAutoTPStrategy -from common import ( - add_common_args, - get_common_train_config, - log_rank0, - run_trainer, - run_training_loop, -) - - -def train_loop_per_worker(config: Dict[str, Any]) -> None: - """ - Main training loop executed by each Ray Train worker. - - Args: - config: Training configuration dict - """ - # Get Ray Train context - ctx = ray.train.get_context() - world_rank = ctx.get_world_rank() - world_size = ctx.get_world_size() - device = ray.train.torch.get_device() - - log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") - - # Initialize DeepSpeed distributed (will detect and use existing process group from Ray Train) - deepspeed.init_distributed() - - # Create and setup the AutoTP strategy - strategy = RayAutoTPStrategy( - tp_size=config["tp_size"], - dp_size=config["dp_size"], - ) - - strategy.setup( - model_name=config["model_name"], - device=device, - dtype=torch.bfloat16, - config={ - "batch_size": config["batch_size"], - "learning_rate": config["learning_rate"], - "weight_decay": config.get("weight_decay", 0.01), - "max_grad_norm": config.get("max_grad_norm", 1.0), - "zero_stage": config["zero_stage"], - "gradient_accumulation_steps": config.get("gradient_accumulation_steps", 1), - "num_layers": config.get("num_layers", 0), - "attn_impl": config.get("attn_impl", "sdpa"), - "activation_checkpointing": config.get("activation_checkpointing", False), - "vocab_parallel": config.get("vocab_parallel", False), - "init_weights_path": config.get("init_weights_path"), - }, - ) - - # Run common training loop - run_training_loop(strategy, config) - - -def get_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Ray Train + DeepSpeed AutoTP Tensor Parallelism Training" - ) - - # Add common arguments - add_common_args(parser) - - # DeepSpeed-specific arguments - parser.add_argument( - "--zero_stage", - type=int, - default=1, - choices=[0, 1, 2], - help="DeepSpeed ZeRO stage (0-2, ZeRO-3 not supported with AutoTP)", - ) - return parser.parse_args() - - -def main(): - """Main entry point.""" - args = get_args() - - # Build train_loop_config - train_loop_config = get_common_train_config(args) - train_loop_config["zero_stage"] = args.zero_stage - train_loop_config["impl_name"] = "deepspeed" - - # Run trainer - run_trainer( - args=args, - train_loop_per_worker=train_loop_per_worker, - train_loop_config=train_loop_config, - experiment_prefix="deepspeed_autotp", - ) - - -if __name__ == "__main__": - main() diff --git a/train_tensor_parallel/train_fsdp.py b/train_tensor_parallel/train_fsdp.py deleted file mode 100644 index fb9ed1e..0000000 --- a/train_tensor_parallel/train_fsdp.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Ray Train + FSDP2 + DTensor Tensor Parallelism Training. - -This script demonstrates training with PyTorch native FSDP2 + DTensor tensor parallelism -using Ray Train's TorchTrainer for distributed execution and checkpointing. - -Example usage: - # 8 GPUs: 4-way tensor parallelism, 2-way data parallelism - python train_fsdp.py \ - --model_name Qwen/Qwen2-7B \ - --tp_size 4 \ - --dp_size 2 \ - --num_workers 8 \ - --dataset_name wikitext \ - --batch_size 2 \ - --seq_length 2048 \ - --num_epochs 3 - - # 4 GPUs: 4-way tensor parallelism only - python train_fsdp.py \ - --model_name Qwen/Qwen2-7B \ - --tp_size 4 \ - --dp_size 1 \ - --num_workers 4 \ - --dataset_name wikitext \ - --num_epochs 3 -""" - -import argparse -import os -from typing import Any, Dict - -os.environ["RAY_TRAIN_V2_ENABLED"] = "1" - -import torch - -import ray.train -import ray.train.torch - -from common import ( - add_common_args, - get_common_train_config, - log_rank0, - run_trainer, - run_training_loop, -) -from fsdp_strategy import RayFSDPStrategy - - -def train_loop_per_worker(config: Dict[str, Any]) -> None: - """ - Main training loop executed by each Ray Train worker. - - Args: - config: Training configuration dict - """ - # Get Ray Train context - ctx = ray.train.get_context() - world_rank = ctx.get_world_rank() - world_size = ctx.get_world_size() - device = ray.train.torch.get_device() - - log_rank0(f"Worker started: world_rank={world_rank}, world_size={world_size}") - - # Create and setup the FSDP+DTensor strategy - strategy = RayFSDPStrategy( - tp_size=config["tp_size"], - dp_size=config["dp_size"], - ) - - strategy.setup( - model_name=config["model_name"], - device=device, - dtype=torch.bfloat16, - config={ - "learning_rate": config["learning_rate"], - "weight_decay": config.get("weight_decay", 0.01), - "num_layers": config.get("num_layers", 0), - "attn_impl": config.get("attn_impl", "sdpa"), - "activation_checkpointing": config.get("activation_checkpointing", False), - "vocab_parallel": config.get("vocab_parallel", False), - "init_weights_path": config.get("init_weights_path"), - }, - ) - - # Run common training loop - run_training_loop(strategy, config) - - -def get_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Ray Train + FSDP2 + DTensor Tensor Parallelism Training" - ) - - # Add common arguments - add_common_args(parser) - - return parser.parse_args() - - -def main(): - """Main entry point.""" - args = get_args() - - # Build train_loop_config - train_loop_config = get_common_train_config(args) - train_loop_config["impl_name"] = "fsdp" - - # Run trainer - run_trainer( - args=args, - train_loop_per_worker=train_loop_per_worker, - train_loop_config=train_loop_config, - experiment_prefix="fsdp_dtensor", - ) - - -if __name__ == "__main__": - main()