char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
fa88ae936
char/aliceai-t5-moe
1from __future__import annotations 2 3from typing import Iterable ,TYPE_CHECKING 4 5import torch 6 7if TYPE_CHECKING : 8from torch import Tensor 9 10from .base import ModelBase ,TextModel ,gguf ,logger 11 12from .qwen import QwenModel 13 14 15@ ModelBase . register ( "KimiLinearModel" , "KimiLinearForCausalLM" ) 16@ ModelBase . example ( "moonshotai/Kimi-Linear-48B-A3B-Instruct" ) 17class KimiLinearModel (TextModel ): 18"""Kimi-Linear model with hybrid MLA+KDA architecture""" 19model_arch = gguf .MODEL_ARCH .KIMI_LINEAR 20 21_experts :list [dict [str ,Tensor ]]| None = None 22 23def set_vocab (self ): 24try : 25self ._set_vocab_gpt2 () 26return 27except Exception : 28pass 29 30from transformers import AutoTokenizer 31tokenizer = AutoTokenizer .from_pretrained (self .dir_model ,trust_remote_code = True ) 32tokpre = self .get_vocab_base_pre (tokenizer ) 33 34if tokpre == "kimi-k2" : 35# Build merges list using the approach similar to HunYuanMoE 36merges = [] 37vocab = {} 38mergeable_ranks = tokenizer .model ._mergeable_ranks # ty: ignore[unresolved-attribute] 39for token ,rank in mergeable_ranks .items (): 40vocab [QwenModel .token_bytes_to_string (token )]= rank 41if len (token )== 1 : 42continue 43merged = QwenModel .bpe (mergeable_ranks ,token ,max_rank = rank ) 44if len (merged )== 2 : 45merges .append (' ' .join (map (QwenModel .token_bytes_to_string ,merged ))) 46# Build token list 47vocab_size = self .hparams ["vocab_size" ] 48special_tokens = tokenizer .special_tokens # ty: ignore[unresolved-attribute] 49reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in {** vocab ,** special_tokens }.items ()} 50tokens :list [str ]= [] 51toktypes :list [int ]= [] 52 53for i in range (vocab_size ): 54if i not in reverse_vocab : 55tokens .append (f"[PAD { i } ]" ) 56toktypes .append (gguf .TokenType .UNUSED ) 57else : 58token = reverse_vocab [i ] 59tokens .append (token ) 60if i in special_tokens .values (): 61toktypes .append (gguf .TokenType .CONTROL ) 62else : 63toktypes .append (gguf .TokenType .NORMAL ) 64 65self .gguf_writer .add_tokenizer_model ("gpt2" ) 66self .gguf_writer .add_tokenizer_pre (tokpre ) 67self .gguf_writer .add_token_list (tokens ) 68self .gguf_writer .add_token_types (toktypes ) 69self .gguf_writer .add_token_merges (merges ) 70 71special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = False ) 72special_vocab .add_to_gguf (self .gguf_writer ) 73# override eos id in config.json with tiktoken eos id 74self .gguf_writer .add_eos_token_id (tokenizer .eos_id )# ty: ignore[unresolved-attribute] 75else : 76raise NotImplementedError (f"Deepseek pre-tokenizer { tokpre !r } is not supported yet!" ) 77 78def set_gguf_parameters (self ): 79# note: To enable MLA KV cache, attention needs to be converted into MQA (ie: GQA with 1 group) 80self .hparams ["num_key_value_heads" ]= 1 81 82super ().set_gguf_parameters () 83self .gguf_writer .add_vocab_size (self .hparams ["vocab_size" ]) 84 85# KDA & MLA params 86# Get ssm_d_conv from linear_attn_config.short_conv_kernel_size or ssm_d_conv 87linear_attn_config = self .hparams ["linear_attn_config" ] 88# n_head == 0 for KDA layers, n_head > 0 for MLA layers 89# full_attention_layers list will be used to distinguish layer type 90_num_kv_heads = list () 91_full_attn_layers = linear_attn_config ["full_attn_layers" ] 92for il in range (self .hparams ["num_hidden_layers" ]): 93if il + 1 in _full_attn_layers : 94_num_kv_heads .append (self .hparams ["num_key_value_heads" ]) 95else : 96_num_kv_heads .append (0 ) 97assert len (_num_kv_heads )== self .hparams ["num_hidden_layers" ] 98self .gguf_writer .add_head_count_kv (_num_kv_heads ) 99 100if (ssm_d_conv := linear_attn_config .get ("short_conv_kernel_size" ))is not None : 101self .gguf_writer .add_ssm_conv_kernel (ssm_d_conv ) 102if (kda_head_dim := linear_attn_config .get ("head_dim" ))is not None : 103self .gguf_writer .add_kda_head_dim (kda_head_dim ) 104 105# MLA params - use add_* methods that handle arch substitution 106# Support both HuggingFace naming (q_lora_rank, kv_lora_rank) and internal naming (n_lora_q, n_lora_kv) 107if (q_lora_rank := self .find_hparam (["q_lora_rank" ,"n_lora_q" ],optional = True ))is not None : 108self .gguf_writer .add_q_lora_rank (q_lora_rank ) 109# To enable MLA KV cache, MLA needs to be converted into MQA with larger heads, then decompresses to MHA 110kv_lora_rank = self .find_hparam (["kv_lora_rank" ,"n_lora_kv" ],optional = False ) 111self .gguf_writer .add_kv_lora_rank (kv_lora_rank ) 112 113# MLA head dimensions 114# Support HuggingFace naming: qk_nope_head_dim, qk_rope_head_dim, v_head_dim 115qk_nope_head_dim = self .hparams .get ("qk_nope_head_dim" ) 116# Rotation - use qk_rope_head_dim for Kimi 117qk_rope_head_dim = self .find_hparam (["qk_rope_head_dim" ,"n_rot" ],optional = False ) 118self .gguf_writer .add_rope_dimension_count (qk_rope_head_dim ) 119self .gguf_writer .add_key_length (kv_lora_rank + qk_rope_head_dim ) 120v_head_dim = self .hparams .get ("v_head_dim" ) 121 122# Calculate n_embd_head_k_mla = qk_nope_head_dim + qk_rope_head_dim 123if (n_embd_head_k_mla := self .find_hparam (["n_embd_head_k_mla" ],optional = True ))is not None : 124self .gguf_writer .add_key_length_mla (n_embd_head_k_mla ) 125elif qk_nope_head_dim is not None : 126n_embd_head_k_mla = qk_nope_head_dim + qk_rope_head_dim 127self .gguf_writer .add_key_length_mla (n_embd_head_k_mla ) 128 129# n_embd_head_v_mla = v_head_dim 130if (n_embd_head_v_mla := self .hparams .get ("n_embd_head_v_mla" ))is not None : 131self .gguf_writer .add_value_length_mla (n_embd_head_v_mla ) 132elif v_head_dim is not None : 133self .gguf_writer .add_value_length_mla (v_head_dim ) 134 135# moe_intermediate_size (1024 for Kimi) 136self .gguf_writer .add_expert_feed_forward_length (self .hparams ["moe_intermediate_size" ]) 137# num_shared_experts (1 for Kimi) 138self .gguf_writer .add_expert_shared_count (self .hparams ["num_shared_experts" ]) 139# first_k_dense_replace (1 for Kimi - first layer uses dense MLP) 140self .gguf_writer .add_leading_dense_block_count (self .hparams ["first_k_dense_replace" ]) 141# Routed scaling factor (expert_weights_scale = 2.446 for Kimi) 142self .gguf_writer .add_expert_weights_scale (self .hparams ["routed_scaling_factor" ]) 143 144def prepare_tensors (self ): 145super ().prepare_tensors () 146if self ._experts is not None : 147experts = [k for d in self ._experts for k in d .keys ()] 148if len (experts )> 0 : 149raise ValueError (f"Unprocessed experts: { experts } " ) 150 151def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 152logger .info (f"Processing { name } : shape before = { tuple ( data_torch . shape ) } " ) 153 154# Handle KDA conv1d weights 155# HuggingFace/vLLM stores as [d_inner, d_conv] (2D), memory layout: conv_step changes fastest 156# llama.cpp expects ggml ne = [d_conv, 1, d_inner, 1], memory layout: ne[0]=d_conv changes fastest 157# GGUF reverses numpy shape when writing, so numpy (1, d_inner, 1, d_conv) -> ggml ne = [d_conv, 1, d_inner, 1] 158# Memory layouts match: both have conv_step (d_conv) changing fastest 159if name .endswith ((".q_conv1d.weight" ,".k_conv1d.weight" ,".v_conv1d.weight" )): 160# HF shape: [d_inner, d_conv] e.g. [4096, 4] 161# Target numpy shape: (1, d_inner, 1, d_conv) -> ggml ne = [d_conv, 1, d_inner, 1] 162if data_torch .ndim == 2 : 163d_inner ,d_conv = data_torch .shape 164# Reshape to (1, d_inner, 1, d_conv) - memory layout preserved (d_conv fastest) 165data_torch = data_torch .reshape (1 ,d_inner ,1 ,d_conv ) 166logger .info (f"Reshaped conv1d weight { name } : [d_inner= { d_inner } , d_conv= { d_conv } ] -> numpy { tuple ( data_torch . shape ) } -> ggml ne=[ { d_conv } , 1, { d_inner } , 1]" ) 167elif data_torch .ndim == 3 : 168# Already 3D [d_inner, 1, d_conv] from unsqueeze 169d_inner ,_ ,d_conv = data_torch .shape 170data_torch = data_torch .reshape (1 ,d_inner ,1 ,d_conv ) 171logger .info (f"Reshaped conv1d weight { name } : [d_inner= { d_inner } , 1, d_conv= { d_conv } ] -> numpy { tuple ( data_torch . shape ) } -> ggml ne=[ { d_conv } , 1, { d_inner } , 1]" ) 172 173# Handle A_log: iHF stores as [1, 1, num_heads, 1] 174# llama.cpp expects ggml ne = [1, num_heads, 1, 1] 175# GGUF reverses numpy shape: numpy (1, 1, num_heads, 1) -> ggml ne = [1, num_heads, 1, 1] 176if name .endswith (".A_log" ): 177data_torch = - torch .exp (data_torch ) 178if name .endswith (".dt_bias" ): 179name = name .rpartition (".dt_bias" )[0 ]+ ".dt_proj.bias" 180logger .info ("Changed dt_bias to dt_proj.bias" ) 181 182# process the experts separately 183if name .find ("block_sparse_moe.experts" )!= - 1 : 184n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ]) 185assert bid is not None 186 187if self ._experts is None : 188self ._experts = [{}for _ in range (self .block_count )] 189 190self ._experts [bid ][name ]= data_torch 191 192if len (self ._experts [bid ])>= n_experts * 3 : 193# merge the experts into a single 3d tensor 194# w1: gate, w2: down, w3: up 195for wid ,tname in [("w1" ,gguf .MODEL_TENSOR .FFN_GATE_EXP ), 196 ("w2" ,gguf .MODEL_TENSOR .FFN_DOWN_EXP ), 197 ("w3" ,gguf .MODEL_TENSOR .FFN_UP_EXP )]: 198datas :list [Tensor ]= [] 199for xid in range (n_experts ): 200ename = f"model.layers. { bid } .block_sparse_moe.experts. { xid } . { wid } .weight" 201datas .append (self ._experts [bid ][ename ]) 202del self ._experts [bid ][ename ] 203data_torch = torch .stack (datas ,dim = 0 ) 204new_name = self .format_tensor_name (tname ,bid ) 205yield from super ().modify_tensors (data_torch ,new_name ,bid ) 206return 207 208# note: MLA with the absorption optimization, needs these two split and k_b_proj transposed 209if name .endswith ("kv_b_proj.weight" ): 210name_kb = name .replace ("kv_b_proj" ,"k_b_proj" ) 211name_vb = name .replace ("kv_b_proj" ,"v_b_proj" ) 212n_head_kv = self .hparams ["num_key_value_heads" ] 213v_head_dim = self .find_hparam (["n_embd_head_v_mla" ,"v_head_dim" ],optional = False ) 214qk_nope_head_dim = self .hparams ["qk_nope_head_dim" ] 215logger .info ("Split kv_b n_head_kv %d\n" % n_head_kv ) 216assert data_torch .shape [0 ]== n_head_kv * (v_head_dim + qk_nope_head_dim ) 217kv_b = data_torch .view (n_head_kv ,v_head_dim + qk_nope_head_dim ,data_torch .shape [- 1 ]) 218k_b ,v_b = torch .split (kv_b , [qk_nope_head_dim ,v_head_dim ],dim = 1 ) 219k_b = k_b .transpose (1 ,2 ) 220yield from super ().modify_tensors (k_b ,name_kb ,bid ) 221yield from super ().modify_tensors (v_b ,name_vb ,bid ) 222return 223 224yield from super ().modify_tensors (data_torch ,name ,bid )