This commit is contained in:
leafspark 2024-08-02 21:10:32 -07:00 committed by GitHub
parent 952f9f2d20
commit b3721e75f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 932 additions and 2 deletions

View File

@ -1,2 +1,24 @@
# AutoGGUF
automatically quant GGUF models
AutoGGUF - Automated GGUF Model Quantizer
This application provides a graphical user interface for quantizing GGUF models
using the llama.cpp library. It allows users to download different versions of
llama.cpp, manage multiple backends, and perform quantization tasks with various
options.
Main features:
1. Download and manage llama.cpp backends
2. Select and quantize GGUF models
3. Configure quantization parameters
4. Monitor system resources during quantization
Usage:
Run the main.py script to start the application.
Dependencies:
- PyQt6
- requests
- psutil
Author: leafspark
Version: 1.0.0
License: apache-2.0

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
PyQt6==6.5.2
psutil==5.9.5
requests==2.31.0

2
run.bat Normal file
View File

@ -0,0 +1,2 @@
@echo off
python src/main.py

637
src/AutoGGUF.py Normal file
View File

@ -0,0 +1,637 @@
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
from imports_and_globals import ensure_directory
class AutoGGUF(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("AutoGGUF (automated GGUF model quantizer)")
self.setGeometry(100, 100, 1200, 1000)
main_layout = QHBoxLayout()
left_layout = QVBoxLayout()
right_layout = QVBoxLayout()
# System info
self.ram_bar = QProgressBar()
self.cpu_label = QLabel("CPU Usage: ")
left_layout.addWidget(QLabel("RAM Usage:"))
left_layout.addWidget(self.ram_bar)
left_layout.addWidget(self.cpu_label)
# Modify the backend selection
backend_layout = QHBoxLayout()
self.backend_combo = QComboBox()
self.refresh_backends_button = QPushButton("Refresh Backends")
self.refresh_backends_button.clicked.connect(self.refresh_backends)
backend_layout.addWidget(QLabel("Llama.cpp Backend:"))
backend_layout.addWidget(self.backend_combo)
backend_layout.addWidget(self.refresh_backends_button)
left_layout.addLayout(backend_layout)
# Add Download llama.cpp section
download_group = QGroupBox("Download llama.cpp")
download_layout = QFormLayout()
self.release_combo = QComboBox()
self.refresh_releases_button = QPushButton("Refresh Releases")
self.refresh_releases_button.clicked.connect(self.refresh_releases)
release_layout = QHBoxLayout()
release_layout.addWidget(self.release_combo)
release_layout.addWidget(self.refresh_releases_button)
download_layout.addRow("Select Release:", release_layout)
self.asset_combo = QComboBox()
download_layout.addRow("Select Asset:", self.asset_combo)
self.download_progress = QProgressBar()
self.download_button = QPushButton("Download")
self.download_button.clicked.connect(self.download_llama_cpp)
download_layout.addRow(self.download_progress)
download_layout.addRow(self.download_button)
download_group.setLayout(download_layout)
right_layout.addWidget(download_group)
# Initialize releases and backends
self.refresh_releases()
self.refresh_backends()
# Models path
models_layout = QHBoxLayout()
self.models_input = QLineEdit(os.path.abspath("models"))
models_button = QPushButton("Browse")
models_button.clicked.connect(self.browse_models)
models_layout.addWidget(QLabel("Models Path:"))
models_layout.addWidget(self.models_input)
models_layout.addWidget(models_button)
left_layout.addLayout(models_layout)
# Output path
output_layout = QHBoxLayout()
self.output_input = QLineEdit(os.path.abspath("quantized_models"))
output_button = QPushButton("Browse")
output_button.clicked.connect(self.browse_output)
output_layout.addWidget(QLabel("Output Path:"))
output_layout.addWidget(self.output_input)
output_layout.addWidget(output_button)
left_layout.addLayout(output_layout)
# Logs path
logs_layout = QHBoxLayout()
self.logs_input = QLineEdit(os.path.abspath("logs"))
logs_button = QPushButton("Browse")
logs_button.clicked.connect(self.browse_logs)
logs_layout.addWidget(QLabel("Logs Path:"))
logs_layout.addWidget(self.logs_input)
logs_layout.addWidget(logs_button)
left_layout.addLayout(logs_layout)
# Model list
self.model_list = QListWidget()
self.load_models()
left_layout.addWidget(QLabel("Available Models:"))
left_layout.addWidget(self.model_list)
# Quantization options
quant_options_scroll = QScrollArea()
quant_options_widget = QWidget()
quant_options_layout = QFormLayout()
self.quant_type = QComboBox()
self.quant_type.addItems([
"Q4_0", "Q4_1", "Q5_0", "Q5_1", "IQ2_XXS", "IQ2_XS", "IQ2_S", "IQ2_M", "IQ1_S", "IQ1_M",
"Q2_K", "Q2_K_S", "IQ3_XXS", "IQ3_S", "IQ3_M", "Q3_K", "IQ3_XS", "Q3_K_S", "Q3_K_M", "Q3_K_L",
"IQ4_NL", "IQ4_XS", "Q4_K", "Q4_K_S", "Q4_K_M", "Q5_K", "Q5_K_S", "Q5_K_M", "Q6_K", "Q8_0",
"Q4_0_4_4", "Q4_0_4_8", "Q4_0_8_8", "F16", "BF16", "F32", "COPY"
])
quant_options_layout.addRow(self.create_label("Quantization Type:", "Select the quantization type"), self.quant_type)
self.allow_requantize = QCheckBox("Allow Requantize")
self.leave_output_tensor = QCheckBox("Leave Output Tensor")
self.pure = QCheckBox("Pure")
quant_options_layout.addRow(self.create_label("", "Allows requantizing tensors that have already been quantized"), self.allow_requantize)
quant_options_layout.addRow(self.create_label("", "Will leave output.weight un(re)quantized"), self.leave_output_tensor)
quant_options_layout.addRow(self.create_label("", "Disable k-quant mixtures and quantize all tensors to the same type"), self.pure)
self.imatrix = QLineEdit()
self.imatrix_button = QPushButton("Browse")
self.imatrix_button.clicked.connect(self.browse_imatrix)
imatrix_layout = QHBoxLayout()
imatrix_layout.addWidget(self.imatrix)
imatrix_layout.addWidget(self.imatrix_button)
quant_options_layout.addRow(self.create_label("IMatrix:", "Use data in file as importance matrix for quant optimizations"), imatrix_layout)
self.include_weights = QLineEdit()
self.exclude_weights = QLineEdit()
quant_options_layout.addRow(self.create_label("Include Weights:", "Use importance matrix for these tensors"), self.include_weights)
quant_options_layout.addRow(self.create_label("Exclude Weights:", "Don't use importance matrix for these tensors"), self.exclude_weights)
self.use_output_tensor_type = QCheckBox("Use Output Tensor Type")
self.use_output_tensor_type.stateChanged.connect(self.toggle_output_tensor_type)
self.output_tensor_type = QComboBox()
self.output_tensor_type.addItems(["F32", "F16", "Q4_0", "Q4_1", "Q5_0", "Q5_1", "Q8_0"])
self.output_tensor_type.setEnabled(False)
output_tensor_layout = QHBoxLayout()
output_tensor_layout.addWidget(self.use_output_tensor_type)
output_tensor_layout.addWidget(self.output_tensor_type)
quant_options_layout.addRow(self.create_label("Output Tensor Type:", "Use this type for the output.weight tensor"), output_tensor_layout)
self.use_token_embedding_type = QCheckBox("Use Token Embedding Type")
self.use_token_embedding_type.stateChanged.connect(self.toggle_token_embedding_type)
self.token_embedding_type = QComboBox()
self.token_embedding_type.addItems(["F32", "F16", "Q4_0", "Q4_1", "Q5_0", "Q5_1", "Q8_0"])
self.token_embedding_type.setEnabled(False)
token_embedding_layout = QHBoxLayout()
token_embedding_layout.addWidget(self.use_token_embedding_type)
token_embedding_layout.addWidget(self.token_embedding_type)
quant_options_layout.addRow(self.create_label("Token Embedding Type:", "Use this type for the token embeddings tensor"), token_embedding_layout)
self.keep_split = QCheckBox("Keep Split")
self.override_kv = QLineEdit()
quant_options_layout.addRow(self.create_label("", "Will generate quantized model in the same shards as input"), self.keep_split)
quant_options_layout.addRow(self.create_label("Override KV:", "Override model metadata by key in the quantized model"), self.override_kv)
quant_options_widget.setLayout(quant_options_layout)
quant_options_scroll.setWidget(quant_options_widget)
quant_options_scroll.setWidgetResizable(True)
left_layout.addWidget(quant_options_scroll)
# Quantize button
quantize_button = QPushButton("Quantize Selected Model")
quantize_button.clicked.connect(self.quantize_model)
left_layout.addWidget(quantize_button)
# Task list
self.task_list = QListWidget()
self.task_list.setSelectionMode(QListWidget.SelectionMode.NoSelection)
self.task_list.itemDoubleClicked.connect(self.show_task_details)
left_layout.addWidget(QLabel("Tasks:"))
left_layout.addWidget(self.task_list)
# IMatrix section
imatrix_group = QGroupBox("IMatrix Generation")
imatrix_layout = QFormLayout()
self.imatrix_datafile = QLineEdit()
self.imatrix_datafile_button = QPushButton("Browse")
self.imatrix_datafile_button.clicked.connect(self.browse_imatrix_datafile)
imatrix_datafile_layout = QHBoxLayout()
imatrix_datafile_layout.addWidget(self.imatrix_datafile)
imatrix_datafile_layout.addWidget(self.imatrix_datafile_button)
imatrix_layout.addRow(self.create_label("Data File:", "Input data file for IMatrix generation"), imatrix_datafile_layout)
self.imatrix_model = QLineEdit()
self.imatrix_model_button = QPushButton("Browse")
self.imatrix_model_button.clicked.connect(self.browse_imatrix_model)
imatrix_model_layout = QHBoxLayout()
imatrix_model_layout.addWidget(self.imatrix_model)
imatrix_model_layout.addWidget(self.imatrix_model_button)
imatrix_layout.addRow(self.create_label("Model:", "Model to be quantized"), imatrix_model_layout)
self.imatrix_output = QLineEdit()
self.imatrix_output_button = QPushButton("Browse")
self.imatrix_output_button.clicked.connect(self.browse_imatrix_output)
imatrix_output_layout = QHBoxLayout()
imatrix_output_layout.addWidget(self.imatrix_output)
imatrix_output_layout.addWidget(self.imatrix_output_button)
imatrix_layout.addRow(self.create_label("Output:", "Output path for the generated IMatrix"), imatrix_output_layout)
self.imatrix_frequency = QLineEdit()
imatrix_layout.addRow(self.create_label("Output Frequency:", "How often to save the IMatrix"), self.imatrix_frequency)
# GPU Offload for IMatrix
gpu_offload_layout = QHBoxLayout()
self.gpu_offload_slider = QSlider(Qt.Orientation.Horizontal)
self.gpu_offload_slider.setRange(0, 100)
self.gpu_offload_slider.valueChanged.connect(self.update_gpu_offload_spinbox)
self.gpu_offload_spinbox = QSpinBox()
self.gpu_offload_spinbox.setRange(0, 100)
self.gpu_offload_spinbox.valueChanged.connect(self.update_gpu_offload_slider)
self.gpu_offload_auto = QCheckBox("Auto")
self.gpu_offload_auto.stateChanged.connect(self.toggle_gpu_offload_auto)
gpu_offload_layout.addWidget(self.gpu_offload_slider)
gpu_offload_layout.addWidget(self.gpu_offload_spinbox)
gpu_offload_layout.addWidget(self.gpu_offload_auto)
imatrix_layout.addRow(self.create_label("GPU Offload:", "Set GPU offload value (-ngl)"), gpu_offload_layout)
imatrix_generate_button = QPushButton("Generate IMatrix")
imatrix_generate_button.clicked.connect(self.generate_imatrix)
imatrix_layout.addRow(imatrix_generate_button)
imatrix_group.setLayout(imatrix_layout)
right_layout.addWidget(imatrix_group)
main_widget = QWidget()
main_layout.addLayout(left_layout, 2)
main_layout.addLayout(right_layout, 1)
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# Modify the task list to support right-click menu
self.task_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.task_list.customContextMenuRequested.connect(self.show_task_context_menu)
# Timer for updating system info
self.timer = QTimer()
self.timer.timeout.connect(self.update_system_info)
self.timer.start(200)
# Initialize threads
self.quant_threads = []
def refresh_backends(self):
llama_bin = os.path.abspath("llama_bin")
if not os.path.exists(llama_bin):
os.makedirs(llama_bin)
self.backend_combo.clear()
for item in os.listdir(llama_bin):
item_path = os.path.join(llama_bin, item)
if os.path.isdir(item_path):
self.backend_combo.addItem(item, userData=item_path)
def refresh_releases(self):
try:
response = requests.get("https://api.github.com/repos/ggerganov/llama.cpp/releases")
releases = response.json()
self.release_combo.clear()
for release in releases:
self.release_combo.addItem(release['tag_name'], userData=release)
self.release_combo.currentIndexChanged.connect(self.update_assets)
self.update_assets()
except Exception as e:
self.show_error(f"Error fetching releases: {str(e)}")
def update_assets(self):
self.asset_combo.clear()
release = self.release_combo.currentData()
if release:
for asset in release['assets']:
self.asset_combo.addItem(asset['name'], userData=asset)
def download_llama_cpp(self):
asset = self.asset_combo.currentData()
if not asset:
self.show_error("No asset selected")
return
llama_bin = os.path.abspath("llama_bin")
if not os.path.exists(llama_bin):
os.makedirs(llama_bin)
save_path = os.path.join(llama_bin, asset['name'])
self.download_thread = DownloadThread(asset['browser_download_url'], save_path)
self.download_thread.progress_signal.connect(self.update_download_progress)
self.download_thread.finished_signal.connect(self.download_finished)
self.download_thread.error_signal.connect(self.show_error)
self.download_thread.start()
self.download_button.setEnabled(False)
self.download_progress.setValue(0)
def update_download_progress(self, progress):
self.download_progress.setValue(progress)
def download_finished(self, extract_dir):
self.download_button.setEnabled(True)
self.download_progress.setValue(100)
QMessageBox.information(self, "Download Complete", f"llama.cpp binary downloaded and extracted to {extract_dir}")
self.refresh_backends()
def show_task_context_menu(self, position):
item = self.task_list.itemAt(position)
if item is not None:
context_menu = QMenu(self)
properties_action = QAction("Properties", self)
properties_action.triggered.connect(lambda: self.show_task_properties(item))
context_menu.addAction(properties_action)
if self.task_list.itemWidget(item).status != "Completed":
cancel_action = QAction("Cancel", self)
cancel_action.triggered.connect(lambda: self.cancel_task(item))
context_menu.addAction(cancel_action)
if self.task_list.itemWidget(item).status == "Canceled":
retry_action = QAction("Retry", self)
retry_action.triggered.connect(lambda: self.retry_task(item))
context_menu.addAction(retry_action)
delete_action = QAction("Delete", self)
delete_action.triggered.connect(lambda: self.delete_task(item))
context_menu.addAction(delete_action)
context_menu.exec(self.task_list.viewport().mapToGlobal(position))
def show_task_properties(self, item):
task_item = self.task_list.itemWidget(item)
for thread in self.quant_threads:
if thread.log_file == task_item.log_file:
model_info_dialog = ModelInfoDialog(thread.model_info, self)
model_info_dialog.exec()
break
def cancel_task(self, item):
task_item = self.task_list.itemWidget(item)
for thread in self.quant_threads:
if thread.log_file == task_item.log_file:
thread.terminate()
task_item.update_status("Canceled")
break
def retry_task(self, item):
task_item = self.task_list.itemWidget(item)
# TODO: Implement the logic to restart the task
pass
def delete_task(self, item):
reply = QMessageBox.question(self, 'Confirm Deletion',
"Are you sure you want to delete this task?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
row = self.task_list.row(item)
self.task_list.takeItem(row)
# If the task is still running, terminate it
task_item = self.task_list.itemWidget(item)
for thread in self.quant_threads:
if thread.log_file == task_item.log_file:
thread.terminate()
self.quant_threads.remove(thread)
break
def create_label(self, text, tooltip):
label = QLabel(text)
label.setToolTip(tooltip)
return label
def load_models(self):
models_dir = self.models_input.text()
ensure_directory(models_dir)
self.model_list.clear()
for file in os.listdir(models_dir):
if file.endswith(".gguf"):
self.model_list.addItem(file)
def browse_backend(self):
backend_path = QFileDialog.getExistingDirectory(self, "Select Llama.cpp Backend Directory")
if backend_path:
self.backend_input.setText(os.path.abspath(backend_path))
ensure_directory(backend_path)
def browse_models(self):
models_path = QFileDialog.getExistingDirectory(self, "Select Models Directory")
if models_path:
self.models_input.setText(os.path.abspath(models_path))
ensure_directory(models_path)
self.load_models()
def browse_output(self):
output_path = QFileDialog.getExistingDirectory(self, "Select Output Directory")
if output_path:
self.output_input.setText(os.path.abspath(output_path))
ensure_directory(output_path)
def browse_logs(self):
logs_path = QFileDialog.getExistingDirectory(self, "Select Logs Directory")
if logs_path:
self.logs_input.setText(os.path.abspath(logs_path))
ensure_directory(logs_path)
def browse_imatrix(self):
imatrix_file, _ = QFileDialog.getOpenFileName(self, "Select IMatrix File", "", "DAT Files (*.dat)")
if imatrix_file:
self.imatrix.setText(os.path.abspath(imatrix_file))
def update_system_info(self):
ram = psutil.virtual_memory()
cpu = psutil.cpu_percent()
self.ram_bar.setValue(int(ram.percent))
self.ram_bar.setFormat(f"{ram.percent:.1f}% ({ram.used // 1024 // 1024} MB / {ram.total // 1024 // 1024} MB)")
self.cpu_label.setText(f"CPU Usage: {cpu:.1f}%")
def toggle_output_tensor_type(self, state):
self.output_tensor_type.setEnabled(state == Qt.CheckState.Checked)
def toggle_token_embedding_type(self, state):
self.token_embedding_type.setEnabled(state == Qt.CheckState.Checked)
def validate_quantization_inputs(self):
if not self.backend_input.text():
raise ValueError("Backend path is required")
if not self.models_input.text():
raise ValueError("Models path is required")
if not self.output_input.text():
raise ValueError("Output path is required")
if not self.logs_input.text():
raise ValueError("Logs path is required")
def quantize_model(self):
try:
self.validate_quantization_inputs()
selected_model = self.model_list.currentItem()
if not selected_model:
raise ValueError("No model selected")
model_name = selected_model.text()
backend_path = self.backend_combo.currentData()
if not backend_path:
raise ValueError("No backend selected")
quant_type = self.quant_type.currentText()
input_path = os.path.join(self.models_input.text(), model_name)
output_name = f"{os.path.splitext(model_name)[0]}_{quant_type}.gguf"
output_path = os.path.join(self.output_input.text(), output_name)
if not os.path.exists(input_path):
raise FileNotFoundError(f"Input file '{input_path}' does not exist.")
command = [os.path.join(backend_path, "llama-quantize")]
if self.allow_requantize.isChecked():
command.append("--allow-requantize")
if self.leave_output_tensor.isChecked():
command.append("--leave-output-tensor")
if self.pure.isChecked():
command.append("--pure")
if self.imatrix.text():
command.extend(["--imatrix", self.imatrix.text()])
if self.include_weights.text():
command.extend(["--include-weights", self.include_weights.text()])
if self.exclude_weights.text():
command.extend(["--exclude-weights", self.exclude_weights.text()])
if self.use_output_tensor_type.isChecked():
command.extend(["--output-tensor-type", self.output_tensor_type.currentText()])
if self.use_token_embedding_type.isChecked():
command.extend(["--token-embedding-type", self.token_embedding_type.currentText()])
if self.keep_split.isChecked():
command.append("--keep-split")
if self.override_kv.text():
command.extend(["--override-kv", self.override_kv.text()])
command.extend([input_path, output_path, quant_type])
logs_path = self.logs_input.text()
ensure_directory(logs_path)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = os.path.join(logs_path, f"{model_name}_{timestamp}_{quant_type}.log")
thread = QuantizationThread(command, backend_path, log_file)
self.quant_threads.append(thread)
task_item = TaskListItem(f"Quantizing {model_name} to {quant_type}", log_file)
list_item = QListWidgetItem(self.task_list)
list_item.setSizeHint(task_item.sizeHint())
self.task_list.addItem(list_item)
self.task_list.setItemWidget(list_item, task_item)
thread.status_signal.connect(task_item.update_status)
thread.finished_signal.connect(lambda: self.task_finished(thread))
thread.error_signal.connect(lambda err: self.handle_error(err, task_item))
thread.model_info_signal.connect(self.update_model_info)
thread.start()
except Exception as e:
self.show_error(f"Error starting quantization: {str(e)}")
def update_model_info(self, model_info):
# TODO: Do something with this
pass
def task_finished(self, thread):
if thread in self.quant_threads:
self.quant_threads.remove(thread)
def show_task_details(self, item):
task_item = self.task_list.itemWidget(item)
if task_item:
log_dialog = QDialog(self)
log_dialog.setWindowTitle(f"Log for {task_item.task_name}")
log_dialog.setGeometry(200, 200, 800, 600)
log_text = QPlainTextEdit()
log_text.setReadOnly(True)
layout = QVBoxLayout()
layout.addWidget(log_text)
log_dialog.setLayout(layout)
# Load existing content
if os.path.exists(task_item.log_file):
with open(task_item.log_file, 'r') as f:
log_text.setPlainText(f.read())
# Connect to the thread if it's still running
for thread in self.quant_threads:
if thread.log_file == task_item.log_file:
thread.output_signal.connect(log_text.appendPlainText)
break
log_dialog.exec()
def browse_imatrix_datafile(self):
datafile, _ = QFileDialog.getOpenFileName(self, "Select Data File", "", "All Files (*)")
if datafile:
self.imatrix_datafile.setText(os.path.abspath(datafile))
def browse_imatrix_model(self):
model_file, _ = QFileDialog.getOpenFileName(self, "Select Model File", "", "GGUF Files (*.gguf)")
if model_file:
self.imatrix_model.setText(os.path.abspath(model_file))
def browse_imatrix_output(self):
output_file, _ = QFileDialog.getSaveFileName(self, "Select Output File", "", "DAT Files (*.dat)")
if output_file:
self.imatrix_output.setText(os.path.abspath(output_file))
def update_gpu_offload_spinbox(self, value):
self.gpu_offload_spinbox.setValue(value)
def update_gpu_offload_slider(self, value):
self.gpu_offload_slider.setValue(value)
def toggle_gpu_offload_auto(self, state):
is_auto = state == Qt.CheckState.Checked
self.gpu_offload_slider.setEnabled(not is_auto)
self.gpu_offload_spinbox.setEnabled(not is_auto)
def generate_imatrix(self):
try:
backend_path = self.backend_input.text()
if not os.path.exists(backend_path):
raise FileNotFoundError(f"Backend path does not exist: {backend_path}")
command = [
os.path.join(backend_path, "llama-imatrix"),
"-f", self.imatrix_datafile.text(),
"-m", self.imatrix_model.text(),
"-o", self.imatrix_output.text(),
"--output-frequency", self.imatrix_frequency.text()
]
if self.gpu_offload_auto.isChecked():
command.extend(["-ngl", "99"])
elif self.gpu_offload_spinbox.value() > 0:
command.extend(["-ngl", str(self.gpu_offload_spinbox.value())])
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = os.path.join(self.logs_input.text(), f"imatrix_{timestamp}.log")
thread = QuantizationThread(command, backend_path, log_file)
self.quant_threads.append(thread)
task_item = TaskListItem("Generating IMatrix", log_file)
list_item = QListWidgetItem(self.task_list)
list_item.setSizeHint(task_item.sizeHint())
self.task_list.addItem(list_item)
self.task_list.setItemWidget(list_item, task_item)
thread.status_signal.connect(task_item.update_status)
thread.finished_signal.connect(lambda: self.task_finished(thread))
thread.error_signal.connect(lambda err: self.handle_error(err, task_item))
thread.start()
except Exception as e:
self.show_error(f"Error starting IMatrix generation: {str(e)}")
def show_error(self, message):
QMessageBox.critical(self, "Error", message)
def handle_error(self, error_message, task_item):
self.show_error(error_message)
task_item.set_error()
def closeEvent(self, event: QCloseEvent):
if self.quant_threads:
reply = QMessageBox.question(self, 'Warning',
"Some tasks are still running. Are you sure you want to quit?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
for thread in self.quant_threads:
thread.terminate()
event.accept()
else:
event.ignore()
else:
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = AutoGGUF()
window.show()
sys.exit(app.exec())

53
src/DownloadThread.py Normal file
View File

@ -0,0 +1,53 @@
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
class DownloadThread(QThread):
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(str)
error_signal = pyqtSignal(str)
def __init__(self, url, save_path):
super().__init__()
self.url = url
self.save_path = save_path
def run(self):
try:
response = requests.get(self.url, stream=True)
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
block_size = 8192
downloaded = 0
with open(self.save_path, 'wb') as file:
for data in response.iter_content(block_size):
size = file.write(data)
downloaded += size
if total_size:
progress = int((downloaded / total_size) * 100)
self.progress_signal.emit(progress)
# Extract the downloaded zip file
extract_dir = os.path.splitext(self.save_path)[0]
with zipfile.ZipFile(self.save_path, 'r') as zip_ref:
zip_ref.extractall(extract_dir)
# Remove the zip file after extraction
os.remove(self.save_path)
self.finished_signal.emit(extract_dir)
except Exception as e:
self.error_signal.emit(str(e))

48
src/ModelInfoDialog.py Normal file
View File

@ -0,0 +1,48 @@
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
class ModelInfoDialog(QDialog):
def __init__(self, model_info, parent=None):
super().__init__(parent)
self.setWindowTitle("Model Information")
self.setGeometry(200, 200, 600, 400)
layout = QVBoxLayout()
info_text = QTextEdit()
info_text.setReadOnly(True)
info_text.setHtml(self.format_model_info(model_info))
layout.addWidget(info_text)
close_button = QPushButton("Close")
close_button.clicked.connect(self.accept)
layout.addWidget(close_button)
self.setLayout(layout)
def format_model_info(self, model_info):
html = "<h2>Model Information</h2>"
html += f"<p><b>Architecture:</b> {model_info.get('architecture', 'N/A')}</p>"
html += f"<p><b>Quantization Type:</b> {model_info.get('quantization_type', 'N/A')}</p>"
html += f"<p><b>KV Pairs:</b> {model_info.get('kv_pairs', 'N/A')}</p>"
html += f"<p><b>Tensors:</b> {model_info.get('tensors', 'N/A')}</p>"
html += "<h3>Key-Value Pairs:</h3>"
for key, value in model_info.get('kv_data', {}).items():
html += f"<p><b>{key}:</b> {value}</p>"
return html

78
src/QuantizationThread.py Normal file
View File

@ -0,0 +1,78 @@
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
class QuantizationThread(QThread):
output_signal = pyqtSignal(str)
status_signal = pyqtSignal(str)
finished_signal = pyqtSignal()
error_signal = pyqtSignal(str)
model_info_signal = pyqtSignal(dict)
def __init__(self, command, cwd, log_file):
super().__init__()
self.command = command
self.cwd = cwd
self.log_file = log_file
self.process = None
self.model_info = {}
def run(self):
try:
self.process = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, cwd=self.cwd)
with open(self.log_file, 'w') as log:
for line in self.process.stdout:
line = line.strip()
self.output_signal.emit(line)
log.write(line + '\n')
log.flush()
self.status_signal.emit("In Progress")
self.parse_model_info(line)
self.process.wait()
if self.process.returncode == 0:
self.status_signal.emit("Completed")
self.model_info_signal.emit(self.model_info)
else:
self.error_signal.emit(f"Process exited with code {self.process.returncode}")
self.finished_signal.emit()
except Exception as e:
self.error_signal.emit(str(e))
def parse_model_info(self, line):
if "llama_model_loader: loaded meta data with" in line:
parts = line.split()
self.model_info['kv_pairs'] = parts[6]
self.model_info['tensors'] = parts[9]
elif "general.architecture" in line:
self.model_info['architecture'] = line.split('=')[-1].strip()
elif line.startswith("llama_model_loader: - kv"):
key = line.split(':')[2].strip()
value = line.split('=')[-1].strip()
self.model_info.setdefault('kv_data', {})[key] = value
elif line.startswith("llama_model_loader: - type"):
parts = line.split(':')
if len(parts) > 1:
quant_type = parts[1].strip()
tensors = parts[2].strip().split()[0]
self.model_info.setdefault('quantization_type', []).append(f"{quant_type}: {tensors} tensors")
def terminate(self):
if self.process:
os.kill(self.process.pid, signal.SIGTERM)
self.process.wait(timeout=5)
if self.process.poll() is None:
os.kill(self.process.pid, signal.SIGKILL)

57
src/TaskListItem.py Normal file
View File

@ -0,0 +1,57 @@
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
class TaskListItem(QWidget):
def __init__(self, task_name, log_file, parent=None):
super().__init__(parent)
self.task_name = task_name
self.log_file = log_file
self.status = "Pending"
layout = QHBoxLayout(self)
self.task_label = QLabel(task_name)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.status_label = QLabel(self.status)
layout.addWidget(self.task_label)
layout.addWidget(self.progress_bar)
layout.addWidget(self.status_label)
self.progress_timer = QTimer(self)
self.progress_timer.timeout.connect(self.update_progress)
self.progress_value = 0
def update_status(self, status):
self.status = status
self.status_label.setText(status)
if status == "In Progress":
self.progress_bar.setRange(0, 100)
self.progress_timer.start(100)
elif status == "Completed":
self.progress_timer.stop()
self.progress_bar.setValue(100)
elif status == "Canceled":
self.progress_timer.stop()
self.progress_bar.setValue(0)
def set_error(self):
self.status = "Error"
self.status_label.setText("Error")
self.status_label.setStyleSheet("color: red;")
self.progress_bar.setRange(0, 100)
self.progress_timer.stop()
def update_progress(self):
self.progress_value = (self.progress_value + 1) % 101
self.progress_bar.setValue(self.progress_value)

View File

@ -0,0 +1,21 @@
import os
import sys
import psutil
import subprocess
import time
import signal
import json
import platform
import requests
import zipfile
from datetime import datetime
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QPushButton,
QListWidget, QLineEdit, QLabel, QFileDialog, QProgressBar, QComboBox, QTextEdit,
QCheckBox, QGroupBox, QFormLayout, QScrollArea, QSlider, QSpinBox, QListWidgetItem,
QMessageBox, QDialog, QPlainTextEdit, QMenu)
from PyQt6.QtCore import QTimer, QThread, pyqtSignal, Qt, QSize
from PyQt6.QtGui import QCloseEvent, QAction
def ensure_directory(path):
if not os.path.exists(path):
os.makedirs(path)

9
src/main.py Normal file
View File

@ -0,0 +1,9 @@
import sys
from PyQt6.QtWidgets import QApplication
from AutoGGUF import AutoGGUF
if __name__ == "__main__":
app = QApplication(sys.argv)
window = AutoGGUF()
window.show()
sys.exit(app.exec())