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 4 5from typing import Any ,Callable ,Iterable ,TYPE_CHECKING 6 7if TYPE_CHECKING : 8from torch import Tensor 9 10from .base import MmprojModel ,ModelBase ,gguf ,logger 11 12from .qwen import Qwen3Model ,Qwen3MoeModel 13from .qwenvl import Qwen25AudioModel 14 15 16@ ModelBase . register ( "Qwen3VLForConditionalGeneration" , "Qwen3VLMoeForConditionalGeneration" , "Qwen3_5ForConditionalGeneration" , "Qwen3_5MoeForConditionalGeneration" ) 17@ ModelBase . example ( "Qwen/Qwen3-VL-4B-Instruct" , "Qwen/Qwen3-VL-30B-A3B-Instruct" , "Qwen/Qwen3.5-9B" , "Qwen/Qwen3.5-35B-A3B" ) 18class Qwen3VLVisionModel (MmprojModel ): 19def __init__ (self ,* args ,** kwargs ): 20super ().__init__ (* args ,** kwargs ) 21if self .hparams_vision is None : 22logger .info ("No vision config found, skipping vision tensor processing" ) 23return 24 25# Compute image_size if not present 26if "image_size" not in self .hparams_vision : 27# For Qwen3VL/Qwen3VLMoe, compute from num_position_embeddings 28num_pos = self .hparams_vision .get ("num_position_embeddings" ,2304 ) 29patch_size = self .hparams_vision .get ("patch_size" ,16 ) 30# num_position_embeddings = (image_size / patch_size) ** 2 31# So image_size = sqrt(num_position_embeddings) * patch_size 32image_size = int (num_pos ** 0.5 * patch_size ) 33self .hparams_vision ["image_size" ]= image_size 34 35# Rename config values for compatibility 36self .hparams_vision ["num_attention_heads" ]= self .hparams_vision .get ("num_heads" ) 37self .hparams_vision ["num_hidden_layers" ]= self .hparams_vision .get ("depth" ) 38 39self .is_deepstack_layers = [False ]* int (self .hparams_vision ["num_hidden_layers" ]or 0 ) 40for idx in self .hparams_vision .get ("deepstack_visual_indexes" , []): 41self .is_deepstack_layers [idx ]= True 42 43def set_gguf_parameters (self ): 44super ().set_gguf_parameters () 45# in case mixed modalities, the arch will be handled by subclass 46if not self .has_audio_encoder : 47self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .QWEN3VL ) 48self .gguf_writer .add_vision_use_gelu (True ) 49 50if self .hparams_vision is not None : 51merge_size = self .hparams_vision .get ("spatial_merge_size" ) 52if merge_size is not None : 53self .gguf_writer .add_vision_spatial_merge_size (int (merge_size )) 54 55# Use text config's rms_norm_eps for vision attention layernorm eps 56rms_norm_eps = self .global_config .get ("text_config" , {}).get ("rms_norm_eps" ,1e-6 ) 57self .gguf_writer .add_vision_attention_layernorm_eps (rms_norm_eps ) 58 59if self .is_deepstack_layers : 60self .gguf_writer .add_vision_is_deepstack_layers (self .is_deepstack_layers ) 61 62@classmethod 63def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 64name ,gen = item 65 66# Skip text model tensors 67if name .startswith ("lm_head." ): 68return None 69 70# Skip MTP tensors 71if name .startswith ("mtp." ): 72return None 73 74if name .startswith ("model.visual." ): 75name = name .replace ("model.visual." ,"visual." ,1 ) 76 77if not name .startswith ("visual." ): 78return None 79 80return super ().filter_tensors ((name ,gen )) 81 82def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 83assert self .hparams_vision is not None 84 85if name .startswith ("visual.deepstack_merger_list." ): 86prefix ,rest = name .split ("." ,maxsplit = 3 )[2 :] 87# prefix is the layer index, convert to absolute clip layer index! 88idx = self .hparams_vision .get ("deepstack_visual_indexes" , [])[int (prefix )] 89target = rest 90 91tensor_type :gguf .MODEL_TENSOR 92if target .startswith ("norm." ): 93tensor_type = gguf .MODEL_TENSOR .V_DS_NORM 94suffix = target .split ("." ,1 )[1 ] 95elif target .startswith ("linear_fc1." ): 96tensor_type = gguf .MODEL_TENSOR .V_DS_FC1 97suffix = target .split ("." ,1 )[1 ] 98elif target .startswith ("linear_fc2." ): 99tensor_type = gguf .MODEL_TENSOR .V_DS_FC2 100suffix = target .split ("." ,1 )[1 ] 101else : 102raise ValueError (f"Unexpected deepstack tensor: { name } " ) 103 104new_name = self .format_tensor_name (tensor_type ,idx ,suffix = f". { suffix } " ) 105yield from super ().modify_tensors (data_torch ,new_name ,bid ) 106return 107 108if name .startswith ("visual.merger." ): 109suffix = name .split ("." ,2 )[2 ] 110if suffix .startswith ("linear_fc" ): 111fc_idx_str ,tail = suffix .split ("." ,1 ) 112fc_num = int (fc_idx_str .replace ("linear_fc" ,"" )) 113# Qwen3VL has linear_fc1 and linear_fc2 114# Map to indices 0 and 2 (matching Qwen2VL which uses indices 0 and 2) 115if fc_num == 1 : 116fc_idx = 0 117elif fc_num == 2 : 118fc_idx = 2 119else : 120raise ValueError (f"unexpected fc index { fc_num } in { name } " ) 121new_name = self .format_tensor_name (gguf .MODEL_TENSOR .V_MMPROJ ,fc_idx ,suffix = f". { tail } " ) 122elif suffix .startswith ("norm." ): 123new_name = self .format_tensor_name (gguf .MODEL_TENSOR .V_POST_NORM ,suffix = f". { suffix . split ( '.' , 1 )[ 1 ] } " ) 124else : 125raise ValueError (f"Unexpected merger tensor: { name } " ) 126yield (new_name ,data_torch ) 127return 128 129if name == "visual.patch_embed.proj.weight" : 130# split Conv3D into Conv2Ds along temporal dimension 131c1 ,c2 ,kt ,_ ,_ = data_torch .shape 132del c1 ,c2 133if kt != 2 : 134raise ValueError ("Current implementation only supports temporal_patch_size of 2" ) 135yield (gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .V_ENC_EMBD_PATCH ]+ ".weight" ,data_torch [:, :,0 , ...]) 136yield (gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .V_ENC_EMBD_PATCH ]+ ".weight.1" ,data_torch [:, :,1 , ...]) 137return 138 139if name == "visual.patch_embed.proj.bias" : 140# Include the bias - it's used by the C++ code 141yield (gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .V_ENC_EMBD_PATCH ]+ ".bias" ,data_torch ) 142return 143 144yield from MmprojModel .modify_tensors (self ,data_torch ,name ,bid ) 145 146 147@ ModelBase . register ( "Qwen3OmniMoeForConditionalGeneration" ) 148@ ModelBase . example ( "Qwen/Qwen3-Omni-30B-A3B-Instruct" ) 149class Qwen3OmniMmprojModel (Qwen3VLVisionModel ,Qwen25AudioModel ): 150has_audio_encoder = True 151has_vision_encoder = True 152 153def get_vision_config (self )-> dict [str ,Any ]| None : 154if self .has_vision_encoder : 155return self .global_config ["thinker_config" ].get ("vision_config" ) 156else : 157return None 158 159def get_audio_config (self )-> dict [str ,Any ]| None : 160if self .has_audio_encoder : 161return self .global_config ["thinker_config" ].get ("audio_config" ) 162else : 163return None 164 165def set_gguf_parameters (self ): 166if self .has_vision_encoder : 167Qwen3VLVisionModel .set_gguf_parameters (self ) 168self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .QWEN3VL ) 169if self .has_audio_encoder : 170Qwen25AudioModel .set_gguf_parameters (self ) 171self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .QWEN3A ) 172 173@classmethod 174def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 175name ,gen = item 176 177# Skip text model tensors 178if name .startswith ("lm_head." ): 179return None 180 181# Skip MTP tensors 182if name .startswith ("mtp." ): 183return None 184 185if name .startswith ("model.visual." ): 186name = name .replace ("model.visual." ,"visual." ,1 ) 187 188if name .startswith ("thinker.audio_tower." ): 189name = name .replace ("thinker.audio_tower." ,"audio_tower." ,1 ) 190 191if "visual." not in name and "audio_tower." not in name : 192return None 193 194return MmprojModel .filter_tensors ((name ,gen )) 195 196def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 197if "visual." in name : 198if not self .has_vision_encoder : 199raise ValueError (f"Model does not have vision encoder, but found tensor { name } " ) 200# need to transform vision tensor naming, so that modify_tensors() logic can be used correctly 201name = name .replace ("thinker.visual." ,"model.visual." ) 202if ".merger_list." in name : 203name = name .replace (".merger_list." ,".deepstack_merger_list." ) 204name = name .replace (".ln_q" ,".norm" ) 205name = name .replace (".mlp.0" ,".linear_fc1" ) 206name = name .replace (".mlp.2" ,".linear_fc2" ) 207elif ".merger." in name : 208name = name .replace (".ln_q" ,".norm" ) 209name = name .replace (".mlp.0" ,".linear_fc1" ) 210name = name .replace (".mlp.2" ,".linear_fc2" ) 211yield from Qwen3VLVisionModel .modify_tensors (self ,data_torch ,name ,bid ) 212elif "audio_tower." in name : 213if not self .has_audio_encoder : 214raise ValueError (f"Model does not have audio encoder, but found tensor { name } " ) 215if "conv2d" in name and name .endswith (".bias" ): 216# transform conv2d bias [n_embd] --> [1, 1, n_embd] 217data_torch = data_torch .unsqueeze (- 1 ).unsqueeze (- 1 ) 218yield from Qwen25AudioModel .modify_tensors (self ,data_torch ,name ,bid ) 219 220 221@ ModelBase . register ( "Qwen3ASRForConditionalGeneration" ) 222@ ModelBase . example ( "Qwen/Qwen3-ASR-0.6B-hf" ) 223class Qwen3ASRMmprojModel (Qwen3OmniMmprojModel ): 224has_audio_encoder = True 225has_vision_encoder = False 226 227 228@ ModelBase . register ( "Glm4vForConditionalGeneration" , "Glm4vMoeForConditionalGeneration" , "GlmOcrForConditionalGeneration" ) 229@ ModelBase . example ( "zai-org/GLM-4.1V-9B-Thinking" , "zai-org/GLM-4.5V" ) 230class Glm4VVisionModel (Qwen3VLVisionModel ): 231def set_gguf_parameters (self ): 232MmprojModel .set_gguf_parameters (self )# skip Qwen3VLVisionModel parameters 233assert self .hparams_vision is not None 234self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .GLM4V ) 235 236hidden_act = str (self .hparams_vision .get ("hidden_act" ,"" )).lower () 237if hidden_act == "gelu" : 238self .gguf_writer .add_vision_use_gelu (True ) 239elif hidden_act == "silu" : 240self .gguf_writer .add_vision_use_silu (True ) 241 242rms_norm_eps = self .hparams_vision .get ("rms_norm_eps" ,1e-5 ) 243self .gguf_writer .add_vision_attention_layernorm_eps (rms_norm_eps ) 244 245def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 246if name .startswith ("visual.merger." ): 247yield from ModelBase .modify_tensors (self ,data_torch ,name ,bid ) 248return 249yield from super ().modify_tensors (data_torch ,name ,bid ) 250 251 252@ ModelBase . register ( "Qwen3VLForConditionalGeneration" ) 253@ ModelBase . example ( "Qwen/Qwen3-VL-4B-Instruct" ) 254class Qwen3VLTextModel (Qwen3Model ): 255model_arch = gguf .MODEL_ARCH .QWEN3VL 256 257def set_gguf_parameters (self ): 258super ().set_gguf_parameters () 259if "thinker_config" in self .hparams : 260vision_config = self .hparams ["thinker_config" ].get ("vision_config" , {}) 261else : 262vision_config = self .hparams .get ("vision_config" , {}) 263deepstack_layer_num = len (vision_config .get ("deepstack_visual_indexes" , [])) 264self .gguf_writer .add_num_deepstack_layers (deepstack_layer_num ) 265 266@classmethod 267def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 268name ,gen = item 269 270name = name .replace ("thinker." ,"" ) 271 272return super ().filter_tensors ((name ,gen )) 273 274 275@ ModelBase . register ( "Qwen3VLMoeForConditionalGeneration" ) 276@ ModelBase . example ( "Qwen/Qwen3-VL-30B-A3B-Instruct" ) 277class Qwen3VLMoeTextModel (Qwen3MoeModel ): 278model_arch = gguf .MODEL_ARCH .QWEN3VLMOE 279 280def set_gguf_parameters (self ): 281super ().set_gguf_parameters () 282vision_config = self .hparams .get ("vision_config" , {}) 283deepstack_layer_num = len (vision_config .get ("deepstack_visual_indexes" , [])) 284self .gguf_writer .add_num_deepstack_layers (deepstack_layer_num ) 285 286@classmethod 287def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 288name ,gen = item 289 290name = name .replace ("thinker." ,"" ) 291 292return super ().filter_tensors ((name ,gen )) 293 294def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 295# Qwen3VL has transposed packed tensors, so we treat it differently from general Qwen2MoE packed tensors 296if name .endswith ("mlp.experts.down_proj" )or name .endswith ("mlp.experts.down_proj.weight" ): 297mapped = f" { name } .weight" if not name .endswith (".weight" )else name 298permuted = data_torch .permute (0 ,2 ,1 ).contiguous () 299yield from ModelBase .modify_tensors (self ,permuted ,mapped ,bid ) 300return 301 302if name .endswith ("mlp.experts.gate_up_proj" )or name .endswith ("mlp.experts.gate_up_proj.weight" ): 303if data_torch .ndim < 3 or data_torch .shape [- 1 ]% 2 != 0 : 304raise ValueError (f"Unexpected gate_up_proj shape for { name } : { tuple ( data_torch . shape ) } " ) 305split_dim = data_torch .shape [- 1 ]// 2 306gate = data_torch [..., :split_dim ].contiguous () 307up = data_torch [...,split_dim :].contiguous () 308# Input gate/up: (n_expert=128, n_embd=2048, n_ff_exp=768) 309# Want GGML ne: {n_embd, n_ff_exp, n_expert} = {2048, 768, 128} 310# Need PyTorch: (128, 768, 2048) [reversed of GGML] 311# So: permute(0, 2, 1): (128, 2048, 768) -> (128, 768, 2048) 312base_name = name .removesuffix (".weight" ) 313base = base_name .rsplit ('.' ,1 )[0 ] 314mapped_gate = f" { base } .gate_proj.weight" 315mapped_up = f" { base } .up_proj.weight" 316perm_gate = gate .permute (0 ,2 ,1 ).contiguous () 317perm_up = up .permute (0 ,2 ,1 ).contiguous () 318yield from ModelBase .modify_tensors (self ,perm_gate ,mapped_gate ,bid ) 319yield from ModelBase .modify_tensors (self ,perm_up ,mapped_up ,bid ) 320return 321 322yield from super ().modify_tensors (data_torch ,name ,bid ) 323 324 325@ ModelBase . register ( "Qwen3OmniMoeForConditionalGeneration" ) 326@ ModelBase . example ( "Qwen/Qwen3-Omni-30B-A3B-Instruct" ) 327class Qwen3OmniMoeTextModel (Qwen3VLMoeTextModel ): 328model_arch = gguf .MODEL_ARCH .QWEN3VLMOE 329 330def set_vocab (self ): 331super ().set_vocab () 332# correct BOS/EOS tokens 333with open (self .dir_model / "tokenizer_config.json" ,"r" ,encoding = "utf-8" )as f : 334tokenizer_config = json .load (f ) 335added_tokens = tokenizer_config .get ("added_tokens_decoder" , {}) 336for token_id ,data in added_tokens .items (): 337if data .get ("content" )== "<|im_end|>" : 338self .gguf_writer .add_bos_token_id (int (token_id )) 339self .gguf_writer .add_eos_token_id (int (token_id )) 340break 341 342def set_gguf_parameters (self ): 343super ().set_gguf_parameters () 344self .gguf_writer .add_num_deepstack_layers (0 ) 345 346 347@ ModelBase . register ( "Qwen3ASRForConditionalGeneration" ) 348@ ModelBase . example ( "Qwen/Qwen3-ASR-0.6B-hf" ) 349class Qwen3ASRTextModel (Qwen3VLTextModel ): 350model_arch = gguf .MODEL_ARCH .QWEN3VL 351 352def set_gguf_parameters (self ): 353super ().set_gguf_parameters () 354self .gguf_writer .add_num_deepstack_layers (0 ) 355 356def set_vocab (self ): 357super ().set_vocab () 358# fix chat template, use correct chatml format 359self .gguf_writer .add_chat_template ("{% for message in messages %}{{'<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>' + '\\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\\n' }}{% endif %}" ) 360# correct BOS/EOS tokens 361with open (self .dir_model / "tokenizer_config.json" ,"r" ,encoding = "utf-8" )as f : 362tokenizer_config = json .load (f ) 363added_tokens = tokenizer_config .get ("added_tokens_decoder" , {}) 364for token_id ,data in added_tokens .items (): 365if data .get ("content" )== "<|im_end|>" : 366self .gguf_writer .add_bos_token_id (int (token_id )) 367self .gguf_writer .add_eos_token_id (int (token_id )) 368break