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

287 lines
11 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import re
import torch
import torch.distributed as dist
from megatron.core import mpu
from megatron.core.extensions.transformer_engine import TEGroupedLinear, TELayerNormColumnParallelLinear, TELinear
from megatron.core.inference.communication_utils import recv_from_prev_pipeline_rank_, send_to_next_pipeline_rank
from megatron.core.models.common.embeddings.language_model_embedding import LanguageModelEmbedding
from megatron.core.packed_seq_params import PackedSeqParams
from megatron.core.ssm.mamba_context_parallel import _undo_attention_load_balancing
from megatron.core.transformer.moe.router import TopKRouter
from torch import nn
from transformers.utils import is_torch_npu_available
from swift.tuners import LoraConfig, Swift
from swift.utils import (activate_parameters, deep_getattr, find_layers, freeze_parameters, get_logger,
get_model_parameter_info)
from swift.utils import get_packed_seq_params as _get_packed_seq_params
logger = get_logger()
def find_all_linears(model, extra_layers=None):
def _cond(name, module):
if (extra_layers and isinstance(module, tuple(extra_layers))) or name != 'output_layer' and isinstance(
module, (TELinear, TELayerNormColumnParallelLinear, TEGroupedLinear, nn.Linear)):
return True
return False
return find_layers(model, _cond)
def find_router(model):
return find_layers(model, lambda name, module: isinstance(module, TopKRouter))
def find_embedding(model):
return find_layers(model, lambda name, module: isinstance(module, LanguageModelEmbedding))
def get_multimodal_target_regex(
args,
model,
*,
freeze_llm: bool = False,
freeze_vit: bool = True,
freeze_aligner: bool = True,
include_embedding: bool = False,
include_router: bool = False,
) -> str:
megatron_model_meta = args.megatron_model_meta
modules = []
visual_cls = megatron_model_meta.visual_cls
vision_tower = [f'visual.{vit}' for vit in visual_cls._vision_tower]
aligner = [f'visual.{aligner}' for aligner in visual_cls._aligner]
if not freeze_llm:
modules.append('language_model')
if not freeze_vit:
modules += vision_tower
if not freeze_aligner:
modules += aligner
assert len(modules) > 0, f'modules: {modules}'
extra_layers = []
if include_embedding:
extra_layers.append(LanguageModelEmbedding)
if include_router:
extra_layers.append(TopKRouter)
res = []
for module in modules:
rejected_modules = []
if not freeze_vit:
for _aligner in aligner:
if _aligner.startswith(f'{module}.'):
rejected_modules.append(_aligner)
sub_module = deep_getattr(model, module)
if sub_module is None:
continue
target_modules = find_all_linears(sub_module, extra_layers)
if not target_modules:
continue
target_modules = [tm for tm in target_modules if tm]
target_pattern = rf'.*\.({"|".join(target_modules)})' if target_modules else ''
rejected_pattern = rf'(?!({"|".join(rejected_modules)}))' if rejected_modules else ''
res.append(rf'{rejected_pattern}{re.escape(module)}(?=\.){target_pattern}')
return rf'^({"|".join(res)})$'
def get_target_modules(args, model):
if isinstance(args.target_modules, str):
return args.target_modules
target_modules = args.target_modules.copy()
if 'all-linear' in target_modules:
if args.is_multimodal and not args.language_model_only:
if args.tuner_type == 'lora_llm':
kwargs = {
'freeze_llm': False,
'freeze_vit': True,
'freeze_aligner': True,
}
else: # lora
kwargs = {
'freeze_llm': args.freeze_llm,
'freeze_vit': args.freeze_vit,
'freeze_aligner': args.freeze_aligner,
}
return get_multimodal_target_regex(
args,
model,
include_embedding='all-embedding' in target_modules,
include_router='all-router' in target_modules,
**kwargs,
)
else:
target_modules.remove('all-linear')
target_modules += find_all_linears(model)
if 'all-embedding' in target_modules:
target_modules.remove('all-embedding')
target_modules += find_embedding(model)
if 'all-router' in target_modules:
target_modules.remove('all-router')
target_modules += find_router(model)
return target_modules
def get_modules_to_save(args, model):
if args.task_type == 'seq_cls':
args.modules_to_save.append('output_layer')
modules_to_save = args.modules_to_save.copy()
if 'all-embedding' in args.modules_to_save:
modules_to_save.remove('all-embedding')
modules_to_save += find_embedding(model)
return modules_to_save
def prepare_adapter(args, model):
target_modules = get_target_modules(args, model)
modules_to_save = get_modules_to_save(args, model)
lora_kwargs = {
'r': args.lora_rank,
'target_modules': target_modules,
'lora_alpha': args.lora_alpha,
'lora_dropout': args.lora_dropout,
'bias': args.lora_bias,
'modules_to_save': modules_to_save,
'use_rslora': args.use_rslora,
}
lora_config = LoraConfig(task_type='CAUSAL_LM', lora_dtype=args.lora_dtype, **lora_kwargs)
logger.info(f'lora_config: {lora_config}')
model = Swift.prepare_model(model, lora_config)
if args.mcore_ref_adapter or args.ref_adapters:
model.add_adapter('ref_adapter', lora_config)
model.base_model._cast_adapter_dtype(adapter_name='ref_adapter', autocast_adapter_dtype=True)
for n, p in model.named_parameters():
if '.ref_adapter.' in n:
p.requires_grad = False
return model
def _prepare_full_vit(args, model):
megatron_model_meta = args.megatron_model_meta
visual_cls = megatron_model_meta.visual_cls
vision_tower = [f'visual.{vit}' for vit in visual_cls._vision_tower]
aligner = [f'visual.{aligner}' for aligner in visual_cls._aligner]
for module_prefix in vision_tower + aligner:
module = deep_getattr(model, module_prefix)
if module is not None:
module.requires_grad_(True)
def prepare_mcore_model(args, model):
if args.tuner_type == 'full':
freeze_parameters(model, args.freeze_parameters_ratio, args.freeze_parameters, args.freeze_parameters_regex)
if args.trainable_parameters or args.trainable_parameters_regex:
activate_parameters(model, args.trainable_parameters, args.trainable_parameters_regex)
elif args.tuner_type in {'lora', 'lora_llm'}:
model = prepare_adapter(args, model)
if args.tuner_type == 'lora_llm':
_prepare_full_vit(args, model)
logger.info(f'model: {model}')
logger.info_if(
f'[rank{dist.get_rank()}] model_parameter_info: {get_model_parameter_info(model)}',
cond=mpu.get_data_parallel_rank() == 0)
return model
def forward_step_helper(model, inputs, dtype=None):
config = model.config
dtype = dtype or config.params_dtype
if not mpu.is_pipeline_first_stage():
recv_shape_buffer = torch.empty((3, ), device=torch.cuda.current_device(), dtype=torch.int64)
recv_from_prev_pipeline_rank_(recv_shape_buffer)
recv_buffer = torch.empty(recv_shape_buffer.tolist(), device=torch.cuda.current_device(), dtype=dtype)
recv_from_prev_pipeline_rank_(recv_buffer)
model.set_input_tensor(recv_buffer)
output_tensor = model(**inputs)
if not mpu.is_pipeline_last_stage():
recv_shape_buffer = torch.tensor(output_tensor.shape, device=torch.cuda.current_device(), dtype=torch.int64)
send_to_next_pipeline_rank(recv_shape_buffer)
send_to_next_pipeline_rank(output_tensor)
output_tensor = None
return output_tensor
def get_padding_to(args):
padding_to = None
if args.tensor_model_parallel_size > 1 and args.sequence_parallel:
padding_to = args.tensor_model_parallel_size
if args.context_parallel_size > 1:
padding_to = (padding_to or 1) * args.context_parallel_size
origin_padding_to = padding_to
fp8_format = getattr(args, 'fp8_format', None) or getattr(args, 'fp8', None)
fp4_format = getattr(args, 'fp4_format', None) or getattr(args, 'fp4', None)
if args.fp8_recipe == 'blockwise':
padding_to = (padding_to or 1) * 128
elif args.fp8_recipe == 'mxfp8':
# MXFP8 uses a block size of 32. Under sequence parallel, the sequence is
# split across TP ranks, so each per-rank shard (seq_len / TP) must itself
# be divisible by 32. Pad the total length to TP * 32 to guarantee this.
padding_to = (padding_to or 1) * 32
elif fp8_format is not None or fp4_format is not None:
padding_to = (padding_to or 1) * 16
if args.attention_backend == 'fused':
padding_to = max(padding_to or 1, ((origin_padding_to) or 1) * 64)
return padding_to
def get_packed_seq_params(args, position_ids: torch.Tensor) -> PackedSeqParams:
params = _get_packed_seq_params(position_ids)
packed = PackedSeqParams(
cu_seqlens_q=params['cu_seq_lens_q'],
cu_seqlens_kv=params['cu_seq_lens_k'],
max_seqlen_q=params['max_length_q'],
max_seqlen_kv=params['max_length_k'],
qkv_format='thd',
)
if hasattr(packed, 'cp_partition_mode'):
packed.cp_partition_mode = args.cp_partition_mode
if is_torch_npu_available():
packed.cu_seqlens_q_padded = params['cu_seq_lens_q']
packed.cu_seqlens_kv_padded = params['cu_seq_lens_k']
return packed
def reconstruct_tensor_cp(tensor, packed_seq_params, dim=1) -> torch.Tensor:
"""In CP mode, all-gather and undo the load-balanced (zigzag) chunking
produced by ``split_cp_inputs``, restoring the full sequence in original
token order along ``dim``.
Args:
tensor: CP-sharded local tensor whose sequence dim is at ``dim``.
packed_seq_params: ``PackedSeqParams`` for THD inputs, or ``None`` for
regular ``[B, S, ...]`` inputs.
dim: Sequence dimension index of ``tensor`` (default: 1).
Returns:
torch.Tensor: Full-sequence tensor with the same shape as ``tensor``
except the size at ``dim`` is multiplied by ``cp_size``.
"""
cp_size = mpu.get_context_parallel_world_size()
if cp_size <= 1:
return tensor
cp_rank = mpu.get_context_parallel_rank()
cp_group = mpu.get_context_parallel_group()
# All-gather across CP ranks (preserve local autograd graph for `tensor`).
output_list = [torch.empty_like(tensor) for _ in range(cp_size)]
torch.distributed.all_gather(output_list, tensor.contiguous(), group=cp_group)
output_list[cp_rank] = tensor
gathered = torch.cat(output_list, dim=dim)
# `_undo_attention_load_balancing` assumes sequence dim is 0; transpose if needed.
if dim != 0:
gathered = gathered.transpose(0, dim).contiguous()
out = _undo_attention_load_balancing(gathered, cp_size, packed_seq_params)
if dim != 0:
out = out.transpose(0, dim).contiguous()
return out