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 math 4import re 5 6from typing import Callable ,Iterable ,TYPE_CHECKING 7 8import torch 9 10if TYPE_CHECKING : 11from torch import Tensor 12 13from .base import MmprojModel ,ModelBase ,TextModel ,_MISTRAL_COMMON_DATASET_MEAN ,_MISTRAL_COMMON_DATASET_STD ,gguf 14 15from .qwen import Qwen3Model 16 17 18@ ModelBase . register ( "StepVLForConditionalGeneration" , "Step3p7ForConditionalGeneration" ) 19@ ModelBase . example ( "stepfun-ai/Step3-VL-10B" , "stepfun-ai/Step-3.7-Flash" ) 20class Step3VLVisionModel (MmprojModel ): 21def __init__ (self ,* args ,** kwargs ): 22super ().__init__ (* args ,** kwargs ) 23assert self .hparams_vision is not None 24 25if not self .hparams_vision .get ("intermediate_size" ): 26hidden_size = self .hparams_vision .get ("hidden_size" )or self .hparams_vision .get ("width" )or 0 27assert hidden_size > 0 28mlp_ratio = float (self .hparams_vision .get ("mlp_ratio" ,8960 / 1536 )) 29self .hparams_vision ["intermediate_size" ]= int (round (hidden_size * mlp_ratio )) 30 31self .preprocessor_config .setdefault ("image_mean" ,list (_MISTRAL_COMMON_DATASET_MEAN )) 32self .preprocessor_config .setdefault ("image_std" ,list (_MISTRAL_COMMON_DATASET_STD )) 33 34def set_gguf_parameters (self ): 35super ().set_gguf_parameters () 36assert self .hparams_vision is not None 37 38projector_stride = int (self .global_config .get ("understand_projector_stride" ,- 1 )) 39hidden_size = int (self .hparams_vision .get ("hidden_size" ,self .hparams_vision .get ("width" ,- 1 ))) 40num_layers = int (self .hparams_vision .get ("num_hidden_layers" ,self .hparams_vision .get ("layers" ,- 1 ))) 41assert (projector_stride ,int (self .hparams_vision .get ("image_size" ,- 1 )),hidden_size ,num_layers )== (2 ,728 ,1536 ,47 ), ( 42"current Step3-VL conversion path is only validated for Step3-VL-10B" 43 ) 44 45self .gguf_writer .add_clip_projector_type (gguf .VisionProjectorType .STEP3VL ) 46self .gguf_writer .add_vision_attention_layernorm_eps (float (self .hparams_vision .get ("layer_norm_eps" ,1e-5 ))) 47self .gguf_writer .add_vision_projector_scale_factor (projector_stride ** 2 ) 48# 3024 max resize comes from step3-vl-10b processing_step3.py. 49self .gguf_writer .add_vision_preproc_image_size (3024 ) 50 51def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 52if ".position_embd." in new_name : 53return gguf .GGMLQuantizationType .F32 54if ("mm.0." in new_name or "mm.1." in new_name )and new_name .endswith (".weight" ): 55return gguf .GGMLQuantizationType .F16 if self .ftype == gguf .LlamaFileType .MOSTLY_F16 else gguf .GGMLQuantizationType .F32 56return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 57 58@classmethod 59def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 60name ,gen = item 61 62if name .startswith (("model." ,"lm_head." )): 63return None 64 65return super ().filter_tensors (item ) 66 67def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 68if name .startswith ("vision_model.vit_downsampler" ): 69match = re .match (r"vision_model\.vit_downsampler(\d+)\.(weight|bias)" ,name ) 70if match is None : 71raise ValueError (f"Unexpected Step3-VL projector tensor { name !r } " ) 72 73proj_id = int (match .group (1 ))- 1 74suffix = f". { match . group ( 2 ) } " 75yield (self .format_tensor_name (gguf .MODEL_TENSOR .V_MMPROJ ,proj_id ,suffix = suffix ),data_torch ) 76return 77 78if name == "vit_large_projector.weight" : 79yield (self .format_tensor_name (gguf .MODEL_TENSOR .V_MMPROJ_FC ),data_torch ) 80return 81 82if name .startswith ("vision_model." ): 83if name == "vision_model.positional_embedding" : 84name += ".weight" 85elif name .endswith (".gamma" )and ".ls_" in name : 86name = name .removesuffix (".gamma" )+ ".weight" 87 88name = name .replace ("attn.in_proj_weight" ,"attn.in_proj.weight" ) 89name = name .replace ("attn.in_proj_bias" ,"attn.in_proj.bias" ) 90 91yield from super ().modify_tensors (data_torch ,name ,bid ) 92 93 94@ ModelBase . register ( "StepVLForConditionalGeneration" ) 95@ ModelBase . example ( "stepfun-ai/Step3-VL-10B" ) 96class Step3VLTextModel (Qwen3Model ): 97model_arch = gguf .MODEL_ARCH .QWEN3 98 99 100@ ModelBase . register ( "Step3p5ForCausalLM" , "Step3p7ForConditionalGeneration" ) 101@ ModelBase . example ( "stepfun-ai/Step-3.7-Flash" ) 102class Step35Model (TextModel ): 103model_arch = gguf .MODEL_ARCH .STEP35 104supports_mtp_export = True 105 106# The --mtp / --no-mtp toggles are ModelBase.mtp_only / no_mtp (set in 107# convert_hf_to_gguf.py main()). Unlike Qwen3.5, which stores MTP under a 108# `mtp.*` namespace, Step3.5 appends MTP layers at 109# `model.layers.{num_hidden_layers + i}`, so we filter them by layer index. 110# The trunk layer count is captured before indexing so the classmethod 111# filter_tensors can tell the appended MTP block(s) apart from the trunk. 112_n_main_layers :int | None = None 113 114def __init__ (self ,* args ,** kwargs ): 115super ().__init__ (* args ,** kwargs ) 116# NextN/MTP layers are appended past num_hidden_layers; extend the 117# tensor map to cover them so the MTP block's tensors get correctly 118# indexed names. When --no-mtp drops the MTP blocks, fall back to the 119# base num_hidden_layers so we don't reserve unused slots. 120n_nextn = int (self .hparams .get ("num_nextn_predict_layers" ,0 )) 121if n_nextn > 0 and not self .no_mtp : 122self .block_count += n_nextn 123self .tensor_map = gguf .get_tensor_name_map (self .model_arch ,self .block_count ) 124 125def index_tensors (self ,remote_hf_model_id :str | None = None ): 126# filter_tensors is a classmethod and can't reach self.hparams; stash 127# the trunk layer count here (before indexing runs) so it can detect 128# the appended MTP layers by index. 129hparams = {** self .hparams ,** self .hparams .get ("text_config" , {})} 130key = next ((k for k in ["n_layers" ,"num_hidden_layers" ,"n_layer" ,"num_layers" ]if k in hparams ),None ) 131 type(self )._n_main_layers = hparams .get (key ) 132return super ().index_tensors (remote_hf_model_id = remote_hf_model_id ) 133 134def set_gguf_parameters (self ): 135rope_theta = self .hparams .get ("rope_theta" ) 136if isinstance (rope_theta ,list ): 137self .hparams ["rope_theta" ]= float (rope_theta [0 ]) 138self .hparams ["local_rope_theta" ]= float (rope_theta [1 ]) 139self .rope_parameters ["rope_theta" ]= self .hparams ["rope_theta" ] 140self .rope_parameters ["sliding_attention" ]= {"rope_theta" :self .hparams ["local_rope_theta" ]} 141 142super ().set_gguf_parameters () 143 144layer_types = self .hparams .get ("layer_types" )or [] 145partial_rotary_factors = self .hparams .get ("partial_rotary_factors" )or [] 146attn_other = self .hparams .get ("attention_other_setting" )or {} 147 148n_head_base = self .hparams ["num_attention_heads" ] 149n_kv_base = self .hparams ["num_attention_groups" ] 150 151n_head_swa = attn_other .get ("num_attention_heads" ,n_head_base ) 152n_kv_swa = attn_other .get ("num_attention_groups" ,n_kv_base ) 153 154n_nextn = int (self .hparams .get ("num_nextn_predict_layers" ,0 )) 155 156# The Step3p5 HF checkpoint stores layer_types/partial_rotary_factors 157# entries for the MTP blocks past num_hidden_layers; preserve them so 158# the MTP layer's attention shape, SWA flag, and partial RoPE dim are 159# set correctly. Pad with full-attention defaults if the checkpoint 160# truncated them. 161def _pad (arr ,n ,default ): 162arr = list (arr ) 163if len (arr )< n : 164arr = arr + [default ]* (n - len (arr )) 165return arr [:n ] 166 167layer_types = _pad (layer_types ,self .block_count ,"full_attention" ) 168partial_rotary_factors = _pad ( 169partial_rotary_factors , 170self .block_count , 1710.5 ,# full_attention default for Step3p5 172 ) 173assert [1.0 if lt == "sliding_attention" else 0.5 for lt in layer_types ]== partial_rotary_factors 174head_arr = [n_head_swa if lt == "sliding_attention" else n_head_base for lt in layer_types ] 175kv_arr = [n_kv_swa if lt == "sliding_attention" else n_kv_base for lt in layer_types ] 176swa_pat = [lt == "sliding_attention" for lt in layer_types ] 177 178self .gguf_writer .add_head_count (head_arr ) 179self .gguf_writer .add_head_count_kv (kv_arr ) 180 181self .gguf_writer .add_sliding_window (self .hparams ["sliding_window" ]) 182self .gguf_writer .add_sliding_window_pattern (swa_pat ) 183 184self .gguf_writer .add_value_length (self .hparams ["head_dim" ]) 185 186# MoE params 187self .gguf_writer .add_expert_count (self .hparams ["moe_num_experts" ]) 188self .gguf_writer .add_expert_used_count (self .hparams ["moe_top_k" ]) 189self .gguf_writer .add_expert_feed_forward_length (self .hparams ["moe_intermediate_size" ]) 190self .gguf_writer .add_expert_shared_feed_forward_length (self .hparams ["share_expert_dim" ]) 191 192if (moe_router_scaling_factor := self .hparams .get ("moe_router_scaling_factor" ))is not None : 193self .gguf_writer .add_expert_weights_scale (moe_router_scaling_factor ) 194if (norm_expert_weight := self .hparams .get ("norm_expert_weight" ))is not None : 195self .gguf_writer .add_expert_weights_norm (norm_expert_weight ) 196 197# leading dense blocks 198leading_dense = 0 199moe_layers_enum = self .hparams .get ("moe_layers_enum" ) 200if isinstance (moe_layers_enum ,str )and moe_layers_enum .strip (): 201moe_layers = sorted (int (i )for i in moe_layers_enum .strip ().split ("," )) 202if moe_layers : 203leading_dense = max (0 ,moe_layers [0 ]) 204self .gguf_writer .add_leading_dense_block_count (leading_dense ) 205self .gguf_writer .add_moe_every_n_layers (int (self .hparams .get ("moe_every_n_layer" ,1 ))) 206 207self .gguf_writer .add_layer_norm_rms_eps (self .hparams .get ("rms_norm_eps" ,1e-5 )) 208 209# Optional per-layer SwiGLU clamps. MTP layers default to no clamping (0.0). 210if (limits := self .hparams .get ("swiglu_limits" ))is not None : 211limits_f = _pad ( 212 [0.0 if v is None else float (v )for v in limits ], 213self .block_count , 2140.0 , 215 ) 216self .gguf_writer .add_swiglu_clamp_exp (limits_f ) 217if (limits_shared := self .hparams .get ("swiglu_limits_shared" ))is not None : 218limits_shared_f = _pad ( 219 [0.0 if v is None else float (v )for v in limits_shared ], 220self .block_count , 2210.0 , 222 ) 223self .gguf_writer .add_swiglu_clamp_shexp (limits_shared_f ) 224 225if n_nextn > 0 and not self .no_mtp : 226self .gguf_writer .add_nextn_predict_layers (n_nextn ) 227 228@classmethod 229def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 230if (titem := super ().filter_tensors (item ))is None : 231return None 232name ,gen = titem 233 234# Map router bias (expert selection bias) to a GGUF bias tensor 235if name .endswith (".moe.router_bias" ): 236name += ".bias" 237 238# Step3.5 appends the MTP block(s) past num_hidden_layers. 239assert cls ._n_main_layers is not None 240is_mtp = (m := re .match (r"model\.layers\.(\d+)\." ,name ))is not None and int (m .group (1 ))>= cls ._n_main_layers 241 242# --no-mtp: drop the appended MTP block(s) entirely. 243if is_mtp and cls .no_mtp : 244return None 245# --mtp: keep ONLY MTP-block tensors plus the shared embeddings/norm/ 246# lm_head (so the resulting GGUF carries just the draft head). 247if cls .mtp_only and not is_mtp and name not in ( 248"model.embed_tokens.weight" ,"model.norm.weight" ,"lm_head.weight" , 249 ): 250return None 251 252# The checkpoint nests the per-MTP-layer shared head under 253# `model.layers.{N+i}.transformer.shared_head.{norm,output}.weight`; 254# strip the `transformer.` infix and rename `output` → `head` so the 255# existing NEXTN_SHARED_HEAD_{NORM,HEAD} tensor mapping picks them up. 256# Mirrors vllm's `_rewrite_spec_layer_name` (step3p5_mtp.py). 257if is_mtp : 258name = name .replace (".transformer." ,"." ) 259name = name .replace ("shared_head.output" ,"shared_head.head" ) 260 261return name ,gen 262 263def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None ): 264if name .endswith ("norm.weight" ): 265data_torch += 1.0 266 267if name .endswith ((".self_attn.g_proj.weight" ,".moe.gate.weight" ,".moe.up_proj.weight" ,".moe.gate_proj.weight" ,".moe.down_proj.weight" )): 268data_torch = data_torch .squeeze ().contiguous () 269 270yield from super ().modify_tensors (data_torch ,name ,bid ) 271 272def prepare_metadata (self ,vocab_only :bool ): 273from_dir = self .fname_out .is_dir () 274super ().prepare_metadata (vocab_only = vocab_only ) 275 276# Mirror Qwen3.5's behavior: when emitting a draft-only file into a 277# directory, prefix with "mtp-" so it doesn't collide with the trunk. 278if not self .mtp_only or not from_dir : 279return 280 281output_type :str = self .ftype .name .partition ("_" )[2 ] 282fname_default :str = gguf .naming_convention ( 283self .metadata .name ,self .metadata .basename ,self .metadata .finetune , 284self .metadata .version ,size_label = None ,output_type = output_type ,model_type = None ) 285self .fname_out = self .fname_out .parent / f"mtp- { fname_default } .gguf" 286 287def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 288# Step35 can optionally use Llama-3 style RoPE scaling (HF: rope_scaling.rope_type == "llama3"). 289# llama.cpp represents this via a single extra tensor: "rope_freqs.weight" (aka MODEL_TENSOR.ROPE_FREQS). 290rope_params = self .rope_parameters .get ("full_attention" ,self .rope_parameters ) 291rope_type = rope_params .get ("rope_type" )or "" 292if rope_type .lower ()!= "llama3" : 293return 294 295# Step35 configs can carry per-layer rope_theta as a list; for llama3 rope factors we use the base value. 296rope_theta = self .hparams .get ("rope_theta" ,10000.0 ) 297if isinstance (rope_theta ,list ): 298rope_theta = rope_theta [0 ] 299base = float (rope_theta ) 300 301if (storage_dim := self .hparams .get ("head_dim" ))is None : 302storage_dim = self .hparams ["hidden_size" ]// self .hparams ["num_attention_heads" ] 303storage_dim = int (storage_dim ) 304 305# Llama 3 factors apply only to the rotary dims used by full_attention layers 306# (partial_rotary_factor * head_dim). Remaining slots are padded with 1.0 so 307# sliding_attention layers remain unaffected. set_gguf_parameters already 308# guarantees at least one full_attention layer. 309layer_types = (self .hparams .get ("layer_types" )or [])[:self .block_count ] 310partial_rotary_factors = (self .hparams .get ("partial_rotary_factors" )or [])[:self .block_count ] 311full_attention_factor = next ( 312float (f )for lt ,f in zip (layer_types ,partial_rotary_factors )if lt == "full_attention" 313 ) 314rotary_dim = int (storage_dim * full_attention_factor ) 315 316freqs = 1.0 / (base ** (torch .arange (0 ,rotary_dim ,2 ,dtype = torch .float32 )/ rotary_dim )) 317 318factor = float (rope_params .get ("factor" ,8.0 )) 319low_freq_factor = float (rope_params .get ("low_freq_factor" ,1.0 )) 320high_freq_factor = float (rope_params .get ("high_freq_factor" ,4.0 )) 321old_context_len = int (rope_params .get ("original_max_position_embeddings" ,8192 )) 322 323low_freq_wavelen = old_context_len / low_freq_factor 324high_freq_wavelen = old_context_len / high_freq_factor 325 326rope_factors :list [float ]= [] 327for freq in freqs : 328wavelen = 2 * math .pi / float (freq ) 329if wavelen < high_freq_wavelen : 330rope_factors .append (1.0 ) 331elif wavelen > low_freq_wavelen : 332rope_factors .append (factor ) 333else : 334smooth = (old_context_len / wavelen - low_freq_factor )/ (high_freq_factor - low_freq_factor ) 335rope_factors .append (1.0 / ((1.0 - smooth )/ factor + smooth )) 336 337# Pad to head_dim/2 with 1.0 so non-scaled layers remain neutral. 338if len (rope_factors )< storage_dim // 2 : 339rope_factors .extend ([1.0 ]* (storage_dim // 2 - len (rope_factors ))) 340 341yield (self .format_tensor_name (gguf .MODEL_TENSOR .ROPE_FREQS ),torch .tensor (rope_factors ,dtype = torch .float32 ))