Upload gui.py
Browse files
gui.py
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Graphical User Interface for HPC-Quantize V2 (Factored Q2_K Engine).
|
| 3 |
+
|
| 4 |
+
Launch::
|
| 5 |
+
|
| 6 |
+
python3 gui.py
|
| 7 |
+
|
| 8 |
+
Provides:
|
| 9 |
+
* Source GGUF, iMatrix (.gguf / .dat), and Output (.gguf / .npz) file pickers
|
| 10 |
+
* Interactive GGUF tensor table with Q2_K eligibility & iMatrix match status
|
| 11 |
+
* Full HPCConfig controls (Geometry-Preserving Analogs, Coupled Bilinear Q2_K,
|
| 12 |
+
Sieve + 36-state Viterbi, Spectral/Vesica/DC shaping, Candidate lattice size)
|
| 13 |
+
* Live CLI command preview with one-click copy
|
| 14 |
+
* Threaded execution with per-tensor progress bar, live log, and cancellation
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import argparse
|
| 20 |
+
import queue
|
| 21 |
+
import shlex
|
| 22 |
+
import threading
|
| 23 |
+
import time
|
| 24 |
+
import tkinter as tk
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
from tkinter import filedialog, messagebox, ttk
|
| 27 |
+
|
| 28 |
+
import numpy as np
|
| 29 |
+
|
| 30 |
+
from hpc_quantize_v2 import (
|
| 31 |
+
GGUFReader,
|
| 32 |
+
HPCConfig,
|
| 33 |
+
Q2KQuantizer,
|
| 34 |
+
expand_importance,
|
| 35 |
+
factor_only_solve,
|
| 36 |
+
lookup_imatrix,
|
| 37 |
+
read_imatrix,
|
| 38 |
+
)
|
| 39 |
+
from hpc_quantize_v2.gguf import is_q2k_eligible, write_quantized_gguf
|
| 40 |
+
from hpc_quantize_v2.reference import reference_solve, synthetic_cases
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class HPCQuantizeGUI:
|
| 44 |
+
def __init__(self, root: tk.Tk) -> None:
|
| 45 |
+
self.root = root
|
| 46 |
+
self.root.title("HPC-Quantize V2 — Factored Q2_K & Geometry-Preserving Analog Engine")
|
| 47 |
+
self.root.geometry("1180x820")
|
| 48 |
+
self.root.minsize(980, 680)
|
| 49 |
+
|
| 50 |
+
self._log_queue: queue.Queue[tuple[str, object]] = queue.Queue()
|
| 51 |
+
self._stop_event = threading.Event()
|
| 52 |
+
self._worker: threading.Thread | None = None
|
| 53 |
+
self._reader: GGUFReader | None = None
|
| 54 |
+
self._imatrix: dict[str, np.ndarray] | None = None
|
| 55 |
+
|
| 56 |
+
self._init_vars()
|
| 57 |
+
self._apply_theme()
|
| 58 |
+
self._build_layout()
|
| 59 |
+
self._auto_populate_defaults()
|
| 60 |
+
self._update_cli_preview()
|
| 61 |
+
self.root.after(100, self._poll_queue)
|
| 62 |
+
|
| 63 |
+
def _init_vars(self) -> None:
|
| 64 |
+
self.gguf_var = tk.StringVar()
|
| 65 |
+
self.imatrix_var = tk.StringVar()
|
| 66 |
+
self.output_var = tk.StringVar()
|
| 67 |
+
self.mode_var = tk.StringVar(value="all")
|
| 68 |
+
self.filter_var = tk.StringVar()
|
| 69 |
+
|
| 70 |
+
self.optimizer_var = tk.StringVar(value="hpc")
|
| 71 |
+
self.cand_d_var = tk.IntVar(value=6)
|
| 72 |
+
self.cand_m_var = tk.IntVar(value=6)
|
| 73 |
+
self.starts_var = tk.IntVar(value=2)
|
| 74 |
+
self.iters_var = tk.IntVar(value=8)
|
| 75 |
+
self.ulp_var = tk.IntVar(value=4)
|
| 76 |
+
self.limit_var = tk.IntVar(value=0)
|
| 77 |
+
|
| 78 |
+
self.use_analogs_var = tk.BooleanVar(value=True)
|
| 79 |
+
self.coupled_q2k_var = tk.BooleanVar(value=True)
|
| 80 |
+
self.joint_subscales_var = tk.BooleanVar(value=True)
|
| 81 |
+
self.use_spectral_var = tk.BooleanVar(value=True)
|
| 82 |
+
self.subblock_sieve_var = tk.BooleanVar(value=True)
|
| 83 |
+
self.factor_floor_var = tk.BooleanVar(value=True)
|
| 84 |
+
self.cand_floor_var = tk.BooleanVar(value=True)
|
| 85 |
+
self.dc_carry_var = tk.BooleanVar(value=True)
|
| 86 |
+
|
| 87 |
+
self.dc_lambda_var = tk.DoubleVar(value=1.0)
|
| 88 |
+
self.vw_lambda_var = tk.DoubleVar(value=1.0)
|
| 89 |
+
self.dc_decay_var = tk.DoubleVar(value=0.85)
|
| 90 |
+
self.sse_budget_var = tk.DoubleVar(value=0.0005)
|
| 91 |
+
|
| 92 |
+
self.cli_preview_var = tk.StringVar()
|
| 93 |
+
self.status_var = tk.StringVar(value="Ready.")
|
| 94 |
+
self.progress_var = tk.DoubleVar(value=0.0)
|
| 95 |
+
|
| 96 |
+
for v in (
|
| 97 |
+
self.gguf_var,
|
| 98 |
+
self.imatrix_var,
|
| 99 |
+
self.output_var,
|
| 100 |
+
self.mode_var,
|
| 101 |
+
self.optimizer_var,
|
| 102 |
+
self.cand_d_var,
|
| 103 |
+
self.cand_m_var,
|
| 104 |
+
self.limit_var,
|
| 105 |
+
self.use_spectral_var,
|
| 106 |
+
):
|
| 107 |
+
v.trace_add("write", lambda *_: self._update_cli_preview())
|
| 108 |
+
|
| 109 |
+
def _apply_theme(self) -> None:
|
| 110 |
+
style = ttk.Style(self.root)
|
| 111 |
+
if "clam" in style.theme_names():
|
| 112 |
+
style.theme_use("clam")
|
| 113 |
+
bg = "#141820"
|
| 114 |
+
panel = "#1c222d"
|
| 115 |
+
fg = "#e6edf3"
|
| 116 |
+
accent = "#3b82f6"
|
| 117 |
+
self.root.configure(bg=bg)
|
| 118 |
+
style.configure(".", background=bg, foreground=fg, fieldbackground=panel)
|
| 119 |
+
style.configure("TFrame", background=bg)
|
| 120 |
+
style.configure("TLabelframe", background=bg, foreground=fg)
|
| 121 |
+
style.configure("TLabelframe.Label", background=bg, foreground="#93c5fd", font=("Sans", 10, "bold"))
|
| 122 |
+
style.configure("TLabel", background=bg, foreground=fg)
|
| 123 |
+
style.configure("TCheckbutton", background=bg, foreground=fg)
|
| 124 |
+
style.configure("TRadiobutton", background=bg, foreground=fg)
|
| 125 |
+
style.configure("TButton", background=panel, foreground=fg, padding=(10, 5))
|
| 126 |
+
style.map("TButton", background=[("active", accent)])
|
| 127 |
+
style.configure("Accent.TButton", background=accent, foreground="#ffffff", font=("Sans", 10, "bold"))
|
| 128 |
+
style.configure(
|
| 129 |
+
"Treeview",
|
| 130 |
+
background=panel,
|
| 131 |
+
fieldbackground=panel,
|
| 132 |
+
foreground=fg,
|
| 133 |
+
rowheight=24,
|
| 134 |
+
)
|
| 135 |
+
style.configure("Treeview.Heading", background="#262f3f", foreground=fg, font=("Sans", 9, "bold"))
|
| 136 |
+
style.map("Treeview", background=[("selected", accent)])
|
| 137 |
+
|
| 138 |
+
def _build_layout(self) -> None:
|
| 139 |
+
main = ttk.Frame(self.root, padding=10)
|
| 140 |
+
main.pack(fill=tk.BOTH, expand=True)
|
| 141 |
+
|
| 142 |
+
# 1. File Selection Frame
|
| 143 |
+
files_frame = ttk.LabelFrame(main, text="1. GGUF & iMatrix Paths", padding=10)
|
| 144 |
+
files_frame.pack(fill=tk.X, pady=(0, 8))
|
| 145 |
+
files_frame.columnconfigure(1, weight=1)
|
| 146 |
+
|
| 147 |
+
ttk.Label(files_frame, text="Source GGUF:").grid(row=0, column=0, sticky=tk.W, padx=(0, 8), pady=3)
|
| 148 |
+
ttk.Entry(files_frame, textvariable=self.gguf_var).grid(row=0, column=1, sticky=tk.EW, pady=3)
|
| 149 |
+
ttk.Button(files_frame, text="Browse...", command=self._browse_gguf).grid(row=0, column=2, padx=4, pady=3)
|
| 150 |
+
ttk.Button(files_frame, text="Inspect GGUF", command=self.inspect_files).grid(row=0, column=3, pady=3)
|
| 151 |
+
|
| 152 |
+
ttk.Label(files_frame, text="iMatrix (.gguf/.dat):").grid(row=1, column=0, sticky=tk.W, padx=(0, 8), pady=3)
|
| 153 |
+
ttk.Entry(files_frame, textvariable=self.imatrix_var).grid(row=1, column=1, sticky=tk.EW, pady=3)
|
| 154 |
+
ttk.Button(files_frame, text="Browse...", command=self._browse_imatrix).grid(row=1, column=2, padx=4, pady=3)
|
| 155 |
+
ttk.Button(files_frame, text="Clear", command=lambda: self.imatrix_var.set("")).grid(row=1, column=3, pady=3)
|
| 156 |
+
|
| 157 |
+
ttk.Label(files_frame, text="Output (.gguf/.npz):").grid(row=2, column=0, sticky=tk.W, padx=(0, 8), pady=3)
|
| 158 |
+
ttk.Entry(files_frame, textvariable=self.output_var).grid(row=2, column=1, sticky=tk.EW, pady=3)
|
| 159 |
+
ttk.Button(files_frame, text="Save As...", command=self._browse_output).grid(row=2, column=2, padx=4, pady=3)
|
| 160 |
+
|
| 161 |
+
# 2. Middle Split: Left = Settings, Right = Tensor Browser
|
| 162 |
+
mid = ttk.PanedWindow(main, orient=tk.HORIZONTAL)
|
| 163 |
+
mid.pack(fill=tk.BOTH, expand=True, pady=(0, 8))
|
| 164 |
+
|
| 165 |
+
left_pane = ttk.Frame(mid)
|
| 166 |
+
right_pane = ttk.Frame(mid)
|
| 167 |
+
mid.add(left_pane, weight=2)
|
| 168 |
+
mid.add(right_pane, weight=3)
|
| 169 |
+
|
| 170 |
+
# Left: Engine & Factorization Options
|
| 171 |
+
opt_frame = ttk.LabelFrame(left_pane, text="2. Factored Q2_K & Analog Controls", padding=10)
|
| 172 |
+
opt_frame.pack(fill=tk.BOTH, expand=True, padx=(0, 6))
|
| 173 |
+
|
| 174 |
+
mode_row = ttk.Frame(opt_frame)
|
| 175 |
+
mode_row.pack(fill=tk.X, pady=(0, 6))
|
| 176 |
+
ttk.Label(mode_row, text="Target Scope:").pack(side=tk.LEFT, padx=(0, 8))
|
| 177 |
+
ttk.Radiobutton(
|
| 178 |
+
mode_row, text="All Q2_K-Eligible Tensors", variable=self.mode_var, value="all"
|
| 179 |
+
).pack(side=tk.LEFT, padx=4)
|
| 180 |
+
ttk.Radiobutton(
|
| 181 |
+
mode_row, text="Selected Tensor(s)", variable=self.mode_var, value="selected"
|
| 182 |
+
).pack(side=tk.LEFT, padx=4)
|
| 183 |
+
|
| 184 |
+
opt_row = ttk.Frame(opt_frame)
|
| 185 |
+
opt_row.pack(fill=tk.X, pady=(0, 6))
|
| 186 |
+
ttk.Label(opt_row, text="Global Optimizer:").pack(side=tk.LEFT, padx=(0, 8))
|
| 187 |
+
ttk.Radiobutton(
|
| 188 |
+
opt_row, text="HPC (Sieve + 36-State Viterbi)", variable=self.optimizer_var, value="hpc"
|
| 189 |
+
).pack(side=tk.LEFT, padx=4)
|
| 190 |
+
ttk.Radiobutton(
|
| 191 |
+
opt_row, text="MSE (Local Greedy)", variable=self.optimizer_var, value="mse"
|
| 192 |
+
).pack(side=tk.LEFT, padx=4)
|
| 193 |
+
|
| 194 |
+
checks = ttk.Frame(opt_frame)
|
| 195 |
+
checks.pack(fill=tk.X, pady=4)
|
| 196 |
+
ttk.Checkbutton(
|
| 197 |
+
checks,
|
| 198 |
+
text="Geometry-Preserving Weight Analogs (Iso-Moment + Vesica Gram + Subscale Lattice)",
|
| 199 |
+
variable=self.use_analogs_var,
|
| 200 |
+
).pack(anchor=tk.W, pady=2)
|
| 201 |
+
ttk.Checkbutton(
|
| 202 |
+
checks,
|
| 203 |
+
text="Coupled Orthogonal-Basis Bilinear Q2_K Factorization",
|
| 204 |
+
variable=self.coupled_q2k_var,
|
| 205 |
+
).pack(anchor=tk.W, pady=2)
|
| 206 |
+
ttk.Checkbutton(
|
| 207 |
+
checks,
|
| 208 |
+
text="Exact Joint (Ls, Lm) 16x16 Sub-block Scan",
|
| 209 |
+
variable=self.joint_subscales_var,
|
| 210 |
+
).pack(anchor=tk.W, pady=2)
|
| 211 |
+
ttk.Checkbutton(
|
| 212 |
+
checks,
|
| 213 |
+
text="Spectral Fold / Vesica / DC Objective Penalty",
|
| 214 |
+
variable=self.use_spectral_var,
|
| 215 |
+
).pack(anchor=tk.W, pady=2)
|
| 216 |
+
ttk.Checkbutton(
|
| 217 |
+
checks,
|
| 218 |
+
text="Sub-block 6-State Sieve & Rolling DC Residual Carry",
|
| 219 |
+
variable=self.subblock_sieve_var,
|
| 220 |
+
).pack(anchor=tk.W, pady=2)
|
| 221 |
+
ttk.Checkbutton(
|
| 222 |
+
checks,
|
| 223 |
+
text="Local-Safety Candidate & Factored Floors (Monotone Bound)",
|
| 224 |
+
variable=self.factor_floor_var,
|
| 225 |
+
).pack(anchor=tk.W, pady=2)
|
| 226 |
+
|
| 227 |
+
grid_f = ttk.Frame(opt_frame)
|
| 228 |
+
grid_f.pack(fill=tk.X, pady=6)
|
| 229 |
+
for col in range(4):
|
| 230 |
+
grid_f.columnconfigure(col, weight=1)
|
| 231 |
+
|
| 232 |
+
self._spin_pair(grid_f, 0, 0, "Cand d (1-24):", self.cand_d_var, 1, 24)
|
| 233 |
+
self._spin_pair(grid_f, 0, 2, "Cand dmin (1-24):", self.cand_m_var, 1, 24)
|
| 234 |
+
self._spin_pair(grid_f, 1, 0, "Solver Starts:", self.starts_var, 1, 8)
|
| 235 |
+
self._spin_pair(grid_f, 1, 2, "Solver Iters:", self.iters_var, 1, 32)
|
| 236 |
+
self._spin_pair(grid_f, 2, 0, "FP16 ULP Radius:", self.ulp_var, 0, 16)
|
| 237 |
+
self._spin_pair(grid_f, 2, 2, "Tensor Limit (0=all):", self.limit_var, 0, 9999)
|
| 238 |
+
|
| 239 |
+
spec_f = ttk.LabelFrame(opt_frame, text="Spectral & DC Parameters", padding=6)
|
| 240 |
+
spec_f.pack(fill=tk.X, pady=4)
|
| 241 |
+
for col in range(4):
|
| 242 |
+
spec_f.columnconfigure(col, weight=1)
|
| 243 |
+
ttk.Label(spec_f, text="DC Lambda:").grid(row=0, column=0, sticky=tk.W, padx=4, pady=2)
|
| 244 |
+
ttk.Entry(spec_f, textvariable=self.dc_lambda_var, width=8).grid(row=0, column=1, sticky=tk.EW, padx=4, pady=2)
|
| 245 |
+
ttk.Label(spec_f, text="Vesica Lambda:").grid(row=0, column=2, sticky=tk.W, padx=4, pady=2)
|
| 246 |
+
ttk.Entry(spec_f, textvariable=self.vw_lambda_var, width=8).grid(row=0, column=3, sticky=tk.EW, padx=4, pady=2)
|
| 247 |
+
ttk.Label(spec_f, text="DC Decay:").grid(row=1, column=0, sticky=tk.W, padx=4, pady=2)
|
| 248 |
+
ttk.Entry(spec_f, textvariable=self.dc_decay_var, width=8).grid(row=1, column=1, sticky=tk.EW, padx=4, pady=2)
|
| 249 |
+
ttk.Label(spec_f, text="SSE Budget:").grid(row=1, column=2, sticky=tk.W, padx=4, pady=2)
|
| 250 |
+
ttk.Entry(spec_f, textvariable=self.sse_budget_var, width=8).grid(row=1, column=3, sticky=tk.EW, padx=4, pady=2)
|
| 251 |
+
|
| 252 |
+
# Right: GGUF Tensor Browser
|
| 253 |
+
browser_frame = ttk.LabelFrame(right_pane, text="3. GGUF Tensor Inspector", padding=10)
|
| 254 |
+
browser_frame.pack(fill=tk.BOTH, expand=True)
|
| 255 |
+
|
| 256 |
+
search_row = ttk.Frame(browser_frame)
|
| 257 |
+
search_row.pack(fill=tk.X, pady=(0, 6))
|
| 258 |
+
ttk.Label(search_row, text="Filter:").pack(side=tk.LEFT, padx=(0, 6))
|
| 259 |
+
filter_entry = ttk.Entry(search_row, textvariable=self.filter_var)
|
| 260 |
+
filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
| 261 |
+
filter_entry.bind("<KeyRelease>", lambda _e: self._populate_tensor_tree())
|
| 262 |
+
ttk.Button(
|
| 263 |
+
search_row, text="Select Eligible", command=self._select_all_eligible
|
| 264 |
+
).pack(side=tk.LEFT, padx=4)
|
| 265 |
+
|
| 266 |
+
cols = ("name", "type", "dims", "row", "q2k", "imat")
|
| 267 |
+
self.tree = ttk.Treeview(browser_frame, columns=cols, show="headings", selectmode="extended")
|
| 268 |
+
self.tree.heading("name", text="Tensor Name")
|
| 269 |
+
self.tree.heading("type", text="Type")
|
| 270 |
+
self.tree.heading("dims", text="Dims")
|
| 271 |
+
self.tree.heading("row", text="Row Width")
|
| 272 |
+
self.tree.heading("q2k", text="Q2_K")
|
| 273 |
+
self.tree.heading("imat", text="iMatrix")
|
| 274 |
+
|
| 275 |
+
self.tree.column("name", width=250, anchor=tk.W)
|
| 276 |
+
self.tree.column("type", width=60, anchor=tk.CENTER)
|
| 277 |
+
self.tree.column("dims", width=110, anchor=tk.CENTER)
|
| 278 |
+
self.tree.column("row", width=75, anchor=tk.E)
|
| 279 |
+
self.tree.column("q2k", width=55, anchor=tk.CENTER)
|
| 280 |
+
self.tree.column("imat", width=65, anchor=tk.CENTER)
|
| 281 |
+
self.tree.bind("<<TreeviewSelect>>", lambda _e: self._update_cli_preview())
|
| 282 |
+
|
| 283 |
+
tree_scroll = ttk.Scrollbar(browser_frame, orient=tk.VERTICAL, command=self.tree.yview)
|
| 284 |
+
self.tree.configure(yscrollcommand=tree_scroll.set)
|
| 285 |
+
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
| 286 |
+
tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
| 287 |
+
|
| 288 |
+
# 4. Equivalent CLI Command Preview
|
| 289 |
+
cli_frame = ttk.LabelFrame(main, text="4. Equivalent Terminal Command", padding=8)
|
| 290 |
+
cli_frame.pack(fill=tk.X, pady=(0, 8))
|
| 291 |
+
ttk.Entry(cli_frame, textvariable=self.cli_preview_var, state="readonly").pack(
|
| 292 |
+
side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 6)
|
| 293 |
+
)
|
| 294 |
+
ttk.Button(cli_frame, text="Copy Command", command=self._copy_cli).pack(side=tk.RIGHT)
|
| 295 |
+
|
| 296 |
+
# 5. Action Buttons + Progress + Log Console
|
| 297 |
+
bottom = ttk.LabelFrame(main, text="5. Execution & Live Output", padding=8)
|
| 298 |
+
bottom.pack(fill=tk.BOTH, expand=False)
|
| 299 |
+
|
| 300 |
+
btn_row = ttk.Frame(bottom)
|
| 301 |
+
btn_row.pack(fill=tk.X, pady=(0, 6))
|
| 302 |
+
self.run_btn = ttk.Button(
|
| 303 |
+
btn_row, text="Run Quantization", style="Accent.TButton", command=self.start_quantization
|
| 304 |
+
)
|
| 305 |
+
self.run_btn.pack(side=tk.LEFT, padx=(0, 6))
|
| 306 |
+
self.stop_btn = ttk.Button(
|
| 307 |
+
btn_row, text="Cancel", state=tk.DISABLED, command=self.stop_worker
|
| 308 |
+
)
|
| 309 |
+
self.stop_btn.pack(side=tk.LEFT, padx=(0, 6))
|
| 310 |
+
self.bench_btn = ttk.Button(
|
| 311 |
+
btn_row, text="Run Solver Benchmark", command=self.start_benchmark
|
| 312 |
+
)
|
| 313 |
+
self.bench_btn.pack(side=tk.LEFT, padx=(0, 12))
|
| 314 |
+
ttk.Label(btn_row, textvariable=self.status_var).pack(side=tk.LEFT, fill=tk.X, expand=True)
|
| 315 |
+
|
| 316 |
+
self.progress = ttk.Progressbar(bottom, variable=self.progress_var, maximum=100.0)
|
| 317 |
+
self.progress.pack(fill=tk.X, pady=(0, 6))
|
| 318 |
+
|
| 319 |
+
log_frame = ttk.Frame(bottom)
|
| 320 |
+
log_frame.pack(fill=tk.BOTH, expand=True)
|
| 321 |
+
self.log_text = tk.Text(
|
| 322 |
+
log_frame,
|
| 323 |
+
height=9,
|
| 324 |
+
bg="#0f131a",
|
| 325 |
+
fg="#d1d5db",
|
| 326 |
+
insertbackground="#ffffff",
|
| 327 |
+
font=("Monospace", 9),
|
| 328 |
+
wrap=tk.WORD,
|
| 329 |
+
)
|
| 330 |
+
log_scroll = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
|
| 331 |
+
self.log_text.configure(yscrollcommand=log_scroll.set)
|
| 332 |
+
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
| 333 |
+
log_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
| 334 |
+
|
| 335 |
+
def _spin_pair(self, parent, row, col, label, var, low, high) -> None:
|
| 336 |
+
ttk.Label(parent, text=label).grid(row=row, column=col, sticky=tk.W, padx=4, pady=2)
|
| 337 |
+
sp = ttk.Spinbox(parent, from_=low, to=high, textvariable=var, width=6)
|
| 338 |
+
sp.grid(row=row, column=col + 1, sticky=tk.EW, padx=4, pady=2)
|
| 339 |
+
|
| 340 |
+
def _auto_populate_defaults(self) -> None:
|
| 341 |
+
default_gguf = Path("Raon.gguf")
|
| 342 |
+
default_imat = Path("imatrix.gguf")
|
| 343 |
+
if default_imat.exists():
|
| 344 |
+
self.imatrix_var.set(str(default_imat.resolve()))
|
| 345 |
+
if default_gguf.exists():
|
| 346 |
+
self.gguf_var.set(str(default_gguf.resolve()))
|
| 347 |
+
self.output_var.set(str(default_gguf.with_name("Raon-Q2K.gguf").resolve()))
|
| 348 |
+
self.inspect_files()
|
| 349 |
+
|
| 350 |
+
def _browse_gguf(self) -> None:
|
| 351 |
+
path = filedialog.askopenfilename(
|
| 352 |
+
title="Select Source GGUF",
|
| 353 |
+
filetypes=[("GGUF Model", "*.gguf"), ("All Files", "*.*")],
|
| 354 |
+
)
|
| 355 |
+
if path:
|
| 356 |
+
self.gguf_var.set(path)
|
| 357 |
+
p = Path(path)
|
| 358 |
+
if not self.output_var.get():
|
| 359 |
+
self.output_var.set(str(p.with_name(f"{p.stem}-Q2K.gguf")))
|
| 360 |
+
self.inspect_files()
|
| 361 |
+
|
| 362 |
+
def _browse_imatrix(self) -> None:
|
| 363 |
+
path = filedialog.askopenfilename(
|
| 364 |
+
title="Select iMatrix File",
|
| 365 |
+
filetypes=[("iMatrix Files", "*.gguf *.dat *.imatrix"), ("All Files", "*.*")],
|
| 366 |
+
)
|
| 367 |
+
if path:
|
| 368 |
+
self.imatrix_var.set(path)
|
| 369 |
+
self.inspect_files()
|
| 370 |
+
|
| 371 |
+
def _browse_output(self) -> None:
|
| 372 |
+
path = filedialog.asksaveasfilename(
|
| 373 |
+
title="Select Output File",
|
| 374 |
+
defaultextension=".gguf",
|
| 375 |
+
filetypes=[("GGUF Model", "*.gguf"), ("NumPy Archive", "*.npz"), ("All Files", "*.*")],
|
| 376 |
+
)
|
| 377 |
+
if path:
|
| 378 |
+
self.output_var.set(path)
|
| 379 |
+
|
| 380 |
+
def _log(self, msg: str) -> None:
|
| 381 |
+
self.log_text.insert(tk.END, msg + "\n")
|
| 382 |
+
self.log_text.see(tk.END)
|
| 383 |
+
|
| 384 |
+
def build_config(self) -> HPCConfig:
|
| 385 |
+
cfg = HPCConfig(
|
| 386 |
+
n_cand_d=int(self.cand_d_var.get()),
|
| 387 |
+
n_cand_m=int(self.cand_m_var.get()),
|
| 388 |
+
factor_starts=int(self.starts_var.get()),
|
| 389 |
+
factor_standalone_iters=int(self.iters_var.get()),
|
| 390 |
+
ulp_radius=int(self.ulp_var.get()),
|
| 391 |
+
optimizer=self.optimizer_var.get(),
|
| 392 |
+
use_weight_analogs=bool(self.use_analogs_var.get()),
|
| 393 |
+
factor_coupled_q2k=bool(self.coupled_q2k_var.get()),
|
| 394 |
+
factor_joint_subscales=bool(self.joint_subscales_var.get()),
|
| 395 |
+
use_spectral=bool(self.use_spectral_var.get()),
|
| 396 |
+
subblock_sieve=bool(self.subblock_sieve_var.get()),
|
| 397 |
+
use_factor_floor=bool(self.factor_floor_var.get()),
|
| 398 |
+
use_candidate_floor=bool(self.cand_floor_var.get()),
|
| 399 |
+
apply_dc_carry=bool(self.dc_carry_var.get()),
|
| 400 |
+
dc_lambda=float(self.dc_lambda_var.get()),
|
| 401 |
+
vw_lambda=float(self.vw_lambda_var.get()),
|
| 402 |
+
dc_decay=float(self.dc_decay_var.get()),
|
| 403 |
+
sse_budget=float(self.sse_budget_var.get()),
|
| 404 |
+
verbose=False,
|
| 405 |
+
)
|
| 406 |
+
return cfg.validate()
|
| 407 |
+
|
| 408 |
+
def build_cli_command(self) -> str:
|
| 409 |
+
cmd = ["python3", "-m", "hpc_quantize_v2"]
|
| 410 |
+
gguf = self.gguf_var.get().strip()
|
| 411 |
+
if gguf:
|
| 412 |
+
cmd += ["--gguf", gguf]
|
| 413 |
+
selected = self._selected_tensor_names()
|
| 414 |
+
if self.mode_var.get() == "selected" and len(selected) == 1:
|
| 415 |
+
cmd += ["--tensor", next(iter(selected))]
|
| 416 |
+
else:
|
| 417 |
+
cmd.append("--all-q2k")
|
| 418 |
+
imat = self.imatrix_var.get().strip()
|
| 419 |
+
if imat:
|
| 420 |
+
cmd += ["--imatrix-gguf", imat]
|
| 421 |
+
out = self.output_var.get().strip()
|
| 422 |
+
if out:
|
| 423 |
+
cmd += ["--output", out]
|
| 424 |
+
if self.limit_var.get() > 0:
|
| 425 |
+
cmd += ["--limit", str(self.limit_var.get())]
|
| 426 |
+
cmd += ["--cand-d", str(self.cand_d_var.get()), "--cand-m", str(self.cand_m_var.get())]
|
| 427 |
+
if self.optimizer_var.get() == "mse":
|
| 428 |
+
cmd.append("--mse")
|
| 429 |
+
if not self.use_spectral_var.get():
|
| 430 |
+
cmd.append("--no-spectral")
|
| 431 |
+
cmd.append("--verbose")
|
| 432 |
+
return " ".join(shlex.quote(part) for part in cmd)
|
| 433 |
+
|
| 434 |
+
def _update_cli_preview(self) -> None:
|
| 435 |
+
try:
|
| 436 |
+
self.cli_preview_var.set(self.build_cli_command())
|
| 437 |
+
except Exception:
|
| 438 |
+
pass
|
| 439 |
+
|
| 440 |
+
def _copy_cli(self) -> None:
|
| 441 |
+
cmd = self.cli_preview_var.get()
|
| 442 |
+
self.root.clipboard_clear()
|
| 443 |
+
self.root.clipboard_append(cmd)
|
| 444 |
+
self.status_var.set("Copied CLI command to clipboard.")
|
| 445 |
+
|
| 446 |
+
def inspect_files(self) -> None:
|
| 447 |
+
imat_path = self.imatrix_var.get().strip()
|
| 448 |
+
if imat_path and Path(imat_path).exists():
|
| 449 |
+
try:
|
| 450 |
+
self._imatrix = read_imatrix(imat_path)
|
| 451 |
+
self._log(f"Loaded iMatrix: {len(self._imatrix)} entries from {imat_path}")
|
| 452 |
+
except Exception as exc:
|
| 453 |
+
self._imatrix = None
|
| 454 |
+
self._log(f"iMatrix load error: {exc}")
|
| 455 |
+
else:
|
| 456 |
+
self._imatrix = None
|
| 457 |
+
|
| 458 |
+
gguf_path = self.gguf_var.get().strip()
|
| 459 |
+
if not gguf_path or not Path(gguf_path).exists():
|
| 460 |
+
return
|
| 461 |
+
try:
|
| 462 |
+
self._reader = GGUFReader(gguf_path)
|
| 463 |
+
eligible = sum(1 for t in self._reader.tensors if is_q2k_eligible(t.name, t.dims))
|
| 464 |
+
self._log(
|
| 465 |
+
f"Inspected {gguf_path}: {len(self._reader.tensors)} tensors "
|
| 466 |
+
f"({eligible} Q2_K-eligible), {len(self._reader.metadata)} metadata keys."
|
| 467 |
+
)
|
| 468 |
+
self.status_var.set(f"Loaded {len(self._reader.tensors)} tensors ({eligible} Q2_K-eligible).")
|
| 469 |
+
self._populate_tensor_tree()
|
| 470 |
+
except Exception as exc:
|
| 471 |
+
self._reader = None
|
| 472 |
+
self._log(f"GGUF inspect error: {exc}")
|
| 473 |
+
|
| 474 |
+
def _populate_tensor_tree(self) -> None:
|
| 475 |
+
self.tree.delete(*self.tree.get_children())
|
| 476 |
+
if self._reader is None:
|
| 477 |
+
return
|
| 478 |
+
filt = self.filter_var.get().strip().lower()
|
| 479 |
+
for ti in self._reader.tensors:
|
| 480 |
+
if filt and filt not in ti.name.lower() and filt not in ti.type_name.lower():
|
| 481 |
+
continue
|
| 482 |
+
elig = is_q2k_eligible(ti.name, ti.dims)
|
| 483 |
+
has_imat = (
|
| 484 |
+
self._imatrix is not None
|
| 485 |
+
and lookup_imatrix(self._imatrix, ti.name) is not None
|
| 486 |
+
)
|
| 487 |
+
self.tree.insert(
|
| 488 |
+
"",
|
| 489 |
+
tk.END,
|
| 490 |
+
iid=ti.name,
|
| 491 |
+
values=(
|
| 492 |
+
ti.name,
|
| 493 |
+
ti.type_name,
|
| 494 |
+
str(ti.dims),
|
| 495 |
+
ti.row_width,
|
| 496 |
+
"Yes" if elig else "No",
|
| 497 |
+
"Yes" if has_imat else "-",
|
| 498 |
+
),
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
def _select_all_eligible(self) -> None:
|
| 502 |
+
if self._reader is None:
|
| 503 |
+
return
|
| 504 |
+
eligible_ids = [
|
| 505 |
+
ti.name
|
| 506 |
+
for ti in self._reader.tensors
|
| 507 |
+
if is_q2k_eligible(ti.name, ti.dims) and self.tree.exists(ti.name)
|
| 508 |
+
]
|
| 509 |
+
self.tree.selection_set(eligible_ids)
|
| 510 |
+
|
| 511 |
+
def _selected_tensor_names(self) -> set[str]:
|
| 512 |
+
return set(self.tree.selection())
|
| 513 |
+
|
| 514 |
+
def stop_worker(self) -> None:
|
| 515 |
+
if self._worker and self._worker.is_alive():
|
| 516 |
+
self._stop_event.set()
|
| 517 |
+
self.status_var.set("Cancelling after current tensor...")
|
| 518 |
+
|
| 519 |
+
def start_quantization(self) -> None:
|
| 520 |
+
if self._worker and self._worker.is_alive():
|
| 521 |
+
return
|
| 522 |
+
gguf_path = self.gguf_var.get().strip()
|
| 523 |
+
if not gguf_path or not Path(gguf_path).exists():
|
| 524 |
+
messagebox.showerror("Missing Source GGUF", "Please select a valid source GGUF file.")
|
| 525 |
+
return
|
| 526 |
+
out_path = self.output_var.get().strip()
|
| 527 |
+
if not out_path:
|
| 528 |
+
p = Path(gguf_path)
|
| 529 |
+
out_path = str(p.with_name(f"{p.stem}-Q2K.gguf"))
|
| 530 |
+
self.output_var.set(out_path)
|
| 531 |
+
try:
|
| 532 |
+
cfg = self.build_config()
|
| 533 |
+
except Exception as exc:
|
| 534 |
+
messagebox.showerror("Invalid Configuration", str(exc))
|
| 535 |
+
return
|
| 536 |
+
|
| 537 |
+
selected = self._selected_tensor_names() if self.mode_var.get() == "selected" else None
|
| 538 |
+
if self.mode_var.get() == "selected" and not selected:
|
| 539 |
+
messagebox.showwarning("No Tensor Selected", "Select one or more tensors in the table first.")
|
| 540 |
+
return
|
| 541 |
+
|
| 542 |
+
imat_path = self.imatrix_var.get().strip()
|
| 543 |
+
limit = int(self.limit_var.get())
|
| 544 |
+
|
| 545 |
+
self._stop_event.clear()
|
| 546 |
+
self.progress_var.set(0.0)
|
| 547 |
+
self.run_btn.configure(state=tk.DISABLED)
|
| 548 |
+
self.bench_btn.configure(state=tk.DISABLED)
|
| 549 |
+
self.stop_btn.configure(state=tk.NORMAL)
|
| 550 |
+
self.status_var.set("Running quantization...")
|
| 551 |
+
|
| 552 |
+
self._worker = threading.Thread(
|
| 553 |
+
target=self._quantize_worker,
|
| 554 |
+
args=(gguf_path, imat_path, out_path, cfg, selected, limit),
|
| 555 |
+
daemon=True,
|
| 556 |
+
)
|
| 557 |
+
self._worker.start()
|
| 558 |
+
|
| 559 |
+
def _quantize_worker(
|
| 560 |
+
self,
|
| 561 |
+
gguf_path: str,
|
| 562 |
+
imat_path: str,
|
| 563 |
+
out_path: str,
|
| 564 |
+
cfg: HPCConfig,
|
| 565 |
+
selected: set[str] | None,
|
| 566 |
+
limit: int,
|
| 567 |
+
) -> None:
|
| 568 |
+
t0 = time.time()
|
| 569 |
+
try:
|
| 570 |
+
imat = None
|
| 571 |
+
if imat_path and Path(imat_path).exists():
|
| 572 |
+
imat = read_imatrix(imat_path)
|
| 573 |
+
self._log_queue.put(("log", f"Loaded iMatrix ({len(imat)} tensors) from {imat_path}"))
|
| 574 |
+
|
| 575 |
+
quantizer = Q2KQuantizer(cfg)
|
| 576 |
+
if out_path.lower().endswith(".npz") and selected and len(selected) == 1:
|
| 577 |
+
reader = GGUFReader(gguf_path)
|
| 578 |
+
tname = next(iter(selected))
|
| 579 |
+
x, row = reader.read_tensor(tname)
|
| 580 |
+
imp = None
|
| 581 |
+
if imat is not None:
|
| 582 |
+
raw = lookup_imatrix(imat, tname)
|
| 583 |
+
if raw is not None:
|
| 584 |
+
imp = expand_importance(raw, x.size, row)
|
| 585 |
+
res = quantizer.quantize(x, importance=imp, row_width=row)
|
| 586 |
+
np.savez_compressed(
|
| 587 |
+
out_path,
|
| 588 |
+
scales=res.scales,
|
| 589 |
+
qs=res.qs,
|
| 590 |
+
dm=res.dm,
|
| 591 |
+
dequantized=res.dequantize(),
|
| 592 |
+
rmse=np.float32(res.stats.rmse),
|
| 593 |
+
)
|
| 594 |
+
self._log_queue.put(
|
| 595 |
+
("done", f"Saved {out_path} (RMSE={res.stats.rmse:.6e}, {time.time() - t0:.1f}s)")
|
| 596 |
+
)
|
| 597 |
+
return
|
| 598 |
+
|
| 599 |
+
def on_progress(done: int, total: int, name: str, action: str, rmse: float | None, has_imat: bool):
|
| 600 |
+
pct = 100.0 * done / max(total, 1)
|
| 601 |
+
self._log_queue.put(("progress", (pct, f"[{done}/{total}] {action} {name}")))
|
| 602 |
+
if action == "Q2_K" and rmse is not None:
|
| 603 |
+
self._log_queue.put(
|
| 604 |
+
(
|
| 605 |
+
"log",
|
| 606 |
+
f"[{done}/{total}] Q2_K {name:44s} RMSE={rmse:.6e} iMatrix={'yes' if has_imat else 'no'}",
|
| 607 |
+
)
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
stats = write_quantized_gguf(
|
| 611 |
+
gguf_path,
|
| 612 |
+
out_path,
|
| 613 |
+
quantizer,
|
| 614 |
+
imat=imat,
|
| 615 |
+
quantize_all=(selected is None),
|
| 616 |
+
tensor_names=selected,
|
| 617 |
+
limit=limit,
|
| 618 |
+
verbose=False,
|
| 619 |
+
progress_callback=on_progress,
|
| 620 |
+
stop_event=self._stop_event,
|
| 621 |
+
)
|
| 622 |
+
dt = time.time() - t0
|
| 623 |
+
self._log_queue.put(
|
| 624 |
+
(
|
| 625 |
+
"done",
|
| 626 |
+
f"Wrote {out_path}: {int(stats['quantized'])}/{int(stats['tensors'])} tensors Q2_K, "
|
| 627 |
+
f"overall RMSE={stats['rmse']:.6e} ({dt:.1f}s)",
|
| 628 |
+
)
|
| 629 |
+
)
|
| 630 |
+
except InterruptedError as exc:
|
| 631 |
+
self._log_queue.put(("cancelled", str(exc)))
|
| 632 |
+
except Exception as exc:
|
| 633 |
+
self._log_queue.put(("error", str(exc)))
|
| 634 |
+
|
| 635 |
+
def start_benchmark(self) -> None:
|
| 636 |
+
if self._worker and self._worker.is_alive():
|
| 637 |
+
return
|
| 638 |
+
try:
|
| 639 |
+
cfg = self.build_config()
|
| 640 |
+
except Exception as exc:
|
| 641 |
+
messagebox.showerror("Invalid Configuration", str(exc))
|
| 642 |
+
return
|
| 643 |
+
self._stop_event.clear()
|
| 644 |
+
self.progress_var.set(0.0)
|
| 645 |
+
self.run_btn.configure(state=tk.DISABLED)
|
| 646 |
+
self.bench_btn.configure(state=tk.DISABLED)
|
| 647 |
+
self.stop_btn.configure(state=tk.NORMAL)
|
| 648 |
+
self.status_var.set("Running factored solver benchmark...")
|
| 649 |
+
self._worker = threading.Thread(target=self._bench_worker, args=(cfg,), daemon=True)
|
| 650 |
+
self._worker.start()
|
| 651 |
+
|
| 652 |
+
def _bench_worker(self, cfg: HPCConfig) -> None:
|
| 653 |
+
from collections import defaultdict
|
| 654 |
+
|
| 655 |
+
try:
|
| 656 |
+
cases = synthetic_cases(123, 8)
|
| 657 |
+
gains = defaultdict(list)
|
| 658 |
+
total = len(cases)
|
| 659 |
+
for idx, (kind, x) in enumerate(cases, 1):
|
| 660 |
+
if self._stop_event.is_set():
|
| 661 |
+
raise InterruptedError("Benchmark cancelled.")
|
| 662 |
+
w = np.ones(x.size, dtype=np.float32)
|
| 663 |
+
v2 = factor_only_solve(x, w, cfg)
|
| 664 |
+
ref_obj, _ = reference_solve(x, w, cfg, cfg.use_spectral)
|
| 665 |
+
gains[kind].append((ref_obj - v2.objective) / max(abs(ref_obj), 1.0e-30))
|
| 666 |
+
self._log_queue.put(("progress", (100.0 * idx / total, f"Bench {idx}/{total} ({kind})")))
|
| 667 |
+
|
| 668 |
+
self._log_queue.put(("log", f"--- Benchmark (spectral={cfg.use_spectral}, analogs={cfg.use_weight_analogs}) ---"))
|
| 669 |
+
for kind, vals in gains.items():
|
| 670 |
+
arr = np.asarray(vals)
|
| 671 |
+
self._log_queue.put(
|
| 672 |
+
(
|
| 673 |
+
"log",
|
| 674 |
+
f" {kind:14s} mean={arr.mean()*100:6.2f}% median={np.median(arr)*100:6.2f}% "
|
| 675 |
+
f"min={arr.min()*100:6.2f}% worse={int(np.sum(arr < -1e-9))}/{arr.size}",
|
| 676 |
+
)
|
| 677 |
+
)
|
| 678 |
+
self._log_queue.put(("done", "Benchmark complete."))
|
| 679 |
+
except InterruptedError as exc:
|
| 680 |
+
self._log_queue.put(("cancelled", str(exc)))
|
| 681 |
+
except Exception as exc:
|
| 682 |
+
self._log_queue.put(("error", str(exc)))
|
| 683 |
+
|
| 684 |
+
def _poll_queue(self) -> None:
|
| 685 |
+
while True:
|
| 686 |
+
try:
|
| 687 |
+
kind, payload = self._log_queue.get_nowait()
|
| 688 |
+
except queue.Empty:
|
| 689 |
+
break
|
| 690 |
+
if kind == "log":
|
| 691 |
+
self._log(str(payload))
|
| 692 |
+
elif kind == "progress":
|
| 693 |
+
pct, msg = payload # type: ignore[misc]
|
| 694 |
+
self.progress_var.set(float(pct))
|
| 695 |
+
self.status_var.set(str(msg))
|
| 696 |
+
elif kind == "done":
|
| 697 |
+
self.progress_var.set(100.0)
|
| 698 |
+
self.status_var.set(str(payload))
|
| 699 |
+
self._log(str(payload))
|
| 700 |
+
self._reset_buttons()
|
| 701 |
+
elif kind in ("cancelled", "error"):
|
| 702 |
+
self.status_var.set(f"{kind.upper()}: {payload}")
|
| 703 |
+
self._log(f"{kind.upper()}: {payload}")
|
| 704 |
+
self._reset_buttons()
|
| 705 |
+
self.root.after(100, self._poll_queue)
|
| 706 |
+
|
| 707 |
+
def _reset_buttons(self) -> None:
|
| 708 |
+
self.run_btn.configure(state=tk.NORMAL)
|
| 709 |
+
self.bench_btn.configure(state=tk.NORMAL)
|
| 710 |
+
self.stop_btn.configure(state=tk.DISABLED)
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
def main() -> None:
|
| 714 |
+
parser = argparse.ArgumentParser(description="HPC-Quantize V2 GUI")
|
| 715 |
+
parser.add_argument("--gguf", help="pre-populate source GGUF path")
|
| 716 |
+
parser.add_argument("--imatrix", help="pre-populate iMatrix path")
|
| 717 |
+
parser.add_argument("--output", help="pre-populate output GGUF path")
|
| 718 |
+
parser.add_argument("--check", action="store_true", help="validate GUI bindings without opening a window")
|
| 719 |
+
args = parser.parse_args()
|
| 720 |
+
|
| 721 |
+
if args.check:
|
| 722 |
+
cfg = HPCConfig().validate()
|
| 723 |
+
print("GUI check OK:", cfg)
|
| 724 |
+
return
|
| 725 |
+
|
| 726 |
+
root = tk.Tk()
|
| 727 |
+
app = HPCQuantizeGUI(root)
|
| 728 |
+
if args.gguf:
|
| 729 |
+
app.gguf_var.set(args.gguf)
|
| 730 |
+
if args.imatrix:
|
| 731 |
+
app.imatrix_var.set(args.imatrix)
|
| 732 |
+
if args.output:
|
| 733 |
+
app.output_var.set(args.output)
|
| 734 |
+
if args.gguf or args.imatrix:
|
| 735 |
+
app.inspect_files()
|
| 736 |
+
root.mainloop()
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
if __name__ == "__main__":
|
| 740 |
+
main()
|