char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
fa88ae936
char/aliceai-t5-moe
1from __future__import annotations 2 3import json 4import math 5 6from typing import Callable ,Iterable ,TYPE_CHECKING 7 8import torch 9 10if TYPE_CHECKING : 11from torch import Tensor 12 13from .base import MmprojModel ,ModelBase ,SentencePieceTokenTypes ,TextModel ,gguf ,logger 14 15 16@ ModelBase . register ( "PhiForCausalLM" ) 17@ ModelBase . example ( "microsoft/phi-2" ) 18class Phi2Model (TextModel ): 19model_arch = gguf .MODEL_ARCH .PHI2 20 21def set_gguf_parameters (self ): 22rot_pct = self .rope_parameters ["partial_rotary_factor" ] 23n_embd = self .find_hparam (["hidden_size" ,"n_embd" ]) 24n_head = self .find_hparam (["num_attention_heads" ,"n_head" ]) 25 26self .gguf_writer .add_context_length (self .find_hparam (["n_positions" ,"max_position_embeddings" ])) 27 28self .gguf_writer .add_embedding_length (n_embd ) 29self .gguf_writer .add_feed_forward_length (4 * n_embd ) 30self .gguf_writer .add_block_count (self .block_count ) 31self .gguf_writer .add_head_count (n_head ) 32self .gguf_writer .add_head_count_kv (n_head ) 33self .gguf_writer .add_layer_norm_eps (self .find_hparam (["layer_norm_epsilon" ,"layer_norm_eps" ])) 34self .gguf_writer .add_rope_dimension_count (int (rot_pct * n_embd )// n_head ) 35self .gguf_writer .add_file_type (self .ftype ) 36self .gguf_writer .add_add_bos_token (False ) 37 38 39@ ModelBase . register ( "Phi3ForCausalLM" , "Phi4ForCausalLMV" ) 40@ ModelBase . example ( "microsoft/Phi-3-mini-4k-instruct" ) 41class Phi3MiniModel (TextModel ): 42model_arch = gguf .MODEL_ARCH .PHI3 43 44def set_vocab (self ): 45# Phi-4 model uses GPT2Tokenizer 46tokenizer_config_file = self .dir_model / 'tokenizer_config.json' 47if tokenizer_config_file .is_file (): 48with open (tokenizer_config_file ,"r" ,encoding = "utf-8" )as f : 49tokenizer_config_json = json .load (f ) 50tokenizer_class = tokenizer_config_json ['tokenizer_class' ] 51if tokenizer_class == 'GPT2Tokenizer' : 52return self ._set_vocab_gpt2 () 53 54from sentencepiece import SentencePieceProcessor 55 56tokenizer_path = self .dir_model / 'tokenizer.model' 57 58if not tokenizer_path .is_file (): 59raise ValueError (f'Error: Missing { tokenizer_path } ' ) 60 61tokenizer = SentencePieceProcessor () 62tokenizer .LoadFromFile (str (tokenizer_path )) 63 64vocab_size = self .hparams .get ('vocab_size' ,tokenizer .vocab_size ()) 65 66tokens :list [bytes ]= [f"[PAD { i } ]" .encode ("utf-8" )for i in range (vocab_size )] 67scores :list [float ]= [- 10000.0 ]* vocab_size 68toktypes :list [int ]= [SentencePieceTokenTypes .UNUSED ]* vocab_size 69 70for token_id in range (tokenizer .vocab_size ()): 71 72piece = tokenizer .IdToPiece (token_id ) 73text = piece .encode ("utf-8" ) 74score = tokenizer .GetScore (token_id ) 75 76toktype = SentencePieceTokenTypes .NORMAL 77if tokenizer .IsUnknown (token_id ): 78toktype = SentencePieceTokenTypes .UNKNOWN 79elif tokenizer .IsControl (token_id ): 80toktype = SentencePieceTokenTypes .CONTROL 81elif tokenizer .IsUnused (token_id ): 82toktype = SentencePieceTokenTypes .UNUSED 83elif tokenizer .IsByte (token_id ): 84toktype = SentencePieceTokenTypes .BYTE 85 86tokens [token_id ]= text 87scores [token_id ]= score 88toktypes [token_id ]= toktype 89 90added_tokens_file = self .dir_model / 'added_tokens.json' 91if added_tokens_file .is_file (): 92with open (added_tokens_file ,"r" ,encoding = "utf-8" )as f : 93added_tokens_json = json .load (f ) 94 95for key in added_tokens_json : 96token_id = added_tokens_json [key ] 97if token_id >= vocab_size : 98logger .debug (f'ignore token { token_id } : id is out of range, max= { vocab_size - 1 } ' ) 99continue 100 101tokens [token_id ]= key .encode ("utf-8" ) 102scores [token_id ]= - 1000.0 103toktypes [token_id ]= SentencePieceTokenTypes .USER_DEFINED 104 105tokenizer_config_file = self .dir_model / 'tokenizer_config.json' 106if tokenizer_config_file .is_file (): 107with open (tokenizer_config_file ,"r" ,encoding = "utf-8" )as f : 108tokenizer_config_json = json .load (f ) 109added_tokens_decoder = tokenizer_config_json .get ("added_tokens_decoder" , {}) 110for token_id ,foken_data in added_tokens_decoder .items (): 111token_id = int (token_id ) 112token = foken_data ["content" ].encode ("utf-8" ) 113if toktypes [token_id ]!= SentencePieceTokenTypes .UNUSED : 114if tokens [token_id ]!= token : 115logger .warning (f'replacing token { token_id } : { tokens [ token_id ]. decode ( "utf-8" )!r } -> { token . decode ( "utf-8" )!r } ' ) 116tokens [token_id ]= token 117scores [token_id ]= - 1000.0 118toktypes [token_id ]= SentencePieceTokenTypes .USER_DEFINED 119if foken_data .get ("special" ): 120toktypes [token_id ]= SentencePieceTokenTypes .CONTROL 121 122tokenizer_file = self .dir_model / 'tokenizer.json' 123if tokenizer_file .is_file (): 124with open (tokenizer_file ,"r" ,encoding = "utf-8" )as f : 125tokenizer_json = json .load (f ) 126added_tokens = tokenizer_json .get ("added_tokens" , []) 127for foken_data in added_tokens : 128token_id = int (foken_data ["id" ]) 129token = foken_data ["content" ].encode ("utf-8" ) 130if toktypes [token_id ]!= SentencePieceTokenTypes .UNUSED : 131if tokens [token_id ]!= token : 132logger .warning (f'replacing token { token_id } : { tokens [ token_id ]. decode ( "utf-8" )!r } -> { token . decode ( "utf-8" )!r } ' ) 133tokens [token_id ]= token 134scores [token_id ]= - 1000.0 135toktypes [token_id ]= SentencePieceTokenTypes .USER_DEFINED 136if foken_data .get ("special" ): 137toktypes [token_id ]= SentencePieceTokenTypes .CONTROL 138 139self .gguf_writer .add_tokenizer_model ("llama" ) 140self .gguf_writer .add_tokenizer_pre ("default" ) 141self .gguf_writer .add_token_list (tokens ) 142self .gguf_writer .add_token_scores (scores ) 143self .gguf_writer .add_token_types (toktypes ) 144 145special_vocab = gguf .SpecialVocab (self .dir_model ,n_vocab = len (tokens )) 146special_vocab .add_to_gguf (self .gguf_writer ) 147 148def set_gguf_parameters (self ): 149n_embd = self .find_hparam (["hidden_size" ,"n_embd" ]) 150n_head = self .find_hparam (["num_attention_heads" ,"n_head" ]) 151n_head_kv = self .find_hparam (["num_key_value_heads" ,"n_head_kv" ]) 152rms_eps = self .find_hparam (["rms_norm_eps" ]) 153max_pos_embds = self .find_hparam (["n_positions" ,"max_position_embeddings" ]) 154orig_max_pos_embds = self .rope_parameters ["original_max_position_embeddings" ] 155rot_pct = self .rope_parameters .get ("partial_rotary_factor" ,1.0 ) 156rope_dims = int (rot_pct * n_embd )// n_head 157 158self .gguf_writer .add_context_length (max_pos_embds ) 159self .gguf_writer .add_rope_scaling_orig_ctx_len (orig_max_pos_embds ) 160self .gguf_writer .add_embedding_length (n_embd ) 161self .gguf_writer .add_feed_forward_length (self .find_hparam (["intermediate_size" ])) 162self .gguf_writer .add_block_count (self .block_count ) 163self .gguf_writer .add_head_count (n_head ) 164self .gguf_writer .add_head_count_kv (n_head_kv ) 165self .gguf_writer .add_layer_norm_rms_eps (rms_eps ) 166self .gguf_writer .add_rope_dimension_count (rope_dims ) 167self .gguf_writer .add_rope_freq_base (self .rope_parameters .get ("full_attention" ,self .rope_parameters )["rope_theta" ]) 168self .gguf_writer .add_file_type (self .ftype ) 169sliding_window = self .hparams .get ("sliding_window" ) 170# use zero value of sliding_window to distinguish Phi-4 from other PHI3 models 171if sliding_window is None : 172sliding_window = 0 173self .gguf_writer .add_sliding_window (sliding_window ) 174 175def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 176n_embd = self .find_hparam (["hidden_size" ,"n_embd" ]) 177n_head = self .find_hparam (["num_attention_heads" ,"n_head" ]) 178max_pos_embds = self .find_hparam (["n_positions" ,"max_position_embeddings" ]) 179orig_max_pos_embds = self .rope_parameters ["original_max_position_embeddings" ] 180rot_pct = self .rope_parameters .get ("partial_rotary_factor" ,1.0 ) 181rope_dims = int (rot_pct * n_embd )// n_head 182 183# write rope scaling for long context (128k) model 184long_factors = self .rope_parameters .get ('long_factor' ) 185short_factors = self .rope_parameters .get ('short_factor' ) 186if not long_factors : 187return 188 189scale = max_pos_embds / orig_max_pos_embds 190 191rope_scaling_type = self .rope_parameters .get ('rope_type' ,'' ).lower () 192if len (rope_scaling_type )== 0 : 193raise KeyError ('Missing the required key rope_scaling.type' ) 194 195if rope_scaling_type == 'su' or rope_scaling_type == 'longrope' : 196attn_factor = math .sqrt (1 + math .log (scale )/ math .log (orig_max_pos_embds ))if scale > 1.0 else 1.0 197elif rope_scaling_type == 'yarn' : 198attn_factor = 0.1 * math .log (scale )+ 1.0 if scale > 1.0 else 1.0 199else : 200raise NotImplementedError (f'The rope scaling type { rope_scaling_type } is not supported yet' ) 201 202self .gguf_writer .add_rope_scaling_attn_factors (attn_factor ) 203 204if long_factors is None or short_factors is None : 205raise KeyError ('Missing the required key rope_scaling.long_factor or rope_scaling_short_factor' ) 206 207if len (long_factors )!= len (short_factors )or len (long_factors )!= rope_dims / 2 : 208raise ValueError (f'The length of rope long and short factors must be { rope_dims / 2 } . long_factors = { len ( long_factors ) } , short_factors = { len ( short_factors ) } .' ) 209 210yield (self .format_tensor_name (gguf .MODEL_TENSOR .ROPE_FACTORS_LONG ),torch .tensor (long_factors ,dtype = torch .float32 )) 211yield (self .format_tensor_name (gguf .MODEL_TENSOR .ROPE_FACTORS_SHORT ),torch .tensor (short_factors ,dtype = torch .float32 )) 212 213 214@ ModelBase . register ( "Phi4ForCausalLMV" ) 215# [TAG_HF_EXAMPLE_MISSING] 216class Phi4VisionMmprojModel (MmprojModel ): 217def __init__ (self ,* args ,** kwargs ): 218super ().__init__ (* args ,** kwargs ) 219assert self .hparams_vision is not None 220 221self .vision_total_layers = int (self .find_vparam (self .n_block_keys )) 222if self .vision_total_layers < 2 : 223raise ValueError ( 224f"Phi-4 vision mmproj conversion requires at least 2 vision layers, got { self . vision_total_layers } " 225 ) 226 227# Phi-4 uses SigLIP2 hidden_states[-2], so export one fewer encoder block and 228# drop post-layernorm/head weights. This makes the GGUF runtime output match 229# the feature map consumed by the patched siglip.cpp Phi-4 projector path. 230self .vision_export_layers = self .vision_total_layers - 1 231self .vision_last_layer_idx = self .vision_total_layers - 1 232 233for key in self .n_block_keys : 234if key in self .hparams_vision : 235self .hparams_vision [key ]= self .vision_export_layers 236break 237 238self .block_count = self .vision_export_layers 239self .tensor_map = gguf .get_tensor_name_map (gguf .MODEL_ARCH .MMPROJ ,self .block_count ) 240 241patch_size = self .preprocessor_config .get ("patch_size" ) 242if patch_size is None : 243raise KeyError ("Phi-4 vision mmproj conversion requires patch_size in preprocessor_config.json" ) 244 245self .hparams_vision ["patch_size" ]= patch_size 246 247pos_emb_name = next ( 248 ( 249name for name in self .model_tensors 250if name .endswith ("vision_model.embeddings.position_embedding.weight" ) 251 ), 252None , 253 ) 254if pos_emb_name is None : 255raise KeyError ("Phi-4 vision mmproj conversion could not find position_embedding.weight" ) 256 257pos_emb_shape = self .model_tensors [pos_emb_name ]().shape 258base_grid_tokens = int (pos_emb_shape [0 ]) 259grid_side = math .isqrt (base_grid_tokens ) 260if grid_side * grid_side != base_grid_tokens : 261raise ValueError (f"Unexpected Phi-4 position embedding shape: { tuple ( pos_emb_shape ) } " ) 262 263self .hparams_vision ["image_size" ]= grid_side * patch_size 264 265min_num_patches = self .preprocessor_config .get ("min_num_patches" ,self .global_config .get ("min_num_patches" )) 266max_num_patches = self .preprocessor_config .get ("max_num_patches" ,self .global_config .get ("max_num_patches" )) 267if min_num_patches is None or max_num_patches is None : 268raise KeyError ("Phi-4 vision mmproj conversion requires min_num_patches and max_num_patches" ) 269 270self .min_pixels = int (min_num_patches )* patch_size * patch_size 271self .max_pixels = int (max_num_patches )* patch_size * patch_size 272 273def set_gguf_parameters (self ): 274super ().set_gguf_parameters () 275assert self .hparams_vision is not None 276 277self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .PHI4 ) 278self .gguf_writer .add_vision_min_pixels (self .min_pixels ) 279self .gguf_writer .add_vision_max_pixels (self .max_pixels ) 280self .gguf_writer .add_vision_use_gelu (True ) 281self .gguf_writer .add_vision_attention_layernorm_eps (self .hparams_vision .get ("layer_norm_eps" ,1e-6 )) 282 283@classmethod 284def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 285name ,gen = item 286 287name = name .replace ("model.vision_tower.vision_tower." ,"vision_tower." ) 288 289if not name .startswith (("vision_tower." ,"model.mm_projector." ,"mm_projector." )): 290return None 291 292if ".vision_model.head." in name : 293return None 294 295if ".vision_model.post_layernorm." in name : 296return None 297 298return super ().filter_tensors ((name ,gen )) 299 300def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 301if name .startswith ("vision_tower." ): 302if bid is not None and bid == self .vision_last_layer_idx : 303return 304 305if name .endswith ("vision_model.embeddings.patch_embedding.weight" ): 306assert self .hparams_vision is not None 307if data_torch .ndim != 2 : 308raise ValueError (f"Unexpected Phi-4 patch embedding shape: { tuple ( data_torch . shape ) } " ) 309 310patch_area = self .hparams_vision ["patch_size" ]** 2 311in_features = data_torch .shape [1 ] 312if in_features % patch_area != 0 : 313raise ValueError ( 314f"Phi-4 patch embedding input dim { in_features } is not divisible by patch area { patch_area } " 315 ) 316 317num_channels = in_features // patch_area 318patch_size = self .hparams_vision ["patch_size" ] 319data_torch = data_torch .view (data_torch .shape [0 ],patch_size ,patch_size ,num_channels ) 320data_torch = data_torch .permute (0 ,3 ,1 ,2 ) 321 322yield from super ().modify_tensors (data_torch ,name ,bid ) 323return 324 325if name .startswith (("model.mm_projector." ,"mm_projector." )): 326local_name = name 327local_name = local_name .replace ("model.mm_projector." ,"" ) 328local_name = local_name .replace ("mm_projector." ,"" ) 329 330if not (local_name .startswith ("0." )or local_name .startswith ("2." )): 331return 332 333suffix = ".bias" if local_name .endswith (".bias" )else ".weight" 334mm_idx = int (local_name .split ("." ,maxsplit = 1 )[0 ]) 335yield (self .format_tensor_name (gguf .MODEL_TENSOR .V_MMPROJ ,mm_idx ,suffix = suffix ),data_torch ) 336return 337 338return 339 340 341@ ModelBase . register ( "PhiMoEForCausalLM" ) 342@ ModelBase . example ( "microsoft/Phi-3.5-MoE-instruct" ) 343class PhiMoeModel (Phi3MiniModel ): 344model_arch = gguf .MODEL_ARCH .PHIMOE 345 346_experts :list [dict [str ,Tensor ]]| None = None 347 348def set_gguf_parameters (self ): 349super ().set_gguf_parameters () 350self .gguf_writer .add_expert_used_count (self .find_hparam (["num_experts_per_tok" ,"num_experts_per_token" ])) 351self .gguf_writer .add_expert_count (self .find_hparam (["num_local_experts" ,"num_experts" ])) 352 353def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 354# process the experts separately 355if name .find ("block_sparse_moe.experts" )!= - 1 : 356n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ]) 357assert bid is not None 358 359if self ._experts is None : 360self ._experts = [{}for _ in range (self .block_count )] 361 362self ._experts [bid ][name ]= data_torch 363 364if len (self ._experts [bid ])>= n_experts * 3 : 365# merge the experts into a single 3d tensor 366for w_name in ["w1" ,"w2" ,"w3" ]: 367datas :list [Tensor ]= [] 368 369for xid in range (n_experts ): 370ename = f"model.layers. { bid } .block_sparse_moe.experts. { xid } . { w_name } .weight" 371datas .append (self ._experts [bid ][ename ]) 372del self ._experts [bid ][ename ] 373 374data_torch = torch .stack (datas ,dim = 0 ) 375 376merged_name = f"model.layers. { bid } .block_sparse_moe.experts. { w_name } .weight" 377 378yield from super ().modify_tensors (data_torch ,merged_name ,bid ) 379return 380else : 381return 382 383yield from super ().modify_tensors (data_torch ,name ,bid ) 384 385def prepare_tensors (self ): 386super ().prepare_tensors () 387 388if self ._experts is not None : 389# flatten `list[dict[str, Tensor]]` into `list[str]` 390experts = [k for d in self ._experts for k in d .keys ()] 391if len (experts )> 0 : 392raise ValueError (f"Unprocessed experts: { experts } " )