# 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, }