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

361 lines
13 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import re
import torch
import torch.nn as nn
from bisect import bisect_right
from contextlib import contextmanager, nullcontext
from transformers.integrations import is_deepspeed_zero3_enabled
from transformers.trainer_utils import set_seed
from typing import Callable, List, Optional, Tuple
from .logger import get_logger
from .utils import deep_getattr
logger = get_logger()
def get_n_params_grads(model) -> Tuple[List[int], List[int]]:
n_params, n_grads = [], []
for p in model.parameters():
if is_deepspeed_zero3_enabled():
import deepspeed
context = deepspeed.zero.GatheredParameters(p)
else:
context = nullcontext()
with context:
n_params.append(p.numel())
n_grads.append(p.numel() if p.requires_grad else 0)
return n_params, n_grads
def get_model_parameter_info(model: nn.Module, name: Optional[str] = None) -> str:
n_params, n_grads = get_n_params_grads(model)
n_params = sum(n_params)
n_grads = sum(n_grads)
n_buffers = sum(p.numel() for p in model.buffers())
if name is None:
name = model.__class__.__name__
n_params /= 1e6
n_grads /= 1e6
n_buffers /= 1e6
s = (f'{name}: '
f'{n_params:.4f}M Params ({n_grads:.4f}M Trainable '
f'[{100 * n_grads / n_params:.4f}%]), '
f'{n_buffers:.4f}M Buffers.')
return s
def find_sub_module(module: torch.nn.Module, module_name: str) -> List[torch.nn.Module]:
_modules = list()
for name, sub_module in module.named_modules():
if not name:
continue
if name.endswith(module_name):
_modules.append(sub_module)
return _modules
def show_layers(model: nn.Module, max_lines: Optional[int] = 20) -> None:
named_p = list(model.named_parameters())
for i, (n, p) in enumerate(named_p):
if max_lines is not None and i >= max_lines:
logger.info('...')
break
logger.info(f'[{n}]: requires_grad={p.requires_grad}, dtype={p.dtype}, device={p.device}')
def freeze_parameters(model: nn.Module,
freeze_parameters_ratio: float,
freeze_parameters: List[str],
freeze_parameters_regex: Optional[str] = None) -> None:
if freeze_parameters_ratio > 0:
n_parameters = get_n_params_grads(model)[0]
n_parameters = np.array(n_parameters, dtype=np.int64)
n_freeze_parameters = int(np.sum(n_parameters) * freeze_parameters_ratio)
n_parameters_cs = np.cumsum(n_parameters)
idx = bisect_right(n_parameters_cs, n_freeze_parameters)
for _, p in zip(range(idx), model.parameters()):
p.requires_grad = False
if freeze_parameters:
for n, p in model.named_parameters():
for freeze_p in freeze_parameters:
if n.startswith(freeze_p):
p.requires_grad = False
if freeze_parameters_regex is not None:
try:
pattern = re.compile(freeze_parameters_regex)
except re.error as e:
logger.warning(f"Invalid freeze_parameters_regex '{freeze_parameters_regex}': {e}")
return
for n, p in model.named_parameters():
if pattern.search(n):
p.requires_grad = False
def activate_parameters(model: nn.Module,
additional_trainable_parameters: List[str],
trainable_parameters_regex: Optional[str] = None) -> None:
has_activate = False
if len(additional_trainable_parameters) > 0:
for n, p in model.named_parameters():
for additional_tp in additional_trainable_parameters:
if n.startswith(additional_tp):
p.requires_grad = True
has_activate = True
if not has_activate:
logger.warning('len(additional_trainable_parameters) > 0 but no parameters are activated. '
f'additional_trainable_parameters: {additional_trainable_parameters}')
has_activate = False
if trainable_parameters_regex is not None:
try:
pattern = re.compile(trainable_parameters_regex)
except re.error as e:
logger.warning(f"Invalid trainable_parameters_regex '{trainable_parameters_regex}': {e}")
return
for n, p in model.named_parameters():
if pattern.search(n):
p.requires_grad = True
has_activate = True
if not has_activate:
logger.warning('trainable_parameters_regex is provided but no parameters are activated. '
f'trainable_parameters_regex: {trainable_parameters_regex}')
def find_layers(
model: nn.Module,
cond: Callable[[str, nn.Module], bool],
sub_module: Optional[str] = None,
min_name_len: Optional[int] = None,
) -> List[str]:
# The content of target_module_names cannot exist in inner_nodes.
sub_module_str = sub_module
if sub_module is None:
sub_module = model
else:
sub_module = deep_getattr(model, sub_module)
inner_nodes = set()
for name, module in model.named_modules():
name = re.sub(r'\d+\.', '{}.', name)
if not cond(name, module):
inner_nodes.add(name)
target_module_names = set()
for name, module in sub_module.named_modules():
if sub_module_str:
name = f'{sub_module_str}.{name}' if name else sub_module_str
if cond(name, module):
module_name_list = name.split('.')
module_name = module_name_list.pop()
i = 1
for inner_node in inner_nodes:
while module_name_list and inner_node.endswith(re.sub(
r'\d+\.', '{}.', module_name)) or min_name_len and i < min_name_len:
module_name = f'{module_name_list.pop()}.{module_name}'
i += 1
target_module_names.add(module_name)
return list(target_module_names)
def find_norm(model: nn.Module) -> List[str]:
# find_layer_norm
return find_layers(
model,
lambda name, module: isinstance(module, torch.nn.LayerNorm) or 'rmsnorm' in module.__class__.__name__.lower())
def find_embedding(model: nn.Module) -> List[str]:
return find_layers(model, lambda name, module: isinstance(module, torch.nn.Embedding))
def find_all_linears(model, model_arch=None, extra_layers=None, sub_module=None):
if model_arch is None:
model_arch = model.model_meta.model_arch
# lm_head
if model_arch and model_arch.lm_head:
output = model_arch.lm_head
idx = output.rfind('.')
lm_head_name = output[idx + 1:]
else:
lm_head_name = 'lm_head'
# 'score', 'classifier': classification model
# 'v_head': reward model
ignore_layers = [lm_head_name, 'score', 'v_head', 'classifier'] + ['lora_A', 'lora_B', 'base_layer']
ignore_linear_cls = [
'glulinear', # phi4-mm
'gemma4clippablelinear', # gemma4
]
def _cond(name, module):
module_name = module.__class__.__name__.lower()
if (extra_layers and isinstance(module, tuple(extra_layers)) or
('linear' in module_name and all(linear_cls not in module_name
for linear_cls in ignore_linear_cls))) and all(layer not in name
for layer in ignore_layers):
return True
return False
return find_layers(model, _cond, sub_module=sub_module)
def get_multimodal_target_regex(
model,
*,
freeze_llm: bool = False,
freeze_vit: bool = True,
freeze_aligner: bool = True,
include_embedding: bool = False,
exclude_router: bool = False,
) -> str:
model_arch = model.model_meta.model_arch
modules = []
if not freeze_llm:
modules += model_arch.language_model
if not freeze_vit:
modules += model_arch.vision_tower
if not freeze_aligner:
modules += model_arch.aligner
assert len(modules) > 0, f'modules: {modules}'
extra_layers = []
if include_embedding:
extra_layers.append(nn.Embedding)
res = []
for module in modules:
rejected_modules = []
if not freeze_vit or not freeze_llm:
for aligner in model_arch.aligner:
if aligner.startswith(f'{module}.'):
rejected_modules.append(aligner)
sub_module = deep_getattr(model, module)
if sub_module is None:
logger.warning(f'module: {module} is None')
continue
if isinstance(sub_module, nn.Linear) and module.endswith('lm_head'):
target_modules = []
else:
target_modules = find_all_linears(sub_module, model_arch, extra_layers)
if exclude_router and model.model_info.is_moe_model:
target_modules = [tm for tm in target_modules if tm not in {'gate'}]
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_cu_seqlens_from_position_ids(position_ids: torch.LongTensor):
position_ids = position_ids[0]
seq_start_indices = torch.where(position_ids == 0)[0]
seq_end_indices = torch.cat([seq_start_indices[1:], torch.tensor([len(position_ids)], device=position_ids.device)])
seq_lengths = seq_end_indices - seq_start_indices
cu_seqlens = torch.cumsum(torch.cat([torch.tensor([0], device=position_ids.device), seq_lengths]), dim=0)
return cu_seqlens.to(torch.int32)
def get_position_ids_from_cu_seqlens(cu_seqlens: torch.LongTensor):
seq_lengths = cu_seqlens[1:] - cu_seqlens[:-1]
position_ids = torch.cat([torch.arange(seq_len, device=cu_seqlens.device) for seq_len in seq_lengths], dim=0)
return position_ids.unsqueeze(0)
def seed_worker(worker_id: int, num_workers: int, rank: int):
"""
Helper function to set worker seed during Dataloader initialization.
"""
init_seed = torch.initial_seed() % 2**32
worker_seed = num_workers * rank + init_seed
set_seed(worker_seed)
@contextmanager
def unwrap_model_for_generation(
model,
accelerator,
gather_deepspeed3_params=True,
gather_parameters: List[nn.Parameter] = None,
):
unwrapped_model = accelerator.unwrap_model(model)
if accelerator.state.deepspeed_plugin is not None and accelerator.state.deepspeed_plugin.zero_stage == 3:
if not gather_deepspeed3_params:
yield accelerator.unwrap_model(model)
else:
import deepspeed
parameters = [
parameter for name, parameter in model.named_parameters()
if not gather_parameters or name in gather_parameters
]
with deepspeed.zero.GatheredParameters(parameters):
from trl.models.utils import add_hooks, remove_hooks
remove_hooks(model)
yield accelerator.unwrap_model(model)
add_hooks(model)
else:
yield unwrapped_model
@contextmanager
def disable_deepspeed_zero3():
import transformers.integrations.deepspeed as ds_module
orig_weak_ref = ds_module._hf_deepspeed_config_weak_ref
ds_module._hf_deepspeed_config_weak_ref = None
try:
yield
finally:
ds_module._hf_deepspeed_config_weak_ref = orig_weak_ref
def get_modules_to_not_convert(model):
if not hasattr(model, 'model_meta') or not hasattr(model, 'model_info'):
return
model_arch = model.model_meta.model_arch
model_type = model.model_meta.model_type
prefix_list = []
suffix_list = []
if model.model_info.is_moe_model:
suffix_list += ['mlp.gate', 'mlp.shared_expert_gate']
if model_type in {'qwen3_next', 'qwen3_5', 'qwen3_5_moe'}:
suffix_list += ['in_proj_a', 'in_proj_b']
if model_arch is not None:
for key in ['vision_tower', 'aligner']:
value = getattr(model_arch, key, None)
if value:
prefix_list += value
suffix_list.append('lm_head')
res = []
for n, m in model.named_modules():
if 'linear' in m.__class__.__name__.lower() and (any(n.endswith(suffix) for suffix in suffix_list)
or any(n.startswith(prefix) for prefix in prefix_list)):
res.append(n)
return res if res else None
def get_packed_seq_params(position_ids: torch.Tensor):
assert position_ids.shape[0] == 1, f'position_ids.shape: {position_ids.shape}'
position_ids_f = position_ids.flatten()
indices_q = torch.arange(position_ids_f.shape[0], device=position_ids_f.device, dtype=torch.int32)
cu_seqlens = torch.cat([
indices_q[position_ids_f == 0],
torch.tensor(position_ids_f.shape, device=position_ids_f.device, dtype=torch.int32),
])
max_length = cu_seqlens.diff().max() # position_ids_f.max() + 1
return {
'cu_seq_lens_q': cu_seqlens,
'cu_seq_lens_k': cu_seqlens,
'max_length_q': max_length,
'max_length_k': max_length,
}