159 lines
5.9 KiB
Python
159 lines
5.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Adapted from https://github.com/vllm-project/vllm/blob/v0.7.3/vllm/distributed/communication_op.py
|
|
|
|
import torch
|
|
|
|
from fastvideo.distributed.parallel_state import (get_sp_group, get_sp_world_size, get_tp_group,
|
|
model_parallel_is_initialized)
|
|
from fastvideo.distributed.utils import (unpad_sequence_tensor, compute_padding_for_sp, pad_sequence_tensor)
|
|
from fastvideo.logger import init_logger
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
# Track if SP communication has been warmed up
|
|
_sp_warmup_done = False
|
|
|
|
|
|
def tensor_model_parallel_all_reduce(input_: torch.Tensor) -> torch.Tensor:
|
|
"""All-reduce the input tensor across model parallel group."""
|
|
return get_tp_group().all_reduce(input_)
|
|
|
|
|
|
def tensor_model_parallel_all_gather(input_: torch.Tensor, dim: int = -1) -> torch.Tensor:
|
|
"""All-gather the input tensor across model parallel group."""
|
|
return get_tp_group().all_gather(input_, dim)
|
|
|
|
|
|
# TODO: remove model, make it sequence_parallel
|
|
def sequence_model_parallel_all_to_all_4D(input_: torch.Tensor,
|
|
scatter_dim: int = 2,
|
|
gather_dim: int = 1) -> torch.Tensor:
|
|
"""All-to-all communication of 4D tensors (e.g. QKV matrices) across sequence parallel group."""
|
|
return get_sp_group().all_to_all_4D(input_, scatter_dim, gather_dim)
|
|
|
|
|
|
def sequence_model_parallel_all_gather(input_: torch.Tensor, dim: int = -1) -> torch.Tensor:
|
|
"""All-gather the input tensor across model parallel group."""
|
|
return get_sp_group().all_gather(input_, dim)
|
|
|
|
|
|
def sequence_model_parallel_all_gather_with_unpad(input_: torch.Tensor,
|
|
original_seq_len: int,
|
|
dim: int = -1) -> torch.Tensor:
|
|
"""All-gather the input tensor and remove padding.
|
|
|
|
Args:
|
|
input_: Sharded (and possibly padded) tensor to gather
|
|
original_seq_len: Original sequence length before padding
|
|
dim: Dimension to gather along (default: -1)
|
|
|
|
Returns:
|
|
Tensor: Gathered and unpadded tensor
|
|
"""
|
|
|
|
# First gather across all ranks
|
|
gathered = get_sp_group().all_gather(input_, dim)
|
|
|
|
current_seq_len = gathered.shape[dim]
|
|
if current_seq_len > original_seq_len:
|
|
gathered = unpad_sequence_tensor(gathered, original_seq_len, seq_dim=dim)
|
|
|
|
return gathered
|
|
|
|
|
|
def sequence_model_parallel_shard(input_: torch.Tensor, dim: int = 1) -> tuple[torch.Tensor, int]:
|
|
"""Shard the input tensor across model parallel group with optional padding.
|
|
|
|
Args:
|
|
input_: Input tensor to shard
|
|
dim: Dimension to shard along (default: 1)
|
|
|
|
Returns:
|
|
tuple: (sharded_tensor, original_seq_len)
|
|
- sharded_tensor: The sharded (and possibly padded) tensor
|
|
- original_seq_len: Original sequence length before padding
|
|
"""
|
|
|
|
sp_world_size = get_sp_world_size()
|
|
|
|
original_seq_len = input_.shape[dim]
|
|
|
|
# Compute padding if needed
|
|
padded_seq_len, padding_amount = compute_padding_for_sp(original_seq_len, sp_world_size)
|
|
|
|
# Pad if necessary
|
|
if padding_amount > 0:
|
|
input_ = pad_sequence_tensor(input_, padded_seq_len, seq_dim=dim)
|
|
|
|
# Sharding with autograd-aware backward (all-gather in backward).
|
|
input_ = get_sp_group().shard(input_, dim=dim, scale_grad=True)
|
|
|
|
return input_, original_seq_len
|
|
|
|
|
|
def warmup_sequence_parallel_communication(device: torch.device | None = None) -> None:
|
|
"""Warmup NCCL communicators for sequence parallel all-to-all operations.
|
|
|
|
The first NCCL collective operation is slow due to lazy communicator
|
|
initialization. This function runs dummy all-to-all operations to
|
|
trigger the initialization upfront, before the first real forward pass.
|
|
|
|
Args:
|
|
device: Device to use for warmup tensors. If None, uses CUDA device 0.
|
|
"""
|
|
global _sp_warmup_done
|
|
|
|
if _sp_warmup_done:
|
|
return
|
|
|
|
if not model_parallel_is_initialized():
|
|
return
|
|
|
|
sp_world_size = get_sp_world_size()
|
|
if sp_world_size <= 1:
|
|
_sp_warmup_done = True
|
|
return
|
|
|
|
if device is None:
|
|
device = torch.device("cuda")
|
|
|
|
logger.info("Warming up sequence parallel communication (SP=%d)...", sp_world_size)
|
|
|
|
# Use small but representative tensor shapes for warmup
|
|
# Shape: [batch, seq_len, num_heads, head_dim]
|
|
# The all-to-all patterns used in attention:
|
|
# 1. scatter_dim=2 (heads), gather_dim=1 (seq) - before attention
|
|
# 2. scatter_dim=1 (seq), gather_dim=2 (heads) - after attention
|
|
batch_size = 1
|
|
seq_len_per_rank = 16 # Small sequence per rank
|
|
num_heads = sp_world_size * 4 # Must be divisible by sp_world_size
|
|
head_dim = 64
|
|
|
|
# Create dummy tensor for warmup
|
|
dummy = torch.zeros(batch_size, seq_len_per_rank, num_heads, head_dim, device=device, dtype=torch.bfloat16)
|
|
|
|
# Warmup pattern 1: scatter heads, gather sequence (before attention)
|
|
_ = sequence_model_parallel_all_to_all_4D(dummy, scatter_dim=2, gather_dim=1)
|
|
|
|
# Warmup pattern 2: scatter sequence, gather heads (after attention)
|
|
dummy2 = torch.zeros(batch_size,
|
|
seq_len_per_rank * sp_world_size,
|
|
num_heads // sp_world_size,
|
|
head_dim,
|
|
device=device,
|
|
dtype=torch.bfloat16)
|
|
_ = sequence_model_parallel_all_to_all_4D(dummy2, scatter_dim=1, gather_dim=2)
|
|
|
|
# Warmup all-gather (used for replicated tokens)
|
|
dummy3 = torch.zeros(batch_size, 8, num_heads // sp_world_size, head_dim, device=device, dtype=torch.bfloat16)
|
|
_ = sequence_model_parallel_all_gather(dummy3, dim=2)
|
|
|
|
# Synchronize to ensure warmup completes
|
|
torch.cuda.synchronize(device)
|
|
|
|
# Clean up
|
|
del dummy, dummy2, dummy3
|
|
|
|
_sp_warmup_done = True
|
|
logger.info("Sequence parallel communication warmup complete.")
|