char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
6c84c7d5d
char/aliceai-t5-moe
1from __future__import annotations 2 3from typing import Iterable ,cast 4 5import torch 6from torch import Tensor 7 8import gguf 9import numpy as np 10 11from .base import ModelBase 12from .qwen import _LinearAttentionVReorderBase ,_Qwen35MRopeMixin 13from .qwen3vl import Qwen3VLVisionModel 14 15 16@ ModelBase . register ( "Qwen4ExpForConditionalGeneration" , "Qwen4ExpForCausalLM" ) 17@ ModelBase . example ( "Qwen/Qwen3.8-Flash-Next" ) 18class Qwen4ExpTextModel (_Qwen35MRopeMixin ,_LinearAttentionVReorderBase ): 19"""Qwen3.8-Flash-Next. 20 21Shares the Qwen3.5 gated delta net and interleaved mrope, and adds three things: 22hyper-connections in place of every layer norm, QSA sparse attention on the full 23attention layers, and PLE n-gram hash embeddings on a single layer. 24""" 25 26model_arch = gguf .MODEL_ARCH .QWEN4EXP 27 28# the MTP block is a separate draft head; vLLM drops it too 29supports_mtp_export = False 30no_mtp = True 31 32def __init__ (self ,* args ,** kwargs ): 33super ().__init__ (* args ,** kwargs ) 34# only the shard names, so the table itself is never held 35self ._ple_shards :dict [int ,str ]= {} 36self ._ple_row_dim :int | None = None 37 38def _read_hash_constants (self ,suffix :str )-> list [int ]: 39"""Read an int64 PLE constant straight from the checkpoint. 40 41prepare_tensors() casts every non-float dtype to float32 before 42modify_tensors() sees it (base.py), which would silently round these 4345-bit multipliers. Reading the lazy tensor here bypasses that. 44""" 45for name ,gen in self .model_tensors .items (): 46if name .endswith (suffix ): 47t = gen () 48if t .dtype != torch .int64 : 49t = t .to (torch .int64 ) 50return [int (x )for x in t .tolist ()] 51raise ValueError (f"PLE constant { suffix !r } missing from the checkpoint" ) 52 53def set_gguf_parameters (self ): 54super ().set_gguf_parameters () 55hp = self .hparams 56 57self .gguf_writer .add_hyper_connection_count (hp ["hc_count" ]) 58self .gguf_writer .add_hyper_connection_low_rank (hp ["hc_lowrank" ]) 59 60n_layer = hp ["num_hidden_layers" ] 61self .gguf_writer .add_indexer_head_count (hp ["indexer_n_heads" ]) 62self .gguf_writer .add_indexer_key_length (hp ["indexer_head_dim" ]) 63self .gguf_writer .add_indexer_top_k (hp ["indexer_budget" ]) 64ratio = hp ["indexer_compress_ratio" ] 65layer_types = hp ["layer_types" ] 66self .gguf_writer .add_attention_compress_ratios ( 67 [ratio if layer_types [i ]== "full_attention" else 0 for i in range (n_layer )] 68 ) 69 70# ple_layer_ids is 1-based in the HF config; empty means no n-gram table, 71# so emit no PLE keys rather than optional ones 72ple_layers = [i - 1 for i in hp ["ple_layer_ids" ]] 73if not ple_layers : 74return 75self .gguf_writer .add_ple_layers (ple_layers ) 76self .gguf_writer .add_ple_ngram_size (hp ["ngram_size" ]) 77self .gguf_writer .add_ple_heads_per_ngram (hp ["heads_per_ngram" ]) 78self .gguf_writer .add_ple_conv_kernel (hp ["ple_conv_kernel_size" ]) 79self .gguf_writer .add_ple_eos_token_id (self ._eos_token_id ()) 80# an image is decoded as an embeddings-only batch, so the graph has no placeholder 81# ids to hash; carry the id and let it stand in for those positions 82_img = self ._image_token_id () 83if _img is not None : 84self .gguf_writer .add_ple_image_token_id (int (_img )) 85if self ._ple_row_dim is not None : 86self .gguf_writer .add_embedding_length_per_layer_input (self ._ple_row_dim ) 87 88self .gguf_writer .add_ple_layer_multipliers ( 89self ._read_hash_constants ("ple_embedding.layer_multipliers" )) 90self .gguf_writer .add_ple_head_offsets ( 91self ._read_hash_constants ("ple_embedding.ngram_heads_offsets" )) 92self .gguf_writer .add_ple_head_vocab_sizes ( 93self ._read_hash_constants ("ple_embedding.ngram_heads_vocab_sizes" )) 94 95def _image_token_id (self )-> int | None : 96img = self .hparams .get ("image_token_id" ) 97return None if img is None else int (img ) 98 99def _eos_token_id (self )-> int : 100eos = self .hparams .get ("eos_token_id" ) 101if isinstance (eos ,list ): 102# the PLE hash resets n-grams on the primary EOS 103return int (eos [- 1 ]) 104if eos is None : 105raise ValueError ("eos_token_id is required: the PLE hash resets its n-grams on it" ) 106return int (eos ) 107 108def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 109# int64 hash constants must stay exact; 1-D tensors force F32, so use KV 110if name .endswith ("ple_embedding.layer_multipliers" ): 111self ._ple_multipliers = [int (x )for x in data_torch .tolist ()] 112return [] 113if name .endswith ("ple_embedding.ngram_heads_offsets" ): 114self ._ple_head_offsets = [int (x )for x in data_torch .tolist ()] 115return [] 116if name .endswith ("ple_embedding.ngram_heads_vocab_sizes" ): 117self ._ple_head_vocab_sizes = [int (x )for x in data_torch .tolist ()] 118return [] 119 120if ".ngram_embedding.shard_" in name : 121return self ._place_ple_shard (data_torch ,name ) 122 123# one projection feeds indexer q and k; split it, as minimax-m3 does 124if ".indexer.index_qk_proj.weight" in name : 125n_q = self .hparams ["indexer_n_heads" ]* self .hparams ["indexer_head_dim" ] 126q = data_torch [:n_q ] 127k = data_torch [n_q :] 128return [ 129 (self .format_tensor_name (gguf .MODEL_TENSOR .INDEXER_Q_PROJ ,bid ,".weight" ),q ), 130 (self .format_tensor_name (gguf .MODEL_TENSOR .INDEXER_K_PROJ ,bid ,".weight" ),k ), 131 ] 132 133# Gemma zero-centred gammas the inherited norm.weight rule misses 134if name .endswith ((".ple.norm_key.weight" ,".ple.norm_query.weight" ,".ple.norm_conv.weight" , 135".indexer.q_layernorm.weight" ,".indexer.k_layernorm.weight" )): 136return [(self .map_tensor_name (name ),data_torch + 1 )] 137 138if name .endswith (".ple.conv1d.weight" ): 139return [(self .map_tensor_name (name ),data_torch .squeeze ())] 140 141return super ().modify_tensors (data_torch ,name ,bid ) 142 143# the shards concatenate into a tensor of well over 100 GB 144# use LazyChunkedTensor here, a single shard resident at a time 145def _place_ple_shard (self ,data_torch :Tensor ,name :str )-> Iterable [tuple [str ,Tensor ]]: 146 147idx = int (name .rpartition (".shard_" )[2 ].partition ("." )[0 ]) 148n_parts = self .hparams ["split_ngram_parts" ] 149 150self ._ple_shards [idx ]= name 151self ._ple_row_dim = int (data_torch .shape [- 1 ]) 152 153if len (self ._ple_shards )< n_parts : 154return [] 155 156# the checkpoint may yield the shards in any order, the row order is by index 157shards = [self ._ple_shards [i ]for i in sorted (self ._ple_shards )] 158rows = 0 159for shard in shards : 160shape = self .model_tensors [shard ]().shape 161if int (shape [- 1 ])!= self ._ple_row_dim : 162raise ValueError ( 163f"PLE shard { shard } has row dim { int ( shape [ - 1 ]) } , expected { self . _ple_row_dim } " ) 164rows += int (shape [0 ]) 165 166table = gguf .LazyChunkedTensor ( 167 [self ._load_ple_shard (shard )for shard in shards ], 168shape = (rows ,self ._ple_row_dim ), 169dtype = np .float32 , 170 ) 171gguf_name = gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .PER_LAYER_TOKEN_EMBD ] 172return [(gguf_name + ".weight" ,cast (Tensor ,table ))] 173 174def _load_ple_shard (self ,name :str ): 175def load ()-> np .ndarray : 176from .base import LazyTorchTensor 177 178# a fresh lazy tensor every call, or to_eager() memoizes every shard 179eager = LazyTorchTensor .to_eager (self .model_tensors [name ]()) 180return eager .to (torch .float32 ).contiguous ().numpy () 181return load 182 183def prepare_tensors (self ): 184super ().prepare_tensors () 185n_parts = self .hparams .get ("split_ngram_parts" ,0 ) 186if self ._ple_shards and len (self ._ple_shards )!= n_parts : 187raise ValueError ( 188f"got { len ( self . _ple_shards ) } PLE embedding shards, expected { n_parts } " 189 ) 190 191 192@ ModelBase . register ( "Qwen4ExpForConditionalGeneration" ) 193@ ModelBase . example ( "Qwen/Qwen3.8-Flash-Next" ) 194class Qwen4ExpVisionModel (Qwen3VLVisionModel ): 195"""The vision tower is an unmodified Qwen3-VL ViT."""