char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
54ee5ee64
char/aliceai-t5-moe
1from __future__import annotations 2 3import math 4import re 5 6import torch 7 8from typing import TYPE_CHECKING ,Any ,Callable ,Iterable 9 10if TYPE_CHECKING : 11from torch import Tensor 12 13from .base import MmprojModel ,ModelBase ,gguf 14 15from .deepseek import DeepseekV2Model 16 17 18@ ModelBase . register ( "Dots3NoteForCausalLM" , "Dots3NoteForConditionalGeneration" , "Dots3NoteTextForCausalLM" ) 19class Dots3NoteModel (DeepseekV2Model ): 20model_arch = gguf .MODEL_ARCH .DOTS3NOTE 21skip_mtp = False 22supports_mtp_export = True 23 24# trunk layer count, stashed before indexing for filter_tensors (mirrors DeepseekV32Model) 25_n_main_layers :int | None = None 26 27def index_tensors (self ,remote_hf_model_id :str | None = None ): 28 type(self )._n_main_layers = self .hparams ["num_hidden_layers" ] 29return super ().index_tensors (remote_hf_model_id = remote_hf_model_id ) 30 31def __init__ (self ,* args ,** kwargs ): 32super ().__init__ (* args ,** kwargs ) 33 34hparams = self .hparams 35 36# config file doesn't specify MTP block, detect it from model weight 37self .n_nextn = 1 if "model.mtp.embed_tokens.weight" in self .model_tensors else 0 38if self .n_nextn : 39self .block_count += self .n_nextn 40self .tensor_map = gguf .get_tensor_name_map (self .model_arch ,self .block_count ) 41 42self .layer_types = hparams ["layer_types" ] 43if len (self .layer_types )< hparams ["num_hidden_layers" ]: 44raise ValueError ("layer_types is shorter than num_hidden_layers" ) 45 46if hparams .get ("use_dsa" ,True )is not True : 47raise ValueError ("dots3-note conversion requires use_dsa=true" ) 48if hparams .get ("normalization" ,"RMSNorm" )!= "RMSNorm" or hparams .get ("final_norm" ,"RMSNorm" )!= "RMSNorm" : 49raise ValueError ("dots3-note conversion only supports RMSNorm" ) 50if hparams .get ("k_rope_only_layernorm" ,True )is not True : 51raise ValueError ("dots3-note conversion requires k_rope_only_layernorm=true" ) 52if hparams .get ("topk_method" ,"noaux_tc" )!= "noaux_tc" or hparams .get ("scoring_func" )!= "sigmoid" : 53raise ValueError ("dots3-note conversion only supports noaux_tc/sigmoid expert gating" ) 54if hparams .get ("n_group" ,1 )!= 1 or hparams .get ("topk_group" ,1 )!= 1 : 55raise ValueError ("dots3-note conversion does not support grouped expert routing" ) 56if hparams .get ("use_dynamic_rsf" ,False )or hparams .get ("moe_gating_fp32" ,False ): 57raise ValueError ("dots3-note conversion does not support use_dynamic_rsf/moe_gating_fp32" ) 58for key in ("attention_gate_type" ,"swa_attention_gate_type" ): 59if hparams .get (key ,"headwise" )!= "headwise" : 60raise ValueError (f"dots3-note conversion only supports headwise attention gate, got { key } = { hparams . get ( key )!r } " ) 61if hparams ["swa_qk_nope_head_dim" ]+ hparams ["swa_qk_rope_head_dim" ]!= hparams .get ("swa_head_dim" ,256 ): 62raise ValueError ("swa_head_dim must equal swa_qk_nope_head_dim + swa_qk_rope_head_dim" ) 63if hparams ["swa_qk_rope_head_dim" ]!= hparams ["qk_rope_head_dim" ]: 64# both layer kinds share a single rope_dimension_count 65raise ValueError ("swa_qk_rope_head_dim must match qk_rope_head_dim" ) 66 67self .apply_lora_rescale = hparams .get ("apply_mla_qkv_lora_rescale" ,False ) 68 69def _is_swa_layer (self ,bid :int )-> bool : 70if bid >= self .hparams ["num_hidden_layers" ]: 71# note: the NextN/MTP block uses the sliding-attention MLA 72return True 73return self .layer_types [bid ]== "sliding_attention" 74 75def set_vocab (self ): 76from transformers import AutoTokenizer 77tokenizer = AutoTokenizer .from_pretrained (self .dir_model ) 78special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 79tokens ,toktypes ,tokpre = self .get_vocab_base () 80self .gguf_writer .add_tokenizer_model ("gpt2" ) 81self .gguf_writer .add_tokenizer_pre (tokpre ) 82self .gguf_writer .add_token_list (tokens ) 83self .gguf_writer .add_token_types (toktypes ) 84special_vocab ._set_special_token ("eot" ,tokenizer .get_added_vocab ()["<|endofassistant|>" ])# ty: ignore[unresolved-attribute] 85special_vocab .add_to_gguf (self .gguf_writer ) 86 87@classmethod 88def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 89if (titem := super ().filter_tensors (item ))is None : 90return None 91name ,gen = titem 92if name .startswith (("vision_encoder." ,"audio_encoder." )): 93return None 94 95assert cls ._n_main_layers is not None 96is_mtp = name .startswith ("model.mtp." )or \ 97 ((m := re .match (r"model\.layers\.(\d+)\." ,name ))is not None and int (m .group (1 ))>= cls ._n_main_layers ) 98 99# --no-mtp: drop the NextN/MTP block; --mtp: keep only that block plus the shared embeddings/norm/lm_head 100if is_mtp and cls .no_mtp : 101return None 102if cls .mtp_only and not is_mtp and name not in ( 103"model.embed_tokens.weight" ,"model.norm.weight" ,"lm_head.weight" , 104 ): 105return None 106 107return name ,gen 108 109def set_gguf_parameters (self ): 110hparams = self .hparams 111 112# head_count is a per-layer array because the two layer kinds have different head counts 113n_layer = hparams ["num_hidden_layers" ] 114hparams ["num_attention_heads" ]= [ 115hparams ["swa_num_attention_heads" ]if self ._is_swa_layer (il )else hparams ["num_attention_heads" ] 116for il in range (self .block_count ) 117 ] 118 119# prevent the base class from emitting key/value_length from the unused head_dim 120hparams .pop ("head_dim" ,None ) 121 122super ().set_gguf_parameters () 123 124# MLA geometry of the sliding-window layers (rope.freq_base_swa is emitted by the base class) 125swa_kv_lora_rank = hparams ["swa_kv_lora_rank" ] 126self .gguf_writer .add_sliding_window (hparams ["sliding_window_size" ]) 127self .gguf_writer .add_sliding_window_pattern ([self ._is_swa_layer (il )for il in range (n_layer )]) 128self .gguf_writer .add_kv_lora_rank_swa (swa_kv_lora_rank ) 129self .gguf_writer .add_key_length_swa (swa_kv_lora_rank + hparams ["swa_qk_rope_head_dim" ]) 130self .gguf_writer .add_value_length_swa (swa_kv_lora_rank ) 131self .gguf_writer .add_key_length_mla_swa (hparams ["swa_qk_nope_head_dim" ]+ hparams ["swa_qk_rope_head_dim" ]) 132self .gguf_writer .add_value_length_mla_swa (hparams ["swa_v_head_dim" ]) 133if hparams ["swa_q_lora_rank" ]!= hparams ["q_lora_rank" ]: 134raise ValueError ("dots3-note conversion assumes a shared q_lora_rank for both layer kinds" ) 135 136if self .n_nextn : 137self .gguf_writer .add_nextn_predict_layers (self .n_nextn ) 138 139# DSA indexer (full-attention layers only) 140self .gguf_writer .add_indexer_head_count (hparams ["index_n_heads" ]) 141self .gguf_writer .add_indexer_key_length (hparams ["index_head_dim" ]) 142self .gguf_writer .add_indexer_top_k (hparams ["index_topk" ]) 143self .gguf_writer .add_indexer_types ([not self ._is_swa_layer (il )for il in range (n_layer )]) 144 145def prepare_metadata (self ,vocab_only :bool ): 146from_dir = self .fname_out .is_dir () 147super ().prepare_metadata (vocab_only = vocab_only ) 148 149if not self .mtp_only or not from_dir : 150return 151 152output_type :str = self .ftype .name .partition ("_" )[2 ] 153fname_default :str = gguf .naming_convention ( 154self .metadata .name ,self .metadata .basename ,self .metadata .finetune , 155self .metadata .version ,size_label = None ,output_type = output_type ,model_type = None ) 156self .fname_out = self .fname_out .parent / f"mtp- { fname_default } .gguf" 157 158def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 159# move the MTP token embedding into the NextN block so the standard nextn mapping picks it up 160if name == "model.mtp.embed_tokens.weight" : 161name = f"model.layers. { self . hparams [ 'num_hidden_layers' ] } .embed_tokens.weight" 162bid = self .hparams ["num_hidden_layers" ] 163 164# fold the activation rescale sqrt(n_embd/lora_rank) into the preceding RMSNorm weight 165# this also covers the indexer wq_b, which reads the same rescaled q_lora activation 166if self .apply_lora_rescale and bid is not None : 167if name .endswith ("q_a_layernorm.weight" ): 168data_torch = data_torch * math .sqrt (self .hparams ["hidden_size" ]/ self .hparams ["q_lora_rank" ]) 169elif name .endswith ("kv_a_layernorm.weight" ): 170rank = self .hparams ["swa_kv_lora_rank" ]if self ._is_swa_layer (bid )else self .hparams ["kv_lora_rank" ] 171data_torch = data_torch * math .sqrt (self .hparams ["hidden_size" ]/ rank ) 172 173# MLA absorption: split kv_b_proj into k_b (transposed) and v_b, per-layer-kind geometry 174if name .endswith ("kv_b_proj.weight" ): 175assert bid is not None 176if self ._is_swa_layer (bid ): 177n_head = self .hparams ["swa_num_attention_heads" ] 178qk_nope_head_dim = self .hparams ["swa_qk_nope_head_dim" ] 179v_head_dim = self .hparams ["swa_v_head_dim" ] 180else : 181n_head = self .hparams ["num_attention_heads" ] 182qk_nope_head_dim = self .hparams ["qk_nope_head_dim" ] 183v_head_dim = self .hparams ["v_head_dim" ] 184if isinstance (n_head ,list ):# set_gguf_parameters turns this into a per-layer array 185n_head = n_head [bid ] 186 187assert data_torch .shape [0 ]== n_head * (qk_nope_head_dim + v_head_dim ) 188 189kv_b = data_torch .view (n_head ,qk_nope_head_dim + v_head_dim ,data_torch .shape [- 1 ]) 190k_b ,v_b = kv_b .split ([qk_nope_head_dim ,v_head_dim ],dim = 1 ) 191k_b = k_b .transpose (1 ,2 ) 192 193yield from ModelBase .modify_tensors (self ,k_b ,name .replace ("kv_b_proj" ,"k_b_proj" ),bid ) 194yield from ModelBase .modify_tensors (self ,v_b ,name .replace ("kv_b_proj" ,"v_b_proj" ),bid ) 195return 196 197yield from super ().modify_tensors (data_torch ,name ,bid ) 198 199 200@ ModelBase . register ( "Dots3NoteForCausalLM" , "Dots3NoteForConditionalGeneration" ) 201class Dots3NoteMmprojModel (MmprojModel ): 202has_vision_encoder = True 203has_audio_encoder = True 204 205def __init__ (self ,* args ,** kwargs ): 206super ().__init__ (* args ,** kwargs ) 207assert self .hparams_vision is not None 208assert self .hparams_audio is not None 209 210# preprocessor_config.json nests the image params under vision_config 211self .preprocessor_config = {** self .preprocessor_config ,** self .preprocessor_config .get ("vision_config" , {})} 212 213vis = self .hparams_vision 214# in this config, hidden_size is the adapter output width; embed_dim is the tower width 215vis ["hidden_size" ]= vis ["embed_dim" ] 216vis ["image_size" ]= 0 # dynamic resolution 217self .pyramid = [max (0 ,n )for n in vis ["pyramid_num_routed" ]] 218 219if vis .get ("adapter_type" )!= "patch_merger" or not vis .get ("pre_pixel_shuffle" ): 220raise ValueError ("dots3-note vision conversion requires adapter_type=patch_merger and pre_pixel_shuffle" ) 221if vis .get ("router_scoring_func" ,"sigmoid" )!= "sigmoid" or vis .get ("router_scale" ,1.0 )!= 1.0 : 222raise ValueError ("dots3-note vision conversion only supports sigmoid routing with router_scale=1.0" ) 223if vis .get ("temporal_patch_size" ,1 )!= 1 or vis .get ("use_bias" )or not vis .get ("use_qk_norm" ): 224raise ValueError ("unsupported dots3-note vision config variant" ) 225 226aud = self .hparams_audio 227if not aud .get ("use_conv2d_stem" )or not aud .get ("use_rope" )or not aud .get ("use_rms_norm" )or aud .get ("use_causal" ): 228raise ValueError ("unsupported dots3-note audio config variant" ) 229if aud ["whisper_config" ].get ("activation_function" )!= "swiglu" : 230raise ValueError ("dots3-note audio conversion requires the swiglu activation" ) 231if aud .get ("merge_factor" ,1 )!= 1 or aud .get ("chunk_seconds" )!= 60 : 232raise ValueError ("unsupported dots3-note audio chunking config" ) 233# the graph hard-codes these rope parameters 234rope = aud .get ("rope_parameters" , {}) 235if rope .get ("partial_rotary_factor" )!= 0.5 or rope .get ("rope_theta" )!= 10000.0 : 236raise ValueError ("unsupported dots3-note audio rope config" ) 237 238def get_audio_config (self )-> dict [str ,Any ]| None : 239cfg = self .global_config .get ("audio_config" ) 240if cfg is not None : 241# aliases so MmprojModel.find_aparam() / n_block_keys can resolve them 242whisper = cfg ["whisper_config" ] 243cfg ["hidden_size" ]= whisper ["d_model" ] 244cfg ["intermediate_size" ]= whisper ["encoder_ffn_dim" ] 245cfg ["num_attention_heads" ]= whisper ["encoder_attention_heads" ] 246cfg ["num_hidden_layers" ]= whisper ["encoder_layers" ] 247return cfg 248 249def set_gguf_parameters (self ): 250super ().set_gguf_parameters () 251assert self .hparams_vision is not None 252assert self .hparams_audio is not None 253 254self .gguf_writer .add_clip_vision_projector_type (gguf .VisionProjectorType .DOTS3NOTE_V ) 255self .gguf_writer .add_vision_use_silu (True ) 256self .gguf_writer .add_vision_attention_layernorm_eps (self .hparams_vision ["rms_norm_eps" ]) 257self .gguf_writer .add_vision_spatial_merge_size (self .hparams_vision ["spatial_merge_size" ]) 258self .gguf_writer .add_vision_min_pixels (self .preprocessor_config ["min_pixels" ]) 259self .gguf_writer .add_vision_max_pixels (self .preprocessor_config ["max_pixels" ]) 260# pyramid MoE: per-block routed expert count, 0 = dense block 261self .gguf_writer .add_vision_expert_count_per_layer (self .pyramid ) 262self .gguf_writer .add_vision_expert_used_count (int (self .hparams_vision ["capacity_factor" ])) 263 264self .gguf_writer .add_clip_audio_projector_type (gguf .VisionProjectorType .DOTS3NOTE_A ) 265self .gguf_writer .add_audio_num_mel_bins (self .hparams_audio ["whisper_config" ]["num_mel_bins" ]) 266self .gguf_writer .add_audio_attention_layernorm_eps (1e-6 )# Dots3NoteAudioRMSNorm default 267 268@classmethod 269def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 270name ,_ = item 271if not name .startswith (("vision_encoder." ,"audio_encoder." )): 272return None 273return super ().filter_tensors (item ) 274 275_vis_experts :dict [int ,dict [str ,Tensor ]]| None = None 276 277def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 278# router params have no .weight suffix in the checkpoint, but gguf tools expect one 279if name .endswith ((".gate_weight" ,".router_bias" )): 280name += ".weight" 281 282# audio fc1 fuses gate and up for swiglu; split it 283if ".speech_encoder.layers." in name and ".fc1." in name : 284gate ,up = data_torch .chunk (2 ,dim = 0 ) 285yield from super ().modify_tensors (gate ,name .replace (".fc1." ,".fc1_gate." ),bid ) 286yield from super ().modify_tensors (up ,name .replace (".fc1." ,".fc1_up." ),bid ) 287return 288 289# vision MoE: stack per-expert weights into a single 3D tensor per block 290if ".mlp.experts." in name : 291assert bid is not None 292n_expert = self .pyramid [bid ] 293if self ._vis_experts is None : 294self ._vis_experts = {} 295buf = self ._vis_experts .setdefault (bid , {}) 296buf [name ]= data_torch 297 298if len (buf )>= n_expert * 3 : 299for w_name in ("fc1" ,"fc2" ,"fc3" ): 300datas :list [Tensor ]= [] 301for xid in range (n_expert ): 302ename = f"vision_encoder.blocks. { bid } .mlp.experts. { xid } . { w_name } .weight" 303datas .append (buf .pop (ename )) 304merged = torch .stack (datas ,dim = 0 ) 305yield from super ().modify_tensors (merged ,f"vision_encoder.blocks. { bid } .mlp.experts. { w_name } .weight" ,bid ) 306return 307 308yield from super ().modify_tensors (data_torch ,name ,bid ) 309 310def prepare_tensors (self ): 311super ().prepare_tensors () 312if self ._vis_experts is not None : 313leftover = [k for d in self ._vis_experts .values ()for k in d .keys ()] 314if leftover : 315raise ValueError (f"unprocessed vision experts: { leftover } " ) 316 317def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 318# FP32 routing is load-bearing for the vision MoE (near-tied expert scores) 319if ".ffn_gate_inp." in new_name or ".exp_probs_b." in new_name : 320return gguf .GGMLQuantizationType .F32 321if ".conv2d" in new_name or "a.conv_out" in new_name : 322return gguf .GGMLQuantizationType .F32 323return super ().tensor_force_quant (name ,new_name ,bid ,n_dims )