char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
c81f4192f
char/aliceai-t5-moe
1#!/usr/bin/env python3 2from __future__import annotations 3 4import logging 5import argparse 6import os 7import re 8import sys 9from pathlib import Path 10from typing import Any 11 12# Necessary to load the local gguf package 13if "NO_LOCAL_GGUF" not in os .environ and (Path (__file__ ).parent .parent .parent .parent / 'gguf-py' ).exists (): 14sys .path .insert (0 ,str (Path (__file__ ).parent .parent .parent )) 15 16from gguf import GGUFReader ,GGUFValueType ,ReaderTensor # noqa: E402 17 18logger = logging .getLogger ("gguf-dump" ) 19 20 21def get_file_host_endian (reader :GGUFReader )-> tuple [str ,str ]: 22file_endian = reader .endianess .name 23if reader .byte_order == 'S' : 24host_endian = 'BIG' if file_endian == 'LITTLE' else 'LITTLE' 25else : 26host_endian = file_endian 27return (host_endian ,file_endian ) 28 29 30# For more information about what field.parts and field.data represent, 31# please see the comments in the modify_gguf.py example. 32def dump_metadata (reader :GGUFReader ,args :argparse .Namespace )-> None : 33host_endian ,file_endian = get_file_host_endian (reader ) 34f'* File is { file_endian } endian, script is running on a { host_endian } endian host.' )# noqa: NP100 35f'* Dumping { len ( reader . fields ) } key/value pair(s)' )# noqa: NP100 36for n ,field in enumerate (reader .fields .values (),1 ): 37if not field .types : 38pretty_type = 'N/A' 39elif field .types [0 ]== GGUFValueType .ARRAY : 40nest_count = len (field .types )- 1 41pretty_type = '[' * nest_count + str (field .types [- 1 ].name )+ ']' * nest_count 42else : 43pretty_type = str (field .types [- 1 ].name ) 44 45log_message = f' { n :5 } : { pretty_type :10 } | { len ( field . data ):8 } | { field . name } ' 46if field .types : 47curr_type = field .types [0 ] 48if curr_type == GGUFValueType .STRING : 49content = field .contents () 50if len (content )> 60 : 51content = content [:57 ]+ '...' 52log_message += ' = {0}' .format (repr (content )) 53elif curr_type in reader .gguf_scalar_to_np : 54log_message += ' = {0}' .format (field .contents ()) 55else : 56content = repr (field .contents (slice (6 ))) 57if len (field .data )> 6 : 58content = content [:- 1 ]+ ', ...]' 59log_message += ' = {0}' .format (content ) 60log_message )# noqa: NP100 61if args .no_tensors : 62return 63f'* Dumping { len ( reader . tensors ) } tensor(s)' )# noqa: NP100 64for n ,tensor in enumerate (reader .tensors ,1 ): 65prettydims = ', ' .join ('{0:5}' .format (d )for d in list (tensor .shape )+ [1 ]* (4 - len (tensor .shape ))) 66f' { n :5 } : { tensor . n_elements :10 } | { prettydims } | { tensor . tensor_type . name :7 } | { tensor . name } ' )# noqa: NP100 67 68 69def dump_metadata_json (reader :GGUFReader ,args :argparse .Namespace )-> None : 70import json 71host_endian ,file_endian = get_file_host_endian (reader ) 72metadata :dict [str ,Any ]= {} 73tensors :dict [str ,Any ]= {} 74result = { 75"filename" :args .model , 76"endian" :file_endian , 77"metadata" :metadata , 78"tensors" :tensors , 79 } 80for idx ,field in enumerate (reader .fields .values ()): 81curr :dict [str ,Any ]= { 82"index" :idx , 83"type" :field .types [0 ].name if field .types else 'UNKNOWN' , 84"offset" :field .offset , 85 } 86metadata [field .name ]= curr 87if field .types [:1 ]== [GGUFValueType .ARRAY ]: 88curr ["array_types" ]= [t .name for t in field .types ][1 :] 89if not args .json_array : 90continue 91curr ["value" ]= field .contents () 92else : 93curr ["value" ]= field .contents () 94if not args .no_tensors : 95for idx ,tensor in enumerate (reader .tensors ): 96tensors [tensor .name ]= { 97"index" :idx , 98"shape" :tensor .shape .tolist (), 99"type" :tensor .tensor_type .name , 100"offset" :tensor .field .offset , 101 } 102json .dump (result ,sys .stdout ) 103 104 105def markdown_table_with_alignment_support (header_map :list [dict [str ,str ]],data :list [dict [str ,Any ]]): 106# JSON to Markdown table formatting: https://stackoverflow.com/a/72983854/2850957 107 108# Alignment Utility Function 109def strAlign (padding :int ,alignMode :str | None ,strVal :str ): 110if alignMode == 'center' : 111return strVal .center (padding ) 112elif alignMode == 'right' : 113return strVal .rjust (padding - 1 )+ ' ' 114elif alignMode == 'left' : 115return ' ' + strVal .ljust (padding - 1 ) 116else :# default left 117return ' ' + strVal .ljust (padding - 1 ) 118 119def dashAlign (padding :int ,alignMode :str | None ): 120if alignMode == 'center' : 121return ':' + '-' * (padding - 2 )+ ':' 122elif alignMode == 'right' : 123return '-' * (padding - 1 )+ ':' 124elif alignMode == 'left' : 125return ':' + '-' * (padding - 1 ) 126else :# default left 127return '-' * (padding ) 128 129# Calculate Padding For Each Column Based On Header and Data Length 130rowsPadding = {} 131for index ,columnEntry in enumerate (header_map ): 132padCount = max ([len (str (v ))for d in data for k ,v in d .items ()if k == columnEntry ['key_name' ]],default = 0 )+ 2 133headerPadCount = len (columnEntry ['header_name' ])+ 2 134rowsPadding [index ]= headerPadCount if padCount <= headerPadCount else padCount 135 136# Render Markdown Header 137rows = [] 138rows .append ('|' .join (strAlign (rowsPadding [index ],columnEntry .get ('align' ),str (columnEntry ['header_name' ]))for index ,columnEntry in enumerate (header_map ))) 139rows .append ('|' .join (dashAlign (rowsPadding [index ],columnEntry .get ('align' ))for index ,columnEntry in enumerate (header_map ))) 140 141# Render Tabular Data 142for item in data : 143rows .append ('|' .join (strAlign (rowsPadding [index ],columnEntry .get ('align' ),str (item [columnEntry ['key_name' ]]))for index ,columnEntry in enumerate (header_map ))) 144 145# Convert Tabular String Rows Into String 146tableString = "" 147for row in rows : 148tableString += f'| { row } |\n' 149 150return tableString 151 152 153def element_count_rounded_notation (count :int )-> str : 154if count > 1e15 : 155# Quadrillion 156scaled_amount = count * 1e-15 157scale_suffix = "Q" 158elif count > 1e12 : 159# Trillions 160scaled_amount = count * 1e-12 161scale_suffix = "T" 162elif count > 1e9 : 163# Billions 164scaled_amount = count * 1e-9 165scale_suffix = "B" 166elif count > 1e6 : 167# Millions 168scaled_amount = count * 1e-6 169scale_suffix = "M" 170elif count > 1e3 : 171# Thousands 172scaled_amount = count * 1e-3 173scale_suffix = "K" 174else : 175# Under Thousands 176scaled_amount = count 177scale_suffix = "" 178return f" { '~' if count > 1e3 else '' } { round ( scaled_amount ) } { scale_suffix } " 179 180 181def translate_tensor_name (name ): 182words = name .split ("." ) 183 184# Source: https://github.com/ggml-org/ggml/blob/master/docs/gguf.md#standardized-tensor-names 185abbreviation_dictionary = { 186'token_embd' :'Token embedding' , 187'pos_embd' :'Position embedding' , 188'output_norm' :'Output normalization' , 189'output' :'Output' , 190'attn_norm' :'Attention normalization' , 191'attn_norm_2' :'Attention normalization' , 192'attn_qkv' :'Attention query-key-value' , 193'attn_q' :'Attention query' , 194'attn_k' :'Attention key' , 195'attn_v' :'Attention value' , 196'attn_output' :'Attention output' , 197'ffn_norm' :'Feed-forward network normalization' , 198'ffn_up' :'Feed-forward network "up"' , 199'ffn_gate' :'Feed-forward network "gate"' , 200'ffn_down' :'Feed-forward network "down"' , 201'ffn_gate_inp' :'Expert-routing layer for the Feed-forward network in Mixture of Expert models' , 202'ffn_gate_exp' :'Feed-forward network "gate" layer per expert in Mixture of Expert models' , 203'ffn_down_exp' :'Feed-forward network "down" layer per expert in Mixture of Expert models' , 204'ffn_up_exp' :'Feed-forward network "up" layer per expert in Mixture of Expert models' , 205'ssm_in' :'State space model input projections' , 206'ssm_conv1d' :'State space model rolling/shift' , 207'ssm_x' :'State space model selective parametrization' , 208'ssm_a' :'State space model state compression' , 209'ssm_d' :'State space model skip connection' , 210'ssm_dt' :'State space model time step' , 211'ssm_out' :'State space model output projection' , 212'blk' :'Block' , 213'enc' :'Encoder' , 214'dec' :'Decoder' , 215 } 216 217expanded_words = [] 218for word in words : 219word_norm = word .strip ().lower () 220if word_norm in abbreviation_dictionary : 221expanded_words .append (abbreviation_dictionary [word_norm ].title ()) 222else : 223expanded_words .append (word .title ()) 224 225return ' ' .join (expanded_words ) 226 227 228def dump_markdown_metadata (reader :GGUFReader ,args :argparse .Namespace )-> None : 229host_endian ,file_endian = get_file_host_endian (reader ) 230markdown_content = "" 231markdown_content += f'# { args . model } - GGUF Internal File Dump\n\n' 232markdown_content += f'- Endian: { file_endian } endian\n' 233markdown_content += '\n' 234markdown_content += '## Key Value Metadata Store\n\n' 235markdown_content += f'There are { len ( reader . fields ) } key-value pairs in this file\n' 236markdown_content += '\n' 237total_model_bytes = 0 238total_model_elements = 0 239 240kv_dump_table :list [dict [str ,str | int ]]= [] 241for n ,field in enumerate (reader .fields .values (),1 ): 242if not field .types : 243pretty_type = 'N/A' 244elif field .types [0 ]== GGUFValueType .ARRAY : 245nest_count = len (field .types )- 1 246pretty_type = '[' * nest_count + str (field .types [- 1 ].name )+ ']' * nest_count 247else : 248pretty_type = str (field .types [- 1 ].name ) 249 250def escape_markdown_inline_code (value_string ): 251# Find the longest contiguous sequence of backticks in the string then 252# wrap string with appropriate number of backticks required to escape it 253max_backticks = max ((len (match .group (0 ))for match in re .finditer (r'`+' ,value_string )),default = 0 ) 254inline_code_marker = '`' * (max_backticks + 1 ) 255 256# If the string starts or ends with a backtick, add a space at the beginning and end 257if value_string .startswith ('`' )or value_string .endswith ('`' ): 258value_string = f" { value_string } " 259 260return f" { inline_code_marker } { value_string } { inline_code_marker } " 261 262total_elements = len (field .data ) 263value = "" 264if len (field .types )== 1 : 265curr_type = field .types [0 ] 266if curr_type == GGUFValueType .STRING : 267truncate_length = 60 268value_string = str (bytes (field .parts [- 1 ]),encoding = 'utf-8' ) 269if len (value_string )> truncate_length : 270head = escape_markdown_inline_code (value_string [:truncate_length // 2 ]) 271tail = escape_markdown_inline_code (value_string [- truncate_length // 2 :]) 272value = "{head}...{tail}" .format (head = head ,tail = tail ) 273else : 274value = escape_markdown_inline_code (value_string ) 275elif curr_type in reader .gguf_scalar_to_np : 276value = str (field .parts [- 1 ][0 ]) 277else : 278if field .types [0 ]== GGUFValueType .ARRAY : 279curr_type = field .types [1 ] 280array_elements = [] 281 282if curr_type == GGUFValueType .STRING : 283render_element = min (5 ,total_elements ) 284for element_pos in range (render_element ): 285truncate_length = 30 286value_string = str (bytes (field .parts [- 1 - (total_elements - element_pos - 1 )* 2 ]),encoding = 'utf-8' ) 287if len (value_string )> truncate_length : 288head = escape_markdown_inline_code (value_string [:truncate_length // 2 ]) 289tail = escape_markdown_inline_code (value_string [- truncate_length // 2 :]) 290value = "{head}...{tail}" .format (head = head ,tail = tail ) 291else : 292value = escape_markdown_inline_code (value_string ) 293array_elements .append (value ) 294 295elif curr_type in reader .gguf_scalar_to_np : 296render_element = min (7 ,total_elements ) 297for element_pos in range (render_element ): 298array_elements .append (str (field .parts [- 1 - (total_elements - element_pos - 1 )][0 ])) 299 300value = f'[ { ", " . join ( array_elements ). strip () } { ", ..." if total_elements > len ( array_elements ) else "" } ]' 301 302kv_dump_table .append ({"n" :n ,"pretty_type" :pretty_type ,"total_elements" :total_elements ,"field_name" :field .name ,"value" :value }) 303 304kv_dump_table_header_map = [ 305 {'key_name' :'n' ,'header_name' :'POS' ,'align' :'right' }, 306 {'key_name' :'pretty_type' ,'header_name' :'TYPE' ,'align' :'left' }, 307 {'key_name' :'total_elements' ,'header_name' :'Count' ,'align' :'right' }, 308 {'key_name' :'field_name' ,'header_name' :'Key' ,'align' :'left' }, 309 {'key_name' :'value' ,'header_name' :'Value' ,'align' :'left' }, 310 ] 311 312markdown_content += markdown_table_with_alignment_support (kv_dump_table_header_map ,kv_dump_table ) 313 314markdown_content += "\n" 315 316if not args .no_tensors : 317# Group tensors by their prefix and maintain order 318tensor_prefix_order :list [str ]= [] 319tensor_name_to_key :dict [str ,int ]= {} 320tensor_groups :dict [str ,list [ReaderTensor ]]= {} 321total_elements = sum (tensor .n_elements for tensor in reader .tensors ) 322 323# Parsing Tensors Record 324for key ,tensor in enumerate (reader .tensors ): 325tensor_components = tensor .name .split ('.' ) 326 327# Classify Tensor Group 328tensor_group_name = "base" 329if tensor_components [0 ]== 'blk' : 330tensor_group_name = f" { tensor_components [ 0 ] } . { tensor_components [ 1 ] } " 331elif tensor_components [0 ]in ['enc' ,'dec' ]and tensor_components [1 ]== 'blk' : 332tensor_group_name = f" { tensor_components [ 0 ] } . { tensor_components [ 1 ] } . { tensor_components [ 2 ] } " 333elif tensor_components [0 ]in ['enc' ,'dec' ]: 334tensor_group_name = f" { tensor_components [ 0 ] } " 335 336# Check if new Tensor Group 337if tensor_group_name not in tensor_groups : 338tensor_groups [tensor_group_name ]= [] 339tensor_prefix_order .append (tensor_group_name ) 340 341# Record Tensor and Tensor Position 342tensor_groups [tensor_group_name ].append (tensor ) 343tensor_name_to_key [tensor .name ]= key 344 345# Tensors Mapping Dump 346markdown_content += f'## Tensors Overview { element_count_rounded_notation ( total_elements ) } Elements\n\n' 347markdown_content += f'Total number of elements in all tensors: { total_elements } Elements\n' 348markdown_content += '\n' 349 350for group in tensor_prefix_order : 351tensors = tensor_groups [group ] 352group_elements = sum (tensor .n_elements for tensor in tensors ) 353markdown_content += f"- [ { translate_tensor_name ( group ) } Tensor Group - { element_count_rounded_notation ( group_elements ) } Elements](# { group . replace ( '.' , '_' ) } )\n" 354 355markdown_content += "\n" 356 357markdown_content += "### Tensor Data Offset\n" 358markdown_content += '\n' 359markdown_content += 'This table contains the offset and data segment relative to start of file\n' 360markdown_content += '\n' 361 362tensor_mapping_table :list [dict [str ,str | int ]]= [] 363for key ,tensor in enumerate (reader .tensors ): 364data_offset_pretty = '{0:#16x}' .format (tensor .data_offset ) 365data_size_pretty = '{0:#16x}' .format (tensor .n_bytes ) 366tensor_mapping_table .append ({"t_id" :key ,"layer_name" :tensor .name ,"data_offset" :data_offset_pretty ,"data_size" :data_size_pretty }) 367 368tensors_mapping_table_header_map = [ 369 {'key_name' :'t_id' ,'header_name' :'T_ID' ,'align' :'right' }, 370 {'key_name' :'layer_name' ,'header_name' :'Tensor Layer Name' ,'align' :'left' }, 371 {'key_name' :'data_offset' ,'header_name' :'Data Offset (B)' ,'align' :'right' }, 372 {'key_name' :'data_size' ,'header_name' :'Data Size (B)' ,'align' :'right' }, 373 ] 374 375markdown_content += markdown_table_with_alignment_support (tensors_mapping_table_header_map ,tensor_mapping_table ) 376markdown_content += "\n" 377 378for group in tensor_prefix_order : 379tensors = tensor_groups [group ] 380group_elements = sum (tensor .n_elements for tensor in tensors ) 381group_percentage = group_elements / total_elements * 100 382total_group_bytes = 0 383total_group_elements = 0 384markdown_content += f"### <a name=\" { group . replace ( '.' , '_' ) } \"> { translate_tensor_name ( group ) } Tensor Group : { element_count_rounded_notation ( group_elements ) } Elements</a>\n\n" 385 386# Precalculate column sizing for visual consistency 387prettify_element_est_count_size :int = 1 388prettify_element_count_size :int = 1 389prettify_dimension_max_widths :dict [int ,int ]= {} 390for tensor in tensors : 391prettify_element_est_count_size = max (prettify_element_est_count_size ,len (str (element_count_rounded_notation (tensor .n_elements )))) 392prettify_element_count_size = max (prettify_element_count_size ,len (str (tensor .n_elements ))) 393for i ,dimension_size in enumerate (list (tensor .shape )+ [1 ]* (4 - len (tensor .shape ))): 394prettify_dimension_max_widths [i ]= max (prettify_dimension_max_widths .get (i ,1 ),len (str (dimension_size ))) 395 396# Generate Tensor Layer Table Content 397tensor_dump_table :list [dict [str ,str | int ]]= [] 398for tensor in tensors : 399human_friendly_name = translate_tensor_name (tensor .name .replace (".weight" ,".(W)" ).replace (".bias" ,".(B)" )) 400pretty_dimension = ' x ' .join (f' { str ( d ):>{ prettify_dimension_max_widths [ i ]} } ' for i ,d in enumerate (list (tensor .shape )+ [1 ]* (4 - len (tensor .shape )))) 401element_count_est = f"( { element_count_rounded_notation ( tensor . n_elements ):>{ prettify_element_est_count_size } } )" 402element_count_string = f" { element_count_est } { tensor . n_elements :>{ prettify_element_count_size } } " 403type_name_string = f" { tensor . tensor_type . name } " 404if tensor .n_elements > 0 : 405bpw = (tensor .n_bytes * 8 )/ tensor .n_elements 406else : 407bpw = float ('nan' ) 408tensor_dump_table .append ({"t_id" :tensor_name_to_key [tensor .name ],"layer_name" :tensor .name ,"human_layer_name" :human_friendly_name ,"element_count" :element_count_string ,"pretty_dimension" :pretty_dimension ,"tensor_type" :type_name_string ,"bpw" :f" { bpw :.4f } " }) 409total_group_bytes += tensor .n_bytes 410total_group_elements += tensor .n_elements 411 412tensor_dump_table_header_map = [ 413 {'key_name' :'t_id' ,'header_name' :'T_ID' ,'align' :'right' }, 414 {'key_name' :'layer_name' ,'header_name' :'Tensor Layer Name' ,'align' :'left' }, 415 {'key_name' :'human_layer_name' ,'header_name' :'Human Friendly Tensor Layer Name' ,'align' :'left' }, 416 {'key_name' :'element_count' ,'header_name' :'Elements' ,'align' :'left' }, 417 {'key_name' :'pretty_dimension' ,'header_name' :'Shape' ,'align' :'left' }, 418 {'key_name' :'tensor_type' ,'header_name' :'Type' ,'align' :'left' }, 419 {'key_name' :'bpw' ,'header_name' :'BPW' ,'align' :'right' }, 420 ] 421 422markdown_content += markdown_table_with_alignment_support (tensor_dump_table_header_map ,tensor_dump_table ) 423 424markdown_content += "\n" 425markdown_content += f"- Total elements in { group } : ( { element_count_rounded_notation ( group_elements ):>4 } ) { group_elements } \n" 426markdown_content += f"- Percentage of total elements: { group_percentage :.2f } %\n" 427if total_group_elements > 0 : 428total_group_bpw = (total_group_bytes * 8 )/ total_group_elements 429markdown_content += f"- Bits per Weight (BPW) for { group } : { total_group_bpw :.4f } bits\n" 430else : 431markdown_content += f"- Bits per Weight (BPW) for { group } : undefined (no elements)\n" 432markdown_content += "\n\n" 433total_model_bytes += total_group_bytes 434total_model_elements += total_group_elements 435 436if total_model_elements > 0 : 437total_model_bpw = (total_model_bytes * 8 )/ total_model_elements 438markdown_content += f"Total BPW for { os . path . basename ( args . model ) } : { total_model_bpw :.4f } bits" 439else : 440markdown_content += f"Total BPW for { os . path . basename ( args . model ) } : undefined (no elements)" 441markdown_content )# noqa: NP100 442 443 444def main ()-> None : 445parser = argparse .ArgumentParser (description = "Dump GGUF file metadata" ) 446parser .add_argument ("model" ,type = str ,help = "GGUF format model filename" ) 447parser .add_argument ("--no-tensors" ,action = "store_true" ,help = "Don't dump tensor metadata" ) 448parser .add_argument ("--json" ,action = "store_true" ,help = "Produce JSON output" ) 449parser .add_argument ("--json-array" ,action = "store_true" ,help = "Include full array values in JSON output (long)" ) 450parser .add_argument ("--data-offset" ,action = "store_true" ,help = "Start of data offset" ) 451parser .add_argument ("--data-alignment" ,action = "store_true" ,help = "Data alignment applied globally to data field" ) 452parser .add_argument ("--markdown" ,action = "store_true" ,help = "Produce markdown output" ) 453parser .add_argument ("--verbose" ,action = "store_true" ,help = "increase output verbosity" ) 454 455args = parser .parse_args (None if len (sys .argv )> 1 else ["--help" ]) 456 457logging .basicConfig (level = logging .DEBUG if args .verbose else logging .INFO ) 458 459if not args .json and not args .markdown and not args .data_offset and not args .data_alignment : 460logger .info (f'* Loading: { args . model } ' ) 461 462reader = GGUFReader (args .model ,'r' ) 463 464if args .json : 465dump_metadata_json (reader ,args ) 466elif args .markdown : 467dump_markdown_metadata (reader ,args ) 468elif args .data_offset : 469reader .data_offset )# noqa: NP100 470elif args .data_alignment : 471reader .alignment )# noqa: NP100 472else : 473dump_metadata (reader ,args ) 474 475 476if __name__ == '__main__' : 477main ()