Skip to content

Commit eca1ac5

Browse files
committed
Initial commit
0 parents  commit eca1ac5

File tree

9 files changed

+681
-0
lines changed

9 files changed

+681
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

EasyQuantizationGUI.bat

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@echo off
2+
start /b pythonw EasyQuantizationGUI.py

EasyQuantizationGUI.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import tkinter as tk
2+
from tkinter import filedialog, ttk, messagebox
3+
import sys
4+
import os
5+
import subprocess
6+
import shutil
7+
import winsound
8+
import tkinter.scrolledtext as scrolledtext
9+
10+
def resource_path(relative_path):
11+
""" Get absolute path to resource, works for dev and for PyInstaller """
12+
try:
13+
# PyInstaller creates a temp folder and stores path in _MEIPASS
14+
base_path = sys._MEIPASS
15+
except Exception:
16+
base_path = os.path.abspath(".")
17+
18+
return os.path.join(base_path, relative_path)
19+
20+
def scroll_entry_to_end(entry):
21+
entry.xview_moveto(1)
22+
23+
def browse_file(entry):
24+
file_path = filedialog.askopenfilename(filetypes=[("Model files", "*.safetensors *.sft")])
25+
if file_path:
26+
entry.delete(0, tk.END)
27+
entry.insert(0, file_path)
28+
scroll_entry_to_end(entry)
29+
update_output_filename()
30+
31+
def browse_output_file(entry):
32+
file_path = filedialog.asksaveasfilename(defaultextension=".gguf",
33+
filetypes=[("GGUF files", "*.gguf")])
34+
if file_path:
35+
entry.delete(0, tk.END)
36+
entry.insert(0, file_path)
37+
scroll_entry_to_end(entry)
38+
39+
def update_output_filename(*args):
40+
input_file = input_entry.get()
41+
quantize_level = quantize_level_var.get()
42+
if input_file:
43+
input_dir = os.path.dirname(input_file)
44+
base_name = os.path.splitext(os.path.basename(input_file))[0]
45+
output_file = os.path.join(input_dir, f"{base_name}-{quantize_level}.gguf")
46+
if '/' in input_file:
47+
output_file = output_file.replace('\\', '/')
48+
elif '\\' in input_file:
49+
output_file = output_file.replace('/', '\\')
50+
output_entry.delete(0, tk.END)
51+
output_entry.insert(0, output_file)
52+
scroll_entry_to_end(output_entry)
53+
54+
def disable_ui():
55+
input_entry.config(state='disabled')
56+
output_entry.config(state='disabled')
57+
input_browse.config(state='disabled')
58+
output_browse.config(state='disabled')
59+
quantize_dropdown.config(state='disabled')
60+
run_button.config(state='disabled')
61+
62+
def enable_ui():
63+
input_entry.config(state='normal')
64+
output_entry.config(state='normal')
65+
input_browse.config(state='normal')
66+
output_browse.config(state='normal')
67+
quantize_dropdown.config(state='readonly')
68+
run_button.config(state='normal')
69+
70+
def run_llama_quantize():
71+
input_file = input_entry.get()
72+
output_file = output_entry.get()
73+
quantize_level = quantize_level_var.get()
74+
75+
if not input_file or not output_file:
76+
messagebox.showerror("Error", "Please select both input and output files.")
77+
return
78+
79+
output_dir = os.path.dirname(output_file)
80+
required_space = 40_000_000_000 # ~40 GB (a bit more than 36.5 GB)
81+
available_space = shutil.disk_usage(output_dir).free
82+
83+
if available_space < required_space:
84+
required_gb = required_space / (1024**3)
85+
available_gb = available_space / (1024**3)
86+
messagebox.showerror("Error", f"You need {required_gb:.1f} GB of drive space to continue. Only {available_gb:.1f} GB available.")
87+
return
88+
89+
disable_ui()
90+
91+
# Clear previous log
92+
process_text.delete('1.0', tk.END)
93+
process_text.insert(tk.END, "Starting conversion process...\n")
94+
process_text.see(tk.END)
95+
root.update()
96+
97+
# Convert the input file to GGUF format
98+
convert_py_path = resource_path("convert.py")
99+
output_dir = os.path.dirname(output_file)
100+
temp_gguf_file = os.path.join(output_dir, "temporary_file_during_quantization")
101+
102+
try:
103+
startupinfo = subprocess.STARTUPINFO()
104+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
105+
startupinfo.wShowWindow = subprocess.SW_HIDE
106+
107+
process = subprocess.Popen(["python", convert_py_path, "--src", input_file, "--dst", temp_gguf_file],
108+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
109+
bufsize=1, universal_newlines=True, startupinfo=startupinfo)
110+
111+
for line in process.stdout:
112+
process_text.insert(tk.END, line)
113+
process_text.see(tk.END)
114+
root.update()
115+
116+
process.wait()
117+
if process.returncode != 0:
118+
raise subprocess.CalledProcessError(process.returncode, process.args)
119+
120+
process_text.insert(tk.END, "Conversion completed successfully.\n")
121+
except subprocess.CalledProcessError as e:
122+
process_text.insert(tk.END, f"Error converting file: {e}\n")
123+
process_text.insert(tk.END, f"Command: {e.cmd}\n")
124+
process_text.insert(tk.END, f"Return code: {e.returncode}\n")
125+
process_text.see(tk.END)
126+
root.update()
127+
enable_ui()
128+
return
129+
130+
# Quantize the converted file
131+
llama_quantize_path = resource_path("llama-quantize.exe")
132+
process_text.insert(tk.END, "Starting quantization process...\n")
133+
process_text.see(tk.END)
134+
root.update()
135+
136+
try:
137+
startupinfo = subprocess.STARTUPINFO()
138+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
139+
startupinfo.wShowWindow = subprocess.SW_HIDE
140+
141+
process = subprocess.Popen([llama_quantize_path, temp_gguf_file, output_file, quantize_level],
142+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
143+
bufsize=1, universal_newlines=True, startupinfo=startupinfo)
144+
145+
for line in process.stdout:
146+
process_text.insert(tk.END, line)
147+
process_text.see(tk.END)
148+
root.update()
149+
150+
process.wait()
151+
if process.returncode != 0:
152+
raise subprocess.CalledProcessError(process.returncode, process.args)
153+
154+
process_text.insert(tk.END, "Quantization completed successfully.\n")
155+
except subprocess.CalledProcessError as e:
156+
process_text.insert(tk.END, f"Error running llama-quantize: {e}\n")
157+
process_text.insert(tk.END, f"Command: {e.cmd}\n")
158+
process_text.insert(tk.END, f"Return code: {e.returncode}\n")
159+
process_text.see(tk.END)
160+
root.update()
161+
finally:
162+
# Clean up the temporary file
163+
if os.path.exists(temp_gguf_file):
164+
os.remove(temp_gguf_file)
165+
166+
process_text.insert(tk.END, "Quantization process completed.")
167+
process_text.see(tk.END)
168+
root.update()
169+
170+
enable_ui()
171+
172+
# Play sound effect
173+
winsound.PlaySound("SystemAsterisk", winsound.SND_ALIAS)
174+
175+
root = tk.Tk()
176+
root.title("Easy Quantization GUI")
177+
root.geometry("800x600") # Enlarge the main window
178+
179+
# Quantize level selection
180+
quantize_frame = tk.Frame(root)
181+
quantize_frame.pack(pady=10, padx=10)
182+
183+
quantize_label = tk.Label(quantize_frame, text="Quantize Level:")
184+
quantize_label.pack(side=tk.LEFT)
185+
186+
quantize_levels = ["Q2_K", "Q3_K_S", "Q4_0", "Q4_1", "Q4_K_S", "Q5_0", "Q5_1", "Q5_K_S", "Q6_K", "Q8_0"]
187+
quantize_level_var = tk.StringVar(root)
188+
quantize_level_var.set("Q8_0") # Set default value to Q8_0
189+
190+
quantize_dropdown = ttk.Combobox(quantize_frame, textvariable=quantize_level_var, values=quantize_levels, state="readonly")
191+
quantize_dropdown.pack(side=tk.LEFT)
192+
quantize_dropdown.bind("<<ComboboxSelected>>", lambda event: scroll_entry_to_end(output_entry))
193+
194+
# Input file selection
195+
input_frame = tk.Frame(root)
196+
input_frame.pack(pady=10, padx=10, fill=tk.X)
197+
198+
input_label = tk.Label(input_frame, text="Input File:")
199+
input_label.pack(side=tk.LEFT)
200+
201+
input_entry = tk.Entry(input_frame)
202+
input_entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
203+
204+
input_browse = tk.Button(input_frame, text="Browse", command=lambda: browse_file(input_entry))
205+
input_browse.pack(side=tk.RIGHT)
206+
207+
# Add binding to scroll input entry when it gains focus
208+
input_entry.bind("<FocusIn>", lambda event: scroll_entry_to_end(input_entry))
209+
210+
# Output file selection
211+
output_frame = tk.Frame(root)
212+
output_frame.pack(pady=10, padx=10, fill=tk.X)
213+
214+
output_label = tk.Label(output_frame, text="Output File:")
215+
output_label.pack(side=tk.LEFT)
216+
217+
output_entry = tk.Entry(output_frame)
218+
output_entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
219+
220+
output_browse = tk.Button(output_frame, text="Browse", command=lambda: browse_output_file(output_entry))
221+
output_browse.pack(side=tk.RIGHT)
222+
223+
# Add binding to scroll output entry when it gains focus
224+
output_entry.bind("<FocusIn>", lambda event: scroll_entry_to_end(output_entry))
225+
226+
# Run button
227+
run_button = tk.Button(root, text="Run Quantization", command=run_llama_quantize)
228+
run_button.pack(pady=20)
229+
230+
# Bind events to update output filename
231+
input_entry.bind("<KeyRelease>", update_output_filename)
232+
quantize_level_var.trace_add("write", update_output_filename)
233+
234+
def on_window_resize(event):
235+
scroll_entry_to_end(input_entry)
236+
scroll_entry_to_end(output_entry)
237+
238+
root.bind("<Configure>", on_window_resize)
239+
240+
# Add process log to main window
241+
process_frame = tk.Frame(root)
242+
process_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
243+
244+
process_label = tk.Label(process_frame, text="Process Log:")
245+
process_label.pack(side=tk.TOP, anchor='w')
246+
247+
process_text = scrolledtext.ScrolledText(process_frame, wrap=tk.WORD, height=15)
248+
process_text.pack(expand=True, fill=tk.BOTH)
249+
250+
root.mainloop()

0 commit comments

Comments
 (0)