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 numpy 9import enum 10from pathlib import Path 11from typing import Any ,Optional ,Tuple ,Type 12import warnings 13 14import numpy as np 15from PySide6 .QtWidgets import ( 16QApplication ,QMainWindow ,QWidget ,QVBoxLayout ,QHBoxLayout , 17QPushButton ,QLabel ,QLineEdit ,QFileDialog ,QTableWidget , 18QTableWidgetItem ,QComboBox ,QMessageBox ,QTabWidget , 19QTextEdit ,QFormLayout , 20QHeaderView ,QDialog ,QDialogButtonBox 21) 22from PySide6 .QtCore import Qt 23 24# Necessary to load the local gguf package 25if "NO_LOCAL_GGUF" not in os .environ and (Path (__file__ ).parent .parent .parent .parent / 'gguf-py' ).exists (): 26sys .path .insert (0 ,str (Path (__file__ ).parent .parent .parent )) 27 28import gguf 29from gguf import GGUFReader ,GGUFWriter ,GGUFValueType ,ReaderField 30from gguf .constants import TokenType ,RopeScalingType ,PoolingType ,GGMLQuantizationType 31 32logger = logging .getLogger ("gguf-editor-gui" ) 33 34# Map of key names to enum types for automatic enum interpretation 35KEY_TO_ENUM_TYPE = { 36gguf .Keys .Tokenizer .TOKEN_TYPE :TokenType , 37gguf .Keys .Rope .SCALING_TYPE :RopeScalingType , 38gguf .Keys .LLM .POOLING_TYPE :PoolingType , 39gguf .Keys .General .FILE_TYPE :GGMLQuantizationType , 40} 41 42# Define the tokenizer keys that should be edited together 43TOKENIZER_LINKED_KEYS = [ 44gguf .Keys .Tokenizer .LIST , 45gguf .Keys .Tokenizer .TOKEN_TYPE , 46gguf .Keys .Tokenizer .SCORES 47] 48 49 50class TokenizerEditorDialog (QDialog ): 51def __init__ (self ,tokens ,token_types ,scores ,parent = None ): 52super ().__init__ (parent ) 53self .setWindowTitle ("Edit Tokenizer Data" ) 54self .resize (900 ,600 ) 55 56self .tokens = tokens .copy ()if tokens else [] 57self .token_types = token_types .copy ()if token_types else [] 58self .scores = scores .copy ()if scores else [] 59 60# Ensure all arrays have the same length 61max_len = max (len (self .tokens ),len (self .token_types ),len (self .scores )) 62if len (self .tokens )< max_len : 63self .tokens .extend (["" ]* (max_len - len (self .tokens ))) 64if len (self .token_types )< max_len : 65self .token_types .extend ([0 ]* (max_len - len (self .token_types ))) 66if len (self .scores )< max_len : 67self .scores .extend ([0.0 ]* (max_len - len (self .scores ))) 68 69layout = QVBoxLayout (self ) 70 71# Add filter controls 72filter_layout = QHBoxLayout () 73filter_layout .addWidget (QLabel ("Filter:" )) 74self .filter_edit = QLineEdit () 75self .filter_edit .setPlaceholderText ("Type to filter tokens..." ) 76self .filter_edit .textChanged .connect (self .apply_filter ) 77filter_layout .addWidget (self .filter_edit ) 78 79# Add page controls 80self .page_size = 100 # Show 100 items per page 81self .current_page = 0 82self .total_pages = max (1 , (len (self .tokens )+ self .page_size - 1 )// self .page_size ) 83 84self .page_label = QLabel (f"Page 1 of { self . total_pages } " ) 85filter_layout .addWidget (self .page_label ) 86 87prev_page = QPushButton ("Previous" ) 88prev_page .clicked .connect (self .previous_page ) 89filter_layout .addWidget (prev_page ) 90 91next_page = QPushButton ("Next" ) 92next_page .clicked .connect (self .next_page ) 93filter_layout .addWidget (next_page ) 94 95layout .addLayout (filter_layout ) 96 97# Tokenizer data table 98self .tokens_table = QTableWidget () 99self .tokens_table .setColumnCount (4 ) 100self .tokens_table .setHorizontalHeaderLabels (["Index" ,"Token" ,"Type" ,"Score" ]) 101self .tokens_table .horizontalHeader ().setSectionResizeMode (0 ,QHeaderView .ResizeMode .ResizeToContents ) 102self .tokens_table .horizontalHeader ().setSectionResizeMode (1 ,QHeaderView .ResizeMode .Stretch ) 103self .tokens_table .horizontalHeader ().setSectionResizeMode (2 ,QHeaderView .ResizeMode .ResizeToContents ) 104self .tokens_table .horizontalHeader ().setSectionResizeMode (3 ,QHeaderView .ResizeMode .ResizeToContents ) 105 106layout .addWidget (self .tokens_table ) 107 108# Controls 109controls_layout = QHBoxLayout () 110 111add_button = QPushButton ("Add Token" ) 112add_button .clicked .connect (self .add_token ) 113controls_layout .addWidget (add_button ) 114 115remove_button = QPushButton ("Remove Selected" ) 116remove_button .clicked .connect (self .remove_selected ) 117controls_layout .addWidget (remove_button ) 118 119controls_layout .addStretch () 120 121layout .addLayout (controls_layout ) 122 123# Buttons 124buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 125buttons .accepted .connect (self .accept ) 126buttons .rejected .connect (self .reject ) 127layout .addWidget (buttons ) 128 129# Initialize the filtered values 130self .filtered_indices = list (range (len (self .tokens ))) 131 132# Load data for the first page 133self .load_page () 134 135def apply_filter (self ): 136"""Filter the tokens based on the search text.""" 137filter_text = self .filter_edit .text ().lower () 138 139if not filter_text : 140# No filter, show all values 141self .filtered_indices = list (range (len (self .tokens ))) 142else : 143# Apply filter 144self .filtered_indices = [] 145for i ,token in enumerate (self .tokens ): 146if filter_text in str (token ).lower (): 147self .filtered_indices .append (i ) 148 149# Reset to first page and reload 150self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 151self .current_page = 0 152self .page_label .setText (f"Page 1 of { self . total_pages } " ) 153self .load_page () 154 155def previous_page (self ): 156"""Go to the previous page of results.""" 157if self .current_page > 0 : 158self .current_page -= 1 159self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 160self .load_page () 161 162def next_page (self ): 163"""Go to the next page of results.""" 164if self .current_page < self .total_pages - 1 : 165self .current_page += 1 166self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 167self .load_page () 168 169def load_page (self ): 170"""Load the current page of tokenizer data.""" 171self .tokens_table .setRowCount (0 )# Clear the table 172 173# Calculate start and end indices for the current page 174start_idx = self .current_page * self .page_size 175end_idx = min (start_idx + self .page_size ,len (self .filtered_indices )) 176 177# Pre-allocate rows for better performance 178self .tokens_table .setRowCount (end_idx - start_idx ) 179 180for row ,i in enumerate (range (start_idx ,end_idx )): 181orig_idx = self .filtered_indices [i ] 182 183# Index 184index_item = QTableWidgetItem (str (orig_idx )) 185index_item .setData (Qt .ItemDataRole .UserRole ,orig_idx )# Store original index 186index_item .setFlags (index_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 187self .tokens_table .setItem (row ,0 ,index_item ) 188 189# Token 190token_item = QTableWidgetItem (str (self .tokens [orig_idx ])) 191self .tokens_table .setItem (row ,1 ,token_item ) 192 193# Token Type 194token_type = self .token_types [orig_idx ]if orig_idx < len (self .token_types )else 0 195try : 196enum_val = TokenType (token_type ) 197display_text = f" { enum_val . name } ( { token_type } )" 198except (ValueError ,KeyError ): 199display_text = f"Unknown ( { token_type } )" 200 201type_item = QTableWidgetItem (display_text ) 202type_item .setData (Qt .ItemDataRole .UserRole ,token_type ) 203 204# Make type cell editable with a double-click handler 205type_item .setFlags (type_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 206self .tokens_table .setItem (row ,2 ,type_item ) 207 208# Score 209score = self .scores [orig_idx ]if orig_idx < len (self .scores )else 0.0 210score_item = QTableWidgetItem (str (score )) 211self .tokens_table .setItem (row ,3 ,score_item ) 212 213# Connect double-click handler for token type cells 214self .tokens_table .cellDoubleClicked .connect (self .handle_cell_double_click ) 215 216def handle_cell_double_click (self ,row ,column ): 217"""Handle double-click on a cell, specifically for token type editing.""" 218if column == 2 :# Token Type column 219orig_item = self .tokens_table .item (row ,0 ) 220if orig_item : 221orig_idx = orig_item .data (Qt .ItemDataRole .UserRole ) 222self .edit_token_type (row ,orig_idx ) 223 224def edit_token_type (self ,row ,orig_idx ): 225"""Edit a token type using a dialog with a dropdown of all enum options.""" 226current_value = self .token_types [orig_idx ]if orig_idx < len (self .token_types )else 0 227 228# Create a dialog with enum options 229dialog = QDialog (self ) 230dialog .setWindowTitle ("Select Token Type" ) 231layout = QVBoxLayout (dialog ) 232 233combo = QComboBox () 234for enum_val in TokenType : 235combo .addItem (f" { enum_val . name } ( { enum_val . value } )" ,enum_val .value ) 236 237# Set current value 238try : 239if isinstance (current_value ,int ): 240enum_val = TokenType (current_value ) 241combo .setCurrentText (f" { enum_val . name } ( { current_value } )" ) 242except (ValueError ,KeyError ): 243pass 244 245layout .addWidget (combo ) 246 247buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 248buttons .accepted .connect (dialog .accept ) 249buttons .rejected .connect (dialog .reject ) 250layout .addWidget (buttons ) 251 252if dialog .exec ()== QDialog .DialogCode .Accepted : 253# Get the selected value 254new_value = combo .currentData () 255enum_val = TokenType (new_value ) 256display_text = f" { enum_val . name } ( { new_value } )" 257 258# Update the display 259type_item = self .tokens_table .item (row ,2 ) 260if type_item : 261type_item .setText (display_text ) 262type_item .setData (Qt .ItemDataRole .UserRole ,new_value ) 263 264# Update the actual value 265self .token_types [orig_idx ]= new_value 266 267def add_token (self ): 268"""Add a new token to the end of the list.""" 269# Add to the end of the arrays 270self .tokens .append ("" ) 271self .token_types .append (0 )# Default to normal token 272self .scores .append (0.0 ) 273 274orig_idx = len (self .tokens )- 1 275 276# Add to filtered indices if it matches the current filter 277filter_text = self .filter_edit .text ().lower () 278if not filter_text or filter_text in "" : 279self .filtered_indices .append (orig_idx ) 280 281# Update pagination 282self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 283 284# Go to the last page to show the new item 285self .current_page = self .total_pages - 1 286self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 287 288# Reload the page 289self .load_page () 290 291def remove_selected (self ): 292"""Remove selected tokens from all arrays.""" 293selected_rows = [] 294for item in self .tokens_table .selectedItems (): 295row = item .row () 296if row not in selected_rows : 297selected_rows .append (row ) 298 299if not selected_rows : 300return 301 302# Get original indices in descending order to avoid index shifting 303orig_indices = [] 304for row in selected_rows : 305orig_item = self .tokens_table .item (row ,0 ) 306if orig_item : 307orig_indices .append (orig_item .data (Qt .ItemDataRole .UserRole )) 308orig_indices .sort (reverse = True ) 309 310# Remove from all arrays 311for idx in orig_indices : 312if idx < len (self .tokens ): 313del self .tokens [idx ] 314if idx < len (self .token_types ): 315del self .token_types [idx ] 316if idx < len (self .scores ): 317del self .scores [idx ] 318 319# Rebuild filtered_indices 320self .filtered_indices = [] 321filter_text = self .filter_edit .text ().lower () 322 323for i ,token in enumerate (self .tokens ): 324if not filter_text or filter_text in str (token ).lower (): 325self .filtered_indices .append (i ) 326 327# Update pagination 328self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 329self .current_page = min (self .current_page ,self .total_pages - 1 ) 330self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 331 332# Reload the page 333self .load_page () 334 335def get_data (self ): 336"""Return the edited tokenizer data.""" 337return self .tokens ,self .token_types ,self .scores 338 339 340class ArrayEditorDialog (QDialog ): 341def __init__ (self ,array_values ,element_type ,key = None ,parent = None ): 342super ().__init__ (parent ) 343self .setWindowTitle ("Edit Array Values" ) 344self .resize (700 ,500 ) 345 346self .array_values = array_values 347self .element_type = element_type 348self .key = key 349 350# Get enum type for this array if applicable 351self .enum_type = None 352if key in KEY_TO_ENUM_TYPE and element_type == GGUFValueType .INT32 : 353self .enum_type = KEY_TO_ENUM_TYPE [key ] 354 355layout = QVBoxLayout (self ) 356 357# Add enum type information if applicable 358if self .enum_type is not None : 359enum_info_layout = QHBoxLayout () 360enum_label = QLabel (f"Editing { self . enum_type . __name__ } values:" ) 361enum_info_layout .addWidget (enum_label ) 362 363# Add a legend for the enum values 364enum_values = ", " .join ([f" { e . name } = { e . value } " for e in self .enum_type ]) 365enum_values_label = QLabel (f"Available values: { enum_values } " ) 366enum_values_label .setWordWrap (True ) 367enum_info_layout .addWidget (enum_values_label ,1 ) 368 369layout .addLayout (enum_info_layout ) 370 371# Add search/filter controls 372filter_layout = QHBoxLayout () 373filter_layout .addWidget (QLabel ("Filter:" )) 374self .filter_edit = QLineEdit () 375self .filter_edit .setPlaceholderText ("Type to filter values..." ) 376self .filter_edit .textChanged .connect (self .apply_filter ) 377filter_layout .addWidget (self .filter_edit ) 378 379# Add page controls for large arrays 380self .page_size = 100 # Show 100 items per page 381self .current_page = 0 382self .total_pages = max (1 , (len (array_values )+ self .page_size - 1 )// self .page_size ) 383 384self .page_label = QLabel (f"Page 1 of { self . total_pages } " ) 385filter_layout .addWidget (self .page_label ) 386 387prev_page = QPushButton ("Previous" ) 388prev_page .clicked .connect (self .previous_page ) 389filter_layout .addWidget (prev_page ) 390 391next_page = QPushButton ("Next" ) 392next_page .clicked .connect (self .next_page ) 393filter_layout .addWidget (next_page ) 394 395layout .addLayout (filter_layout ) 396 397# Array items table 398self .items_table = QTableWidget () 399 400# Set up columns based on whether we have an enum type 401if self .enum_type is not None : 402self .items_table .setColumnCount (3 ) 403self .items_table .setHorizontalHeaderLabels (["Index" ,"Value" ,"Actions" ]) 404self .items_table .horizontalHeader ().setSectionResizeMode (0 ,QHeaderView .ResizeMode .ResizeToContents ) 405self .items_table .horizontalHeader ().setSectionResizeMode (1 ,QHeaderView .ResizeMode .Stretch ) 406self .items_table .horizontalHeader ().setSectionResizeMode (2 ,QHeaderView .ResizeMode .ResizeToContents ) 407else : 408self .items_table .setColumnCount (2 ) 409self .items_table .setHorizontalHeaderLabels (["Index" ,"Value" ]) 410self .items_table .horizontalHeader ().setSectionResizeMode (0 ,QHeaderView .ResizeMode .ResizeToContents ) 411self .items_table .horizontalHeader ().setSectionResizeMode (1 ,QHeaderView .ResizeMode .Stretch ) 412 413layout .addWidget (self .items_table ) 414 415# Controls 416controls_layout = QHBoxLayout () 417 418add_button = QPushButton ("Add Item" ) 419add_button .clicked .connect (self .add_item ) 420controls_layout .addWidget (add_button ) 421 422remove_button = QPushButton ("Remove Selected" ) 423remove_button .clicked .connect (self .remove_selected ) 424controls_layout .addWidget (remove_button ) 425 426# Add bulk edit button for enum arrays 427if self .enum_type is not None : 428bulk_edit_button = QPushButton ("Bulk Edit Selected" ) 429bulk_edit_button .clicked .connect (self .bulk_edit_selected ) 430controls_layout .addWidget (bulk_edit_button ) 431 432controls_layout .addStretch () 433 434layout .addLayout (controls_layout ) 435 436# Buttons 437buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 438buttons .accepted .connect (self .accept ) 439buttons .rejected .connect (self .reject ) 440layout .addWidget (buttons ) 441 442# Initialize the filtered values 443self .filtered_indices = list (range (len (self .array_values ))) 444 445# Load array values for the first page 446self .load_page () 447 448def apply_filter (self ): 449"""Filter the array values based on the search text.""" 450filter_text = self .filter_edit .text ().lower () 451 452if not filter_text : 453# No filter, show all values 454self .filtered_indices = list (range (len (self .array_values ))) 455else : 456# Apply filter 457self .filtered_indices = [] 458for i ,value in enumerate (self .array_values ): 459# For enum values, search in both name and value 460if self .enum_type is not None and isinstance (value ,int ): 461try : 462enum_val = self .enum_type (value ) 463display_text = f" { enum_val . name } ( { value } )" .lower () 464if filter_text in display_text : 465self .filtered_indices .append (i ) 466except (ValueError ,KeyError ): 467# If not a valid enum value, just check the raw value 468if filter_text in str (value ).lower (): 469self .filtered_indices .append (i ) 470else : 471# For non-enum values, just check the string representation 472if filter_text in str (value ).lower (): 473self .filtered_indices .append (i ) 474 475# Reset to first page and reload 476self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 477self .current_page = 0 478self .page_label .setText (f"Page 1 of { self . total_pages } " ) 479self .load_page () 480 481def previous_page (self ): 482"""Go to the previous page of results.""" 483if self .current_page > 0 : 484self .current_page -= 1 485self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 486self .load_page () 487 488def next_page (self ): 489"""Go to the next page of results.""" 490if self .current_page < self .total_pages - 1 : 491self .current_page += 1 492self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 493self .load_page () 494 495def load_page (self ): 496"""Load the current page of array values.""" 497self .items_table .setRowCount (0 )# Clear the table 498 499# Calculate start and end indices for the current page 500start_idx = self .current_page * self .page_size 501end_idx = min (start_idx + self .page_size ,len (self .filtered_indices )) 502 503# Pre-allocate rows for better performance 504self .items_table .setRowCount (end_idx - start_idx ) 505 506for row ,i in enumerate (range (start_idx ,end_idx )): 507orig_idx = self .filtered_indices [i ] 508value = self .array_values [orig_idx ] 509 510# Index 511index_item = QTableWidgetItem (str (orig_idx )) 512index_item .setData (Qt .ItemDataRole .UserRole ,orig_idx )# Store original index 513index_item .setFlags (index_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 514self .items_table .setItem (row ,0 ,index_item ) 515 516# Value 517if self .enum_type is not None : 518# Display enum value and name 519try : 520if isinstance (value , (int ,numpy .signedinteger )): 521enum_val = self .enum_type (value ) 522display_text = f" { enum_val . name } ( { value } )" 523else : 524display_text = str (value ) 525except (ValueError ,KeyError ): 526display_text = f"Unknown ( { value } )" 527 528# Store the enum value in the item 529value_item = QTableWidgetItem (display_text ) 530value_item .setData (Qt .ItemDataRole .UserRole ,value ) 531value_item .setFlags (value_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 532self .items_table .setItem (row ,1 ,value_item ) 533 534# Add an edit button in a separate column 535edit_button = QPushButton ("Edit" ) 536edit_button .setProperty ("row" ,row ) 537edit_button .clicked .connect (self .edit_array_enum_value ) 538 539# Create a widget to hold the button 540button_widget = QWidget () 541button_layout = QHBoxLayout (button_widget ) 542button_layout .setContentsMargins (2 ,2 ,2 ,2 ) 543button_layout .addWidget (edit_button ) 544button_layout .addStretch () 545 546self .items_table .setCellWidget (row ,2 ,button_widget ) 547else : 548value_item = QTableWidgetItem (str (value )) 549self .items_table .setItem (row ,1 ,value_item ) 550 551def edit_array_enum_value (self ): 552"""Handle editing an enum value in the array editor.""" 553button = self .sender () 554row = button .property ("row" ) 555 556# Get the original index from the table item 557orig_item = self .items_table .item (row ,0 ) 558new_item = self .items_table .item (row ,1 ) 559if orig_item and new_item and self .enum_type and self .edit_enum_value (row ,self .enum_type ): 560orig_idx = orig_item .data (Qt .ItemDataRole .UserRole ) 561new_value = new_item .data (Qt .ItemDataRole .UserRole ) 562# Update the stored value in the array 563if isinstance (new_value , (int ,float ,str ,bool )): 564self .array_values [orig_idx ]= new_value 565 566def bulk_edit_selected (self ): 567"""Edit multiple enum values at once.""" 568if not self .enum_type : 569return 570 571selected_rows = set () 572for item in self .items_table .selectedItems (): 573selected_rows .add (item .row ()) 574 575if not selected_rows : 576QMessageBox .information (self ,"No Selection" ,"Please select at least one row to edit." ) 577return 578 579# Create a dialog with enum options 580dialog = QDialog (self ) 581dialog .setWindowTitle (f"Bulk Edit { self . enum_type . __name__ } Values" ) 582layout = QVBoxLayout (dialog ) 583 584layout .addWidget (QLabel (f"Set { len ( selected_rows ) } selected items to:" )) 585 586combo = QComboBox () 587for enum_val in self .enum_type : 588combo .addItem (f" { enum_val . name } ( { enum_val . value } )" ,enum_val .value ) 589 590layout .addWidget (combo ) 591 592buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 593buttons .accepted .connect (dialog .accept ) 594buttons .rejected .connect (dialog .reject ) 595layout .addWidget (buttons ) 596 597if dialog .exec ()== QDialog .DialogCode .Accepted : 598# Get the selected value 599new_value = combo .currentData () 600enum_val = self .enum_type (new_value ) 601display_text = f" { enum_val . name } ( { new_value } )" 602 603# Update all selected rows 604for row in selected_rows : 605orig_item = self .items_table .item (row ,0 ) 606new_item = self .items_table .item (row ,1 ) 607if orig_item and new_item : 608orig_idx = orig_item .data (Qt .ItemDataRole .UserRole ) 609self .array_values [orig_idx ]= new_value 610 611# Update the display 612new_item .setText (display_text ) 613new_item .setData (Qt .ItemDataRole .UserRole ,new_value ) 614 615def add_item (self ): 616# Add to the end of the array 617orig_idx = len (self .array_values ) 618 619# Add default value based on type 620if self .enum_type is not None : 621# Default to first enum value 622default_value = list (self .enum_type )[0 ].value 623self .array_values .append (default_value ) 624else : 625if self .element_type == GGUFValueType .STRING : 626self .array_values .append ("" ) 627else : 628self .array_values .append (0 ) 629 630# Add to filtered indices if it matches the current filter 631self .filtered_indices .append (orig_idx ) 632 633# Update pagination 634self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 635 636# Go to the last page to show the new item 637self .current_page = self .total_pages - 1 638self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 639 640# Reload the page 641self .load_page () 642 643def remove_selected (self ): 644selected_rows = [] 645for item in self .items_table .selectedItems (): 646row = item .row () 647if row not in selected_rows : 648selected_rows .append (row ) 649 650if not selected_rows : 651return 652 653# Get original indices in descending order to avoid index shifting 654orig_indices = list () 655for row in selected_rows : 656orig_item = self .items_table .item (row ,0 ) 657if orig_item : 658orig_indices .append (orig_item .data (Qt .ItemDataRole .UserRole )) 659orig_indices .sort (reverse = True ) 660 661# Remove from array_values 662for idx in orig_indices : 663del self .array_values [idx ] 664 665# Rebuild filtered_indices 666self .filtered_indices = [] 667filter_text = self .filter_edit .text ().lower () 668 669for i ,value in enumerate (self .array_values ): 670if not filter_text : 671self .filtered_indices .append (i ) 672else : 673# Apply filter 674if self .enum_type is not None and isinstance (value ,int ): 675try : 676enum_val = self .enum_type (value ) 677display_text = f" { enum_val . name } ( { value } )" .lower () 678if filter_text in display_text : 679self .filtered_indices .append (i ) 680except (ValueError ,KeyError ): 681if filter_text in str (value ).lower (): 682self .filtered_indices .append (i ) 683else : 684if filter_text in str (value ).lower (): 685self .filtered_indices .append (i ) 686 687# Update pagination 688self .total_pages = max (1 , (len (self .filtered_indices )+ self .page_size - 1 )// self .page_size ) 689self .current_page = min (self .current_page ,self .total_pages - 1 ) 690self .page_label .setText (f"Page { self . current_page + 1 } of { self . total_pages } " ) 691 692# Reload the page 693self .load_page () 694 695def edit_enum_value (self ,row :int ,enum_type :Type [enum .Enum ]): 696"""Edit an enum value using a dialog with a dropdown of all enum options.""" 697# Get the original index from the table item 698orig_item = self .items_table .item (row ,0 ) 699if orig_item : 700orig_idx = orig_item .data (Qt .ItemDataRole .UserRole ) 701else : 702return 703current_value = self .array_values [orig_idx ] 704 705# Create a dialog with enum options 706dialog = QDialog (self ) 707dialog .setWindowTitle (f"Select { enum_type . __name__ } Value" ) 708layout = QVBoxLayout (dialog ) 709 710# Add description 711description = QLabel (f"Select a { enum_type . __name__ } value:" ) 712layout .addWidget (description ) 713 714# Use a combo box for quick selection 715combo = QComboBox () 716for enum_val in enum_type : 717combo .addItem (f" { enum_val . name } ( { enum_val . value } )" ,enum_val .value ) 718 719# Set current value 720try : 721if isinstance (current_value ,int ): 722enum_val = enum_type (current_value ) 723combo .setCurrentText (f" { enum_val . name } ( { current_value } )" ) 724except (ValueError ,KeyError ): 725pass 726 727layout .addWidget (combo ) 728 729buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 730buttons .accepted .connect (dialog .accept ) 731buttons .rejected .connect (dialog .reject ) 732layout .addWidget (buttons ) 733 734if dialog .exec ()== QDialog .DialogCode .Accepted : 735# Update the value display and stored data 736new_value = combo .currentData () 737enum_val = enum_type (new_value ) 738display_text = f" { enum_val . name } ( { new_value } )" 739 740new_item = self .items_table .item (row ,1 ) 741if new_item : 742new_item .setText (display_text ) 743new_item .setData (Qt .ItemDataRole .UserRole ,new_value ) 744 745# Update the actual array value 746self .array_values [orig_idx ]= new_value 747return True 748return False 749 750def get_array_values (self ): 751# The array_values list is kept up-to-date as edits are made 752return self .array_values 753 754 755class AddMetadataDialog (QDialog ): 756def __init__ (self ,parent = None ): 757super ().__init__ (parent ) 758self .setWindowTitle ("Add Metadata" ) 759self .resize (400 ,200 ) 760 761layout = QVBoxLayout (self ) 762 763form_layout = QFormLayout () 764 765self .key_edit = QLineEdit () 766form_layout .addRow ("Key:" ,self .key_edit ) 767 768self .type_combo = QComboBox () 769for value_type in GGUFValueType : 770if value_type != GGUFValueType .ARRAY :# Skip array type for simplicity 771self .type_combo .addItem (value_type .name ,value_type ) 772form_layout .addRow ("Type:" ,self .type_combo ) 773 774self .value_edit = QTextEdit () 775form_layout .addRow ("Value:" ,self .value_edit ) 776 777layout .addLayout (form_layout ) 778 779buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 780buttons .accepted .connect (self .accept ) 781buttons .rejected .connect (self .reject ) 782layout .addWidget (buttons ) 783 784def get_data (self )-> Tuple [str ,GGUFValueType ,Any ]: 785key = self .key_edit .text () 786value_type = self .type_combo .currentData () 787value_text = self .value_edit .toPlainText () 788 789# Convert value based on type 790if value_type == GGUFValueType .UINT8 : 791value = np .uint8 (int (value_text )) 792elif value_type == GGUFValueType .INT8 : 793value = np .int8 (int (value_text )) 794elif value_type == GGUFValueType .UINT16 : 795value = np .uint16 (int (value_text )) 796elif value_type == GGUFValueType .INT16 : 797value = np .int16 (int (value_text )) 798elif value_type == GGUFValueType .UINT32 : 799value = np .uint32 (int (value_text )) 800elif value_type == GGUFValueType .INT32 : 801value = np .int32 (int (value_text )) 802elif value_type == GGUFValueType .FLOAT32 : 803value = np .float32 (float (value_text )) 804elif value_type == GGUFValueType .BOOL : 805value = value_text .lower ()in ('true' ,'yes' ,'1' ) 806elif value_type == GGUFValueType .STRING : 807value = value_text 808else : 809value = value_text 810 811return key ,value_type ,value 812 813 814class GGUFEditorWindow (QMainWindow ): 815def __init__ (self ): 816super ().__init__ () 817 818self .setWindowTitle ("GGUF Editor" ) 819self .resize (1000 ,800 ) 820 821self .current_file = None 822self .reader = None 823self .modified = False 824self .metadata_changes = {}# Store changes to apply when saving 825self .metadata_to_remove = set ()# Store keys to remove when saving 826self .on_metadata_changed_is_connected = False 827 828self .setup_ui () 829 830def setup_ui (self ): 831central_widget = QWidget () 832self .setCentralWidget (central_widget ) 833 834main_layout = QVBoxLayout (central_widget ) 835 836# File controls 837file_layout = QHBoxLayout () 838 839self .file_path_edit = QLineEdit () 840self .file_path_edit .setReadOnly (True ) 841file_layout .addWidget (self .file_path_edit ) 842 843open_button = QPushButton ("Open GGUF" ) 844open_button .clicked .connect (self .open_file ) 845file_layout .addWidget (open_button ) 846 847save_button = QPushButton ("Save As..." ) 848save_button .clicked .connect (self .save_file ) 849file_layout .addWidget (save_button ) 850 851main_layout .addLayout (file_layout ) 852 853# Tabs for different views 854self .tabs = QTabWidget () 855 856# Metadata tab 857self .metadata_tab = QWidget () 858metadata_layout = QVBoxLayout (self .metadata_tab ) 859 860# Metadata table 861self .metadata_table = QTableWidget () 862self .metadata_table .setColumnCount (4 ) 863self .metadata_table .setHorizontalHeaderLabels (["Key" ,"Type" ,"Value" ,"Actions" ]) 864self .metadata_table .horizontalHeader ().setSectionResizeMode (0 ,QHeaderView .ResizeMode .Stretch ) 865self .metadata_table .horizontalHeader ().setSectionResizeMode (1 ,QHeaderView .ResizeMode .ResizeToContents ) 866self .metadata_table .horizontalHeader ().setSectionResizeMode (2 ,QHeaderView .ResizeMode .Stretch ) 867self .metadata_table .horizontalHeader ().setSectionResizeMode (3 ,QHeaderView .ResizeMode .ResizeToContents ) 868metadata_layout .addWidget (self .metadata_table ) 869 870# Metadata controls 871metadata_controls = QHBoxLayout () 872 873add_metadata_button = QPushButton ("Add Metadata" ) 874add_metadata_button .clicked .connect (self .add_metadata ) 875metadata_controls .addWidget (add_metadata_button ) 876 877metadata_controls .addStretch () 878 879metadata_layout .addLayout (metadata_controls ) 880 881# Tensors tab 882self .tensors_tab = QWidget () 883tensors_layout = QVBoxLayout (self .tensors_tab ) 884 885self .tensors_table = QTableWidget () 886self .tensors_table .setColumnCount (5 ) 887self .tensors_table .setHorizontalHeaderLabels (["Name" ,"Type" ,"Shape" ,"Elements" ,"Size (bytes)" ]) 888self .tensors_table .horizontalHeader ().setSectionResizeMode (0 ,QHeaderView .ResizeMode .Stretch ) 889self .tensors_table .horizontalHeader ().setSectionResizeMode (1 ,QHeaderView .ResizeMode .ResizeToContents ) 890self .tensors_table .horizontalHeader ().setSectionResizeMode (2 ,QHeaderView .ResizeMode .ResizeToContents ) 891self .tensors_table .horizontalHeader ().setSectionResizeMode (3 ,QHeaderView .ResizeMode .ResizeToContents ) 892self .tensors_table .horizontalHeader ().setSectionResizeMode (4 ,QHeaderView .ResizeMode .ResizeToContents ) 893tensors_layout .addWidget (self .tensors_table ) 894 895# Add tabs to tab widget 896self .tabs .addTab (self .metadata_tab ,"Metadata" ) 897self .tabs .addTab (self .tensors_tab ,"Tensors" ) 898 899main_layout .addWidget (self .tabs ) 900 901# Status bar 902self .statusBar ().showMessage ("Ready" ) 903 904def load_file (self ,file_path ): 905"""Load a GGUF file by path""" 906try : 907self .statusBar ().showMessage (f"Loading { file_path } ..." ) 908QApplication .processEvents () 909 910self .reader = GGUFReader (file_path ,'r' ) 911self .current_file = file_path 912self .file_path_edit .setText (file_path ) 913 914self .load_metadata () 915self .load_tensors () 916 917self .metadata_changes = {} 918self .metadata_to_remove = set () 919self .modified = False 920 921self .statusBar ().showMessage (f"Loaded { file_path } " ) 922return True 923except Exception as e : 924QMessageBox .critical (self ,"Error" ,f"Failed to open file: { str ( e ) } " ) 925self .statusBar ().showMessage ("Error loading file" ) 926return False 927 928def open_file (self ): 929file_path ,_ = QFileDialog .getOpenFileName ( 930self ,"Open GGUF File" ,"" ,"GGUF Files (*.gguf);;All Files (*)" 931 ) 932 933if not file_path : 934return 935 936self .load_file (file_path ) 937 938def load_metadata (self ): 939self .metadata_table .setRowCount (0 ) 940 941if not self .reader : 942return 943 944# Disconnect to prevent triggering during loading 945if self .on_metadata_changed_is_connected : 946with warnings .catch_warnings (): 947warnings .filterwarnings ('ignore' ) 948self .metadata_table .itemChanged .disconnect (self .on_metadata_changed ) 949self .on_metadata_changed_is_connected = False 950 951for i , (key ,field )in enumerate (self .reader .fields .items ()): 952self .metadata_table .insertRow (i ) 953 954# Key 955key_item = QTableWidgetItem (key ) 956key_item .setFlags (key_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 957self .metadata_table .setItem (i ,0 ,key_item ) 958 959# Type 960if not field .types : 961type_str = "N/A" 962elif field .types [0 ]== GGUFValueType .ARRAY : 963nest_count = len (field .types )- 1 964element_type = field .types [- 1 ].name 965# Check if this is an enum array 966enum_type = self .get_enum_for_key (key ) 967if enum_type is not None and field .types [- 1 ]== GGUFValueType .INT32 : 968element_type = enum_type .__name__ 969type_str = '[' * nest_count + element_type + ']' * nest_count 970else : 971type_str = str (field .types [0 ].name ) 972# Check if this is an enum field 973enum_type = self .get_enum_for_key (key ) 974if enum_type is not None and field .types [0 ]== GGUFValueType .INT32 : 975type_str = enum_type .__name__ 976 977type_item = QTableWidgetItem (type_str ) 978type_item .setFlags (type_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 979self .metadata_table .setItem (i ,1 ,type_item ) 980 981# Value 982value_str = self .format_field_value (field ) 983value_item = QTableWidgetItem (value_str ) 984 985# Make only simple values editable 986if len (field .types )== 1 and field .types [0 ]!= GGUFValueType .ARRAY : 987value_item .setFlags (value_item .flags ()| Qt .ItemFlag .ItemIsEditable ) 988else : 989value_item .setFlags (value_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 990 991self .metadata_table .setItem (i ,2 ,value_item ) 992 993# Actions 994actions_widget = QWidget () 995actions_layout = QHBoxLayout (actions_widget ) 996actions_layout .setContentsMargins (2 ,2 ,2 ,2 ) 997 998# Add Edit button for arrays and enum fields 999if field .types and field .types [0 ]== GGUFValueType .ARRAY : 1000edit_button = QPushButton ("Edit" ) 1001edit_button .setProperty ("row" ,i ) 1002edit_button .setProperty ("key" ,key ) 1003edit_button .clicked .connect (self .edit_array_metadata ) 1004actions_layout .addWidget (edit_button ) 1005 1006# Add special label for tokenizer linked fields 1007if key in TOKENIZER_LINKED_KEYS : 1008edit_button .setText ("Edit Tokenizer" ) 1009edit_button .setToolTip ("Edit all tokenizer data together" ) 1010elif len (field .types )== 1 and self .get_enum_for_key (key )is not None : 1011edit_button = QPushButton ("Edit" ) 1012edit_button .setProperty ("row" ,i ) 1013edit_button .setProperty ("key" ,key ) 1014edit_button .clicked .connect (self .edit_metadata_enum ) 1015actions_layout .addWidget (edit_button ) 1016 1017remove_button = QPushButton ("Remove" ) 1018remove_button .setProperty ("row" ,i ) 1019remove_button .setProperty ("key" ,key ) 1020remove_button .clicked .connect (self .remove_metadata ) 1021actions_layout .addWidget (remove_button ) 1022 1023self .metadata_table .setCellWidget (i ,3 ,actions_widget ) 1024 1025# Reconnect after loading 1026self .metadata_table .itemChanged .connect (self .on_metadata_changed ) 1027self .on_metadata_changed_is_connected = True 1028 1029def extract_array_values (self ,field :ReaderField )-> list : 1030"""Extract all values from an array field.""" 1031if not field .types or field .types [0 ]!= GGUFValueType .ARRAY : 1032return [] 1033 1034curr_type = field .types [1 ] 1035array_values = [] 1036total_elements = len (field .data ) 1037 1038if curr_type == GGUFValueType .STRING : 1039for element_pos in range (total_elements ): 1040value_string = str (bytes (field .parts [- 1 - (total_elements - element_pos - 1 )* 2 ]),encoding = 'utf-8' ) 1041array_values .append (value_string ) 1042elif self .reader and curr_type in self .reader .gguf_scalar_to_np : 1043for element_pos in range (total_elements ): 1044array_values .append (field .parts [- 1 - (total_elements - element_pos - 1 )][0 ]) 1045 1046return array_values 1047 1048def get_enum_for_key (self ,key :str )-> Optional [Type [enum .Enum ]]: 1049"""Get the enum type for a given key if it exists.""" 1050return KEY_TO_ENUM_TYPE .get (key ) 1051 1052def format_enum_value (self ,value :Any ,enum_type :Type [enum .Enum ])-> str : 1053"""Format a value as an enum if possible.""" 1054try : 1055if isinstance (value , (int ,str )): 1056enum_value = enum_type (value ) 1057return f" { enum_value . name } ( { value } )" 1058except (ValueError ,KeyError ): 1059pass 1060return str (value ) 1061 1062def format_field_value (self ,field :ReaderField )-> str : 1063if not field .types : 1064return "N/A" 1065 1066if len (field .types )== 1 : 1067curr_type = field .types [0 ] 1068if curr_type == GGUFValueType .STRING : 1069return str (bytes (field .parts [- 1 ]),encoding = 'utf-8' ) 1070elif self .reader and curr_type in self .reader .gguf_scalar_to_np : 1071value = field .parts [- 1 ][0 ] 1072# Check if this field has an enum type 1073enum_type = self .get_enum_for_key (field .name ) 1074if enum_type is not None : 1075return self .format_enum_value (value ,enum_type ) 1076return str (value ) 1077 1078if field .types [0 ]== GGUFValueType .ARRAY : 1079array_values = self .extract_array_values (field ) 1080render_element = min (5 ,len (array_values )) 1081 1082# Get enum type for this array if applicable 1083enum_type = self .get_enum_for_key (field .name ) 1084 1085if enum_type is not None : 1086array_elements = [] 1087for i in range (render_element ): 1088array_elements .append (self .format_enum_value (array_values [i ],enum_type )) 1089else : 1090array_elements = [str (array_values [i ])for i in range (render_element )] 1091 1092return f"[ { ', ' . join ( array_elements ). strip () } { ', ...' if len ( array_values ) > len ( array_elements ) else '' } ]" 1093 1094return "Complex value" 1095 1096def load_tensors (self ): 1097self .tensors_table .setRowCount (0 ) 1098 1099if not self .reader : 1100return 1101 1102for i ,tensor in enumerate (self .reader .tensors ): 1103self .tensors_table .insertRow (i ) 1104 1105# Name 1106name_item = QTableWidgetItem (tensor .name ) 1107name_item .setFlags (name_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1108self .tensors_table .setItem (i ,0 ,name_item ) 1109 1110# Type 1111type_item = QTableWidgetItem (tensor .tensor_type .name ) 1112type_item .setFlags (type_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1113self .tensors_table .setItem (i ,1 ,type_item ) 1114 1115# Shape 1116shape_str = " × " .join (str (d )for d in tensor .shape ) 1117shape_item = QTableWidgetItem (shape_str ) 1118shape_item .setFlags (shape_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1119self .tensors_table .setItem (i ,2 ,shape_item ) 1120 1121# Elements 1122elements_item = QTableWidgetItem (str (tensor .n_elements )) 1123elements_item .setFlags (elements_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1124self .tensors_table .setItem (i ,3 ,elements_item ) 1125 1126# Size 1127size_item = QTableWidgetItem (f" { tensor . n_bytes :, } " ) 1128size_item .setFlags (size_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1129self .tensors_table .setItem (i ,4 ,size_item ) 1130 1131def on_metadata_changed (self ,item ): 1132if item .column ()!= 2 :# Only handle value column changes 1133return 1134 1135row = item .row () 1136orig_item = self .metadata_table .item (row ,0 ) 1137key = None 1138if orig_item : 1139key = orig_item .text () 1140new_value = item .text () 1141 1142field = None 1143if self .reader and key : 1144field = self .reader .get_field (key ) 1145if not field or not field .types or not key : 1146return 1147 1148value_type = field .types [0 ] 1149 1150# Check if this is an enum field 1151enum_type = self .get_enum_for_key (key ) 1152if enum_type is not None and value_type == GGUFValueType .INT32 : 1153# Try to parse the enum value from the text 1154try : 1155# Check if it's a name 1156try : 1157enum_val = enum_type [new_value ] 1158converted_value = enum_val .value 1159except (KeyError ,AttributeError ): 1160# Check if it's a number or "NAME (value)" format 1161if '(' in new_value and ')' in new_value : 1162# Extract the value from "NAME (value)" format 1163value_part = new_value .split ('(' )[1 ].split (')' )[0 ].strip () 1164converted_value = int (value_part ) 1165else : 1166# Try to convert directly to int 1167converted_value = int (new_value ) 1168 1169# Validate that it's a valid enum value 1170enum_type (converted_value ) 1171 1172# Store the change 1173self .metadata_changes [key ]= (value_type ,converted_value ) 1174self .modified = True 1175 1176# Update display with formatted enum value 1177formatted_value = self .format_enum_value (converted_value ,enum_type ) 1178item .setText (formatted_value ) 1179 1180self .statusBar ().showMessage (f"Changed { key } to { formatted_value } " ) 1181return 1182except (ValueError ,KeyError )as e : 1183QMessageBox .warning ( 1184self , 1185f"Invalid Enum Value ( { e } )" , 1186f"' { new_value } ' is not a valid { enum_type . __name__ } value.\n" 1187f"Valid values are: { ', ' . join ( v . name for v in enum_type ) } " ) 1188 1189# Revert to original value 1190original_value = self .format_field_value (field ) 1191item .setText (original_value ) 1192return 1193 1194try : 1195# Convert the string value to the appropriate type 1196if value_type == GGUFValueType .UINT8 : 1197converted_value = np .uint8 (int (new_value )) 1198elif value_type == GGUFValueType .INT8 : 1199converted_value = np .int8 (int (new_value )) 1200elif value_type == GGUFValueType .UINT16 : 1201converted_value = np .uint16 (int (new_value )) 1202elif value_type == GGUFValueType .INT16 : 1203converted_value = np .int16 (int (new_value )) 1204elif value_type == GGUFValueType .UINT32 : 1205converted_value = np .uint32 (int (new_value )) 1206elif value_type == GGUFValueType .INT32 : 1207converted_value = np .int32 (int (new_value )) 1208elif value_type == GGUFValueType .FLOAT32 : 1209converted_value = np .float32 (float (new_value )) 1210elif value_type == GGUFValueType .BOOL : 1211converted_value = new_value .lower ()in ('true' ,'yes' ,'1' ) 1212elif value_type == GGUFValueType .STRING : 1213converted_value = new_value 1214else : 1215# Unsupported type for editing 1216return 1217 1218# Store the change 1219self .metadata_changes [key ]= (value_type ,converted_value ) 1220self .modified = True 1221 1222self .statusBar ().showMessage (f"Changed { key } to { new_value } " ) 1223except ValueError : 1224QMessageBox .warning (self ,"Invalid Value" ,f"The value ' { new_value } ' is not valid for type { value_type . name } " ) 1225 1226# Revert to original value 1227original_value = self .format_field_value (field ) 1228item .setText (original_value ) 1229 1230def remove_metadata (self ): 1231button = self .sender () 1232key = button .property ("key" ) 1233row = button .property ("row" ) 1234 1235reply = QMessageBox .question ( 1236self ,"Confirm Removal" , 1237f"Are you sure you want to remove the metadata key ' { key } '?" , 1238QMessageBox .StandardButton .Yes | QMessageBox .StandardButton .No ,QMessageBox .StandardButton .No 1239 ) 1240 1241if reply == QMessageBox .StandardButton .Yes : 1242self .metadata_table .removeRow (row ) 1243self .metadata_to_remove .add (key ) 1244 1245# If we previously had changes for this key, remove them 1246if key in self .metadata_changes : 1247del self .metadata_changes [key ] 1248 1249self .modified = True 1250self .statusBar ().showMessage (f"Marked { key } for removal" ) 1251 1252def edit_metadata_enum (self ): 1253"""Edit an enum metadata field.""" 1254button = self .sender () 1255key = button .property ("key" ) 1256row = button .property ("row" ) 1257 1258field = None 1259if self .reader : 1260field = self .reader .get_field (key ) 1261if not field or not field .types : 1262return 1263 1264enum_type = self .get_enum_for_key (key ) 1265if enum_type is None : 1266return 1267 1268# Get current value 1269current_value = field .contents () 1270 1271# Create a dialog with enum options 1272dialog = QDialog (self ) 1273dialog .setWindowTitle (f"Select { enum_type . __name__ } Value" ) 1274layout = QVBoxLayout (dialog ) 1275 1276combo = QComboBox () 1277for enum_val in enum_type : 1278combo .addItem (f" { enum_val . name } ( { enum_val . value } )" ,enum_val .value ) 1279 1280# Set current value 1281try : 1282if isinstance (current_value , (int ,str )): 1283enum_val = enum_type (current_value ) 1284combo .setCurrentText (f" { enum_val . name } ( { current_value } )" ) 1285except (ValueError ,KeyError ): 1286pass 1287 1288layout .addWidget (combo ) 1289 1290buttons = QDialogButtonBox (QDialogButtonBox .StandardButton .Ok | QDialogButtonBox .StandardButton .Cancel ) 1291buttons .accepted .connect (dialog .accept ) 1292buttons .rejected .connect (dialog .reject ) 1293layout .addWidget (buttons ) 1294 1295if dialog .exec ()== QDialog .DialogCode .Accepted : 1296# Get the selected value 1297new_value = combo .currentData () 1298enum_val = enum_type (new_value ) 1299 1300# Store the change 1301self .metadata_changes [key ]= (field .types [0 ],new_value ) 1302self .modified = True 1303 1304# Update display 1305display_text = f" { enum_val . name } ( { new_value } )" 1306target_item = self .metadata_table .item (row ,2 ) 1307if target_item : 1308target_item .setText (display_text ) 1309 1310self .statusBar ().showMessage (f"Changed { key } to { display_text } " ) 1311 1312def edit_array_metadata (self ): 1313button = self .sender () 1314key = button .property ("key" ) 1315row = button .property ("row" ) 1316 1317# Check if this is one of the linked tokenizer keys 1318if key in TOKENIZER_LINKED_KEYS : 1319self .edit_tokenizer_metadata (key ) 1320return 1321 1322field = None 1323if self .reader : 1324field = self .reader .get_field (key ) 1325if not field or not field .types or field .types [0 ]!= GGUFValueType .ARRAY : 1326return 1327 1328# Get array element type 1329element_type = field .types [1 ] 1330 1331# Extract array values 1332array_values = self .extract_array_values (field ) 1333 1334# Open array editor dialog 1335dialog = ArrayEditorDialog (array_values ,element_type ,key ,self ) 1336if dialog .exec ()== QDialog .DialogCode .Accepted : 1337new_values = dialog .get_array_values () 1338 1339# Store the change 1340self .metadata_changes [key ]= (GGUFValueType .ARRAY , (element_type ,new_values )) 1341self .modified = True 1342 1343# Update display 1344enum_type = self .get_enum_for_key (key ) 1345if enum_type is not None and element_type == GGUFValueType .INT32 : 1346value_str = f"[ { ', ' . join ( self . format_enum_value ( v , enum_type ) for v in new_values [: 5 ]) } { ', ...' if len ( new_values ) > 5 else '' } ]" 1347else : 1348value_str = f"[ { ', ' . join ( str ( v ) for v in new_values [: 5 ]) } { ', ...' if len ( new_values ) > 5 else '' } ]" 1349target_item = self .metadata_table .item (row ,2 ) 1350if target_item : 1351target_item .setText (value_str ) 1352 1353self .statusBar ().showMessage (f"Updated array values for { key } " ) 1354 1355def edit_tokenizer_metadata (self ,trigger_key ): 1356"""Edit the linked tokenizer metadata arrays together.""" 1357if not self .reader : 1358return 1359 1360# Get all three fields 1361tokens_field = self .reader .get_field (gguf .Keys .Tokenizer .LIST ) 1362token_types_field = self .reader .get_field (gguf .Keys .Tokenizer .TOKEN_TYPE ) 1363scores_field = self .reader .get_field (gguf .Keys .Tokenizer .SCORES ) 1364 1365# Extract values from each field 1366tokens = self .extract_array_values (tokens_field )if tokens_field else [] 1367token_types = self .extract_array_values (token_types_field )if token_types_field else [] 1368scores = self .extract_array_values (scores_field )if scores_field else [] 1369 1370# Apply any pending changes 1371if gguf .Keys .Tokenizer .LIST in self .metadata_changes : 1372_ , (_ ,tokens )= self .metadata_changes [gguf .Keys .Tokenizer .LIST ] 1373if gguf .Keys .Tokenizer .TOKEN_TYPE in self .metadata_changes : 1374_ , (_ ,token_types )= self .metadata_changes [gguf .Keys .Tokenizer .TOKEN_TYPE ] 1375if gguf .Keys .Tokenizer .SCORES in self .metadata_changes : 1376_ , (_ ,scores )= self .metadata_changes [gguf .Keys .Tokenizer .SCORES ] 1377 1378# Open the tokenizer editor dialog 1379dialog = TokenizerEditorDialog (tokens ,token_types ,scores ,self ) 1380if dialog .exec ()== QDialog .DialogCode .Accepted : 1381new_tokens ,new_token_types ,new_scores = dialog .get_data () 1382 1383# Store changes for all three arrays 1384if tokens_field : 1385self .metadata_changes [gguf .Keys .Tokenizer .LIST ]= ( 1386GGUFValueType .ARRAY , 1387 (tokens_field .types [1 ],new_tokens ) 1388 ) 1389 1390if token_types_field : 1391self .metadata_changes [gguf .Keys .Tokenizer .TOKEN_TYPE ]= ( 1392GGUFValueType .ARRAY , 1393 (token_types_field .types [1 ],new_token_types ) 1394 ) 1395 1396if scores_field : 1397self .metadata_changes [gguf .Keys .Tokenizer .SCORES ]= ( 1398GGUFValueType .ARRAY , 1399 (scores_field .types [1 ],new_scores ) 1400 ) 1401 1402self .modified = True 1403 1404# Update display for all three fields 1405self .update_tokenizer_display (gguf .Keys .Tokenizer .LIST ,new_tokens ) 1406self .update_tokenizer_display (gguf .Keys .Tokenizer .TOKEN_TYPE ,new_token_types ) 1407self .update_tokenizer_display (gguf .Keys .Tokenizer .SCORES ,new_scores ) 1408 1409self .statusBar ().showMessage ("Updated tokenizer data" ) 1410 1411def update_tokenizer_display (self ,key ,values ): 1412"""Update the display of a tokenizer field in the metadata table.""" 1413for row in range (self .metadata_table .rowCount ()): 1414key_item = self .metadata_table .item (row ,0 ) 1415if key_item and key_item .text ()== key : 1416value_str = f"[ { ', ' . join ( str ( v ) for v in values [: 5 ]) } { ', ...' if len ( values ) > 5 else '' } ]" 1417value_item = self .metadata_table .item (row ,2 ) 1418if value_item : 1419value_item .setText (value_str ) 1420break 1421 1422def add_metadata (self ): 1423dialog = AddMetadataDialog (self ) 1424if dialog .exec ()== QDialog .DialogCode .Accepted : 1425key ,value_type ,value = dialog .get_data () 1426 1427if not key : 1428QMessageBox .warning (self ,"Invalid Key" ,"Key cannot be empty" ) 1429return 1430 1431# Check if key already exists 1432for row in range (self .metadata_table .rowCount ()): 1433orig_item = self .metadata_table .item (row ,0 ) 1434if orig_item and orig_item .text ()== key : 1435QMessageBox .warning (self ,"Duplicate Key" ,f"Key ' { key } ' already exists" ) 1436return 1437 1438# Add to table 1439row = self .metadata_table .rowCount () 1440self .metadata_table .insertRow (row ) 1441 1442# Key 1443key_item = QTableWidgetItem (key ) 1444key_item .setFlags (key_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1445self .metadata_table .setItem (row ,0 ,key_item ) 1446 1447# Type 1448type_item = QTableWidgetItem (value_type .name ) 1449type_item .setFlags (type_item .flags ()& ~ Qt .ItemFlag .ItemIsEditable ) 1450self .metadata_table .setItem (row ,1 ,type_item ) 1451 1452# Value 1453value_item = QTableWidgetItem (str (value )) 1454value_item .setFlags (value_item .flags ()| Qt .ItemFlag .ItemIsEditable ) 1455self .metadata_table .setItem (row ,2 ,value_item ) 1456 1457# Actions 1458actions_widget = QWidget () 1459actions_layout = QHBoxLayout (actions_widget ) 1460actions_layout .setContentsMargins (2 ,2 ,2 ,2 ) 1461 1462remove_button = QPushButton ("Remove" ) 1463remove_button .setProperty ("row" ,row ) 1464remove_button .setProperty ("key" ,key ) 1465remove_button .clicked .connect (self .remove_metadata ) 1466actions_layout .addWidget (remove_button ) 1467 1468self .metadata_table .setCellWidget (row ,3 ,actions_widget ) 1469 1470# Store the change 1471self .metadata_changes [key ]= (value_type ,value ) 1472self .modified = True 1473 1474self .statusBar ().showMessage (f"Added new metadata key { key } " ) 1475 1476def save_file (self ): 1477if not self .reader : 1478QMessageBox .warning (self ,"No File Open" ,"Please open a GGUF file first" ) 1479return 1480 1481if not self .modified and not self .metadata_changes and not self .metadata_to_remove : 1482QMessageBox .information (self ,"No Changes" ,"No changes to save" ) 1483return 1484 1485file_path ,_ = QFileDialog .getSaveFileName ( 1486self ,"Save GGUF File As" ,"" ,"GGUF Files (*.gguf);;All Files (*)" 1487 ) 1488 1489if not file_path : 1490return 1491 1492try : 1493self .statusBar ().showMessage (f"Saving to { file_path } ..." ) 1494QApplication .processEvents () 1495 1496# Get architecture and endianness from the original file 1497arch = 'unknown' 1498field = self .reader .get_field (gguf .Keys .General .ARCHITECTURE ) 1499if field : 1500arch = field .contents () 1501 1502# Create writer 1503writer = GGUFWriter (file_path ,arch = arch ,endianess = self .reader .endianess ) 1504 1505# Get alignment if present 1506alignment = None 1507field = self .reader .get_field (gguf .Keys .General .ALIGNMENT ) 1508if field : 1509alignment = field .contents () 1510if alignment is not None : 1511writer .data_alignment = alignment 1512 1513# Copy metadata with changes 1514for field in self .reader .fields .values (): 1515# Skip virtual fields and fields written by GGUFWriter 1516if field .name == gguf .Keys .General .ARCHITECTURE or field .name .startswith ('GGUF.' ): 1517continue 1518 1519# Skip fields marked for removal 1520if field .name in self .metadata_to_remove : 1521continue 1522 1523# Apply changes if any 1524sub_type = None 1525if field .name in self .metadata_changes : 1526value_type ,value = self .metadata_changes [field .name ] 1527if value_type == GGUFValueType .ARRAY : 1528# Handle array values 1529sub_type ,value = value 1530else : 1531# Copy original value 1532value = field .contents () 1533value_type = field .types [0 ] 1534if value_type == GGUFValueType .ARRAY : 1535sub_type = field .types [- 1 ] 1536 1537if value is not None : 1538writer .add_key_value (field .name ,value ,value_type ,sub_type = sub_type ) 1539 1540# Add new metadata 1541for key , (value_type ,value )in self .metadata_changes .items (): 1542# Skip if the key already existed (we handled it above) 1543if self .reader .get_field (key )is not None : 1544continue 1545 1546sub_type = None 1547if value_type == GGUFValueType .ARRAY : 1548# Handle array values 1549sub_type ,value = value 1550 1551writer .add_key_value (key ,value ,value_type ,sub_type = sub_type ) 1552 1553# Add tensors (including data) 1554for tensor in self .reader .tensors : 1555writer .add_tensor (tensor .name ,tensor .data ,raw_shape = tensor .data .shape ,raw_dtype = tensor .tensor_type ,tensor_endianess = self .reader .endianess ) 1556 1557# Write header and metadata 1558writer .open_output_file (Path (file_path )) 1559writer .write_header_to_file () 1560writer .write_kv_data_to_file () 1561 1562# Write tensor data using the optimized method 1563writer .write_tensors_to_file (progress = False ) 1564 1565writer .close () 1566 1567self .statusBar ().showMessage (f"Saved to { file_path } " ) 1568 1569# Ask if user wants to open the new file 1570reply = QMessageBox .question ( 1571self ,"Open Saved File" , 1572"Would you like to open the newly saved file?" , 1573QMessageBox .StandardButton .Yes | QMessageBox .StandardButton .No ,QMessageBox .StandardButton .Yes 1574 ) 1575 1576if reply == QMessageBox .StandardButton .Yes : 1577self .reader = GGUFReader (file_path ,'r' ) 1578self .current_file = file_path 1579self .file_path_edit .setText (file_path ) 1580 1581self .load_metadata () 1582self .load_tensors () 1583 1584self .metadata_changes = {} 1585self .metadata_to_remove = set () 1586self .modified = False 1587 1588except Exception as e : 1589QMessageBox .critical (self ,"Error" ,f"Failed to save file: { str ( e ) } " ) 1590self .statusBar ().showMessage ("Error saving file" ) 1591 1592 1593def main ()-> None : 1594parser = argparse .ArgumentParser (description = "GUI GGUF Editor" ) 1595parser .add_argument ("model_path" ,nargs = "?" ,help = "path to GGUF model file to load at startup" ) 1596parser .add_argument ("--verbose" ,action = "store_true" ,help = "increase output verbosity" ) 1597 1598args = parser .parse_args () 1599 1600logging .basicConfig (level = logging .DEBUG if args .verbose else logging .INFO ) 1601 1602app = QApplication (sys .argv ) 1603window = GGUFEditorWindow () 1604window .show () 1605 1606# Load model if specified 1607if args .model_path : 1608if os .path .isfile (args .model_path )and args .model_path .endswith ('.gguf' ): 1609window .load_file (args .model_path ) 1610else : 1611logger .error (f"Invalid model path: { args . model_path } " ) 1612QMessageBox .warning ( 1613window , 1614"Invalid Model Path" , 1615f"The specified file does not exist or is not a GGUF file: { args . model_path } " ) 1616 1617sys .exit (app .exec ()) 1618 1619 1620if __name__ == '__main__' : 1621main ()