Files
2026-07-13 13:33:03 +08:00

170 lines
7.5 KiB
Python

import os
import json
from transformers import PretrainedConfig, AutoConfig
from utils.model_mapper import ModelMapper
from typing import Optional, List, Dict, Any, Union
from dataclasses import dataclass, field, asdict
# model config
class LlmConfig(PretrainedConfig):
model_type = "llm_config"
def __init__(self, **kwargs):
self.hidden_size = kwargs.pop("hidden_size", 0)
self.num_attention_heads = kwargs.pop("num_attention_heads", None)
self.num_hidden_layers = kwargs.pop("num_hidden_layers", None)
self.num_key_value_heads = kwargs.pop("num_key_value_heads", self.num_attention_heads)
self.head_dim = kwargs.pop("head_dim", self.hidden_size // self.num_attention_heads if self.num_attention_heads else None)
self.rope_theta = kwargs.pop("rope_theta", 10000.0)
self.rope_ratio = kwargs.pop("rope_ratio", 1.0)
self.sliding_window = kwargs.pop("sliding_window", 0)
self.sliding_window = self.sliding_window if self.sliding_window is not None else 0
self.layer_types = kwargs.pop("layer_types", [])
self.attention_type = kwargs.pop("attention_type", 'full')
self.tie_word_embeddings = kwargs.pop("tie_word_embeddings", False)
self.scale_emb = kwargs.pop("scale_emb", None)
self.conv_L_cache = kwargs.pop("conv_L_cache", 0)
self.rope_parameters = kwargs.pop("rope_parameters", None)
self.qk_norm_after_rope = kwargs.pop("qk_norm_after_rope", False)
self.model_map = kwargs.pop("model_map", {})
super().__init__(**kwargs)
@staticmethod
def _register_external_model(model_type: str):
EXTERNAL_MODEL_REGISTRY = {
'funaudiochat': ('funaudiochat.register', 'register_funaudiochat'),
}
if model_type in EXTERNAL_MODEL_REGISTRY:
module_path, func_name = EXTERNAL_MODEL_REGISTRY[model_type]
try:
import importlib
module = importlib.import_module(module_path)
getattr(module, func_name)()
except ImportError:
raise ImportError(
f"{model_type} requires external package. "
f"Please clone it from GitHub and set PYTHONPATH accordingly."
)
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
config_path = os.path.join(pretrained_model_name_or_path, 'config.json')
if os.path.exists(config_path):
with open(config_path, 'r') as f:
raw_config = json.load(f)
model_type = raw_config.get('model_type')
cls._register_external_model(model_type)
# Handle models without top-level model_type (e.g., lfm2_audio)
if model_type is None:
archs = raw_config.get('architectures', [])
if 'Lfm2AudioForConditionalGeneration' in archs and 'lfm' in raw_config:
from transformers import Lfm2Config
config = Lfm2Config(**raw_config['lfm'])
config.model_type = 'lfm2_audio'
else:
config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True, **kwargs)
else:
config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True, **kwargs)
model_type, model_map = ModelMapper().get_map(config)
llm_config_kwargs = {
'origin_config': config,
'model_type': model_type,
'model_map': model_map
}
llm_config = cls(**llm_config_kwargs)
# rename attribute for different models
ModelMapper.do_map(llm_config, config, model_map['config'])
# Post-processing and setting defaults
if llm_config.num_key_value_heads is None:
llm_config.num_key_value_heads = llm_config.num_attention_heads
# Compatibility: transformers>=5.x moved rope_theta into rope_parameters dict
if llm_config.rope_theta is None or llm_config.rope_theta == 10000.0:
# Try rope_parameters (transformers 5.x style)
rp = getattr(config, 'rope_parameters', None) or getattr(config, 'rope_scaling', None)
if isinstance(rp, dict) and 'rope_theta' in rp:
llm_config.rope_theta = rp['rope_theta']
# Fallback to raw config JSON
elif 'rope_theta' in raw_config:
llm_config.rope_theta = raw_config['rope_theta']
if llm_config.rope_theta is None:
llm_config.rope_theta = 10000.0
if llm_config.rope_ratio is None:
llm_config.rope_ratio = 1.0
if llm_config.head_dim is None and llm_config.hidden_size and llm_config.num_attention_heads:
if isinstance(llm_config.num_attention_heads, list):
llm_config.head_dim = [llm_config.hidden_size // atten_head for atten_head in llm_config.num_attention_heads]
else:
llm_config.head_dim = llm_config.hidden_size // llm_config.num_attention_heads
# Determine attention type.
# Only `sliding_attention` layers need a sliding-window mask and
# therefore the "mix" path. `linear_attention` layers (e.g. Qwen3.5)
# do not use the attention mask at all, so they are treated as full.
sliding_attn_layers = []
if hasattr(llm_config, 'layer_types') and llm_config.layer_types:
for i in range(len(llm_config.layer_types)):
if llm_config.layer_types[i] == 'sliding_attention':
sliding_attn_layers.append(i)
if llm_config.num_hidden_layers and len(sliding_attn_layers) >= llm_config.num_hidden_layers:
llm_config.attention_type = 'sliding'
elif len(sliding_attn_layers) > 0:
llm_config.attention_type = 'mix'
llm_config.sliding_attn_layers = sliding_attn_layers
else:
llm_config.attention_type = 'full'
return llm_config
# export config
@dataclass
class VisionExportConfig:
"""Configuration for vision-related capabilities."""
image_mean: Optional[List[float]] = field(default_factory=list)
image_norm: Optional[List[float]] = field(default_factory=list)
image_size: Optional[Union[int, List[int]]] = None
image_size_unit: Optional[int] = None
vision_start: Optional[int] = None
vision_end: Optional[int] = None
image_pad: Optional[int] = None
num_grid_per_side: Optional[int] = None
has_deepstack: bool = False
image_max_size: Optional[int] = None
global_image: Optional[int] = None
vision_id_start_id: Optional[int] = None
vision_id_end_id: Optional[int] = None
vision_slice_start_id: Optional[int] = None
vision_slice_end_id: Optional[int] = None
@dataclass
class LLMExportConfig:
"""Top-level container for all export configurations."""
is_audio: bool = False
is_visual: bool = False
has_talker: bool = False
attention_mask: str = 'float'
attention_type: str = 'full'
sliding_window: int = 0
tie_embeddings: Optional[Union[List[int], Dict[str, Any]]] = field(default_factory=list)
jinja: Dict[str, Any] = field(default_factory=dict)
vision: Optional[VisionExportConfig] = None
def to_dict(self) -> Dict[str, Any]:
"""Converts the configuration to a dictionary for JSON serialization."""
nested_dict = asdict(self)
vision_data = nested_dict.pop('vision', None)
if vision_data:
nested_dict.update(vision_data)
final_dict = {key: value for key, value in nested_dict.items() if value is not None}
return final_dict