| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import torch |
| from torch import Tensor |
| from torch.distributed import ProcessGroup, all_gather, broadcast_object_list, get_process_group_ranks, get_world_size |
| from torch.distributed.utils import _verify_param_shape_across_processes |
|
|
| from imaginaire.utils import distributed |
|
|
|
|
| def split_inputs_cp(x: Tensor, seq_dim: int, cp_group: ProcessGroup) -> Tensor: |
| """ |
| Split input tensor along the sequence dimension for checkpoint parallelism. |
| |
| This function divides the input tensor into equal parts along the specified |
| sequence dimension, based on the number of ranks in the checkpoint parallelism group. |
| It then selects the part corresponding to the current rank. |
| |
| Args: |
| x: Input tensor to be split. |
| seq_dim: The dimension along which to split the input (sequence dimension). |
| cp_group: The process group for checkpoint parallelism. |
| |
| Returns: |
| A slice of the input tensor corresponding to the current rank. |
| |
| Raises: |
| AssertionError: If the sequence dimension is not divisible by the number of ranks. |
| """ |
| |
| if x.device.type != "cuda": |
| x = x.cuda() |
|
|
| cp_ranks = get_process_group_ranks(cp_group) |
| cp_size = len(cp_ranks) |
|
|
| assert x.shape[seq_dim] % cp_size == 0, f"{x.shape[seq_dim]} cannot divide cp_size {cp_size}" |
| x = x.view(*x.shape[:seq_dim], cp_size, x.shape[seq_dim] // cp_size, *x.shape[(seq_dim + 1) :]) |
| seq_idx = torch.tensor([cp_group.rank()], device=x.device) |
| x = x.index_select(seq_dim, seq_idx) |
| |
| x = x.view(*x.shape[:seq_dim], -1, *x.shape[(seq_dim + 2) :]) |
| return x |
|
|
|
|
| def cat_outputs_cp(x: Tensor, seq_dim: int, cp_group: ProcessGroup) -> Tensor: |
| """ |
| Concatenate outputs from different ranks in the checkpoint parallelism group. |
| |
| This function gathers tensors from all ranks in the checkpoint parallelism group |
| and concatenates them along the specified sequence dimension. |
| |
| Args: |
| x: Input tensor to be concatenated. |
| seq_dim: The dimension along which to concatenate the tensors (sequence dimension). |
| cp_group: The process group for checkpoint parallelism. |
| |
| Returns: |
| A tensor that is the concatenation of tensors from all ranks in the cp_group. |
| |
| Raises: |
| RuntimeError: If the gather operation fails. |
| """ |
| |
| world_size = get_world_size(cp_group) |
|
|
| |
| gathered_tensors = [torch.zeros_like(x) for _ in range(world_size)] |
|
|
| |
| try: |
| all_gather(gathered_tensors, x, group=cp_group) |
| except RuntimeError as e: |
| raise RuntimeError(f"Failed to gather tensors: {e}") |
|
|
| |
| return torch.cat(gathered_tensors, dim=seq_dim) |
|
|
|
|
| def cat_outputs_cp_with_grad(x: Tensor, seq_dim: int, cp_group: ProcessGroup) -> Tensor: |
| """ |
| Concatenate outputs from different ranks in the context parallelism group. |
| |
| This function gathers tensors from all ranks in the checkpoint parallelism group |
| and concatenates them along the specified sequence dimension. |
| |
| It retains computational graph locally for each rank by replacing the portion of the tensor with original output. |
| |
| Args: |
| x: Input tensor to be concatenated. |
| seq_dim: The dimension along which to concatenate the tensors (sequence dimension). |
| cp_group: The process group for checkpoint parallelism. |
| |
| Returns: |
| A tensor that is the concatenation of tensors from all ranks in the cp_group. |
| |
| Raises: |
| RuntimeError: If the gather operation fails. |
| """ |
| |
| cp_size = cp_group.size() |
| assert cp_size > 0, "cp_size should be greater than 0" |
|
|
| |
| gathered_tensors = [torch.zeros_like(x) for _ in range(cp_size)] |
|
|
| |
| try: |
| all_gather(gathered_tensors, x, group=cp_group) |
| except RuntimeError as e: |
| raise RuntimeError(f"Failed to gather tensors: {e}") |
|
|
| rank = cp_group.rank() |
| gathered_tensors[rank] = x |
| |
| return torch.cat(gathered_tensors, dim=seq_dim) |
|
|
|
|
| def robust_broadcast(tensor: torch.Tensor, src: int, pg: ProcessGroup, is_check_shape: bool = False) -> torch.Tensor: |
| """ |
| Perform a robust broadcast operation that works regardless of tensor shapes on different ranks. |
| |
| Args: |
| tensor (torch.Tensor): The tensor to broadcast (on src rank) or receive (on other ranks). |
| src (int): The source rank for the broadcast. Defaults to 0. |
| |
| Returns: |
| torch.Tensor: The broadcasted tensor on all ranks. |
| """ |
| |
| if tensor.device.type != "cuda": |
| tensor = tensor.cuda() |
|
|
| |
| if distributed.get_rank() == src: |
| shape = torch.tensor(tensor.shape).cuda() |
| else: |
| shape = torch.empty(tensor.dim(), dtype=torch.long).cuda() |
| if is_check_shape: |
| _verify_param_shape_across_processes(pg, [shape]) |
| torch.distributed.broadcast(shape, src, group=pg) |
|
|
| |
| if distributed.get_rank() != src: |
| tensor = tensor.new_empty(shape.tolist()).type_as(tensor) |
|
|
| |
| torch.distributed.broadcast(tensor, src, group=pg) |
|
|
| return tensor |
|
|
|
|
| def broadcast(item: torch.Tensor | str | None, process_group: ProcessGroup | None = None) -> torch.Tensor | str | None: |
| """ |
| Broadcast the item from the minimum rank in the specified group(s). |
| """ |
| if process_group is None: |
| return item |
|
|
| min_rank = min(get_process_group_ranks(process_group)) |
| if isinstance(item, torch.Tensor): |
| |
| if item.device.type != "cuda": |
| item = item.cuda() |
| item = robust_broadcast(item, min_rank, process_group) |
| elif item is not None: |
| broadcastable_list = [item] |
| broadcast_object_list(broadcastable_list, min_rank, group=process_group) |
| item = broadcastable_list[0] |
| return item |
|
|
|
|
| def broadcast_split_tensor( |
| tensor: torch.Tensor, |
| seq_dim: int, |
| process_group: ProcessGroup | None = None, |
| ) -> torch.Tensor: |
| """ |
| Broadcast the tensor from the minimum rank in the specified group(s). |
| """ |
| if tensor is None: |
| return tensor |
|
|
| |
| if tensor.device.type != "cuda": |
| tensor = tensor.cuda() |
|
|
| min_rank = min(get_process_group_ranks(process_group)) |
| tensor = robust_broadcast(tensor, min_rank, process_group) |
| return split_inputs_cp(tensor, seq_dim, process_group) |
|
|