char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
9c142e3a2
char/aliceai-t5-moe
1#!/usr/bin/env python3 2 3import os 4import sys 5import torch 6import transformers 7import json 8import textwrap 9import numpy as np 10from pathlib import Path 11 12 13def get_model_name_from_env_path (env_path_name ): 14model_path = os .getenv (env_path_name ) 15if not model_path : 16f"Error: { env_path_name } environment variable not set" ) 17sys .exit (1 ) 18 19if not os .path .exists (model_path ): 20f"Error: Model file not found: { model_path } " ) 21sys .exit (1 ) 22 23name = os .path .basename (os .path .normpath (model_path )) 24if name .endswith (".gguf" ): 25name = name [:- 5 ] 26 27return name 28 29 30def summarize (tensor :torch .Tensor ,name :str ,max_seq :int = 3 ,max_vals :int = 3 ): 31""" 32Print a tensor in llama.cpp debug style. 33 34Supports: 35- 2D tensors (seq, hidden) 36- 3D tensors (batch, seq, hidden) 37- 4D tensors (batch, seq, heads, dim_per_head) via flattening heads × dim_per_head 38 39Shows first and last max_vals of each vector per sequence position. 40""" 41t = tensor .detach ().to (torch .float32 ).cpu () 42 43# Determine dimensions 44if t .ndim == 3 : 45_ ,s ,_ = t .shape 46elif t .ndim == 2 : 47_ ,s = 1 ,t .shape [0 ] 48t = t .unsqueeze (0 ) 49elif t .ndim == 4 : 50_ ,s ,_ ,_ = t .shape 51else : 52f"Skipping tensor due to unsupported dimensions: { t . ndim } " ) 53return 54 55ten_shape = t .shape 56 57f"ggml_debug: { name } = (f32) ... = {{ { ten_shape } }}" ) 58" [" ) 59" [" ) 60 61# Determine indices for first and last sequences 62first_indices = list (range (min (s ,max_seq ))) 63last_indices = list (range (max (0 ,s - max_seq ),s )) 64 65# Check if there's an overlap between first and last indices or if we're at the edge case of s = 2 * max_seq 66has_overlap = bool (set (first_indices )& set (last_indices ))or (max_seq * 2 == s ) 67 68# Combine indices 69if has_overlap : 70# If there's overlap, just use the combined unique indices 71indices = sorted (list (set (first_indices + last_indices ))) 72separator_index = None 73else : 74# If no overlap, we'll add a separator between first and last sequences 75indices = first_indices + last_indices 76separator_index = len (first_indices ) 77 78for i ,si in enumerate (indices ): 79# Add separator if needed 80if separator_index is not None and i == separator_index : 81" ..." ) 82 83# Extract appropriate slice 84vec = t [0 ,si ] 85if vec .ndim == 2 :# 4D case: flatten heads × dim_per_head 86flat = vec .flatten ().tolist () 87else :# 2D or 3D case 88flat = vec .tolist () 89 90# First and last slices 91first = flat [:max_vals ] 92last = flat [- max_vals :]if len (flat )>= max_vals else flat 93first_str = ", " .join (f" { v :12.4f } " for v in first ) 94last_str = ", " .join (f" { v :12.4f } " for v in last ) 95 96f" [ { first_str } , ..., { last_str } ]" ) 97 98" ]," ) 99" ]" ) 100f" sum = { t . sum (). item ():.6f } \n" ) 101 102 103def debug_hook (name ): 104def fn (_m ,input ,output ): 105if isinstance (input ,torch .Tensor ): 106summarize (input ,name + "_in" ) 107elif isinstance (input , (tuple ,list ))and len (input )> 0 and isinstance (input [0 ],torch .Tensor ): 108summarize (input [0 ],name + "_in" ) 109if isinstance (output ,torch .Tensor ): 110summarize (output ,name + "_out" ) 111elif isinstance (output , (tuple ,list ))and len (output )> 0 and isinstance (output [0 ],torch .Tensor ): 112summarize (output [0 ],name + "_out" ) 113 114return fn 115 116 117def setup_rope_debug (model_module_path :str ,function_name :str = "apply_rotary_pos_emb" ): 118""" 119Apply monkey patch to dump RoPE activations for debugging. 120 121Args: 122model_module_path: Path to the model module (e.g., "transformers.models.apertus.modeling_apertus") 123function_name: Name of the RoPE function to patch (default: "apply_rotary_pos_emb") 124 125Example: 126from utils.common import setup_rope_debug 127setup_rope_debug("transformers.models.apertus.modeling_apertus") 128""" 129import importlib 130 131# Import the module and get the original function 132module = importlib .import_module (model_module_path ) 133orig_rope = getattr (module ,function_name ) 134 135# Set torch print options for better debugging 136torch .set_printoptions (threshold = float ('inf' )) 137torch .set_printoptions (precision = 6 ,sci_mode = False ) 138 139def debug_rope (q ,k ,cos ,sin ,position_ids = None ,unsqueeze_dim = 1 ): 140# log inputs 141summarize (q ,"RoPE.q_in" ) 142summarize (k ,"RoPE.k_in" ) 143 144# call original 145q_out ,k_out = orig_rope (q ,k ,cos ,sin ,position_ids ,unsqueeze_dim ) 146 147# log outputs 148summarize (q_out ,"RoPE.q_out" ) 149summarize (k_out ,"RoPE.k_out" ) 150 151return q_out ,k_out 152 153# Patch it 154setattr (module ,function_name ,debug_rope ) 155f"RoPE debug patching applied to { model_module_path } . { function_name } " ) 156 157 158def save_output_data (data ,tokens ,prompt ,model_name ,type_suffix = "" ,output_dir = "data" ): 159""" 160Save output data (logits/embeddings), tokens, and prompt to files. 161 162Args: 163data: numpy array of floats (logits or embeddings) 164tokens: list or array of token IDs 165prompt: string containing the input prompt 166model_name: name of the model 167type_suffix: optional suffix like "-embeddings" (default: "") 168output_dir: directory to save files (default: "data") 169 170Creates the following files in output_dir: 171- pytorch-{model_name}{type_suffix}.bin 172- pytorch-{model_name}{type_suffix}.txt 173- pytorch-{model_name}{type_suffix}-prompt.txt 174- pytorch-{model_name}{type_suffix}-tokens.bin 175""" 176data_dir = Path (output_dir ) 177data_dir .mkdir (exist_ok = True ) 178base_path = data_dir / f"pytorch- { model_name } { type_suffix } " 179 180# Convert and flatten logits/embeddings 181data = data .cpu ().numpy ()if isinstance (data ,torch .Tensor )else np .asarray (data ) 182data = data .flatten ()if data .ndim > 1 else data 183 184# Save logits/embedding files 185data .astype (np .float32 ).tofile (f" { base_path } .bin" ) 186f"Data saved to { base_path } .bin" ) 187 188with open (f" { base_path } .txt" ,"w" )as f : 189f .writelines (f" { i } : { value :.6f } \n" for i ,value in enumerate (data )) 190f"Data saved to { base_path } .txt" ) 191 192# Convert and flatten tokens 193tokens = tokens .cpu ().numpy ()if isinstance (tokens ,torch .Tensor )else np .asarray (tokens ) 194tokens = tokens .flatten ()if tokens .ndim > 1 else tokens 195 196# Save token binary file 197tokens .astype (np .int32 ).tofile (f" { base_path } -tokens.bin" ) 198f"Tokens saved to { base_path } -tokens.bin" ) 199 200# Save prompt file 201with open (f" { base_path } -prompt.txt" ,"w" )as f : 202f .write (f"prompt: { prompt } \n" ) 203f .write (f"n_tokens: { len ( tokens ) } \n" ) 204f .write (f"token ids: { ', ' . join ( str ( int ( tid )) for tid in tokens ) } \n" ) 205f"Prompt saved to { base_path } -prompt.txt" ) 206 207 208def compare_tokens (original ,converted ,type_suffix = "" ,output_dir = "data" ): 209data_dir = Path (output_dir ) 210 211# Read tokens from both models 212tokens1_file = data_dir / f" { original } { type_suffix } -tokens.bin" 213tokens2_file = data_dir / f" { converted } { type_suffix } -tokens.bin" 214 215if not tokens1_file .exists (): 216f"Error: Token file not found: { tokens1_file } " ) 217return False 218 219if not tokens2_file .exists (): 220f"Error: Token file not found: { tokens2_file } " ) 221return False 222 223tokens1 = np .fromfile (tokens1_file ,dtype = np .int32 ) 224tokens2 = np .fromfile (tokens2_file ,dtype = np .int32 ) 225 226f"\nComparing tokens between:" ) 227f" Original : { original } ( { len ( tokens1 ) } tokens)" ) 228f" Converted: { converted } ( { len ( tokens2 ) } tokens)" ) 229 230if len (tokens1 )!= len (tokens2 ): 231f"\n❌ Token count mismatch: { len ( tokens1 ) } vs { len ( tokens2 ) } " ) 232return False 233 234if np .array_equal (tokens1 ,tokens2 ): 235f"\n✅ All { len ( tokens1 ) } tokens match!" ) 236return True 237 238mismatches = np .where (tokens1 != tokens2 )[0 ] 239f"\n❌ Found { len ( mismatches ) } mismatched tokens:" ) 240 241num_to_show = min (len (mismatches ),10 ) 242for idx in mismatches [:num_to_show ]: 243f" Position { idx } : { tokens1 [ idx ] } vs { tokens2 [ idx ] } " ) 244 245if len (mismatches )> num_to_show : 246f" ... and { len ( mismatches ) - num_to_show } more mismatches" ) 247 248return False 249 250 251def show_version_warning (current_version ,model_version ): 252if not model_version : 253return False 254 255try : 256from packaging .version import parse ,InvalidVersion 257try : 258return parse (current_version )< parse (model_version ) 259except InvalidVersion : 260return current_version != model_version 261except ImportError : 262return current_version != model_version 263 264def get_model_transformers_version (model_path ): 265if not model_path : 266return None 267 268config_path = Path (model_path )/ "config.json" 269if not config_path .is_file (): 270return None 271 272try : 273with open (config_path ,"r" ,encoding = "utf-8" )as f : 274config = json .load (f ) 275return config .get ("transformers_version" ) 276except (IOError ,json .JSONDecodeError )as e : 277f"Warning: Could not read or parse { config_path } : { e } " ,file = sys .stderr ) 278return None 279 280def exit_with_warning (message ,model_path ): 281message ) 282 283if model_path and transformers is not None : 284model_transformers_version = get_model_transformers_version (model_path ) 285transformers_version = transformers .__version__ 286if show_version_warning (transformers_version ,model_transformers_version ): 287warning_message = f""" 288===================================================================== 289Verification failure might be due to a transformers version mismatch: 290 291Current transformers version: { transformers_version } 292Model's required version : { model_transformers_version } 293 294Consider installing the version specified by the model's config: 295pip install transformers== { model_transformers_version } 296===================================================================== 297""" 298textwrap .dedent (warning_message )) 299sys .exit (1 )