char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d23c47f2a
char/aliceai-t5-moe
1from __future__import annotations 2 3import re 4from typing import Iterable 5 6import torch 7 8from .base import ModelBase ,gguf ,logger 9from .deepseek import DeepseekV2Model 10 11 12def split_gate_up (weight :torch .Tensor ,moe_intermediate_size :int ): 13"""Split a fused stacked gate_up expert tensor into (gate, up). 14 15weight: [n_expert, 2*moe_intermediate_size, hidden] (gate first, up second). 16Returns (gate, up) each [n_expert, moe_intermediate_size, hidden]. 17""" 18assert weight .shape [1 ]== 2 * moe_intermediate_size ,f" { weight . shape [ 1 ] } != 2* { moe_intermediate_size } " 19gate = weight [:, :moe_intermediate_size , :].contiguous () 20up = weight [:,moe_intermediate_size :, :].contiguous () 21return gate ,up 22 23 24@ ModelBase . register ( "HYV4ForCausalLM" ) 25@ ModelBase . example ( "tencent/Hy4-preview" ) 26class HYV4Model (DeepseekV2Model ): 27"""HY_V4: DeepSeek-V3 style MLA + MoE with iHC, a gated MLA output and a learnable sink. 28 29Reuses DeepseekV2Model for the vocab and the MLA metadata, but overrides the tensor mapping 30because HY_V4 ships pre-stacked / fused experts plus extra iHC, gate and sink tensors. The 31rope rows are mapped straight through (no permute) - the graph rotates consecutive pairs. 32 33DSA is supported: indexer weights are exported for the layers marked "full" in indexer_types. 34"shared" layers reuse the top-k of the last preceding full layer at inference time, so they 35carry no indexer weights. 36 37MTP (num_nextn_predict_layers) is dropped, so the GGUF cannot be used for speculative 38decoding. The reference only runs the MTP layers while training or while speculating, so they 39cannot change single-token logits. 40""" 41 42model_arch = gguf .MODEL_ARCH .HY_V4 43 44merge_expert = False 45 46# tensors a "full" indexer layer must carry 47INDEXER_SUFFIXES = frozenset ({ 48"self_attn.indexer.wq_b.weight" , 49"self_attn.indexer.wk.weight" , 50"self_attn.indexer.k_norm.weight" , 51"self_attn.indexer.k_norm.bias" , 52"self_attn.indexer.weights_proj.weight" , 53 }) 54 55@classmethod 56def filter_tensors (cls ,item ): 57# drop MTP here, not in modify_tensors, so the weights are never read 58if item [0 ].startswith ("model.mtp_layers." ): 59return None 60return super ().filter_tensors (item ) 61 62def _check_indexer_hparams (self ): 63for key in ("index_n_heads" ,"index_head_dim" ,"index_topk" ): 64if key not in self .hparams : 65raise ValueError (f"HY_V4 has DSA layers but no { key } " ) 66 67def indexer_is_full (self )-> list [bool ]| None : 68"""Per-layer indexer ownership, or None when the checkpoint has no DSA. 69 70indexer_types entries are "full" (owns an indexer) or "shared" (reuses the preceding 71full layer's top-k). Missing indexer_types with sparse layers means every sparse layer 72owns one. 73""" 74hparams = self .hparams 75n_layer = hparams ["num_hidden_layers" ] 76indexer_types = hparams .get ("indexer_types" ) 77 78# the reference drives DSA off indexer_types alone; layer_types is only a fallback for 79# checkpoints predating it (it was renamed to deepseek_sparse_attention upstream) 80if indexer_types is None : 81layer_types = hparams .get ("layer_types" )or [] 82sparse = {"sparse_attention" ,"deepseek_sparse_attention" } 83if not any (t in sparse for t in layer_types ): 84return None 85if len (layer_types )< n_layer : 86raise ValueError (f"HY_V4 layer_types has { len ( layer_types ) } entries, need { n_layer } " ) 87self ._check_indexer_hparams () 88return [t in sparse for t in layer_types [:n_layer ]] 89 90self ._check_indexer_hparams () 91 92if len (indexer_types )< n_layer : 93raise ValueError (f"HY_V4 indexer_types has { len ( indexer_types ) } entries, need { n_layer } " ) 94unknown = {t for t in indexer_types [:n_layer ]}- {"full" ,"shared" } 95if unknown : 96raise ValueError (f"HY_V4 unknown indexer_types values: { sorted ( unknown ) } " ) 97is_full = [t == "full" for t in indexer_types [:n_layer ]] 98if is_full and not is_full [0 ]: 99raise ValueError ("HY_V4 layer 0 must be indexer_types 'full' (nothing precedes it to share)" ) 100return is_full 101 102def set_gguf_parameters (self ): 103hparams = self .hparams 104 105# HY4 has n_group == topk_group == 1 (no group routing). Drop the keys so the base does 106# not emit expert_group_count/used; llama.cpp then takes the ungrouped MoE path. 107if hparams .get ("n_group" )== 1 and hparams .get ("topk_group" )== 1 : 108hparams .pop ("n_group" ,None ) 109hparams .pop ("topk_group" ,None ) 110 111# HY_V4 config expresses dense/sparse layers via mlp_layer_types, but DeepseekV2Model 112# needs first_k_dense_replace. Derive it as the contiguous leading "dense" block 113# (the real config.json also carries first_k_dense_replace; prefer it when present, 114# but assert the two agree so a mismatch fails loudly). 115mlp_types = hparams .get ("mlp_layer_types" ) 116explicit = hparams .get ("first_k_dense_replace" ) 117derived = None 118if mlp_types is not None : 119lead = 0 120for t in mlp_types : 121if t == "dense" : 122lead += 1 123else : 124break 125if any (t == "dense" for t in mlp_types [lead :]): 126raise NotImplementedError ("HY_V4 converter expects a contiguous leading dense block" ) 127derived = lead 128if explicit is not None and derived is not None and explicit != derived : 129raise ValueError ( 130f"HY_V4 first_k_dense_replace ( { explicit } ) disagrees with mlp_layer_types " 131f"leading-dense count ( { derived } )" 132 ) 133if explicit is None : 134if derived is None : 135raise ValueError ("HY_V4 needs first_k_dense_replace or mlp_layer_types to place dense layers" ) 136hparams ["first_k_dense_replace" ]= derived 137 138# reuse DeepseekV2 MLA + MoE metadata (forces num_key_value_heads=1, writes q/kv lora, 139# key/value lengths, expert counts, weights scale/norm, rope dims, etc.) 140super ().set_gguf_parameters () 141 142# HY4 uses DeepSeek-V3 sigmoid routing with e_score_correction_bias. The config has no 143# scoring_func key, so the base does not write a gating func; set it explicitly. 144self .gguf_writer .add_expert_gating_func (gguf .ExpertGatingFuncType .SIGMOID ) 145 146# routed-expert SwiGLU logits clamp (only routed experts; shared/dense are not clamped, 147# so swiglu_clamp_shexp is intentionally not written). 0.0 disables the clamp. 148swiglu_limit = float (hparams .get ("swiglu_limit" ,0.0 )or 0.0 ) 149if swiglu_limit > 0.0 : 150self .gguf_writer .add_swiglu_clamp_exp ([swiglu_limit ]* self .block_count ) 151 152# iHC (independent Hyper-Connections) 153self .gguf_writer .add_hyper_connection_count (hparams ["hc_mult" ]) 154self .gguf_writer .add_hyper_connection_epsilon (hparams ["hc_eps" ]) 155self .gguf_writer .add_hyper_connection_magnitude (hparams ["hc_magnitude" ]) 156 157# is_full is written explicitly; the graph must not infer it from tensor presence 158is_full = self .indexer_is_full () 159if is_full is not None : 160self .gguf_writer .add_indexer_head_count (hparams ["index_n_heads" ]) 161self .gguf_writer .add_indexer_key_length (hparams ["index_head_dim" ]) 162self .gguf_writer .add_indexer_top_k (hparams ["index_topk" ]) 163self .gguf_writer .add_indexer_types (is_full ) 164logger .info ( 165"HY_V4 DSA: %d/%d layers own an indexer (top_k=%d, n_heads=%d, head_dim=%d)" , 166sum (is_full ),len (is_full ),hparams ["index_topk" ], 167hparams ["index_n_heads" ],hparams ["index_head_dim" ], 168 ) 169 170if hparams .get ("num_nextn_predict_layers" ,0 ): 171logger .warning ( 172"HY_V4: dropping %d MTP (nextn) layer(s) - the reference runs them only under " 173"training / speculative decoding. This GGUF cannot be used for speculative decoding." , 174hparams ["num_nextn_predict_layers" ], 175 ) 176 177def prepare_tensors (self ): 178# Hy4-preview for some reason has num_key_value_heads equal to 8, so override it here 179# without this conversion/deepseek.py fails on assert 180self .hparams ["num_key_value_heads" ]= self .hparams ["num_attention_heads" ] 181 182# validate before the base materializes tensors, so a mismatch fails early 183is_full = self .indexer_is_full () 184if is_full is not None : 185present :dict [int ,set [str ]]= {} 186for name in self .model_tensors : 187m = re .match (r"model\.layers\.(\d+)\.(self_attn\.indexer\..+)$" ,name ) 188if m : 189present .setdefault (int (m .group (1 )),set ()).add (m .group (2 )) 190for il ,expect_full in enumerate (is_full ): 191seen = present .get (il ,set ()) 192if expect_full and seen != self .INDEXER_SUFFIXES : 193raise ValueError ( 194f"HY_V4 layer { il } is indexer_types 'full' but is missing indexer tensors: " 195f" { sorted ( self . INDEXER_SUFFIXES - seen ) } " 196 ) 197if not expect_full and seen : 198raise ValueError ( 199f"HY_V4 layer { il } is indexer_types 'shared' but carries indexer tensors: " 200f" { sorted ( seen ) } " 201 ) 202 203super ().prepare_tensors () 204 205def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 206# iHC mixing matrices are 2D .weight tensors that the reference keeps in fp32 207# (_keep_in_fp32_modules_strict). 1D tensors (hc_base/scale, attn_sinks, 208# e_score_correction_bias) and the router (FFN_GATE_INP) are already forced F32 by the 209# base rules. Force the HC *_fn matrices here. 210if new_name .endswith (("hc_attn_fn.weight" ,"hc_ffn_fn.weight" ,"output_hc_fn.weight" )): 211return gguf .GGMLQuantizationType .F32 212# indexer k_norm is fp32 in the reference; the base rules already cover 213# *_norm.weight and INDEXER_PROJ, but not this bias 214if self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .INDEXER_K_NORM ,bid ,suffix = ".bias" ): 215return gguf .GGMLQuantizationType .F32 216# enable_lm_head_fp32: mirror the reference fp32 LM-head matmul by keeping output F32. 217if new_name == "output.weight" and self .hparams .get ("enable_lm_head_fp32" ,False ): 218return gguf .GGMLQuantizationType .F32 219return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 220 221def modify_tensors (self ,data_torch :torch .Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,torch .Tensor ]]: 222hparams = self .hparams 223moe_inter = hparams ["moe_intermediate_size" ] 224 225tn = self .format_tensor_name 226 227# fused stacked experts: split gate_up into gate/up 228if name .endswith ("mlp.experts.gate_up_proj" ): 229gate ,up = split_gate_up (data_torch ,moe_inter ) 230yield from super ().modify_tensors (gate ,tn (gguf .MODEL_TENSOR .FFN_GATE_EXP ,bid ),bid ) 231yield from super ().modify_tensors (up ,tn (gguf .MODEL_TENSOR .FFN_UP_EXP ,bid ),bid ) 232return 233 234# add .weight suffixes 235if name .endswith ("mlp.experts.down_proj" )or name .endswith (".self_attn.learnable_sink_param" ): 236name += ".weight" 237 238if re .search (r"\.hc_head\.hc_head_(?:fn|base|scale)$" ,name ): 239name += ".weight" 240 241if re .search (r"\.hc_(?:attn|mlp)_layer\.hc_pre\.hc_(?:fn|base|scale)$" ,name ): 242name += ".weight" 243 244yield from super ().modify_tensors (data_torch ,name ,bid )