char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
465e49b9c
char/aliceai-t5-moe
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4from __future__import annotations 5 6import ast 7import logging 8import contextlib 9import json 10import os 11import re 12import sys 13from enum import IntEnum 14from pathlib import Path 15from hashlib import sha256 16from typing import TYPE_CHECKING ,Any ,Callable ,ContextManager ,Iterable ,Iterator ,Literal ,Sequence ,TypeVar ,cast 17from itertools import chain 18from transformers import AutoConfig 19 20import numpy as np 21import torch 22 23if TYPE_CHECKING : 24from torch import Tensor 25 26if 'NO_LOCAL_GGUF' not in os .environ : 27sys .path .insert (1 ,str (Path (__file__ ).parent .parent / 'gguf-py' )) 28import gguf 29from gguf .vocab import MistralTokenizerType ,MistralVocab 30 31try : 32from mistral_common .tokens .tokenizers .base import TokenizerVersion # type: ignore[import-not-found, ty:unresolved-import] 33from mistral_common .tokens .tokenizers .multimodal import DATASET_MEAN as _MISTRAL_COMMON_DATASET_MEAN ,DATASET_STD as _MISTRAL_COMMON_DATASET_STD # type: ignore[import-not-found, ty:unresolved-import] 34from mistral_common .tokens .tokenizers .tekken import Tekkenizer # type: ignore[import-not-found, ty:unresolved-import] 35from mistral_common .tokens .tokenizers .sentencepiece import (# type: ignore[import-not-found, ty:unresolved-import] 36SentencePieceTokenizer , 37 ) 38 39_mistral_common_installed = True 40_mistral_import_error_msg = "" 41except ImportError : 42_MISTRAL_COMMON_DATASET_MEAN = (0.48145466 ,0.4578275 ,0.40821073 ) 43_MISTRAL_COMMON_DATASET_STD = (0.26862954 ,0.26130258 ,0.27577711 ) 44 45_mistral_common_installed = False 46TokenizerVersion :Any = None 47Tekkenizer :Any = None 48SentencePieceTokenizer :Any = None 49_mistral_import_error_msg = ( 50"Mistral format requires `mistral-common` to be installed. Please run " 51"`pip install mistral-common[image,audio]` to install it." 52 ) 53 54 55logger = logging .getLogger ("hf-to-gguf" ) 56 57 58AnyModel = TypeVar ("AnyModel" ,bound = "type[ModelBase]" ) 59 60 61# for checkpoints that ship no config.json, we will try to provide a synthetic one 62HparamsMatcher = Callable [[Path ],bool ] 63HparamsLoader = Callable [[Path ],dict [str ,Any ]] 64 65 66class SentencePieceTokenTypes (IntEnum ): 67NORMAL = 1 68UNKNOWN = 2 69CONTROL = 3 70USER_DEFINED = 4 71UNUSED = 5 72BYTE = 6 73 74 75class ModelType (IntEnum ): 76TEXT = 1 77MMPROJ = 2 78 79 80class ModelBase : 81_model_classes :dict [ModelType ,dict [str ,type [ModelBase ]]]= { 82ModelType .TEXT : {}, 83ModelType .MMPROJ : {}, 84 } 85_hparams_loaders :list [tuple [HparamsMatcher ,HparamsLoader ]]= [] 86 87dir_model :Path 88ftype :gguf .LlamaFileType 89fname_out :Path 90is_big_endian :bool 91endianess :gguf .GGUFEndian 92use_temp_file :bool 93lazy :bool 94dry_run :bool 95hparams :dict [str ,Any ] 96model_tensors :dict [str ,Callable [[],Tensor ]] 97gguf_writer :gguf .GGUFWriter 98model_name :str | None 99metadata_override :Path | None 100metadata :gguf .Metadata 101dir_model_card :Path 102remote_hf_model_id :str | None 103target_model_dir :Path | None 104 105# subclasses should define this! 106model_arch :gguf .MODEL_ARCH 107 108# subclasses should initialize this! 109block_count :int 110tensor_map :gguf .TensorNameMap 111 112# Mistral format specifics 113is_mistral_format :bool = False 114disable_mistral_community_chat_template :bool = False 115sentence_transformers_dense_modules :bool = False 116 117# MTP (multi-token prediction) export modes; set by main() before instantiation. 118# Architectures that implement the filtering/export behavior opt in by 119# setting supports_mtp_export = True on their model class or a mixin. 120supports_mtp_export :bool = False 121mtp_only :bool = False 122no_mtp :bool = False 123 124def __init__ (self ,dir_model :Path ,ftype :gguf .LlamaFileType ,fname_out :Path ,* ,is_big_endian :bool = False , 125use_temp_file :bool = False ,eager :bool = False , 126metadata_override :Path | None = None ,model_name :str | None = None , 127split_max_tensors :int = 0 ,split_max_size :int = 0 ,dry_run :bool = False , 128small_first_shard :bool = False ,hparams :dict [str ,Any ]| None = None ,remote_hf_model_id :str | None = None , 129disable_mistral_community_chat_template :bool = False , 130sentence_transformers_dense_modules :bool = False , 131target_model_dir :Path | None = None , 132fuse_gate_up_exps :bool = False , 133fp8_as_q8 :bool = False , 134fuse_qkv :bool = False ): 135if type (self )is ModelBase or \ 136type (self )is TextModel or \ 137type (self )is MmprojModel : 138raise TypeError (f" { type ( self ). __name__ !r } should not be directly instantiated" ) 139 140if self .is_mistral_format and not _mistral_common_installed : 141raise ImportError (_mistral_import_error_msg ) 142 143self .dir_model = dir_model 144self .ftype = ftype 145self .fname_out = fname_out 146self .is_big_endian = is_big_endian 147self .endianess = gguf .GGUFEndian .BIG if is_big_endian else gguf .GGUFEndian .LITTLE 148self .use_temp_file = use_temp_file 149self .lazy = not eager or (remote_hf_model_id is not None ) 150self .dry_run = dry_run 151self .remote_hf_model_id = remote_hf_model_id 152self .sentence_transformers_dense_modules = sentence_transformers_dense_modules 153self .target_model_dir = target_model_dir 154self .fuse_gate_up_exps = fuse_gate_up_exps 155self ._gate_exp_buffer :dict [int ,Tensor ]= {} 156self ._up_exp_buffer :dict [int ,Tensor ]= {} 157self .fuse_qkv = fuse_qkv 158self ._q_buffer :dict [int ,Tensor ]= {} 159self ._k_buffer :dict [int ,Tensor ]= {} 160self ._v_buffer :dict [int ,Tensor ]= {} 161self ._q_bias_buffer :dict [int ,Tensor ]= {} 162self ._k_bias_buffer :dict [int ,Tensor ]= {} 163self ._v_bias_buffer :dict [int ,Tensor ]= {} 164self ._fusable_qkv_weight_layers :set [int ]= set () 165self ._fusable_qkv_bias_layers :set [int ]= set () 166self .hparams = ModelBase .load_hparams (self .dir_model ,self .is_mistral_format )if hparams is None else hparams 167self .model_tensors = self .index_tensors (remote_hf_model_id = remote_hf_model_id ) 168self .metadata_override = metadata_override 169self .model_name = model_name 170self .dir_model_card = dir_model # overridden in convert_lora_to_gguf.py 171self ._is_nvfp4 = False 172self ._is_mxfp4 = False 173self ._fp8_as_q8 = fp8_as_q8 174self ._fp8_dequantized :set [str ]= set () 175 176# Apply heuristics to figure out typical tensor encoding based on first tensor's dtype 177# NOTE: can't use field "torch_dtype" in config.json, because some finetunes lie. 178if self .ftype == gguf .LlamaFileType .GUESSED : 179for _ ,tensor in self .get_tensors (): 180if tensor .dim ()< 2 : 181continue 182 183if tensor .dtype == torch .bfloat16 : 184self .ftype = gguf .LlamaFileType .MOSTLY_BF16 185logger .info ("heuristics detected bfloat16 tensor dtype, setting --outtype bf16" ) 186break 187elif tensor .dtype == torch .float16 : 188self .ftype = gguf .LlamaFileType .MOSTLY_F16 189logger .info ("heuristics detected float16 tensor dtype, setting --outtype f16" ) 190break 191else : 192self .ftype = gguf .LlamaFileType .MOSTLY_F16 193logger .info ("heuristics unable to detect tensor dtype, defaulting to --outtype f16" ) 194 195# Configure GGUF Writer 196self .gguf_writer = gguf .GGUFWriter (path = None ,arch = gguf .MODEL_ARCH_NAMES [self .model_arch ],endianess = self .endianess ,use_temp_file = self .use_temp_file , 197split_max_tensors = split_max_tensors ,split_max_size = split_max_size ,dry_run = dry_run ,small_first_shard = small_first_shard ) 198 199# Mistral specific 200self .disable_mistral_community_chat_template = disable_mistral_community_chat_template 201 202@classmethod 203def add_prefix_to_filename (cls ,path :Path ,prefix :str )-> Path : 204stem ,suffix = path .stem ,path .suffix 205new_name = f" { prefix } { stem } { suffix } " 206return path .with_name (new_name ) 207 208def find_hparam (self ,keys :Iterable [str ],optional :bool = False )-> Any : 209key = next ((k for k in keys if k in self .hparams ),None ) 210if key is not None : 211return self .hparams [key ] 212if optional : 213return None 214raise KeyError (f"could not find any of: { keys } " ) 215 216def index_tensors (self ,remote_hf_model_id :str | None = None )-> dict [str ,Callable [[],Tensor ]]: 217tensors :dict [str ,Callable [[],Tensor ]]= {} 218 219if remote_hf_model_id is not None : 220is_safetensors = True 221 222logger .info (f"Using remote model with HuggingFace id: { remote_hf_model_id } " ) 223remote_tensors = gguf .utility .SafetensorRemote .get_list_tensors_hf_model (remote_hf_model_id ) 224for name ,remote_tensor in remote_tensors .items (): 225data_gen = lambda r = remote_tensor :LazyTorchTensor .from_remote_tensor (r )# noqa: E731 226if titem := self .filter_tensors ((name ,data_gen )): 227tname ,tgen = titem 228tensors [tname ]= tgen 229 230return tensors 231 232prefix = "model" if not self .is_mistral_format else "consolidated" 233part_names :list [str ]= ModelBase .get_model_part_names (self .dir_model ,prefix ,".safetensors" ) 234is_safetensors :bool = len (part_names )> 0 235if not is_safetensors : 236part_names = ModelBase .get_model_part_names (self .dir_model ,"pytorch_model" ,".bin" ) 237 238tensor_names_from_index :set [str ]= set () 239tensor_names_from_parts :set [str ]= set () 240 241if not self .is_mistral_format : 242index_name = "model.safetensors" if is_safetensors else "pytorch_model.bin" 243index_name += ".index.json" 244index_file = self .dir_model / index_name 245 246if index_file .is_file (): 247logger .info (f"gguf: loading model weight map from ' { index_name } '" ) 248with open (index_file ,"r" ,encoding = "utf-8" )as f : 249index :dict [str ,Any ]= json .load (f ) 250weight_map = index .get ("weight_map" ) 251if weight_map is None or not isinstance (weight_map ,dict ): 252raise ValueError (f"Can't load 'weight_map' from { index_name !r } " ) 253tensor_names_from_index .update (weight_map .keys ()) 254part_dict :dict [str ,None ]= dict .fromkeys (weight_map .values (),None )# ty: ignore[invalid-assignment] 255part_names = sorted (part_dict .keys ()) 256else : 257weight_map = {} 258else : 259weight_map = {} 260 261for part_name in part_names : 262logger .info (f"gguf: indexing model part ' { part_name } '" ) 263ctx :ContextManager [Any ] 264if is_safetensors : 265ctx = cast (ContextManager [Any ],gguf .utility .SafetensorsLocal (self .dir_model / part_name )) 266else : 267ctx = contextlib .nullcontext (torch .load (str (self .dir_model / part_name ),map_location = "cpu" ,mmap = True ,weights_only = True )) 268 269with ctx as model_part : 270assert model_part is not None 271 272for name in model_part .keys (): 273tensor_names_from_parts .add (name ) 274if is_safetensors : 275data :gguf .utility .LocalTensor = model_part [name ] 276if self .lazy : 277data_gen = lambda data = data :LazyTorchTensor .from_local_tensor (data )# noqa: E731 278else : 279dtype = LazyTorchTensor ._dtype_str_map [data .dtype ] 280data_gen = lambda data = data ,dtype = dtype :torch .from_numpy (data .mmap_bytes ()).view (dtype ).reshape (data .shape )# noqa: E731 281else : 282data_torch :Tensor = model_part [name ] 283if self .lazy : 284data_gen = lambda data = data_torch :LazyTorchTensor .from_eager (data )# noqa: E731 285else : 286data_gen = lambda data = data_torch :data # noqa: E731 287if titem := self .filter_tensors ((name ,data_gen )): 288tname ,tgen = titem 289tensors [tname ]= tgen 290 291# verify tensor name presence and identify potentially missing files 292if len (tensor_names_from_index )> 0 : 293if len (tensor_names_from_parts .symmetric_difference (tensor_names_from_index ))> 0 : 294missing = sorted (tensor_names_from_index .difference (tensor_names_from_parts )) 295extra = sorted (tensor_names_from_parts .difference (tensor_names_from_index )) 296missing_files = sorted (set (weight_map [n ]for n in missing if n in weight_map )) 297if len (extra )== 0 and len (missing_files )> 0 : 298raise ValueError (f"Missing or incomplete model files: { missing_files } \n" 299f"Missing tensors: { missing } " ) 300else : 301raise ValueError ("Mismatch between weight map and model parts for tensor names:\n" 302f"Missing tensors: { missing } \n" 303f"Extra tensors: { extra } " ) 304 305return tensors 306 307@staticmethod 308def _scale_is_trivial (scale :Tensor )-> bool : 309return scale .numel ()<= 1 and abs (float (scale .float ().sum ())- 1.0 )< 1e-6 310 311def _write_scale_tensor (self ,scale_name :str ,scale :Tensor ): 312if not self ._scale_is_trivial (scale ): 313scale_f32 = scale .float ().numpy ().flatten () 314logger .info (f" + { scale_name } (per-tensor scale, shape [ { scale_f32 . size } ])" ) 315self .gguf_writer .add_tensor (scale_name ,scale_f32 ) 316 317def _write_scales_tensor (self ,scale_name :str ,scales :list [float ]): 318if not np .allclose (scales ,1.0 ,atol = 1e-6 ): 319scale_vals = np .array (scales ,dtype = np .float32 ) 320logger .info (f" + { scale_name } (per-expert scale, shape [ { len ( scales ) } ])" ) 321self .gguf_writer .add_tensor (scale_name ,scale_vals ) 322 323def dequant_model (self ): 324# If all quantized tensors were already handled (e.g. pure NVFP4), skip 325if self ._is_nvfp4 and not any (k .endswith ((".weight_scale" ,".weight_scale_inv" ))for k in self .model_tensors ): 326return 327 328tensors_to_remove :list [str ]= [] 329new_tensors :dict [str ,Callable [[],Tensor ]]= {} 330 331if (quant_config := self .hparams .get ("quantization_config" ))and isinstance (quant_config ,dict ): 332quant_method = quant_config .get ("quant_method" ) 333 334def dequant_bitnet (weight :Tensor ,scale :Tensor )-> Tensor : 335weight = weight .view (torch .uint8 ) 336orig_shape = weight .shape 337 338shift = torch .tensor ([0 ,2 ,4 ,6 ],dtype = torch .uint8 ).reshape ((4 ,* (1 for _ in range (len (orig_shape ))))) 339data = weight .unsqueeze (0 ).expand ((4 ,* orig_shape ))>> shift 340data = data & 3 341data = (data .float ()- 1 ).reshape ((orig_shape [0 ]* 4 ,* orig_shape [1 :])) 342 343# The scale is inverted 344return data / scale .float () 345 346def dequant_simple (weight :Tensor ,scale :Tensor ,block_size :Sequence [int ]| None = None )-> Tensor : 347scale = scale .float () 348 349if block_size is not None : 350dim_offset = scale .ndim - len (block_size ) 351for i ,size in enumerate (block_size ): 352scale = scale .repeat_interleave (size ,dim_offset + i ) 353# unpad the scale (e.g. when the tensor size isn't a multiple of the block size) 354scale = scale [tuple (slice (0 ,size )for size in weight .shape )] 355 356# align scale dims to weight for correct broadcasting (e.g. [128] -> [128, 1, 1]) 357while scale .ndim < weight .ndim : 358scale = scale .unsqueeze (- 1 ) 359 360return weight .float ()* scale 361 362# ref: https://github.com/ModelCloud/GPTQModel/blob/037c5c0f6c9e33c500d975b038d02e7ca437546d/gptqmodel/nn_modules/qlinear/__init__.py#L437-L476 363def dequant_gptq (g_idx :Tensor ,qweight :Tensor ,qzeros :Tensor ,scales :Tensor )-> Tensor : 364bits = quant_config ["bits" ] 365assert bits in (2 ,3 ,4 ,8 ) 366assert qweight .dtype == qzeros .dtype 367maxq = (2 ** bits )- 1 368weight = None 369zeros = None 370pack_dtype_bits = qweight .dtype .itemsize * 8 371 372if bits in [2 ,4 ,8 ]: 373pack_factor = pack_dtype_bits // bits 374wf = torch .tensor (list (range (0 ,pack_dtype_bits ,bits )),dtype = torch .int32 ).unsqueeze (0 ) 375if self .lazy : 376wf = LazyTorchTensor .from_eager (wf ) 377 378zeros = torch .bitwise_right_shift ( 379qzeros .unsqueeze (2 ).expand (- 1 ,- 1 ,pack_factor ), 380wf .unsqueeze (0 ) 381 ).to (torch .int16 if bits == 8 else torch .int8 ) 382zeros = torch .bitwise_and (zeros ,maxq ).reshape (scales .shape ) 383 384weight = torch .bitwise_and ( 385torch .bitwise_right_shift ( 386qweight .unsqueeze (1 ).expand (- 1 ,pack_factor ,- 1 ), 387wf .unsqueeze (- 1 ) 388 ).to (torch .int16 if bits == 8 else torch .int8 ), 389maxq 390 ) 391elif bits == 3 : 392raise NotImplementedError ("3-bit gptq dequantization is not yet implemented" ) 393 394assert weight is not None 395assert zeros is not None 396 397weight = weight .reshape (weight .shape [0 ]* weight .shape [1 ],weight .shape [2 ]) 398 399# gptq_v2 doesn't need to offset zeros 400if quant_config .get ("checkpoint_format" ,"gptq" )== "gptq" : 401zeros += 1 402 403return (scales [g_idx ].float ()* (weight - zeros [g_idx ]).float ()).T 404 405def dequant_packed (w :Tensor ,scale :Tensor ,shape_tensor :Tensor ,zero_point :Tensor | None ,num_bits :int ,group_size :int ): 406assert w .dtype == torch .int32 407shape = tuple (shape_tensor .tolist ()) 408assert len (shape )== 2 409mask = (1 << num_bits )- 1 410 411shifts = torch .arange (0 ,32 - (num_bits - 1 ),num_bits ,dtype = torch .int32 ) 412if self .lazy : 413shifts = LazyTorchTensor .from_eager (shifts ) 414 415if zero_point is None : 416offset = 1 << (num_bits - 1 ) 417else : 418assert len (zero_point .shape )== 2 419offset = (zero_point .unsqueeze (1 )>> shifts .reshape (1 ,- 1 ,1 ))& mask 420offset = offset .reshape (- 1 ,zero_point .shape [1 ]) 421# trim padding, and prepare for broadcast 422# NOTE: the zero-point is packed along dim 0 423offset = offset [:shape [0 ], :].unsqueeze (- 1 ) 424 425# extract values 426# NOTE: the weights are packed along dim 1 427unpacked = (w .unsqueeze (- 1 )>> shifts .reshape (1 ,1 ,- 1 ))& mask 428unpacked = unpacked .reshape (shape [0 ],- 1 ) 429 430# trim padding 431unpacked = unpacked [:, :shape [1 ]] 432 433# prepare for broadcast of the scale 434unpacked = unpacked .reshape (shape [0 ], (unpacked .shape [- 1 ]+ group_size - 1 )// group_size ,group_size ) 435unpacked = unpacked - offset 436 437return (unpacked * scale .unsqueeze (- 1 ).float ()).reshape (shape ) 438 439if quant_method == "bitnet" : 440for name in self .model_tensors .keys (): 441if name .endswith (".weight_scale" ): 442weight_name = name .removesuffix ("_scale" ) 443w = self .model_tensors [weight_name ] 444s = self .model_tensors [name ] 445self .model_tensors [weight_name ]= lambda w = w ,s = s :dequant_bitnet (w (),s ()) 446tensors_to_remove .append (name ) 447elif quant_method == "fp8" : 448block_size = quant_config .get ("weight_block_size" ) 449for name in self .model_tensors .keys (): 450if name .endswith ("_scale_inv" ): 451weight_name = name .removesuffix ("_scale_inv" ) 452w = self .model_tensors [weight_name ] 453s = self .model_tensors [name ] 454self .model_tensors [weight_name ]= lambda w = w ,s = s ,bs = block_size :dequant_simple (w (),s (),bs ) 455tensors_to_remove .append (name ) 456if self ._fp8_as_q8 : 457self ._fp8_dequantized .add (weight_name ) 458if name .endswith (".activation_scale" ):# unused 459tensors_to_remove .append (name ) 460if name .endswith ("_activation_scale" ):# Mistral-Small-4-119B-2602, unused 461tensors_to_remove .append (name ) 462# mistral format 463if name .endswith (".qscale_weight" ): 464weight_name = name .removesuffix ("qscale_weight" )+ "weight" 465w = self .model_tensors [weight_name ] 466s = self .model_tensors [name ] 467self .model_tensors [weight_name ]= lambda w = w ,s = s ,bs = block_size :dequant_simple (w (),s (),bs ) 468tensors_to_remove .append (name ) 469if self ._fp8_as_q8 : 470self ._fp8_dequantized .add (weight_name ) 471if name .endswith (".qscale_act" ): 472tensors_to_remove .append (name ) 473elif quant_method == "gptq" : 474for name in self .model_tensors .keys (): 475if name .endswith (".qweight" ): 476base_name = name .removesuffix (".qweight" ) 477g_idx = self .model_tensors [base_name + ".g_idx" ] 478qweight = self .model_tensors [base_name + ".qweight" ] 479qzeros = self .model_tensors [base_name + ".qzeros" ] 480scales = self .model_tensors [base_name + ".scales" ] 481new_tensors [base_name + ".weight" ]= ( 482lambda g = g_idx ,z = qzeros ,w = qweight ,s = scales :dequant_gptq ( 483g (),w (),z (),s () 484 ) 485 ) 486tensors_to_remove += [ 487base_name + n 488for n in ( 489".g_idx" , 490".qzeros" , 491".qweight" , 492".scales" , 493 ) 494 ] 495elif quant_method == "compressed-tensors" : 496quant_format = quant_config ["format" ] 497groups = quant_config ["config_groups" ] 498nvfp4_compressed_tensors = ( 499quant_format == "nvfp4-pack-quantized" 500or quant_format == "mixed-precision" 501and bool (groups ) 502and all (g .get ("format" )== "nvfp4-pack-quantized" for g in groups .values ()if isinstance (g ,dict )) 503 ) 504 505if len (groups )> 1 and not nvfp4_compressed_tensors : 506raise NotImplementedError ("Can't handle multiple config groups for compressed-tensors yet" ) 507weight_config = tuple (groups .values ())[0 ]["weights" ] 508 509if quant_format == "float-quantized" or quant_format == "int-quantized" or quant_format == "naive-quantized" : 510block_size = weight_config .get ("block_structure" ,None ) 511strategy = weight_config .get ("strategy" ) 512assert strategy == "channel" or strategy == "block" 513assert weight_config .get ("group_size" )is None # didn't find a model using this yet 514is_fp8 = ( 515quant_format == "float-quantized" 516and weight_config .get ("type" )== "float" 517and weight_config .get ("num_bits" )== 8 518 ) 519for name in self .model_tensors .keys (): 520if name .endswith (".weight_scale" ): 521weight_name = name .removesuffix ("_scale" ) 522w = self .model_tensors [weight_name ] 523s = self .model_tensors [name ] 524self .model_tensors [weight_name ]= lambda w = w ,s = s :dequant_simple (w (),s (),block_size ) 525tensors_to_remove .append (name ) 526if self ._fp8_as_q8 and is_fp8 : 527self ._fp8_dequantized .add (weight_name ) 528elif quant_format == "pack-quantized" : 529assert weight_config .get ("strategy" )== "group" 530assert weight_config .get ("type" ,"int" )== "int" 531num_bits = weight_config .get ("num_bits" ) 532group_size = weight_config .get ("group_size" ) 533assert isinstance (num_bits ,int ) 534assert isinstance (group_size ,int ) 535for name in self .model_tensors .keys (): 536if name .endswith (".weight_packed" ): 537base_name = name .removesuffix ("_packed" ) 538w = self .model_tensors [name ] 539scale = self .model_tensors [base_name + "_scale" ] 540shape = self .model_tensors [base_name + "_shape" ] 541zero_point = self .model_tensors .get (base_name + "_zero_point" ,lambda :None ) 542new_tensors [base_name ]= ( 543lambda w = w ,scale = scale ,shape = shape ,zero_point = zero_point :dequant_packed ( 544w (),scale (),shape (),zero_point (),num_bits ,group_size , 545 ) 546 ) 547tensors_to_remove += [base_name + n for n in ("_packed" ,"_shape" ,"_scale" )] 548if (base_name + "_zero_point" )in self .model_tensors : 549tensors_to_remove .append (base_name + "_zero_point" ) 550elif nvfp4_compressed_tensors : 551# Don't error from compressed-tensors, we'll handle them in _generate_nvfp4_tensors 552pass 553else : 554raise NotImplementedError (f"Quant format { quant_format !r } for method { quant_method !r } is not yet supported" ) 555elif quant_method == "modelopt" : 556# Mixed-precision ModelOpt models: NVFP4 tensors are handled by 557# _generate_nvfp4_tensors; FP8 tensors have 1D weight_scale and 558# are dequantized here. k/v scale tensors are unused. 559for name in self .model_tensors .keys (): 560if name .endswith (".weight_scale" ): 561weight_name = name .removesuffix ("_scale" ) 562if weight_name not in self .model_tensors : 563tensors_to_remove .append (name ) 564continue 565w = self .model_tensors [weight_name ] 566s = self .model_tensors [name ] 567is_fp8_weight = False 568if self ._fp8_as_q8 : 569is_fp8_weight = w ().dtype in (torch .float8_e4m3fn ,torch .float8_e5m2 ) 570self .model_tensors [weight_name ]= lambda w = w ,s = s :dequant_simple (w (),s (),None ) 571tensors_to_remove .append (name ) 572if is_fp8_weight : 573self ._fp8_dequantized .add (weight_name ) 574if name .endswith ((".input_scale" ,".k_scale" ,".v_scale" )): 575tensors_to_remove .append (name ) 576elif quant_method is not None : 577raise NotImplementedError (f"Quant method is not yet supported: { quant_method !r } " ) 578 579for name in tensors_to_remove : 580if name in self .model_tensors : 581del self .model_tensors [name ] 582 583for name ,value in new_tensors .items (): 584self .model_tensors [name ]= value 585 586@classmethod 587def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 588name ,gen = item 589 590if name .endswith ("e_score_correction_bias" ): 591name = name .replace ("e_score_correction_bias" ,"e_score_correction.bias" ) 592 593if "language_model." in name : 594name = name .replace ("language_model." ,"" ) 595 596return name ,gen 597 598def get_tensors (self )-> Iterator [tuple [str ,Tensor ]]: 599for name ,gen in self .model_tensors .items (): 600yield name ,gen () 601 602def format_tensor_name (self ,key :gguf .MODEL_TENSOR ,bid :int | None = None ,suffix :str = ".weight" )-> str : 603if key not in gguf .MODEL_TENSORS [self .model_arch ]: 604raise ValueError (f"Missing { key !r } for MODEL_TENSORS of { self . model_arch !r } " ) 605name :str = gguf .TENSOR_NAMES [key ] 606if "{bid}" in name : 607assert bid is not None 608name = name .format (bid = bid ) 609return name + suffix 610 611def match_model_tensor_name (self ,name :str ,key :gguf .MODEL_TENSOR ,bid :int | None ,suffix :str = ".weight" )-> bool : 612if key not in gguf .MODEL_TENSORS [self .model_arch ]: 613return False 614key_name :str = gguf .TENSOR_NAMES [key ] 615if "{bid}" in key_name : 616if bid is None : 617return False 618key_name = key_name .format (bid = bid ) 619else : 620if bid is not None : 621return False 622return name == (key_name + suffix ) 623 624def map_tensor_name (self ,name :str ,try_suffixes :Sequence [str ]= (".weight" ,".bias" ))-> str : 625new_name = self .tensor_map .get_name (key = name ,try_suffixes = try_suffixes ) 626if new_name is None : 627raise ValueError (f"Can not map tensor { name !r } " ) 628return new_name 629 630def prepare_qkv_fusion (self )-> None : 631self ._fusable_qkv_weight_layers .clear () 632self ._fusable_qkv_bias_layers .clear () 633if not self .fuse_qkv or gguf .MODEL_TENSOR .ATTN_QKV not in gguf .MODEL_TENSORS [self .model_arch ]: 634return 635 636qkv_types = { 637gguf .MODEL_TENSOR .ATTN_Q , 638gguf .MODEL_TENSOR .ATTN_K , 639gguf .MODEL_TENSOR .ATTN_V , 640 } 641weights :dict [int ,set [gguf .MODEL_TENSOR ]]= {} 642biases :dict [int ,set [gguf .MODEL_TENSOR ]]= {} 643 644for name in self .model_tensors : 645mapped = self .tensor_map .get_type_and_name (name ,try_suffixes = (".weight" ,".bias" )) 646if mapped is None : 647continue 648tensor_type ,new_name = mapped 649if tensor_type not in qkv_types : 650continue 651 652bid = next ((int (part )for part in new_name .split ("." )if part .isdecimal ()),None ) 653if bid is None : 654continue 655if new_name .endswith (".weight" ): 656weights .setdefault (bid ,set ()).add (tensor_type ) 657elif new_name .endswith (".bias" ): 658biases .setdefault (bid ,set ()).add (tensor_type ) 659 660for bid ,weight_types in weights .items (): 661bias_types = biases .get (bid ,set ()) 662if weight_types == qkv_types and (not bias_types or bias_types == qkv_types ): 663self ._fusable_qkv_weight_layers .add (bid ) 664if bias_types : 665self ._fusable_qkv_bias_layers .add (bid ) 666 667def set_gguf_parameters (self ): 668raise NotImplementedError ("set_gguf_parameters() must be implemented in subclasses" ) 669 670def modify_tensors (self ,data_torch :Tensor ,name :str ,bid :int | None )-> Iterable [tuple [str ,Tensor ]]: 671new_name = self .map_tensor_name (name ) 672 673# Handle gate/up expert tensor fusion if enabled 674if self .fuse_gate_up_exps and bid is not None : 675if self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .FFN_GATE_EXP ,bid ): 676self ._gate_exp_buffer [bid ]= data_torch 677elif self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .FFN_UP_EXP ,bid ): 678self ._up_exp_buffer [bid ]= data_torch 679 680# Check if both gate and up are buffered for this layer 681if bid in self ._gate_exp_buffer and bid in self ._up_exp_buffer : 682gate_data = self ._gate_exp_buffer .pop (bid ) 683up_data = self ._up_exp_buffer .pop (bid ) 684# gate/up shape: (n_expert, n_ff, n_embd), concatenate to (n_expert, n_ff*2, n_embd) 685fused_data = torch .cat ([gate_data ,up_data ],dim = 1 ) 686fused_name = self .format_tensor_name (gguf .MODEL_TENSOR .FFN_GATE_UP_EXP ,bid ) 687logger .info (f"Fused gate_exps and up_exps for layer { bid } " ) 688return [(fused_name ,fused_data )] 689 690# If we buffered a gate/up tensor, wait for the other 691if self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .FFN_GATE_EXP ,bid )or \ 692self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .FFN_UP_EXP ,bid ): 693return [] 694 695# Handle Q/K/V tensor fusion if enabled 696qkv_bid = next ((int (part )for part in new_name .split ("." )if part .isdecimal ()),None )if self .fuse_qkv else None 697if qkv_bid is not None : 698is_bias = new_name .endswith ('.bias' ) 699suffix = '.bias' if is_bias else '.weight' 700fusable_layers = self ._fusable_qkv_bias_layers if is_bias else self ._fusable_qkv_weight_layers 701if qkv_bid not in fusable_layers : 702return [(new_name ,data_torch )] 703 704buf_q = self ._q_bias_buffer if is_bias else self ._q_buffer 705buf_k = self ._k_bias_buffer if is_bias else self ._k_buffer 706buf_v = self ._v_bias_buffer if is_bias else self ._v_buffer 707 708if self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_Q ,qkv_bid ,suffix ): 709buf_q [qkv_bid ]= data_torch 710elif self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_K ,qkv_bid ,suffix ): 711buf_k [qkv_bid ]= data_torch 712elif self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_V ,qkv_bid ,suffix ): 713buf_v [qkv_bid ]= data_torch 714 715if qkv_bid in buf_q and qkv_bid in buf_k and qkv_bid in buf_v : 716q_data = buf_q .pop (qkv_bid ) 717k_data = buf_k .pop (qkv_bid ) 718v_data = buf_v .pop (qkv_bid ) 719fused_data = torch .cat ([q_data ,k_data ,v_data ],dim = 0 ) 720fused_name = self .format_tensor_name (gguf .MODEL_TENSOR .ATTN_QKV ,qkv_bid ,suffix = suffix ) 721logger .info (f"Fused Q, K, V { suffix [ 1 :] } into QKV for layer { qkv_bid } " ) 722return [(fused_name ,fused_data )] 723 724if self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_Q ,qkv_bid ,suffix )or \ 725self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_K ,qkv_bid ,suffix )or \ 726self .match_model_tensor_name (new_name ,gguf .MODEL_TENSOR .ATTN_V ,qkv_bid ,suffix ): 727return [] 728 729return [(new_name ,data_torch )] 730 731def tensor_force_quant (self ,name :str ,new_name :str ,bid :int | None ,n_dims :int )-> gguf .GGMLQuantizationType | bool : 732del new_name ,bid # unused 733# Force FP8-original tensors to Q8_0 when requested; Q8_0 is faster than F16/BF16. 734if self ._fp8_as_q8 and name in self ._fp8_dequantized and n_dims >= 2 : 735return gguf .GGMLQuantizationType .Q8_0 736return False 737 738# some models need extra generated tensors (like rope_freqs) 739def generate_extra_tensors (self )-> Iterable [tuple [str ,Tensor ]]: 740return () 741 742@staticmethod 743def repack_mxfp4_blocks (packed :Tensor ,scale :Tensor )-> np .ndarray : 744""" 745Repack 4-bit MX weights into ggml `block_mxfp4`. Lossless - only moves bits. 746 747Source (compressed-tensors "mxfp4-pack-quantized", also used by DeepSeek-V4): 748packed uint8 [rows, cols/2] element 2i in the low nibble, 2i+1 in the high one 749scale uint8 [rows, cols/32] one E8M0 biased exponent per 32-element group 750 751Destination, per group: one scale byte then 16 code bytes, where byte j holds 752element j in the low nibble and element j+16 in the high one. 753 754The 4-bit codes need no remapping: both sides index into ggml's kvalues_mxfp4 755order. ggml doubles the kvalues and halves the scale, so the value is the same. 756""" 757p = packed .contiguous ().view (torch .uint8 ) 758s = scale .contiguous ().view (torch .uint8 ) 759 760rows ,packed_cols = p .shape 761cols = packed_cols * 2 762if cols % 32 != 0 : 763raise ValueError (f"MXFP4 source row has { cols } values, expected a multiple of 32" ) 764 765n_blocks = cols // 32 766if tuple (s .shape )!= (rows ,n_blocks ): 767raise ValueError (f"MXFP4 scale shape { tuple ( s . shape ) } does not match { ( rows , n_blocks ) } " ) 768 769src = p .reshape (rows ,n_blocks ,16 ) 770lo = src & 0x0F # elements 0, 2, 4, ... 771hi = (src >> 4 )& 0x0F # elements 1, 3, 5, ... 772 773vals = torch .stack ((lo ,hi ),dim =- 1 ).reshape (rows ,n_blocks ,32 ) 774qs = vals [:, :, :16 ]| (vals [:, :,16 :]<< 4 ) 775 776raw = torch .cat ((s .unsqueeze (- 1 ),qs .to (torch .uint8 )),dim =- 1 ) 777return raw .reshape (rows ,n_blocks * 17 ).cpu ().numpy () 778 779@staticmethod 780def _nvfp4_pack (weight :Tensor ,scale :Tensor )-> tuple [np .ndarray ,list [int ]]: 781"""Repack NVFP4 ModelOpt tensors into ggml super-block layout. 782Preserves original E4M3 scale bits as UE4M3 (strip sign bit). 783The per-tensor scale2 factor is stored as a separate tensor and applied at inference time via ggml_mul(). 784Returns (raw_data, logical_shape).""" 785 786out_features = weight .shape [0 ] 787n_blocks = scale .shape [1 ] 788 789# Unpack ModelOpt nibble-packed weights 790w = weight .reshape (out_features ,n_blocks ,8 ) 791vals = torch .stack ([w & 0x0F ,w >> 4 ],dim =- 1 ).reshape (out_features ,n_blocks ,16 ) 792 793# Preserve original E4M3 scale bits as UE4M3 (strip sign bit) 794d_ue = scale .view (torch .uint8 ).numpy ().reshape (out_features ,n_blocks )& 0x7F 795qs = (vals [:, :, :8 ]| (vals [:, :,8 :]<< 4 )).to (torch .uint8 ).numpy () 796 797# Pack into super-blocks: [4 UE4M3 scales, 32 qs bytes] = 36 bytes per 64 elements 798n_super = n_blocks // 4 799d_grouped = d_ue .reshape (out_features ,n_super ,4 ) 800qs_grouped = qs .reshape (out_features ,n_super ,4 ,8 ).reshape (out_features ,n_super ,32 ) 801raw = np .concatenate ([d_grouped ,qs_grouped ],axis =- 1 ).reshape (out_features ,n_super * 36 ) 802return raw , [out_features ,n_super * 64 ] 803 804def _repack_nvfp4 (self ,name :str ,weight :Tensor ,scale :Tensor ,scale2 :Tensor ,input_scale :Tensor ): 805new_name = self .map_tensor_name (name ) 806 807raw ,shape = self ._nvfp4_pack (weight ,scale ) 808logger .info (f"Repacked { new_name } with shape { shape } and quantization NVFP4" ) 809self .gguf_writer .add_tensor (new_name ,raw ,raw_dtype = gguf .GGMLQuantizationType .NVFP4 ) 810 811self ._write_scale_tensor (new_name .replace (".weight" ,".scale" ),scale2 ) 812self ._write_scale_tensor (new_name .replace (".weight" ,".input_scale" ),input_scale ) 813 814def _generate_nvfp4_tensors (self ): 815# Per-layer expert merging to avoid holding all experts in memory 816expert_blocks :dict [tuple [int ,str ],list [tuple [int ,np .ndarray ]]]= {} 817expert_scales :dict [tuple [int ,str ],list [tuple [int ,float ]]]= {} 818expert_input_scales :dict [tuple [int ,str ],list [tuple [int ,float ]]]= {} 819expert_shapes :dict [tuple [int ,str ],list [int ]]= {} 820n_experts = self .find_hparam (["num_local_experts" ,"num_experts" ],optional = True )or 0 821consumed :list [str ]= [] 822 823for name in self .model_tensors .keys (): 824if not name .endswith (".weight" ): 825continue 826scale_name = name .replace (".weight" ,".weight_scale" ) 827scale2_name = name .replace (".weight" ,".weight_scale_2" ) 828input_scale_name = name .replace (".weight" ,".input_scale" ) 829if scale_name not in self .model_tensors : 830continue 831# Force eager materialization of lazy tensors 832weight = LazyTorchTensor .to_eager (self .model_tensors [name ]()) 833scale = LazyTorchTensor .to_eager (self .model_tensors [scale_name ]()) 834 835# Skip non-NVFP4 tensors (e.g. FP8 with per-channel 1D scales) 836if scale .ndim < 2 : 837continue 838 839scale2 = LazyTorchTensor .to_eager (self .model_tensors .get (scale2_name ,lambda :torch .tensor (1.0 ))()) 840input_scale = LazyTorchTensor .to_eager (self .model_tensors .get (input_scale_name ,lambda :torch .tensor (1.0 ))()) 841 842# Mark tensors for removal from model_tensors (already written to gguf) 843consumed .extend ([name ,scale_name ]) 844if scale2_name in self .model_tensors : 845consumed .append (scale2_name ) 846if input_scale_name in self .model_tensors : 847consumed .append (input_scale_name ) 848 849# Check if this is a per-expert tensor 850m = re .search (r'\.experts\.(\d+)\.(gate_proj|up_proj|down_proj)\.weight$' ,name ) 851if m : 852expert_id = int (m .group (1 )) 853proj_type = m .group (2 ) 854bid_m = re .search (r'\.layers\.(\d+)\.' ,name ) 855bid = int (bid_m .group (1 ))if bid_m else 0 856key = (bid ,proj_type ) 857 858raw ,shape = self ._nvfp4_pack (weight ,scale ) 859 860if key not in expert_blocks : 861expert_blocks [key ]= [] 862expert_scales [key ]= [] 863expert_input_scales [key ]= [] 864expert_shapes [key ]= shape 865expert_blocks [key ].append ((expert_id ,raw .copy ())) 866# Collect per-expert scale2 (scalar per expert) 867expert_scales [key ].append ((expert_id ,float (scale2 .float ().sum ()))) 868# Collect per-expert input_scale (scalar per expert) 869expert_input_scales [key ].append ((expert_id ,float (input_scale .float ().sum ()))) 870 871# Flush when all experts for this (layer, proj) are collected 872if n_experts > 0 and len (expert_blocks [key ])>= n_experts : 873self ._flush_nvfp4_experts (key ,expert_blocks ,expert_scales ,expert_input_scales ,expert_shapes ,bid ,proj_type ) 874else : 875self ._repack_nvfp4 (name ,weight ,scale ,scale2 ,input_scale ) 876 877# Flush any remaining experts (fallback if n_experts was unknown) 878for bid ,proj_type in list (expert_blocks .keys ()): 879self ._flush_nvfp4_experts ((bid ,proj_type ),expert_blocks ,expert_scales ,expert_input_scales ,expert_shapes ,bid ,proj_type ) 880 881# Remove consumed tensors so get_tensors/modify_tensors won't see them 882for name in consumed : 883self .model_tensors .pop (name ,None ) 884 885# Remove any remaining unused auxiliary tensors 886for name in list (self .model_tensors .keys ()): 887if name .endswith ((".k_scale" ,".v_scale" )): 888del self .model_tensors [name ] 889 890def _flush_nvfp4_experts (self ,key ,expert_blocks ,expert_scales ,expert_input_scales ,expert_shapes ,bid ,proj_type ): 891experts = expert_blocks .pop (key ) 892scales = expert_scales .pop (key ) 893input_scales = expert_input_scales .pop (key ) 894shape = expert_shapes .pop (key ) 895 896experts .sort (key = lambda x :x [0 ]) 897merged = np .stack ([e [1 ]for e in experts ],axis = 0 ) 898merged_name = f"model.layers. { bid } .mlp.experts. { proj_type } .weight" 899new_name = self .map_tensor_name (merged_name ) 900logger .info (f"Repacked { new_name } with shape [ { len ( experts ) } , { shape [ 0 ] } , { shape [ 1 ] } ] and quantization NVFP4" ) 901self .gguf_writer .add_tensor (new_name ,merged ,raw_dtype = gguf .GGMLQuantizationType .NVFP4 ) 902 903scales .sort (key = lambda x :x [0 ]) 904self ._write_scales_tensor (new_name .replace (".weight" ,".scale" ), [s [1 ]for s in scales ]) 905 906input_scales .sort (key = lambda x :x [0 ]) 907self ._write_scales_tensor (new_name .replace (".weight" ,".input_scale" ), [s [1 ]for s in input_scales ]) 908 909del experts ,merged 910 911def prepare_tensors (self ): 912# detect NVFP4 quantization (ModelOpt and Compressed-tensors formats) 913quantization_config = self .hparams .get ("quantization_config" )or {} 914quant_algo = quantization_config .get ("quant_algo" ) 915quant_method = quantization_config .get ("quant_method" ) 916quant_format = quantization_config .get ("format" ) 917quant_groups = quantization_config .get ("config_groups" )or {} 918quant_layers = quantization_config .get ("quantized_layers" )or {} 919quant_config_file = self .dir_model / "hf_quant_config.json" 920 921if (not quant_algo or not quant_layers )and quant_config_file .is_file (): 922with open (quant_config_file ,"r" ,encoding = "utf-8" )as f : 923hf_quant_config = json .load (f ) 924quant_config = hf_quant_config .get ("quantization" )or {} 925producer = hf_quant_config .get ("producer" )or {} 926producer_name = (producer .get ("name" )or "" ).lower () 927if quant_method is None : 928self .hparams .setdefault ("quantization_config" , {})["quant_method" ]= producer_name 929quant_method = producer_name 930quant_algo = quant_config .get ("quant_algo" ,quant_algo ) 931quant_method = quant_config .get ("quant_method" ,quant_method ) 932quant_format = quant_config .get ("format" ,quant_format ) 933quant_groups = quant_config .get ("config_groups" ,quant_groups )or {} 934quant_layers = quant_config .get ("quantized_layers" ,quant_layers )or {} 935 936# Some models use per-tensor quant_algo (e.g. "MIXED_PRECISION" with 937# per-layer NVFP4/FP8) instead of a single global "NVFP4" value. 938nvfp4_compressed_tensors = quant_method == "compressed-tensors" and ( 939quant_format == "nvfp4-pack-quantized" 940or quant_format == "mixed-precision" 941and bool (quant_groups ) 942and all (g .get ("format" )== "nvfp4-pack-quantized" for g in quant_groups .values ()if isinstance (g ,dict )) 943 ) 944if quant_algo != "NVFP4" : 945if nvfp4_compressed_tensors : 946quant_algo = "NVFP4" 947elif any (str (v .get ("quant_algo" )).endswith ("NVFP4" )for v in quant_layers .values ()if isinstance (v ,dict )): 948quant_algo = "NVFP4" 949 950self ._is_nvfp4 = quant_algo in ("NVFP4" ,"W4A16_NVFP4" ) 951self ._is_mxfp4 = quant_method == "mxfp4" 952 953# NVFP4 weights are repacked and written directly to gguf_writer. 954# This must run before dequant_model so NVFP4 tensors are removed 955# from model_tensors, leaving only non-NVFP4 (e.g. FP8) for dequant. 956if self ._is_nvfp4 : 957if nvfp4_compressed_tensors : 958# Convert compressed-tensors 'global' scales into the reciprocal 959def inverse_scale (gen ): 960def load (): 961scale = LazyTorchTensor .to_eager (gen ()).float () 962return 1.0 / scale 963return load 964 965# Change the compressed-tensors names to the ModelOpt names for handling consistently later 966for name in list (self .model_tensors .keys ()): 967if name .endswith (".weight_packed" ): 968weight_name = name .removesuffix ("_packed" ) 969if weight_name not in self .model_tensors : 970self .model_tensors [weight_name ]= self .model_tensors .pop (name ) 971elif name .endswith (".weight_global_scale" ): 972scale2_name = name .replace (".weight_global_scale" ,".weight_scale_2" ) 973if scale2_name not in self .model_tensors : 974self .model_tensors [scale2_name ]= inverse_scale (self .model_tensors .pop (name )) 975elif name .endswith (".input_global_scale" ): 976input_scale_name = name .replace (".input_global_scale" ,".input_scale" ) 977if input_scale_name not in self .model_tensors : 978self .model_tensors [input_scale_name ]= inverse_scale (self .model_tensors .pop (name )) 979self ._generate_nvfp4_tensors () 980 981self .dequant_model () 982 983self .prepare_qkv_fusion () 984 985# Handle empty tensor_map for models with block_count=0 (like MobileNetV5) 986if self .tensor_map .mapping : 987max_name_len = max (len (s )for _ ,s in self .tensor_map .mapping .values ())+ len (".weight," ) 988else : 989max_name_len = len ("vision_encoder.weight," )# Default reasonable length 990 991for name ,data_torch in chain (self .generate_extra_tensors (),self .get_tensors ()): 992# we don't need these 993if name .endswith ((".attention.masked_bias" ,".attention.bias" ,".rotary_emb.inv_freq" )): 994continue 995 996old_dtype = data_torch .dtype 997 998# convert any unsupported data types to float32 999if data_torch .dtype not in (torch .float16 ,torch .float32 ): 1000data_torch = data_torch .to (torch .float32 ) 1001 1002# use the first number-like part of the tensor name as the block id 1003bid = None 1004for part in name .split ("." ): 1005if part .isdecimal (): 1006bid = int (part ) 1007break 1008 1009for new_name ,data_torch in (self .modify_tensors (data_torch ,name ,bid )): 1010# TODO: why do we squeeze here? 1011# data = data_torch.squeeze().numpy() 1012data = data_torch .numpy () 1013 1014n_dims = len (data .shape ) 1015data_qtype :gguf .GGMLQuantizationType | bool = self .tensor_force_quant (name ,new_name ,bid ,n_dims ) 1016 1017# Most of the codebase that takes in 1D tensors or norms only handles F32 tensors 1018if n_dims <= 1 or new_name .endswith ("_norm.weight" ): 1019data_qtype = gguf .GGMLQuantizationType .F32 1020 1021# Conditions should closely match those in llama_model_quantize_internal in llama.cpp 1022# Some tensor types are always in float32 1023if data_qtype is False and ( 1024any ( 1025self .match_model_tensor_name (new_name ,key ,bid ) 1026for key in ( 1027gguf .MODEL_TENSOR .FFN_GATE_INP , 1028gguf .MODEL_TENSOR .FFN_GATE_INP_SHEXP , 1029gguf .MODEL_TENSOR .POS_EMBD , 1030gguf .MODEL_TENSOR .TOKEN_TYPES , 1031gguf .MODEL_TENSOR .SSM_CONV1D , 1032gguf .MODEL_TENSOR .SHORTCONV_CONV , 1033gguf .MODEL_TENSOR .TIME_MIX_FIRST , 1034gguf .MODEL_TENSOR .TIME_MIX_W1 , 1035gguf .MODEL_TENSOR .TIME_MIX_W2 , 1036gguf .MODEL_TENSOR .TIME_MIX_DECAY_W1 , 1037gguf .MODEL_TENSOR .TIME_MIX_DECAY_W2 , 1038gguf .MODEL_TENSOR .TIME_MIX_LERP_FUSED , 1039gguf .MODEL_TENSOR .POSNET_NORM1 , 1040gguf .MODEL_TENSOR .POSNET_NORM2 , 1041gguf .MODEL_TENSOR .V_ENC_EMBD_POS , 1042gguf .MODEL_TENSOR .A_ENC_EMBD_POS , 1043gguf .MODEL_TENSOR .ALTUP_CORRECT_COEF , 1044gguf .MODEL_TENSOR .ALTUP_PREDICT_COEF , 1045# Kimi KDA conv weights should be F32 1046gguf .MODEL_TENSOR .SSM_CONV1D_Q , 1047gguf .MODEL_TENSOR .SSM_CONV1D_K , 1048gguf .MODEL_TENSOR .SSM_CONV1D_V , 1049# DSA indexer weights should be F32 1050gguf .MODEL_TENSOR .INDEXER_PROJ , 1051 ) 1052 ) 1053or new_name [- 7 :]not in (".weight" ,".lora_a" ,".lora_b" ) 1054 ): 1055data_qtype = gguf .GGMLQuantizationType .F32 1056 1057if data_qtype is False and any ( 1058self .match_model_tensor_name (new_name ,key ,bid ) 1059for key in ( 1060gguf .MODEL_TENSOR .TOKEN_EMBD , 1061gguf .MODEL_TENSOR .PER_LAYER_TOKEN_EMBD , 1062gguf .MODEL_TENSOR .OUTPUT , 1063gguf .MODEL_TENSOR .ALTUP_ROUTER , 1064gguf .MODEL_TENSOR .LAUREL_L , 1065gguf .MODEL_TENSOR .LAUREL_R , 1066 ) 1067 ): 1068if self .ftype in ( 1069gguf .LlamaFileType .MOSTLY_TQ1_0 , 1070gguf .LlamaFileType .MOSTLY_TQ2_0 , 1071 ): 1072# TODO: use Q4_K and Q6_K 1073data_qtype = gguf .GGMLQuantizationType .F16 1074 1075# No override (data_qtype is False), or wants to be quantized (data_qtype is True) 1076if isinstance (data_qtype ,bool ): 1077if self .ftype == gguf .LlamaFileType .ALL_F32 : 1078data_qtype = gguf .GGMLQuantizationType .F32 1079elif self .ftype == gguf .LlamaFileType .MOSTLY_F16 : 1080data_qtype = gguf .GGMLQuantizationType .F16 1081elif self .ftype == gguf .LlamaFileType .MOSTLY_BF16 : 1082data_qtype = gguf .GGMLQuantizationType .BF16 1083elif self .ftype == gguf .LlamaFileType .MOSTLY_Q8_0 : 1084data_qtype = gguf .GGMLQuantizationType .Q8_0 1085elif self .ftype == gguf .LlamaFileType .MOSTLY_TQ1_0 : 1086data_qtype = gguf .GGMLQuantizationType .TQ1_0 1087elif self .ftype == gguf .LlamaFileType .MOSTLY_TQ2_0 : 1088data_qtype = gguf .GGMLQuantizationType .TQ2_0 1089else : 1090raise ValueError (f"Unknown file type: { self . ftype . name } " ) 1091 1092# a chunked tensor quantizes as one chunk at a time, while it is written 1093quantize = data .quantize if isinstance (data ,gguf .LazyChunkedTensor )else ( 1094lambda qtype ,d = data :gguf .quants .quantize (d ,qtype )) 1095 1096try : 1097data = quantize (data_qtype ) 1098except gguf .QuantError as e : 1099logger .warning ("%s, %s" ,e ,"falling back to F16" ) 1100data_qtype = gguf .GGMLQuantizationType .F16 1101data = quantize (data_qtype ) 1102 1103shape = gguf .quant_shape_from_byte_shape (data .shape ,data_qtype )if data .dtype == np .uint8 else data .shape 1104 1105# reverse shape to make it similar to the internal ggml dimension order 1106shape_str = f"{{ { ', ' . join ( str ( n ) for n in reversed ( shape )) } }}" 1107 1108# n_dims is implicit in the shape 1109logger .info (f" { f'%- { max_name_len } s' % f' { new_name } ,' } { old_dtype } --> { data_qtype . name } , shape = { shape_str } " ) 1110 1111self .gguf_writer .add_tensor (new_name ,data ,raw_dtype = data_qtype ) 1112 1113qkv_buffers = ( 1114self ._q_buffer ,self ._k_buffer ,self ._v_buffer , 1115self ._q_bias_buffer ,self ._k_bias_buffer ,self ._v_bias_buffer , 1116 ) 1117if any (qkv_buffers ): 1118raise ValueError ("QKV fusion did not consume all buffered tensors" ) 1119 1120def set_type (self ): 1121self .gguf_writer .add_type (gguf .GGUFType .MODEL ) 1122 1123def prepare_metadata (self ,vocab_only :bool ): 1124 1125total_params ,shared_params ,expert_params ,expert_count = self .gguf_writer .get_total_parameter_count () 1126 1127self .metadata = gguf .Metadata .load (self .metadata_override ,self .dir_model_card ,self .model_name ,total_params ) 1128 1129# If we are using HF model id, set the metadata name to the model id 1130if self .remote_hf_model_id : 1131self .metadata .name = self .remote_hf_model_id 1132 1133# Fallback to model directory name if metadata name is still missing 1134if self .metadata .name is None : 1135self .metadata .name = self .dir_model .name 1136 1137if self .ftype in (gguf .LlamaFileType .ALL_F32 ,gguf .LlamaFileType .MOSTLY_F16 ,gguf .LlamaFileType .MOSTLY_BF16 ): 1138if self ._is_nvfp4 : 1139self .ftype = gguf .LlamaFileType .MOSTLY_NVFP4 1140elif self ._is_mxfp4 : 1141self .ftype = gguf .LlamaFileType .MOSTLY_MXFP4_MOE 1142 1143# Generate parameter weight class (useful for leader boards) if not yet determined 1144if self .metadata .size_label is None and total_params > 0 : 1145self .metadata .size_label = gguf .size_label (total_params ,shared_params ,expert_params ,expert_count ) 1146 1147self .set_type () 1148 1149logger .info ("Set meta model" ) 1150self .metadata .set_gguf_meta_model (self .gguf_writer ) 1151 1152logger .info ("Set model parameters" ) 1153self .set_gguf_parameters () 1154 1155logger .info ("Set model quantization version" ) 1156self .gguf_writer .add_quantization_version (gguf .GGML_QUANT_VERSION ) 1157 1158def write_vocab (self ): 1159raise NotImplementedError ("write_vocab() must be implemented in subclasses" ) 1160 1161def write (self ): 1162self .prepare_tensors () 1163self .prepare_metadata (vocab_only = False ) 1164self .gguf_writer .write_header_to_file (path = self .fname_out ) 1165self .gguf_writer .write_kv_data_to_file () 1166self .gguf_writer .write_tensors_to_file (progress = True ) 1167self .gguf_writer .close () 1168 1169@staticmethod 1170def get_model_part_names (dir_model :Path ,prefix :str ,suffix :str )-> list [str ]: 1171part_names :list [str ]= [] 1172for filename in os .listdir (dir_model ): 1173if filename .startswith (prefix )and filename .endswith (suffix ): 1174part_names .append (filename ) 1175 1176part_names .sort () 1177 1178return part_names 1179 1180@staticmethod 1181def load_hparams_guess (dir_model :Path )-> dict [str ,Any ]| None : 1182# some models ship no config.json, will try to guess them 1183from conversion import load_all_models 1184load_all_models () 1185 1186for matcher ,loader in ModelBase ._hparams_loaders : 1187if matcher (dir_model ): 1188return loader (dir_model ) 1189return None 1190 1191@classmethod 1192def register_hparams_loader (cls ,matcher :HparamsMatcher )-> Callable [[HparamsLoader ],HparamsLoader ]: 1193def inner (loader :HparamsLoader )-> HparamsLoader : 1194cls ._hparams_loaders .append ((matcher ,loader )) 1195return loader 1196return inner 1197 1198@staticmethod 1199def load_hparams (dir_model :Path ,is_mistral_format :bool ): 1200if is_mistral_format : 1201with open (dir_model / "params.json" ,"r" ,encoding = "utf-8" )as f : 1202config = json .load (f ) 1203return config 1204 1205try : 1206# for security reason, we don't allow loading remote code by default 1207# if a model need remote code, we will fallback to config.json 1208config = AutoConfig .from_pretrained (dir_model ,trust_remote_code = False ).to_dict () 1209except Exception as e : 1210logger .warning (f"Failed to load model config from { dir_model } : { e } " ) 1211if not (dir_model / "config.json" ).is_file (): 1212config = ModelBase .load_hparams_guess (dir_model ) 1213if config is not None : 1214return config 1215logger .warning ("Trying to load config.json instead" ) 1216with open (dir_model / "config.json" ,"r" ,encoding = "utf-8" )as f : 1217config = json .load (f ) 1218if "llm_config" in config : 1219# rename for InternVL 1220config ["text_config" ]= config ["llm_config" ] 1221if "lm_config" in config : 1222# rename for GlmASR 1223config ["text_config" ]= config ["lm_config" ] 1224if "thinker_config" in config : 1225# rename for Qwen2.5-Omni 1226config ["text_config" ]= config ["thinker_config" ]["text_config" ] 1227if "language_config" in config : 1228# rename for DeepSeekOCR 1229config ["text_config" ]= config ["language_config" ] 1230if "lfm" in config : 1231# rename for LFM2-Audio 1232config ["text_config" ]= config ["lfm" ] 1233return config 1234 1235@classmethod 1236def register (cls ,* names :str )-> Callable [[AnyModel ],AnyModel ]: 1237assert names 1238 1239def func (modelcls :AnyModel )-> AnyModel : 1240model_type = ModelType .MMPROJ if modelcls .model_arch == gguf .MODEL_ARCH .MMPROJ else ModelType .TEXT 1241for name in names : 1242cls ._model_classes [model_type ][name ]= modelcls 1243return modelcls 1244return func 1245 1246@classmethod 1247def example (cls ,* hf_repos :str )-> Callable [[AnyModel ],AnyModel ]: 1248del hf_repos # unused 1249 1250def func (modelcls :AnyModel )-> AnyModel : 1251return modelcls 1252return func 1253 1254@classmethod 1255def print_registered_models (cls ): 1256for model_type ,model_classes in cls ._model_classes .items (): 1257logger .error (f" { model_type . name } models:" ) 1258for name in sorted (model_classes .keys ()): 1259logger .error (f" - { name } " ) 1260 1261@classmethod 1262def from_model_architecture (cls ,arch :str ,model_type = ModelType .TEXT )-> type [ModelBase ]: 1263try : 1264return cls ._model_classes [model_type ][arch ] 1265except KeyError : 1266raise NotImplementedError (f'Architecture { arch !r } not supported!' )from None 1267 1268 1269class TextModel (ModelBase ): 1270model_type = ModelType .TEXT 1271hf_arch :str 1272 1273def __init__ (self ,* args ,** kwargs ): 1274super ().__init__ (* args ,** kwargs ) 1275if not self .is_mistral_format : 1276self .hf_arch = get_model_architecture (self .hparams ,self .model_type ) 1277else : 1278self .hf_arch = "" 1279 1280if "text_config" in self .hparams : 1281# move the text_config to the root level 1282self .hparams = {** self .hparams ,** self .hparams ["text_config" ]} 1283 1284self .block_count = self .find_hparam (["n_layers" ,"num_hidden_layers" ,"n_layer" ,"num_layers" ]) 1285self .tensor_map = gguf .get_tensor_name_map (self .model_arch ,self .block_count ) 1286 1287self .rope_parameters = self .hparams .get ("rope_parameters" ,self .hparams .get ("rope_scaling" ))or {} 1288 1289rope_theta = self .find_hparam (["global_rope_theta" ,"rope_global_theta" ,"rope_theta_global" ,"rope_theta" ,"rotary_emb_base" ],optional = True ) 1290local_rope_theta = self .find_hparam (["local_rope_theta" ,"rope_local_theta" ,"rope_theta_local" ,"swa_rope_theta" ,"rope_local_base_freq" ],optional = True ) 1291partial_rotary_factor = self .find_hparam (["partial_rotary_factor" ,"rope_pct" ,"rope_percent" ],optional = True ) 1292original_max_position_embeddings = self .find_hparam (["original_max_position_embeddings" ],optional = True ) 1293 1294# Ensure global params are mirrored in rope_parameters 1295if "full_attention" not in self .rope_parameters and "sliding_attention" not in self .rope_parameters : 1296if local_rope_theta is not None : 1297self .rope_parameters ["sliding_attention" ]= {"rope_theta" :local_rope_theta } 1298if "rope_theta" not in self .rope_parameters and rope_theta is not None : 1299self .rope_parameters ["rope_theta" ]= rope_theta 1300if "rope_type" not in self .rope_parameters and (rope_type := self .rope_parameters .get ("type" ))is not None : 1301self .rope_parameters ["rope_type" ]= rope_type 1302if "partial_rotary_factor" not in self .rope_parameters and partial_rotary_factor is not None : 1303self .rope_parameters ["partial_rotary_factor" ]= partial_rotary_factor 1304if "original_max_position_embeddings" not in self .rope_parameters and original_max_position_embeddings is not None : 1305self .rope_parameters ["original_max_position_embeddings" ]= original_max_position_embeddings 1306 1307@classmethod 1308def __init_subclass__ (cls ): 1309# can't use an abstract property, because overriding it without type errors 1310# would require using decorated functions instead of simply defining the property 1311if "model_arch" not in cls .__dict__ : 1312raise TypeError (f"Missing property 'model_arch' for { cls . __name__ !r } " ) 1313 1314@classmethod 1315def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 1316name ,gen = item 1317 1318# Skip multimodal tensors 1319if name .startswith (("mlp" ,"vit." ,"vpm." ,"siglip2." ,"conformer." ,"merger." ,"resampler." ,"sound_encoder." ,"sound_projection." ,"speech_embeddings." )) \ 1320or "visual." in name or "vision." in name or "audio." in name or "talker." in name \ 1321or "vision_" in name or "audio_" in name \ 1322or "token2wav." in name or "code2wav." in name \ 1323or "projector." in name or "pre_mm_projector_norm" in name \ 1324or "image_newline" in name or "view_seperator" in name \ 1325or "patch_embed" in name or "patch_embedding" in name \ 1326or "patch_merger." in name or "patch_merge_mlp." in name or "model.connector." in name : 1327return None 1328 1329return super ().filter_tensors (item ) 1330 1331def set_vocab (self ): 1332self ._set_vocab_gpt2 () 1333 1334def prepare_metadata (self ,vocab_only :bool ): 1335super ().prepare_metadata (vocab_only = vocab_only ) 1336 1337total_params = self .gguf_writer .get_total_parameter_count ()[0 ] 1338# Extract the encoding scheme from the file type name. e.g. 'gguf.LlamaFileType.MOSTLY_Q8_0' --> 'Q8_0' 1339output_type :str = self .ftype .name .partition ("_" )[2 ] 1340 1341# Filename Output 1342if self .fname_out .is_dir (): 1343# Generate default filename based on model specification and available metadata 1344if not vocab_only : 1345fname_default :str = gguf .naming_convention (self .metadata .name ,self .metadata .basename ,self .metadata .finetune ,self .metadata .version ,self .metadata .size_label ,output_type ,model_type = "LoRA" if total_params < 0 else None ) 1346else : 1347fname_default :str = gguf .naming_convention (self .metadata .name ,self .metadata .basename ,self .metadata .finetune ,self .metadata .version ,size_label = None ,output_type = None ,model_type = "vocab" ) 1348 1349# Use the default filename 1350self .fname_out = self .fname_out / f" { fname_default } .gguf" 1351else : 1352# Output path is a custom defined templated filename 1353# Note: `not is_dir()` is used because `.is_file()` will not detect 1354# file template strings as it doesn't actually exist as a file 1355 1356# Process templated file name with the output ftype, useful with the "auto" ftype 1357self .fname_out = self .fname_out .parent / gguf .fill_templated_filename (self .fname_out .name ,output_type ) 1358 1359logger .info ("Set model tokenizer" ) 1360self .set_vocab () 1361 1362def set_gguf_parameters (self ): 1363self .gguf_writer .add_block_count (self .block_count ) 1364 1365if (n_ctx := self .find_hparam (["max_position_embeddings" ,"n_ctx" ,"n_positions" ,"max_length" ,"max_sequence_length" ,"model_max_length" ],optional = True ))is not None : 1366self .gguf_writer .add_context_length (n_ctx ) 1367logger .info (f"gguf: context length = { n_ctx } " ) 1368 1369if (n_embd := self .find_hparam (["hidden_size" ,"n_embd" ,"dim" ],optional = True ))is not None : 1370self .gguf_writer .add_embedding_length (n_embd ) 1371logger .info (f"gguf: embedding length = { n_embd } " ) 1372 1373if (n_ff := self .find_hparam (["prefix_dense_intermediate_size" ,"dense_intermediate_size" ,"intermediate_size" ,"n_inner" ,"hidden_dim" ],optional = True ))is not None : 1374self .gguf_writer .add_feed_forward_length (n_ff ) 1375logger .info (f"gguf: feed forward length = { n_ff } " ) 1376 1377if (n_head := self .find_hparam (["num_attention_heads" ,"n_head" ,"n_heads" ],optional = True ))is not None : 1378self .gguf_writer .add_head_count (n_head ) 1379logger .info (f"gguf: head count = { n_head } " ) 1380 1381if (n_head_kv := self .find_hparam (["num_key_value_heads" ,"n_kv_heads" ],optional = True ))is not None : 1382self .gguf_writer .add_head_count_kv (n_head_kv ) 1383logger .info (f"gguf: key-value head count = { n_head_kv } " ) 1384 1385if self .hparams .get ("is_causal" )is False : 1386self .gguf_writer .add_causal_attention (False ) 1387logger .info ("gguf: causal attention = False" ) 1388 1389# TODO: Handle "sliding_attention" similarly when models start implementing it 1390rope_params = self .rope_parameters .get ("full_attention" ,self .rope_parameters ) 1391if (rope_type := rope_params .get ("rope_type" ))is not None : 1392rope_factor = rope_params .get ("factor" ) 1393rope_gguf_type = gguf .RopeScalingType .NONE 1394if rope_type == "linear" and rope_factor is not None : 1395rope_gguf_type = gguf .RopeScalingType .LINEAR 1396self .gguf_writer .add_rope_scaling_type (rope_gguf_type ) 1397self .gguf_writer .add_rope_scaling_factor (rope_factor ) 1398elif rope_type == "yarn" and rope_factor is not None : 1399rope_gguf_type = gguf .RopeScalingType .YARN 1400self .gguf_writer .add_rope_scaling_type (rope_gguf_type ) 1401self .gguf_writer .add_rope_scaling_factor (rope_factor ) 1402self .gguf_writer .add_rope_scaling_orig_ctx_len (rope_params ["original_max_position_embeddings" ]) 1403if (yarn_ext_factor := rope_params .get ("extrapolation_factor" ))is not None : 1404self .gguf_writer .add_rope_scaling_yarn_ext_factor (yarn_ext_factor ) 1405if (yarn_attn_factor := rope_params .get ("attention_factor" ,rope_params .get ("attn_factor" )))is not None : 1406self .gguf_writer .add_rope_scaling_yarn_attn_factor (yarn_attn_factor ) 1407if (yarn_beta_fast := rope_params .get ("beta_fast" ))is not None : 1408self .gguf_writer .add_rope_scaling_yarn_beta_fast (yarn_beta_fast ) 1409if (yarn_beta_slow := rope_params .get ("beta_slow" ))is not None : 1410self .gguf_writer .add_rope_scaling_yarn_beta_slow (yarn_beta_slow ) 1411# self.gguf_writer.add_rope_scaling_yarn_log_mul(rope_params["mscale_all_dim"]) 1412elif rope_type == "su" or rope_type == "longrope" : 1413rope_gguf_type = gguf .RopeScalingType .LONGROPE 1414self .gguf_writer .add_rope_scaling_type (rope_gguf_type ) 1415elif rope_type == "dynamic" : 1416# HunYuan, handled in model class 1417pass 1418elif rope_type .lower ()== "llama3" : 1419# Handled in generate_extra_tensors 1420pass 1421else : 1422logger .warning (f"Unknown RoPE type: { rope_type } " ) 1423logger .info (f"gguf: rope scaling type = { rope_gguf_type . name } " ) 1424 1425if "mrope_section" in self .rope_parameters : 1426mrope_section = self .rope_parameters ["mrope_section" ] 1427# Pad to 4 dimensions [time, height, width, extra] 1428while len (mrope_section )< 4 : 1429mrope_section .append (0 ) 1430self .gguf_writer .add_rope_dimension_sections (mrope_section [:4 ]) 1431logger .info (f"gguf: mrope sections: { mrope_section [: 4 ] } " ) 1432 1433if (rope_theta := rope_params .get ("rope_theta" ))is not None : 1434self .gguf_writer .add_rope_freq_base (rope_theta ) 1435logger .info (f"gguf: rope theta = { rope_theta } " ) 1436if (local_rope_theta := self .rope_parameters .get ("sliding_attention" , {}).get ("rope_theta" ))is not None : 1437self .gguf_writer .add_rope_freq_base_swa (local_rope_theta ) 1438logger .info (f"gguf: rope theta swa = { local_rope_theta } " ) 1439if (f_rms_eps := self .find_hparam (["rms_norm_eps" ,"norm_eps" ],optional = True ))is not None : 1440self .gguf_writer .add_layer_norm_rms_eps (f_rms_eps ) 1441logger .info (f"gguf: rms norm epsilon = { f_rms_eps } " ) 1442if (f_norm_eps := self .find_hparam (["layer_norm_eps" ,"layer_norm_epsilon" ,"norm_epsilon" ],optional = True ))is not None : 1443self .gguf_writer .add_layer_norm_eps (f_norm_eps ) 1444logger .info (f"gguf: layer norm epsilon = { f_norm_eps } " ) 1445if (n_experts := self .find_hparam (["num_local_experts" ,"num_experts" ,"n_routed_experts" ],optional = True ))is not None : 1446self .gguf_writer .add_expert_count (n_experts ) 1447logger .info (f"gguf: expert count = { n_experts } " ) 1448if (n_experts_used := self .find_hparam (["num_experts_per_tok" ,"num_experts_per_token" ,"top_k_experts" ],optional = True ))is not None : 1449self .gguf_writer .add_expert_used_count (n_experts_used ) 1450logger .info (f"gguf: experts used count = { n_experts_used } " ) 1451if (n_expert_groups := self .hparams .get ("n_group" ))is not None : 1452self .gguf_writer .add_expert_group_count (n_expert_groups ) 1453logger .info (f"gguf: expert groups count = { n_expert_groups } " ) 1454if (n_group_used := self .hparams .get ("topk_group" ))is not None : 1455self .gguf_writer .add_expert_group_used_count (n_group_used ) 1456logger .info (f"gguf: expert groups used count = { n_group_used } " ) 1457 1458if (score_func := self .find_hparam (["score_function" ,"scoring_func" ,"score_func" ,"moe_router_activation" ,"moe_router_activation_func" ,"expert_selection_fn" ],optional = True ))is not None : 1459if score_func == "sigmoid" : 1460self .gguf_writer .add_expert_gating_func (gguf .ExpertGatingFuncType .SIGMOID ) 1461elif score_func == "softmax" : 1462self .gguf_writer .add_expert_gating_func (gguf .ExpertGatingFuncType .SOFTMAX ) 1463elif score_func == "sqrtsoftplus" : 1464self .gguf_writer .add_expert_gating_func (gguf .ExpertGatingFuncType .SQRTSOFTPLUS ) 1465else : 1466raise ValueError (f"Unsupported expert score gating function value: { score_func } " ) 1467logger .info (f"gguf: expert score gating function = { score_func } " ) 1468 1469if (head_dim := self .hparams .get ("head_dim" ))is not None : 1470self .gguf_writer .add_key_length (head_dim ) 1471self .gguf_writer .add_value_length (head_dim ) 1472 1473self .gguf_writer .add_file_type (self .ftype ) 1474logger .info (f"gguf: file type = { self . ftype } " ) 1475 1476def write_vocab (self ): 1477if len (self .gguf_writer .tensors )!= 1 : 1478raise ValueError ('Splitting the vocabulary is not supported' ) 1479 1480self .prepare_metadata (vocab_only = True ) 1481self .gguf_writer .write_header_to_file (path = self .fname_out ) 1482self .gguf_writer .write_kv_data_to_file () 1483self .gguf_writer .close () 1484 1485def does_token_look_special (self ,token :str | bytes )-> bool : 1486if isinstance (token , (bytes ,bytearray )): 1487token_text = token .decode (encoding = "utf-8" ) 1488elif isinstance (token ,memoryview ): 1489token_text = token .tobytes ().decode (encoding = "utf-8" ) 1490else : 1491token_text = token 1492 1493# Some models mark some added tokens which ought to be control tokens as not special. 1494# (e.g. command-r, command-r-plus, deepseek-coder, gemma{,-2}) 1495seems_special = token_text in ( 1496"<pad>" ,# deepseek-coder 1497"<mask>" ,"<2mass>" ,"[@BOS@]" ,# gemma{,-2} 1498 ) 1499 1500seems_special = seems_special or (token_text .startswith ("<|" )and token_text .endswith ("|>" )) 1501seems_special = seems_special or (token_text .startswith ("<|" )and token_text .endswith ("|>" ))# deepseek-coder 1502 1503# TODO: should these be marked as UNUSED instead? (maybe not) 1504seems_special = seems_special or (token_text .startswith ("<unused" )and token_text .endswith (">" ))# gemma{,-2} 1505 1506return seems_special 1507 1508# used for GPT-2 BPE and WordPiece vocabs 1509def get_vocab_base (self )-> tuple [list [str ],list [int ],str ]: 1510tokens :list [str ]= [] 1511toktypes :list [int ]= [] 1512 1513from transformers import AutoTokenizer 1514tokenizer = AutoTokenizer .from_pretrained (self .dir_model ) 1515vocab_size = self .hparams .get ("vocab_size" ,len (tokenizer .vocab ))# ty: ignore[unresolved-attribute] 1516assert max (tokenizer .vocab .values ())< vocab_size # ty: ignore[unresolved-attribute] 1517 1518tokpre = self .get_vocab_base_pre (tokenizer ) 1519 1520reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in tokenizer .vocab .items ()}# ty: ignore[unresolved-attribute] 1521added_vocab = tokenizer .get_added_vocab ()# ty: ignore[unresolved-attribute] 1522 1523added_tokens_decoder = tokenizer .added_tokens_decoder # ty: ignore[unresolved-attribute] 1524 1525for i in range (vocab_size ): 1526if i not in reverse_vocab : 1527tokens .append (f"[PAD { i } ]" ) 1528toktypes .append (gguf .TokenType .UNUSED ) 1529else : 1530token :str = reverse_vocab [i ] 1531if token in added_vocab : 1532# The tokenizer in llama.cpp assumes the CONTROL and USER_DEFINED tokens are pre-normalized. 1533# To avoid unexpected issues - we make sure to normalize non-normalized tokens 1534if not added_tokens_decoder [i ].normalized : 1535previous_token = token 1536token = tokenizer .decode (tokenizer .encode (token ,add_special_tokens = False ))# ty: ignore[unresolved-attribute, invalid-assignment] 1537if previous_token != token : 1538logger .info (f" { repr ( previous_token ) } is encoded and decoded back to { repr ( token ) } using AutoTokenizer" ) 1539 1540if added_tokens_decoder [i ].special or self .does_token_look_special (token ): 1541toktypes .append (gguf .TokenType .CONTROL ) 1542else : 1543# NOTE: this was added for Gemma. 1544# Encoding and decoding the tokens above isn't sufficient for this case. 1545token = token .replace (b"\xe2\x96\x81" .decode ("utf-8" )," " )# pre-normalize user-defined spaces 1546toktypes .append (gguf .TokenType .USER_DEFINED ) 1547else : 1548toktypes .append (gguf .TokenType .NORMAL ) 1549tokens .append (token ) 1550 1551return tokens ,toktypes ,tokpre 1552 1553# NOTE: this function is generated by convert_hf_to_gguf_update.py 1554# do not modify it manually! 1555# ref: https://github.com/ggml-org/llama.cpp/pull/6920 1556# Marker: Start get_vocab_base_pre 1557def get_vocab_base_pre (self ,tokenizer )-> str : 1558# encoding this string and hashing the resulting tokens would (hopefully) give us a unique identifier that 1559# is specific for the BPE pre-tokenizer used by the model 1560# we will use this unique identifier to write a "tokenizer.ggml.pre" entry in the GGUF file which we can 1561# use in llama.cpp to implement the same pre-tokenizer 1562 1563chktxt = '\n \n\n \n\n\n \t \t\t \t\n \n \n \n \n🚀 (normal) 😶\u200d🌫️ (multiple emojis concatenated) ✅ 🦙🦙 3 33 333 3333 33333 333333 3333333 33333333 3.3 3..3 3...3 កាន់តែពិសេសអាច😁 ?我想在apple工作1314151天~ ------======= нещо на Български \'\'\'\'\'\'```````""""......!!!!!!?????? I\'ve been \'told he\'s there, \'RE you sure? \'M not sure I\'ll make it, \'D you like some tea? We\'Ve a\'lL' 1564 1565chktok = tokenizer .encode (chktxt ) 1566chkhsh = sha256 (str (chktok ).encode ()).hexdigest () 1567 1568logger .debug (f"chktok: { chktok } " ) 1569logger .debug (f"chkhsh: { chkhsh } " ) 1570 1571res = None 1572 1573# NOTE: if you get an error here, you need to update the convert_hf_to_gguf_update.py script 1574# or pull the latest version of the model from Huggingface 1575# don't edit the hashes manually! 1576if chkhsh == "b6e8e1518dc4305be2fe39c313ed643381c4da5db34a98f6a04c093f8afbe99b" : 1577# ref: https://huggingface.co/THUDM/glm-4-9b-chat 1578res = "chatglm-bpe" 1579if chkhsh == "81d72c7348a9f0ebe86f23298d37debe0a5e71149e29bd283904c02262b27516" : 1580# ref: https://huggingface.co/THUDM/glm-4-9b-chat 1581res = "chatglm-bpe" 1582if chkhsh == "a1336059768a55c99a734006ffb02203cd450fed003e9a71886c88acf24fdbc2" : 1583# ref: https://huggingface.co/THUDM/glm-4-9b-hf 1584res = "glm4" 1585if chkhsh == "9ca2dd618e8afaf09731a7cf6e2105b373ba6a1821559f258b272fe83e6eb902" : 1586# ref: https://huggingface.co/zai-org/GLM-4.5-Air 1587res = "glm4" 1588if chkhsh == "cdf5f35325780597efd76153d4d1c16778f766173908894c04afc20108536267" : 1589# ref: https://huggingface.co/zai-org/GLM-4.7-Flash 1590res = "glm4" 1591if chkhsh == "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35" : 1592# ref: https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0 1593res = "minerva-7b" 1594if chkhsh == "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664" : 1595# ref: https://huggingface.co/tencent/Hunyuan-A13B-Instruct 1596res = "hunyuan" 1597if chkhsh == "bba3b3366b646dbdded5dbc42d59598b849371afc42f7beafa914afaa5b70aa6" : 1598# ref: https://huggingface.co/tencent/Hunyuan-4B-Instruct 1599res = "hunyuan-dense" 1600if chkhsh == "e6ddf9c6686791c12d698d34c31ab9be1fea9af5a3d9a6909783ab382198ae1c" : 1601# ref: https://huggingface.co/tencent/Hy4-preview 1602res = "hy_v4" 1603if chkhsh == "a6b57017d60e6edb4d88ecc2845188e0eb333a70357e45dcc9b53964a73bbae6" : 1604# ref: https://huggingface.co/tiiuae/Falcon-H1-0.5B-Base 1605res = "falcon-h1" 1606if chkhsh == "60476e1243776c4fb1b993dbd7a5f15ac22f83c80afdf425fa5ae01c8d44ef86" : 1607# ref: https://huggingface.co/tiiuae/Falcon-H1-1B-Base 1608res = "falcon-h1" 1609if chkhsh == "3eda48b4c4dc7de733d1a8b3e3b4a85243dbbf704da2ee9d42c6beced8897896" : 1610# ref: https://huggingface.co/tiiuae/Falcon-H1-7B-Base 1611res = "falcon-h1" 1612if chkhsh == "48f8e02c0359c0bbdd82f26909171fac1c18a457bb47573ed1fe3bbb2c1cfd4b" : 1613# ref: https://huggingface.co/tiiuae/Falcon-H1-34B-Base 1614res = "falcon-h1" 1615if chkhsh == "81212dc7cdb7e0c1074ca62c5aeab0d43c9f52b8a737be7b12a777c953027890" : 1616# ref: https://huggingface.co/moonshotai/Kimi-K2-Base 1617res = "kimi-k2" 1618if chkhsh == "d4540891389ea895b53b399da6ac824becc30f2fba0e9ddbb98f92e55ca0e97c" : 1619# ref: https://huggingface.co/Qwen/Qwen3-Embedding-0.6B 1620res = "qwen2" 1621if chkhsh == "1444df51289cfa8063b96f0e62b1125440111bc79a52003ea14b6eac7016fd5f" : 1622# ref: https://huggingface.co/openbmb/MiniCPM-V-4_6 1623res = "qwen35" 1624if chkhsh == "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273" : 1625# ref: https://huggingface.co/alvarobartt/grok-2-tokenizer 1626res = "grok-2" 1627if chkhsh == "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df" : 1628# ref: https://huggingface.co/aari1995/German_Semantic_V3 1629res = "jina-v2-de" 1630if chkhsh == "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4" : 1631# ref: https://huggingface.co/evilfreelancer/ruGPT3XL 1632res = "gpt-2" 1633if chkhsh == "9e454714343b69b99b71795c1d27a68c2a1d15dab111f4d353109f966af29da7" : 1634# ref: https://huggingface.co/LiquidAI/LFM2.5-8B-A1B 1635res = "lfm2" 1636if chkhsh == "0a766d034107bc736a3f2dc4968fd62e54a3570f1454443e0c5a4cc6bd7941ed" : 1637# ref: https://huggingface.co/XHToken/Spark-X2.5-1.7B 1638res = "spark2_5" 1639if chkhsh == "0ef9807a4087ebef797fc749390439009c3b9eda9ad1a097abbe738f486c01e5" : 1640# ref: https://huggingface.co/meta-llama/Meta-Llama-3-8B 1641res = "llama-bpe" 1642if chkhsh == "049ecf7629871e3041641907f3de7c733e4dbfdc736f57d882ba0b0845599754" : 1643# ref: https://huggingface.co/deepseek-ai/deepseek-llm-7b-base 1644res = "deepseek-llm" 1645if chkhsh == "347715f544604f9118bb75ed199f68779f423cabb20db6de6f31b908d04d7821" : 1646# ref: https://huggingface.co/deepseek-ai/deepseek-coder-6.7b-base 1647res = "deepseek-coder" 1648if chkhsh == "8aeee3860c56296a157a1fe2fad249ec40aa59b1bb5709f4ade11c4e6fe652ed" : 1649# ref: https://huggingface.co/tiiuae/falcon-7b 1650res = "falcon" 1651if chkhsh == "0876d13b50744004aa9aeae05e7b0647eac9d801b5ba4668afc01e709c15e19f" : 1652# ref: https://huggingface.co/BAAI/bge-small-en-v1.5 1653res = "bert-bge" 1654if chkhsh == "9d032fcbd5501f4a38150912590928bfb36091efb5df11b8e2124b0390e3fb1e" : 1655# ref: https://huggingface.co/tiiuae/Falcon3-7B-Base 1656res = "falcon3" 1657if chkhsh == "8e62295832751ca1e8f92f2226f403dea30dc5165e448b5bfa05af5340c64ec7" : 1658# ref: https://huggingface.co/BAAI/bge-large-zh-v1.5 1659res = "bert-bge-large" 1660if chkhsh == "b6dc8df998e1cfbdc4eac8243701a65afe638679230920b50d6f17d81c098166" : 1661# ref: https://huggingface.co/mosaicml/mpt-7b 1662res = "mpt" 1663if chkhsh == "35d91631860c815f952d711435f48d356ebac988362536bed955d43bfa436e34" : 1664# ref: https://huggingface.co/bigcode/starcoder2-3b 1665res = "starcoder" 1666if chkhsh == "3ce83efda5659b07b1ad37ca97ca5797ea4285d9b9ab0dc679e4a720c9da7454" : 1667# ref: https://huggingface.co/openai-community/gpt2 1668res = "gpt-2" 1669if chkhsh == "32d85c31273f8019248f2559fed492d929ea28b17e51d81d3bb36fff23ca72b3" : 1670# ref: https://huggingface.co/stabilityai/stablelm-2-zephyr-1_6b 1671res = "stablelm2" 1672if chkhsh == "6221ad2852e85ce96f791f476e0b390cf9b474c9e3d1362f53a24a06dc8220ff" : 1673# ref: https://huggingface.co/smallcloudai/Refact-1_6-base 1674res = "refact" 1675if chkhsh == "9c2227e4dd922002fb81bde4fc02b0483ca4f12911410dee2255e4987644e3f8" : 1676# ref: https://huggingface.co/CohereForAI/c4ai-command-r-v01 1677res = "command-r" 1678if chkhsh == "d772b220ace2baec124bed8cfafce0ead7d6c38a4b65ef11261cf9d5d62246d1" : 1679# ref: https://huggingface.co/CohereLabs/tiny-aya-base 1680res = "tiny_aya" 1681if chkhsh == "52df12b4c8d4176e7481aab4b6e8454d1fd0a210a04a574f6d4e067d10e23c3e" : 1682# ref: https://huggingface.co/CohereLabs/North-Mini-Code-1.0 1683res = "cohere2moe" 1684if chkhsh == "e636dc30a262dcc0d8c323492e32ae2b70728f4df7dfe9737d9f920a282b8aea" : 1685# ref: https://huggingface.co/Qwen/Qwen1.5-7B 1686res = "qwen2" 1687if chkhsh == "b6dc8df998e1cfbdc4eac8243701a65afe638679230920b50d6f17d81c098166" : 1688# ref: https://huggingface.co/allenai/OLMo-1.7-7B-hf 1689res = "olmo" 1690if chkhsh == "a8594e3edff7c29c003940395316294b2c623e09894deebbc65f33f1515df79e" : 1691# ref: https://huggingface.co/databricks/dbrx-base 1692res = "dbrx" 1693if chkhsh == "c7699093ba4255a91e702aa38a596aa81669f3525dae06c2953267dde580f448" : 1694# ref: https://huggingface.co/jinaai/jina-reranker-v1-tiny-en 1695res = "jina-v1-en" 1696if chkhsh == "0876d13b50744004aa9aeae05e7b0647eac9d801b5ba4668afc01e709c15e19f" : 1697# ref: https://huggingface.co/jinaai/jina-embeddings-v2-base-en 1698res = "jina-v2-en" 1699if chkhsh == "171aeeedd6fb548d418a7461d053f11b6f1f1fc9b387bd66640d28a4b9f5c643" : 1700# ref: https://huggingface.co/jinaai/jina-embeddings-v2-base-es 1701res = "jina-v2-es" 1702if chkhsh == "27949a2493fc4a9f53f5b9b029c82689cfbe5d3a1929bb25e043089e28466de6" : 1703# ref: https://huggingface.co/jinaai/jina-embeddings-v2-base-de 1704res = "jina-v2-de" 1705if chkhsh == "a023e9fdc5a11f034d3ef515b92350e56fb2af1f66c6b6811a4444ea9bf8763d" : 1706# ref: https://huggingface.co/jinaai/jina-embeddings-v5-text-nano 1707res = "jina-v5-nano" 1708if chkhsh == "c136ed14d01c2745d4f60a9596ae66800e2b61fa45643e72436041855ad4089d" : 1709# ref: https://huggingface.co/abacusai/Smaug-Llama-3-70B-Instruct 1710res = "smaug-bpe" 1711if chkhsh == "c7ea5862a53e4272c035c8238367063e2b270d51faa48c0f09e9d5b54746c360" : 1712# ref: https://huggingface.co/LumiOpen/Poro-34B-chat 1713res = "poro-chat" 1714if chkhsh == "7967bfa498ade6b757b064f31e964dddbb80f8f9a4d68d4ba7998fcf281c531a" : 1715# ref: https://huggingface.co/jinaai/jina-embeddings-v2-base-code 1716res = "jina-v2-code" 1717if chkhsh == "7fc505bd3104ca1083b150b17d088b59534ede9bde81f0dd2090967d7fe52cee" : 1718# ref: https://huggingface.co/LumiOpen/Viking-7B 1719res = "viking" 1720if chkhsh == "b53802fb28e26d645c3a310b34bfe07da813026ec7c7716883404d5e0f8b1901" : 1721# ref: https://huggingface.co/core42/jais-13b 1722res = "jais" 1723if chkhsh == "bc5108ee1eb6a3d600cadd065f63190fbd0554dbc9e4bbd6a0d977970afc8d2a" : 1724# ref: https://huggingface.co/inceptionai/Jais-2-8B-Chat 1725res = "jais-2" 1726if chkhsh == "7b3e7548e4308f52a76e8229e4e6cc831195d0d1df43aed21ac6c93da05fec5f" : 1727# ref: https://huggingface.co/WisdomShell/CodeShell-7B 1728res = "codeshell" 1729if chkhsh == "63b97e4253352e6f357cc59ea5b583e3a680eaeaf2632188c2b952de2588485e" : 1730# ref: https://huggingface.co/mistralai/Mistral-Nemo-Base-2407 1731res = "tekken" 1732if chkhsh == "855059429035d75a914d1eda9f10a876752e281a054a7a3d421ef0533e5b6249" : 1733# ref: https://huggingface.co/HuggingFaceTB/SmolLM-135M 1734res = "smollm" 1735if chkhsh == "3c30d3ad1d6b64202cd222813e7736c2db6e1bd6d67197090fc1211fbc612ae7" : 1736# ref: https://huggingface.co/bigscience/bloom 1737res = "bloom" 1738if chkhsh == "bc01ce58980e1db43859146dc51b1758b3b88729b217a74792e9f8d43e479d21" : 1739# ref: https://huggingface.co/TurkuNLP/gpt3-finnish-small 1740res = "gpt3-finnish" 1741if chkhsh == "4e2b24cc4770243d65a2c9ec19770a72f08cffc161adbb73fcbb6b7dd45a0aae" : 1742# ref: https://huggingface.co/LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct 1743res = "exaone" 1744if chkhsh == "fcace8b9cac38ce847670c970cd5892031a753a1ef381abd1d9af00f713da085" : 1745# ref: https://huggingface.co/microsoft/phi-2 1746res = "phi-2" 1747if chkhsh == "60824e3c0d9401f89943cbb2fff727f0e2d4c545ba4df2d6e4f09a6db0f5b450" : 1748# ref: https://huggingface.co/facebook/chameleon-7b 1749res = "chameleon" 1750if chkhsh == "8b5a93ed704057481f240da0be7e7dca721d7f8f4755263b6807227a2cbeae65" : 1751# ref: https://huggingface.co/sentence-transformers/stsb-roberta-base 1752res = "roberta-bpe" 1753if chkhsh == "ad851be1dba641f2e3711822f816db2c265f788b37c63b4e1aeacb9ee92de8eb" : 1754# ref: https://huggingface.co/ai-sage/GigaChat-20B-A3B-instruct 1755res = "gigachat" 1756if chkhsh == "d4c8f286ea6b520b3d495c4455483cfa2302c0cfcd4be05d781b6a8a0a7cdaf1" : 1757# ref: https://huggingface.co/Infinigence/Megrez-3B-Instruct 1758res = "megrez" 1759if chkhsh == "877081d19cf6996e2c4ff0e1236341e9b7bde288f5311a56a937f0afbbb3aeb5" : 1760# ref: https://huggingface.co/deepseek-ai/DeepSeek-V3 1761res = "deepseek-v3" 1762if chkhsh == "b3f499bb4255f8ca19fccd664443283318f2fd2414d5e0b040fbdd0cc195d6c5" : 1763# ref: https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B 1764res = "deepseek-r1-qwen" 1765if chkhsh == "ccc2ef013c104be7bae2965776d611e1d7a8a2a9c547dd93a682c9a9fc80352e" : 1766# ref: https://huggingface.co/Xenova/gpt-4o 1767res = "gpt-4o" 1768if chkhsh == "7dec86086fcc38b66b7bc1575a160ae21cf705be7718b9d5598190d7c12db76f" : 1769# ref: https://huggingface.co/UW/OLMo2-8B-SuperBPE-t180k 1770res = "superbpe" 1771if chkhsh == "1994ffd01900cfb37395608534236ecd63f2bd5995d6cb1004dda1af50240f15" : 1772# ref: https://huggingface.co/trillionlabs/Trillion-7B-preview 1773res = "trillion" 1774if chkhsh == "96a5f08be6259352137b512d4157e333e21df7edd3fcd152990608735a65b224" : 1775# ref: https://huggingface.co/inclusionAI/Ling-lite 1776res = "bailingmoe" 1777if chkhsh == "d353350c764d8c3b39c763113960e4fb4919bea5fbf208a0e3b22e8469dc7406" : 1778# ref: https://huggingface.co/meta-llama/Llama-4-Scout-17B-16E-Instruct 1779res = "llama4" 1780if chkhsh == "0e9433cbbb161f89e264eb32e8e64bfe69e834973ffca5d41d3948a604a3e2a3" : 1781# ref: https://huggingface.co/mistral-community/pixtral-12b 1782res = "pixtral" 1783if chkhsh == "d5f1dd6f980fec569fb218a81a7658ac45fc56b38c5a0adeb1c232fbe04ef5ec" : 1784# ref: https://huggingface.co/ByteDance-Seed/Seed-Coder-8B-Base 1785res = "seed-coder" 1786if chkhsh == "b0a6b1c0bd5998ebd9df08611efde34a4ff03faed45ae09c43e6b31ebd4b94cf" : 1787# ref: https://huggingface.co/skt/A.X-4.0 1788res = "a.x-4.0" 1789if chkhsh == "f6791d196f87ce6b56a7d234be618e0d58f8cda3549416635b2bebcd22cd95c4" : 1790# ref: https://huggingface.co/K-intelligence/Midm-2.0-Base-Instruct 1791res = "midm-2.0" 1792if chkhsh == "169bf0296a13c4d9b7672313f749eb36501d931022de052aad6e36f2bf34dd51" : 1793# ref: https://huggingface.co/LiquidAI/LFM2.5-350M 1794res = "lfm2" 1795if chkhsh == "2085e1638f6c377a0aa4ead21b27bb4cb941bf800df86ed391011769c1758dfb" : 1796# ref: https://huggingface.co/LGAI-EXAONE/EXAONE-4.0-32B 1797res = "exaone4" 1798if chkhsh == "a1e163ecab2e718a4c829d1148b6e86824ec36163bb71941c3dca9cd5ac25756" : 1799# ref: https://huggingface.co/JetBrains/Mellum-4b-base 1800res = "mellum" 1801if chkhsh == "a0b64b4385f123663873756336c085744376d015ff328bb1d901598f63c44152" : 1802# ref: https://huggingface.co/answerdotai/ModernBERT-base 1803res = "modern-bert" 1804if chkhsh == "49fc0303c9e0d2c2c565c510f64b2d9b271276acdcdadff733249eda9f7d59df" : 1805# ref: https://huggingface.co/arcee-ai/Trinity-Tokenizer 1806res = "afmoe" 1807if chkhsh == "9b1be57e70d20d9501b2b3186e792d81181ae36ada3903c26f9fea418cf87206" : 1808# ref: https://huggingface.co/inclusionAI/Ling-mini-base-2.0 1809res = "bailingmoe2" 1810if chkhsh == "53e325976a6e142379c19b09afcae354f2f496f147afa8f9e189a33fe4e3024e" : 1811# ref: https://huggingface.co/ibm-granite/granite-docling-258M 1812res = "granite-docling" 1813if chkhsh == "f4f37b6c8eb9ea29b3eac6bb8c8487c5ab7885f8d8022e67edc1c68ce8403e95" : 1814# ref: https://huggingface.co/MiniMaxAI/MiniMax-M2 1815res = "minimax-m2" 1816if chkhsh == "4a2e2abae11ca2b86d570fc5b44be4d5eb5e72cc8f22dd136a94b37da83ab665" : 1817# ref: https://huggingface.co/KORMo-Team/KORMo-tokenizer 1818res = "kormo" 1819if chkhsh == "9d70134b369a70e5735009b6de918f7581b5211f7c074d1f89f753aea8248af1" : 1820# ref: https://huggingface.co/tencent/Youtu-LLM-2B 1821res = "youtu" 1822if chkhsh == "16389f0a1f51ee53e562ffd51c371dc508639ab0e4261502071836e50e223e91" : 1823# ref: https://huggingface.co/upstage/Solar-Open-100B 1824res = "solar-open" 1825if chkhsh == "6c81ce329e0802883b22eabab0d3fa48357337ef1ecb45443828bf1f6254833f" : 1826# ref: https://huggingface.co/LGAI-EXAONE/K-EXAONE-236B-A23B 1827res = "exaone-moe" 1828if chkhsh == "d30d75d9059f1aa2c19359de71047b3ae408c70875e8a3ccf8c5fba56c9d8af4" : 1829# ref: https://huggingface.co/Qwen/Qwen3.5-9B-Instruct 1830res = "qwen35" 1831if chkhsh == "b4b8ca1f9769494fbd956ebc4c249de6131fb277a4a3345a7a92c7dd7a55808d" : 1832# ref: https://huggingface.co/jdopensource/JoyAI-LLM-Flash 1833res = "joyai-llm" 1834if chkhsh == "e4d54df1ebc1f2b91acd986c5b51aa50837d5faf7c7398e73c1f9e9ee5d19869" : 1835# ref: https://huggingface.co/kakaocorp/kanana-2-30b-a3b-instruct-2601 1836res = "kanana2" 1837if chkhsh == "862f827721df956049dff5ca81a57f29e575280bc622e290d3bf4e35eca29015" : 1838# ref: https://huggingface.co/codefuse-ai/F2LLM-v2-4B 1839res = "f2llmv2" 1840if chkhsh == "62f6fb0a6fd5098caeabb19b07a5c1099cafc8b9c40eab6ea89ece4ec02fbc57" : 1841# ref: https://huggingface.co/sarvamai/sarvam-30b 1842res = "sarvam-moe" 1843if chkhsh == "f728162c1315c26e40249849799b4ba3fe584c32084b4795b03eb295e63cb5af" : 1844# ref: https://huggingface.co/lewtun/talkie-1930-13b-it-hf 1845res = "talkie" 1846if chkhsh == "36f3066e97b7f3994b379aaacde306c1444c6ae84e81a5ae3cd2b7ed3b8c42d4" : 1847# ref: https://huggingface.co/openbmb/MiniCPM5-1B 1848res = "minicpm5" 1849if chkhsh == "f241072145675bf8322086f115aebad05e9f869557a238bf2150a2a417d1bf60" : 1850# ref: https://huggingface.co/ibm-granite/granite-embedding-97m-multilingual-r2 1851res = "granite-embed-multi-97m" 1852if chkhsh == "789696f5946cc0fc59371f39f6097cafed196b3acded6140432f26bbb1ae1669" : 1853# ref: https://huggingface.co/ibm-granite/granite-embedding-311m-multilingual-r2 1854res = "granite-embed-multi-311m" 1855if chkhsh == "9dcf830ee9990cdbf78cc523a5f7bd9ad8f3f9890c2d3581d2785ad10f07049d" : 1856# ref: https://huggingface.co/JetBrains/Mellum2-12B-A2.5B-Base 1857res = "mellum2" 1858if chkhsh == "972da7b59cec44d1f0a490a86c96df53859e486e481563e5dddac155013d87ac" : 1859# ref: https://huggingface.co/poolside/Laguna-XS.2 1860res = "laguna" 1861 1862if res is None : 1863logger .warning ("\n" ) 1864logger .warning ("**************************************************************************************" ) 1865logger .warning ("** WARNING: The BPE pre-tokenizer was not recognized!" ) 1866logger .warning ("** There are 2 possible reasons for this:" ) 1867logger .warning ("** - the model has not been added to convert_hf_to_gguf_update.py yet" ) 1868logger .warning ("** - the pre-tokenization config has changed upstream" ) 1869logger .warning ("** Check your model files and convert_hf_to_gguf_update.py and update them accordingly." ) 1870logger .warning ("** ref: https://github.com/ggml-org/llama.cpp/pull/6920" ) 1871logger .warning ("**" ) 1872logger .warning (f"** chkhsh: { chkhsh } " ) 1873logger .warning ("**************************************************************************************" ) 1874logger .warning ("\n" ) 1875raise NotImplementedError ("BPE pre-tokenizer was not recognized - update get_vocab_base_pre()" ) 1876 1877logger .debug (f"tokenizer.ggml.pre: { repr ( res ) } " ) 1878logger .debug (f"chkhsh: { chkhsh } " ) 1879 1880return res 1881# Marker: End get_vocab_base_pre 1882 1883def _set_vocab_none (self )-> None : 1884self .gguf_writer .add_tokenizer_model ("none" ) 1885 1886def _set_vocab_gpt2 (self )-> None : 1887tokens ,toktypes ,tokpre = self .get_vocab_base () 1888self .gguf_writer .add_tokenizer_model ("gpt2" ) 1889self .gguf_writer .add_tokenizer_pre (tokpre ) 1890self .gguf_writer .add_token_list (tokens ) 1891self .gguf_writer .add_token_types (toktypes ) 1892 1893special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 1894special_vocab .add_to_gguf (self .gguf_writer ) 1895 1896def _set_vocab_whitespace (self )-> None : 1897tokens ,toktypes ,_ = self .get_vocab_base () 1898self .gguf_writer .add_tokenizer_model ("whitespace" ) 1899self .gguf_writer .add_tokenizer_pre ("whitespace" )# pinned, not hash-detected: chktxt hash collides with jina-v1-en 1900self .gguf_writer .add_token_list (tokens ) 1901self .gguf_writer .add_token_types (toktypes ) 1902 1903special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 1904special_vocab .add_to_gguf (self .gguf_writer ) 1905 1906def _set_vocab_hybriddna (self ): 1907from transformers import AutoTokenizer 1908tokenizer = AutoTokenizer .from_pretrained (self .dir_model ,trust_remote_code = True ) 1909vocab_size = self .hparams .get ("vocab_size" ,len (tokenizer .vocab ))# ty: ignore[unresolved-attribute] 1910assert max (tokenizer .vocab .values ())< vocab_size # ty: ignore[unresolved-attribute] 1911 1912reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in tokenizer .vocab .items ()}# ty: ignore[unresolved-attribute] 1913# k-mers can share text with a base-vocab BPE token (e.g. CCCCCC) and get 1914# dropped by get_vocab(); a reserved marker suffix (U+E000) keeps each 1915# k-mer's own id (llama.cpp strips it on detokenization) 1916for kmer in tokenizer .kmers :# ty: ignore[unresolved-attribute] 1917reverse_vocab [tokenizer .dna_token_to_id [kmer ]]= kmer + "\ue000" # ty: ignore[unresolved-attribute] 1918added_vocab = tokenizer .get_added_vocab ()# ty: ignore[unresolved-attribute] 1919added_tokens_decoder = tokenizer .added_tokens_decoder # ty: ignore[unresolved-attribute] 1920 1921tokens :list [str ]= [] 1922toktypes :list [int ]= [] 1923for i in range (vocab_size ): 1924if i not in reverse_vocab : 1925tokens .append (f"[PAD { i } ]" ) 1926toktypes .append (gguf .TokenType .UNUSED ) 1927else : 1928token :str = reverse_vocab [i ] 1929if token in added_vocab : 1930if added_tokens_decoder [i ].special or self .does_token_look_special (token ): 1931toktypes .append (gguf .TokenType .CONTROL ) 1932else : 1933toktypes .append (gguf .TokenType .USER_DEFINED ) 1934else : 1935toktypes .append (gguf .TokenType .NORMAL ) 1936tokens .append (token ) 1937 1938tokpre = self .get_vocab_base_pre (tokenizer ) 1939self .gguf_writer .add_tokenizer_model ("hybriddna" ) 1940self .gguf_writer .add_tokenizer_pre (tokpre ) 1941self .gguf_writer .add_token_list (tokens ) 1942self .gguf_writer .add_token_types (toktypes ) 1943 1944special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 1945special_vocab .add_to_gguf (self .gguf_writer ) 1946 1947def _set_vocab_qwen (self ): 1948from .qwen import QwenModel 1949 1950dir_model = self .dir_model 1951hparams = self .hparams 1952tokens :list [str ]= [] 1953toktypes :list [int ]= [] 1954 1955from transformers import AutoTokenizer 1956tokenizer = AutoTokenizer .from_pretrained (dir_model ,trust_remote_code = True ) 1957vocab_size = hparams ["vocab_size" ] 1958assert max (tokenizer .get_vocab ().values ())< vocab_size # ty: ignore[unresolved-attribute] 1959 1960tokpre = self .get_vocab_base_pre (tokenizer ) 1961 1962merges = [] 1963vocab = {} 1964mergeable_ranks = tokenizer .mergeable_ranks # ty: ignore[unresolved-attribute] 1965for token ,rank in mergeable_ranks .items (): 1966vocab [QwenModel .token_bytes_to_string (token )]= rank 1967if len (token )== 1 : 1968continue 1969merged = QwenModel .bpe (mergeable_ranks ,token ,max_rank = rank ) 1970assert len (merged )== 2 1971merges .append (' ' .join (map (QwenModel .token_bytes_to_string ,merged ))) 1972 1973# for this kind of tokenizer, added_vocab is not a subset of vocab, so they need to be combined 1974added_vocab = tokenizer .special_tokens # ty: ignore[unresolved-attribute] 1975reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in {** vocab ,** added_vocab }.items ()} 1976 1977for i in range (vocab_size ): 1978if i not in reverse_vocab : 1979tokens .append (f"[PAD { i } ]" ) 1980toktypes .append (gguf .TokenType .UNUSED ) 1981elif reverse_vocab [i ]in added_vocab : 1982tokens .append (reverse_vocab [i ]) 1983toktypes .append (gguf .TokenType .CONTROL ) 1984else : 1985tokens .append (reverse_vocab [i ]) 1986toktypes .append (gguf .TokenType .NORMAL ) 1987 1988self .gguf_writer .add_tokenizer_model ("gpt2" ) 1989self .gguf_writer .add_tokenizer_pre (tokpre ) 1990self .gguf_writer .add_token_list (tokens ) 1991self .gguf_writer .add_token_types (toktypes ) 1992 1993special_vocab = gguf .SpecialVocab (dir_model ,load_merges = False ) 1994special_vocab .merges = merges 1995# only add special tokens when they were not already loaded from config.json 1996if len (special_vocab .special_token_ids )== 0 : 1997special_vocab ._set_special_token ("bos" ,tokenizer .special_tokens ["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 1998special_vocab ._set_special_token ("eos" ,tokenizer .special_tokens ["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 1999# this one is usually not in config.json anyway 2000special_vocab ._set_special_token ("unk" ,tokenizer .special_tokens ["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 2001special_vocab .add_to_gguf (self .gguf_writer ) 2002 2003def _set_vocab_sentencepiece (self ,add_to_gguf = True ): 2004tokens ,scores ,toktypes = self ._create_vocab_sentencepiece () 2005 2006self .gguf_writer .add_tokenizer_model ("llama" ) 2007self .gguf_writer .add_tokenizer_pre ("default" ) 2008self .gguf_writer .add_token_list (tokens ) 2009self .gguf_writer .add_token_scores (scores ) 2010self .gguf_writer .add_token_types (toktypes ) 2011 2012special_vocab = gguf .SpecialVocab (self .dir_model ,n_vocab = len (tokens )) 2013special_vocab .add_to_gguf (self .gguf_writer ) 2014 2015def _create_vocab_sentencepiece (self ): 2016from sentencepiece import SentencePieceProcessor 2017 2018tokenizer_path = self .dir_model / 'tokenizer.model' 2019 2020if not tokenizer_path .is_file (): 2021raise FileNotFoundError (f"File not found: { tokenizer_path } " ) 2022 2023tokenizer = SentencePieceProcessor () 2024tokenizer .LoadFromFile (str (tokenizer_path )) 2025 2026vocab_size = self .find_hparam ([ 2027"vocab_size_per_layer_input" ,# gemma3n 2028"vocab_size" , 2029 ],optional = True )or tokenizer .vocab_size () 2030 2031tokens :list [bytes ]= [f"[PAD { i } ]" .encode ("utf-8" )for i in range (vocab_size )] 2032scores :list [float ]= [- 10000.0 ]* vocab_size 2033toktypes :list [int ]= [SentencePieceTokenTypes .UNUSED ]* vocab_size 2034 2035for token_id in range (tokenizer .vocab_size ()): 2036if token_id >= vocab_size : 2037logger .warning (f'ignore tokens from { token_id } : id is out of range, max= { vocab_size - 1 } ' ) 2038break 2039 2040piece = tokenizer .IdToPiece (token_id ) 2041text = piece .encode ("utf-8" ) 2042score = tokenizer .GetScore (token_id ) 2043 2044toktype = SentencePieceTokenTypes .NORMAL 2045if tokenizer .IsUnknown (token_id ): 2046toktype = SentencePieceTokenTypes .UNKNOWN 2047elif tokenizer .IsControl (token_id ): 2048toktype = SentencePieceTokenTypes .CONTROL 2049elif tokenizer .IsUnused (token_id ): 2050toktype = SentencePieceTokenTypes .UNUSED 2051elif tokenizer .IsByte (token_id ): 2052toktype = SentencePieceTokenTypes .BYTE 2053 2054tokens [token_id ]= text 2055scores [token_id ]= score 2056toktypes [token_id ]= toktype 2057 2058added_tokens_file = self .dir_model / 'added_tokens.json' 2059if added_tokens_file .is_file (): 2060with open (added_tokens_file ,"r" ,encoding = "utf-8" )as f : 2061added_tokens_json = json .load (f ) 2062for key in added_tokens_json : 2063token_id = added_tokens_json [key ] 2064if token_id >= vocab_size : 2065logger .warning (f'ignore token { token_id } : id is out of range, max= { vocab_size - 1 } ' ) 2066continue 2067 2068tokens [token_id ]= key .encode ("utf-8" ) 2069scores [token_id ]= - 1000.0 2070toktypes [token_id ]= SentencePieceTokenTypes .USER_DEFINED 2071 2072tokenizer_config_file = self .dir_model / 'tokenizer_config.json' 2073if tokenizer_config_file .is_file (): 2074with open (tokenizer_config_file ,"r" ,encoding = "utf-8" )as f : 2075tokenizer_config_json = json .load (f ) 2076added_tokens_decoder = tokenizer_config_json .get ("added_tokens_decoder" , {}) 2077for token_id ,token_data in added_tokens_decoder .items (): 2078token_id = int (token_id ) 2079token :str = token_data ["content" ] 2080if token_id >= vocab_size : 2081logger .warning (f'ignore token { token_id } : id is out of range, max= { vocab_size - 1 } ' ) 2082continue 2083if toktypes [token_id ]!= SentencePieceTokenTypes .UNUSED : 2084if tokens [token_id ]!= token .encode ("utf-8" ): 2085logger .warning (f'replacing token { token_id } : { tokens [ token_id ]. decode ( "utf-8" )!r } -> { token !r } ' ) 2086if token_data .get ("special" )or self .does_token_look_special (token ): 2087toktypes [token_id ]= SentencePieceTokenTypes .CONTROL 2088else : 2089token = token .replace (b"\xe2\x96\x81" .decode ("utf-8" )," " )# pre-normalize user-defined spaces 2090toktypes [token_id ]= SentencePieceTokenTypes .USER_DEFINED 2091 2092scores [token_id ]= - 1000.0 2093tokens [token_id ]= token .encode ("utf-8" ) 2094 2095if vocab_size > len (tokens ): 2096pad_count = vocab_size - len (tokens ) 2097logger .debug (f"Padding vocab with { pad_count } token(s) - [PAD1] through [PAD { pad_count } ]" ) 2098for i in range (1 ,pad_count + 1 ): 2099tokens .append (bytes (f"[PAD { i } ]" ,encoding = "utf-8" )) 2100scores .append (- 1000.0 ) 2101toktypes .append (SentencePieceTokenTypes .UNUSED ) 2102 2103return tokens ,scores ,toktypes 2104 2105def _set_vocab_llama_hf (self ): 2106vocab = gguf .LlamaHfVocab (self .dir_model ) 2107tokens = [] 2108scores = [] 2109toktypes = [] 2110 2111for text ,score ,toktype in vocab .all_tokens (): 2112tokens .append (text ) 2113scores .append (score ) 2114toktypes .append (toktype ) 2115 2116assert len (tokens )== vocab .vocab_size 2117 2118self .gguf_writer .add_tokenizer_model ("llama" ) 2119self .gguf_writer .add_tokenizer_pre ("default" ) 2120self .gguf_writer .add_token_list (tokens ) 2121self .gguf_writer .add_token_scores (scores ) 2122self .gguf_writer .add_token_types (toktypes ) 2123 2124special_vocab = gguf .SpecialVocab (self .dir_model ,n_vocab = len (tokens )) 2125special_vocab .add_to_gguf (self .gguf_writer ) 2126 2127def _set_vocab_rwkv_world (self ): 2128assert (self .dir_model / "rwkv_vocab_v20230424.txt" ).is_file () 2129vocab_size = self .hparams .get ("vocab_size" ,65536 ) 2130 2131tokens :list [bytes ]= ['<s>' .encode ("utf-8" )] 2132toktypes :list [int ]= [gguf .TokenType .CONTROL ] 2133 2134with open (self .dir_model / "rwkv_vocab_v20230424.txt" ,"r" ,encoding = "utf-8" )as f : 2135lines = f .readlines () 2136for line in lines : 2137parts = line .split (' ' ) 2138assert len (parts )>= 3 2139token ,token_len = ast .literal_eval (' ' .join (parts [1 :- 1 ])),int (parts [- 1 ]) 2140token = token .encode ("utf-8" )if isinstance (token ,str )else token 2141assert isinstance (token ,bytes ) 2142assert len (token )== token_len 2143token_text :str = repr (token )[2 :- 1 ]# "b'\xff'" -> "\xff" 2144tokens .append (token_text .encode ("utf-8" )) 2145toktypes .append (gguf .TokenType .NORMAL ) 2146remainder = vocab_size - len (tokens ) 2147assert remainder >= 0 2148for i in range (len (tokens ),vocab_size ): 2149tokens .append (f"[PAD { i } ]" .encode ("utf-8" )) 2150toktypes .append (gguf .TokenType .UNUSED ) 2151 2152self .gguf_writer .add_tokenizer_model ("rwkv" ) 2153self .gguf_writer .add_token_list (tokens ) 2154self .gguf_writer .add_token_types (toktypes ) 2155special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = False ) 2156if special_vocab .chat_template is None : 2157template_path = Path (__file__ ).parent .parent / "models" / "templates" / "llama-cpp-rwkv-world.jinja" 2158if template_path .is_file (): 2159with open (template_path ,"r" ,encoding = "utf-8" )as f : 2160template = f .read () 2161else : 2162template = "rwkv-world" 2163special_vocab .chat_template = template 2164# hack: Add '\n\n' as the EOT token to make it chat normally 2165special_vocab ._set_special_token ("eot" ,261 ) 2166# hack: Override these as they have already been set (incorrectly) 2167special_vocab .special_token_ids ["bos" ]= 0 2168special_vocab .special_token_ids ["eos" ]= 0 2169 2170special_vocab .add_to_gguf (self .gguf_writer ) 2171 2172def _set_vocab_builtin (self ,model_name :Literal ["gpt-neox" ,"llama-spm" ],vocab_size :int ): 2173tokenizer_path = Path (sys .path [0 ])/ "models" / f"ggml-vocab- { model_name } .gguf" 2174logger .warning (f"Using tokenizer from ' { os . path . relpath ( tokenizer_path , os . getcwd ()) } '" ) 2175vocab_reader = gguf .GGUFReader (tokenizer_path ,"r" ) 2176 2177default_pre = "mpt" if model_name == "gpt-neox" else "default" 2178 2179field = vocab_reader .get_field (gguf .Keys .Tokenizer .MODEL ) 2180assert field # tokenizer model 2181self .gguf_writer .add_tokenizer_model (bytes (field .parts [- 1 ]).decode ("utf-8" )) 2182 2183field = vocab_reader .get_field (gguf .Keys .Tokenizer .PRE ) 2184self .gguf_writer .add_tokenizer_pre (bytes (field .parts [- 1 ]).decode ("utf-8" )if field else default_pre ) 2185 2186field = vocab_reader .get_field (gguf .Keys .Tokenizer .LIST ) 2187assert field # token list 2188self .gguf_writer .add_token_list ([bytes (field .parts [i ])for i in field .data ][:vocab_size ]) 2189 2190if model_name == "llama-spm" : 2191field = vocab_reader .get_field (gguf .Keys .Tokenizer .SCORES ) 2192assert field # token scores 2193self .gguf_writer .add_token_scores ([field .parts [i ].tolist ()[0 ]for i in field .data ][:vocab_size ]) 2194 2195field = vocab_reader .get_field (gguf .Keys .Tokenizer .TOKEN_TYPE ) 2196assert field # token types 2197self .gguf_writer .add_token_types ([field .parts [i ].tolist ()[0 ]for i in field .data ][:vocab_size ]) 2198 2199if model_name != "llama-spm" : 2200field = vocab_reader .get_field (gguf .Keys .Tokenizer .MERGES ) 2201assert field # token merges 2202self .gguf_writer .add_token_merges ([bytes (field .parts [i ])for i in field .data ]) 2203 2204if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .BOS_ID ))is not None : 2205self .gguf_writer .add_bos_token_id (field .parts [- 1 ].tolist ()[0 ]) 2206if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .EOS_ID ))is not None : 2207self .gguf_writer .add_eos_token_id (field .parts [- 1 ].tolist ()[0 ]) 2208if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .UNK_ID ))is not None : 2209self .gguf_writer .add_unk_token_id (field .parts [- 1 ].tolist ()[0 ]) 2210if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .PAD_ID ))is not None : 2211self .gguf_writer .add_pad_token_id (field .parts [- 1 ].tolist ()[0 ]) 2212if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .ADD_BOS ))is not None : 2213self .gguf_writer .add_add_bos_token (field .parts [- 1 ].tolist ()[0 ]) 2214if (field := vocab_reader .get_field (gguf .Keys .Tokenizer .ADD_EOS ))is not None : 2215self .gguf_writer .add_add_eos_token (field .parts [- 1 ].tolist ()[0 ]) 2216 2217def _try_set_pooling_type (self )-> None : 2218# get pooling path 2219pooling_path = None 2220module_path = self .dir_model / "modules.json" 2221if module_path .is_file (): 2222with open (module_path ,encoding = "utf-8" )as f : 2223modules = json .load (f ) 2224for mod in modules : 2225if mod ["type" ].endswith ("Pooling" ): 2226pooling_path = mod ["path" ] 2227break 2228 2229mode_mapping = { 2230"mean" :gguf .PoolingType .MEAN , 2231"cls" :gguf .PoolingType .CLS , 2232"lasttoken" :gguf .PoolingType .LAST , 2233 } 2234 2235# get pooling type 2236if pooling_path is not None : 2237with open (self .dir_model / pooling_path / "config.json" ,encoding = "utf-8" )as f : 2238pooling = json .load (f ) 2239if pooling .get ("pooling_mode_mean_tokens" ): 2240pooling_type = gguf .PoolingType .MEAN 2241elif pooling .get ("pooling_mode_cls_token" ): 2242pooling_type = gguf .PoolingType .CLS 2243elif pooling .get ("pooling_mode_lasttoken" ): 2244pooling_type = gguf .PoolingType .LAST 2245elif (pooling_mode := pooling .get ("pooling_mode" ))in mode_mapping : 2246pooling_type = mode_mapping [pooling_mode ] 2247else : 2248raise NotImplementedError ("Only MEAN, CLS, and LAST pooling types supported" ) 2249self .gguf_writer .add_pooling_type (pooling_type ) 2250 2251def _set_vocab_glmedge (self ): 2252from transformers import AutoTokenizer 2253tokenizer = AutoTokenizer .from_pretrained (self .dir_model ) 2254special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 2255tokens ,toktypes ,tokpre = self .get_vocab_base () 2256self .gguf_writer .add_tokenizer_model ("gpt2" ) 2257self .gguf_writer .add_tokenizer_pre (tokpre ) 2258self .gguf_writer .add_token_list (tokens ) 2259self .gguf_writer .add_token_types (toktypes ) 2260special_vocab ._set_special_token ("eos" ,tokenizer .get_added_vocab ()["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 2261special_vocab ._set_special_token ("eot" ,tokenizer .get_added_vocab ()["<|user|>" ])# ty: ignore[unresolved-attribute] 2262special_vocab ._set_special_token ("unk" ,tokenizer .get_added_vocab ()["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 2263special_vocab ._set_special_token ("bos" ,tokenizer .get_added_vocab ()["<|endoftext|>" ])# ty: ignore[unresolved-attribute] 2264special_vocab .add_to_gguf (self .gguf_writer ) 2265 2266def _set_vocab_glm (self ): 2267from transformers import AutoTokenizer 2268tokenizer = AutoTokenizer .from_pretrained (self .dir_model ) 2269special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 2270tokens ,toktypes ,tokpre = self .get_vocab_base () 2271self .gguf_writer .add_tokenizer_model ("gpt2" ) 2272self .gguf_writer .add_tokenizer_pre (tokpre ) 2273self .gguf_writer .add_token_list (tokens ) 2274self .gguf_writer .add_token_types (toktypes ) 2275# Special tokens 2276# Note: Using <|endoftext|> (151329) for eot causes endless generation 2277special_vocab ._set_special_token ("bos" ,tokenizer .get_added_vocab ()["[gMASK]" ])# ty: ignore[unresolved-attribute] # 151331 2278special_vocab ._set_special_token ("eot" ,tokenizer .get_added_vocab ()["<|user|>" ])# ty: ignore[unresolved-attribute] # 151336 2279special_vocab ._set_special_token ("unk" ,tokenizer .get_added_vocab ()["<|endoftext|>" ])# ty: ignore[unresolved-attribute] # 151329 2280special_vocab ._set_special_token ("eom" ,tokenizer .get_added_vocab ()["<|observation|>" ])# ty: ignore[unresolved-attribute] # 151338 2281special_vocab .add_to_gguf (self .gguf_writer ) 2282 2283def _set_vocab_interns1 (self ): 2284tokens :list [str ]= [] 2285toktypes :list [int ]= [] 2286 2287from transformers import AutoTokenizer 2288tokenizer = AutoTokenizer .from_pretrained (self .dir_model ,trust_remote_code = True ) 2289vocab = getattr (tokenizer ,'vocab' ,tokenizer .get_vocab ())# ty: ignore[unresolved-attribute] 2290vocab_size = self .hparams .get ("vocab_size" ,len (vocab )) 2291assert max (vocab .values ())< vocab_size 2292 2293tokpre = self .get_vocab_base_pre (tokenizer ) 2294 2295reverse_vocab = {id_ :encoded_tok for encoded_tok ,id_ in vocab .items ()} 2296added_vocab = tokenizer .get_added_vocab ()# ty: ignore[unresolved-attribute] 2297 2298added_tokens_decoder = tokenizer .added_tokens_decoder # ty: ignore[unresolved-attribute] 2299 2300for i in range (vocab_size ): 2301if i not in reverse_vocab : 2302tokens .append (f"[PAD { i } ]" ) 2303toktypes .append (gguf .TokenType .UNUSED ) 2304else : 2305token :str = reverse_vocab [i ] 2306if token in added_vocab : 2307# The tokenizer in llama.cpp assumes the CONTROL and USER_DEFINED tokens are pre-normalized. 2308# To avoid unexpected issues - we make sure to normalize non-normalized tokens 2309if not added_tokens_decoder [i ].normalized : 2310previous_token = token 2311token = tokenizer .decode (tokenizer .encode (token ,add_special_tokens = False ))# ty: ignore[unresolved-attribute, invalid-assignment] 2312if previous_token != token : 2313logger .info (f" { repr ( previous_token ) } is encoded and decoded back to { repr ( token ) } using AutoTokenizer" ) 2314 2315if added_tokens_decoder [i ].special or self .does_token_look_special (token ): 2316toktypes .append (gguf .TokenType .CONTROL ) 2317else : 2318toktypes .append (gguf .TokenType .USER_DEFINED ) 2319else : 2320toktypes .append (gguf .TokenType .NORMAL ) 2321tokens .append (token ) 2322 2323self .gguf_writer .add_tokenizer_model ("gpt2" ) 2324self .gguf_writer .add_tokenizer_pre (tokpre ) 2325self .gguf_writer .add_token_list (tokens ) 2326self .gguf_writer .add_token_types (toktypes ) 2327 2328special_vocab = gguf .SpecialVocab (self .dir_model ,load_merges = True ) 2329special_vocab ._set_special_token ("bos" ,151643 ) 2330special_vocab .add_to_gguf (self .gguf_writer ) 2331 2332def _set_vocab_mistral (self ): 2333from .mistral import MistralModel 2334 2335if not _mistral_common_installed : 2336raise ImportError (_mistral_import_error_msg ) 2337 2338vocab = MistralVocab (self .dir_model ) 2339logger .info ( 2340f"Converting tokenizer { vocab . tokenizer_type } of size { vocab . vocab_size } ." 2341 ) 2342 2343self .gguf_writer .add_tokenizer_model (vocab .gguf_tokenizer_model ) 2344 2345tokens = [] 2346scores = [] 2347toktypes = [] 2348 2349for text ,score ,toktype in vocab .all_tokens (): 2350tokens .append (text ) 2351scores .append (score ) 2352toktypes .append (toktype ) 2353 2354assert len (tokens )== vocab .vocab_size , ( 2355f"token count ( { len ( tokens ) } ) != vocab size ( { vocab . vocab_size } )" 2356 ) 2357 2358if vocab .tokenizer_type == MistralTokenizerType .tekken : 2359self .gguf_writer .add_tokenizer_pre ("tekken" ) 2360self .gguf_writer .add_token_merges ( 2361vocab .extract_vocab_merges_from_model () 2362 ) 2363 2364logger .info ( 2365f"Setting bos, eos, unk and pad token IDs to { vocab . bos_id } , { vocab . eos_id } , { vocab . unk_id } , { vocab . pad_id } ." 2366 ) 2367 2368self .gguf_writer .add_bos_token_id (vocab .bos_id ) 2369self .gguf_writer .add_eos_token_id (vocab .eos_id ) 2370self .gguf_writer .add_unk_token_id (vocab .unk_id ) 2371self .gguf_writer .add_pad_token_id (vocab .pad_id ) 2372 2373self .gguf_writer .add_token_list (tokens ) 2374self .gguf_writer .add_token_scores (scores ) 2375self .gguf_writer .add_token_types (toktypes ) 2376self .gguf_writer .add_vocab_size (vocab .vocab_size ) 2377 2378self .gguf_writer .add_add_bos_token (True ) 2379self .gguf_writer .add_add_eos_token (False ) 2380 2381local_template_file_path = self .dir_model / "chat_template.jinja" 2382 2383if self .is_mistral_format and local_template_file_path .is_file (): 2384# Ministral-3 and other new Mistral models come with chat templates. 2385# ref: https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512/tree/main 2386logger .info ("Using an existing Mistral local chat template." ) 2387 2388with open (local_template_file_path ,"r" ,encoding = "utf-8" )as f : 2389template = f .read () 2390elif not self .is_mistral_format or not self .disable_mistral_community_chat_template : 2391template_dir = Path (__file__ ).parent .parent / "models/templates/" 2392 2393# Log only for Mistral format that the official tokenization and detokenization is via `mistral-common`. 2394if self .is_mistral_format : 2395logger .info ( 2396"Using a Mistral community chat template. These templates can be subject to errors in early days or weeks after a release. " 2397"Mistral recommends to use `mistral-common` to perform tokenization and detokenization." 2398 ) 2399template = MistralModel .get_community_chat_template (vocab ,template_dir ,self .is_mistral_format ) 2400else : 2401logger .info ("Not using a Mistral local or community chat template. Ensure to perform the tokenization and detokenization via `mistral-common`." ) 2402template = None 2403 2404if template is not None : 2405self .gguf_writer .add_chat_template (template ) 2406 2407def _set_vocab_plamo (self ): 2408# PLaMo models use a custom tokenizer with a .jsonl file 2409tokenizer_jsonl_path = self .dir_model / "tokenizer.jsonl" 2410tokenizer_config_path = self .dir_model / "tokenizer_config.json" 2411 2412if not tokenizer_jsonl_path .is_file (): 2413raise FileNotFoundError (f"PLaMo tokenizer file not found: { tokenizer_jsonl_path } " ) 2414 2415# Load tokenizer config 2416with open (tokenizer_config_path ,"r" ,encoding = "utf-8" )as f : 2417tokenizer_config = json .load (f ) 2418 2419# Load tokens from JSONL file (actually a list format) 2420tokens = [] 2421scores = [] 2422toktypes = [] 2423 2424with open (tokenizer_jsonl_path ,"r" ,encoding = "utf-8" )as f : 2425for line_num ,line in enumerate (f ): 2426if line .strip (): 2427token_data = json .loads (line ) 2428# Format: [token, score, type, ?, ?, ?, ?] 2429token = token_data [0 ].encode ("utf-8" ) 2430score = float (token_data [1 ]) 2431token_type_str = token_data [2 ]if len (token_data )> 2 else "NORMAL" 2432 2433tokens .append (token ) 2434scores .append (score ) 2435 2436if token_type_str == "UNKNOWN" : 2437toktypes .append (gguf .TokenType .UNKNOWN ) 2438elif token_type_str == "CONTROL" : 2439toktypes .append (gguf .TokenType .CONTROL ) 2440elif token_type_str == "BYTE" : 2441toktypes .append (gguf .TokenType .BYTE ) 2442else : 2443token_str = token_data [0 ] 2444if token_str .startswith ("<|plamo:" )and token_str .endswith ("|>" ): 2445toktypes .append (gguf .TokenType .CONTROL ) 2446else : 2447toktypes .append (gguf .TokenType .NORMAL ) 2448 2449vocab_size = self .hparams ["vocab_size" ] 2450if vocab_size > len (tokens ): 2451pad_count = vocab_size - len (tokens ) 2452logger .debug (f"Padding vocab with { pad_count } token(s) - [PAD1] through [PAD { pad_count } ]" ) 2453for i in range (1 ,pad_count + 1 ): 2454tokens .append (bytes (f"[PAD { i } ]" ,encoding = "utf-8" )) 2455scores .append (- 1000.0 ) 2456toktypes .append (gguf .TokenType .UNUSED ) 2457 2458self .gguf_writer .add_tokenizer_model ("plamo2" ) 2459self .gguf_writer .add_tokenizer_pre ("default" ) 2460self .gguf_writer .add_token_list (tokens ) 2461self .gguf_writer .add_token_scores (scores ) 2462self .gguf_writer .add_token_types (toktypes ) 2463 2464if "bos_token" in tokenizer_config and tokenizer_config ["bos_token" ]is not None : 2465token_id = tokens .index (tokenizer_config ["bos_token" ].encode ("utf-8" )) 2466self .gguf_writer .add_bos_token_id (token_id ) 2467if "eos_token" in tokenizer_config and tokenizer_config ["eos_token" ]is not None : 2468token_id = tokens .index (tokenizer_config ["eos_token" ].encode ("utf-8" )) 2469self .gguf_writer .add_eos_token_id (token_id ) 2470if "pad_token" in tokenizer_config and tokenizer_config ["pad_token" ]is not None : 2471token_id = tokens .index (tokenizer_config ["pad_token" ].encode ("utf-8" )) 2472self .gguf_writer .add_pad_token_id (token_id ) 2473if "sep_token" in tokenizer_config and tokenizer_config ["sep_token" ]is not None : 2474token_id = tokens .index (tokenizer_config ["sep_token" ].encode ("utf-8" )) 2475self .gguf_writer .add_sep_token_id (token_id ) 2476if "unk_token" in tokenizer_config and tokenizer_config ["unk_token" ]is not None : 2477token_id = tokens .index (tokenizer_config ["unk_token" ].encode ("utf-8" )) 2478self .gguf_writer .add_unk_token_id (token_id ) 2479 2480# Add <|plamo:op|> as EOT to ensure appropriate end of generation 2481self .gguf_writer .add_eot_token_id (4 ) 2482 2483self .gguf_writer .add_add_space_prefix (False ) 2484 2485 2486class MmprojModel (ModelBase ): 2487model_type = ModelType .MMPROJ 2488model_arch = gguf .MODEL_ARCH .MMPROJ 2489preprocessor_config :dict [str ,Any ] 2490global_config :dict [str ,Any ] 2491 2492n_block_keys = ["n_layers" ,"num_hidden_layers" ,"n_layer" ,"num_layers" ,"depth" ,"layers" ,"encoder_layers" ,"vt_num_hidden_layers" ] 2493 2494has_vision_encoder :bool = True # by default 2495has_audio_encoder :bool = False 2496 2497# for models having multiple encoders, we need to separate their hparams 2498hparams_vision :dict [str ,Any ]| None = None 2499hparams_audio :dict [str ,Any ]| None = None 2500 2501def __init__ (self ,* args ,** kwargs ): 2502super ().__init__ (* args ,** kwargs ) 2503 2504if self .model_arch != gguf .MODEL_ARCH .MMPROJ : 2505raise TypeError ("MmprojModel must be subclassed with model_arch = gguf.MODEL_ARCH.MMPROJ" ) 2506 2507# get n_embd of the text model 2508if not self .is_mistral_format : 2509if "text_config" not in self .hparams : 2510self .hparams ["text_config" ]= {} 2511if "audio_config" not in self .hparams : 2512self .hparams ["audio_config" ]= {} 2513text_config = {** self .hparams ,** self .hparams ["text_config" ]} 2514self .n_embd_text = text_config .get ("hidden_size" ,text_config .get ("n_embd" ,0 )) 2515else : 2516text_config = { 2517k :v for k ,v in self .hparams .items ()if k not in ["vision_encoder" ,"audio_encoder" ] 2518 } 2519# mistral native params.json: "dim" is the text hidden size ("hidden_dim" is the FFN intermediate size) 2520self .n_embd_text = text_config .get ("dim" ,0 ) 2521 2522assert self .n_embd_text > 0 ,"n_embd not found in hparams" 2523 2524# move vision config to the top level, while preserving the original hparams in global_config 2525import copy 2526self .global_config = copy .deepcopy (self .hparams ) 2527self .hparams_vision = self .get_vision_config () 2528self .hparams_audio = self .get_audio_config () 2529 2530if self .hparams_vision is None and self .hparams_audio is None : 2531raise ValueError ("vision_config / audio_config not found in hparams" ) 2532 2533# for compat with vision-only models 2534self .hparams = self .hparams_vision or self .hparams_audio or self .hparams 2535 2536# TODO @ngxson : this is a hack to support both vision and audio encoders 2537have_multiple_encoders = self .has_audio_encoder and self .has_vision_encoder 2538self .block_count = 128 if have_multiple_encoders else self .find_hparam (self .n_block_keys ,True ) 2539self .tensor_map = gguf .get_tensor_name_map (gguf .MODEL_ARCH .MMPROJ ,self .block_count ) 2540 2541# load preprocessor config 2542self .preprocessor_config = {} 2543 2544# prefer preprocessor_config.json if possible 2545preprocessor_config_path = self .dir_model / "preprocessor_config.json" 2546if preprocessor_config_path .is_file (): 2547with open (preprocessor_config_path ,"r" ,encoding = "utf-8" )as f : 2548cfg = json .load (f ) 2549# move media_proc_cfg to root level for compat 2550if "media_proc_cfg" in cfg : 2551cfg = { 2552** cfg , 2553** cfg ["media_proc_cfg" ], 2554 } 2555# merge configs 2556self .preprocessor_config = {** self .preprocessor_config ,** cfg } 2557 2558# prefer processor_config.json if possible 2559processor_config_path = self .dir_model / "processor_config.json" 2560if processor_config_path .is_file (): 2561with open (processor_config_path ,"r" ,encoding = "utf-8" )as f : 2562cfg = json .load (f ) 2563# move image_processor to root level for compat 2564if "image_processor" in cfg : 2565cfg = { 2566** cfg , 2567** cfg ["image_processor" ], 2568 } 2569# merge configs 2570self .preprocessor_config = {** self .preprocessor_config ,** cfg } 2571 2572@classmethod 2573def filter_tensors (cls ,item :tuple [str ,Callable [[],Tensor ]])-> tuple [str ,Callable [[],Tensor ]]| None : 2574name ,gen = item 2575 2576# Skip non-multimodal tensors 2577if "language_model." in name : 2578return None 2579 2580return super ().filter_tensors (item ) 2581 2582def get_vision_config (self )-> dict [str ,Any ]| None : 2583config_name = "vision_config" if not self .is_mistral_format else "vision_encoder" 2584return self .global_config .get (config_name ) 2585 2586def get_audio_config (self )-> dict [str ,Any ]| None : 2587mm_config_key = "whisper_config" if "whisper_config" in self .hparams else "audio_config" 2588return self .global_config .get (mm_config_key ) 2589 2590def set_type (self ): 2591self .gguf_writer .add_type (gguf .GGUFType .MMPROJ ) 2592 2593def prepare_metadata (self ,vocab_only :bool ): 2594super ().prepare_metadata (vocab_only = vocab_only ) 2595 2596output_type :str = self .ftype .name .partition ("_" )[2 ] 2597 2598if self .fname_out .is_dir (): 2599fname_default :str = gguf .naming_convention (self .metadata .name ,self .metadata .basename ,self .metadata .finetune ,self .metadata .version ,size_label = None ,output_type = output_type ,model_type = None ) 2600self .fname_out = self .fname_out / f"mmproj- { fname_default } .gguf" 2601else : 2602self .fname_out = self .fname_out .parent / gguf .fill_templated_filename (self .fname_out .name ,output_type ) 2603 2604def set_gguf_parameters (self ): 2605self .gguf_writer .add_file_type (self .ftype ) 2606 2607if self .has_vision_encoder : 2608self .gguf_writer .add_clip_has_vision_encoder (True ) 2609self .gguf_writer .add_vision_projection_dim (self .n_embd_text ) 2610 2611# vision config 2612self .image_size = self .find_vparam (["image_size" ]) 2613self .gguf_writer .add_vision_image_size (self .image_size ) 2614self .gguf_writer .add_vision_patch_size (self .find_vparam (["patch_size" ])) 2615self .gguf_writer .add_vision_embedding_length (self .find_vparam (["hidden_size" ,"width" ,"vt_hidden_size" ])) 2616self .gguf_writer .add_vision_feed_forward_length (self .find_vparam (["intermediate_size" ,"vt_intermediate_size" ])) 2617self .gguf_writer .add_vision_block_count (self .find_vparam (self .n_block_keys )) 2618self .gguf_writer .add_vision_head_count (self .find_vparam (["num_attention_heads" ,"num_heads" ,"heads" ,"vt_num_attention_heads" ])) 2619 2620# preprocessor config 2621image_mean = _MISTRAL_COMMON_DATASET_MEAN if self .is_mistral_format else self .preprocessor_config ["image_mean" ] 2622image_std = _MISTRAL_COMMON_DATASET_STD if self .is_mistral_format else self .preprocessor_config ["image_std" ] 2623 2624self .gguf_writer .add_vision_image_mean (image_mean ) 2625self .gguf_writer .add_vision_image_std (image_std ) 2626 2627if self .has_audio_encoder : 2628self .gguf_writer .add_clip_has_audio_encoder (True ) 2629self .gguf_writer .add_audio_projection_dim (self .n_embd_text ) 2630 2631# audio config 2632self .gguf_writer .add_audio_embedding_length (self .find_aparam (["hidden_size" ])) 2633self .gguf_writer .add_audio_feed_forward_length (self .find_aparam (["intermediate_size" ])) 2634self .gguf_writer .add_audio_block_count (self .find_aparam (self .n_block_keys )) 2635self .gguf_writer .add_audio_head_count (self .find_aparam (["num_attention_heads" ])) 2636 2637if not self .has_vision_encoder and not self .has_audio_encoder : 2638raise ValueError ("MmprojModel must have either vision or audio encoder" ) 2639 2640def write_vocab (self ): 2641raise ValueError ("MmprojModel does not support vocab writing" ) 2642 2643def find_vparam (self ,keys :Iterable [str ],optional :bool = False )-> Any : 2644assert self .hparams_vision is not None 2645return self ._find_param (self .hparams_vision ,keys ,optional ) 2646 2647def find_aparam (self ,keys :Iterable [str ],optional :bool = False )-> Any : 2648assert self .hparams_audio is not None 2649return self ._find_param (self .hparams_audio ,keys ,optional ) 2650 2651def _find_param (self ,obj :dict [str ,Any ],keys :Iterable [str ],optional :bool = False )-> Any : 2652key = next ((k for k in keys if k in obj ),None ) 2653if key is not None : 2654return obj [key ] 2655if optional : 2656return None 2657raise KeyError (f"could not find any of: { keys } " ) 2658 2659def tensor_force_quant (self ,name ,new_name ,bid ,n_dims ): 2660if ".patch_embd.weight" in new_name or ".patch_merger.weight" in new_name : 2661return gguf .GGMLQuantizationType .F16 if self .ftype == gguf .LlamaFileType .MOSTLY_F16 else gguf .GGMLQuantizationType .F32 2662return super ().tensor_force_quant (name ,new_name ,bid ,n_dims ) 2663 2664 2665class LazyTorchTensor (gguf .LazyBase ): 2666_tensor_type = torch .Tensor 2667# to keep the type-checker happy 2668dtype :torch .dtype 2669shape :torch .Size 2670 2671# only used when converting a torch.Tensor to a np.ndarray 2672_dtype_map :dict [torch .dtype ,type ]= { 2673torch .float16 :np .float16 , 2674torch .float32 :np .float32 , 2675torch .uint8 :np .uint8 , 2676torch .int64 :np .int64 , 2677 } 2678 2679# only used when byteswapping data. Only correct size is needed 2680# TODO: uncomment uint64, uint32, and uint16, ref: https://github.com/pytorch/pytorch/issues/58734 2681_dtype_byteswap_map :dict [torch .dtype ,type ]= { 2682torch .float64 :np .float64 , 2683torch .float32 :np .float32 , 2684torch .bfloat16 :np .float16 , 2685torch .float16 :np .float16 , 2686torch .int64 :np .int64 , 2687# torch.uint64: np.uint64, 2688torch .int32 :np .int32 , 2689# torch.uint32: np.uint32, 2690torch .int16 :np .int16 , 2691# torch.uint16: np.uint16, 2692torch .int8 :np .int8 , 2693torch .uint8 :np .uint8 , 2694torch .bool :np .uint8 , 2695torch .float8_e4m3fn :np .uint8 , 2696torch .float8_e5m2 :np .uint8 , 2697 } 2698 2699# used for safetensors slices 2700# ref: https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/src/lib.rs#L1046 2701# TODO: uncomment U64, U32, and U16, ref: https://github.com/pytorch/pytorch/issues/58734 2702_dtype_str_map :dict [str ,torch .dtype ]= { 2703"F64" :torch .float64 , 2704"F32" :torch .float32 , 2705"BF16" :torch .bfloat16 , 2706"F16" :torch .float16 , 2707# "U64": torch.uint64, 2708"I64" :torch .int64 , 2709# "U32": torch.uint32, 2710"I32" :torch .int32 , 2711# "U16": torch.uint16, 2712"I16" :torch .int16 , 2713"U8" :torch .uint8 , 2714"I8" :torch .int8 , 2715"BOOL" :torch .bool , 2716"F8_E4M3" :torch .float8_e4m3fn , 2717"F8_E5M2" :torch .float8_e5m2 , 2718 } 2719 2720def numpy (self )-> gguf .LazyNumpyTensor : 2721dtype = self ._dtype_map [self .dtype ] 2722return gguf .LazyNumpyTensor ( 2723meta = gguf .LazyNumpyTensor .meta_with_dtype_and_shape (dtype ,self .shape ), 2724args = (self ,), 2725func = (lambda s :s .numpy ()) 2726 ) 2727 2728@classmethod 2729def meta_with_dtype_and_shape (cls ,dtype :torch .dtype ,shape :tuple [int , ...])-> Tensor : 2730return torch .empty (size = shape ,dtype = dtype ,device = "meta" ) 2731 2732@classmethod 2733def from_safetensors_slice (cls ,st_slice :Any )-> Tensor : 2734dtype = cls ._dtype_str_map [st_slice .get_dtype ()] 2735shape :tuple [int , ...]= tuple (st_slice .get_shape ()) 2736lazy = cls (meta = cls .meta_with_dtype_and_shape (dtype ,shape ),args = (st_slice ,),func = lambda s :s [...]if len (s .get_shape ())== 0 else s [:]) 2737return cast (torch .Tensor ,lazy ) 2738 2739@classmethod 2740def from_local_tensor (cls ,t :gguf .utility .LocalTensor )-> Tensor : 2741def load_tensor (tensor :gguf .utility .LocalTensor )-> Tensor : 2742def byteswap_tensor (tensor :np .ndarray ,dtype :type )-> np .ndarray : 2743if sys .byteorder == 'big' : 2744# switch data back to big endian 2745tensor = tensor .view (dtype ).byteswap (inplace = False ) 2746return tensor 2747dtype = cls ._dtype_str_map [tensor .dtype ] 2748numpy_dtype = cls ._dtype_byteswap_map [dtype ] 2749return torch .from_numpy (byteswap_tensor (tensor .mmap_bytes (),numpy_dtype )).view (dtype ).reshape (tensor .shape ) 2750dtype = cls ._dtype_str_map [t .dtype ] 2751shape = t .shape 2752lazy = cls (meta = cls .meta_with_dtype_and_shape (dtype ,shape ),args = (t ,),func = lambda r :load_tensor (r )) 2753return cast (torch .Tensor ,lazy ) 2754 2755@classmethod 2756def from_remote_tensor (cls ,remote_tensor :gguf .utility .RemoteTensor ): 2757def byteswap_tensor (tensor :np .ndarray ,dtype :type )-> np .ndarray : 2758if sys .byteorder == 'big' : 2759# switch data back to big endian 2760tensor = tensor .view (dtype ).byteswap (inplace = False ) 2761return tensor 2762dtype = cls ._dtype_str_map [remote_tensor .dtype ] 2763numpy_dtype = cls ._dtype_byteswap_map [dtype ] 2764shape = remote_tensor .shape 2765meta = cls .meta_with_dtype_and_shape (dtype ,shape ) 2766lazy = cls (meta = meta ,args = (remote_tensor ,),func = lambda r :torch .from_numpy (byteswap_tensor (np .frombuffer (r .data (),dtype = numpy_dtype ),numpy_dtype )).view (dtype ).reshape (shape )) 2767return cast (torch .Tensor ,lazy ) 2768 2769@classmethod 2770def __torch_function__ (cls ,func ,types ,args = (),kwargs = None ): 2771del types # unused 2772 2773if kwargs is None : 2774kwargs = {} 2775 2776if func is torch .Tensor .numpy : 2777assert len (args ) 2778return args [0 ].numpy () 2779 2780return cls ._wrap_fn (func )(* args ,** kwargs ) 2781 2782 2783if hasattr (torch ,"float8_e8m0fnu" ): 2784_torch_float8_e8m0 = torch .float8_e8m0fnu 2785LazyTorchTensor ._dtype_map [_torch_float8_e8m0 ]= np .uint8 2786LazyTorchTensor ._dtype_byteswap_map [_torch_float8_e8m0 ]= np .uint8 2787LazyTorchTensor ._dtype_str_map ["F8_E8M0" ]= _torch_float8_e8m0 2788else : 2789# Older torch builds do not expose F8_E8M0. Keep the raw bytes so callers 2790# that know the format can decode them explicitly. 2791LazyTorchTensor ._dtype_str_map ["F8_E8M0" ]= torch .uint8 2792 2793 2794def get_model_architecture (hparams :dict [str ,Any ],model_type :ModelType )-> str : 2795# TODO @ngxson : this won't work correctly if the model has both audio & vision encoders 2796# maybe we should fallback to text model's arch in that case, since not many models have both 2797text_config = hparams .get ("text_config" , {}) 2798vision_config = hparams .get ("vision_config" , {}) 2799arch = None 2800if (arches := hparams .get ("architectures" ))is not None and len (arches )> 0 : 2801arch = arches [0 ] 2802elif "ssm_cfg" in hparams : 2803# For non-hf Mamba and Mamba2 models 2804arch = hparams ["ssm_cfg" ].get ("layer" ,"Mamba" )+ "ForCausalLM" 2805 2806# Step3-VL keeps text config under text_config but uses a custom top-level architecture. 2807# For text conversion we route to a dedicated text-only class. 2808# TODO: refactor this later to avoid adding exception here 2809# Kimi-K3's text_config reports "KimiLinearForCausalLM", which is the older 2810# Kimi-Linear-48B architecture and cannot load K3 (no attention residuals, 2811# latent MoE, situ, ...). Route on the top-level architecture instead. 2812if model_type == ModelType .TEXT and arch in ("StepVLForConditionalGeneration" ,"Sarashina2VisionForCausalLM" ,"Exaone4_5_ForConditionalGeneration" ,"Step3p7ForConditionalGeneration" ,"KimiK3ForConditionalGeneration" ): 2813return arch 2814 2815# if "architectures" is found in the sub-config, use that instead 2816if model_type == ModelType .TEXT and text_config .get ("architectures" )is not None : 2817arch = text_config ["architectures" ][0 ] 2818elif model_type == ModelType .MMPROJ and vision_config .get ("architectures" )is not None : 2819arch = vision_config ["architectures" ][0 ] 2820if arch is None : 2821raise ValueError ("Failed to detect model architecture" ) 2822return arch