char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d82b7a7c1
char/aliceai-t5-moe
1#!/usr/bin/env python3 2from __future__import annotations 3 4import logging 5import argparse 6import os 7import sys 8import json 9from pathlib import Path 10 11from tqdm import tqdm 12from typing import Any ,Sequence ,NamedTuple 13 14# Necessary to load the local gguf package 15if "NO_LOCAL_GGUF" not in os .environ and (Path (__file__ ).parent .parent .parent .parent / 'gguf-py' ).exists (): 16sys .path .insert (0 ,str (Path (__file__ ).parent .parent .parent )) 17 18import gguf 19 20logger = logging .getLogger ("gguf-new-metadata" ) 21 22 23class MetadataDetails (NamedTuple ): 24type :gguf .GGUFValueType 25value :Any 26description :str = '' 27sub_type :gguf .GGUFValueType | None = None 28 29 30def get_field_data (reader :gguf .GGUFReader ,key :str )-> Any : 31field = reader .get_field (key ) 32 33return field .contents ()if field else None 34 35 36def find_token (token_list :Sequence [int ],token :str )-> Sequence [int ]: 37token_ids = [index for index ,value in enumerate (token_list )if value == token ] 38 39if len (token_ids )== 0 : 40raise LookupError (f'Unable to find " { token } " in token list!' ) 41 42return token_ids 43 44 45def copy_with_new_metadata (reader :gguf .GGUFReader ,writer :gguf .GGUFWriter ,new_metadata :dict [str ,MetadataDetails ],remove_metadata :Sequence [str ])-> None : 46for field in reader .fields .values (): 47# Suppress virtual fields and fields written by GGUFWriter 48if field .name == gguf .Keys .General .ARCHITECTURE or field .name .startswith ('GGUF.' ): 49logger .debug (f'Suppressing { field . name } ' ) 50continue 51 52# Skip old chat templates if we have new ones 53if field .name .startswith (gguf .Keys .Tokenizer .CHAT_TEMPLATE )and gguf .Keys .Tokenizer .CHAT_TEMPLATE in new_metadata : 54logger .debug (f'Skipping { field . name } ' ) 55continue 56 57if field .name in remove_metadata : 58logger .debug (f'Removing { field . name } ' ) 59continue 60 61val_type = field .types [0 ] 62sub_type = field .types [- 1 ]if val_type == gguf .GGUFValueType .ARRAY else None 63old_val = MetadataDetails (val_type ,field .contents (),sub_type = sub_type ) 64val = new_metadata .get (field .name ,old_val ) 65 66if field .name in new_metadata : 67logger .debug (f'Modifying { field . name } : " { old_val . value } " -> " { val . value } " { val . description } ' ) 68del new_metadata [field .name ] 69elif val .value is not None : 70logger .debug (f'Copying { field . name } ' ) 71 72if val .value is not None : 73writer .add_key_value (field .name ,val .value ,val .type ,sub_type = sub_type if val .sub_type is None else val .sub_type ) 74 75if gguf .Keys .Tokenizer .CHAT_TEMPLATE in new_metadata : 76logger .debug ('Adding chat template(s)' ) 77writer .add_chat_template (new_metadata [gguf .Keys .Tokenizer .CHAT_TEMPLATE ].value ) 78del new_metadata [gguf .Keys .Tokenizer .CHAT_TEMPLATE ] 79 80for key ,val in new_metadata .items (): 81logger .debug (f'Adding { key } : " { val . value } " { val . description } ' ) 82writer .add_key_value (key ,val .value ,val .type ) 83 84total_bytes = 0 85 86for tensor in reader .tensors : 87total_bytes += tensor .n_bytes 88writer .add_tensor_info (tensor .name ,tensor .data .shape ,tensor .data .dtype ,tensor .data .nbytes ,tensor .tensor_type ) 89 90bar = tqdm (desc = "Writing" ,total = total_bytes ,unit = "byte" ,unit_scale = True ) 91 92writer .write_header_to_file () 93writer .write_kv_data_to_file () 94writer .write_ti_data_to_file () 95 96for tensor in reader .tensors : 97writer .write_tensor_data (tensor .data ,tensor_endianess = reader .endianess ) 98bar .update (tensor .n_bytes ) 99 100writer .close () 101 102 103def main ()-> None : 104tokenizer_metadata = (getattr (gguf .Keys .Tokenizer ,n )for n in gguf .Keys .Tokenizer .__dict__ .keys ()if not n .startswith ('_' )) 105token_names = dict ((n .split ('.' )[- 1 ][:- len ('_token_id' )],n )for n in tokenizer_metadata if n .endswith ('_token_id' )) 106 107parser = argparse .ArgumentParser (description = "Make a copy of a GGUF file with new metadata" ) 108parser .add_argument ("input" ,type = Path ,help = "GGUF format model input filename" ) 109parser .add_argument ("output" ,type = Path ,help = "GGUF format model output filename" ) 110parser .add_argument ("--general-name" ,type = str ,help = "The models general.name" ,metavar = '"name"' ) 111parser .add_argument ("--general-description" ,type = str ,help = "The models general.description" ,metavar = '"Description ..."' ) 112parser .add_argument ("--chat-template" ,type = str ,help = "Chat template string (or JSON string containing templates)" ,metavar = '"{% ... %} ..."' ) 113parser .add_argument ("--chat-template-config" ,type = Path ,help = "Config file containing chat template(s)" ,metavar = 'tokenizer_config.json' ) 114parser .add_argument ("--chat-template-file" ,type = Path ,help = "Jinja file containing chat template" ,metavar = 'chat_template.jinja' ) 115parser .add_argument ("--pre-tokenizer" ,type = str ,help = "The models tokenizer.ggml.pre" ,metavar = '"pre tokenizer"' ) 116parser .add_argument ("--remove-metadata" ,action = "append" ,type = str ,help = "Remove metadata (by key name) from output model" ,metavar = 'general.url' ) 117parser .add_argument ("--special-token" ,action = "append" ,type = str ,help = "Special token by value" ,nargs = 2 ,metavar = (' | ' .join (token_names .keys ()),'"<token>"' )) 118parser .add_argument ("--special-token-by-id" ,action = "append" ,type = str ,help = "Special token by id" ,nargs = 2 ,metavar = (' | ' .join (token_names .keys ()),'0' )) 119parser .add_argument ("--force" ,action = "store_true" ,help = "Bypass warnings without confirmation" ) 120parser .add_argument ("--verbose" ,action = "store_true" ,help = "Increase output verbosity" ) 121args = parser .parse_args (None if len (sys .argv )> 2 else ["--help" ]) 122 123logging .basicConfig (level = logging .DEBUG if args .verbose else logging .INFO ) 124 125new_metadata = {} 126remove_metadata = args .remove_metadata or [] 127 128if args .general_name : 129new_metadata [gguf .Keys .General .NAME ]= MetadataDetails (gguf .GGUFValueType .STRING ,args .general_name ) 130 131if args .general_description : 132new_metadata [gguf .Keys .General .DESCRIPTION ]= MetadataDetails (gguf .GGUFValueType .STRING ,args .general_description ) 133 134if args .chat_template : 135new_metadata [gguf .Keys .Tokenizer .CHAT_TEMPLATE ]= MetadataDetails (gguf .GGUFValueType .STRING ,json .loads (args .chat_template )if args .chat_template .startswith ('[' )else args .chat_template ) 136 137if args .chat_template_config : 138with open (args .chat_template_config ,'r' ,encoding = 'utf-8' )as fp : 139config = json .load (fp ) 140template = config .get ('chat_template' ) 141if template : 142new_metadata [gguf .Keys .Tokenizer .CHAT_TEMPLATE ]= MetadataDetails (gguf .GGUFValueType .STRING ,template ) 143 144if args .chat_template_file : 145with open (args .chat_template_file ,'r' ,encoding = 'utf-8' )as fp : 146template = fp .read () 147new_metadata [gguf .Keys .Tokenizer .CHAT_TEMPLATE ]= MetadataDetails (gguf .GGUFValueType .STRING ,template ) 148 149if args .pre_tokenizer : 150new_metadata [gguf .Keys .Tokenizer .PRE ]= MetadataDetails (gguf .GGUFValueType .STRING ,args .pre_tokenizer ) 151 152if remove_metadata : 153logger .warning ('*** Warning *** Warning *** Warning **' ) 154logger .warning ('* Most metadata is required for a fully functional GGUF file,' ) 155logger .warning ('* removing crucial metadata may result in a corrupt output file!' ) 156 157if not args .force : 158logger .warning ('* Enter exactly YES if you are positive you want to proceed:' ) 159response = input ('YES, I am sure> ' ) 160if response != 'YES' : 161logger .info ("You didn't enter YES. Okay then, see ya!" ) 162sys .exit (0 ) 163 164logger .info (f'* Loading: { args . input } ' ) 165reader = gguf .GGUFReader (args .input ,'r' ) 166 167arch = get_field_data (reader ,gguf .Keys .General .ARCHITECTURE ) 168 169token_list = get_field_data (reader ,gguf .Keys .Tokenizer .LIST )or [] 170 171for name ,token in args .special_token or []: 172if name not in token_names : 173logger .warning (f'Unknown special token " { name } ", ignoring...' ) 174else : 175ids = find_token (token_list ,token ) 176new_metadata [token_names [name ]]= MetadataDetails (gguf .GGUFValueType .UINT32 ,ids [0 ],f'= { token } ' ) 177 178if len (ids )> 1 : 179logger .warning (f'Multiple " { token } " tokens found, choosing ID { ids [ 0 ] } , use --special-token-by-id if you want another:' ) 180logger .warning (', ' .join (str (i )for i in ids )) 181 182for name ,id_string in args .special_token_by_id or []: 183if name not in token_names : 184logger .warning (f'Unknown special token " { name } ", ignoring...' ) 185elif not id_string .isdecimal (): 186raise LookupError (f'Token ID " { id_string } " is not a valid ID!' ) 187else : 188id_int = int (id_string ) 189 190if id_int >= 0 and id_int < len (token_list ): 191new_metadata [token_names [name ]]= MetadataDetails (gguf .GGUFValueType .UINT32 ,id_int ,f'= { token_list [ id_int ] } ' ) 192else : 193raise LookupError (f'Token ID { id_int } is not within token list!' ) 194 195if os .path .isfile (args .output )and not args .force : 196logger .warning ('*** Warning *** Warning *** Warning **' ) 197logger .warning (f'* The " { args . output } " GGUF file already exists, it will be overwritten!' ) 198logger .warning ('* Enter exactly YES if you are positive you want to proceed:' ) 199response = input ('YES, I am sure> ' ) 200if response != 'YES' : 201logger .info ("You didn't enter YES. Okay then, see ya!" ) 202sys .exit (0 ) 203 204logger .info (f'* Writing: { args . output } ' ) 205writer = gguf .GGUFWriter (args .output ,arch = arch ,endianess = reader .endianess ) 206 207alignment = get_field_data (reader ,gguf .Keys .General .ALIGNMENT ) 208if alignment is not None : 209logger .debug (f'Setting custom alignment: { alignment } ' ) 210writer .data_alignment = alignment 211 212copy_with_new_metadata (reader ,writer ,new_metadata ,remove_metadata ) 213 214 215if __name__ == '__main__' : 216main ()