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 Any ,Callable ,Iterable ,TYPE_CHECKING 7 8import torch 9 10if TYPE_CHECKING : 11from torch import Tensor 12 13from .base import MmprojModel ,ModelBase ,TextModel ,gguf 14 15 16@ ModelBase . register ( "MiMoV2FlashForCausalLM" , "MiMoV2ForCausalLM" ) 17@ ModelBase . example ( "XiaomiMiMo/MiMo-V2.5" ) 18class MimoV2Model (TextModel ): 19model_arch = gguf .MODEL_ARCH .MIMO2 20 21# MiMo V2-Flash, V2.5 and V2.5-Pro all ship 3 trained MTP layers under model.mtp.layers.{0,1,2}. 22# The HF config does not expose the count, so it's hardcoded to match the count found in the safetensors. 23_n_nextn = 3 24 25def __init__ (self ,* args ,** kwargs ): 26super ().__init__ (* args ,** kwargs ) 27 28self .block_count = self .hparams ["num_hidden_layers" ]+ self ._n_nextn 29self .tensor_map = gguf .get_tensor_name_map (self .model_arch ,self .block_count ) 30 31@staticmethod 32def _tp_aware_qkv_dequant (weight :Tensor ,scale_inv :Tensor , 33n_q :int ,n_kv :int ,hd :int ,vhd :int , 34bs :int = 128 )-> Tensor : 35# MiMo-V2.5 (TP=4) and V2.5-Pro (TP=8) ship qkv_proj sharded across TP 36# ranks; per rank, rows are stacked as [Q_per | K_per | V_per]. 37# weight_scale_inv has ceil(rows_per_rank/bs) block-rows per rank (last 38# may extend past rows_per_rank with phantom rows not in the weight). 39# Naive repeat_interleave aligns rank 0 only and mis-applies scales to 40# later ranks once rows_per_rank isn't a multiple of bs. 41# Re-group the per-rank [Q_per|K_per|V_per] rows into a single fused 42# [Q | K | V] tensor matching the un-sharded original layout. 43q_size = n_q * hd 44k_size = n_kv * hd 45v_size = n_kv * vhd 46total_rows = q_size + k_size + v_size 47if weight .shape [0 ]!= total_rows : 48raise ValueError (f"qkv_proj weight rows { weight . shape [ 0 ] } != q+k+v { total_rows } " ) 49 50# detect TP from scale_inv block count, descending order so larger matches first 51tp = None 52for cand in (8 ,4 ): 53if total_rows % cand != 0 : 54continue 55rpr = total_rows // cand 56bpr = (rpr + bs - 1 )// bs 57if scale_inv .shape [0 ]== cand * bpr : 58tp = cand 59break 60if tp is None : 61raise ValueError ( 62f"qkv_proj: cannot detect TP - scale_inv rows { scale_inv . shape [ 0 ] } , " 63f"q+k+v { total_rows } " ) 64 65q_per = q_size // tp 66k_per = k_size // tp 67v_per = v_size // tp 68rows_per_rank = q_per + k_per + v_per 69blocks_per_rank = (rows_per_rank + bs - 1 )// bs 70 71scale_inv = scale_inv .float () 72# per-row scale-row index: rank * blocks_per_rank + (rr_in_rank // bs) 73row_idx = torch .arange (total_rows ) 74rr = row_idx % rows_per_rank 75rank = row_idx // rows_per_rank 76scale_row_idx = rank * blocks_per_rank + (rr // bs ) 77# gather: (total_rows, n_col_blocks) 78scale_per_row_block = scale_inv [scale_row_idx ] 79# expand col-blocks -> cols: each block-col covers `bs` weight cols 80scale_full = scale_per_row_block .repeat_interleave (bs ,dim = 1 ) 81# crop to weight col count (in case last col-block isn't full) 82scale_full = scale_full [:, :weight .shape [1 ]] 83dequant = weight .float ()* scale_full 84 85if tp == 1 : 86return dequant 87 88# Re-group per-rank [Q_per|K_per|V_per] rows into unified [Q | K | V] 89qs ,ks ,vs = [], [], [] 90for r in range (tp ): 91base = r * rows_per_rank 92qs .append (dequant [base :base + q_per ]) 93ks .append (dequant [base + q_per :base + q_per + k_per ]) 94vs .append (dequant [base + q_per + k_per :base + rows_per_rank ]) 95return torch .cat (qs + ks + vs ,dim = 0 ) 96 97def dequant_model (self ): 98# Capture raw FP8 (weight, scale_inv) lambdas for qkv_proj BEFORE super 99# rewrites them with the existing dequant. Replace super's lambda after 100# it runs so scale_inv removal still happens via the standard path. 101qkv_overrides :dict [str ,tuple [Callable ,Callable ,int ]]= {} 102qc = self .hparams .get ("quantization_config" ) 103if isinstance (qc ,dict )and qc .get ("quant_method" )== "fp8" : 104pat = re .compile (r"^model\.layers\.(\d+)\.self_attn\.qkv_proj\.weight_scale_inv$" ) 105for name in list (self .model_tensors .keys ()): 106m = pat .match (name ) 107if not m : 108continue 109weight_name = name .removesuffix ("_scale_inv" ) 110if weight_name not in self .model_tensors : 111continue 112qkv_overrides [weight_name ]= ( 113self .model_tensors [weight_name ], 114self .model_tensors [name ], 115int (m .group (1 )), 116 ) 117 118super ().dequant_model () 119 120if not qkv_overrides : 121return 122 123n_q = self .hparams ["num_attention_heads" ] 124hd = self .hparams ["head_dim" ] 125vhd = self .hparams ["v_head_dim" ] 126hybrid = self .hparams ["hybrid_layer_pattern" ] 127n_layer_text = self .hparams ["num_hidden_layers" ] 128for weight_name , (w_fn ,s_fn ,bid )in qkv_overrides .items (): 129# MTP layers (bid >= n_layer_text) use SWA-style attention dims 130is_swa = True if bid >= n_layer_text else hybrid [bid ]== 1 131n_kv = self .hparams ["swa_num_key_value_heads" if is_swa else "num_key_value_heads" ] 132self .model_tensors [weight_name ]= ( 133lambda w_fn = w_fn ,s_fn = s_fn ,n_q = n_q ,n_kv = n_kv ,hd = hd ,vhd = vhd : 134MimoV2Model ._tp_aware_qkv_dequant (w_fn (),s_fn (),n_q ,n_kv ,hd ,vhd ) 135 ) 136 137def set_gguf_parameters (self ): 138super ().set_gguf_parameters () 139 140assert self .hparams ["swa_head_dim" ]== self .hparams ["head_dim" ] 141assert self .hparams ["swa_num_attention_heads" ]== self .hparams ["num_attention_heads" ] 142assert self .hparams ["swa_v_head_dim" ]== self .hparams ["v_head_dim" ] 143assert self .hparams ["topk_method" ]== "noaux_tc" 144 145n_head_kv = self .hparams ["num_key_value_heads" ] 146n_head_kv_swa = self .hparams ["swa_num_key_value_heads" ] 147# Extend the per-layer pattern with SWA entries for the MTP blocks so the 148# runtime arrays (sized to extended block_count) are fully populated. 149hybrid = list (self .hparams ["hybrid_layer_pattern" ])+ [1 ]* self ._n_nextn 150n_head_kv_arr = [n_head_kv_swa if use_swa == 1 else n_head_kv for use_swa in hybrid ] 151self .gguf_writer .add_head_count_kv (n_head_kv_arr ) 152 153self .gguf_writer .add_sliding_window (self .hparams ["sliding_window" ]) 154self .gguf_writer .add_sliding_window_pattern (hybrid ) 155self .gguf_writer .add_value_length (self .hparams ["v_head_dim" ]) 156self .gguf_writer .add_expert_count (self .hparams ["n_routed_experts" ]) 157self .gguf_writer .add_expert_feed_forward_length (self .hparams ["moe_intermediate_size" ]) 158 159rope_dim = int (self .hparams ["head_dim" ]* self .rope_parameters ["partial_rotary_factor" ]) 160self .gguf_writer .add_rope_dimension_count (rope_dim ) 161 162self .gguf_writer .add_layer_norm_rms_eps (self .hparams .get ("layernorm_epsilon" ,1e-5 )) 163 164v_scale = self .hparams .get ("attention_value_scale" ) 165if v_scale is not None : 166self .gguf_writer .add_attn_value_scale (float (v_scale )) 167 168self .gguf_writer .add_nextn_predict_layers (self ._n_nextn ) 169 170_experts :list [dict [str ,Tensor ]]| None = None 171 172@classmethod 173def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 174name ,gen = item 175 176if "attention_sink" in name and not name .endswith (".weight" ): 177name += ".weight" 178 179return super ().filter_tensors ((name ,gen )) 180 181def modify_tensors (self ,data_torch ,name ,bid ): 182# Remap MTP/NextN tensors to additional layer slots so the standard tensor map handles them. 183# HF: model.mtp.layers.{i}.foo -> model.layers.{n_layer_text + i}.foo 184m = re .match (r"^model\.mtp\.layers\.(\d+)\.(.*)$" ,name ) 185if m is not None : 186mtp_idx = int (m .group (1 )) 187assert mtp_idx < self ._n_nextn ,f"MTP layer index { mtp_idx } >= _n_nextn ( { self . _n_nextn } )" 188rest = m .group (2 ) 189n_layer_text = self .hparams ["num_hidden_layers" ] 190new_bid = n_layer_text + mtp_idx 191name = f"model.layers. { new_bid } . { rest } " 192bid = new_bid 193 194# process the experts separately 195if name .find ("mlp.experts" )!= - 1 : 196n_experts = self .hparams ["n_routed_experts" ] 197assert bid is not None 198 199if self ._experts is None : 200self ._experts = [{}for _ in range (self .block_count )] 201 202self ._experts [bid ][name ]= data_torch 203 204if len (self ._experts [bid ])>= n_experts * 3 : 205# merge the experts into a single 3d tensor 206for w_name in ["gate_proj" ,"up_proj" ,"down_proj" ]: 207datas :list [Tensor ]= [] 208 209for xid in range (n_experts ): 210ename_to_retrieve = f"model.layers. { bid } .mlp.experts. { xid } . { w_name } .weight" 211datas .append (self ._experts [bid ][ename_to_retrieve ]) 212del self ._experts [bid ][ename_to_retrieve ] 213 214data_torch = torch .stack (datas ,dim = 0 ) 215merged_name = f"model.layers. { bid } .mlp.experts. { w_name } .weight" 216 217yield from super ().modify_tensors (data_torch ,merged_name ,bid ) 218return 219else : 220return 221yield from super ().modify_tensors (data_torch ,name ,bid ) 222 223def prepare_tensors (self ): 224super ().prepare_tensors () 225 226if self ._experts is not None : 227# flatten `list[dict[str, Tensor]]` into `list[str]` 228experts = [k for d in self ._experts for k in d .keys ()] 229if len (experts )> 0 : 230raise ValueError (f"Unprocessed experts: { experts } " ) 231 232 233@ ModelBase . register ( "MiMoV2ForCausalLM" ) 234@ ModelBase . example ( "XiaomiMiMo/MiMo-V2.5" ) 235class MiMoV2VisionAudioModel (MmprojModel ): 236has_audio_encoder = True 237 238_audio_tok_hparams :dict [str ,Any ]| None = None 239_rvq_codebook_sizes :list [int ]| None = None 240_code_embd :dict [int ,Tensor ]| None = None 241 242def __init__ (self ,* args ,** kwargs ): 243super ().__init__ (* args ,** kwargs ) 244assert self .hparams_vision is not None 245hp = self .hparams_vision 246 247hp ["image_size" ]= hp .get ("image_size" ,560 ) 248hp ["num_attention_heads" ]= hp .get ("num_heads" ,32 ) 249hp ["num_hidden_layers" ]= hp .get ("depth" ,28 ) 250 251self .n_q_heads = int (hp ["num_heads" ]) 252self .num_kv_heads = int (hp .get ("num_key_value_heads" ,8 )) 253self .head_dim = int (hp .get ("qk_channels" ,64 )) 254self .spatial_merge_size = int (hp ["spatial_merge_size" ]) 255# MiMoV2 vision RMSNorm: HF uses getattr(config, "rms_norm_eps", 1e-6) and the 256# field is absent from MiMo-V2.5's vision_config 257self .rms_norm_eps = float (hp .get ("rms_norm_eps" ,1e-6 )) 258 259# fullatt_block_indexes are also reflected in vit_window_attn_types as -1 260self .fullatt_block_indexes = list (hp .get ("fullatt_block_indexes" )or []) 261self .vit_window_attn_types = list (hp .get ("vit_window_attn_types" )or []) 262self .visual_token_window_size = int (hp .get ("visual_token_window_size" ,- 1 )) 263self .use_sink = bool (hp .get ("use_sink" ,False )) 264 265def get_audio_config (self )-> dict [str ,Any ]| None : 266if self ._audio_tok_hparams is None : 267path = self .dir_model / "audio_tokenizer" / "config.json" 268with open (path ,"r" ,encoding = "utf-8" )as f : 269cfg = json .load (f ) 270# aliases so MmprojModel.find_aparam() / n_block_keys can resolve them 271cfg ["hidden_size" ]= cfg ["d_model" ] 272cfg ["intermediate_size" ]= cfg ["encoder_ffn_dim" ] 273cfg ["num_attention_heads" ]= cfg ["encoder_attention_heads" ] 274self ._audio_tok_hparams = cfg 275return self ._audio_tok_hparams 276 277def set_gguf_parameters (self ): 278super ().set_gguf_parameters () 279 280self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .MIMOVL ) 281self .gguf_writer .add_vision_use_silu (True ) 282self .gguf_writer .add_vision_head_count_kv (self .num_kv_heads ) 283self .gguf_writer .add_vision_spatial_merge_size (self .spatial_merge_size ) 284self .gguf_writer .add_uint32 (gguf .Keys .ClipVision .WINDOW_SIZE ,self .visual_token_window_size ) 285self .gguf_writer .add_vision_wa_pattern_mode (self .vit_window_attn_types ) 286self .gguf_writer .add_vision_attention_layernorm_eps (self .rms_norm_eps ) 287self .gguf_writer .add_vision_min_pixels (int (self .preprocessor_config ["min_pixels" ])) 288self .gguf_writer .add_vision_max_pixels (int (self .preprocessor_config ["max_pixels" ])) 289 290assert self .hparams_audio is not None 291self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .MIMO_AUDIO ) 292self .gguf_writer .add_audio_num_mel_bins (self .hparams_audio ["n_mels" ]) 293self .gguf_writer .add_audio_attention_layernorm_eps (self .hparams_audio .get ("layer_norm_eps" ,1e-5 )) 294 295assert self ._rvq_codebook_sizes is not None 296self .gguf_writer .add_audio_rvq_num_quantizers (len (self ._rvq_codebook_sizes )) 297self .gguf_writer .add_audio_rvq_codebook_size (self ._rvq_codebook_sizes ) 298 299n_layer = self .hparams_audio ["encoder_layers" ] 300swa_per_block = self .hparams_audio .get ("swa_per_block" ,1 ) 301if self .hparams_audio .get ("hybrid_attention" )and swa_per_block > 1 : 302wa_pattern = [0 if i % swa_per_block < swa_per_block - 1 else - 1 for i in range (n_layer )] 303else : 304wa_pattern = [- 1 ]* n_layer 305self .gguf_writer .add_audio_wa_pattern_mode (wa_pattern ) 306self .gguf_writer .add_audio_window_size (int (self .hparams_audio ["encoder_attn_window_size" ][0 ])) 307 308audio_cfg = self .global_config ["audio_config" ] 309self .gguf_writer .add_audio_local_block_count (int (audio_cfg ["input_local_layers" ])) 310self .gguf_writer .add_audio_local_group_size (int (audio_cfg ["group_size" ])) 311 312def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 313# for audio encoder: keep codebook in F32 314if new_name in ( 315gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .A_ENC_RVQ_CODEBOOK ]+ ".weight" , 316gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .A_MM_CODE_EMBD ]+ ".weight" , 317 ): 318return gguf .GGMLQuantizationType .F32 319if ("encoder.conv" in name or "encoder.down_sample_layer" in name )and name .endswith (".weight" ): 320return gguf .GGMLQuantizationType .F32 321return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 322 323@classmethod 324def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 325name ,_ = item 326if name .startswith ("visual." )or name .startswith ("speech_embeddings." )or name .startswith ("audio_encoder." ): 327return super ().filter_tensors (item ) 328return None 329 330def modify_tensors (self ,data_torch ,name ,bid ): 331# Conv3D patch embed: split along the temporal axis (kt=2) into two Conv2D 332# weights that the existing qwen2vl-style two-Conv2D path consumes. 333if name == "visual.patch_embed.proj.weight" : 334_ ,_ ,kt ,_ ,_ = data_torch .shape 335if kt != 2 : 336raise ValueError (f"unexpected temporal_patch_size: { kt } " ) 337embd_name = gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .V_ENC_EMBD_PATCH ] 338yield (embd_name + ".weight" ,data_torch [:, :,0 , ...]) 339yield (embd_name + ".weight.1" ,data_torch [:, :,1 , ...]) 340return 341 342if m := re .match (r"^speech_embeddings\.(\d+)\.weight$" ,name ): 343if self ._code_embd is None : 344self ._code_embd = {} 345self ._code_embd [int (m .group (1 ))]= data_torch 346 347n_channels = int (self .global_config ["audio_config" ]["audio_channels" ]) 348if len (self ._code_embd )< n_channels : 349return 350merged = torch .stack ([self ._code_embd .pop (i )for i in range (n_channels )],dim = 0 ) 351yield (self .format_tensor_name (gguf .MODEL_TENSOR .A_MM_CODE_EMBD ),merged ) 352return 353 354if "conv1.bias" in name or "conv2.bias" in name : 355# transpose conv1/conv2 bias so it broadcasts against [n_frames, C_out, 1] 356data_torch = data_torch .unsqueeze (- 1 ) 357 358if name == "audio_encoder.projection.mlp.0.weight" : 359yield (self .format_tensor_name (gguf .MODEL_TENSOR .A_MMPROJ ,1 ),data_torch ) 360return 361if name == "audio_encoder.projection.mlp.2.weight" : 362yield (self .format_tensor_name (gguf .MODEL_TENSOR .A_MMPROJ ,2 ),data_torch ) 363return 364 365yield from super ().modify_tensors (data_torch ,name ,bid ) 366 367def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 368# note: audio encoder is in its own subdir "audio_tokenizer" 369from safetensors .torch import load_file 370 371tok_dir = self .dir_model / "audio_tokenizer" 372state_dict = load_file (tok_dir / "model.safetensors" ) 373 374codebook_re = re .compile (r"^encoder\.quantizer\.vq\.layers\.(\d+)\._codebook\.embed$" ) 375codebooks :dict [int ,Tensor ]= {} 376 377# EMA/training-only RVQ buffers - not needed for inference (nearest-codebook 378# lookup only reads "_codebook.embed") 379skip_suffixes = ( 380"_codebook.cluster_size" , 381"_codebook.embed_avg" , 382"_codebook.inited" , 383 ) 384for name ,tensor in state_dict .items (): 385if name .endswith (skip_suffixes ): 386continue 387if m := codebook_re .match (name ): 388codebooks [int (m .group (1 ))]= tensor 389continue 390yield name ,tensor 391 392# gather codebooks and merge into 3D tensor, similar to MoE MLP tensors 393n_q = len (codebooks ) 394ordered = [codebooks [i ]for i in range (n_q )] 395self ._rvq_codebook_sizes = [int (cb .shape [0 ])for cb in ordered ] 396max_bins = max (self ._rvq_codebook_sizes ) 397dim = ordered [0 ].shape [1 ] 398merged = ordered [0 ].new_zeros (n_q ,max_bins ,dim ) 399for i ,cb in enumerate (ordered ): 400merged [i , :cb .shape [0 ], :]= cb 401 402yield (self .format_tensor_name (gguf .MODEL_TENSOR .A_ENC_RVQ_CODEBOOK ),merged )