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 typing import Callable ,Iterable ,TYPE_CHECKING ,Sequence 7 8import torch 9 10if TYPE_CHECKING : 11from torch import Tensor 12 13from .base import MmprojModel ,ModelBase ,TextModel ,gguf ,logger 14 15 16@ ModelBase . register ( "GemmaForCausalLM" ) 17# [TAG_HF_EXAMPLE_GATED] google/gemma-2b is gated 18@ ModelBase . example ( "trl-internal-testing/tiny-GemmaForCausalLM" ) 19class GemmaModel (TextModel ): 20model_arch = gguf .MODEL_ARCH .GEMMA 21 22def set_vocab (self ): 23self ._set_vocab_sentencepiece () 24 25# TODO: these special tokens should be exported only for the CodeGemma family 26special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = False , 27special_token_types = ['prefix' ,'suffix' ,'middle' ,'fsep' ,'eot' ]) 28special_vocab ._set_special_token ("prefix" ,67 ) 29special_vocab ._set_special_token ("suffix" ,69 ) 30special_vocab ._set_special_token ("middle" ,68 ) 31special_vocab ._set_special_token ("fsep" ,70 ) 32special_vocab ._set_special_token ("eot" ,107 ) 33special_vocab .chat_template = None # do not add it twice 34special_vocab .add_to_gguf (self .gguf_writer ) 35 36self .gguf_writer .add_add_space_prefix (False ) 37 38def set_gguf_parameters (self ): 39hparams = self .hparams 40 41self .gguf_writer .add_context_length (hparams ["max_position_embeddings" ]) 42self .gguf_writer .add_embedding_length (hparams ["hidden_size" ]) 43self .gguf_writer .add_block_count (self .block_count ) 44self .gguf_writer .add_feed_forward_length (hparams ["intermediate_size" ]) 45self .gguf_writer .add_head_count (hparams ["num_attention_heads" ]) 46self .gguf_writer .add_head_count_kv (self .hparams ["num_key_value_heads" ]if "num_key_value_heads" in hparams else hparams ["num_attention_heads" ]) 47self .gguf_writer .add_layer_norm_rms_eps (self .hparams ["rms_norm_eps" ]) 48self .gguf_writer .add_key_length (hparams ["head_dim" ]) 49self .gguf_writer .add_value_length (hparams ["head_dim" ]) 50self .gguf_writer .add_file_type (self .ftype ) 51 52@classmethod 53def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 54name ,gen = item 55 56# lm_head is not used in llama.cpp, while autoawq will include this tensor in model 57# To prevent errors, skip loading lm_head.weight. 58if name == "lm_head.weight" : 59logger .debug (f"Skipping get tensor { name !r } in safetensors so that convert can end normally." ) 60return None 61 62return super ().filter_tensors (item ) 63 64def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 65# ref: https://github.com/huggingface/transformers/blob/fc37f38915372c15992b540dfcbbe00a916d4fc6/src/transformers/models/gemma/modeling_gemma.py#L89 66if name .endswith ("norm.weight" ): 67data_torch = data_torch + 1 68 69yield from super ().modify_tensors (data_torch ,name ,bid ) 70 71 72@ ModelBase . register ( "Gemma2ForCausalLM" ) 73# [TAG_HF_EXAMPLE_GATED] google/gemma-2-9b-it is gated 74@ ModelBase . example ( "trl-internal-testing/tiny-Gemma2ForCausalLM" ) 75class Gemma2Model (TextModel ): 76model_arch = gguf .MODEL_ARCH .GEMMA2 77 78def set_vocab (self ): 79self ._set_vocab_sentencepiece () 80 81self .gguf_writer .add_add_space_prefix (False ) 82 83def set_gguf_parameters (self ): 84hparams = self .hparams 85 86self .gguf_writer .add_context_length (hparams ["max_position_embeddings" ]) 87self .gguf_writer .add_embedding_length (hparams ["hidden_size" ]) 88self .gguf_writer .add_block_count (self .block_count ) 89self .gguf_writer .add_feed_forward_length (hparams ["intermediate_size" ]) 90self .gguf_writer .add_head_count (hparams ["num_attention_heads" ]) 91self .gguf_writer .add_head_count_kv (self .hparams ["num_key_value_heads" ]if "num_key_value_heads" in hparams else hparams ["num_attention_heads" ]) 92self .gguf_writer .add_layer_norm_rms_eps (self .hparams ["rms_norm_eps" ]) 93self .gguf_writer .add_key_length (hparams ["head_dim" ]) 94self .gguf_writer .add_value_length (hparams ["head_dim" ]) 95self .gguf_writer .add_file_type (self .ftype ) 96self .gguf_writer .add_attn_logit_softcapping ( 97self .hparams ["attn_logit_softcapping" ] 98 ) 99self .gguf_writer .add_final_logit_softcapping ( 100self .hparams ["final_logit_softcapping" ] 101 ) 102self .gguf_writer .add_sliding_window (self .hparams ["sliding_window" ]) 103 104@classmethod 105def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 106name ,gen = item 107 108# lm_head is not used in llama.cpp, while autoawq will include this tensor in model 109# To prevent errors, skip loading lm_head.weight. 110if name == "lm_head.weight" : 111logger .debug (f"Skipping get tensor { name !r } in safetensors so that convert can end normally." ) 112return None 113 114return super ().filter_tensors (item ) 115 116def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 117# ref: https://github.com/huggingface/transformers/blob/fc37f38915372c15992b540dfcbbe00a916d4fc6/src/transformers/models/gemma/modeling_gemma.py#L89 118if name .endswith ("norm.weight" ): 119data_torch = data_torch + 1 120 121yield from super ().modify_tensors (data_torch ,name ,bid ) 122 123 124@ ModelBase . register ( "Gemma3ForCausalLM" , "Gemma3ForConditionalGeneration" ) 125# [TAG_HF_EXAMPLE_GATED] google/gemma-3-4b-it is gated 126@ ModelBase . example ( "trl-internal-testing/tiny-Gemma3ForConditionalGeneration" , "hf-tiny-v2/tiny-random-Gemma3ForCausalLM" ) 127class Gemma3Model (TextModel ): 128model_arch = gguf .MODEL_ARCH .GEMMA3 129 130def norm_shift (self ,name :str )-> float : 131return 1.0 if name .endswith ("norm.weight" )else 0.0 # Gemma3RMSNorm adds 1.0 to the norm value 132 133def set_vocab (self ): 134if (self .dir_model / "tokenizer.model" ).is_file (): 135self ._set_vocab_sentencepiece () 136self .gguf_writer .add_add_space_prefix (False ) 137else : 138self ._set_vocab_gpt2 () 139 140def set_gguf_parameters (self ): 141super ().set_gguf_parameters () 142hparams = self .hparams 143 144# some default values are not specified in the hparams 145self .gguf_writer .add_context_length (hparams .get ("max_position_embeddings" ,131072 )) 146self .gguf_writer .add_head_count (hparams .get ("num_attention_heads" ,8 )) 147self .gguf_writer .add_layer_norm_rms_eps (self .hparams .get ("rms_norm_eps" ,1e-6 )) 148self .gguf_writer .add_key_length (hparams .get ("head_dim" ,256 )) 149self .gguf_writer .add_value_length (hparams .get ("head_dim" ,256 )) 150self .gguf_writer .add_rope_freq_base (self .rope_parameters .get ("full_attention" ,self .rope_parameters ).get ("rope_theta" ,1_000_000.0 ))# for global layers 151# attn_logit_softcapping is removed in Gemma3 152assert hparams .get ("attn_logit_softcapping" )is None 153if (final_logit_softcap := hparams .get ("final_logit_softcapping" )): 154self .gguf_writer .add_final_logit_softcapping (final_logit_softcap ) 155if hparams .get ("sliding_window_pattern" )!= 1 : 156self .gguf_writer .add_sliding_window (hparams ["sliding_window" ]) 157self .gguf_writer .add_head_count_kv (hparams .get ("num_key_value_heads" ,4 )) 158 159def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 160# remove OOV (out-of-vocabulary) rows in token_embd 161if "embed_tokens.weight" in name : 162n_vocab_real = - 1 163if (self .dir_model / "tokenizer.model" ).is_file (): 164tokens = self ._create_vocab_sentencepiece ()[0 ] 165n_vocab_real = len (tokens ) 166else : 167with open (self .dir_model / "tokenizer.json" ,"r" ,encoding = "utf-8" )as f : 168tokenizer_json = json .load (f ) 169n_vocab_real = len (tokenizer_json ["model" ]["vocab" ])+ len (tokenizer_json ["added_tokens" ]) 170data_torch = data_torch [:n_vocab_real ] 171 172# ref code in Gemma3RMSNorm 173# output = output * (1.0 + self.weight.float()) 174# note: this is not the case on gemma3n 175f_shift = self .norm_shift (name ) 176if f_shift != 0.0 : 177data_torch = data_torch + f_shift 178 179yield from super ().modify_tensors (data_torch ,name ,bid ) 180 181 182@ ModelBase . register ( "Gemma3TextModel" ) 183# [TAG_HF_EXAMPLE_GATED] google/embeddinggemma-300m is gated 184@ ModelBase . example ( "hf-tiny-v2/tiny-random-Gemma3TextModel" ) 185class EmbeddingGemma (Gemma3Model ): 186model_arch = gguf .MODEL_ARCH .GEMMA_EMBEDDING 187module_paths = [] 188dense_features_dims = {} 189 190def __init__ (self ,* args ,** kwargs ): 191super ().__init__ (* args ,** kwargs ) 192if self .sentence_transformers_dense_modules : 193# read modules.json to determine if model has Dense layers 194modules_file = self .dir_model / "modules.json" 195if modules_file .is_file (): 196with open (modules_file ,encoding = "utf-8" )as modules_json_file : 197mods = json .load (modules_json_file ) 198for mod in mods : 199if mod ["type" ].endswith ("Dense" ): 200mod_path = mod ["path" ] 201# check if model.safetensors file for Dense layer exists 202model_tensors_file = self .dir_model / mod_path / "model.safetensors" 203if model_tensors_file .is_file (): 204self .module_paths .append (mod_path ) 205# read config.json of the Dense layer to get in/out features 206mod_conf_file = self .dir_model / mod_path / "config.json" 207if mod_conf_file .is_file (): 208with open (mod_conf_file ,encoding = "utf-8" )as mod_conf_json_file : 209mod_conf = json .load (mod_conf_json_file ) 210# hparams dense_2_feat_out and dense_3_feat_in are required when loading model's dense weights 211prefix = self ._get_dense_prefix (mod_path ) 212if mod_conf ["in_features" ]is not None and mod_conf ["out_features" ]is not None : 213self .dense_features_dims [prefix ]= (mod_conf ["in_features" ],mod_conf ["out_features" ]) 214 215def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 216from safetensors .torch import load_file 217module_paths = list (self .module_paths ) 218for i ,module_path in enumerate (module_paths ): 219tensors_file = self .dir_model / module_path / "model.safetensors" 220local_tensors = load_file (tensors_file ) 221tensor_name = self ._get_dense_prefix (module_path ) 222for name ,local_tensor in local_tensors .items (): 223if not name .endswith (".weight" ): 224continue 225orig_name = name .replace ("linear" ,tensor_name ) 226name = self .map_tensor_name (orig_name ) 227yield name ,local_tensor .clone () 228 229@staticmethod 230def _get_dense_prefix (module_path )-> str : 231"""Get the tensor name prefix for the Dense layer from module path.""" 232tensor_name = "dense_2" if module_path == "2_Dense" else "dense_3" 233return tensor_name 234 235def set_gguf_parameters (self ): 236super ().set_gguf_parameters () 237 238# Override the sliding window size as it gets adjusted by the Gemma3TextConfig 239# constructor. We want to use the value from the original model's config.json. 240# ref: https://github.com/huggingface/transformers/pull/40700 241with open (self .dir_model / "config.json" ,"r" ,encoding = "utf-8" )as f : 242config = json .load (f ) 243orig_sliding_window = config .get ("sliding_window" ) 244if orig_sliding_window is None : 245raise ValueError ("sliding_window not found in model config - this is required for the model" ) 246 247logger .info (f"Using original sliding_window from config: { orig_sliding_window } " 248f"instead of { self . hparams [ 'sliding_window' ] } " ) 249self .gguf_writer .add_sliding_window (orig_sliding_window ) 250if self .sentence_transformers_dense_modules : 251for dense ,dims in self .dense_features_dims .items (): 252logger .info (f"Setting dense layer { dense } in/out features to { dims } " ) 253self .gguf_writer .add_dense_features_dims (dense ,dims [0 ],dims [1 ]) 254 255self ._try_set_pooling_type () 256 257 258@ ModelBase . register ( "Gemma3ForConditionalGeneration" ) 259# [TAG_HF_EXAMPLE_GATED] google/gemma-3-4b-it is gated 260@ ModelBase . example ( "trl-internal-testing/tiny-Gemma3ForConditionalGeneration" ) 261class Gemma3VisionModel (MmprojModel ): 262def set_gguf_parameters (self ): 263super ().set_gguf_parameters () 264hparams = self .hparams 265self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .GEMMA3 ) 266# default values below are taken from HF transformers code 267self .gguf_writer .add_vision_attention_layernorm_eps (hparams .get ("layer_norm_eps" ,1e-6 )) 268self .gguf_writer .add_vision_use_gelu (True ) 269# calculate proj_scale_factor (used by tinygemma3 test model) 270image_seq_length = self .preprocessor_config .get ("image_seq_length" ,256 ) 271n_per_side = int (image_seq_length ** 0.5 ) 272image_size = self .hparams ["image_size" ] 273patch_size = self .hparams ["patch_size" ] 274proj_scale_factor = (image_size // patch_size )// n_per_side 275if proj_scale_factor > 0 and proj_scale_factor != 4 : 276# we only need to write this if it's not the default value 277# in this case, we are converting a test model 278self .gguf_writer .add_vision_projector_scale_factor (proj_scale_factor ) 279 280def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 281# related to https://github.com/ggml-org/llama.cpp/issues/13025 282if "input_projection" in name : 283return gguf .GGMLQuantizationType .F16 284if ".embeddings." in name : 285return gguf .GGMLQuantizationType .F32 286return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 287 288@classmethod 289def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 290name ,gen = item 291 292if "vision_model.head." in name : 293# skip redundant tensors for tinygemma3 294return None 295 296if not name .startswith (("multi_modal_projector." ,"vision_tower." ,"multimodal_projector." ,"vision_model." )): 297return None 298 299name = name .replace ("_weight" ,".weight" ) 300 301return super ().filter_tensors ((name ,gen )) 302 303def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 304# correct norm value ; only this "soft_emb_norm" need to be corrected as it's part of Gemma projector 305# the other norm values are part of SigLIP model, and they are already correct 306# ref code: Gemma3RMSNorm 307if "soft_emb_norm.weight" in name : 308logger .info (f"Correcting norm value for ' { name } '" ) 309data_torch = data_torch + 1 310 311yield from super ().modify_tensors (data_torch ,name ,bid ) 312 313 314class ConformerAudioModel (MmprojModel ): 315_batch_norm_tensors :list [dict [str ,Tensor ]]| None = None 316 317@staticmethod 318def is_audio_tensor (name :str ): 319return any (p in name for p in ["audio" ,"codebook" ,"conformer" ,"depth_embedding" ,"depthformer" ,"depth_linear" ]) 320 321def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 322if ConformerAudioModel .is_audio_tensor (name ): 323if ".conv" in name or "_conv" in name and ".weight" in name : 324return gguf .GGMLQuantizationType .F32 325return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 326 327def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 328# fold running_mean, running_var and eps into weight and bias for batch_norm 329if "batch_norm" in name : 330if self ._batch_norm_tensors is None : 331self ._batch_norm_tensors = [{}for _ in range (self .block_count )] 332assert bid is not None 333self ._batch_norm_tensors [bid ][name ]= data_torch 334 335if len (self ._batch_norm_tensors [bid ])< 5 : 336return 337 338weight = self ._batch_norm_tensors [bid ][f"conformer.layers. { bid } .conv.batch_norm.weight" ] 339bias = self ._batch_norm_tensors [bid ][f"conformer.layers. { bid } .conv.batch_norm.bias" ] 340running_mean = self ._batch_norm_tensors [bid ][f"conformer.layers. { bid } .conv.batch_norm.running_mean" ] 341running_var = self ._batch_norm_tensors [bid ][f"conformer.layers. { bid } .conv.batch_norm.running_var" ] 342eps = 1e-5 # default value 343 344a = weight / torch .sqrt (running_var + eps ) 345b = bias - running_mean * a 346yield from super ().modify_tensors (a ,f"conformer.layers. { bid } .conv.batch_norm.weight" ,bid ) 347yield from super ().modify_tensors (b ,f"conformer.layers. { bid } .conv.batch_norm.bias" ,bid ) 348return 349 350# reshape conv weights 351if name .startswith ("conformer.pre_encode.conv." )and name .endswith (".bias" ): 352data_torch = data_torch [:,None ,None ] 353if "conv.depthwise_conv" in name and name .endswith (".weight" ): 354assert data_torch .shape [1 ]== 1 355data_torch = data_torch .reshape (data_torch .shape [0 ],data_torch .shape [2 ]) 356if "conv.pointwise_conv" in name and name .endswith (".weight" ): 357assert data_torch .shape [2 ]== 1 358data_torch = data_torch .reshape (data_torch .shape [0 ],data_torch .shape [1 ]) 359 360mapped_name = self .map_tensor_name (name , (".weight" ,".bias" ,".input_max" ,".input_min" ,".output_max" ,".output_min" )) 361yield (mapped_name ,data_torch ) 362 363 364@ ModelBase . register ( "Gemma3nForConditionalGeneration" ) 365# [TAG_HF_EXAMPLE_GATED] google/gemma-3n-E2B-it is gated 366@ ModelBase . example ( "hf-tiny-v2/tiny-random-Gemma3nForConditionalGeneration" ) 367class Gemma3nVisionAudioModel (ConformerAudioModel ): 368has_audio_encoder = True 369has_vision_encoder = True 370 371# Double indexed mapping for MobileNetV5 blocks (not supported by tensor_mapping.py) 372# This is the only known model having this, so we prefer implementing it outside of tensor_mapping.py 373block_tensor_mapping = { 374"model.vision_tower.timm_model.blocks.{bid}.{sid}.conv_exp.weight" :"v.blk.{bid}.{sid}.conv_exp.weight" , 375"model.vision_tower.timm_model.blocks.{bid}.{sid}.bn1.weight" :"v.blk.{bid}.{sid}.bn1.weight" , 376"model.vision_tower.timm_model.blocks.{bid}.{sid}.conv_pwl.weight" :"v.blk.{bid}.{sid}.conv_pwl.weight" , 377"model.vision_tower.timm_model.blocks.{bid}.{sid}.bn2.weight" :"v.blk.{bid}.{sid}.bn2.weight" , 378"model.vision_tower.timm_model.blocks.{bid}.{sid}.dw_start.conv.weight" :"v.blk.{bid}.{sid}.dw_start.conv.weight" , 379"model.vision_tower.timm_model.blocks.{bid}.{sid}.dw_start.bn.weight" :"v.blk.{bid}.{sid}.dw_start.bn.weight" , 380"model.vision_tower.timm_model.blocks.{bid}.{sid}.dw_mid.conv.weight" :"v.blk.{bid}.{sid}.dw_mid.conv.weight" , 381"model.vision_tower.timm_model.blocks.{bid}.{sid}.dw_mid.bn.weight" :"v.blk.{bid}.{sid}.dw_mid.bn.weight" , 382"model.vision_tower.timm_model.blocks.{bid}.{sid}.pw_exp.conv.weight" :"v.blk.{bid}.{sid}.pw_exp.conv.weight" , 383"model.vision_tower.timm_model.blocks.{bid}.{sid}.pw_exp.bn.weight" :"v.blk.{bid}.{sid}.pw_exp.bn.weight" , 384"model.vision_tower.timm_model.blocks.{bid}.{sid}.pw_proj.conv.weight" :"v.blk.{bid}.{sid}.pw_proj.conv.weight" , 385"model.vision_tower.timm_model.blocks.{bid}.{sid}.pw_proj.bn.weight" :"v.blk.{bid}.{sid}.pw_proj.bn.weight" , 386"model.vision_tower.timm_model.blocks.{bid}.{sid}.layer_scale.gamma" :"v.blk.{bid}.{sid}.layer_scale.gamma" , 387"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.query.proj.weight" :"v.blk.{bid}.{sid}.attn.query.proj.weight" , 388"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.key.proj.weight" :"v.blk.{bid}.{sid}.attn.key.proj.weight" , 389"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.value.proj.weight" :"v.blk.{bid}.{sid}.attn.value.proj.weight" , 390"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.output.proj.weight" :"v.blk.{bid}.{sid}.attn.output.proj.weight" , 391"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.key.down_conv.weight" :"v.blk.{bid}.{sid}.attn.key.down_conv.weight" , 392"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.key.norm.weight" :"v.blk.{bid}.{sid}.attn.key.norm.weight" , 393"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.value.down_conv.weight" :"v.blk.{bid}.{sid}.attn.value.down_conv.weight" , 394"model.vision_tower.timm_model.blocks.{bid}.{sid}.attn.value.norm.weight" :"v.blk.{bid}.{sid}.attn.value.norm.weight" , 395"model.vision_tower.timm_model.blocks.{bid}.{sid}.norm.weight" :"v.blk.{bid}.{sid}.norm.weight" , 396 } 397 398def __init__ (self ,* args ,** kwargs ): 399# Parent init will call find_hparam which now returns 0 for empty keys 400super ().__init__ (* args ,** kwargs ) 401assert self .hparams_vision is not None 402self .hparams_vision ["n_layers" ]= 128 # fake value for audio encoder, vision encoder doesn't use it 403self .hparams_vision ["intermediate_size" ]= self .hparams_vision .get ("intermediate_size" ,2048 )* 4 404self .hparams_vision ["num_attention_heads" ]= self .hparams_vision .get ("num_attention_heads" ,8 ) 405 406# MobileNetV5 does not use image_mean/std 407self .preprocessor_config ["image_mean" ]= [0.0 ,0.0 ,0.0 ] 408self .preprocessor_config ["image_std" ]= [1.0 ,1.0 ,1.0 ] 409self .hparams_vision ["image_size" ]= self .preprocessor_config .get ( 410"size" , {"height" :768 ,"width" :768 } 411 )["height" ] 412 413# Image sequence length (256 tokens = 16x16 for Gemma3n) 414image_seq_length = self .preprocessor_config .get ("image_seq_length" ,256 ) 415image_size = self .hparams_vision ["image_size" ] 416self .hparams_vision ["patch_size" ]= image_size // image_seq_length 417 418# remap audio hparams 419assert self .hparams_audio is not None 420self .hparams_audio ["n_layers" ]= self .hparams_audio ["conf_num_hidden_layers" ] 421self .hparams_audio ["num_attention_heads" ]= self .hparams_audio ["conf_num_attention_heads" ] 422self .hparams_audio ["feat_in" ]= self .hparams_audio ["input_feat_size" ] 423self .hparams_audio ["intermediate_size" ]= self .hparams_audio .get ("intermediate_size" ,6144 ) 424 425def set_gguf_parameters (self ): 426super ().set_gguf_parameters () 427 428# vision params 429self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .GEMMA3NV ) 430self .gguf_writer .add_vision_attention_layernorm_eps (self .hparams .get ("layer_norm_eps" ,1e-6 )) 431 432# audio params 433assert self .hparams_audio is not None 434self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .GEMMA3NA ) 435self .gguf_writer .add_audio_num_mel_bins (self .hparams_audio ["feat_in" ]) 436self .gguf_writer .add_audio_attention_layernorm_eps (1e-5 ) 437 438def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 439# Force quantization settings for specific tensor types 440if "input_projection" in name or "input_proj" in name : 441return gguf .GGMLQuantizationType .F16 442if ".embeddings." in name or "stem" in name : 443return gguf .GGMLQuantizationType .F32 444return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 445 446def custom_map (self ,name :str )-> str : 447"""Parses names like model.vision_tower.timm_model.blocks.1.2.suffix and applies template mapping.""" 448parts = name .split ("." ) 449# MobileNet blocks have at least 7 parts: model, vision_tower, timm_model, blocks, bid, sid, and suffix 450if len (parts )>= 7 : 451bid ,sid = parts [4 ],parts [5 ] 452suffix = "." .join (parts [6 :]) 453template = f"model.vision_tower.timm_model.blocks.{{bid}}.{{sid}}. { suffix } " 454if template in self .block_tensor_mapping : 455return self .block_tensor_mapping [template ].format (bid = bid ,sid = sid ) 456 457raise ValueError (f"Unknown name: { name } " ) 458 459def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 460if (ConformerAudioModel .is_audio_tensor (name )): 461name = name .replace ("model.audio_tower.conformer." ,"conformer.layers." ) 462yield from super ().modify_tensors (data_torch ,name ,bid ) 463 464# Gemma3n uses 465# - model.embed_vision.* for projection layers 466# - model.vision_tower.* for vision encoder 467# Skip non-vision tensors 468if not (name .startswith ("model.embed_vision." )or name .startswith ("model.vision_tower." )): 469return 470 471if name .startswith ("model.vision_tower.timm_model.blocks." ): 472# Double-indexed block tensors through custom logic 473yield (self .custom_map (name ),data_torch ) 474return 475else : 476# Route non-repeating (conv_stem, msfa, embedding, etc.) and un-catched through tensor_mapping.py 477new_name = self .map_tensor_name (name ) 478 479if new_name .endswith ("conv_stem.conv.bias" )or new_name .endswith ("layer_scale.gamma" ): 480data_torch = data_torch .unsqueeze (0 ).unsqueeze (- 1 ).unsqueeze (- 1 )# [1, C, 1, 1] 481 482yield from ModelBase .modify_tensors (self ,data_torch ,new_name ,bid ) 483 484 485@ ModelBase . register ( "Gemma3nForCausalLM" , "Gemma3nForConditionalGeneration" ) 486# [TAG_HF_EXAMPLE_GATED] google/gemma-3n-E2B-it is gated 487@ ModelBase . example ( "hf-tiny-v2/tiny-random-Gemma3nForConditionalGeneration" ) 488class Gemma3NModel (Gemma3Model ): 489model_arch = gguf .MODEL_ARCH .GEMMA3N 490 491_altup_proj :list [Tensor ]= [] 492_altup_unembd :list [Tensor ]= [] 493 494def __init__ (self ,* args ,** kwargs ): 495super ().__init__ (* args ,** kwargs ) 496assert self .hparams ["altup_num_inputs" ]== 4 ,"Current conversion only supports 4 altup inputs" 497self ._altup_proj = [ 498torch .Tensor (),# to be replaced 499torch .Tensor (),# to be replaced 500torch .Tensor (),# to be replaced 501 ] 502self ._altup_unembd = [ 503torch .Tensor (),# to be replaced 504torch .Tensor (),# to be replaced 505torch .Tensor (),# to be replaced 506 ] 507 508def norm_shift (self ,name :str )-> float : 509del name 510return 0.0 # same value with Gemma3p5RMSNorm scale_shift on python code 511 512def set_vocab (self ): 513# For Gemma3n multimodal models, we need the FULL vocab_size (262400) 514# which includes special tokens from 262144-262399 for vision/audio. 515# The vocab_size_per_layer_input (262144) is only the embedding size per layer. 516# Temporarily override the hparams lookup order to prioritize vocab_size. 517 518# Store original vocab_size_per_layer_input if it exists 519vocab_size_per_layer_input = self .hparams .get ("vocab_size_per_layer_input" ) 520 521# Temporarily remove vocab_size_per_layer_input to force using vocab_size 522if vocab_size_per_layer_input is not None : 523del self .hparams ["vocab_size_per_layer_input" ] 524 525# Call parent set_vocab which will now use vocab_size (262400) 526super ().set_vocab () 527 528# Restore vocab_size_per_layer_input for later use 529if vocab_size_per_layer_input is not None : 530self .hparams ["vocab_size_per_layer_input" ]= vocab_size_per_layer_input 531 532def set_gguf_parameters (self ): 533super ().set_gguf_parameters () 534self .gguf_writer .add_altup_active_idx (self .hparams ["altup_active_idx" ]) 535self .gguf_writer .add_altup_num_inputs (self .hparams ["altup_num_inputs" ]) 536self .gguf_writer .add_embedding_length_per_layer_input (self .hparams ["hidden_size_per_layer_input" ]) 537self .gguf_writer .add_shared_kv_layers (self .hparams ["num_kv_shared_layers" ]) 538 539activation_sparsity_scale = [] 540for s in self .hparams ["activation_sparsity_pattern" ]: 541normal_dist = torch .distributions .normal .Normal (0 ,1 ) 542std_multiplier = normal_dist .icdf (torch .tensor (s ,dtype = torch .float32 )) 543activation_sparsity_scale .append (std_multiplier .item ()) 544self .gguf_writer .add_activation_sparsity_scale (activation_sparsity_scale ) 545 546sliding_window_pattern = [] 547for t in self .hparams ["layer_types" ]: 548sliding_window_pattern .append (t == "sliding_attention" ) 549self .gguf_writer .add_sliding_window_pattern (sliding_window_pattern ) 550 551def _stack_matrices (self ,matrices :list [Tensor ])-> Tensor | None : 552has_all = all (m .numel ()> 0 for m in matrices ) 553if not has_all : 554return None 555else : 556return torch .stack (matrices ,dim = 0 ) 557 558@classmethod 559def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 560name ,gen = item 561 562if name .endswith ("_scale" ): 563name = name + ".weight" 564 565return super ().filter_tensors ((name ,gen )) 566 567def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 568# TODO: implement self.prediction_coefs.weight.clamp_(...) 569 570# Pad token embeddings for vision/audio special tokens (262144-262399) 571if "embed_tokens.weight" in name or "embed_tokens_per_layer" in name : 572# Move to CPU to avoid meta device issues during padding 573data_torch = data_torch .to (device = "cpu" ) 574 575vocab_size = self .hparams .get ("vocab_size" ,262400 ) 576current_size = data_torch .shape [0 ]# First dimension is vocab_size 577 578if current_size < vocab_size : 579# Pad with zeros for vision/audio tokens (they get embeddings from vision tower) 580padding_size = vocab_size - current_size 581tensor_type = "per-layer embeddings" if "per_layer" in name else "token embeddings" 582logger .info (f"Padding { tensor_type } shape { list ( data_torch . shape ) } from { current_size } to { vocab_size } (adding { padding_size } vision/audio token slots)" ) 583 584# Create padding with zeros (vision tokens won't use these embeddings) 585padding = torch .zeros ((padding_size ,data_torch .shape [1 ]),dtype = data_torch .dtype ,device = data_torch .device ) 586data_torch = torch .cat ([data_torch ,padding ],dim = 0 ) 587 588# Continue with normal processing 589yield from ModelBase .modify_tensors (self ,data_torch ,name ,bid ) 590return 591 592if "altup_unembed_projections" in name : 593data_torch = data_torch .to (device = "cpu" ) 594# altup_unembed matrices are [hidden_size, hidden_size], NOT vocab-based 595# They should NOT be padded 596if ".0." in name : 597self ._altup_unembd [0 ]= data_torch 598elif ".1." in name : 599self ._altup_unembd [1 ]= data_torch 600elif ".2." in name : 601self ._altup_unembd [2 ]= data_torch 602else : 603raise ValueError (f"Unknown name: { name } " ) 604out = self ._stack_matrices (self ._altup_unembd ) 605if out is not None : 606yield from ModelBase .modify_tensors (self ,out ,"model.altup_unembed_projections.weight" ,bid ) 607return 608else : 609return 610 611if "altup_projections" in name : 612data_torch = data_torch .to (device = "cpu" ) 613if ".0." in name : 614self ._altup_proj [0 ]= data_torch 615elif ".1." in name : 616self ._altup_proj [1 ]= data_torch 617elif ".2." in name : 618self ._altup_proj [2 ]= data_torch 619else : 620raise ValueError (f"Unknown name: { name } " ) 621out = self ._stack_matrices (self ._altup_proj ) 622if out is not None : 623yield from ModelBase .modify_tensors (self ,out ,"model.altup_projections.weight" ,bid ) 624return 625else : 626return 627 628yield from super ().modify_tensors (data_torch ,name ,bid ) 629 630 631@ ModelBase . register ( "Gemma4ForConditionalGeneration" , "Gemma4ForCausalLM" ) 632@ ModelBase . example ( "google/gemma-4-31B-it" , "google/gemma-4-26B-A4B-it" , "google/gemma-4-E2B-it" ) 633class Gemma4Model (Gemma3Model ): 634model_arch = gguf .MODEL_ARCH .GEMMA4 635 636def norm_shift (self ,name :str )-> float : 637del name # unused 638return 0.0 639 640def set_vocab (self ): 641vocab = gguf .LlamaHfVocab (self .dir_model ) 642tokens = [] 643scores = [] 644toktypes = [] 645visible_tokens = {"<|channel>" ,"<channel|>" ,"<|tool_call>" ,"<tool_call|>" ,"<|tool_response>" ,"<tool_response|>" ,"<|\"|>" } 646 647for text ,score ,toktype in vocab .all_tokens (): 648tokens .append (text ) 649scores .append (score ) 650text_str = text .decode () 651if text_str in visible_tokens : 652# always render these tokens, so that the chat parser can read them 653toktypes .append (gguf .TokenType .USER_DEFINED ) 654logger .info (f"Token ' { text_str } ' is set to USER_DEFINED" ) 655else : 656toktypes .append (toktype ) 657 658assert len (tokens )== vocab .vocab_size 659 660self .gguf_writer .add_tokenizer_model ("gemma4" ) 661self .gguf_writer .add_token_list (tokens ) 662self .gguf_writer .add_token_scores (scores ) 663self .gguf_writer .add_token_types (toktypes ) 664 665special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 666special_vocab .add_to_gguf (self .gguf_writer ) 667self .gguf_writer .add_add_space_prefix (False ) 668self .gguf_writer .add_add_bos_token (True ) 669 670def set_gguf_parameters (self ): 671super ().set_gguf_parameters () 672 673num_kv_shared_layers = self .hparams ["num_kv_shared_layers" ] 674self .gguf_writer .add_shared_kv_layers (num_kv_shared_layers ) 675 676# per-layer embedding is optional 677n_pl_embd = self .hparams .get ("hidden_size_per_layer_input" )or 0 678self .gguf_writer .add_embedding_length_per_layer_input (n_pl_embd ) 679 680swa_layers = [t == "sliding_attention" for t in self .hparams ["layer_types" ]] 681self .gguf_writer .add_sliding_window_pattern (swa_layers ) 682 683per_layer_config = self .hparams .get ("per_layer_config" ) 684layer_types = self .hparams .get ("layer_types" , []) 685if (head_dim_full := self .hparams .get ("global_head_dim" ))is None and per_layer_config is not None : 686for layer_idx ,layer_config in per_layer_config .items (): 687layer_idx = int (layer_idx ) 688if layer_idx < len (layer_types ): 689if layer_types [layer_idx ]== "full_attention" and "head_dim" in layer_config : 690head_dim_full = layer_config ["head_dim" ] 691break 692 693assert head_dim_full is not None 694 695head_dim_swa = self .hparams ["head_dim" ] 696# correct the head dim for global/swa layers 697self .gguf_writer .add_key_length (head_dim_full ) 698self .gguf_writer .add_value_length (head_dim_full ) 699self .gguf_writer .add_key_length_swa (head_dim_swa ) 700self .gguf_writer .add_value_length_swa (head_dim_swa ) 701 702expert_intermediate_size = self .find_hparam (["expert_intermediate_size" ,"moe_intermediate_size" ]) 703if expert_intermediate_size is not None : 704self .gguf_writer .add_expert_feed_forward_length (expert_intermediate_size ) 705 706# if use_double_wide_mlp is set, we need to adjust the value for kv shared layers 707use_double_wide_mlp = self .hparams .get ("use_double_wide_mlp" ,False ) 708first_kv_shared_layer_idx = self .block_count - num_kv_shared_layers 709if use_double_wide_mlp : 710n_ff = self .hparams ["intermediate_size" ] 711n_ff_arr = [n_ff if il < first_kv_shared_layer_idx else n_ff * 2 for il in range (self .block_count )] 712self .gguf_writer .add_feed_forward_length (n_ff_arr ) 713 714if (num_key_value_heads_full := self .hparams .get ("num_global_key_value_heads" ))is None and per_layer_config is not None : 715for layer_idx ,layer_config in per_layer_config .items (): 716layer_idx = int (layer_idx ) 717if layer_idx < len (layer_types ): 718if layer_types [layer_idx ]== "full_attention" and "num_key_value_heads" in layer_config : 719num_key_value_heads_full = layer_config ["num_key_value_heads" ] 720break 721 722num_key_value_heads_swa = self .hparams .get ("num_key_value_heads" ) 723if num_key_value_heads_full is not None and num_key_value_heads_swa is not None : 724value_arr = [num_key_value_heads_swa if is_swa else num_key_value_heads_full for is_swa in swa_layers ] 725self .gguf_writer .add_head_count_kv (value_arr ) 726 727# handle n_rot differently for global vs swa layers 728partial_rotary_factor_swa = self .rope_parameters .get ("partial_rotary_factor" ,1.0 ) 729n_rot_full = int (head_dim_full )# "proportional" is used, see generate_extra_tensors 730n_rot_swa = int (head_dim_swa * partial_rotary_factor_swa ) 731self .gguf_writer .add_rope_dimension_count (n_rot_full ) 732self .gguf_writer .add_rope_dimension_count_swa (n_rot_swa ) 733 734def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 735# full layer uses "proportional" rope with partial_rotary_factor=0.25 736# the expected ordering is cc000000ss000000 (c = cos, s = sin, 0 = unrotated), 737# but ggml neox only supports ccss000000000000, and we cannot rearrange the head because that will break use_alternative_attention 738# solution is to set specific freq_factors for the unrotated dims 739 740# IMPORTANT: this ROPE_FREQS tensor is ONLY used by the full_attention layers 741rope_params_full = self .hparams ["rope_parameters" ]["full_attention" ] 742assert rope_params_full ["rope_type" ]== "proportional" 743 744per_layer_config = self .hparams .get ("per_layer_config" ) 745if (head_dim_full := self .hparams .get ("global_head_dim" ))is None and per_layer_config is not None : 746layer_types = self .hparams .get ("layer_types" , []) 747for layer_idx ,layer_config in per_layer_config .items (): 748layer_idx = int (layer_idx ) 749if layer_idx < len (layer_types ): 750if layer_types [layer_idx ]== "full_attention" and "head_dim" in layer_config : 751head_dim_full = layer_config ["head_dim" ] 752break 753 754assert head_dim_full is not None 755 756partial_rotary_factor_full = rope_params_full ["partial_rotary_factor" ] 757n_rot_full = int (head_dim_full * partial_rotary_factor_full / 2 ) 758n_unrot_full = int (head_dim_full / 2 )- n_rot_full 759values = [1.0 ]* n_rot_full + [1e30 ]* n_unrot_full 760rope_freqs_full = torch .tensor (values ,dtype = torch .float32 ) 761yield (self .format_tensor_name (gguf .MODEL_TENSOR .ROPE_FREQS ),rope_freqs_full ) 762 763def _generate_nvfp4_tensors (self ): 764# Gemma-4 stores a per-layer router.per_expert_scale ([n_expert]) that scales 765# each expert's contribution. It's mathematically equivalent to a per-expert 766# scalar on the down_proj output, which is exactly where ffn_down_exps_s is 767# applied at inference. Fold it into each expert's NVFP4 weight_scale_2 so the 768# existing NVFP4 path produces the right scales. 769n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ],optional = True )or 0 770for name in [n for n in self .model_tensors if n .endswith (".router.per_expert_scale" )]: 771bid_match = re .search (r"\.layers\.(\d+)\." ,name ) 772if bid_match is None : 773continue 774bid = bid_match .group (1 ) 775prefix = name [:name .index (f".layers. { bid } ." )+ len (f".layers. { bid } ." )] 776w2_targets = [f" { prefix } experts. { e } .down_proj.weight_scale_2" for e in range (n_experts )] 777present = [w2 in self .model_tensors for w2 in w2_targets ] 778if not any (present ): 779continue 780assert all (present ),f"layer { bid } : partial NVFP4 quantization across experts" 781r = self .model_tensors .pop (name ) 782for e ,w2 in enumerate (w2_targets ): 783s = self .model_tensors [w2 ] 784self .model_tensors [w2 ]= lambda s = s ,r = r ,i = e :s ()* r ()[i ] 785super ()._generate_nvfp4_tensors () 786 787@classmethod 788def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 789name ,gen = item 790 791if name .endswith ("per_dim_scale" )or name .endswith ("layer_scalar" ): 792name = name + ".weight" 793if ".experts." in name and not name .endswith ((".weight" ,".weight_scale" ,".weight_scale_2" ,".input_scale" )): 794name += ".weight" 795 796return super ().filter_tensors ((name ,gen )) 797 798def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 799if name .endswith ("router.scale" ): 800name = self .format_tensor_name (gguf .MODEL_TENSOR .FFN_GATE_INP ,bid ,".scale" ) 801yield (name ,data_torch ) 802return 803if ".per_expert_scale" in name : 804# convert per-expert scale to FFN down scale 805name = self .format_tensor_name (gguf .MODEL_TENSOR .FFN_DOWN_EXP ,bid ,".scale" ) 806yield (name ,data_torch ) 807return 808 809yield from super ().modify_tensors (data_torch ,name ,bid ) 810 811 812@ ModelBase . register ( "Gemma4UnifiedForConditionalGeneration" ) 813@ ModelBase . example ( "hf-tiny-v2/tiny-random-Gemma4UnifiedForConditionalGeneration" ) 814class Gemma4UnifiedModel (Gemma4Model ): 815model_arch = gguf .MODEL_ARCH .GEMMA4 816 817def _get_suppress_tokens (self )-> Sequence [int ]| None : 818gen_cfg_path = self .dir_model / "generation_config.json" 819if gen_cfg_path .is_file (): 820with open (gen_cfg_path ,encoding = "utf-8" )as f : 821gen_cfg = json .load (f ) 822return gen_cfg .get ("suppress_tokens" ) 823return None 824 825def set_gguf_parameters (self ): 826super ().set_gguf_parameters () 827 828suppress_tokens = self ._get_suppress_tokens () 829if suppress_tokens is not None : 830self .gguf_writer .add_suppress_tokens (suppress_tokens ) 831 832 833@ ModelBase . register ( "Gemma4AssistantForCausalLM" , "Gemma4UnifiedAssistantForCausalLM" ) 834@ ModelBase . example ( "google/gemma-4-31B-it-assistant" , "google/gemma-4-26B-A4B-it-assistant" , "google/gemma-4-E2B-it-assistant" ) 835class Gemma4AssistantModel (Gemma4Model ): 836model_arch = gguf .MODEL_ARCH .GEMMA4_ASSISTANT 837 838@classmethod 839def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 840name ,gen = item 841 842if "masked_embedding" in name : 843logger .debug (f"Skipping get tensor { name !r } in safetensors so that convert can end normally." ) 844return None 845 846return super ().filter_tensors (item ) 847 848def set_gguf_parameters (self ): 849super ().set_gguf_parameters () 850self .gguf_writer .add_embedding_length_out (self .hparams ["backbone_hidden_size" ]) 851self .gguf_writer .add_nextn_predict_layers (self .block_count ) 852 853 854@ ModelBase . register ( "Gemma4ForConditionalGeneration" ) 855@ ModelBase . example ( "google/gemma-4-31B-it" , "google/gemma-4-26B-A4B-it" , "google/gemma-4-E2B-it" ) 856class Gemma4VisionAudioModel (MmprojModel ): 857has_audio_encoder = True 858has_vision_encoder = True 859 860def __init__ (self ,* args ,** kwargs ): 861super ().__init__ (* args ,** kwargs ) 862assert self .hparams_vision is not None 863self .hparams_vision ["image_size" ]= 224 # unused, but set to avoid error 864 865# remap audio hparams 866if self .hparams_audio : 867self .hparams_audio ["feat_in" ]= self .hparams_audio .get ("input_feat_size" ,128 ) 868if "hidden_size" in self .hparams_audio : 869self .hparams_audio ["intermediate_size" ]= self .hparams_audio ["hidden_size" ]* 4 870else : 871self .has_audio_encoder = False 872 873def set_gguf_parameters (self ): 874super ().set_gguf_parameters () 875 876# vision params 877assert self .hparams_vision is not None 878self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .GEMMA4V ) 879self .gguf_writer .add_vision_attention_layernorm_eps (self .hparams_vision .get ("layer_norm_eps" ,1e-6 )) 880 881# audio params 882if self .has_audio_encoder : 883assert self .hparams_audio is not None 884self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .GEMMA4A ) 885self .gguf_writer .add_audio_num_mel_bins (self .hparams_audio ["feat_in" ]) 886self .gguf_writer .add_audio_attention_layernorm_eps (self .hparams_audio .get ("layer_norm_eps" ,1e-6 )) 887 888def is_audio_tensor (self ,name :str )-> bool : 889return "audio_tower" in name or "embed_audio" in name 890 891def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 892if self .is_audio_tensor (name ): 893if ".conv" in name or "_conv" in name and ".weight" in name : 894return gguf .GGMLQuantizationType .F32 895if "position_embedding_table" in name : 896return gguf .GGMLQuantizationType .F32 897return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 898 899def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 900del bid # unused 901 902if len (data_torch .shape )== 0 : 903# convert scalar tensors (input/output_mix/max) to 1D tensors 904data_torch = data_torch .unsqueeze (0 ) 905 906if self .is_audio_tensor (name ): 907assert self .hparams_audio is not None 908name = name .replace ("model.audio_tower." ,"conformer." ) 909name = name .replace (".linear." ,"." ) 910if name .endswith ("per_dim_key_scale" )or name .endswith ("per_dim_scale" ): 911name = name + ".weight" 912data_torch = torch .nn .functional .softplus (data_torch ) 913if "lconv1d.depthwise_conv1d" in name and name .endswith (".weight" ): 914assert data_torch .shape [1 ]== 1 915data_torch = data_torch .reshape (data_torch .shape [0 ],data_torch .shape [2 ]) 916mapped_name = self .map_tensor_name (name , (".weight" ,".bias" ,".input_max" ,".input_min" ,".output_max" ,".output_min" )) 917yield (mapped_name ,data_torch ) 918 919else : 920name = name .replace ("model.vision_tower.encoder." ,"vision_model.model." ) 921name = name .replace (".linear.weight" ,".weight" ) 922if name .endswith ("layer_scalar" )or name .endswith ("position_embedding_table" ): 923name = name + ".weight" 924if name .endswith ("patch_embedder.input_proj.weight" ): 925n_embd ,ksize_sq_c = data_torch .shape 926patch_size = int ((ksize_sq_c // 3 )** 0.5 ) 927data_torch = data_torch .reshape (n_embd ,patch_size ,patch_size ,3 ) 928data_torch = data_torch .permute (0 ,3 ,1 ,2 ).contiguous () 929mapped_name = self .map_tensor_name (name , (".weight" ,".bias" ,".input_max" ,".input_min" ,".output_max" ,".output_min" )) 930yield (mapped_name ,data_torch ) 931 932 933@ ModelBase . register ( "Gemma4UnifiedForConditionalGeneration" ) 934@ ModelBase . example ( "hf-tiny-v2/tiny-random-Gemma4UnifiedForConditionalGeneration" ) 935class Gemma4UnifiedVisionAudioModel (Gemma4VisionAudioModel ): 936has_audio_encoder = True 937has_vision_encoder = True 938 939def __init__ (self ,* args ,** kwargs ): 940super ().__init__ (* args ,** kwargs ) 941assert self .hparams_vision is not None 942assert self .hparams_audio is not None 943text_embd_dim = self .hparams_vision ["mm_embed_dim" ] 944self .hparams_vision ["hidden_size" ]= text_embd_dim 945self .hparams_audio ["hidden_size" ]= self .hparams_audio ["audio_embed_dim" ] 946# this is a transformer-less vision tower, the params below are redundant but set to avoid error 947self .hparams_vision ["intermediate_size" ]= 0 948self .hparams_vision ["num_layers" ]= 0 949self .hparams_vision ["num_attention_heads" ]= 0 950self .hparams_audio ["intermediate_size" ]= 0 951self .hparams_audio ["num_layers" ]= 0 952self .hparams_audio ["num_attention_heads" ]= 0 953 954def set_gguf_parameters (self ): 955super ().set_gguf_parameters () 956self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .GEMMA4UV ) 957self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .GEMMA4UA ) 958 959def modify_tensors (self ,data_torch ,name ,bid ): 960if name .endswith ("pos_embedding" ): 961name += ".weight" 962data_torch = data_torch .permute (1 ,0 ,2 ) 963elif ".pos_norm." in name : 964# rename to patch_ln3 to reuse the tensor name scheme 965name = name .replace (".pos_norm." ,".patch_ln3." ) 966elif "patch_dense.weight" in name : 967# ggml im2col outputs in RR..GG..BB.. (CHW) order, but weight expects RGBRGB.. (HWC). 968# Permute columns so column i aligns with CHW input position i. 969assert self .hparams_vision is not None 970if "model_patch_size" in self .hparams_vision : 971p = self .hparams_vision ["model_patch_size" ] 972else : 973p = self .hparams_vision ["patch_size" ]* self .hparams_vision ["pooling_kernel_size" ] 974i = torch .arange (p * p * 3 ) 975ch = i // (p * p ) 976row = (i % (p * p ))// p 977col = i % p 978# perm[i] = HWC column index for CHW position i 979perm = row * p * 3 + col * 3 + ch 980data_torch = data_torch [:,perm ] 981elif "patch_ln1.weight" in name or "patch_ln1.bias" in name : 982# same permutation for patch_ln1 as patch_dense to align with CHW input order 983assert self .hparams_vision is not None 984if "model_patch_size" in self .hparams_vision : 985p = self .hparams_vision ["model_patch_size" ] 986else : 987p = self .hparams_vision ["patch_size" ]* self .hparams_vision ["pooling_kernel_size" ] 988i = torch .arange (p * p * 3 ) 989ch = i // (p * p ) 990row = (i % (p * p ))// p 991col = i % p 992# perm[i] = HWC index for CHW position i 993perm = row * p * 3 + col * 3 + ch 994data_torch = data_torch [perm ] 995return super ().modify_tensors (data_torch ,name ,bid )