2023-08-23 10:29:09 -04:00
#!/usr/bin/env python3
2023-08-31 01:02:23 -04:00
from __future__ import annotations
2023-08-21 23:07:43 +03:00
2023-04-14 00:03:03 -07:00
import argparse
import concurrent.futures
import enum
import faulthandler
import functools
import itertools
import json
import math
import mmap
2023-12-14 17:09:34 +09:00
import os
2023-04-14 00:03:03 -07:00
import pickle
import re
import signal
import struct
import sys
2024-03-28 11:44:36 -04:00
import textwrap
2023-08-26 14:13:36 -06:00
import time
2023-04-14 00:03:03 -07:00
import zipfile
2024-03-28 11:44:36 -04:00
from abc import ABC , abstractmethod
2023-08-31 01:02:23 -04:00
from concurrent.futures import ProcessPoolExecutor , ThreadPoolExecutor
2023-04-14 00:03:03 -07:00
from dataclasses import dataclass
from pathlib import Path
2024-03-28 11:44:36 -04:00
from typing import TYPE_CHECKING , Any , Callable , ClassVar , IO , Iterable , Literal , Protocol , TypeVar , runtime_checkable
2023-08-31 01:02:23 -04:00
import numpy as np
2023-11-09 11:09:29 +01:00
from sentencepiece import SentencePieceProcessor
2023-04-14 14:23:21 +02:00
2024-01-20 18:14:18 -05:00
if 'NO_LOCAL_GGUF' not in os . environ :
sys . path . insert ( 1 , str ( Path ( __file__ ) . parent / 'gguf-py' ))
import gguf
2023-08-31 16:49:24 -06:00
2024-01-20 18:14:18 -05:00
if TYPE_CHECKING :
2024-04-09 13:44:08 -04:00
from typing_extensions import Self , TypeAlias
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
if hasattr ( faulthandler , 'register' ) and hasattr ( signal , 'SIGUSR1' ):
2023-04-14 00:03:03 -07:00
faulthandler . register ( signal . SIGUSR1 )
2024-01-20 18:14:18 -05:00
NDArray : TypeAlias = 'np.ndarray[Any, Any]'
2023-04-14 00:03:03 -07:00
2023-10-02 14:58:46 -04:00
ARCH = gguf . MODEL_ARCH . LLAMA
2023-08-21 23:07:43 +03:00
2023-08-26 14:13:36 -06:00
DEFAULT_CONCURRENCY = 8
2023-12-13 13:04:25 +01:00
2024-03-28 11:44:36 -04:00
ADDED_TOKENS_FILE = 'added_tokens.json'
FAST_TOKENIZER_FILE = 'tokenizer.json'
2023-08-21 23:07:43 +03:00
#
# data types
#
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-04-14 00:03:03 -07:00
@dataclass ( frozen = True )
2023-08-26 14:13:36 -06:00
class DataType :
2023-04-14 00:03:03 -07:00
name : str
2023-08-31 01:02:23 -04:00
dtype : np . dtype [ Any ]
valid_conversions : list [ str ]
2023-04-14 00:03:03 -07:00
2023-08-26 14:13:36 -06:00
def elements_to_bytes ( self , n_elements : int ) -> int :
return n_elements * self . dtype . itemsize
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-26 14:13:36 -06:00
@dataclass ( frozen = True )
class UnquantizedDataType ( DataType ):
pass
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-12-13 13:04:25 +01:00
DT_F16 = UnquantizedDataType ( 'F16' , dtype = np . dtype ( np . float16 ), valid_conversions = [ 'F32' , 'Q8_0' ])
DT_F32 = UnquantizedDataType ( 'F32' , dtype = np . dtype ( np . float32 ), valid_conversions = [ 'F16' , 'Q8_0' ])
DT_I32 = UnquantizedDataType ( 'I32' , dtype = np . dtype ( np . int16 ), valid_conversions = [])
DT_BF16 = UnquantizedDataType ( 'BF16' , dtype = np . dtype ( np . uint16 ), valid_conversions = [ 'F32' , 'F16' , 'Q8_0' ])
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-26 14:13:36 -06:00
@dataclass ( frozen = True )
class QuantizedDataType ( DataType ):
block_size : int
2023-08-31 01:02:23 -04:00
quantized_dtype : np . dtype [ Any ]
2023-08-26 14:13:36 -06:00
ggml_type : gguf . GGMLQuantizationType
def quantize ( self , arr : NDArray ) -> NDArray :
raise NotImplementedError ( f 'Quantization for { self . name } not implemented' )
def elements_to_bytes ( self , n_elements : int ) -> int :
assert n_elements % self . block_size == 0 , f 'Invalid number of elements { n_elements } for { self . name } with block size { self . block_size } '
return self . quantized_dtype . itemsize * ( n_elements // self . block_size )
2023-11-20 11:35:47 +01:00
2023-08-26 14:13:36 -06:00
@dataclass ( frozen = True )
class Q8_0QuantizedDataType ( QuantizedDataType ):
# Mini Q8_0 quantization in Python!
def quantize ( self , arr : NDArray ) -> NDArray :
assert arr . size % self . block_size == 0 and arr . size != 0 , f 'Bad array size { arr . size } '
assert arr . dtype == np . float32 , f 'Bad array type { arr . dtype } '
n_blocks = arr . size // self . block_size
blocks = arr . reshape (( n_blocks , self . block_size ))
# Much faster implementation of block quantization contributed by @Cebtenzzre
2023-11-20 11:35:47 +01:00
2023-08-31 01:02:23 -04:00
def quantize_blocks_q8_0 ( blocks : NDArray ) -> Iterable [ tuple [ Any , Any ]]:
2023-08-26 14:13:36 -06:00
d = abs ( blocks ) . max ( axis = 1 ) / np . float32 ( 127 )
with np . errstate ( divide = 'ignore' ):
qs = ( blocks / d [:, None ]) . round ()
qs [ d == 0 ] = 0
yield from zip ( d , qs )
return np . fromiter ( quantize_blocks_q8_0 ( blocks ), count = n_blocks , dtype = self . quantized_dtype )
2023-11-20 11:35:47 +01:00
2023-08-26 14:13:36 -06:00
DT_Q8_0 = Q8_0QuantizedDataType ( 'Q8_0' ,
2023-11-20 11:35:47 +01:00
dtype = np . dtype ( np . float32 ), valid_conversions = [],
ggml_type = gguf . GGMLQuantizationType . Q8_0 , block_size = 32 ,
quantized_dtype = np . dtype ([( 'd' , '<f2' ), ( 'qs' , 'i1' , ( 32 ,))]))
2023-08-26 14:13:36 -06:00
# Quantized types skipped here because they may also map to np.float32
2023-08-31 01:02:23 -04:00
NUMPY_TYPE_TO_DATA_TYPE : dict [ np . dtype [ Any ], DataType ] = {}
2023-08-26 14:13:36 -06:00
for dt in ( DT_BF16 , DT_F16 , DT_F32 , DT_I32 ):
if dt . dtype in NUMPY_TYPE_TO_DATA_TYPE :
raise ValueError ( f 'Invalid duplicate data type { dt } ' )
NUMPY_TYPE_TO_DATA_TYPE [ dt . dtype ] = dt
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
SAFETENSORS_DATA_TYPES : dict [ str , DataType ] = {
2023-08-21 23:07:43 +03:00
'BF16' : DT_BF16 ,
'F16' : DT_F16 ,
'F32' : DT_F32 ,
'I32' : DT_I32 ,
}
2023-04-14 00:03:03 -07:00
2023-08-22 20:05:59 +03:00
# TODO: match this with `llama_ftype`
# TODO: rename to LLAMAFileType
# TODO: move to `gguf.py`
2023-11-20 11:35:47 +01:00
2023-08-22 20:05:59 +03:00
class GGMLFileType ( enum . IntEnum ):
2023-08-26 14:13:36 -06:00
AllF32 = 0
MostlyF16 = 1 # except 1d tensors
MostlyQ8_0 = 7 # except 1d tensors
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
def type_for_tensor ( self , name : str , tensor : LazyTensor ) -> DataType :
2023-08-26 14:13:36 -06:00
dt = GGML_FILE_TYPE_TO_DATA_TYPE . get ( self )
if dt is None :
2023-04-14 00:03:03 -07:00
raise ValueError ( self )
2024-04-08 10:44:19 -05:00
# 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.
# 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.
2023-08-26 14:13:36 -06:00
return dt if len ( tensor . shape ) > 1 else DT_F32
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-31 01:02:23 -04:00
GGML_FILE_TYPE_TO_DATA_TYPE : dict [ GGMLFileType , DataType ] = {
2023-08-26 14:13:36 -06:00
GGMLFileType . AllF32 : DT_F32 ,
GGMLFileType . MostlyF16 : DT_F16 ,
GGMLFileType . MostlyQ8_0 : DT_Q8_0 ,
}
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
#
# hparams loading
#
2023-06-22 14:20:47 +02:00
2023-11-20 11:35:47 +01:00
2023-04-14 00:03:03 -07:00
@dataclass
class Params :
2024-01-20 18:14:18 -05:00
n_vocab : int
n_embd : int
n_layer : int
n_ctx : int
n_ff : int
n_head : int
n_head_kv : int
n_experts : int | None = None
n_experts_used : int | None = None
f_norm_eps : float | None = None
2023-08-21 23:07:43 +03:00
2024-01-20 18:14:18 -05:00
rope_scaling_type : gguf . RopeScalingType | None = None
f_rope_freq_base : float | None = None
f_rope_scale : float | None = None
n_orig_ctx : int | None = None
rope_finetuned : bool | None = None
2023-08-24 20:04:05 +02:00
2024-01-20 18:14:18 -05:00
ftype : GGMLFileType | None = None
2023-08-22 20:05:59 +03:00
2023-08-24 19:26:19 +03:00
# path to the directory containing the model files
2024-01-20 18:14:18 -05:00
path_model : Path | None = None
2023-08-24 19:26:19 +03:00
2023-04-14 00:03:03 -07:00
@staticmethod
2024-01-20 18:14:18 -05:00
def guessed ( model : LazyModel ) -> Params :
2023-06-22 14:20:47 +02:00
# try transformer naming first
2024-01-20 18:14:18 -05:00
n_vocab , n_embd = model [ "model.embed_tokens.weight" ] . shape if "model.embed_tokens.weight" in model else model [ "tok_embeddings.weight" ] . shape
2023-06-22 14:20:47 +02:00
# try transformer naming first
if "model.layers.0.self_attn.q_proj.weight" in model :
2024-01-20 18:14:18 -05:00
n_layer = next ( i for i in itertools . count () if f "model.layers. { i } .self_attn.q_proj.weight" not in model )
elif "model.layers.0.self_attn.W_pack.weight" in model : # next: try baichuan naming
n_layer = next ( i for i in itertools . count () if f "model.layers. { i } .self_attn.W_pack.weight" not in model )
2023-06-22 14:20:47 +02:00
else :
2024-01-20 18:14:18 -05:00
n_layer = next ( i for i in itertools . count () if f "layers. { i } .attention.wq.weight" not in model )
2023-06-22 14:20:47 +02:00
2023-07-07 00:23:49 +08:00
if n_layer < 1 :
2024-03-28 11:44:36 -04:00
msg = """ \
failed to guess 'n_layer'. This model is unknown or unsupported.
Suggestion: provide 'config.json' of the model in the same directory containing model files."""
raise KeyError ( textwrap . dedent ( msg ))
2023-07-07 00:23:49 +08:00
2024-01-20 18:14:18 -05:00
n_head = n_embd // 128 # guessed
n_mult = 256 # guessed
2023-08-21 23:07:43 +03:00
# TODO: verify this
n_ff = int ( 2 * ( 4 * n_embd ) / 3 )
n_ff = n_mult * (( n_ff + n_mult - 1 ) // n_mult )
2023-04-14 00:03:03 -07:00
return Params (
2024-01-20 18:14:18 -05:00
n_vocab = n_vocab ,
n_embd = n_embd ,
n_layer = n_layer ,
n_ctx = - 1 ,
n_ff = n_ff ,
n_head = n_head ,
n_head_kv = n_head ,
f_norm_eps = 1e-5 ,
2023-04-14 00:03:03 -07:00
)
2023-06-22 14:20:47 +02:00
@staticmethod
2024-01-20 18:14:18 -05:00
def loadHFTransformerJson ( model : LazyModel , config_path : Path ) -> Params :
2024-03-28 11:44:36 -04:00
with open ( config_path ) as f :
config = json . load ( f )
2023-06-22 14:20:47 +02:00
2023-11-01 18:04:33 -04:00
rope_scaling_type = f_rope_scale = n_orig_ctx = rope_finetuned = None
2023-08-26 07:11:17 -05:00
rope_scaling = config . get ( "rope_scaling" )
2023-11-01 18:04:33 -04:00
if rope_scaling is not None and ( typ := rope_scaling . get ( "type" )):
rope_factor = rope_scaling . get ( "factor" )
f_rope_scale = rope_factor
if typ == "linear" :
rope_scaling_type = gguf . RopeScalingType . LINEAR
elif typ == "yarn" :
rope_scaling_type = gguf . RopeScalingType . YARN
2024-01-20 18:14:18 -05:00
n_orig_ctx = rope_scaling [ 'original_max_position_embeddings' ]
rope_finetuned = rope_scaling [ 'finetuned' ]
2023-11-01 18:04:33 -04:00
else :
2024-01-20 18:14:18 -05:00
raise NotImplementedError ( f 'Unknown rope scaling type: { typ } ' )
2023-08-25 09:41:52 -05:00
2023-08-21 23:07:43 +03:00
if "max_sequence_length" in config :
n_ctx = config [ "max_sequence_length" ]
elif "max_position_embeddings" in config :
n_ctx = config [ "max_position_embeddings" ]
else :
2024-03-28 11:44:36 -04:00
msg = """ \
failed to guess 'n_ctx'. This model is unknown or unsupported.
Suggestion: provide 'config.json' of the model in the same directory containing model files."""
raise KeyError ( textwrap . dedent ( msg ))
2023-06-22 14:20:47 +02:00
2024-01-20 18:14:18 -05:00
n_experts = None
2023-12-13 13:04:25 +01:00
n_experts_used = None
if "num_local_experts" in config :
n_experts = config [ "num_local_experts" ]
n_experts_used = config [ "num_experts_per_tok" ]
2023-06-22 14:20:47 +02:00
return Params (
2024-01-20 18:14:18 -05:00
n_vocab = config [ "vocab_size" ],
n_embd = config [ "hidden_size" ],
n_layer = config [ "num_hidden_layers" ],
n_ctx = n_ctx ,
n_ff = config [ "intermediate_size" ],
n_head = ( n_head := config [ "num_attention_heads" ]),
n_head_kv = config . get ( "num_key_value_heads" , n_head ),
n_experts = n_experts ,
n_experts_used = n_experts_used ,
f_norm_eps = config [ "rms_norm_eps" ],
f_rope_freq_base = config . get ( "rope_theta" ),
rope_scaling_type = rope_scaling_type ,
f_rope_scale = f_rope_scale ,
n_orig_ctx = n_orig_ctx ,
rope_finetuned = rope_finetuned ,
2023-07-23 15:09:47 +03:00
)
# LLaMA v2 70B params.json
2023-09-10 17:06:53 +02:00
# {"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}
2023-07-23 15:09:47 +03:00
@staticmethod
2024-01-20 18:14:18 -05:00
def loadOriginalParamsJson ( model : LazyModel , config_path : Path ) -> Params :
2024-03-28 11:44:36 -04:00
with open ( config_path ) as f :
config = json . load ( f )
2023-07-23 15:09:47 +03:00
2024-01-20 18:14:18 -05:00
n_experts = None
2023-12-13 13:04:25 +01:00
n_experts_used = None
f_rope_freq_base = None
2023-08-24 21:10:39 +02:00
# hack to determine LLaMA v1 vs v2 vs CodeLlama
2023-12-13 13:04:25 +01:00
if config . get ( "moe" ):
# Mixtral
n_ctx = 32768
elif config . get ( "rope_theta" ) == 1000000 :
2023-08-24 21:10:39 +02:00
# CodeLlama
n_ctx = 16384
elif config [ "norm_eps" ] == 1e-05 :
# LLaMA v2
n_ctx = 4096
else :
# LLaMA v1
n_ctx = 2048
2023-12-13 13:04:25 +01:00
if "layers.0.feed_forward.w1.weight" in model :
n_ff = model [ "layers.0.feed_forward.w1.weight" ] . shape [ 0 ]
if config . get ( "moe" ):
n_ff = model [ "layers.0.feed_forward.experts.0.w1.weight" ] . shape [ 0 ]
2024-01-20 18:14:18 -05:00
n_experts = config [ "moe" ][ "num_experts" ]
2023-12-13 13:04:25 +01:00
n_experts_used = config [ "moe" ][ "num_experts_per_tok" ]
f_rope_freq_base = 1e6
2023-07-23 15:09:47 +03:00
return Params (
2024-01-20 18:14:18 -05:00
n_vocab = model [ "tok_embeddings.weight" ] . shape [ 0 ],
n_embd = config [ "dim" ],
n_layer = config [ "n_layers" ],
n_ctx = n_ctx ,
n_ff = n_ff ,
n_head = ( n_head := config [ "n_heads" ]),
n_head_kv = config . get ( "n_kv_heads" , n_head ),
n_experts = n_experts ,
n_experts_used = n_experts_used ,
f_norm_eps = config [ "norm_eps" ],
f_rope_freq_base = config . get ( "rope_theta" , f_rope_freq_base ),
2023-06-22 14:20:47 +02:00
)
@staticmethod
2024-01-20 18:14:18 -05:00
def load ( model_plus : ModelPlus ) -> Params :
hf_config_path = model_plus . paths [ 0 ] . parent / "config.json"
2023-06-22 14:20:47 +02:00
orig_config_path = model_plus . paths [ 0 ] . parent / "params.json"
2023-07-23 15:09:47 +03:00
if hf_config_path . exists ():
2024-01-20 18:14:18 -05:00
params = Params . loadHFTransformerJson ( model_plus . model , hf_config_path )
2023-07-23 15:09:47 +03:00
elif orig_config_path . exists ():
2024-01-20 18:14:18 -05:00
params = Params . loadOriginalParamsJson ( model_plus . model , orig_config_path )
elif model_plus . format != 'none' :
2023-06-22 14:20:47 +02:00
params = Params . guessed ( model_plus . model )
2023-08-30 02:25:50 -06:00
else :
2024-01-20 18:14:18 -05:00
raise ValueError ( 'Cannot guess params when model format is none' )
2023-06-22 14:20:47 +02:00
2023-08-24 19:26:19 +03:00
params . path_model = model_plus . paths [ 0 ] . parent
2023-06-22 14:20:47 +02:00
return params
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
#
# vocab
#
2024-03-28 11:44:36 -04:00
@runtime_checkable
class BaseVocab ( Protocol ):
tokenizer_model : ClassVar [ str ]
name : ClassVar [ str ]
class NoVocab ( BaseVocab ):
tokenizer_model = "no_vocab"
name = "no_vocab"
def __repr__ ( self ) -> str :
return "<NoVocab for a model without integrated vocabulary>"
@runtime_checkable
class Vocab ( BaseVocab , Protocol ):
vocab_size : int
added_tokens_dict : dict [ str , int ]
added_tokens_list : list [ str ]
fname_tokenizer : Path
def __init__ ( self , base_path : Path ): ...
def all_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]: ...
class BpeVocab ( Vocab ):
2024-03-14 17:21:56 +01:00
tokenizer_model = "gpt2"
name = "bpe"
2024-03-28 11:44:36 -04:00
def __init__ ( self , base_path : Path ):
added_tokens : dict [ str , int ] = {}
2023-08-21 23:07:43 +03:00
2024-03-28 11:44:36 -04:00
if ( fname_tokenizer := base_path / 'vocab.json' ) . exists ():
# "slow" tokenizer
with open ( fname_tokenizer , encoding = "utf-8" ) as f :
self . vocab = json . load ( f )
try :
# FIXME: Verify that added tokens here _cannot_ overlap with the main vocab.
with open ( base_path / ADDED_TOKENS_FILE , encoding = "utf-8" ) as f :
added_tokens = json . load ( f )
except FileNotFoundError :
pass
else :
# "fast" tokenizer
fname_tokenizer = base_path / FAST_TOKENIZER_FILE
# if this fails, FileNotFoundError propagates to caller
with open ( fname_tokenizer , encoding = "utf-8" ) as f :
tokenizer_json = json . load ( f )
tokenizer_model : dict [ str , Any ] = tokenizer_json [ 'model' ]
if (
tokenizer_model [ 'type' ] != 'BPE' or tokenizer_model . get ( 'byte_fallback' , False )
or tokenizer_json [ 'decoder' ][ 'type' ] != 'ByteLevel'
):
raise FileNotFoundError ( 'Cannot find GPT-2 BPE tokenizer' )
self . vocab = tokenizer_model [ "vocab" ]
if ( added := tokenizer_json . get ( 'added_tokens' )) is not None :
# Added tokens here can be duplicates of the main vocabulary.
added_tokens = { item [ 'content' ]: item [ 'id' ]
for item in added
if item [ 'content' ] not in self . vocab }
vocab_size = len ( self . vocab )
expected_ids = list ( range ( vocab_size , vocab_size + len ( added_tokens )))
actual_ids = sorted ( added_tokens . values ())
2024-01-09 13:46:46 -05:00
if expected_ids != actual_ids :
expected_end_id = vocab_size + len ( actual_ids ) - 1
2024-03-28 11:44:36 -04:00
raise ValueError ( f "Expected the { len ( actual_ids ) } added token ID(s) to be sequential in the range "
f " { vocab_size } - { expected_end_id } ; got { actual_ids } " )
2023-12-14 17:09:34 +09:00
2024-01-09 13:46:46 -05:00
items = sorted ( added_tokens . items (), key = lambda text_idx : text_idx [ 1 ])
2024-01-20 18:14:18 -05:00
self . added_tokens_dict = added_tokens
self . added_tokens_list = [ text for ( text , idx ) in items ]
2024-03-28 11:44:36 -04:00
self . vocab_size_base = vocab_size
self . vocab_size = self . vocab_size_base + len ( self . added_tokens_list )
2024-01-20 18:14:18 -05:00
self . fname_tokenizer = fname_tokenizer
2023-12-14 17:09:34 +09:00
2024-01-09 13:46:46 -05:00
def bpe_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
2024-01-17 15:45:03 +02:00
reverse_vocab = { id : encoded_tok for encoded_tok , id in self . vocab . items ()}
2023-12-14 17:09:34 +09:00
2024-01-17 15:45:03 +02:00
for i , _ in enumerate ( self . vocab ):
2024-01-09 13:46:46 -05:00
yield reverse_vocab [ i ], 0.0 , gguf . TokenType . NORMAL
2023-12-14 17:09:34 +09:00
2024-01-09 13:46:46 -05:00
def added_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
for text in self . added_tokens_list :
score = - 1000.0
yield text . encode ( "utf-8" ), score , gguf . TokenType . CONTROL
def all_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
yield from self . bpe_tokens ()
yield from self . added_tokens ()
def __repr__ ( self ) -> str :
return f "<BpeVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>"
2024-03-28 11:44:36 -04:00
class SentencePieceVocab ( Vocab ):
2024-03-14 17:21:56 +01:00
tokenizer_model = "llama"
name = "spm"
2024-03-28 11:44:36 -04:00
def __init__ ( self , base_path : Path ):
added_tokens : dict [ str , int ] = {}
if ( fname_tokenizer := base_path / 'tokenizer.model' ) . exists ():
# normal location
try :
with open ( base_path / ADDED_TOKENS_FILE , encoding = "utf-8" ) as f :
added_tokens = json . load ( f )
except FileNotFoundError :
pass
elif not ( fname_tokenizer := base_path . parent / 'tokenizer.model' ) . exists ():
# not found in alternate location either
raise FileNotFoundError ( 'Cannot find tokenizer.model' )
2024-01-09 13:46:46 -05:00
2024-03-28 11:44:36 -04:00
self . sentencepiece_tokenizer = SentencePieceProcessor ( str ( fname_tokenizer ))
vocab_size = self . sentencepiece_tokenizer . vocab_size ()
2024-01-09 13:46:46 -05:00
2024-01-20 18:14:18 -05:00
new_tokens = { id : piece for piece , id in added_tokens . items () if id >= vocab_size }
2024-01-09 13:46:46 -05:00
expected_new_ids = list ( range ( vocab_size , vocab_size + len ( new_tokens )))
2024-01-20 18:14:18 -05:00
actual_new_ids = sorted ( new_tokens . keys ())
2024-01-09 13:46:46 -05:00
if expected_new_ids != actual_new_ids :
2024-01-20 18:14:18 -05:00
raise ValueError ( f "Expected new token IDs { expected_new_ids } to be sequential; got { actual_new_ids } " )
2024-01-09 13:46:46 -05:00
# Token pieces that were added to the base vocabulary.
2024-03-28 11:44:36 -04:00
self . added_tokens_dict = added_tokens
2024-01-20 18:14:18 -05:00
self . added_tokens_list = [ new_tokens [ id ] for id in actual_new_ids ]
self . vocab_size_base = vocab_size
self . vocab_size = self . vocab_size_base + len ( self . added_tokens_list )
self . fname_tokenizer = fname_tokenizer
2024-01-09 13:46:46 -05:00
def sentencepiece_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
tokenizer = self . sentencepiece_tokenizer
for i in range ( tokenizer . vocab_size ()):
piece = tokenizer . id_to_piece ( i )
2024-03-28 11:44:36 -04:00
text = piece . encode ( "utf-8" )
2024-01-09 13:46:46 -05:00
score : float = tokenizer . get_score ( i )
toktype = gguf . TokenType . NORMAL
if tokenizer . is_unknown ( i ):
toktype = gguf . TokenType . UNKNOWN
if tokenizer . is_control ( i ):
toktype = gguf . TokenType . CONTROL
# NOTE: I think added_tokens are user defined.
# ref: https://github.com/google/sentencepiece/blob/master/src/sentencepiece_model.proto
# if tokenizer.is_user_defined(i): toktype = gguf.TokenType.USER_DEFINED
if tokenizer . is_unused ( i ):
toktype = gguf . TokenType . UNUSED
if tokenizer . is_byte ( i ):
toktype = gguf . TokenType . BYTE
yield text , score , toktype
def added_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
for text in self . added_tokens_list :
score = - 1000.0
yield text . encode ( "utf-8" ), score , gguf . TokenType . USER_DEFINED
def all_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
yield from self . sentencepiece_tokens ()
yield from self . added_tokens ()
def __repr__ ( self ) -> str :
return f "<SentencePieceVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>"
2024-03-28 11:44:36 -04:00
class LlamaHfVocab ( Vocab ):
2024-03-14 17:21:56 +01:00
tokenizer_model = "llama"
name = "hfft"
2024-04-09 13:44:08 -04:00
def __init__ ( self , base_path : Path ):
2024-03-28 11:44:36 -04:00
fname_tokenizer = base_path / FAST_TOKENIZER_FILE
# if this fails, FileNotFoundError propagates to caller
with open ( fname_tokenizer , encoding = 'utf-8' ) as f :
tokenizer_json = json . load ( f )
# pre-check so we know if we need transformers
tokenizer_model : dict [ str , Any ] = tokenizer_json [ 'model' ]
2024-04-21 13:50:41 +02:00
is_llama3 = (
tokenizer_model [ 'type' ] == 'BPE' and tokenizer_model . get ( 'ignore_merges' , False )
and not tokenizer_model . get ( 'byte_fallback' , True )
)
if is_llama3 :
raise TypeError ( 'Llama 3 must be converted with BpeVocab' )
if not is_llama3 and (
2024-03-28 11:44:36 -04:00
tokenizer_model [ 'type' ] != 'BPE' or not tokenizer_model . get ( 'byte_fallback' , False )
or tokenizer_json [ 'decoder' ][ 'type' ] != 'Sequence'
):
raise FileNotFoundError ( 'Cannot find Llama BPE tokenizer' )
2024-01-20 18:14:18 -05:00
try :
from transformers import AutoTokenizer
except ImportError as e :
raise ImportError (
2024-03-28 11:44:36 -04:00
"To use LlamaHfVocab, please install the `transformers` package. "
2024-01-20 18:14:18 -05:00
"You can install it with `pip install transformers`."
) from e
2024-01-09 13:46:46 -05:00
# Allow the tokenizer to default to slow or fast versions.
# Explicitly set tokenizer to use local paths.
self . tokenizer = AutoTokenizer . from_pretrained (
2024-03-28 11:44:36 -04:00
base_path ,
cache_dir = base_path ,
2024-01-09 13:46:46 -05:00
local_files_only = True ,
)
2024-03-28 11:44:36 -04:00
assert self . tokenizer . is_fast # assume tokenizer.json is used
2024-01-09 13:46:46 -05:00
# Initialize lists and dictionaries for added tokens
self . added_tokens_list = []
self . added_tokens_dict = dict ()
2024-01-20 18:14:18 -05:00
self . added_tokens_ids = set ()
2024-01-09 13:46:46 -05:00
# Process added tokens
for tok , tokidx in sorted (
self . tokenizer . get_added_vocab () . items (), key = lambda x : x [ 1 ]
):
# Only consider added tokens that are not in the base vocabulary
if tokidx >= self . tokenizer . vocab_size :
self . added_tokens_list . append ( tok )
self . added_tokens_dict [ tok ] = tokidx
self . added_tokens_ids . add ( tokidx )
# Store special tokens and their IDs
self . specials = {
2023-12-14 17:09:34 +09:00
tok : self . tokenizer . get_vocab ()[ tok ]
for tok in self . tokenizer . all_special_tokens
}
2024-01-09 13:46:46 -05:00
self . special_ids = set ( self . tokenizer . all_special_ids )
2023-12-14 17:09:34 +09:00
2024-01-09 13:46:46 -05:00
# Set vocabulary sizes
self . vocab_size_base = self . tokenizer . vocab_size
2024-01-20 18:14:18 -05:00
self . vocab_size = self . vocab_size_base + len ( self . added_tokens_list )
2023-08-21 23:07:43 +03:00
2024-03-28 11:44:36 -04:00
self . fname_tokenizer = fname_tokenizer
2023-08-21 23:07:43 +03:00
2024-01-20 18:14:18 -05:00
def hf_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
2024-01-09 13:46:46 -05:00
reverse_vocab = {
id : encoded_tok for encoded_tok , id in self . tokenizer . get_vocab () . items ()
}
for token_id in range ( self . vocab_size_base ):
# Skip processing added tokens here
if token_id in self . added_tokens_ids :
2023-12-14 17:09:34 +09:00
continue
2023-08-21 23:07:43 +03:00
2024-01-09 13:46:46 -05:00
# Convert token text to bytes
token_text = reverse_vocab [ token_id ] . encode ( "utf-8" )
2023-10-03 09:16:26 +02:00
2024-01-09 13:46:46 -05:00
# Yield token text, score, and type
yield token_text , self . get_token_score ( token_id ), self . get_token_type (
2024-02-06 07:47:22 +02:00
token_id , token_text , self . special_ids # Reuse already stored special IDs
2024-01-09 13:46:46 -05:00
)
2023-08-21 23:07:43 +03:00
2024-02-06 07:47:22 +02:00
def get_token_type ( self , token_id : int , token_text : bytes , special_ids : set [ int ]) -> gguf . TokenType :
# Special case for byte tokens
if re . fullmatch ( br "<0x[0-9A-Fa-f] {2} >" , token_text ):
return gguf . TokenType . BYTE
2024-01-09 13:46:46 -05:00
# Determine token type based on whether it's a special token
2024-01-20 18:14:18 -05:00
return gguf . TokenType . CONTROL if token_id in special_ids else gguf . TokenType . NORMAL
2023-08-21 23:07:43 +03:00
2023-12-14 17:09:34 +09:00
def get_token_score ( self , token_id : int ) -> float :
2024-01-09 13:46:46 -05:00
# Placeholder for actual logic to determine the token's score
# This needs to be implemented based on specific requirements
return - 1000.0 # Default score
2023-08-21 23:07:43 +03:00
2023-08-31 01:02:23 -04:00
def added_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
2024-01-09 13:46:46 -05:00
for text in self . added_tokens_list :
2023-12-14 17:09:34 +09:00
if text in self . specials :
2024-02-06 07:47:22 +02:00
toktype = self . get_token_type ( self . specials [ text ], b '' , self . special_ids )
2023-12-14 17:09:34 +09:00
score = self . get_token_score ( self . specials [ text ])
else :
toktype = gguf . TokenType . USER_DEFINED
score = - 1000.0
yield text . encode ( "utf-8" ), score , toktype
2024-01-09 13:46:46 -05:00
def has_newline_token ( self ):
return "<0x0A>" in self . tokenizer . vocab or " \n " in self . tokenizer . vocab
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
def all_tokens ( self ) -> Iterable [ tuple [ bytes , float , gguf . TokenType ]]:
2023-12-14 17:09:34 +09:00
yield from self . hf_tokens ()
2023-04-14 00:03:03 -07:00
yield from self . added_tokens ()
def __repr__ ( self ) -> str :
2024-03-28 11:44:36 -04:00
return f "<LlamaHfVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>"
2023-12-14 17:09:34 +09:00
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
#
# data loading
# TODO: reuse (probably move to gguf.py?)
#
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-21 23:07:43 +03:00
def permute ( weights : NDArray , n_head : int , n_head_kv : int ) -> NDArray :
2023-11-20 11:35:47 +01:00
# print( "permute debug " + str(weights.shape[0]) + " x " + str(weights.shape[1]) + " nhead " + str(n_head) + " nheadkv " + str(n_kv_head) )
2023-08-21 23:07:43 +03:00
if n_head_kv is not None and n_head != n_head_kv :
2023-09-28 02:45:20 +08:00
n_head = n_head_kv
2023-04-14 00:03:03 -07:00
return ( weights . reshape ( n_head , 2 , weights . shape [ 0 ] // n_head // 2 , * weights . shape [ 1 :])
2023-11-20 11:35:47 +01:00
. swapaxes ( 1 , 2 )
. reshape ( weights . shape ))
2023-04-14 00:03:03 -07:00
2024-03-28 11:44:36 -04:00
class Tensor ( ABC ):
2024-04-09 13:44:08 -04:00
ndarray : NDArray
2023-04-14 00:03:03 -07:00
data_type : DataType
@abstractmethod
2024-04-09 13:44:08 -04:00
def astype ( self , data_type : DataType ) -> Self : ...
2023-04-14 00:03:03 -07:00
@abstractmethod
2024-04-09 13:44:08 -04:00
def permute ( self , n_head : int , n_head_kv : int ) -> Self : ...
2023-04-14 00:03:03 -07:00
@abstractmethod
2024-04-09 13:44:08 -04:00
def permute_part ( self , n_part : int , n_head : int , n_head_kv : int ) -> Self : ...
2023-07-02 01:00:25 +08:00
@abstractmethod
2024-04-09 13:44:08 -04:00
def part ( self , n_part : int ) -> Self : ...
2023-07-02 01:00:25 +08:00
@abstractmethod
2023-08-31 01:02:23 -04:00
def to_ggml ( self ) -> GGMLCompatibleTensor : ...
2023-04-14 00:03:03 -07:00
2023-08-30 02:25:50 -06:00
def bf16_to_fp32 ( bf16_arr : np . ndarray [ Any , np . dtype [ np . uint16 ]]) -> NDArray :
2023-05-04 19:54:37 +03:00
assert bf16_arr . dtype == np . uint16 , f "Input array should be of dtype uint16, but got { bf16_arr . dtype } "
fp32_arr = bf16_arr . astype ( np . uint32 ) << 16
return fp32_arr . view ( np . float32 )
2023-04-14 00:03:03 -07:00
class UnquantizedTensor ( Tensor ):
2024-03-28 11:44:36 -04:00
def __init__ ( self , ndarray : NDArray ):
2023-04-14 00:03:03 -07:00
assert isinstance ( ndarray , np . ndarray )
self . ndarray = ndarray
self . data_type = NUMPY_TYPE_TO_DATA_TYPE [ ndarray . dtype ]
2024-04-09 13:44:08 -04:00
def astype ( self , data_type : DataType ) -> UnquantizedTensor :
2023-08-26 14:13:36 -06:00
dtype = data_type . dtype
2023-05-04 19:54:37 +03:00
if self . data_type == DT_BF16 :
self . ndarray = bf16_to_fp32 ( self . ndarray )
2023-04-14 00:03:03 -07:00
return UnquantizedTensor ( self . ndarray . astype ( dtype ))
2024-04-09 13:44:08 -04:00
def to_ggml ( self ) -> Self :
2023-04-14 00:03:03 -07:00
return self
2023-08-31 01:02:23 -04:00
def permute_part ( self , n_part : int , n_head : int , n_head_kv : int ) -> UnquantizedTensor :
2023-07-02 01:00:25 +08:00
r = self . ndarray . shape [ 0 ] // 3
2023-08-30 02:25:50 -06:00
return UnquantizedTensor ( permute ( self . ndarray [ r * n_part : r * n_part + r , ... ], n_head , n_head_kv ))
2023-07-02 01:00:25 +08:00
2023-08-31 01:02:23 -04:00
def part ( self , n_part : int ) -> UnquantizedTensor :
2023-07-02 01:00:25 +08:00
r = self . ndarray . shape [ 0 ] // 3
return UnquantizedTensor ( self . ndarray [ r * n_part : r * n_part + r , ... ])
2023-08-31 01:02:23 -04:00
def permute ( self , n_head : int , n_head_kv : int ) -> UnquantizedTensor :
2023-08-21 23:07:43 +03:00
return UnquantizedTensor ( permute ( self . ndarray , n_head , n_head_kv ))
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
def load_unquantized ( lazy_tensor : LazyTensor , expected_dtype : Any = None , convert : bool = False ) -> NDArray :
2023-04-14 00:03:03 -07:00
tensor = lazy_tensor . load ()
assert isinstance ( tensor , UnquantizedTensor )
# double-check:
actual_shape = list ( tensor . ndarray . shape )
assert actual_shape == lazy_tensor . shape , ( actual_shape , lazy_tensor . shape )
if expected_dtype is not None and expected_dtype != tensor . ndarray . dtype :
if convert :
tensor . ndarray = tensor . ndarray . astype ( expected_dtype )
else :
raise ValueError ( f 'expected this tensor to have dtype { expected_dtype } , got { tensor . ndarray . dtype } ' )
return tensor . ndarray
2023-08-31 01:02:23 -04:00
GGMLCompatibleTensor = UnquantizedTensor
2023-04-14 00:03:03 -07:00
@dataclass
class LazyTensor :
_load : Callable [[], Tensor ]
2023-08-31 01:02:23 -04:00
shape : list [ int ]
2023-04-14 00:03:03 -07:00
data_type : DataType
description : str
def load ( self ) -> Tensor :
ret = self . _load ()
2023-08-26 14:13:36 -06:00
# Should be okay if it maps to the same numpy type?
assert ret . data_type == self . data_type or ( self . data_type . dtype == ret . data_type . dtype ), \
2023-11-20 11:35:47 +01:00
( self . data_type , ret . data_type , self . description )
2023-04-14 00:03:03 -07:00
return ret
2023-08-31 01:02:23 -04:00
def astype ( self , data_type : DataType ) -> LazyTensor :
2023-04-14 00:03:03 -07:00
self . validate_conversion_to ( data_type )
def load () -> Tensor :
return self . load () . astype ( data_type )
return LazyTensor ( load , self . shape , data_type , f 'convert( { data_type } ) { self . description } ' )
def validate_conversion_to ( self , data_type : DataType ) -> None :
2023-08-26 14:13:36 -06:00
if data_type != self . data_type and data_type . name not in self . data_type . valid_conversions :
raise ValueError ( f 'Cannot validate conversion from { self . data_type } to { data_type } .' )
2023-04-14 00:03:03 -07:00
2023-08-31 22:13:51 -04:00
LazyModel : TypeAlias = 'dict[str, LazyTensor]'
2023-04-14 00:03:03 -07:00
@dataclass
class ModelPlus :
model : LazyModel
2023-08-31 01:02:23 -04:00
paths : list [ Path ] # Where this was read from.
2023-08-30 02:25:50 -06:00
format : Literal [ 'ggml' , 'torch' , 'safetensors' , 'none' ]
2024-03-28 11:44:36 -04:00
vocab : BaseVocab | None # For GGML models (which have vocab built in), the vocab.
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
def merge_sharded ( models : list [ LazyModel ]) -> LazyModel :
2023-04-14 00:03:03 -07:00
# Original LLaMA models have each file contain one part of each tensor.
# Use a dict instead of a set to preserve order.
names = { name : None for model in models for name in model }
def convert ( name : str ) -> LazyTensor :
2024-03-28 11:44:36 -04:00
lazy_tensors = [ model [ name ] for model in models ]
2023-04-14 00:03:03 -07:00
if len ( lazy_tensors ) == 1 :
# only one file; don't go through this procedure since there might
# be quantized tensors
return lazy_tensors [ 0 ]
if len ( lazy_tensors [ 0 ] . shape ) == 1 :
# the tensor is just duplicated in every file
return lazy_tensors [ 0 ]
if name . startswith ( 'tok_embeddings.' ) or \
name . endswith ( '.attention.wo.weight' ) or \
name . endswith ( '.feed_forward.w2.weight' ):
# split by columns
axis = 1
else :
# split by rows
axis = 0
concatenated_shape = list ( lazy_tensors [ 0 ] . shape )
concatenated_shape [ axis ] = sum ( tensor . shape [ axis ] for tensor in lazy_tensors )
def load () -> UnquantizedTensor :
ndarrays = [ load_unquantized ( tensor ) for tensor in lazy_tensors ]
2024-03-28 11:44:36 -04:00
concatenated = np . concatenate ( ndarrays , axis = axis )
2023-04-14 00:03:03 -07:00
return UnquantizedTensor ( concatenated )
description = 'concatenated[[' + '] | [' . join ( lt . description for lt in lazy_tensors ) + ']]'
return LazyTensor ( load , concatenated_shape , lazy_tensors [ 0 ] . data_type , description )
return { name : convert ( name ) for name in names }
2023-08-31 01:02:23 -04:00
def merge_multifile_models ( models_plus : list [ ModelPlus ]) -> ModelPlus :
2023-04-14 00:03:03 -07:00
formats = set ( mp . format for mp in models_plus )
assert len ( formats ) == 1 , "different formats?"
format = formats . pop ()
paths = [ path for mp in models_plus for path in mp . paths ]
# Use the first non-None vocab, if any.
try :
vocab = next ( mp . vocab for mp in models_plus if mp . vocab is not None )
except StopIteration :
vocab = None
if any ( "model.embed_tokens.weight" in mp . model for mp in models_plus ):
# Transformers models put different tensors in different files, but
2023-12-12 01:53:36 -08:00
# don't split individual tensors between files.
2023-04-14 00:03:03 -07:00
model : LazyModel = {}
for mp in models_plus :
model . update ( mp . model )
else :
model = merge_sharded ([ mp . model for mp in models_plus ])
2024-01-20 18:14:18 -05:00
return ModelPlus ( model , paths , format , vocab ) # pytype: disable=wrong-arg-types
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
def permute_lazy ( lazy_tensor : LazyTensor , n_head : int , n_head_kv : int ) -> LazyTensor :
2023-04-14 00:03:03 -07:00
def load () -> Tensor :
2023-08-21 23:07:43 +03:00
return lazy_tensor . load () . permute ( n_head , n_head_kv )
return LazyTensor ( load , lazy_tensor . shape , lazy_tensor . data_type , f 'permute( { n_head } , { n_head_kv } ) ' + lazy_tensor . description )
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-30 02:25:50 -06:00
def permute_part_lazy ( lazy_tensor : LazyTensor , n_part : int , n_head : int , n_head_kv : int ) -> LazyTensor :
2023-07-02 01:00:25 +08:00
def load () -> Tensor :
2023-08-30 02:25:50 -06:00
return lazy_tensor . load () . permute_part ( n_part , n_head , n_head_kv )
2023-07-02 01:00:25 +08:00
s = lazy_tensor . shape . copy ()
s [ 0 ] = s [ 0 ] // 3
2023-08-30 02:25:50 -06:00
return LazyTensor ( load , s , lazy_tensor . data_type , f 'permute( { n_head } , { n_head_kv } ) ' + lazy_tensor . description )
2023-07-02 01:00:25 +08:00
2023-11-20 11:35:47 +01:00
2023-07-02 01:00:25 +08:00
def part_lazy ( lazy_tensor : LazyTensor , n_part : int ) -> LazyTensor :
def load () -> Tensor :
return lazy_tensor . load () . part ( n_part )
s = lazy_tensor . shape . copy ()
s [ 0 ] = s [ 0 ] // 3
return LazyTensor ( load , s , lazy_tensor . data_type , 'part ' + lazy_tensor . description )
2023-04-14 00:03:03 -07:00
2024-04-03 15:07:05 +02:00
def pack_experts_lazy ( lazy_tensors : list [ LazyTensor ]) -> LazyTensor :
def load () -> Tensor :
tensors = [ lazy_tensor . load () for lazy_tensor in lazy_tensors ]
return UnquantizedTensor ( np . array ([ tensor . ndarray for tensor in tensors ]))
s = lazy_tensors [ 0 ] . shape . copy ()
s . insert ( 0 , len ( lazy_tensors ))
return LazyTensor ( load , s , lazy_tensors [ 0 ] . data_type , 'pack_experts ' + ' | ' . join ( lt . description for lt in lazy_tensors ))
2023-04-14 00:03:03 -07:00
# Functionality that simulates `torch.load` but where individual tensors are
# only loaded into memory on demand, not all at once.
# PyTorch can't do this natively as of time of writing:
# - https://github.com/pytorch/pytorch/issues/64327
# This allows us to de-shard without multiplying RAM usage, and also
# conveniently drops the PyTorch dependency (though we still need numpy).
@dataclass
class LazyStorageKind :
data_type : DataType
@dataclass
class LazyStorage :
load : Callable [[ int , int ], NDArray ]
kind : LazyStorageKind
description : str
class LazyUnpickler ( pickle . Unpickler ):
def __init__ ( self , fp : IO [ bytes ], data_base_path : str , zip_file : zipfile . ZipFile ):
super () . __init__ ( fp )
self . data_base_path = data_base_path
self . zip_file = zip_file
def persistent_load ( self , pid : Any ) -> Any :
assert pid [ 0 ] == 'storage'
assert isinstance ( pid [ 1 ], LazyStorageKind )
data_type = pid [ 1 ] . data_type
filename_stem = pid [ 2 ]
2023-09-05 19:41:00 +02:00
filename = f ' { self . data_base_path } / { filename_stem } '
2023-04-14 00:03:03 -07:00
info = self . zip_file . getinfo ( filename )
def load ( offset : int , elm_count : int ) -> NDArray :
2023-08-26 14:13:36 -06:00
dtype = data_type . dtype
2024-03-28 11:44:36 -04:00
with self . zip_file . open ( info ) as fp :
fp . seek ( offset * dtype . itemsize )
size = elm_count * dtype . itemsize
data = fp . read ( size )
2023-04-14 00:03:03 -07:00
assert len ( data ) == size
return np . frombuffer ( data , dtype )
description = f 'storage data_type= { data_type } path-in-zip= { filename } path= { self . zip_file . filename } '
return LazyStorage ( load = load , kind = pid [ 1 ], description = description )
2023-08-30 02:25:50 -06:00
@staticmethod
2023-06-17 12:32:48 +02:00
def lazy_rebuild_tensor_v2 ( storage : Any , storage_offset : Any , size : Any , stride : Any ,
2023-04-14 00:03:03 -07:00
requires_grad : Any , backward_hooks : Any , metadata : Any = None ) -> LazyTensor :
assert isinstance ( storage , LazyStorage )
def load () -> UnquantizedTensor :
elm_count = stride [ 0 ] * size [ 0 ]
return UnquantizedTensor ( storage . load ( storage_offset , elm_count ) . reshape ( size ))
description = f 'pickled storage_offset= { storage_offset } in { storage . description } '
return LazyTensor ( load , list ( size ), storage . kind . data_type , description )
2023-08-30 02:25:50 -06:00
@staticmethod
2023-05-04 19:54:37 +03:00
def rebuild_from_type_v2 ( func , new_type , args , state ):
return func ( * args )
2024-03-28 11:44:36 -04:00
CLASSES = {
2023-12-12 01:53:36 -08:00
# getattr used here as a workaround for mypy not being smart enough to determine
2023-08-30 02:25:50 -06:00
# the staticmethods have a __func__ attribute.
2024-01-20 18:14:18 -05:00
( 'torch._tensor' , '_rebuild_from_type_v2' ): getattr ( rebuild_from_type_v2 , '__func__' ),
( 'torch._utils' , '_rebuild_tensor_v2' ): getattr ( lazy_rebuild_tensor_v2 , '__func__' ),
( 'torch' , 'BFloat16Storage' ): LazyStorageKind ( DT_BF16 ),
( 'torch' , 'HalfStorage' ): LazyStorageKind ( DT_F16 ),
( 'torch' , 'FloatStorage' ): LazyStorageKind ( DT_F32 ),
( 'torch' , 'IntStorage' ): LazyStorageKind ( DT_I32 ),
( 'torch' , 'Tensor' ): LazyTensor ,
2023-04-14 00:03:03 -07:00
}
def find_class ( self , module : str , name : str ) -> Any :
if not module . startswith ( 'torch' ):
return super () . find_class ( module , name )
return self . CLASSES [( module , name )]
def lazy_load_torch_file ( outer_fp : IO [ bytes ], path : Path ) -> ModelPlus :
zf = zipfile . ZipFile ( outer_fp )
pickle_paths = [ name for name in zf . namelist () if name . endswith ( '.pkl' )]
assert len ( pickle_paths ) == 1 , pickle_paths
pickle_fp = zf . open ( pickle_paths [ 0 ], 'r' )
unpickler = LazyUnpickler ( pickle_fp ,
data_base_path = pickle_paths [ 0 ][: - 4 ],
zip_file = zf )
model = unpickler . load ()
2023-11-17 07:32:34 -08:00
if 'model' in model : model = model [ 'model' ]
2023-04-14 00:03:03 -07:00
as_dict = dict ( model . items ())
return ModelPlus ( model = as_dict , paths = [ path ], format = 'torch' , vocab = None )
def lazy_load_safetensors_file ( fp : IO [ bytes ], path : Path ) -> ModelPlus :
header_size , = struct . unpack ( '<Q' , fp . read ( 8 ))
2023-08-31 01:02:23 -04:00
header : dict [ str , dict [ str , Any ]] = json . loads ( fp . read ( header_size ))
2023-04-14 00:03:03 -07:00
# Use mmap for the actual data to avoid race conditions with the file offset.
mapped = memoryview ( mmap . mmap ( fp . fileno (), 0 , access = mmap . ACCESS_READ ))
2023-04-15 14:53:21 -07:00
byte_buf = mapped [ 8 + header_size :]
2023-04-14 00:03:03 -07:00
2023-08-31 01:02:23 -04:00
def convert ( info : dict [ str , Any ]) -> LazyTensor :
2023-04-14 00:03:03 -07:00
data_type = SAFETENSORS_DATA_TYPES [ info [ 'dtype' ]]
2023-08-26 14:13:36 -06:00
numpy_dtype = data_type . dtype
2023-08-31 01:02:23 -04:00
shape : list [ int ] = info [ 'shape' ]
2023-04-14 00:03:03 -07:00
begin , end = info [ 'data_offsets' ]
assert 0 <= begin <= end <= len ( byte_buf )
assert end - begin == math . prod ( shape ) * numpy_dtype . itemsize
buf = byte_buf [ begin : end ]
def load () -> UnquantizedTensor :
return UnquantizedTensor ( np . frombuffer ( buf , dtype = numpy_dtype ) . reshape ( shape ))
description = f 'safetensors begin= { begin } end= { end } type= { data_type } path= { path } '
return LazyTensor ( load , shape , data_type , description )
2023-05-08 04:54:26 -07:00
model = { name : convert ( info ) for ( name , info ) in header . items () if name != '__metadata__' }
2023-04-14 00:03:03 -07:00
return ModelPlus ( model = model , paths = [ path ], format = 'safetensors' , vocab = None )
def must_read ( fp : IO [ bytes ], length : int ) -> bytes :
ret = fp . read ( length )
if len ( ret ) < length :
2024-03-28 11:44:36 -04:00
raise EOFError ( "unexpectedly reached end of file" )
2023-04-14 00:03:03 -07:00
return ret
@functools.lru_cache ( maxsize = None )
def lazy_load_file ( path : Path ) -> ModelPlus :
fp = open ( path , 'rb' )
first8 = fp . read ( 8 )
fp . seek ( 0 )
if first8 [: 2 ] == b 'PK' :
# A zip file, i.e. PyTorch format
return lazy_load_torch_file ( fp , path )
elif struct . unpack ( '<Q' , first8 )[ 0 ] < 16 * 1024 * 1024 :
# Probably safetensors
return lazy_load_safetensors_file ( fp , path )
else :
raise ValueError ( f "unknown format: { path } " )
In = TypeVar ( 'In' )
Out = TypeVar ( 'Out' )
2023-11-20 11:35:47 +01:00
2023-08-31 01:02:23 -04:00
def bounded_parallel_map ( func : Callable [[ In ], Out ], iterable : Iterable [ In ], concurrency : int , max_workers : int | None = None , use_processpool_executor : bool = False ) -> Iterable [ Out ]:
2023-04-14 00:03:03 -07:00
'''Parallel map, but with backpressure. If the caller doesn't call `next`
fast enough, this will stop calling `func` at some point rather than
letting results pile up in memory. Specifically, there is a max of one
output value buffered per thread.'''
2023-08-26 14:13:36 -06:00
if concurrency < 2 :
yield from map ( func , iterable )
# Not reached.
iterable = iter ( iterable )
2023-08-31 01:02:23 -04:00
executor_class : type [ ThreadPoolExecutor ] | type [ ProcessPoolExecutor ]
2023-08-30 02:25:50 -06:00
if use_processpool_executor :
executor_class = ProcessPoolExecutor
else :
executor_class = ThreadPoolExecutor
2024-01-20 18:14:18 -05:00
with executor_class ( max_workers = max_workers ) as executor :
2023-08-31 01:02:23 -04:00
futures : list [ concurrent . futures . Future [ Out ]] = []
2023-08-26 14:13:36 -06:00
done = False
for _ in range ( concurrency ):
try :
futures . append ( executor . submit ( func , next ( iterable )))
except StopIteration :
done = True
break
2023-04-14 00:03:03 -07:00
while futures :
result = futures . pop ( 0 ) . result ()
2023-08-26 14:13:36 -06:00
while not done and len ( futures ) < concurrency :
try :
futures . append ( executor . submit ( func , next ( iterable )))
except StopIteration :
done = True
break
2023-04-14 00:03:03 -07:00
yield result
2023-11-20 11:35:47 +01:00
2024-03-28 11:44:36 -04:00
def check_vocab_size ( params : Params , vocab : BaseVocab , pad_vocab : bool = False ) -> None :
2024-01-09 13:46:46 -05:00
# Handle special case where the model's vocab size is not set
if params . n_vocab == - 1 :
raise ValueError (
2024-03-28 11:44:36 -04:00
"The model's vocab size is set to -1 in params.json. Please update it manually."
+ ( f " Maybe { vocab . vocab_size } ?" if isinstance ( vocab , Vocab ) else "" ),
2024-01-09 13:46:46 -05:00
)
2024-03-28 11:44:36 -04:00
if not isinstance ( vocab , Vocab ):
2024-03-14 17:21:56 +01:00
return # model has no vocab
2023-12-14 17:09:34 +09:00
2024-01-09 13:46:46 -05:00
# Check for a vocab size mismatch
if params . n_vocab == vocab . vocab_size :
print ( "Ignoring added_tokens.json since model matches vocab size without it." )
return
if pad_vocab and params . n_vocab > vocab . vocab_size :
pad_count = params . n_vocab - vocab . vocab_size
print (
f "Padding vocab with { pad_count } token(s) - <dummy00001> through <dummy { pad_count : 05 } >"
)
for i in range ( 1 , pad_count + 1 ):
vocab . added_tokens_dict [ f "<dummy { i : 05 } >" ] = - 1
2024-01-17 15:45:03 +02:00
vocab . added_tokens_list . append ( f "<dummy { i : 05 } >" )
2024-01-09 13:46:46 -05:00
vocab . vocab_size = params . n_vocab
return
msg = f "Vocab size mismatch (model has { params . n_vocab } , but { vocab . fname_tokenizer } has { vocab . vocab_size } )."
if vocab . vocab_size < params . n_vocab < vocab . vocab_size + 20 :
msg += f " Most likely you are missing added_tokens.json (should be in { vocab . fname_tokenizer . parent } )."
if vocab . vocab_size < params . n_vocab :
msg += " Add the --pad-vocab option and try again."
2024-03-28 11:44:36 -04:00
raise ValueError ( msg )
2023-04-14 00:03:03 -07:00
class OutputFile :
2024-03-28 11:44:36 -04:00
def __init__ ( self , fname_out : Path , endianess : gguf . GGUFEndian = gguf . GGUFEndian . LITTLE ):
2024-01-20 18:14:18 -05:00
self . gguf = gguf . GGUFWriter ( fname_out , gguf . MODEL_ARCH_NAMES [ ARCH ], endianess = endianess )
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
def add_meta_arch ( self , params : Params ) -> None :
2023-08-24 19:26:19 +03:00
name = "LLaMA"
2023-08-30 13:29:40 +03:00
# TODO: better logic to determine model name
2023-09-07 14:27:42 -04:00
if params . n_ctx == 4096 :
2023-08-24 19:26:19 +03:00
name = "LLaMA v2"
2023-09-07 14:27:42 -04:00
elif params . path_model is not None :
2024-01-20 18:14:18 -05:00
name = str ( params . path_model . parent ) . split ( '/' )[ - 1 ]
2023-08-23 23:08:04 +03:00
2024-01-20 18:14:18 -05:00
self . gguf . add_name ( name )
2024-03-14 17:21:56 +01:00
self . gguf . add_vocab_size ( params . n_vocab )
2024-01-20 18:14:18 -05:00
self . gguf . add_context_length ( params . n_ctx )
self . gguf . add_embedding_length ( params . n_embd )
self . gguf . add_block_count ( params . n_layer )
self . gguf . add_feed_forward_length ( params . n_ff )
2023-08-21 23:07:43 +03:00
self . gguf . add_rope_dimension_count ( params . n_embd // params . n_head )
2024-01-20 18:14:18 -05:00
self . gguf . add_head_count ( params . n_head )
self . gguf . add_head_count_kv ( params . n_head_kv )
2023-12-13 13:04:25 +01:00
if params . n_experts :
self . gguf . add_expert_count ( params . n_experts )
if params . n_experts_used :
self . gguf . add_expert_used_count ( params . n_experts_used )
2024-01-20 18:14:18 -05:00
if params . f_norm_eps :
self . gguf . add_layer_norm_rms_eps ( params . f_norm_eps )
else :
raise ValueError ( 'f_norm_eps is None' )
2023-09-07 14:27:42 -04:00
if params . f_rope_freq_base is not None :
2023-08-24 20:04:05 +02:00
self . gguf . add_rope_freq_base ( params . f_rope_freq_base )
2023-11-01 18:04:33 -04:00
if params . rope_scaling_type :
assert params . f_rope_scale is not None
self . gguf . add_rope_scaling_type ( params . rope_scaling_type )
self . gguf . add_rope_scaling_factor ( params . f_rope_scale )
if params . n_orig_ctx is not None :
self . gguf . add_rope_scaling_orig_ctx_len ( params . n_orig_ctx )
if params . rope_finetuned is not None :
self . gguf . add_rope_scaling_finetuned ( params . rope_finetuned )
2023-08-25 09:41:52 -05:00
2023-09-07 14:27:42 -04:00
if params . ftype is not None :
2023-08-22 20:05:59 +03:00
self . gguf . add_file_type ( params . ftype )
2024-01-20 18:14:18 -05:00
def extract_vocabulary_from_model ( self , vocab : Vocab ) -> tuple [ list [ bytes ], list [ float ], list [ gguf . TokenType ]]:
2023-08-21 23:07:43 +03:00
tokens = []
scores = []
toktypes = []
2024-01-09 13:46:46 -05:00
2023-08-30 13:29:40 +03:00
# NOTE: `all_tokens` returns the base vocabulary and added tokens
2023-08-21 23:07:43 +03:00
for text , score , toktype in vocab . all_tokens ():
tokens . append ( text )
scores . append ( score )
toktypes . append ( toktype )
2023-04-14 00:03:03 -07:00
2024-01-17 18:37:36 +02:00
assert len ( tokens ) == vocab . vocab_size
2024-01-17 15:45:03 +02:00
2024-01-09 13:46:46 -05:00
return tokens , scores , toktypes
def add_meta_vocab ( self , vocab : Vocab ) -> None :
# Ensure that tokenizer_model is added to the GGUF model
2024-03-14 17:21:56 +01:00
self . gguf . add_tokenizer_model ( vocab . tokenizer_model )
2024-01-09 13:46:46 -05:00
# Extract model vocabulary for model conversion
tokens , scores , toktypes = self . extract_vocabulary_from_model ( vocab )
# Add extracted token information for model conversion
2023-08-21 23:07:43 +03:00
self . gguf . add_token_list ( tokens )
self . gguf . add_token_scores ( scores )
self . gguf . add_token_types ( toktypes )
2023-08-30 02:25:50 -06:00
def add_meta_special_vocab ( self , svocab : gguf . SpecialVocab ) -> None :
svocab . add_to_gguf ( self . gguf )
2023-08-21 23:07:43 +03:00
def add_tensor_info ( self , name : str , tensor : LazyTensor ) -> None :
2023-08-26 14:13:36 -06:00
n_elements = int ( np . prod ( tensor . shape ))
2024-01-20 18:14:18 -05:00
raw_dtype = getattr ( tensor . data_type , 'ggml_type' , None )
data_type = getattr ( tensor . data_type , 'quantized_type' , None ) or tensor . data_type . dtype
2023-08-26 14:13:36 -06:00
data_nbytes = tensor . data_type . elements_to_bytes ( n_elements )
2024-01-20 18:14:18 -05:00
self . gguf . add_tensor_info ( name , tensor . shape , data_type , data_nbytes , raw_dtype = raw_dtype )
2023-08-21 23:07:43 +03:00
def write_meta ( self ) -> None :
self . gguf . write_header_to_file ()
self . gguf . write_kv_data_to_file ()
def write_tensor_info ( self ) -> None :
self . gguf . write_ti_data_to_file ()
2024-03-14 17:21:56 +01:00
def write_tensor_data ( self , ftype : GGMLFileType , model : LazyModel , concurrency : int ) -> None :
ndarrays_inner = bounded_parallel_map ( OutputFile . do_item , model . items (), concurrency = concurrency )
if ftype == GGMLFileType . MostlyQ8_0 :
ndarrays = bounded_parallel_map (
OutputFile . maybe_do_quantize , ndarrays_inner , concurrency = concurrency , max_workers = concurrency ,
use_processpool_executor = True ,
)
else :
ndarrays = map ( OutputFile . maybe_do_quantize , ndarrays_inner )
start = time . time ()
for i , (( name , lazy_tensor ), ndarray ) in enumerate ( zip ( model . items (), ndarrays )):
elapsed = time . time () - start
size = ' x ' . join ( f " { dim : 6d } " for dim in lazy_tensor . shape )
padi = len ( str ( len ( model )))
print (
f "[ { i + 1 :{ padi } d } / { len ( model ) } ] Writing tensor { name : 38s } | size { size : 16 } | type { lazy_tensor . data_type . name : 4 } | T+ { int ( elapsed ) : 4 } "
)
self . gguf . write_tensor_data ( ndarray )
2023-08-21 23:07:43 +03:00
def close ( self ) -> None :
self . gguf . close ()
2023-04-14 00:03:03 -07:00
@staticmethod
2023-12-14 17:09:34 +09:00
def write_vocab_only (
2024-01-20 18:14:18 -05:00
fname_out : Path , params : Params , vocab : Vocab , svocab : gguf . SpecialVocab ,
endianess : gguf . GGUFEndian = gguf . GGUFEndian . LITTLE , pad_vocab : bool = False ,
2023-12-14 17:09:34 +09:00
) -> None :
2024-03-14 17:21:56 +01:00
check_vocab_size ( params , vocab , pad_vocab = pad_vocab )
2023-08-21 23:07:43 +03:00
2023-10-20 06:19:40 -05:00
of = OutputFile ( fname_out , endianess = endianess )
2023-08-21 23:07:43 +03:00
# meta data
of . add_meta_arch ( params )
of . add_meta_vocab ( vocab )
2023-08-30 02:25:50 -06:00
of . add_meta_special_vocab ( svocab )
2023-08-21 23:07:43 +03:00
of . write_meta ()
of . close ()
@staticmethod
2023-08-31 01:02:23 -04:00
def do_item ( item : tuple [ str , LazyTensor ]) -> tuple [ DataType , NDArray ]:
2023-08-26 14:13:36 -06:00
name , lazy_tensor = item
tensor = lazy_tensor . load () . to_ggml ()
return ( lazy_tensor . data_type , tensor . ndarray )
@staticmethod
2023-08-31 01:02:23 -04:00
def maybe_do_quantize ( item : tuple [ DataType , NDArray ]) -> NDArray :
2023-08-26 14:13:36 -06:00
dt , arr = item
if not isinstance ( dt , QuantizedDataType ):
return arr
return dt . quantize ( arr )
@staticmethod
2023-12-14 17:09:34 +09:00
def write_all (
2024-03-28 11:44:36 -04:00
fname_out : Path , ftype : GGMLFileType , params : Params , model : LazyModel , vocab : BaseVocab , svocab : gguf . SpecialVocab ,
2024-01-20 18:14:18 -05:00
concurrency : int = DEFAULT_CONCURRENCY , endianess : gguf . GGUFEndian = gguf . GGUFEndian . LITTLE ,
2023-12-14 17:09:34 +09:00
pad_vocab : bool = False ,
) -> None :
2024-01-09 13:46:46 -05:00
check_vocab_size ( params , vocab , pad_vocab = pad_vocab )
2023-08-21 23:07:43 +03:00
2023-10-20 06:19:40 -05:00
of = OutputFile ( fname_out , endianess = endianess )
2023-08-21 23:07:43 +03:00
# meta data
of . add_meta_arch ( params )
2024-03-28 11:44:36 -04:00
if isinstance ( vocab , Vocab ):
2024-03-14 17:21:56 +01:00
of . add_meta_vocab ( vocab )
of . add_meta_special_vocab ( svocab )
2024-03-28 11:44:36 -04:00
else : # NoVocab
of . gguf . add_tokenizer_model ( vocab . tokenizer_model )
2023-08-21 23:07:43 +03:00
# tensor info
for name , lazy_tensor in model . items ():
of . add_tensor_info ( name , lazy_tensor )
of . write_meta ()
of . write_tensor_info ()
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
# tensor data
2024-03-14 17:21:56 +01:00
of . write_tensor_data ( ftype , model , concurrency )
2023-04-14 00:03:03 -07:00
2023-08-21 23:07:43 +03:00
of . close ()
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-31 01:02:23 -04:00
def pick_output_type ( model : LazyModel , output_type_str : str | None ) -> GGMLFileType :
2023-12-13 13:04:25 +01:00
wq_type = model [ gguf . TENSOR_NAMES [ gguf . MODEL_TENSOR . ATTN_Q ] . format ( bid = 0 ) + ".weight" ] . data_type
2023-08-21 23:07:43 +03:00
2024-03-18 09:04:41 +01:00
if output_type_str == "f32" or ( output_type_str is None and wq_type in ( DT_F32 , DT_BF16 )):
2023-04-14 00:03:03 -07:00
return GGMLFileType . AllF32
2024-03-18 09:04:41 +01:00
if output_type_str == "f16" or ( output_type_str is None and wq_type == DT_F16 ):
2023-04-14 00:03:03 -07:00
return GGMLFileType . MostlyF16
2023-08-26 14:13:36 -06:00
if output_type_str == "q8_0" :
return GGMLFileType . MostlyQ8_0
2023-08-21 23:07:43 +03:00
2023-04-14 00:03:03 -07:00
name_to_type = { name : lazy_tensor . data_type for ( name , lazy_tensor ) in model . items ()}
2023-08-21 23:07:43 +03:00
2024-03-28 11:44:36 -04:00
raise ValueError ( f "Unexpected combination of types: { name_to_type } " )
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-04-14 00:03:03 -07:00
def convert_to_output_type ( model : LazyModel , output_type : GGMLFileType ) -> LazyModel :
return { name : tensor . astype ( output_type . type_for_tensor ( name , tensor ))
for ( name , tensor ) in model . items ()}
2023-11-20 11:35:47 +01:00
2024-02-14 08:38:35 +01:00
def convert_model_names ( model : LazyModel , params : Params , skip_unknown : bool ) -> LazyModel :
2023-08-30 02:25:50 -06:00
tmap = gguf . TensorNameMap ( ARCH , params . n_layer )
2024-03-28 11:44:36 -04:00
should_skip = set ( gguf . MODEL_TENSOR_SKIP . get ( ARCH , []))
2023-08-21 23:07:43 +03:00
tmp = model
2024-04-03 15:07:05 +02:00
# merge experts into one tensor
if params . n_experts and params . n_experts > 0 :
for i_l in range ( params . n_layer ):
for w in range ( 1 , 4 ):
experts = []
for e in range ( params . n_experts ):
if f "layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" in model :
experts . append ( model [ f "layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" ])
del tmp [ f "layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" ]
elif f "model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" in model :
experts . append ( model [ f "model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" ])
del tmp [ f "model.layers. { i_l } .block_sparse_moe.experts. { e } .w { w } .weight" ]
else :
raise ValueError ( f "Expert tensor not found: layers. { i_l } .feed_forward.experts. { e } .w { w } .weight" )
tmp [ f "layers. { i_l } .feed_forward.experts.w { w } .weight" ] = pack_experts_lazy ( experts )
2023-08-21 23:07:43 +03:00
# HF models permut or pack some of the tensors, so we need to undo that
for i in itertools . count ():
if f "model.layers. { i } .self_attn.q_proj.weight" in model :
print ( f "Permuting layer { i } " )
tmp [ 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 )
tmp [ 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 )
2023-11-20 11:35:47 +01:00
# tmp[f"model.layers.{i}.self_attn.v_proj.weight"] = model[f"model.layers.{i}.self_attn.v_proj.weight"]
2023-08-21 23:07:43 +03:00
elif f "model.layers. { i } .self_attn.W_pack.weight" in model :
print ( f "Unpacking and permuting layer { i } " )
2023-08-30 02:25:50 -06:00
tmp [ 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 )
tmp [ 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 )
2023-08-21 23:07:43 +03:00
tmp [ f "model.layers. { i } .self_attn.v_proj.weight" ] = part_lazy ( model [ f "model.layers. { i } .self_attn.W_pack.weight" ], 2 )
2023-08-29 17:48:41 +08:00
del tmp [ f "model.layers. { i } .self_attn.W_pack.weight" ]
2023-08-21 23:07:43 +03:00
else :
break
out : LazyModel = {}
for name , lazy_tensor in model . items ():
2023-08-30 02:25:50 -06:00
tensor_type , name_new = tmap . get_type_and_name ( name , try_suffixes = ( ".weight" , ".bias" )) or ( None , None )
if name_new is None :
2024-02-14 08:38:35 +01:00
if skip_unknown :
print ( f "Unexpected tensor name: { name } - skipping" )
continue
2024-03-28 11:44:36 -04:00
raise ValueError ( f "Unexpected tensor name: { name } . Use --skip-unknown to ignore it (e.g. LLaVA)" )
2023-08-21 23:07:43 +03:00
2023-08-30 02:25:50 -06:00
if tensor_type in should_skip :
2023-08-21 23:07:43 +03:00
print ( f "skipping tensor { name_new } " )
continue
2023-08-30 02:25:50 -06:00
print ( f " { name : 48s } -> { name_new : 40s } | { lazy_tensor . data_type . name : 6s } | { lazy_tensor . shape } " )
out [ name_new ] = lazy_tensor
2023-08-21 23:07:43 +03:00
return out
2023-04-14 00:03:03 -07:00
2023-11-20 11:35:47 +01:00
2023-08-31 01:02:23 -04:00
def nth_multifile_path ( path : Path , n : int ) -> Path | None :
2023-04-14 00:03:03 -07:00
'''Given any path belonging to a multi-file model (e.g. foo.bin.1), return
the nth path in the model.
'''
# Support the following patterns:
2024-03-28 11:44:36 -04:00
patterns = [
2023-04-14 00:03:03 -07:00
# - x.00.pth, x.01.pth, etc.
( r '\.[0-9] {2} \.pth$' , f '. { n : 02 } .pth' ),
# - x-00001-of-00002.bin, x-00002-of-00002.bin, etc.
( r '-[0-9] {5} -of-(.*)$' , fr '- { n : 05 } -of-\1' ),
# x.bin, x.bin.1, etc.
( r '(\.[0-9]+)?$' , r '\1' if n == 0 else fr '\1. { n } ' )
]
for regex , replacement in patterns :
if re . search ( regex , path . name ):
new_path = path . with_name ( re . sub ( regex , replacement , path . name ))
if new_path . exists ():
return new_path
return None
2023-08-31 01:02:23 -04:00
def find_multifile_paths ( path : Path ) -> list [ Path ]:
2023-04-14 00:03:03 -07:00
'''Given any path belonging to a multi-file model (e.g. foo.bin.1), return
the whole list of paths in the model.
'''
2023-08-31 01:02:23 -04:00
ret : list [ Path ] = []
2023-04-14 00:03:03 -07:00
for i in itertools . count ():
nth_path = nth_multifile_path ( path , i )
if nth_path is None :
break
ret . append ( nth_path )
if not ret :
# No matches. This should only happen if the file was named, e.g.,
# foo.0, and there was no file named foo. Oh well, try to process it
# as a single file.
return [ path ]
return ret
def load_some_model ( path : Path ) -> ModelPlus :
'''Load a model of any supported format.'''
# Be extra-friendly and accept either a file or a directory:
if path . is_dir ():
2023-05-08 04:54:26 -07:00
# Check if it's a set of safetensors files first
2024-04-10 15:23:12 +02:00
globs = [ "model-00001-of-*.safetensors" , "model.safetensors" , "consolidated.safetensors" ]
2023-11-13 17:03:40 -08:00
files = [ file for glob in globs for file in path . glob ( glob )]
2023-05-08 04:54:26 -07:00
if not files :
# Try the PyTorch patterns too, with lower priority
2023-06-17 12:32:48 +02:00
globs = [ "consolidated.00.pth" , "pytorch_model-00001-of-*.bin" , "*.pt" , "pytorch_model.bin" ]
2023-05-08 04:54:26 -07:00
files = [ file for glob in globs for file in path . glob ( glob )]
2023-04-14 00:03:03 -07:00
if not files :
2024-03-28 11:44:36 -04:00
raise FileNotFoundError ( f "Can't find model in directory { path } " )
2023-04-14 00:03:03 -07:00
if len ( files ) > 1 :
2024-03-28 11:44:36 -04:00
raise ValueError ( f "Found multiple models in { path } , not sure which to pick: { files } " )
2023-04-14 00:03:03 -07:00
path = files [ 0 ]
paths = find_multifile_paths ( path )
2023-08-31 01:02:23 -04:00
models_plus : list [ ModelPlus ] = []
2023-04-14 00:03:03 -07:00
for path in paths :
print ( f "Loading model file { path } " )
models_plus . append ( lazy_load_file ( path ))
model_plus = merge_multifile_models ( models_plus )
return model_plus
2024-01-09 13:46:46 -05:00
class VocabFactory :
2024-03-28 11:44:36 -04:00
_VOCAB_CLASSES : list [ type [ Vocab ]] = [ SentencePieceVocab , BpeVocab , LlamaHfVocab ]
2024-03-02 12:27:26 -05:00
2024-01-09 13:46:46 -05:00
def __init__ ( self , path : Path ):
self . path = path
2023-08-21 23:07:43 +03:00
2024-03-28 11:44:36 -04:00
def _create_special_vocab ( self , vocab : BaseVocab , model_parent_path : Path ) -> gguf . SpecialVocab :
2024-03-14 17:21:56 +01:00
load_merges = vocab . name == "bpe"
2024-03-28 11:44:36 -04:00
n_vocab = vocab . vocab_size if isinstance ( vocab , Vocab ) else None
2024-01-09 13:46:46 -05:00
return gguf . SpecialVocab (
model_parent_path ,
load_merges = load_merges ,
special_token_types = None , # Predetermined or passed as a parameter
n_vocab = n_vocab ,
)
2024-03-14 17:21:56 +01:00
def _create_vocab_by_path ( self , vocab_types : list [ str ]) -> Vocab :
2024-03-28 11:44:36 -04:00
vocab_classes : dict [ str , type [ Vocab ]] = { cls . name : cls for cls in self . _VOCAB_CLASSES }
selected_vocabs : dict [ str , type [ Vocab ]] = {}
for vtype in vocab_types :
try :
selected_vocabs [ vtype ] = vocab_classes [ vtype ]
except KeyError :
raise ValueError ( f "Unsupported vocabulary type { vtype } " ) from None
2024-01-09 13:46:46 -05:00
2024-03-28 11:44:36 -04:00
for vtype , cls in selected_vocabs . items ():
try :
vocab = cls ( self . path )
break
except FileNotFoundError :
pass # ignore unavailable tokenizers
else :
raise FileNotFoundError ( f "Could not find a tokenizer matching any of { vocab_types } " )
2024-03-14 17:21:56 +01:00
2024-03-28 11:44:36 -04:00
print ( f "Loaded vocab file { vocab . fname_tokenizer !r} , type { vocab . name !r} " )
return vocab
def load_vocab ( self , vocab_types : list [ str ] | None , model_parent_path : Path ) -> tuple [ BaseVocab , gguf . SpecialVocab ]:
vocab : BaseVocab
if vocab_types is None :
2024-03-14 17:21:56 +01:00
vocab = NoVocab ()
2024-01-09 13:46:46 -05:00
else :
2024-03-14 17:21:56 +01:00
vocab = self . _create_vocab_by_path ( vocab_types )
2024-01-20 18:14:18 -05:00
# FIXME: Respect --vocab-dir?
2024-01-09 13:46:46 -05:00
special_vocab = self . _create_special_vocab (
vocab ,
model_parent_path ,
)
return vocab , special_vocab
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
def default_outfile ( model_paths : list [ Path ], file_type : GGMLFileType ) -> Path :
2023-04-14 00:03:03 -07:00
namestr = {
2024-01-20 18:14:18 -05:00
GGMLFileType . AllF32 : "f32" ,
2023-04-14 00:03:03 -07:00
GGMLFileType . MostlyF16 : "f16" ,
2024-01-20 18:14:18 -05:00
GGMLFileType . MostlyQ8_0 : "q8_0" ,
2023-06-22 14:20:47 +02:00
}[ file_type ]
2023-08-21 23:07:43 +03:00
ret = model_paths [ 0 ] . parent / f "ggml-model- { namestr } .gguf"
2023-04-14 00:03:03 -07:00
if ret in model_paths :
2023-06-17 12:32:48 +02:00
sys . stderr . write (
f "Error: Default output path ( { ret } ) would overwrite the input. "
2024-01-20 18:14:18 -05:00
"Please explicitly specify a path using --outfile. \n " )
2023-04-14 00:03:03 -07:00
sys . exit ( 1 )
return ret
def do_dump_model ( model_plus : ModelPlus ) -> None :
print ( f "model_plus.paths = { model_plus . paths !r} " )
print ( f "model_plus.format = { model_plus . format !r} " )
print ( f "model_plus.vocab = { model_plus . vocab !r} " )
for name , lazy_tensor in model_plus . model . items ():
2024-01-20 18:14:18 -05:00
print ( f " { name } : shape= { lazy_tensor . shape } type= { lazy_tensor . data_type } ; { lazy_tensor . description } " )
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
def main ( args_in : list [ str ] | None = None ) -> None :
2023-11-10 22:04:50 -07:00
output_choices = [ "f32" , "f16" ]
if np . uint32 ( 1 ) == np . uint32 ( 1 ) . newbyteorder ( "<" ):
# We currently only support Q8_0 output on little endian systems.
output_choices . append ( "q8_0" )
2024-03-02 12:27:26 -05:00
parser = argparse . ArgumentParser ( description = "Convert a LLaMA model to a GGML compatible file" )
2024-02-14 08:38:35 +01:00
parser . add_argument ( "--dump" , action = "store_true" , help = "don't convert, just show what's in the model" )
parser . add_argument ( "--dump-single" , action = "store_true" , help = "don't convert, just show what's in a single model file" )
parser . add_argument ( "--vocab-only" , action = "store_true" , help = "extract only the vocab" )
2024-03-14 17:21:56 +01:00
parser . add_argument ( "--no-vocab" , action = "store_true" , help = "store model without the vocab" )
2024-02-14 08:38:35 +01:00
parser . add_argument ( "--outtype" , choices = output_choices , help = "output format - note: q8_0 may be very slow (default: f16 or f32 based on input)" )
parser . add_argument ( "--vocab-dir" , type = Path , help = "directory containing tokenizer.model, if separate from model file" )
2024-03-02 12:27:26 -05:00
parser . add_argument ( "--vocab-type" , help = "vocab types to try in order, choose from 'spm', 'bpe', 'hfft' (default: spm,hfft)" , default = "spm,hfft" )
2024-02-14 08:38:35 +01:00
parser . add_argument ( "--outfile" , type = Path , help = "path to write to; default: based on input" )
parser . add_argument ( "model" , type = Path , help = "directory containing model file, or model file itself (*.pth, *.pt, *.bin)" )
parser . add_argument ( "--ctx" , type = int , help = "model training context (default: based on input)" )
parser . add_argument ( "--concurrency" , type = int , help = f "concurrency used for conversion (default: { DEFAULT_CONCURRENCY } )" , default = DEFAULT_CONCURRENCY )
parser . add_argument ( "--big-endian" , action = "store_true" , help = "model is executed on big endian machine" )
parser . add_argument ( "--pad-vocab" , action = "store_true" , help = "add pad tokens when model vocab expects more than tokenizer metadata provides" )
parser . add_argument ( "--skip-unknown" , action = "store_true" , help = "skip unknown tensor names instead of failing" )
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
args = parser . parse_args ( args_in )
2024-03-28 11:44:36 -04:00
if args . no_vocab and args . vocab_only :
raise ValueError ( "--vocab-only does not make sense with --no-vocab" )
2023-12-27 22:39:45 +07:00
2023-04-14 00:03:03 -07:00
if args . dump_single :
model_plus = lazy_load_file ( args . model )
do_dump_model ( model_plus )
2023-08-30 02:25:50 -06:00
return
2023-08-21 23:07:43 +03:00
2023-08-30 02:25:50 -06:00
if not args . vocab_only :
model_plus = load_some_model ( args . model )
else :
2024-01-20 18:14:18 -05:00
model_plus = ModelPlus ( model = {}, paths = [ args . model / 'dummy' ], format = 'none' , vocab = None )
2023-08-30 02:25:50 -06:00
if args . dump :
do_dump_model ( model_plus )
return
2023-10-20 06:19:40 -05:00
endianess = gguf . GGUFEndian . LITTLE
2024-01-09 13:46:46 -05:00
if args . big_endian :
2023-10-20 06:19:40 -05:00
endianess = gguf . GGUFEndian . BIG
2023-08-21 23:07:43 +03:00
params = Params . load ( model_plus )
if params . n_ctx == - 1 :
if args . ctx is None :
2024-03-28 11:44:36 -04:00
msg = """ \
The model doesn't have a context size, and you didn't specify one with --ctx
Please specify one with --ctx:
- LLaMA v1: --ctx 2048
- LLaMA v2: --ctx 4096"""
parser . error ( textwrap . dedent ( msg ))
2023-08-21 23:07:43 +03:00
params . n_ctx = args . ctx
2023-08-22 20:05:59 +03:00
if args . outtype :
params . ftype = {
"f32" : GGMLFileType . AllF32 ,
"f16" : GGMLFileType . MostlyF16 ,
2023-08-26 14:13:36 -06:00
"q8_0" : GGMLFileType . MostlyQ8_0 ,
2023-08-22 20:05:59 +03:00
}[ args . outtype ]
2023-08-21 23:07:43 +03:00
print ( f "params = { params } " )
2024-01-09 13:46:46 -05:00
model_parent_path = model_plus . paths [ 0 ] . parent
vocab_path = Path ( args . vocab_dir or args . model or model_parent_path )
vocab_factory = VocabFactory ( vocab_path )
2024-03-28 11:44:36 -04:00
vocab_types = None if args . no_vocab else args . vocab_type . split ( "," )
vocab , special_vocab = vocab_factory . load_vocab ( vocab_types , model_parent_path )
2024-01-09 13:46:46 -05:00
2023-08-21 23:07:43 +03:00
if args . vocab_only :
2024-03-28 11:44:36 -04:00
assert isinstance ( vocab , Vocab )
2023-10-22 12:14:56 -06:00
if not args . outfile :
raise ValueError ( "need --outfile if using --vocab-only" )
2023-04-14 00:03:03 -07:00
outfile = args . outfile
2024-01-20 18:14:18 -05:00
OutputFile . write_vocab_only ( outfile , params , vocab , special_vocab ,
endianess = endianess , pad_vocab = args . pad_vocab )
2023-04-14 00:03:03 -07:00
print ( f "Wrote { outfile } " )
2023-08-30 02:25:50 -06:00
return
2024-03-14 17:21:56 +01:00
if model_plus . vocab is not None and args . vocab_dir is None and not args . no_vocab :
2023-08-30 02:25:50 -06:00
vocab = model_plus . vocab
2023-12-14 17:09:34 +09:00
2024-01-20 18:14:18 -05:00
print ( f "Vocab info: { vocab } " )
print ( f "Special vocab info: { special_vocab } " )
model = model_plus . model
2024-02-14 08:38:35 +01:00
model = convert_model_names ( model , params , args . skip_unknown )
2024-01-20 18:14:18 -05:00
ftype = pick_output_type ( model , args . outtype )
model = convert_to_output_type ( model , ftype )
outfile = args . outfile or default_outfile ( model_plus . paths , ftype )
2023-08-21 23:07:43 +03:00
2023-08-30 02:25:50 -06:00
params . ftype = ftype
print ( f "Writing { outfile } , format { ftype } " )
2023-08-22 20:05:59 +03:00
2024-01-20 18:14:18 -05:00
OutputFile . write_all ( outfile , ftype , params , model , vocab , special_vocab ,
concurrency = args . concurrency , endianess = endianess , pad_vocab = args . pad_vocab )
2023-08-30 02:25:50 -06:00
print ( f "Wrote { outfile } " )
2023-04-14 00:03:03 -07:00
2024-01-20 18:14:18 -05:00
if __name__ == '__main__' :
main ()