Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

724 lines
35 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import inspect
import math
import torch
import torch.distributed as dist
from functools import lru_cache, partial
from torch.distributed import init_device_mesh
from transformers import PreTrainedTokenizer
from types import SimpleNamespace
from typing import Optional
from swift.utils import HfConfigFactory, get_cu_seqlens_from_position_ids, get_device, get_dist_setting
from .ulysses import DistributedAttention
from .zigzag_ring_attn import zigzag_ring_flash_attn_varlen_func
@lru_cache(maxsize=None)
def get_signature_parameters(fn):
try:
return inspect.signature(fn).parameters
except (TypeError, ValueError):
return None
def has_signature_parameter(fn, parameter: str) -> bool:
parameters = get_signature_parameters(fn)
return parameters is not None and parameter in parameters
def call_with_supported_kwargs(fn, *args, **kwargs):
parameters = get_signature_parameters(fn)
if parameters is None:
return fn(*args, **kwargs)
if not any(param.kind == inspect.Parameter.VAR_KEYWORD for param in parameters.values()):
kwargs = {key: value for key, value in kwargs.items() if key in parameters}
return fn(*args, **kwargs)
def _call_create_causal_mask(fn, config, input_embeds, attention_mask, cache_position_or_past_key_values, *args,
**kwargs):
if has_signature_parameter(fn, 'cache_position'):
return call_with_supported_kwargs(
fn,
config,
input_embeds,
attention_mask,
cache_position_or_past_key_values,
*args,
**kwargs,
)
if cache_position_or_past_key_values is None and 'past_key_values' in kwargs:
return call_with_supported_kwargs(fn, config, input_embeds, attention_mask, *args, **kwargs)
return call_with_supported_kwargs(
fn,
config,
input_embeds,
attention_mask,
cache_position_or_past_key_values,
*args,
**kwargs,
)
class SequenceParallel:
_global_inited: bool = False
def __init__(self):
self.sp_world_size = None
self.dp_world_size = None
self.rp_world_size = None
self.world_size = None
self.model_dtype = None
self.tokenizer = None
self.device_mesh = None
self.num_heads = None
self.causal_mask_func = None
self.extra_kwargs = {}
@property
def real_position_ids(self) -> torch.Tensor:
"""The real position ids, this is different from the position_ids in mrope"""
return self.extra_kwargs.get('text_position_ids')
def _prepare_flash_attn(self, base_model: torch.nn.Module):
try:
from transformers import masking_utils
_origin_flash_attention_mask = masking_utils.flash_attention_mask
def flash_attention_mask(*args, **kwargs):
if self.world_size == 1:
return _origin_flash_attention_mask(*args, **kwargs)
attention_mask = kwargs.get('attention_mask')
if attention_mask is not None:
if attention_mask.all():
attention_mask = None
return attention_mask
masking_utils.flash_attention_mask = flash_attention_mask
masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping['flash_attention_2'] = flash_attention_mask
def sdpa_mask(*args, **kwargs):
if self.world_size == 1:
return masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping['sdpa_origin'](*args, **kwargs)
if 'cache_position' in kwargs:
device = kwargs['cache_position'].device
else:
# transformers>=5.4.0
device = kwargs['device']
cache_position = self.real_position_ids[0]
cache_position = self.pad(cache_position, padding_value=-1, position_ids=self.real_position_ids, dim=0)
cache_position = torch.arange(0, cache_position.shape[0], device=device)
kwargs['kv_length'] = cache_position.shape[0]
if 'cache_position' in kwargs:
kwargs['cache_position'] = cache_position
else:
kwargs['q_length'] = kwargs['kv_length']
return masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping['sdpa_origin'](*args, **kwargs)
masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping[
'sdpa_origin'] = masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping['sdpa']
masking_utils.ALL_MASK_ATTENTION_FUNCTIONS._global_mapping['sdpa'] = sdpa_mask
def create_causal_mask(config,
input_embeds,
attention_mask,
cache_position_or_past_key_values=None,
*args,
**kwargs):
if self.world_size == 1:
return _call_create_causal_mask(masking_utils.origin_create_causal_mask, config, input_embeds,
attention_mask, cache_position_or_past_key_values, *args, **kwargs)
input_embeds = torch.ones(
(input_embeds.shape[0], input_embeds.shape[1] * self.sp_world_size, input_embeds.shape[2]),
dtype=input_embeds.dtype,
device=input_embeds.device)
if has_signature_parameter(masking_utils.origin_create_causal_mask, 'cache_position'):
cache_position_or_past_key_values = torch.arange(
0,
input_embeds.shape[1],
device=input_embeds.device,
)
return _call_create_causal_mask(masking_utils.origin_create_causal_mask, config, input_embeds,
attention_mask, cache_position_or_past_key_values, *args, **kwargs)
masking_utils.origin_create_causal_mask = masking_utils.create_causal_mask
masking_utils.create_causal_mask = create_causal_mask
except ImportError:
pass
if hasattr(base_model, 'language_model'):
text_model = base_model.language_model
else:
text_model = base_model
from transformers.modeling_flash_attention_utils import is_flash_attn_available
if is_flash_attn_available():
# TODO this works for multi-modal models like qwen2.5-vl
# SDPA is not supported, because we need to copy the code to our project, which will bring
# more works for maintaining.
from transformers import modeling_flash_attention_utils
from transformers.modeling_flash_attention_utils import _flash_attention_forward
_distributed_flash_attention = DistributedAttention(_flash_attention_forward, self)
modeling_flash_attention_utils._flash_attention_forward_origin = _flash_attention_forward
def flash_attention_forward(query_states: torch.Tensor, key_states: torch.Tensor,
value_states: torch.Tensor, attention_mask: Optional[torch.Tensor], q_len,
*args, **kwargs):
if self.world_size == 1:
return _flash_attention_forward(query_states, key_states, value_states, attention_mask, q_len,
*args, **kwargs)
return _distributed_flash_attention(query_states, key_states, value_states, attention_mask,
q_len * self.sp_world_size, *args, **kwargs)
modeling_flash_attention_utils._flash_attention_forward = flash_attention_forward
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
def local_flash_attn(module: torch.nn.Module, query_states, key_states, value_states, attention_mask, *args,
dist_attn, **kwargs):
if self.world_size == 1 or module.__class__ not in [m.__class__ for m in text_model.modules()]:
return ALL_ATTENTION_FUNCTIONS['flash_attention_2_origin'](module, query_states, key_states,
value_states, attention_mask, *args,
**kwargs)
if dist_attn.local_attn is None:
def _attention(query, key, value, *args, **kwargs):
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
if self.rp_world_size is not None and self.rp_world_size > 1:
position_ids = kwargs['position_ids']
cu_seqlens = get_cu_seqlens_from_position_ids(position_ids)
max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item()
position_ids = self._split_packed(position_ids, cu_seqlens)
mask = position_ids != -1
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
# this is important
# the length is correct, but the value is wrong, by mask qkv, we do:
# mask the padded values of q and v to zero
# mask the padded values of k to -1e5
# to keep the attention correct
query, key, value = self._mask_qkv(query, key, value, mask)
output = zigzag_ring_flash_attn_varlen_func(
query,
key,
value,
cu_seqlens=cu_seqlens,
max_seqlen=max_seqlen,
causal=module.is_causal,
dropout_p=kwargs.get('dropout', 0.0),
softmax_scale=kwargs.get('scaling', 0.0),
window_size=kwargs.get('sliding_window') or (-1, -1),
group=self.rp_group)
return output
else:
if 'cu_seq_lens_q' in kwargs:
position_ids = kwargs.get('position_ids')
if self.real_position_ids is not None:
position_ids = self.real_position_ids
position_ids = self.pad(position_ids, padding_value=-1, position_ids=position_ids)
cu_seqlens = get_cu_seqlens_from_position_ids(position_ids)
max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item()
assert query.shape[2] == cu_seqlens[-1]
kwargs['cu_seq_lens_q'] = cu_seqlens
kwargs['cu_seq_lens_k'] = cu_seqlens
kwargs['max_length_q'] = max_seqlen
kwargs['max_length_k'] = max_seqlen
return ALL_ATTENTION_FUNCTIONS['flash_attention_2_origin'](module, query, key, value, *args,
**kwargs)[0]
dist_attn.local_attn = _attention
return dist_attn(
query_states.transpose(1, 2), key_states.transpose(1, 2), value_states.transpose(1, 2), attention_mask,
*args, **kwargs), None
def local_sdpa_attn(module: torch.nn.Module, query_states, key_states, value_states, attention_mask, *args,
dist_attn, **kwargs):
# Bypass SP logic when world_size == 1 (SP disabled) or module not in text_model
if self.world_size == 1 or module.__class__ not in [m.__class__ for m in text_model.modules()]:
return ALL_ATTENTION_FUNCTIONS['sdpa_origin'](module, query_states, key_states, value_states,
attention_mask, *args, **kwargs)
if dist_attn.local_attn is None:
def _attention(query, key, value, *args, **kwargs):
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
if self.rp_world_size > 1:
raise NotImplementedError('SDPA does not support Ring attention.')
return ALL_ATTENTION_FUNCTIONS['sdpa_origin'](module, query, key, value, *args, **kwargs)[0]
dist_attn.local_attn = _attention
return dist_attn(
query_states.transpose(1, 2), key_states.transpose(1, 2), value_states.transpose(1, 2), attention_mask,
*args, **kwargs), None
ALL_ATTENTION_FUNCTIONS['flash_attention_2_origin'] = ALL_ATTENTION_FUNCTIONS['flash_attention_2']
ALL_ATTENTION_FUNCTIONS['sdpa_origin'] = ALL_ATTENTION_FUNCTIONS['sdpa']
ALL_ATTENTION_FUNCTIONS['flash_attention_2'] = partial(
local_flash_attn, dist_attn=DistributedAttention(None, self))
ALL_ATTENTION_FUNCTIONS['sdpa'] = partial(local_sdpa_attn, dist_attn=DistributedAttention(None, self))
def _prepare_forward_hook(self, base_model: torch.nn.Module):
def pre_forward_split_hook(_self, args, kwargs):
if self.world_size == 1:
return args, kwargs
input_ids = kwargs.get('input_ids', None)
inputs_embeds = kwargs.get('inputs_embeds', None)
position_ids = kwargs['position_ids']
attention_mask = kwargs.get('attention_mask', None)
if hasattr(_self, 'language_model'):
embed_tokens = getattr(_self.language_model, 'embed_tokens', None)
else:
embed_tokens = getattr(_self, 'embed_tokens', None)
input_ids, inputs_embeds, _, position_ids, attention_mask, _, _ = self.pad_and_split_inputs(
input_ids,
inputs_embeds,
None,
position_ids,
attention_mask,
None,
embed_tokens=embed_tokens,
real_position_ids=self.real_position_ids)
kwargs['input_ids'] = input_ids
kwargs['inputs_embeds'] = inputs_embeds
kwargs['position_ids'] = position_ids
kwargs['attention_mask'] = attention_mask
return args, kwargs
base_model.register_forward_pre_hook(pre_forward_split_hook, with_kwargs=True)
def _prepare_moe_aux_loss(self, base_model: torch.nn.Module):
from .utils import GatherLoss
def moe_aux_loss_hook(module, args, kwargs, output):
router_logits = getattr(output, 'router_logits', None)
if router_logits is None:
return output
attention_mask = kwargs['attention_mask']
if attention_mask is None:
batch_size = 1
else:
batch_size = attention_mask.shape[0]
assert router_logits[0].shape[0] % batch_size == 0
seq_len = router_logits[0].shape[0] // batch_size
_gathered_logits = []
for i in range(batch_size):
_slice = slice(i * seq_len, (i + 1) * seq_len)
_bs_logits = [logit[_slice] for logit in router_logits]
compute_device = _bs_logits[0].device
_bs_logits = torch.stack([layer_gate.to(compute_device) for layer_gate in _bs_logits], dim=0)
_bs_logits, _ = GatherLoss.apply(_bs_logits, None, 1, self.real_position_ids)
_gathered_logits.append(_bs_logits)
router_logits = torch.stack(_gathered_logits, dim=0)
if self.real_position_ids is not None:
router_logits = router_logits[:, :, :self.real_position_ids.shape[1], :]
output['router_logits'] = tuple(
[logit.reshape(-1, logit.shape[-1]) for logit in router_logits.split(1, dim=1)])
return output
base_model.register_forward_hook(moe_aux_loss_hook, with_kwargs=True)
def prepare(self, sp_size: int, model: torch.nn.Module, tokenizer: PreTrainedTokenizer, padding_free: bool):
from swift.model import get_llm_model
self.num_heads = HfConfigFactory.get_config_attr(model.config, 'num_key_value_heads')
if self.num_heads is None:
self.num_heads = HfConfigFactory.get_config_attr(model.config, 'num_attention_heads')
assert self.num_heads is not None, 'Cannot find num_heads config in config.json'
self.padding_free = padding_free
self.world_size = sp_size
llm_model = get_llm_model(model)
if hasattr(llm_model, 'language_model'):
if hasattr(llm_model.language_model, '_update_causal_mask'):
self.causal_mask_func = llm_model.language_model._update_causal_mask
else:
if hasattr(llm_model, '_update_causal_mask'):
self.causal_mask_func = llm_model._update_causal_mask
if not SequenceParallel._global_inited:
# these operations are global initializations and patches
self._init_device_mesh()
self._prepare_flash_attn(llm_model)
SequenceParallel._global_inited = True
self._prepare_forward_hook(llm_model)
if model.model_info.is_moe_model:
self._prepare_moe_aux_loss(llm_model)
self.model_dtype = next(model.parameters()).dtype
self.tokenizer = tokenizer
if self.rp_world_size > 1 and not self.padding_free:
raise NotImplementedError(
f'The world_size {self.world_size} needs ulysses/ring-attention, which needs --padding_free true')
def _mask_qkv(self, query, key, value, mask):
mask = mask.unsqueeze(2).unsqueeze(3)
query = query * mask
value = value * mask
mask = (~mask) * -1e5 # for bf16
key = key + mask.to(key.dtype)
return query, key, value
def pad(self, tensor, padding_value, position_ids=None, dim=1):
"""Pad tensor for sequence parallel"""
if self.rp_world_size > 1:
world_size = self.world_size * 2
else:
world_size = self.world_size
def _do_pad(tensor):
length = tensor.shape[dim]
pad_num = world_size - (length % world_size)
if pad_num == 0 or pad_num == world_size:
return tensor
if not isinstance(padding_value, torch.Tensor):
# ids
pad_shape = ((*tensor.shape[:dim], pad_num, *tensor.shape[dim + 1:]) if dim != -1 else
(*tensor.shape[:dim], pad_num))
pad = torch.full(pad_shape, padding_value, dtype=tensor.dtype, device=tensor.device)
tensor = torch.cat([tensor, pad], dim=dim)
else:
# For embeddings
tensor = torch.cat([tensor, padding_value.unsqueeze(0).repeat(tensor.shape[0], pad_num, 1)], dim=dim)
return tensor
if position_ids is not None and self.rp_world_size > 1:
cu_seqlens = get_cu_seqlens_from_position_ids(position_ids)
all_tensors = []
for i in range(len(cu_seqlens) - 1):
if dim == 1:
sub_tensor = tensor[:, cu_seqlens[i]:cu_seqlens[i + 1]]
elif dim == -1:
sub_tensor = tensor[..., cu_seqlens[i]:cu_seqlens[i + 1]]
else:
raise NotImplementedError()
all_tensors.append(_do_pad(sub_tensor))
tensor = torch.cat(all_tensors, dim=dim)
return _do_pad(tensor)
def gather(self, local_output, dim: int, position_ids=None):
"""Gather tensor for sequence parallel - reverse of split"""
if self.world_size == 1:
return local_output
if self.rp_world_size > 1:
input_dim = local_output.dim()
assert input_dim >= 2
if position_ids is not None:
position_ids = self.pad(position_ids, padding_value=-1, position_ids=position_ids)
# Step 1: Gather from all sequence parallel ranks
# Each sp_rank has its own piece, we need to gather them first
gathered_sp = [torch.zeros_like(local_output) for _ in range(self.sp_world_size)]
torch.distributed.all_gather(gathered_sp, local_output.contiguous(), group=self.sp_group)
# Concatenate the sp pieces to form the complete chunk for this rp_rank
rp_chunk = torch.cat(gathered_sp, dim=dim)
# Step 2: Gather all rp chunks
gathered_rp = [torch.zeros_like(rp_chunk) for _ in range(self.rp_world_size)]
torch.distributed.all_gather(gathered_rp, rp_chunk, group=self.rp_group)
cu_seqlens = get_cu_seqlens_from_position_ids(position_ids)
all_tensor_length = []
for i in range(len(cu_seqlens) - 1):
length = cu_seqlens[i + 1] - cu_seqlens[i]
padding_length = math.ceil(length / (self.world_size * 2)) * (self.world_size * 2)
all_tensor_length.append(padding_length)
full_output = torch.zeros(
[local_output.shape[0], sum(all_tensor_length), *local_output.shape[2:]], device=local_output.device)
for idx_rp, rp_tensor in enumerate(gathered_rp): # rp world size
# re-group the zigzag to the correct order
accumulated_length = 0
for idx_seq, length in enumerate(all_tensor_length): # sequence number
local_length = length // self.rp_world_size
local_tensor = rp_tensor[:, accumulated_length:local_length + accumulated_length]
chunk_size = local_length // 2
left_idx = accumulated_length * self.rp_world_size + idx_rp * chunk_size
right_idx = accumulated_length * self.rp_world_size + (idx_rp + 1) * chunk_size
full_output[:, left_idx:right_idx] = local_tensor[:, :chunk_size]
left_idx = accumulated_length * self.rp_world_size + (2 * self.rp_world_size - idx_rp
- 1) * chunk_size
right_idx = accumulated_length * self.rp_world_size + (2 * self.rp_world_size - idx_rp) * chunk_size
full_output[:, left_idx:right_idx] = local_tensor[:, chunk_size:]
accumulated_length += local_length
return full_output.contiguous()
else:
gathered_sp = torch.empty(
[local_output.shape[0] * self.sp_world_size] + list(local_output.shape[1:]),
dtype=local_output.dtype,
device=local_output.device)
dist.all_gather_into_tensor(gathered_sp, local_output, group=self.sp_group)
gathered_sp = torch.cat(gathered_sp.split(local_output.shape[0], dim=0), dim=dim)
return gathered_sp.contiguous()
def _split_packed(self, value, cu_seqlens, dim=1):
"""Split and re-group in zigzag"""
local_values = []
for i in range(len(cu_seqlens) - 1):
start, end = cu_seqlens[i], cu_seqlens[i + 1]
if dim == 1:
sub_value = value[:, start:end]
elif dim == -1:
sub_value = value[..., start:end]
else:
raise NotImplementedError()
local_value = sub_value.chunk(2 * self.rp_world_size, dim=dim)
local_values.extend([
local_value[self.rp_rank],
local_value[2 * self.rp_world_size - 1 - self.rp_rank],
])
return torch.cat(local_values, dim=dim).contiguous()
def split(self, input, dim: int, position_ids=None):
"""Split tensor for sequence parallel"""
if self.world_size == 1:
return input
if self.rp_world_size > 1:
input_dim = input.dim()
assert input_dim >= 2
cu_seqlens = get_cu_seqlens_from_position_ids(position_ids)
assert torch.all(cu_seqlens % (2 * self.rp_world_size) == 0)
value_chunks = self._split_packed(input, cu_seqlens, dim=dim)
local_value = value_chunks.chunk(self.sp_world_size, dim=dim)[self.sp_rank].contiguous()
return local_value
else:
rank = self.sp_rank
dim_size = input.size(dim)
assert dim_size % self.sp_world_size == 0, (
f'The dimension to split ({dim_size}) is not a multiple of '
f'world size ({self.sp_world_size}), cannot split tensor evenly')
tensor_list = torch.split(input, dim_size // self.sp_world_size, dim=dim)
output = tensor_list[rank].contiguous()
return output
def pad_and_split_mm_tokens(self, visual_mask, mm_embeds):
input_ids = self.extra_kwargs['input_ids']
empty_embeds = torch.empty(
(input_ids.shape[0], input_ids.shape[1], mm_embeds.shape[-1])).to(mm_embeds.device).to(mm_embeds.dtype)
empty_embeds[visual_mask] = mm_embeds
embeds = SimpleNamespace(weight=mm_embeds)
_, split_input_embeds, _, _, _, _, extra_values = self.pad_and_split_inputs(
None,
empty_embeds,
None,
None,
None,
None,
embeds,
self.real_position_ids,
extra_split_values=[(visual_mask, 0, -1)])
visual_mask = extra_values[0]
return visual_mask, split_input_embeds[visual_mask]
def pad_and_split_inputs(self,
input_ids,
input_embeds,
labels,
position_ids,
attention_mask,
loss_scale,
embed_tokens=None,
real_position_ids=None,
extra_split_values=None):
"""Common implementation for padding and splitting inputs
When a sequence comes, it will be split into rp_world_size * 2 sub tensors, and group them as the
zigzag order. So we get rp_world_size tensors, then we split each tensor to sp_world_size ones.
So, we should first pad the original sequence to the length can be divided by 2 * world_size, then re-group it.
Only support padding_free for ring-attention, because non-padding_free mode needs another pad/split workflow
man, that's a lot of work...
Args:
input_ids: input_ids
input_embeds: input_embeds
labels: labels
position_ids: position_ids or, position_ids for mrope
attention_mask: attention_mask
loss_scale: loss_scale
embed_tokens: embed_tokens
real_position_ids: the real position_ids to represent the seq length information
extra_split_values: List of Tuples for extra split values, e.g.: (tensor, pad_value, split_dim)
"""
tokenizer = self.tokenizer
real_position_ids = real_position_ids if real_position_ids is not None else position_ids
extra_values = []
batch_size = input_ids.shape[
0] if input_ids is not None else input_embeds.shape[0] if input_embeds is not None else None
if real_position_ids is not None and batch_size is not None and real_position_ids.shape[0] == batch_size:
# TODO clone everytime, but the position_ids is a small tensor
self.extra_kwargs['text_position_ids'] = real_position_ids.clone()
if input_ids is not None:
input_ids = self.pad(input_ids, padding_value=tokenizer.pad_token_id, position_ids=real_position_ids)
self.extra_kwargs['input_ids'] = input_ids.clone()
if input_embeds is not None:
pad_emb = torch.zeros(
(1, embed_tokens.weight.shape[-1])).to(embed_tokens.weight.device).to(embed_tokens.weight.dtype)
input_embeds = self.pad(input_embeds, padding_value=pad_emb, position_ids=real_position_ids)
batch_size = input_ids.shape[
0] if input_ids is not None else input_embeds.shape[0] if input_embeds is not None else 1
if position_ids is not None:
position_ids = self.pad(position_ids, padding_value=-1, position_ids=real_position_ids, dim=-1)
if labels is not None:
labels = self.pad(labels, padding_value=-100, position_ids=real_position_ids)
if loss_scale is not None:
loss_scale = self.pad(loss_scale, padding_value=0., position_ids=real_position_ids)
if real_position_ids is not None:
real_position_ids = self.pad(real_position_ids, padding_value=-1, position_ids=real_position_ids)
if (input_ids is not None or input_embeds is not None) and batch_size > 1:
# not padding_free, so not ring-attention
inputs = input_ids if input_ids is not None else input_embeds
attn_shape = inputs.shape[1] # The sequence length
if attention_mask is None:
attention_mask = torch.ones_like(real_position_ids)
# no need position_ids here, because padding_free does not need attention_mask,
# so this is not ring-attention
attention_mask = self.pad(attention_mask, padding_value=0)
cache_position = torch.arange(0, attn_shape, device=inputs.device)
# pad attention mask to 4d to avoid calculation errors
if hasattr(self, 'causal_mask_func') and self.causal_mask_func is not None:
attention_mask = self.causal_mask_func(attention_mask, inputs.to(self.model_dtype), cache_position,
None, None)
if extra_split_values is not None:
for (tensor, pad_value, split_dim) in extra_split_values:
extra_values.append(
self.pad(tensor, padding_value=pad_value, position_ids=real_position_ids, dim=split_dim))
if input_ids is not None:
input_ids = self.split(input_ids, dim=1, position_ids=real_position_ids)
if input_embeds is not None:
input_embeds = self.split(input_embeds, dim=1, position_ids=real_position_ids)
if labels is not None:
labels = torch.roll(labels, shifts=-1, dims=-1)
labels = self.split(labels, dim=-1, position_ids=real_position_ids)
if loss_scale is not None:
loss_scale = torch.roll(loss_scale, shifts=-1, dims=-1)
loss_scale = self.split(loss_scale, dim=-1, position_ids=real_position_ids)
if position_ids is not None:
position_ids = self.split(position_ids, dim=-1, position_ids=real_position_ids)
if extra_split_values is not None:
for i in range(len(extra_values)):
extra_values[i] = self.split(
extra_values[i], dim=extra_split_values[i][2], position_ids=real_position_ids)
return input_ids, input_embeds, labels, position_ids, attention_mask, loss_scale, extra_values
def _gather_object_dp(self, input_data):
"""Gather object for data parallel"""
input_data_list = [None] * self.dp_world_size
dist.all_gather_object(input_data_list, input_data, group=self.dp_group)
return [x for y in input_data_list for x in y]
def _init_device_mesh(self):
"""Initialize device mesh for sequence and ring parallel.
The logic is unified:
1. Determine the Sequence Parallel (SP) size first based on GCD to satisfy constraints.
2. Allocate all remaining model parallelism to Ring Parallel (RP).
"""
_, _, world_size, _ = get_dist_setting()
self.dp_world_size = world_size // self.world_size
# SP size is the GCD of num_heads and world_size, guaranteeing it divides both.
self.sp_world_size = math.gcd(self.num_heads, self.world_size)
# RP takes the remaining factor so all model-parallel GPUs are used.
self.rp_world_size = self.world_size // self.sp_world_size
if self.rp_world_size > 1:
mesh_shape = (self.dp_world_size, self.rp_world_size, self.sp_world_size)
mesh_dim_names = ('data', 'ring', 'sequence')
else:
mesh_shape = (self.dp_world_size, self.sp_world_size)
mesh_dim_names = ('data', 'sequence')
self.device_mesh = init_device_mesh(
get_device().split(':')[0], mesh_shape=mesh_shape, mesh_dim_names=mesh_dim_names)
def _dim_group(self, dim_name: str):
"""Return the process group of the given mesh dim, or None if absent."""
if not self.device_mesh or dim_name not in self.device_mesh.mesh_dim_names:
return None
return self.device_mesh[dim_name].get_group()
def _dim_rank(self, dim_name: str, default: int) -> int:
"""Return the rank within the given mesh dim, or `default` if absent."""
group = self._dim_group(dim_name)
return dist.get_rank(group) if group is not None else default
@property
def sp_group(self):
"""Return the sequence parallel group"""
return self._dim_group('sequence')
def enabled(self):
return self.world_size is not None and self.world_size > 1
@property
def sp_rank(self):
"""Return the sequence parallel rank"""
return self._dim_rank('sequence', default=0)
@property
def dp_group(self):
"""Return the data parallel group"""
return self._dim_group('data')
@property
def dp_rank(self):
"""Return the data parallel rank"""
return self._dim_rank('data', default=0)
@property
def rp_group(self):
"""Return the ring parallel group"""
return self._dim_group('ring')
@property
def rp_rank(self):
"""Return the ring parallel rank"""
return self._dim_rank('ring', default=-1)
def prepare_inputs(self, inputs):
"""Prepare inputs
1. set extra_kwargs['text_position_ids']
2. split labels
"""
position_ids = inputs.get('text_position_ids')
input_ids = inputs.get('input_ids')
if position_ids is None:
position_ids = inputs.get('position_ids')
if position_ids is not None and input_ids is not None and position_ids.shape[0] == input_ids.shape[0]:
self.extra_kwargs['text_position_ids'] = position_ids.clone()
if input_ids is not None:
self.extra_kwargs['input_ids'] = input_ids.clone()
if 'labels' in inputs:
labels = inputs['labels']
_, _, labels, _, _, _, _ = self.pad_and_split_inputs(
None, None, labels, None, None, None, real_position_ids=position_ids)
inputs['labels'] = labels
sequence_parallel = SequenceParallel()