char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
5d246a792
char/aliceai-t5-moe
1#!/usr/bin/env python3 2from __future__import annotations 3 4import logging 5import argparse 6import concurrent .futures 7import enum 8import faulthandler 9import functools 10import itertools 11import json 12import math 13import mmap 14import os 15import pickle 16import re 17import signal 18import struct 19import sys 20import textwrap 21import time 22import zipfile 23from abc import ABC ,abstractmethod 24from concurrent .futures import ProcessPoolExecutor ,ThreadPoolExecutor 25from dataclasses import dataclass 26from pathlib import Path 27from typing import TYPE_CHECKING ,Any ,Callable ,IO ,Iterable ,Literal ,TypeVar 28 29import numpy as np 30 31if 'NO_LOCAL_GGUF' not in os .environ : 32# use .parent.parent since we are in "examples" directory 33sys .path .insert (1 ,str (Path (__file__ ).parent .parent / 'gguf-py' )) 34 35import gguf 36from gguf import BaseVocab ,Vocab ,NoVocab ,BpeVocab ,SentencePieceVocab ,LlamaHfVocab 37 38if TYPE_CHECKING : 39from typing_extensions import Self ,TypeAlias 40 41logger = logging .getLogger ("convert" ) 42 43if hasattr (faulthandler ,'register' )and hasattr (signal ,'SIGUSR1' ): 44faulthandler .register (signal .SIGUSR1 ) 45 46NDArray :TypeAlias = 'np.ndarray[Any, Any]' 47 48ARCH = gguf .MODEL_ARCH .LLAMA 49 50DEFAULT_CONCURRENCY = 8 51 52ADDED_TOKENS_FILE = 'added_tokens.json' 53FAST_TOKENIZER_FILE = 'tokenizer.json' 54 55# 56# data types 57# 58 59 60@dataclass( frozen = True ) 61class DataType : 62name :str 63dtype :np .dtype [Any ] 64valid_conversions :list [str ] 65 66def elements_to_bytes (self ,n_elements :int )-> int : 67return n_elements * self .dtype .itemsize 68 69 70@dataclass( frozen = True ) 71class UnquantizedDataType (DataType ): 72pass 73 74 75DT_F16 = UnquantizedDataType ('F16' ,dtype = np .dtype (np .float16 ),valid_conversions = ['F32' ,'Q8_0' ]) 76DT_F32 = UnquantizedDataType ('F32' ,dtype = np .dtype (np .float32 ),valid_conversions = ['F16' ,'Q8_0' ]) 77DT_I32 = UnquantizedDataType ('I32' ,dtype = np .dtype (np .int16 ),valid_conversions = []) 78DT_BF16 = UnquantizedDataType ('BF16' ,dtype = np .dtype (np .uint16 ),valid_conversions = ['F32' ,'F16' ,'Q8_0' ]) 79 80 81@dataclass( frozen = True ) 82class QuantizedDataType (DataType ): 83block_size :int 84quantized_dtype :np .dtype [Any ] 85ggml_type :gguf .GGMLQuantizationType 86 87def quantize (self ,arr :NDArray )-> NDArray : 88raise NotImplementedError (f'Quantization for { self . name } not implemented' ) 89 90def elements_to_bytes (self ,n_elements :int )-> int : 91assert n_elements % self .block_size == 0 ,f'Invalid number of elements { n_elements } for { self . name } with block size { self . block_size } ' 92return self .quantized_dtype .itemsize * (n_elements // self .block_size ) 93 94 95@dataclass( frozen = True ) 96class Q8_0QuantizedDataType (QuantizedDataType ): 97# Mini Q8_0 quantization in Python! 98def quantize (self ,arr :NDArray )-> NDArray : 99assert arr .size % self .block_size == 0 and arr .size != 0 ,f'Bad array size { arr . size } ' 100assert arr .dtype == np .float32 ,f'Bad array type { arr . dtype } ' 101n_blocks = arr .size // self .block_size 102blocks = arr .reshape ((n_blocks ,self .block_size )) 103# Much faster implementation of block quantization contributed by @Cebtenzzre 104 105def quantize_blocks_q8_0 (blocks :NDArray )-> Iterable [tuple [Any ,Any ]]: 106d = abs (blocks ).max (axis = 1 )/ np .float32 (127 ) 107with np .errstate (divide = 'ignore' ): 108qs = (blocks / d [:,None ]).round () 109qs [d == 0 ]= 0 110yield from zip (d ,qs ) 111return np .fromiter (quantize_blocks_q8_0 (blocks ),count = n_blocks ,dtype = self .quantized_dtype ) 112 113 114DT_Q8_0 = Q8_0QuantizedDataType ('Q8_0' , 115dtype = np .dtype (np .float32 ),valid_conversions = [], 116ggml_type = gguf .GGMLQuantizationType .Q8_0 ,block_size = 32 , 117quantized_dtype = np .dtype ([('d' ,'<f2' ), ('qs' ,'i1' , (32 ,))])) 118 119# Quantized types skipped here because they may also map to np.float32 120NUMPY_TYPE_TO_DATA_TYPE :dict [np .dtype [Any ],DataType ]= {} 121for dt in (DT_BF16 ,DT_F16 ,DT_F32 ,DT_I32 ): 122if dt .dtype in NUMPY_TYPE_TO_DATA_TYPE : 123raise ValueError (f'Invalid duplicate data type { dt } ' ) 124NUMPY_TYPE_TO_DATA_TYPE [dt .dtype ]= dt 125 126SAFETENSORS_DATA_TYPES :dict [str ,DataType ]= { 127'BF16' :DT_BF16 , 128'F16' :DT_F16 , 129'F32' :DT_F32 , 130'I32' :DT_I32 , 131} 132 133# TODO: match this with `llama_ftype` 134# TODO: rename to LLAMAFileType 135# TODO: move to `gguf.py` 136 137 138class GGMLFileType (enum .IntEnum ): 139AllF32 = 0 140MostlyF16 = 1 # except 1d tensors 141MostlyQ8_0 = 7 # except 1d tensors 142 143def type_for_tensor (self ,name :str ,tensor :LazyTensor )-> DataType : 144dt = GGML_FILE_TYPE_TO_DATA_TYPE .get (self ) 145if dt is None : 146raise ValueError (self ) 147# Convert all 1D tensors to F32. Most of the codebase that takes in 1D tensors only handles F32 tensors, and most of the outputs tensors are F32. 148# Also The 1d tensors aren't much of a performance/size issue. So instead of having to have separate F32 and F16 implementations of both, just convert everything to F32 for now. 149return dt if len (tensor .shape )> 1 else DT_F32 150 151 152GGML_FILE_TYPE_TO_DATA_TYPE :dict [GGMLFileType ,DataType ]= { 153GGMLFileType .AllF32 :DT_F32 , 154GGMLFileType .MostlyF16 :DT_F16 , 155GGMLFileType .MostlyQ8_0 :DT_Q8_0 , 156} 157 158# 159# hparams loading 160# 161 162 163@dataclass 164class Params : 165n_vocab :int 166n_embd :int 167n_layer :int 168n_ctx :int 169n_ff :int 170n_head :int 171n_head_kv :int 172n_experts :int | None = None 173n_experts_used :int | None = None 174f_norm_eps :float | None = None 175 176rope_scaling_type :gguf .RopeScalingType | None = None 177f_rope_freq_base :float | None = None 178f_rope_scale :float | None = None 179n_ctx_orig :int | None = None 180rope_finetuned :bool | None = None 181 182ftype :GGMLFileType | None = None 183 184# path to the directory containing the model files 185path_model :Path | None = None 186 187@staticmethod 188def guessed (model :LazyModel )-> Params : 189# try transformer naming first 190n_vocab ,n_embd = model ["model.embed_tokens.weight" ].shape if "model.embed_tokens.weight" in model else model ["tok_embeddings.weight" ].shape 191 192# try transformer naming first 193if "model.layers.0.self_attn.q_proj.weight" in model : 194n_layer = next (i for i in itertools .count ()if f"model.layers. { i } .self_attn.q_proj.weight" not in model ) 195elif "model.layers.0.self_attn.W_pack.weight" in model :# next: try baichuan naming 196n_layer = next (i for i in itertools .count ()if f"model.layers. { i } .self_attn.W_pack.weight" not in model ) 197else : 198n_layer = next (i for i in itertools .count ()if f"layers. { i } .attention.wq.weight" not in model ) 199 200if n_layer < 1 : 201msg = """\ 202failed to guess 'n_layer'. This model is unknown or unsupported. 203Suggestion: provide 'config.json' of the model in the same directory containing model files.""" 204raise KeyError (textwrap .dedent (msg )) 205 206n_head = n_embd // 128 # guessed 207n_mult = 256 # guessed 208 209# TODO: verify this 210n_ff = int (2 * (4 * n_embd )/ 3 ) 211n_ff = n_mult * ((n_ff + n_mult - 1 )// n_mult ) 212 213return Params ( 214n_vocab = n_vocab , 215n_embd = n_embd , 216n_layer = n_layer , 217n_ctx = - 1 , 218n_ff = n_ff , 219n_head = n_head , 220n_head_kv = n_head , 221f_norm_eps = 1e-5 , 222 ) 223 224@staticmethod 225def loadHFTransformerJson (model :LazyModel ,config_path :Path )-> Params : 226with open (config_path )as f : 227config = json .load (f ) 228 229rope_scaling_type = f_rope_scale = n_ctx_orig = rope_finetuned = None 230rope_scaling = config .get ("rope_scaling" ) 231 232if rope_scaling is not None and (typ := rope_scaling .get ("type" )): 233rope_factor = rope_scaling .get ("factor" ) 234f_rope_scale = rope_factor 235if typ == "linear" : 236rope_scaling_type = gguf .RopeScalingType .LINEAR 237elif typ == "yarn" : 238rope_scaling_type = gguf .RopeScalingType .YARN 239n_ctx_orig = rope_scaling ['original_max_position_embeddings' ] 240rope_finetuned = rope_scaling ['finetuned' ] 241else : 242raise NotImplementedError (f'Unknown rope scaling type: { typ } ' ) 243 244if "max_sequence_length" in config : 245n_ctx = config ["max_sequence_length" ] 246elif "max_position_embeddings" in config : 247n_ctx = config ["max_position_embeddings" ] 248else : 249msg = """\ 250failed to guess 'n_ctx'. This model is unknown or unsupported. 251Suggestion: provide 'config.json' of the model in the same directory containing model files.""" 252raise KeyError (textwrap .dedent (msg )) 253 254n_experts = None 255n_experts_used = None 256 257if "num_local_experts" in config : 258n_experts = config ["num_local_experts" ] 259n_experts_used = config ["num_experts_per_tok" ] 260 261return Params ( 262n_vocab = config ["vocab_size" ], 263n_embd = config ["hidden_size" ], 264n_layer = config ["num_hidden_layers" ], 265n_ctx = n_ctx , 266n_ff = config ["intermediate_size" ], 267n_head = (n_head := config ["num_attention_heads" ]), 268n_head_kv = config .get ("num_key_value_heads" ,n_head ), 269n_experts = n_experts , 270n_experts_used = n_experts_used , 271f_norm_eps = config ["rms_norm_eps" ], 272f_rope_freq_base = config .get ("rope_theta" ), 273rope_scaling_type = rope_scaling_type , 274f_rope_scale = f_rope_scale , 275n_ctx_orig = n_ctx_orig , 276rope_finetuned = rope_finetuned , 277 ) 278 279# LLaMA v2 70B params.json 280# {"dim": 8192, "multiple_of": 4096, "ffn_dim_multiplier": 1.3, "n_heads": 64, "n_kv_heads": 8, "n_layers": 80, "norm_eps": 1e-05, "vocab_size": -1} 281@staticmethod 282def loadOriginalParamsJson (model :LazyModel ,config_path :Path )-> Params : 283with open (config_path )as f : 284config = json .load (f ) 285 286n_experts = None 287n_experts_used = None 288f_rope_freq_base = None 289n_ff = None 290 291# hack to determine LLaMA v1 vs v2 vs CodeLlama 292if config .get ("moe" ): 293# Mixtral 294n_ctx = 32768 295elif config .get ("rope_theta" )== 1000000 : 296# CodeLlama 297n_ctx = 16384 298elif config ["norm_eps" ]== 1e-05 : 299# LLaMA v2 300n_ctx = 4096 301else : 302# LLaMA v1 303n_ctx = 2048 304 305if "layers.0.feed_forward.w1.weight" in model : 306n_ff = model ["layers.0.feed_forward.w1.weight" ].shape [0 ] 307 308if config .get ("moe" ): 309n_ff = model ["layers.0.feed_forward.experts.0.w1.weight" ].shape [0 ] 310n_experts = config ["moe" ]["num_experts" ] 311n_experts_used = config ["moe" ]["num_experts_per_tok" ] 312f_rope_freq_base = 1e6 313 314assert n_ff is not None 315 316return Params ( 317n_vocab = model ["tok_embeddings.weight" ].shape [0 ], 318n_embd = config ["dim" ], 319n_layer = config ["n_layers" ], 320n_ctx = n_ctx , 321n_ff = n_ff , 322n_head = (n_head := config ["n_heads" ]), 323n_head_kv = config .get ("n_kv_heads" ,n_head ), 324n_experts = n_experts , 325n_experts_used = n_experts_used , 326f_norm_eps = config ["norm_eps" ], 327f_rope_freq_base = config .get ("rope_theta" ,f_rope_freq_base ), 328 ) 329 330@staticmethod 331def load (model_plus :ModelPlus )-> Params : 332hf_config_path = model_plus .paths [0 ].parent / "config.json" 333orig_config_path = model_plus .paths [0 ].parent / "params.json" 334 335if hf_config_path .exists (): 336params = Params .loadHFTransformerJson (model_plus .model ,hf_config_path ) 337elif orig_config_path .exists (): 338params = Params .loadOriginalParamsJson (model_plus .model ,orig_config_path ) 339elif model_plus .format != 'none' : 340params = Params .guessed (model_plus .model ) 341else : 342raise ValueError ('Cannot guess params when model format is none' ) 343 344params .path_model = model_plus .paths [0 ].parent 345 346return params 347 348 349# 350# data loading 351# TODO: reuse (probably move to gguf.py?) 352# 353 354 355def permute (weights :NDArray ,n_head :int ,n_head_kv :int )-> NDArray : 356if n_head_kv is not None and n_head != n_head_kv : 357n_head = n_head_kv 358return (weights .reshape (n_head ,2 ,weights .shape [0 ]// n_head // 2 ,* weights .shape [1 :]) 359 .swapaxes (1 ,2 ) 360 .reshape (weights .shape )) 361 362 363class Tensor (ABC ): 364ndarray :NDArray 365data_type :DataType 366 367@abstractmethod 368def astype (self ,data_type :DataType )-> Self : ... 369@abstractmethod 370def permute (self ,n_head :int ,n_head_kv :int )-> Self : ... 371@abstractmethod 372def permute_part (self ,n_part :int ,n_head :int ,n_head_kv :int )-> Self : ... 373@abstractmethod 374def part (self ,n_part :int )-> Self : ... 375@abstractmethod 376def to_ggml (self )-> GGMLCompatibleTensor : ... 377 378 379def bf16_to_fp32 (bf16_arr :np .ndarray [Any ,np .dtype [np .uint16 ]])-> NDArray : 380assert bf16_arr .dtype == np .uint16 ,f"Input array should be of dtype uint16, but got { bf16_arr . dtype } " 381fp32_arr = bf16_arr .astype (np .uint32 )<< 16 382return fp32_arr .view (np .float32 ) 383 384 385class UnquantizedTensor (Tensor ): 386def __init__ (self ,ndarray :NDArray ): 387assert isinstance (ndarray ,np .ndarray ) 388self .ndarray = ndarray 389self .data_type = NUMPY_TYPE_TO_DATA_TYPE [ndarray .dtype ] 390 391def astype (self ,data_type :DataType )-> UnquantizedTensor : 392dtype = data_type .dtype 393if self .data_type == DT_BF16 : 394self .ndarray = bf16_to_fp32 (self .ndarray ) 395return UnquantizedTensor (self .ndarray .astype (dtype )) 396 397def to_ggml (self )-> Self : 398return self 399 400def permute_part (self ,n_part :int ,n_head :int ,n_head_kv :int )-> UnquantizedTensor : 401r = self .ndarray .shape [0 ]// 3 402return UnquantizedTensor (permute (self .ndarray [r * n_part :r * n_part + r , ...],n_head ,n_head_kv )) 403 404def part (self ,n_part :int )-> UnquantizedTensor : 405r = self .ndarray .shape [0 ]// 3 406return UnquantizedTensor (self .ndarray [r * n_part :r * n_part + r , ...]) 407 408def permute (self ,n_head :int ,n_head_kv :int )-> UnquantizedTensor : 409return UnquantizedTensor (permute (self .ndarray ,n_head ,n_head_kv )) 410 411 412def load_unquantized (lazy_tensor :LazyTensor ,expected_dtype :Any = None ,convert :bool = False )-> NDArray : 413tensor = lazy_tensor .load () 414assert isinstance (tensor ,UnquantizedTensor ) 415 416# double-check: 417actual_shape = list (tensor .ndarray .shape ) 418assert actual_shape == lazy_tensor .shape , (actual_shape ,lazy_tensor .shape ) 419if expected_dtype is not None and expected_dtype != tensor .ndarray .dtype : 420if convert : 421tensor .ndarray = tensor .ndarray .astype (expected_dtype ) 422else : 423raise ValueError (f'expected this tensor to have dtype { expected_dtype } , got { tensor . ndarray . dtype } ' ) 424 425return tensor .ndarray 426 427 428GGMLCompatibleTensor = UnquantizedTensor 429 430 431@dataclass 432class LazyTensor : 433_load :Callable [[],Tensor ] 434shape :list [int ] 435data_type :DataType 436description :str 437 438def load (self )-> Tensor : 439ret = self ._load () 440# Should be okay if it maps to the same numpy type? 441assert ret .data_type == self .data_type or (self .data_type .dtype == ret .data_type .dtype ), \ 442 (self .data_type ,ret .data_type ,self .description ) 443return ret 444 445def astype (self ,data_type :DataType )-> LazyTensor : 446self .validate_conversion_to (data_type ) 447 448def load ()-> Tensor : 449return self .load ().astype (data_type ) 450return LazyTensor (load ,self .shape ,data_type ,f'convert( { data_type } ) { self . description } ' ) 451 452def validate_conversion_to (self ,data_type :DataType )-> None : 453if data_type != self .data_type and data_type .name not in self .data_type .valid_conversions : 454raise ValueError (f'Cannot validate conversion from { self . data_type } to { data_type } .' ) 455 456 457LazyModel :TypeAlias = 'dict[str, LazyTensor]' 458 459ModelFormat :TypeAlias = Literal ['ggml' ,'torch' ,'safetensors' ,'none' ] 460 461@dataclass 462class ModelPlus : 463model :LazyModel 464paths :list [Path ]# Where this was read from. 465format :ModelFormat 466vocab :BaseVocab | None # For GGML models (which have vocab built in), the vocab. 467 468 469def merge_sharded (models :list [LazyModel ])-> LazyModel : 470# Original LLaMA models have each file contain one part of each tensor. 471# Use a dict instead of a set to preserve order. 472names = {name :None for model in models for name in model } 473 474def convert (name :str )-> LazyTensor : 475lazy_tensors = [model [name ]for model in models ] 476if len (lazy_tensors )== 1 : 477# only one file; don't go through this procedure since there might 478# be quantized tensors 479return lazy_tensors [0 ] 480if len (lazy_tensors [0 ].shape )== 1 : 481# the tensor is just duplicated in every file 482return lazy_tensors [0 ] 483if name .startswith ('tok_embeddings.' )or \ 484name .endswith ('.attention.wo.weight' )or \ 485name .endswith ('.feed_forward.w2.weight' ): 486# split by columns 487axis = 1 488else : 489# split by rows 490axis = 0 491concatenated_shape = list (lazy_tensors [0 ].shape ) 492concatenated_shape [axis ]= sum (tensor .shape [axis ]for tensor in lazy_tensors ) 493 494def load ()-> UnquantizedTensor : 495ndarrays = [load_unquantized (tensor )for tensor in lazy_tensors ] 496concatenated = np .concatenate (ndarrays ,axis = axis ) 497return UnquantizedTensor (concatenated ) 498description = 'concatenated[[' + '] | [' .join (lt .description for lt in lazy_tensors )+ ']]' 499return LazyTensor (load ,concatenated_shape ,lazy_tensors [0 ].data_type ,description ) 500return {name :convert (name )for name in names } 501 502 503def merge_multifile_models (models_plus :list [ModelPlus ])-> ModelPlus : 504formats :set [ModelFormat ]= set (mp .format for mp in models_plus ) 505assert len (formats )== 1 ,"different formats?" 506format = formats .pop () 507paths = [path for mp in models_plus for path in mp .paths ] 508# Use the first non-None vocab, if any. 509try : 510vocab = next (mp .vocab for mp in models_plus if mp .vocab is not None ) 511except StopIteration : 512vocab = None 513 514if any ("model.embed_tokens.weight" in mp .model for mp in models_plus ): 515# Transformers models put different tensors in different files, but 516# don't split individual tensors between files. 517model :LazyModel = {} 518for mp in models_plus : 519model .update (mp .model ) 520else : 521model = merge_sharded ([mp .model for mp in models_plus ]) 522 523return ModelPlus (model ,paths ,format ,vocab ) 524 525 526def permute_lazy (lazy_tensor :LazyTensor ,n_head :int ,n_head_kv :int )-> LazyTensor : 527def load ()-> Tensor : 528return lazy_tensor .load ().permute (n_head ,n_head_kv ) 529return LazyTensor (load ,lazy_tensor .shape ,lazy_tensor .data_type ,f'permute( { n_head } , { n_head_kv } ) ' + lazy_tensor .description ) 530 531 532def permute_part_lazy (lazy_tensor :LazyTensor ,n_part :int ,n_head :int ,n_head_kv :int )-> LazyTensor : 533def load ()-> Tensor : 534return lazy_tensor .load ().permute_part (n_part ,n_head ,n_head_kv ) 535s = lazy_tensor .shape .copy () 536s [0 ]= s [0 ]// 3 537return LazyTensor (load ,s ,lazy_tensor .data_type ,f'permute( { n_head } , { n_head_kv } ) ' + lazy_tensor .description ) 538 539 540def part_lazy (lazy_tensor :LazyTensor ,n_part :int )-> LazyTensor : 541def load ()-> Tensor : 542return lazy_tensor .load ().part (n_part ) 543s = lazy_tensor .shape .copy () 544s [0 ]= s [0 ]// 3 545return LazyTensor (load ,s ,lazy_tensor .data_type ,'part ' + lazy_tensor .description ) 546 547 548def pack_experts_lazy (lazy_tensors :list [LazyTensor ])-> LazyTensor : 549def load ()-> Tensor : 550tensors = [lazy_tensor .load ()for lazy_tensor in lazy_tensors ] 551return UnquantizedTensor (np .array ([tensor .ndarray for tensor in tensors ])) 552s = lazy_tensors [0 ].shape .copy () 553s .insert (0 ,len (lazy_tensors )) 554return LazyTensor (load ,s ,lazy_tensors [0 ].data_type ,'pack_experts ' + ' | ' .join (lt .description for lt in lazy_tensors )) 555 556 557# Functionality that simulates `torch.load` but where individual tensors are 558# only loaded into memory on demand, not all at once. 559# PyTorch can't do this natively as of time of writing: 560# - https://github.com/pytorch/pytorch/issues/64327 561# This allows us to de-shard without multiplying RAM usage, and also 562# conveniently drops the PyTorch dependency (though we still need numpy). 563 564 565@dataclass 566class LazyStorageKind : 567data_type :DataType 568 569 570@dataclass 571class LazyStorage : 572load :Callable [[int ,int ],NDArray ] 573kind :LazyStorageKind 574description :str 575 576 577class LazyUnpickler (pickle .Unpickler ): 578def __init__ (self ,fp :IO [bytes ],data_base_path :str ,zip_file :zipfile .ZipFile ): 579super ().__init__ (fp ) 580self .data_base_path = data_base_path 581self .zip_file = zip_file 582 583def persistent_load (self ,pid :Any )-> Any : 584assert pid [0 ]== 'storage' 585assert isinstance (pid [1 ],LazyStorageKind ) 586data_type = pid [1 ].data_type 587filename_stem = pid [2 ] 588filename = f' { self . data_base_path } / { filename_stem } ' 589info = self .zip_file .getinfo (filename ) 590 591def load (offset :int ,elm_count :int )-> NDArray : 592dtype = data_type .dtype 593with self .zip_file .open (info )as fp : 594fp .seek (offset * dtype .itemsize ) 595size = elm_count * dtype .itemsize 596data = fp .read (size ) 597assert len (data )== size 598return np .frombuffer (data ,dtype ) 599description = f'storage data_type= { data_type } path-in-zip= { filename } path= { self . zip_file . filename } ' 600return LazyStorage (load = load ,kind = pid [1 ],description = description ) 601 602@staticmethod 603def lazy_rebuild_tensor_v2 (storage :Any ,storage_offset :Any ,size :Any ,stride :Any , 604requires_grad :Any ,backward_hooks :Any ,metadata :Any = None )-> LazyTensor : 605assert isinstance (storage ,LazyStorage ) 606 607def load ()-> UnquantizedTensor : 608elm_count = stride [0 ]* size [0 ] 609return UnquantizedTensor (storage .load (storage_offset ,elm_count ).reshape (size )) 610description = f'pickled storage_offset= { storage_offset } in { storage . description } ' 611return LazyTensor (load ,list (size ),storage .kind .data_type ,description ) 612 613@staticmethod 614def rebuild_from_type_v2 (func ,new_type ,args ,state ): 615return func (* args ) 616 617CLASSES :dict [tuple [str ,str ],type [LazyTensor ]| LazyStorageKind ]= { 618# getattr used here as a workaround for mypy not being smart enough to determine 619# the staticmethods have a __func__ attribute. 620 ('torch._tensor' ,'_rebuild_from_type_v2' ):getattr (rebuild_from_type_v2 ,'__func__' ), 621 ('torch._utils' ,'_rebuild_tensor_v2' ):getattr (lazy_rebuild_tensor_v2 ,'__func__' ), 622 ('torch' ,'BFloat16Storage' ):LazyStorageKind (DT_BF16 ), 623 ('torch' ,'HalfStorage' ):LazyStorageKind (DT_F16 ), 624 ('torch' ,'FloatStorage' ):LazyStorageKind (DT_F32 ), 625 ('torch' ,'IntStorage' ):LazyStorageKind (DT_I32 ), 626 ('torch' ,'Tensor' ):LazyTensor , 627 } 628 629def find_class (self ,module :str ,name :str )-> Any : 630if not module .startswith ('torch' ): 631return super ().find_class (module ,name ) 632return self .CLASSES [(module ,name )] 633 634 635def lazy_load_torch_file (outer_fp :IO [bytes ],path :Path )-> ModelPlus : 636zf = zipfile .ZipFile (outer_fp ) 637pickle_paths = [name for name in zf .namelist ()if name .endswith ('.pkl' )] 638assert len (pickle_paths )== 1 ,pickle_paths 639pickle_fp = zf .open (pickle_paths [0 ],'r' ) 640unpickler = LazyUnpickler (pickle_fp , 641data_base_path = pickle_paths [0 ][:- 4 ], 642zip_file = zf ) 643model = unpickler .load () 644if 'model' in model :model = model ['model' ] 645as_dict = dict (model .items ()) 646return ModelPlus (model = as_dict ,paths = [path ],format = 'torch' ,vocab = None ) 647 648 649def lazy_load_safetensors_file (fp :IO [bytes ],path :Path )-> ModelPlus : 650header_size ,= struct .unpack ('<Q' ,fp .read (8 )) 651header :dict [str ,dict [str ,Any ]]= json .loads (fp .read (header_size )) 652# Use mmap for the actual data to avoid race conditions with the file offset. 653mapped = memoryview (mmap .mmap (fp .fileno (),0 ,access = mmap .ACCESS_READ )) 654byte_buf = mapped [8 + header_size :] 655 656def convert (info :dict [str ,Any ])-> LazyTensor : 657data_type = SAFETENSORS_DATA_TYPES [info ['dtype' ]] 658numpy_dtype = data_type .dtype 659shape :list [int ]= info ['shape' ] 660begin ,end = info ['data_offsets' ] 661assert 0 <= begin <= end <= len (byte_buf ) 662assert end - begin == math .prod (shape )* numpy_dtype .itemsize 663buf = byte_buf [begin :end ] 664 665def load ()-> UnquantizedTensor : 666return UnquantizedTensor (np .frombuffer (buf ,dtype = numpy_dtype ).reshape (shape )) 667description = f'safetensors begin= { begin } end= { end } type= { data_type } path= { path } ' 668return LazyTensor (load ,shape ,data_type ,description ) 669model = {name :convert (info )for (name ,info )in header .items ()if name != '__metadata__' } 670return ModelPlus (model = model ,paths = [path ],format = 'safetensors' ,vocab = None ) 671 672 673def must_read (fp :IO [bytes ],length :int )-> bytes : 674ret = fp .read (length ) 675if len (ret )< length : 676raise EOFError ("unexpectedly reached end of file" ) 677return ret 678 679 680@ functools . lru_cache ( maxsize = None ) 681def lazy_load_file (path :Path )-> ModelPlus : 682fp = open (path ,'rb' ) 683first8 = fp .read (8 ) 684fp .seek (0 ) 685if first8 [:2 ]== b'PK' : 686# A zip file, i.e. PyTorch format 687return lazy_load_torch_file (fp ,path ) 688elif struct .unpack ('<Q' ,first8 )[0 ]< 16 * 1024 * 1024 : 689# Probably safetensors 690return lazy_load_safetensors_file (fp ,path ) 691else : 692raise ValueError (f"unknown format: { path } " ) 693 694 695In = TypeVar ('In' ) 696Out = TypeVar ('Out' ) 697 698 699def bounded_parallel_map (func :Callable [[In ],Out ],iterable :Iterable [In ],concurrency :int ,max_workers :int | None = None ,use_processpool_executor :bool = False )-> Iterable [Out ]: 700'''Parallel map, but with backpressure. If the caller doesn't call `next` 701fast enough, this will stop calling `func` at some point rather than 702letting results pile up in memory. Specifically, there is a max of one 703output value buffered per thread.''' 704if concurrency < 2 : 705yield from map (func ,iterable ) 706# Not reached. 707iterable = iter (iterable ) 708executor_class :type [ThreadPoolExecutor ]| type [ProcessPoolExecutor ] 709if use_processpool_executor : 710executor_class = ProcessPoolExecutor 711else : 712executor_class = ThreadPoolExecutor 713with executor_class (max_workers = max_workers )as executor : 714futures :list [concurrent .futures .Future [Out ]]= [] 715done = False 716for _ in range (concurrency ): 717try : 718futures .append (executor .submit (func ,next (iterable ))) 719except StopIteration : 720done = True 721break 722 723while futures : 724result = futures .pop (0 ).result () 725while not done and len (futures )< concurrency : 726try : 727futures .append (executor .submit (func ,next (iterable ))) 728except StopIteration : 729done = True 730break 731yield result 732 733 734def check_vocab_size (params :Params ,vocab :BaseVocab ,pad_vocab :bool = False )-> None : 735# Handle special case where the model's vocab size is not set 736if params .n_vocab == - 1 : 737raise ValueError ( 738"The model's vocab size is set to -1 in params.json. Please update it manually." 739+ (f" Maybe { vocab . vocab_size } ?" if isinstance (vocab ,Vocab )else "" ), 740 ) 741if not isinstance (vocab ,Vocab ): 742return # model has no vocab 743 744# Check for a vocab size mismatch 745if params .n_vocab == vocab .vocab_size : 746logger .warning ("Ignoring added_tokens.json since model matches vocab size without it." ) 747return 748 749if pad_vocab and params .n_vocab > vocab .vocab_size : 750pad_count = params .n_vocab - vocab .vocab_size 751logger .debug ( 752f"Padding vocab with { pad_count } token(s) - <dummy00001> through <dummy { pad_count :05 } >" 753 ) 754for i in range (1 ,pad_count + 1 ): 755vocab .added_tokens_dict [f"<dummy { i :05 } >" ]= - 1 756vocab .added_tokens_list .append (f"<dummy { i :05 } >" ) 757vocab .vocab_size = params .n_vocab 758return 759 760msg = f"Vocab size mismatch (model has { params . n_vocab } , but { vocab . fname_tokenizer } has { vocab . vocab_size } )." 761if vocab .vocab_size < params .n_vocab < vocab .vocab_size + 20 : 762msg += f" Most likely you are missing added_tokens.json (should be in { vocab . fname_tokenizer . parent } )." 763if vocab .vocab_size < params .n_vocab : 764msg += " Add the --pad-vocab option and try again." 765 766raise ValueError (msg ) 767 768 769class OutputFile : 770def __init__ (self ,fname_out :Path ,endianess :gguf .GGUFEndian = gguf .GGUFEndian .LITTLE ): 771self .gguf = gguf .GGUFWriter (fname_out ,gguf .MODEL_ARCH_NAMES [ARCH ],endianess = endianess ) 772 773def add_meta_model (self ,params :Params ,metadata :gguf .Metadata | None )-> None : 774# Metadata About The Model And Its Provenence 775name = "LLaMA" 776if metadata is not None and metadata .name is not None : 777name = metadata .name 778elif params .path_model is not None : 779name = params .path_model .name 780elif params .n_ctx == 4096 : 781# Heuristic detection of LLaMA v2 model 782name = "LLaMA v2" 783 784self .gguf .add_name (name ) 785 786if metadata is not None : 787if metadata .author is not None : 788self .gguf .add_author (metadata .author ) 789if metadata .version is not None : 790self .gguf .add_version (metadata .version ) 791if metadata .organization is not None : 792self .gguf .add_organization (metadata .organization ) 793 794if metadata .finetune is not None : 795self .gguf .add_finetune (metadata .finetune ) 796if metadata .basename is not None : 797self .gguf .add_basename (metadata .basename ) 798 799if metadata .description is not None : 800self .gguf .add_description (metadata .description ) 801if metadata .quantized_by is not None : 802self .gguf .add_quantized_by (metadata .quantized_by ) 803 804if metadata .size_label is not None : 805self .gguf .add_size_label (metadata .size_label ) 806 807if metadata .license is not None : 808self .gguf .add_license (metadata .license ) 809if metadata .license_name is not None : 810self .gguf .add_license_name (metadata .license_name ) 811if metadata .license_link is not None : 812self .gguf .add_license_link (metadata .license_link ) 813 814if metadata .url is not None : 815self .gguf .add_url (metadata .url ) 816if metadata .doi is not None : 817self .gguf .add_doi (metadata .doi ) 818if metadata .uuid is not None : 819self .gguf .add_uuid (metadata .uuid ) 820if metadata .repo_url is not None : 821self .gguf .add_repo_url (metadata .repo_url ) 822 823if metadata .source_url is not None : 824self .gguf .add_source_url (metadata .source_url ) 825if metadata .source_doi is not None : 826self .gguf .add_source_doi (metadata .source_doi ) 827if metadata .source_uuid is not None : 828self .gguf .add_source_uuid (metadata .source_uuid ) 829if metadata .source_repo_url is not None : 830self .gguf .add_source_repo_url (metadata .source_repo_url ) 831 832if metadata .base_models is not None : 833self .gguf .add_base_model_count (len (metadata .base_models )) 834for key ,base_model_entry in enumerate (metadata .base_models ): 835if "name" in base_model_entry : 836self .gguf .add_base_model_name (key ,base_model_entry ["name" ]) 837if "author" in base_model_entry : 838self .gguf .add_base_model_author (key ,base_model_entry ["author" ]) 839if "version" in base_model_entry : 840self .gguf .add_base_model_version (key ,base_model_entry ["version" ]) 841if "organization" in base_model_entry : 842self .gguf .add_base_model_organization (key ,base_model_entry ["organization" ]) 843if "description" in base_model_entry : 844self .gguf .add_base_model_description (key ,base_model_entry ["description" ]) 845if "url" in base_model_entry : 846self .gguf .add_base_model_url (key ,base_model_entry ["url" ]) 847if "doi" in base_model_entry : 848self .gguf .add_base_model_doi (key ,base_model_entry ["doi" ]) 849if "uuid" in base_model_entry : 850self .gguf .add_base_model_uuid (key ,base_model_entry ["uuid" ]) 851if "repo_url" in base_model_entry : 852self .gguf .add_base_model_repo_url (key ,base_model_entry ["repo_url" ]) 853 854if metadata .datasets is not None : 855self .gguf .add_dataset_count (len (metadata .datasets )) 856for key ,dataset_entry in enumerate (metadata .datasets ): 857if "name" in dataset_entry : 858self .gguf .add_dataset_name (key ,dataset_entry ["name" ]) 859if "author" in dataset_entry : 860self .gguf .add_dataset_author (key ,dataset_entry ["author" ]) 861if "version" in dataset_entry : 862self .gguf .add_dataset_version (key ,dataset_entry ["version" ]) 863if "organization" in dataset_entry : 864self .gguf .add_dataset_organization (key ,dataset_entry ["organization" ]) 865if "description" in dataset_entry : 866self .gguf .add_dataset_description (key ,dataset_entry ["description" ]) 867if "url" in dataset_entry : 868self .gguf .add_dataset_url (key ,dataset_entry ["url" ]) 869if "doi" in dataset_entry : 870self .gguf .add_dataset_doi (key ,dataset_entry ["doi" ]) 871if "uuid" in dataset_entry : 872self .gguf .add_dataset_uuid (key ,dataset_entry ["uuid" ]) 873if "repo_url" in dataset_entry : 874self .gguf .add_dataset_repo_url (key ,dataset_entry ["repo_url" ]) 875 876if metadata .tags is not None : 877self .gguf .add_tags (metadata .tags ) 878if metadata .languages is not None : 879self .gguf .add_languages (metadata .languages ) 880 881def add_meta_arch (self ,params :Params )-> None : 882# Metadata About The Neural Architecture Itself 883self .gguf .add_vocab_size (params .n_vocab ) 884self .gguf .add_context_length (params .n_ctx ) 885self .gguf .add_embedding_length (params .n_embd ) 886self .gguf .add_block_count (params .n_layer ) 887self .gguf .add_feed_forward_length (params .n_ff ) 888self .gguf .add_rope_dimension_count (params .n_embd // params .n_head ) 889self .gguf .add_head_count (params .n_head ) 890self .gguf .add_head_count_kv (params .n_head_kv ) 891 892if params .n_experts : 893self .gguf .add_expert_count (params .n_experts ) 894 895if params .n_experts_used : 896self .gguf .add_expert_used_count (params .n_experts_used ) 897 898if params .f_norm_eps : 899self .gguf .add_layer_norm_rms_eps (params .f_norm_eps ) 900else : 901raise ValueError ('f_norm_eps is None' ) 902 903if params .f_rope_freq_base is not None : 904self .gguf .add_rope_freq_base (params .f_rope_freq_base ) 905 906if params .rope_scaling_type : 907assert params .f_rope_scale is not None 908self .gguf .add_rope_scaling_type (params .rope_scaling_type ) 909self .gguf .add_rope_scaling_factor (params .f_rope_scale ) 910 911if params .n_ctx_orig is not None : 912self .gguf .add_rope_scaling_orig_ctx_len (params .n_ctx_orig ) 913 914if params .rope_finetuned is not None : 915self .gguf .add_rope_scaling_finetuned (params .rope_finetuned ) 916 917if params .ftype is not None : 918self .gguf .add_file_type (params .ftype ) 919 920def extract_vocabulary_from_model (self ,vocab :Vocab )-> tuple [list [bytes ],list [float ],list [gguf .TokenType ]]: 921tokens = [] 922scores = [] 923toktypes = [] 924 925# NOTE: `all_tokens` returns the base vocabulary and added tokens 926for text ,score ,toktype in vocab .all_tokens (): 927tokens .append (text ) 928scores .append (score ) 929toktypes .append (toktype ) 930 931assert len (tokens )== vocab .vocab_size 932 933return tokens ,scores ,toktypes 934 935def add_meta_vocab (self ,vocab :Vocab )-> None : 936# Ensure that tokenizer_model is added to the GGUF model 937self .gguf .add_tokenizer_model (vocab .tokenizer_model ) 938 939# Extract model vocabulary for model conversion 940tokens ,scores ,toktypes = self .extract_vocabulary_from_model (vocab ) 941 942# Add extracted token information for model conversion 943self .gguf .add_token_list (tokens ) 944self .gguf .add_token_scores (scores ) 945self .gguf .add_token_types (toktypes ) 946 947def add_meta_special_vocab (self ,svocab :gguf .SpecialVocab )-> None : 948svocab .add_to_gguf (self .gguf ) 949 950def add_tensor_info (self ,name :str ,tensor :LazyTensor )-> None : 951n_elements = int (np .prod (tensor .shape )) 952raw_dtype = getattr (tensor .data_type ,'ggml_type' ,None ) 953data_type = getattr (tensor .data_type ,'quantized_type' ,None )or tensor .data_type .dtype 954data_nbytes = tensor .data_type .elements_to_bytes (n_elements ) 955self .gguf .add_tensor_info (name ,tensor .shape ,data_type ,data_nbytes ,raw_dtype = raw_dtype ) 956 957def write_meta (self )-> None : 958self .gguf .write_header_to_file () 959self .gguf .write_kv_data_to_file () 960 961def write_tensor_info (self )-> None : 962self .gguf .write_ti_data_to_file () 963 964def write_tensor_data (self ,ftype :GGMLFileType ,model :LazyModel ,concurrency :int )-> None : 965ndarrays_inner = bounded_parallel_map (OutputFile .do_item ,model .items (),concurrency = concurrency ) 966if ftype == GGMLFileType .MostlyQ8_0 : 967ndarrays = bounded_parallel_map ( 968OutputFile .maybe_do_quantize ,ndarrays_inner ,concurrency = concurrency ,max_workers = concurrency , 969use_processpool_executor = True , 970 ) 971else : 972ndarrays = map (OutputFile .maybe_do_quantize ,ndarrays_inner ) 973 974start = time .time () 975for i , ((name ,lazy_tensor ),ndarray )in enumerate (zip (model .items (),ndarrays )): 976elapsed = time .time ()- start 977size = ' x ' .join (f" { dim :6d } " for dim in lazy_tensor .shape ) 978padi = len (str (len (model ))) 979logger .info ( 980f"[ { i + 1 :{ padi }d } / { len ( model ) } ] Writing tensor { name :38s } | size { size :16 } | type { lazy_tensor . data_type . name :4 } | T+ { int ( elapsed ):4 } " 981 ) 982self .gguf .write_tensor_data (ndarray ) 983 984def close (self )-> None : 985self .gguf .close () 986 987@staticmethod 988def write_vocab_only ( 989fname_out :Path ,params :Params ,vocab :Vocab ,svocab :gguf .SpecialVocab , 990endianess :gguf .GGUFEndian = gguf .GGUFEndian .LITTLE ,pad_vocab :bool = False ,metadata :gguf .Metadata | None = None , 991 )-> None : 992check_vocab_size (params ,vocab ,pad_vocab = pad_vocab ) 993 994of = OutputFile (fname_out ,endianess = endianess ) 995 996# meta data 997of .add_meta_model (params ,metadata ) 998of .add_meta_arch (params ) 999of .add_meta_vocab (vocab ) 1000of .add_meta_special_vocab (svocab ) 1001 1002of .write_meta () 1003 1004of .close () 1005 1006@staticmethod 1007def do_item (item :tuple [str ,LazyTensor ])-> tuple [DataType ,NDArray ]: 1008name ,lazy_tensor = item 1009tensor = lazy_tensor .load ().to_ggml () 1010return (lazy_tensor .data_type ,tensor .ndarray ) 1011 1012@staticmethod 1013def maybe_do_quantize (item :tuple [DataType ,NDArray ])-> NDArray : 1014dt ,arr = item 1015if not isinstance (dt ,QuantizedDataType ): 1016return arr 1017return dt .quantize (arr ) 1018 1019@staticmethod 1020def write_all ( 1021fname_out :Path ,ftype :GGMLFileType ,params :Params ,model :LazyModel ,vocab :BaseVocab ,svocab :gguf .SpecialVocab , 1022concurrency :int = DEFAULT_CONCURRENCY ,endianess :gguf .GGUFEndian = gguf .GGUFEndian .LITTLE , 1023pad_vocab :bool = False , 1024metadata :gguf .Metadata | None = None , 1025 )-> None : 1026check_vocab_size (params ,vocab ,pad_vocab = pad_vocab ) 1027 1028of = OutputFile (fname_out ,endianess = endianess ) 1029 1030# meta data 1031of .add_meta_model (params ,metadata ) 1032of .add_meta_arch (params ) 1033if isinstance (vocab ,Vocab ): 1034of .add_meta_vocab (vocab ) 1035of .add_meta_special_vocab (svocab ) 1036else :# NoVocab 1037of .gguf .add_tokenizer_model (vocab .tokenizer_model ) 1038 1039# tensor info 1040for name ,lazy_tensor in model .items (): 1041of .add_tensor_info (name ,lazy_tensor ) 1042 1043of .write_meta () 1044of .write_tensor_info () 1045 1046# tensor data 1047of .write_tensor_data (ftype ,model ,concurrency ) 1048 1049of .close () 1050 1051 1052def pick_output_type (model :LazyModel ,output_type_str :str | None )-> GGMLFileType : 1053wq_type = model [gguf .TENSOR_NAMES [gguf .MODEL_TENSOR .ATTN_Q ].format (bid = 0 )+ ".weight" ].data_type 1054 1055if output_type_str == "f32" or (output_type_str is None and wq_type in (DT_F32 ,DT_BF16 )): 1056return GGMLFileType .AllF32 1057if output_type_str == "f16" or (output_type_str is None and wq_type == DT_F16 ): 1058return GGMLFileType .MostlyF16 1059if output_type_str == "q8_0" : 1060return GGMLFileType .MostlyQ8_0 1061 1062name_to_type = {name :lazy_tensor .data_type for (name ,lazy_tensor )in model .items ()} 1063 1064raise ValueError (f"Unexpected combination of types: { name_to_type } " ) 1065 1066 1067def per_model_weight_count_estimation (tensors :Iterable [tuple [str ,LazyTensor ]])-> tuple [int ,int ,int ]: 1068total_params = 0 1069shared_params = 0 1070expert_params = 0 1071 1072for name ,lazy_tensor in tensors : 1073# We don't need these 1074if name .endswith ((".attention.masked_bias" ,".attention.bias" ,".rotary_emb.inv_freq" )): 1075continue 1076 1077# Got A Tensor 1078sum_weights_in_tensor :int = 1 1079 1080# Tensor Volume 1081for dim in lazy_tensor .shape : 1082sum_weights_in_tensor *= dim 1083 1084if ".experts." in name : 1085if ".experts.0." in name : 1086expert_params += sum_weights_in_tensor 1087else : 1088shared_params += sum_weights_in_tensor 1089 1090total_params += sum_weights_in_tensor 1091 1092return total_params ,shared_params ,expert_params 1093 1094 1095def convert_to_output_type (model :LazyModel ,output_type :GGMLFileType )-> LazyModel : 1096return {name :tensor .astype (output_type .type_for_tensor (name ,tensor )) 1097for (name ,tensor )in model .items ()} 1098 1099 1100def convert_model_names (model :LazyModel ,params :Params ,skip_unknown :bool )-> LazyModel : 1101tmap = gguf .TensorNameMap (ARCH ,params .n_layer ) 1102should_skip = set (gguf .MODEL_TENSOR_SKIP .get (ARCH , [])) 1103 1104tmp = model 1105 1106# merge experts into one tensor 1107if params .n_experts and params .n_experts > 0 : 1108for i_l in range (params .n_layer ): 1109for w in range (1 ,4 ): 1110experts = [] 1111for e in range (params .n_experts ): 1112if f"layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" in model : 1113experts .append (model [f"layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" ]) 1114del tmp [f"layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" ] 1115elif f"model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" in model : 1116experts .append (model [f"model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" ]) 1117del tmp [f"model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" ] 1118else : 1119raise ValueError (f"Expert tensor not found: layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" ) 1120tmp [f"layers. { i_l } .feed_forward.experts.w { w } .weight" ]= pack_experts_lazy (experts ) 1121 1122# HF models permut or pack some of the tensors, so we need to undo that 1123for i in itertools .count (): 1124if f"model.layers. { i } .self_attn.q_proj.weight" in model : 1125logger .debug (f"Permuting layer { i } " ) 1126tmp [f"model.layers. { i } .self_attn.q_proj.weight" ]= permute_lazy (model [f"model.layers. { i } .self_attn.q_proj.weight" ],params .n_head ,params .n_head ) 1127tmp [f"model.layers. { i } .self_attn.k_proj.weight" ]= permute_lazy (model [f"model.layers. { i } .self_attn.k_proj.weight" ],params .n_head ,params .n_head_kv ) 1128# tmp[f"model.layers.{i}.self_attn.v_proj.weight"] = model[f"model.layers.{i}.self_attn.v_proj.weight"] 1129elif f"model.layers. { i } .self_attn.W_pack.weight" in model : 1130logger .debug (f"Unpacking and permuting layer { i } " ) 1131tmp [f"model.layers. { i } .self_attn.q_proj.weight" ]= permute_part_lazy (model [f"model.layers. { i } .self_attn.W_pack.weight" ],0 ,params .n_head ,params .n_head ) 1132tmp [f"model.layers. { i } .self_attn.k_proj.weight" ]= permute_part_lazy (model [f"model.layers. { i } .self_attn.W_pack.weight" ],1 ,params .n_head ,params .n_head_kv ) 1133tmp [f"model.layers. { i } .self_attn.v_proj.weight" ]= part_lazy (model [f"model.layers. { i } .self_attn.W_pack.weight" ],2 ) 1134del tmp [f"model.layers. { i } .self_attn.W_pack.weight" ] 1135else : 1136break 1137 1138out :LazyModel = {} 1139for name ,lazy_tensor in model .items (): 1140tensor_type ,name_new = tmap .get_type_and_name (name ,try_suffixes = (".weight" ,".bias" ))or (None ,None ) 1141if name_new is None : 1142if skip_unknown : 1143logger .warning (f"Unexpected tensor name: { name } - skipping" ) 1144continue 1145raise ValueError (f"Unexpected tensor name: { name } . Use --skip-unknown to ignore it (e.g. LLaVA)" ) 1146 1147if tensor_type in should_skip : 1148logger .debug (f"skipping tensor { name_new } " ) 1149continue 1150 1151logger .debug (f" { name :48s } -> { name_new :40s } | { lazy_tensor . data_type . name :6s } | { lazy_tensor . shape } " ) 1152out [name_new ]= lazy_tensor 1153 1154return out 1155 1156 1157def nth_multifile_path (path :Path ,n :int )-> Path | None : 1158'''Given any path belonging to a multi-file model (e.g. foo.bin.1), return 1159the nth path in the model. 1160''' 1161# Support the following patterns: 1162patterns = [ 1163# - x.00.pth, x.01.pth, etc. 1164 (r'\.[0-9]{2}\.pth$' ,f'. { n :02 } .pth' ), 1165# - x-00001-of-00002.bin, x-00002-of-00002.bin, etc. 1166 (r'-[0-9]{5}-of-(.*)$' ,fr'- { n :05 } -of-\1' ), 1167# x.bin, x.bin.1, etc. 1168 (r'(\.[0-9]+)?$' ,r'\1' if n == 0 else fr'\1. { n } ' ) 1169 ] 1170for regex ,replacement in patterns : 1171if re .search (regex ,path .name ): 1172new_path = path .with_name (re .sub (regex ,replacement ,path .name )) 1173if new_path .exists (): 1174return new_path 1175return None 1176 1177 1178def find_multifile_paths (path :Path )-> list [Path ]: 1179'''Given any path belonging to a multi-file model (e.g. foo.bin.1), return 1180the whole list of paths in the model. 1181''' 1182ret :list [Path ]= [] 1183for i in itertools .count (): 1184nth_path = nth_multifile_path (path ,i ) 1185if nth_path is None : 1186break 1187ret .append (nth_path ) 1188if not ret : 1189# No matches. This should only happen if the file was named, e.g., 1190# foo.0, and there was no file named foo. Oh well, try to process it 1191# as a single file. 1192return [path ] 1193return ret 1194 1195 1196def load_some_model (path :Path )-> ModelPlus : 1197'''Load a model of any supported format.''' 1198# Be extra-friendly and accept either a file or a directory: 1199if path .is_dir (): 1200# Check if it's a set of safetensors files first 1201globs = ["model-00001-of-*.safetensors" ,"model.safetensors" ,"consolidated.safetensors" ] 1202files = [file for glob in globs for file in path .glob (glob )] 1203if not files : 1204# Try the PyTorch patterns too, with lower priority 1205globs = ["consolidated.00.pth" ,"pytorch_model-00001-of-*.bin" ,"*.pt" ,"pytorch_model.bin" ] 1206files = [file for glob in globs for file in path .glob (glob )] 1207if not files : 1208raise FileNotFoundError (f"Can't find model in directory { path } " ) 1209if len (files )> 1 : 1210raise ValueError (f"Found multiple models in { path } , not sure which to pick: { files } " ) 1211path = files [0 ] 1212 1213paths = find_multifile_paths (path ) 1214models_plus :list [ModelPlus ]= [] 1215for path in paths : 1216logger .info (f"Loading model file { path } " ) 1217models_plus .append (lazy_load_file (path )) 1218 1219model_plus = merge_multifile_models (models_plus ) 1220return model_plus 1221 1222 1223class VocabFactory : 1224_VOCAB_CLASSES :list [type [Vocab ]]= [SentencePieceVocab ,BpeVocab ,LlamaHfVocab ] 1225 1226def __init__ (self ,path :Path ): 1227self .path = path 1228 1229def _create_special_vocab (self ,vocab :BaseVocab ,model_parent_path :Path )-> gguf .SpecialVocab : 1230load_merges = vocab .name == "bpe" 1231n_vocab = vocab .vocab_size if isinstance (vocab ,Vocab )else None 1232return gguf .SpecialVocab ( 1233model_parent_path , 1234load_merges = load_merges , 1235special_token_types = None ,# Predetermined or passed as a parameter 1236n_vocab = n_vocab , 1237 ) 1238 1239def _create_vocab_by_path (self ,vocab_types :list [str ])-> Vocab : 1240vocab_classes :dict [str ,type [Vocab ]]= {cls .name :cls for cls in self ._VOCAB_CLASSES } 1241selected_vocabs :dict [str ,type [Vocab ]]= {} 1242for vtype in vocab_types : 1243try : 1244selected_vocabs [vtype ]= vocab_classes [vtype ] 1245except KeyError : 1246raise ValueError (f"Unsupported vocabulary type { vtype } " )from None 1247 1248for vtype ,cls in selected_vocabs .items (): 1249try : 1250vocab = cls (self .path ) 1251break 1252except FileNotFoundError : 1253pass # ignore unavailable tokenizers 1254else : 1255raise FileNotFoundError (f"Could not find a tokenizer matching any of { vocab_types } " ) 1256 1257logger .info (f"Loaded vocab file { vocab . fname_tokenizer !r } , type { vocab . name !r } " ) 1258return vocab 1259 1260def load_vocab (self ,vocab_types :list [str ]| None ,model_parent_path :Path )-> tuple [BaseVocab ,gguf .SpecialVocab ]: 1261vocab :BaseVocab 1262if vocab_types is None : 1263vocab = NoVocab () 1264else : 1265vocab = self ._create_vocab_by_path (vocab_types ) 1266# FIXME: Respect --vocab-dir? 1267special_vocab = self ._create_special_vocab ( 1268vocab , 1269model_parent_path , 1270 ) 1271return vocab ,special_vocab 1272 1273 1274def default_convention_outfile (file_type :GGMLFileType ,expert_count :int | None ,model_params_count :tuple [int ,int ,int ],metadata :gguf .Metadata )-> str : 1275name = metadata .name if metadata .name is not None else None 1276basename = metadata .basename if metadata .basename is not None else None 1277finetune = metadata .finetune if metadata .finetune is not None else None 1278version = metadata .version if metadata .version is not None else None 1279size_label = metadata .size_label if metadata .size_label is not None else gguf .size_label (* model_params_count ,expert_count = expert_count or 0 ) 1280 1281output_type = { 1282GGMLFileType .AllF32 :"F32" , 1283GGMLFileType .MostlyF16 :"F16" , 1284GGMLFileType .MostlyQ8_0 :"Q8_0" , 1285 }[file_type ] 1286 1287return gguf .naming_convention (name ,basename ,finetune ,version ,size_label ,output_type ) 1288 1289 1290def default_outfile (model_paths :list [Path ],file_type :GGMLFileType ,expert_count :int | None ,model_params_count :tuple [int ,int ,int ],metadata :gguf .Metadata )-> Path : 1291default_filename = default_convention_outfile (file_type ,expert_count ,model_params_count ,metadata ) 1292ret = model_paths [0 ].parent / f" { default_filename } .gguf" 1293if ret in model_paths : 1294logger .error ( 1295f"Error: Default output path ( { ret } ) would overwrite the input. " 1296"Please explicitly specify a path using --outfile." ) 1297sys .exit (1 ) 1298return ret 1299 1300 1301def do_dump_model (model_plus :ModelPlus )-> None : 1302f"model_plus.paths = { model_plus . paths !r } " )# noqa: NP100 1303f"model_plus.format = { model_plus . format !r } " )# noqa: NP100 1304f"model_plus.vocab = { model_plus . vocab !r } " )# noqa: NP100 1305for name ,lazy_tensor in model_plus .model .items (): 1306f" { name } : shape= { lazy_tensor . shape } type= { lazy_tensor . data_type } ; { lazy_tensor . description } " )# noqa: NP100 1307 1308 1309def main (args_in :list [str ]| None = None )-> None : 1310output_choices = ["f32" ,"f16" ] 1311dummy_val = np .uint32 (1 ) 1312if dummy_val == dummy_val .view (dummy_val .dtype .newbyteorder ("<" )): 1313# We currently only support Q8_0 output on little endian systems. 1314output_choices .append ("q8_0" ) 1315parser = argparse .ArgumentParser (description = "Convert a LLaMA model to a GGML compatible file" ) 1316parser .add_argument ("--dump" ,action = "store_true" ,help = "don't convert, just show what's in the model" ) 1317parser .add_argument ("--dump-single" ,action = "store_true" ,help = "don't convert, just show what's in a single model file" ) 1318parser .add_argument ("--vocab-only" ,action = "store_true" ,help = "extract only the vocab" ) 1319parser .add_argument ("--no-vocab" ,action = "store_true" ,help = "store model without the vocab" ) 1320parser .add_argument ("--outtype" ,choices = output_choices ,help = "output format - note: q8_0 may be very slow (default: f16 or f32 based on input)" ) 1321parser .add_argument ("--vocab-dir" ,type = Path ,help = "directory containing tokenizer.model, if separate from model file" ) 1322parser .add_argument ("--vocab-type" ,help = "vocab types to try in order, choose from 'spm', 'bpe', 'hfft' (default: spm,hfft)" ,default = "spm,hfft" ) 1323parser .add_argument ("--outfile" ,type = Path ,help = "path to write to; default: based on input" ) 1324parser .add_argument ("model" ,type = Path ,help = "directory containing model file, or model file itself (*.pth, *.pt, *.bin)" ) 1325parser .add_argument ("--ctx" ,type = int ,help = "model training context (default: based on input)" ) 1326parser .add_argument ("--concurrency" ,type = int ,help = f"concurrency used for conversion (default: { DEFAULT_CONCURRENCY } )" ,default = DEFAULT_CONCURRENCY ) 1327parser .add_argument ("--big-endian" ,action = "store_true" ,help = "model is executed on big endian machine" ) 1328parser .add_argument ("--pad-vocab" ,action = "store_true" ,help = "add pad tokens when model vocab expects more than tokenizer metadata provides" ) 1329parser .add_argument ("--skip-unknown" ,action = "store_true" ,help = "skip unknown tensor names instead of failing" ) 1330parser .add_argument ("--verbose" ,action = "store_true" ,help = "increase output verbosity" ) 1331parser .add_argument ("--metadata" ,type = Path ,help = "Specify the path for an authorship metadata override file" ) 1332parser .add_argument ("--get-outfile" ,action = "store_true" ,help = "get calculated default outfile name" ) 1333parser .add_argument ("--model-name" ,type = str ,default = None ,help = "name of the model" ) 1334 1335args = parser .parse_args (args_in ) 1336 1337if args .verbose : 1338logging .basicConfig (level = logging .DEBUG ) 1339elif args .dump_single or args .dump or args .get_outfile : 1340# Avoid printing anything besides the dump output 1341logging .basicConfig (level = logging .WARNING ) 1342else : 1343logging .basicConfig (level = logging .INFO ) 1344 1345model_name = args .model_name 1346dir_model = args .model 1347 1348metadata = gguf .Metadata .load (args .metadata ,dir_model ,model_name ) 1349 1350if args .get_outfile : 1351model_plus = load_some_model (dir_model ) 1352params = Params .load (model_plus ) 1353model = convert_model_names (model_plus .model ,params ,args .skip_unknown ) 1354model_params_count = per_model_weight_count_estimation (model_plus .model .items ()) 1355ftype = pick_output_type (model ,args .outtype ) 1356 1357if (metadata is None or metadata .name is None )and params .path_model is not None : 1358metadata .name = params .path_model .name 1359 1360f" { default_convention_outfile ( ftype , params . n_experts , model_params_count , metadata ) } " )# noqa: NP100 1361return 1362 1363if args .no_vocab and args .vocab_only : 1364raise ValueError ("--vocab-only does not make sense with --no-vocab" ) 1365 1366if args .dump_single : 1367model_plus = lazy_load_file (dir_model ) 1368do_dump_model (model_plus ) 1369return 1370 1371if not args .vocab_only : 1372model_plus = load_some_model (dir_model ) 1373else : 1374model_plus = ModelPlus (model = {},paths = [dir_model / 'dummy' ],format = 'none' ,vocab = None ) 1375 1376if args .dump : 1377do_dump_model (model_plus ) 1378return 1379 1380endianess = gguf .GGUFEndian .LITTLE 1381if args .big_endian : 1382endianess = gguf .GGUFEndian .BIG 1383 1384params = None 1385if args .pad_vocab or not args .vocab_only : 1386params = Params .load (model_plus ) 1387if params .n_ctx == - 1 : 1388if args .ctx is None : 1389msg = """\ 1390The model doesn't have a context size, and you didn't specify one with --ctx 1391Please specify one with --ctx: 1392- LLaMA v1: --ctx 2048 1393- LLaMA v2: --ctx 4096""" 1394parser .error (textwrap .dedent (msg )) 1395params .n_ctx = args .ctx 1396 1397if args .outtype : 1398params .ftype = { 1399"f32" :GGMLFileType .AllF32 , 1400"f16" :GGMLFileType .MostlyF16 , 1401"q8_0" :GGMLFileType .MostlyQ8_0 , 1402 }[args .outtype ] 1403 1404logger .info (f"params = { params } " ) 1405 1406model_parent_path = model_plus .paths [0 ].parent 1407vocab_path = Path (args .vocab_dir or dir_model or model_parent_path ) 1408vocab_factory = VocabFactory (vocab_path ) 1409vocab_types = None if args .no_vocab else args .vocab_type .split ("," ) 1410vocab ,special_vocab = vocab_factory .load_vocab (vocab_types ,model_parent_path ) 1411 1412if args .vocab_only : 1413assert isinstance (vocab ,Vocab ) 1414if not args .outfile : 1415raise ValueError ("need --outfile if using --vocab-only" ) 1416outfile = args .outfile 1417if params is None : 1418params = Params ( 1419n_vocab = vocab .vocab_size , 1420n_embd = 1 , 1421n_layer = 1 , 1422n_ctx = 1 , 1423n_ff = 1 , 1424n_head = 1 , 1425n_head_kv = 1 , 1426f_norm_eps = 1e-5 , 1427 ) 1428OutputFile .write_vocab_only (outfile ,params ,vocab ,special_vocab , 1429endianess = endianess ,pad_vocab = args .pad_vocab ,metadata = metadata ) 1430logger .info (f"Wrote { outfile } " ) 1431return 1432 1433if model_plus .vocab is not None and args .vocab_dir is None and not args .no_vocab : 1434vocab = model_plus .vocab 1435 1436assert params is not None 1437 1438if metadata .name is None and params .path_model is not None : 1439metadata .name = params .path_model .name 1440 1441model_params_count = per_model_weight_count_estimation (model_plus .model .items ()) 1442logger .info (f"model parameters count : { model_params_count } ( { gguf . model_weight_count_rounded_notation ( model_params_count [ 0 ]) } )" ) 1443 1444logger .info (f"Vocab info: { vocab } " ) 1445logger .info (f"Special vocab info: { special_vocab } " ) 1446model = model_plus .model 1447model = convert_model_names (model ,params ,args .skip_unknown ) 1448ftype = pick_output_type (model ,args .outtype ) 1449model = convert_to_output_type (model ,ftype ) 1450outfile = args .outfile or default_outfile (model_plus .paths ,ftype ,params .n_experts ,model_params_count ,metadata = metadata ) 1451 1452metadata .size_label = gguf .size_label (* model_params_count ,expert_count = params .n_experts or 0 ) 1453 1454params .ftype = ftype 1455logger .info (f"Writing { outfile } , format { ftype } " ) 1456 1457OutputFile .write_all (outfile ,ftype ,params ,model ,vocab ,special_vocab , 1458concurrency = args .concurrency ,endianess = endianess ,pad_vocab = args .pad_vocab ,metadata = metadata ) 1459logger .info (f"Wrote { outfile } " ) 1460 1461 1462if __name__ == '__main__' : 1463main ()