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 re 5 6from pathlib import Path 7from typing import Callable ,Iterable ,TYPE_CHECKING 8 9import torch 10 11if TYPE_CHECKING : 12from torch import Tensor 13 14from .base import MmprojModel ,ModelBase ,TextModel ,gguf ,logger 15 16from .qwen import QwenModel 17 18 19@ ModelBase . register ( "HunYuanMoEV1ForCausalLM" ) 20@ ModelBase . example ( "tencent/Hunyuan-A13B-Instruct" ) 21class HunYuanMoEModel (TextModel ): 22model_arch = gguf .MODEL_ARCH .HUNYUAN_MOE 23 24def set_vocab (self ): 25from transformers import AutoTokenizer 26tokenizer = AutoTokenizer .from_pretrained (self .dir_model ,trust_remote_code = True ) 27 28# 1. Get the pre-tokenizer identifier hash 29tokpre = self .get_vocab_base_pre (tokenizer ) 30 31# 2. Reverse-engineer the merges list from mergeable_ranks 32merges = [] 33vocab = {} 34mergeable_ranks = tokenizer .mergeable_ranks # ty: ignore[unresolved-attribute] 35for token ,rank in mergeable_ranks .items (): 36vocab [QwenModel .token_bytes_to_string (token )]= rank 37if len (token )== 1 : 38continue 39merged = QwenModel .bpe (mergeable_ranks ,token ,max_rank = rank ) 40if len (merged )== 2 :# todo this is an assert in Qwen, why? 41merges .append (' ' .join (map (QwenModel .token_bytes_to_string ,merged ))) 42 43# 3. Generate the tokens and toktypes lists 44vocab_size = self .hparams ["vocab_size" ] 45assert tokenizer .vocab_size == vocab_size # ty: ignore[unresolved-attribute] 46special_tokens = tokenizer .special_tokens # ty: ignore[unresolved-attribute] 47reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in {** vocab ,** special_tokens }.items ()} 48tokens :list [str ]= [] 49toktypes :list [int ]= [] 50for i in range (vocab_size ): 51if i not in reverse_vocab : 52tokens .append (f"[PAD { i } ]" ) 53toktypes .append (gguf .TokenType .UNUSED ) 54else : 55token = reverse_vocab [i ] 56tokens .append (token ) 57if i in special_tokens .values (): 58toktypes .append (gguf .TokenType .CONTROL ) 59else : 60toktypes .append (gguf .TokenType .NORMAL ) 61 62# 4. Write all vocab-related fields to the GGUF writer 63self .gguf_writer .add_tokenizer_model ("gpt2" ) 64self .gguf_writer .add_tokenizer_pre (tokpre ) 65self .gguf_writer .add_token_list (tokens ) 66self .gguf_writer .add_token_types (toktypes ) 67self .gguf_writer .add_token_merges (merges ) 68 69# 5. Add special tokens and chat templates 70special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = False ) 71special_vocab .add_to_gguf (self .gguf_writer ) 72# FIX for BOS token: Overwrite incorrect id read from config.json 73self .gguf_writer .add_bos_token_id (127959 )# <|bos|> 74 75def set_gguf_parameters (self ): 76super ().set_gguf_parameters () 77hparams = self .hparams 78 79self .gguf_writer .add_expert_shared_feed_forward_length (hparams ["intermediate_size" ]) 80 81moe_intermediate_size = hparams ["moe_intermediate_size" ] 82assert all (n == moe_intermediate_size [0 ]for n in moe_intermediate_size ) 83self .gguf_writer .add_expert_feed_forward_length (moe_intermediate_size [0 ]) 84 85moe_topk = hparams ["moe_topk" ] 86assert all (topk == moe_topk [0 ]for topk in moe_topk ) 87self .gguf_writer .add_expert_used_count (moe_topk [0 ]) 88 89moe_shared_expert = hparams ["num_shared_expert" ] 90assert all (n == moe_shared_expert [0 ]for n in moe_shared_expert ) 91self .gguf_writer .add_expert_shared_count (moe_shared_expert [0 ]) 92 93# Rope 94if self .rope_parameters .get ("rope_type" )== "dynamic" : 95# HunYuan uses NTK Aware Alpha based scaling. Original implementation: https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/ 96# 1000 corresponds to a usable context length of 256k (https://github.com/Tencent-Hunyuan/Hunyuan-A13B/blob/main/report/Hunyuan_A13B_Technical_Report.pdf) 97alpha = self .rope_parameters .get ("alpha" ,1000 ) 98base = self .rope_parameters .get ("rope_theta" ,10000.0 ) 99dim = (hparams ["hidden_size" ]// hparams ["num_attention_heads" ])# 128 100scaled_base = base * (alpha ** (dim / (dim - 2 )))# 10000 * (1000 ** (128 / 126)) = 11158839.9251 101self .gguf_writer .add_rope_freq_base (scaled_base ) 102self .gguf_writer .add_rope_scaling_type (gguf .RopeScalingType .NONE ) 103self .gguf_writer .add_rope_scaling_factor (1 ) 104# There is no consistent way to calculate ctx from alpha, and the config is incorrectly set to 32k 105self .gguf_writer .add_rope_scaling_orig_ctx_len (256 * 1024 )# 256k context length 106self .gguf_writer .add_context_length (256 * 1024 )# 256k context length 107 108# if any of our assumptions about the values are wrong, something has changed and this may need to be updated 109assert alpha == 1000 and base == 10000.0 and dim == 128 and self .hparams ["max_position_embeddings" ]in [32 * 1024 ,256 * 1024 ] , \ 110"HunYuan dynamic RoPE scaling assumptions changed, please update the logic or context length manually" 111 112_experts :list [dict [str ,Tensor ]]| None = None 113 114def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 115if name == "lm_head.weight" : 116if self .hparams .get ("tie_word_embeddings" ,False ): 117logger .info ("Skipping tied output layer 'lm_head.weight'" ) 118return 119 120if name .find ("mlp.experts" )!= - 1 : 121n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ]) 122assert bid is not None 123 124if self ._experts is None : 125self ._experts = [{}for _ in range (self .block_count )] 126 127self ._experts [bid ][name ]= data_torch 128 129if len (self ._experts [bid ])>= n_experts * 3 : 130# merge the experts into a single 3d tensor 131for w_name in ["down_proj" ,"gate_proj" ,"up_proj" ]: 132datas :list [Tensor ]= [] 133 134for xid in range (n_experts ): 135ename = f"model.layers. { bid } .mlp.experts. { xid } . { w_name } .weight" 136datas .append (self ._experts [bid ][ename ]) 137del self ._experts [bid ][ename ] 138 139data_torch = torch .stack (datas ,dim = 0 ) 140merged_name = f"model.layers. { bid } .mlp.experts. { w_name } .weight" 141 142yield from super ().modify_tensors (data_torch ,merged_name ,bid ) 143return 144else : 145return 146 147yield from super ().modify_tensors (data_torch ,name ,bid ) 148 149def prepare_tensors (self ): 150super ().prepare_tensors () 151if self ._experts is not None : 152experts = [k for d in self ._experts for k in d .keys ()] 153if len (experts )> 0 : 154raise ValueError (f"Unprocessed experts: { experts } " ) 155 156 157@ ModelBase . register ( "HunYuanDenseV1ForCausalLM" ) 158@ ModelBase . example ( "tencent/Hunyuan-4B-Instruct" ) 159class HunYuanModel (TextModel ): 160model_arch = gguf .MODEL_ARCH .HUNYUAN_DENSE 161 162def _get_eod_token_id (self )-> int | None : 163"""Get the actual end-of-generation token from config (eod_token_id).""" 164return self .hparams .get ("eod_token_id" ) 165 166def _get_eot_token_id (self )-> int | None : 167"""Get the end-of-turn token from generation_config.json. 168This is the first entry in eos_token_id when it's a list.""" 169gen_cfg_path = self .dir_model / "generation_config.json" 170if gen_cfg_path .is_file (): 171with open (gen_cfg_path ,encoding = "utf-8" )as f : 172gen_cfg = json .load (f ) 173eos = gen_cfg .get ("eos_token_id" ) 174if isinstance (eos ,list )and len (eos )>= 2 : 175return eos [0 ] 176return None 177 178def _fix_special_tokens (self ): 179"""Fix EOS/EOT tokens that are incorrect in upstream configs.""" 180eod_id = self ._get_eod_token_id () 181if eod_id is not None : 182self .gguf_writer .add_eos_token_id (eod_id ) 183eot_id = self ._get_eot_token_id () 184if eot_id is not None : 185self .gguf_writer .add_eot_token_id (eot_id ) 186 187def set_vocab (self ): 188if (self .dir_model / "tokenizer.json" ).is_file (): 189tokens ,toktypes ,tokpre = self .get_vocab_base () 190self .gguf_writer .add_tokenizer_model ("gpt2" ) 191self .gguf_writer .add_tokenizer_pre (tokpre ) 192self .gguf_writer .add_token_list (tokens ) 193self .gguf_writer .add_token_types (toktypes ) 194 195# Some HunYuanVL variants (e.g. OCR-style configs) have pad_token_id=-1; 196# guard SpecialVocab so it doesn't try to emit an invalid pad id. 197token_types = None 198if (self .hparams .get ("pad_token_id" )or 0 )< 0 : 199token_types = ('bos' ,'eos' ,'unk' ,'sep' ,'cls' ,'mask' ) 200special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ,special_token_types = token_types ) 201special_vocab .add_to_gguf (self .gguf_writer ) 202self ._fix_special_tokens () 203else : 204from transformers import AutoTokenizer 205tokenizer = AutoTokenizer .from_pretrained (self .dir_model ,trust_remote_code = True ) 206 207# 1. Get the pre-tokenizer identifier hash 208tokpre = self .get_vocab_base_pre (tokenizer ) 209 210# 2. Reverse-engineer the merges list from mergeable_ranks 211merges = [] 212vocab = {} 213mergeable_ranks = tokenizer .mergeable_ranks # ty: ignore[unresolved-attribute] 214for token ,rank in mergeable_ranks .items (): 215vocab [QwenModel .token_bytes_to_string (token )]= rank 216if len (token )== 1 : 217continue 218merged = QwenModel .bpe (mergeable_ranks ,token ,max_rank = rank ) 219if len (merged )== 2 : 220merges .append (' ' .join (map (QwenModel .token_bytes_to_string ,merged ))) 221 222# 3. Generate the tokens and toktypes lists 223vocab_size = self .hparams ["vocab_size" ] 224assert tokenizer .vocab_size == vocab_size # ty: ignore[unresolved-attribute] 225special_tokens = tokenizer .special_tokens # ty: ignore[unresolved-attribute] 226reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in {** vocab ,** special_tokens }.items ()} 227tokens :list [str ]= [] 228toktypes :list [int ]= [] 229for i in range (vocab_size ): 230if i not in reverse_vocab : 231tokens .append (f"[PAD { i } ]" ) 232toktypes .append (gguf .TokenType .UNUSED ) 233else : 234token = reverse_vocab [i ] 235tokens .append (token ) 236if i in special_tokens .values (): 237toktypes .append (gguf .TokenType .CONTROL ) 238else : 239toktypes .append (gguf .TokenType .NORMAL ) 240 241# 4. Write all vocab-related fields to the GGUF writer 242self .gguf_writer .add_tokenizer_model ("gpt2" ) 243self .gguf_writer .add_tokenizer_pre (tokpre ) 244self .gguf_writer .add_token_list (tokens ) 245self .gguf_writer .add_token_types (toktypes ) 246self .gguf_writer .add_token_merges (merges ) 247 248# 5. Add special tokens and chat templates 249special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = False ) 250special_vocab .add_to_gguf (self .gguf_writer ) 251# FIX for BOS token: Overwrite incorrect id read from config.json 252if self .hparams ['hidden_size' ]== 4096 : 253self .gguf_writer .add_bos_token_id (127958 )# only for 7b dense, fix <|bos|> token 254self ._fix_special_tokens () 255 256def set_gguf_parameters (self ): 257# Some HunYuanVL variants set num_experts=1 (not real MoE); 258# prevent the parent class from emitting expert_count metadata in that case. 259saved_num_experts = self .hparams .pop ("num_experts" ,None ) 260super ().set_gguf_parameters () 261if saved_num_experts is not None and saved_num_experts > 1 : 262self .hparams ["num_experts" ]= saved_num_experts 263hparams = self .hparams 264 265# Rope 266if self .rope_parameters .get ("rope_type" )in ("dynamic" ,"xdrope" ): 267# HunYuan uses NTK Aware Alpha based scaling. Original implementation: https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/ 268# 1000 corresponds to a usable context length of 256k (https://github.com/Tencent-Hunyuan/Hunyuan-A13B/blob/main/report/Hunyuan_A13B_Technical_Report.pdf) 269alpha = self .rope_parameters .get ("alpha" ,50 ) 270base = self .rope_parameters .get ("rope_theta" ,10000.0 ) 271dim = hparams ["head_dim" ] 272scaled_base = base * (alpha ** (dim / (dim - 2 ))) 273self .gguf_writer .add_rope_freq_base (scaled_base ) 274self .gguf_writer .add_rope_scaling_type (gguf .RopeScalingType .NONE ) 275self .gguf_writer .add_rope_scaling_factor (1 ) 276if self .rope_parameters .get ("rope_type" )== "dynamic" : 277# There is no consistent way to calculate ctx from alpha, and the config is incorrectly set to 32k 278self .gguf_writer .add_rope_scaling_orig_ctx_len (256 * 1024 )# 256k context length 279self .gguf_writer .add_context_length (256 * 1024 )# 256k context length 280 281# if any of our assumptions about the values are wrong, something has changed and this may need to be updated 282assert base == 10000.0 and self .hparams ["max_position_embeddings" ]in [32 * 1024 ,256 * 1024 ] , \ 283"HunYuan dynamic RoPE scaling assumptions changed, please update the logic or context length manually" 284 285def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 286if name == "lm_head.weight" : 287if self .hparams .get ("tie_word_embeddings" ,False ): 288logger .info ("Skipping tied output layer 'lm_head.weight'" ) 289return 290 291yield from super ().modify_tensors (data_torch ,name ,bid ) 292 293 294@ ModelBase . register ( "HunYuanVLForConditionalGeneration" ) 295@ ModelBase . example ( "tencent/HunyuanOCR" ) 296class HunyuanVLVisionModel (MmprojModel ): 297def __init__ (self ,* args ,** kwargs ): 298super ().__init__ (* args ,** kwargs ) 299assert self .hparams_vision is not None 300# HunyuanVL uses max_image_size instead of image_size 301if "image_size" not in self .hparams_vision : 302self .hparams_vision ["image_size" ]= self .hparams_vision .get ("max_image_size" ,2048 ) 303 304def set_gguf_parameters (self ): 305super ().set_gguf_parameters () 306assert self .hparams_vision is not None 307vcfg = self .hparams_vision 308self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .HUNYUANVL ) 309self .gguf_writer .add_vision_use_gelu (True ) 310self .gguf_writer .add_vision_attention_layernorm_eps (vcfg .get ("rms_norm_eps" ,1e-5 )) 311self .gguf_writer .add_vision_spatial_merge_size (vcfg .get ("spatial_merge_size" ,2 )) 312self .gguf_writer .add_vision_min_pixels (int (self .preprocessor_config ["min_pixels" ])) 313self .gguf_writer .add_vision_max_pixels (int (self .preprocessor_config ["max_pixels" ])) 314 315@classmethod 316def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 317name ,gen = item 318 319if not name .startswith ("vit." ): 320return None 321 322return super ().filter_tensors (item ) 323 324def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 325# strip CLS token (row 0) from position embeddings so resize_position_embeddings works 326if "position_embedding" in name : 327data_torch = data_torch [1 :]# [n_patches+1, n_embd] -> [n_patches, n_embd] 328yield from super ().modify_tensors (data_torch ,name ,bid ) 329 330def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 331# force conv weights to F32 or F16 to avoid BF16 IM2COL issues on Metal 332# HunyuanVL emit the ViT -> LLM projection as mm.0/mm.2. 333if ("mm.0." in new_name or "mm.2." in new_name )and new_name .endswith (".weight" ): 334return gguf .GGMLQuantizationType .F16 if self .ftype == gguf .LlamaFileType .MOSTLY_F16 else gguf .GGMLQuantizationType .F32 335return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 336 337 338@ ModelBase . register ( "HunYuanVLForConditionalGeneration" ) 339@ ModelBase . example ( "tencent/HunyuanOCR" ) 340class HunyuanVLTextModel (HunYuanModel ): 341model_arch = gguf .MODEL_ARCH .HUNYUAN_VL 342 343def __init__ (self ,dir_model :Path ,* args ,** kwargs ): 344super ().__init__ (dir_model ,* args ,** kwargs ) 345# transformers 5.13.0 encodes HunyuanVL XD-RoPE as dynamic + mrope_section. 346# Normalize it to avoid the HunYuan dynamic-RoPE context assertion. 347if self .rope_parameters .get ("rope_type" )== "dynamic" and "mrope_section" in self .rope_parameters : 348self .rope_parameters ["rope_type" ]= "xdrope" 349self .rope_parameters ["type" ]= "xdrope" 350self .rope_parameters ["xdrope_section" ]= list (self .rope_parameters ["mrope_section" ]) 351 352def set_gguf_parameters (self ): 353super ().set_gguf_parameters () 354 355# XD-RoPE metadata for the HunyuanVL; 356if self .rope_parameters .get ("rope_type" )!= "xdrope" : 357return 358 359self .gguf_writer .add_rope_freq_base (float (self .rope_parameters ["rope_theta" ])) 360self .gguf_writer .add_rope_scaling_alpha (float (self .rope_parameters ["alpha" ])) 361self .gguf_writer .add_rope_scaling_type (gguf .RopeScalingType .NONE ) 362self .gguf_writer .add_rope_scaling_factor (float (self .rope_parameters .get ("factor" ,1 ))) 363 364ctx_len = int (self .hparams ["max_position_embeddings" ]) 365self .gguf_writer .add_rope_scaling_orig_ctx_len (ctx_len ) 366self .gguf_writer .add_context_length (ctx_len ) 367 368self .gguf_writer .add_rope_dimension_sections (list (self .rope_parameters ["xdrope_section" ])) 369 370 371@ ModelBase . register ( "HYV3ForCausalLM" ) 372@ ModelBase . example ( "tencent/Hy3" ) 373class HYV3Model (TextModel ): 374model_arch = gguf .MODEL_ARCH .HY_V3 375supports_mtp_export = True 376 377# Trunk layer count, stashed before indexing so the classmethod 378# filter_tensors can identify the appended MTP block(s) (mirrors 379# Step35Model). 380_n_main_layers :int | None = None 381 382def __init__ (self ,* args ,** kwargs ): 383super ().__init__ (* args ,** kwargs ) 384# NextN/MTP layers are appended past num_hidden_layers; extend the 385# tensor map so the MTP block's tensors resolve to blk.<n>.* names. 386n_nextn = int (self .hparams .get ("num_nextn_predict_layers" ,0 )) 387if n_nextn > 0 and not self .no_mtp : 388self .block_count += n_nextn 389self .tensor_map = gguf .get_tensor_name_map (self .model_arch ,self .block_count ) 390 391def index_tensors (self ,remote_hf_model_id :str | None = None ): 392 type(self )._n_main_layers = self .hparams ["num_hidden_layers" ] 393return super ().index_tensors (remote_hf_model_id = remote_hf_model_id ) 394 395def set_vocab (self ): 396self ._set_vocab_gpt2 () 397 398def set_gguf_parameters (self ): 399super ().set_gguf_parameters () 400self .gguf_writer .add_expert_feed_forward_length (self .hparams ["moe_intermediate_size" ]) 401self .gguf_writer .add_expert_shared_feed_forward_length ( 402self .hparams ["moe_intermediate_size" ]* self .hparams .get ("num_shared_experts" ,1 ) 403 ) 404self .gguf_writer .add_expert_weights_norm (self .hparams .get ("route_norm" ,True )) 405self .gguf_writer .add_expert_weights_scale (float (self .hparams .get ("router_scaling_factor" ,1.0 ))) 406# sigmoid router with expert selection bias 407self .gguf_writer .add_expert_gating_func (gguf .ExpertGatingFuncType .SIGMOID ) 408 409n_nextn = int (self .hparams .get ("num_nextn_predict_layers" ,0 )) 410if n_nextn > 0 and not self .no_mtp : 411self .gguf_writer .add_nextn_predict_layers (n_nextn ) 412 413@classmethod 414def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 415if (titem := super ().filter_tensors (item ))is None : 416return None 417name ,gen = titem 418 419# HY V3 appends the MTP block(s) past num_hidden_layers. 420assert cls ._n_main_layers is not None 421is_mtp = (m := re .match (r"model\.layers\.(\d+)\." ,name ))is not None and int (m .group (1 ))>= cls ._n_main_layers 422 423# --no-mtp: drop the appended MTP block(s) entirely. 424if is_mtp and cls .no_mtp : 425return None 426# --mtp: keep ONLY MTP-block tensors plus the shared embeddings/norm/ 427# lm_head (so the resulting GGUF carries just the draft head). 428if cls .mtp_only and not is_mtp and name not in ( 429"model.embed_tokens.weight" ,"model.norm.weight" ,"lm_head.weight" , 430 ): 431return None 432 433# The MTP block's trailing final_layernorm (applied after the decoder 434# block, before the shared LM head) maps to nextn.shared_head_norm. 435if is_mtp : 436name = name .replace (".final_layernorm." ,".shared_head.norm." ) 437 438return name ,gen 439 440_experts :list [dict [str ,Tensor ]]| None = None 441 442def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 443# merge the per-expert tensors into stacked 3d tensors 444if name .startswith ("model.layers." )and ".mlp.experts." in name : 445n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ]) 446assert bid is not None 447 448if self ._experts is None : 449self ._experts = [{}for _ in range (self .block_count )] 450 451self ._experts [bid ][name ]= data_torch 452 453if len (self ._experts [bid ])>= n_experts * 3 : 454for w_name in ("down_proj" ,"gate_proj" ,"up_proj" ): 455datas :list [Tensor ]= [] 456for xid in range (n_experts ): 457ename = f"model.layers. { bid } .mlp.experts. { xid } . { w_name } .weight" 458datas .append (self ._experts [bid ][ename ]) 459del self ._experts [bid ][ename ] 460 461merged = torch .stack (datas ,dim = 0 ) 462yield from super ().modify_tensors (merged ,f"model.layers. { bid } .mlp.experts. { w_name } .weight" ,bid ) 463return 464 465yield from super ().modify_tensors (data_torch ,name ,bid ) 466 467def prepare_tensors (self ): 468super ().prepare_tensors () 469if self ._experts is not None : 470experts = [k for d in self ._experts for k in d .keys ()] 471if experts : 472raise ValueError (f"Unprocessed experts: { experts } " )