CompressedGemma commited on
Commit
3d69b7f
·
verified ·
1 Parent(s): 6186af4

Upload gui.py

Browse files
Files changed (1) hide show
  1. gui.py +244 -81
gui.py CHANGED
@@ -7,16 +7,19 @@ Launch::
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
@@ -40,25 +43,31 @@ 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()
@@ -66,6 +75,7 @@ class HPCQuantizeGUI:
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)
@@ -91,7 +101,18 @@ class HPCQuantizeGUI:
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,
@@ -120,6 +141,8 @@ class HPCQuantizeGUI:
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))
@@ -130,7 +153,7 @@ class HPCQuantizeGUI:
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)])
@@ -140,27 +163,27 @@ class HPCQuantizeGUI:
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)
@@ -168,11 +191,11 @@ class HPCQuantizeGUI:
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"
@@ -182,7 +205,7 @@ class HPCQuantizeGUI:
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"
@@ -192,40 +215,40 @@ class HPCQuantizeGUI:
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
 
@@ -237,7 +260,7 @@ class HPCQuantizeGUI:
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)
@@ -250,7 +273,7 @@ class HPCQuantizeGUI:
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)
@@ -263,7 +286,7 @@ class HPCQuantizeGUI:
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")
@@ -271,13 +294,15 @@ class HPCQuantizeGUI:
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)
@@ -286,16 +311,50 @@ class HPCQuantizeGUI:
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))
@@ -310,22 +369,30 @@ class HPCQuantizeGUI:
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)
@@ -378,7 +445,7 @@ class HPCQuantizeGUI:
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:
@@ -495,6 +562,7 @@ class HPCQuantizeGUI:
495
  ti.row_width,
496
  "Yes" if elig else "No",
497
  "Yes" if has_imat else "-",
 
498
  ),
499
  )
500
 
@@ -514,7 +582,8 @@ class HPCQuantizeGUI:
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():
@@ -543,11 +612,19 @@ class HPCQuantizeGUI:
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,
@@ -569,20 +646,28 @@ class HPCQuantizeGUI:
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,
@@ -596,16 +681,11 @@ class HPCQuantizeGUI:
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,
@@ -617,14 +697,15 @@ class HPCQuantizeGUI:
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:
@@ -641,7 +722,9 @@ class HPCQuantizeGUI:
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)
@@ -662,10 +745,24 @@ class HPCQuantizeGUI:
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(
@@ -681,6 +778,16 @@ class HPCQuantizeGUI:
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:
@@ -689,20 +796,76 @@ class HPCQuantizeGUI:
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)
 
7
 
8
  Provides:
9
  * Source GGUF, iMatrix (.gguf / .dat), and Output (.gguf / .npz) file pickers
10
+ * Interactive GGUF tensor table with live per-tensor status & RMSE updates
11
+ * Real-time Telemetry Dashboard showing active tensor, current pipeline phase,
12
+ dual progress bars (overall model + intra-tensor superblocks), throughput
13
+ (blk/s), ETA, running RMSE, RMSE/sigma, Boltzmann temperature, and floor hits
14
  * Full HPCConfig controls (Geometry-Preserving Analogs, Coupled Bilinear Q2_K,
15
  Sieve + 36-state Viterbi, Spectral/Vesica/DC shaping, Candidate lattice size)
16
+ * Timestamped verbose log stream with selectable verbosity level
 
17
  """
18
 
19
  from __future__ import annotations
20
 
21
  import argparse
22
+ import datetime as dt
23
  import queue
24
  import shlex
25
  import threading
 
43
  from hpc_quantize_v2.reference import reference_solve, synthetic_cases
44
 
45
 
46
+ def _ts() -> str:
47
+ return dt.datetime.now().strftime("%H:%M:%S.%f")[:-3]
48
+
49
+
50
  class HPCQuantizeGUI:
51
  def __init__(self, root: tk.Tk) -> None:
52
  self.root = root
53
  self.root.title("HPC-Quantize V2 — Factored Q2_K & Geometry-Preserving Analog Engine")
54
+ self.root.geometry("1260x900")
55
+ self.root.minsize(1040, 740)
56
 
57
  self._log_queue: queue.Queue[tuple[str, object]] = queue.Queue()
58
  self._stop_event = threading.Event()
59
  self._worker: threading.Thread | None = None
60
  self._reader: GGUFReader | None = None
61
  self._imatrix: dict[str, np.ndarray] | None = None
62
+ self._start_time: float | None = None
63
+ self._last_logged_phase: str = ""
64
 
65
  self._init_vars()
66
  self._apply_theme()
67
  self._build_layout()
68
  self._auto_populate_defaults()
69
  self._update_cli_preview()
70
+ self.root.after(80, self._poll_queue)
71
 
72
  def _init_vars(self) -> None:
73
  self.gguf_var = tk.StringVar()
 
75
  self.output_var = tk.StringVar()
76
  self.mode_var = tk.StringVar(value="all")
77
  self.filter_var = tk.StringVar()
78
+ self.log_level_var = tk.StringVar(value="ultra")
79
 
80
  self.optimizer_var = tk.StringVar(value="hpc")
81
  self.cand_d_var = tk.IntVar(value=6)
 
101
 
102
  self.cli_preview_var = tk.StringVar()
103
  self.status_var = tk.StringVar(value="Ready.")
104
+ self.model_progress_var = tk.DoubleVar(value=0.0)
105
+ self.tensor_progress_var = tk.DoubleVar(value=0.0)
106
+
107
+ # Live telemetry variables
108
+ self.tele_tensor_var = tk.StringVar(value="Idle")
109
+ self.tele_phase_var = tk.StringVar(value="Waiting to start")
110
+ self.tele_blocks_var = tk.StringVar(value="0 / 0")
111
+ self.tele_speed_var = tk.StringVar(value="0.0 blk/s (ETA --)")
112
+ self.tele_rmse_var = tk.StringVar(value="--")
113
+ self.tele_temp_var = tk.StringVar(value="--")
114
+ self.tele_floors_var = tk.StringVar(value="cand=0, factor=0")
115
+ self.tele_elapsed_var = tk.StringVar(value="0.0s")
116
 
117
  for v in (
118
  self.gguf_var,
 
141
  style.configure("TLabelframe", background=bg, foreground=fg)
142
  style.configure("TLabelframe.Label", background=bg, foreground="#93c5fd", font=("Sans", 10, "bold"))
143
  style.configure("TLabel", background=bg, foreground=fg)
144
+ style.configure("TeleKey.TLabel", background=bg, foreground="#9ca3af", font=("Sans", 9))
145
+ style.configure("TeleVal.TLabel", background=bg, foreground="#60a5fa", font=("Monospace", 9, "bold"))
146
  style.configure("TCheckbutton", background=bg, foreground=fg)
147
  style.configure("TRadiobutton", background=bg, foreground=fg)
148
  style.configure("TButton", background=panel, foreground=fg, padding=(10, 5))
 
153
  background=panel,
154
  fieldbackground=panel,
155
  foreground=fg,
156
+ rowheight=23,
157
  )
158
  style.configure("Treeview.Heading", background="#262f3f", foreground=fg, font=("Sans", 9, "bold"))
159
  style.map("Treeview", background=[("selected", accent)])
 
163
  main.pack(fill=tk.BOTH, expand=True)
164
 
165
  # 1. File Selection Frame
166
+ files_frame = ttk.LabelFrame(main, text="1. GGUF & iMatrix Paths", padding=8)
167
+ files_frame.pack(fill=tk.X, pady=(0, 6))
168
  files_frame.columnconfigure(1, weight=1)
169
 
170
+ ttk.Label(files_frame, text="Source GGUF:").grid(row=0, column=0, sticky=tk.W, padx=(0, 8), pady=2)
171
+ ttk.Entry(files_frame, textvariable=self.gguf_var).grid(row=0, column=1, sticky=tk.EW, pady=2)
172
+ ttk.Button(files_frame, text="Browse...", command=self._browse_gguf).grid(row=0, column=2, padx=4, pady=2)
173
+ ttk.Button(files_frame, text="Inspect GGUF", command=self.inspect_files).grid(row=0, column=3, pady=2)
174
 
175
+ ttk.Label(files_frame, text="iMatrix (.gguf/.dat):").grid(row=1, column=0, sticky=tk.W, padx=(0, 8), pady=2)
176
+ ttk.Entry(files_frame, textvariable=self.imatrix_var).grid(row=1, column=1, sticky=tk.EW, pady=2)
177
+ ttk.Button(files_frame, text="Browse...", command=self._browse_imatrix).grid(row=1, column=2, padx=4, pady=2)
178
+ ttk.Button(files_frame, text="Clear", command=lambda: self.imatrix_var.set("")).grid(row=1, column=3, pady=2)
179
 
180
+ ttk.Label(files_frame, text="Output (.gguf/.npz):").grid(row=2, column=0, sticky=tk.W, padx=(0, 8), pady=2)
181
+ ttk.Entry(files_frame, textvariable=self.output_var).grid(row=2, column=1, sticky=tk.EW, pady=2)
182
+ ttk.Button(files_frame, text="Save As...", command=self._browse_output).grid(row=2, column=2, padx=4, pady=2)
183
 
184
  # 2. Middle Split: Left = Settings, Right = Tensor Browser
185
  mid = ttk.PanedWindow(main, orient=tk.HORIZONTAL)
186
+ mid.pack(fill=tk.BOTH, expand=True, pady=(0, 6))
187
 
188
  left_pane = ttk.Frame(mid)
189
  right_pane = ttk.Frame(mid)
 
191
  mid.add(right_pane, weight=3)
192
 
193
  # Left: Engine & Factorization Options
194
+ opt_frame = ttk.LabelFrame(left_pane, text="2. Factored Q2_K & Analog Controls", padding=8)
195
  opt_frame.pack(fill=tk.BOTH, expand=True, padx=(0, 6))
196
 
197
  mode_row = ttk.Frame(opt_frame)
198
+ mode_row.pack(fill=tk.X, pady=(0, 4))
199
  ttk.Label(mode_row, text="Target Scope:").pack(side=tk.LEFT, padx=(0, 8))
200
  ttk.Radiobutton(
201
  mode_row, text="All Q2_K-Eligible Tensors", variable=self.mode_var, value="all"
 
205
  ).pack(side=tk.LEFT, padx=4)
206
 
207
  opt_row = ttk.Frame(opt_frame)
208
+ opt_row.pack(fill=tk.X, pady=(0, 4))
209
  ttk.Label(opt_row, text="Global Optimizer:").pack(side=tk.LEFT, padx=(0, 8))
210
  ttk.Radiobutton(
211
  opt_row, text="HPC (Sieve + 36-State Viterbi)", variable=self.optimizer_var, value="hpc"
 
215
  ).pack(side=tk.LEFT, padx=4)
216
 
217
  checks = ttk.Frame(opt_frame)
218
+ checks.pack(fill=tk.X, pady=2)
219
  ttk.Checkbutton(
220
  checks,
221
  text="Geometry-Preserving Weight Analogs (Iso-Moment + Vesica Gram + Subscale Lattice)",
222
  variable=self.use_analogs_var,
223
+ ).pack(anchor=tk.W, pady=1)
224
  ttk.Checkbutton(
225
  checks,
226
  text="Coupled Orthogonal-Basis Bilinear Q2_K Factorization",
227
  variable=self.coupled_q2k_var,
228
+ ).pack(anchor=tk.W, pady=1)
229
  ttk.Checkbutton(
230
  checks,
231
  text="Exact Joint (Ls, Lm) 16x16 Sub-block Scan",
232
  variable=self.joint_subscales_var,
233
+ ).pack(anchor=tk.W, pady=1)
234
  ttk.Checkbutton(
235
  checks,
236
  text="Spectral Fold / Vesica / DC Objective Penalty",
237
  variable=self.use_spectral_var,
238
+ ).pack(anchor=tk.W, pady=1)
239
  ttk.Checkbutton(
240
  checks,
241
  text="Sub-block 6-State Sieve & Rolling DC Residual Carry",
242
  variable=self.subblock_sieve_var,
243
+ ).pack(anchor=tk.W, pady=1)
244
  ttk.Checkbutton(
245
  checks,
246
  text="Local-Safety Candidate & Factored Floors (Monotone Bound)",
247
  variable=self.factor_floor_var,
248
+ ).pack(anchor=tk.W, pady=1)
249
 
250
  grid_f = ttk.Frame(opt_frame)
251
+ grid_f.pack(fill=tk.X, pady=4)
252
  for col in range(4):
253
  grid_f.columnconfigure(col, weight=1)
254
 
 
260
  self._spin_pair(grid_f, 2, 2, "Tensor Limit (0=all):", self.limit_var, 0, 9999)
261
 
262
  spec_f = ttk.LabelFrame(opt_frame, text="Spectral & DC Parameters", padding=6)
263
+ spec_f.pack(fill=tk.X, pady=2)
264
  for col in range(4):
265
  spec_f.columnconfigure(col, weight=1)
266
  ttk.Label(spec_f, text="DC Lambda:").grid(row=0, column=0, sticky=tk.W, padx=4, pady=2)
 
273
  ttk.Entry(spec_f, textvariable=self.sse_budget_var, width=8).grid(row=1, column=3, sticky=tk.EW, padx=4, pady=2)
274
 
275
  # Right: GGUF Tensor Browser
276
+ browser_frame = ttk.LabelFrame(right_pane, text="3. GGUF Tensor Inspector & Live Status", padding=8)
277
  browser_frame.pack(fill=tk.BOTH, expand=True)
278
 
279
  search_row = ttk.Frame(browser_frame)
 
286
  search_row, text="Select Eligible", command=self._select_all_eligible
287
  ).pack(side=tk.LEFT, padx=4)
288
 
289
+ cols = ("name", "type", "dims", "row", "q2k", "imat", "state")
290
  self.tree = ttk.Treeview(browser_frame, columns=cols, show="headings", selectmode="extended")
291
  self.tree.heading("name", text="Tensor Name")
292
  self.tree.heading("type", text="Type")
 
294
  self.tree.heading("row", text="Row Width")
295
  self.tree.heading("q2k", text="Q2_K")
296
  self.tree.heading("imat", text="iMatrix")
297
+ self.tree.heading("state", text="Live Status / RMSE")
298
+
299
+ self.tree.column("name", width=220, anchor=tk.W)
300
+ self.tree.column("type", width=52, anchor=tk.CENTER)
301
+ self.tree.column("dims", width=105, anchor=tk.CENTER)
302
+ self.tree.column("row", width=70, anchor=tk.E)
303
+ self.tree.column("q2k", width=48, anchor=tk.CENTER)
304
+ self.tree.column("imat", width=56, anchor=tk.CENTER)
305
+ self.tree.column("state", width=165, anchor=tk.W)
306
  self.tree.bind("<<TreeviewSelect>>", lambda _e: self._update_cli_preview())
307
 
308
  tree_scroll = ttk.Scrollbar(browser_frame, orient=tk.VERTICAL, command=self.tree.yview)
 
311
  tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
312
 
313
  # 4. Equivalent CLI Command Preview
314
+ cli_frame = ttk.LabelFrame(main, text="4. Equivalent Terminal Command", padding=6)
315
+ cli_frame.pack(fill=tk.X, pady=(0, 6))
316
  ttk.Entry(cli_frame, textvariable=self.cli_preview_var, state="readonly").pack(
317
  side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 6)
318
  )
319
  ttk.Button(cli_frame, text="Copy Command", command=self._copy_cli).pack(side=tk.RIGHT)
320
 
321
+ # 5. Live Telemetry Dashboard + Execution + Verbose Console
322
+ bottom = ttk.LabelFrame(main, text="5. Live Telemetry Dashboard & Verbose Execution Stream", padding=8)
323
+ bottom.pack(fill=tk.BOTH, expand=True)
324
+
325
+ tele = ttk.Frame(bottom)
326
+ tele.pack(fill=tk.X, pady=(0, 6))
327
+ for col in (1, 3, 5, 7):
328
+ tele.columnconfigure(col, weight=1)
329
+
330
+ ttk.Label(tele, text="Active Tensor:", style="TeleKey.TLabel").grid(row=0, column=0, sticky=tk.W, padx=4, pady=1)
331
+ ttk.Label(tele, textvariable=self.tele_tensor_var, style="TeleVal.TLabel").grid(
332
+ row=0, column=1, columnspan=3, sticky=tk.W, padx=4, pady=1
333
+ )
334
+ ttk.Label(tele, text="Current Stage:", style="TeleKey.TLabel").grid(row=0, column=4, sticky=tk.W, padx=4, pady=1)
335
+ ttk.Label(tele, textvariable=self.tele_phase_var, style="TeleVal.TLabel").grid(
336
+ row=0, column=5, columnspan=3, sticky=tk.W, padx=4, pady=1
337
+ )
338
+
339
+ ttk.Label(tele, text="Superblocks:", style="TeleKey.TLabel").grid(row=1, column=0, sticky=tk.W, padx=4, pady=1)
340
+ ttk.Label(tele, textvariable=self.tele_blocks_var, style="TeleVal.TLabel").grid(row=1, column=1, sticky=tk.W, padx=4, pady=1)
341
+ ttk.Label(tele, text="Throughput:", style="TeleKey.TLabel").grid(row=1, column=2, sticky=tk.W, padx=4, pady=1)
342
+ ttk.Label(tele, textvariable=self.tele_speed_var, style="TeleVal.TLabel").grid(row=1, column=3, sticky=tk.W, padx=4, pady=1)
343
+ ttk.Label(tele, text="Running RMSE:", style="TeleKey.TLabel").grid(row=1, column=4, sticky=tk.W, padx=4, pady=1)
344
+ ttk.Label(tele, textvariable=self.tele_rmse_var, style="TeleVal.TLabel").grid(row=1, column=5, sticky=tk.W, padx=4, pady=1)
345
+ ttk.Label(tele, text="Sieve Temp / Floors:", style="TeleKey.TLabel").grid(row=1, column=6, sticky=tk.W, padx=4, pady=1)
346
+ ttk.Label(tele, textvariable=self.tele_floors_var, style="TeleVal.TLabel").grid(row=1, column=7, sticky=tk.W, padx=4, pady=1)
347
+
348
+ bars = ttk.Frame(bottom)
349
+ bars.pack(fill=tk.X, pady=(0, 6))
350
+ bars.columnconfigure(1, weight=1)
351
+ ttk.Label(bars, text="Model Progress:", style="TeleKey.TLabel").grid(row=0, column=0, sticky=tk.W, padx=(0, 6), pady=2)
352
+ self.model_progress = ttk.Progressbar(bars, variable=self.model_progress_var, maximum=100.0)
353
+ self.model_progress.grid(row=0, column=1, sticky=tk.EW, pady=2)
354
+
355
+ ttk.Label(bars, text="Tensor Blocks:", style="TeleKey.TLabel").grid(row=1, column=0, sticky=tk.W, padx=(0, 6), pady=2)
356
+ self.tensor_progress = ttk.Progressbar(bars, variable=self.tensor_progress_var, maximum=100.0)
357
+ self.tensor_progress.grid(row=1, column=1, sticky=tk.EW, pady=2)
358
 
359
  btn_row = ttk.Frame(bottom)
360
  btn_row.pack(fill=tk.X, pady=(0, 6))
 
369
  self.bench_btn = ttk.Button(
370
  btn_row, text="Run Solver Benchmark", command=self.start_benchmark
371
  )
372
+ self.bench_btn.pack(side=tk.LEFT, padx=(0, 6))
373
+ ttk.Button(
374
+ btn_row, text="Clear Log", command=lambda: self.log_text.delete("1.0", tk.END)
375
+ ).pack(side=tk.LEFT, padx=(0, 12))
376
 
377
+ ttk.Label(btn_row, text="Log Verbosity:").pack(side=tk.LEFT, padx=(0, 4))
378
+ ttk.Radiobutton(
379
+ btn_row, text="Ultra-Verbose (Every Block Step)", variable=self.log_level_var, value="ultra"
380
+ ).pack(side=tk.LEFT, padx=2)
381
+ ttk.Radiobutton(
382
+ btn_row, text="Stage Transitions", variable=self.log_level_var, value="stages"
383
+ ).pack(side=tk.LEFT, padx=2)
384
+ ttk.Label(btn_row, textvariable=self.status_var).pack(side=tk.RIGHT, padx=4)
385
 
386
  log_frame = ttk.Frame(bottom)
387
  log_frame.pack(fill=tk.BOTH, expand=True)
388
  self.log_text = tk.Text(
389
  log_frame,
390
+ height=11,
391
  bg="#0f131a",
392
  fg="#d1d5db",
393
  insertbackground="#ffffff",
394
  font=("Monospace", 9),
395
+ wrap=tk.NONE,
396
  )
397
  log_scroll = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
398
  self.log_text.configure(yscrollcommand=log_scroll.set)
 
445
  self.output_var.set(path)
446
 
447
  def _log(self, msg: str) -> None:
448
+ self.log_text.insert(tk.END, f"[{_ts()}] {msg}\n")
449
  self.log_text.see(tk.END)
450
 
451
  def build_config(self) -> HPCConfig:
 
562
  ti.row_width,
563
  "Yes" if elig else "No",
564
  "Yes" if has_imat else "-",
565
+ "Pending (Q2_K)" if elig else "Passthrough",
566
  ),
567
  )
568
 
 
582
  def stop_worker(self) -> None:
583
  if self._worker and self._worker.is_alive():
584
  self._stop_event.set()
585
+ self.status_var.set("Stopping immediately...")
586
+ self._log("Cancel requested — halting active block loop...")
587
 
588
  def start_quantization(self) -> None:
589
  if self._worker and self._worker.is_alive():
 
612
  limit = int(self.limit_var.get())
613
 
614
  self._stop_event.clear()
615
+ self._start_time = time.time()
616
+ self._last_logged_phase = ""
617
+ self.model_progress_var.set(0.0)
618
+ self.tensor_progress_var.set(0.0)
619
  self.run_btn.configure(state=tk.DISABLED)
620
  self.bench_btn.configure(state=tk.DISABLED)
621
  self.stop_btn.configure(state=tk.NORMAL)
622
+ self.status_var.set("Starting quantization...")
623
+ self._log(
624
+ f"Starting quantization: source={Path(gguf_path).name} -> output={Path(out_path).name} "
625
+ f"(cand={cfg.n_cand_d}x{cfg.n_cand_m}, analogs={cfg.use_weight_analogs}, "
626
+ f"coupled={cfg.factor_coupled_q2k}, optimizer={cfg.optimizer.upper()})"
627
+ )
628
 
629
  self._worker = threading.Thread(
630
  target=self._quantize_worker,
 
646
  try:
647
  imat = None
648
  if imat_path and Path(imat_path).exists():
649
+ self._log_queue.put(("log", f"Loading iMatrix from {imat_path} ..."))
650
  imat = read_imatrix(imat_path)
651
+ self._log_queue.put(("log", f"Loaded iMatrix: {len(imat)} tensor entries ready."))
652
 
653
  quantizer = Q2KQuantizer(cfg)
654
  if out_path.lower().endswith(".npz") and selected and len(selected) == 1:
655
  reader = GGUFReader(gguf_path)
656
  tname = next(iter(selected))
657
+ ti = reader.info(tname)
658
  x, row = reader.read_tensor(tname)
659
  imp = None
660
  if imat is not None:
661
  raw = lookup_imatrix(imat, tname)
662
  if raw is not None:
663
  imp = expand_importance(raw, x.size, row)
664
+ res = quantizer.quantize(
665
+ x,
666
+ importance=imp,
667
+ row_width=row,
668
+ status_callback=lambda ev: self._log_queue.put(("stage", (1, 1, ti, ev))),
669
+ stop_event=self._stop_event,
670
+ )
671
  np.savez_compressed(
672
  out_path,
673
  scales=res.scales,
 
681
  )
682
  return
683
 
684
+ def on_stage(idx: int, total: int, ti, ev: dict):
685
+ self._log_queue.put(("stage", (idx, total, ti, ev)))
686
+
687
  def on_progress(done: int, total: int, name: str, action: str, rmse: float | None, has_imat: bool):
688
+ self._log_queue.put(("tensor_done", (done, total, name, action, rmse, has_imat)))
 
 
 
 
 
 
 
 
689
 
690
  stats = write_quantized_gguf(
691
  gguf_path,
 
697
  limit=limit,
698
  verbose=False,
699
  progress_callback=on_progress,
700
+ stage_callback=on_stage,
701
  stop_event=self._stop_event,
702
  )
703
+ dt_sec = time.time() - t0
704
  self._log_queue.put(
705
  (
706
  "done",
707
  f"Wrote {out_path}: {int(stats['quantized'])}/{int(stats['tensors'])} tensors Q2_K, "
708
+ f"overall RMSE={stats['rmse']:.6e} ({dt_sec:.1f}s)",
709
  )
710
  )
711
  except InterruptedError as exc:
 
722
  messagebox.showerror("Invalid Configuration", str(exc))
723
  return
724
  self._stop_event.clear()
725
+ self._start_time = time.time()
726
+ self.model_progress_var.set(0.0)
727
+ self.tensor_progress_var.set(0.0)
728
  self.run_btn.configure(state=tk.DISABLED)
729
  self.bench_btn.configure(state=tk.DISABLED)
730
  self.stop_btn.configure(state=tk.NORMAL)
 
745
  w = np.ones(x.size, dtype=np.float32)
746
  v2 = factor_only_solve(x, w, cfg)
747
  ref_obj, _ = reference_solve(x, w, cfg, cfg.use_spectral)
748
+ gain_pct = (ref_obj - v2.objective) / max(abs(ref_obj), 1.0e-30) * 100.0
749
+ gains[kind].append(gain_pct / 100.0)
750
+ self._log_queue.put(
751
+ (
752
+ "bench_step",
753
+ (
754
+ idx,
755
+ total,
756
+ kind,
757
+ f"Case {idx}/{total} [{kind}]: ref={ref_obj:.5e} -> V2={v2.objective:.5e} "
758
+ f"(+{gain_pct:.2f}%, d={float(v2.d):.5f}, dmin={float(v2.dmin):.5f}, starts={v2.starts})",
759
+ ),
760
+ )
761
+ )
762
 
763
+ self._log_queue.put(
764
+ ("log", f"=== Summary (spectral={cfg.use_spectral}, analogs={cfg.use_weight_analogs}) ===")
765
+ )
766
  for kind, vals in gains.items():
767
  arr = np.asarray(vals)
768
  self._log_queue.put(
 
778
  except Exception as exc:
779
  self._log_queue.put(("error", str(exc)))
780
 
781
+ def _update_tree_row_state(self, name: str, state_text: str, focus: bool = False) -> None:
782
+ if not self.tree.exists(name):
783
+ return
784
+ vals = list(self.tree.item(name, "values"))
785
+ if len(vals) >= 7:
786
+ vals[6] = state_text
787
+ self.tree.item(name, values=vals)
788
+ if focus:
789
+ self.tree.see(name)
790
+
791
  def _poll_queue(self) -> None:
792
  while True:
793
  try:
 
796
  break
797
  if kind == "log":
798
  self._log(str(payload))
799
+ elif kind == "stage":
800
+ idx, total, ti, ev = payload # type: ignore[misc]
801
+ phase = ev.get("phase", "")
802
+ done = int(ev.get("done", 0))
803
+ tot_blk = max(int(ev.get("total", 1)), 1)
804
+ detail = ev.get("detail", "")
805
+ blk_pct = 100.0 * done / tot_blk
806
+ overall_pct = 100.0 * ((idx - 1) + (done / tot_blk)) / max(total, 1)
807
+
808
+ self.model_progress_var.set(overall_pct)
809
+ self.tensor_progress_var.set(blk_pct)
810
+ self.tele_tensor_var.set(f"[{idx}/{total}] {ti.name} ({ti.type_name}, dims={ti.dims})")
811
+ self.tele_phase_var.set(phase)
812
+ self.tele_blocks_var.set(f"{done:,} / {tot_blk:,} ({blk_pct:.1f}%)")
813
+
814
+ if "rate" in ev and "eta" in ev:
815
+ self.tele_speed_var.set(f"{ev['rate']:.1f} blk/s (ETA {ev['eta']:.1f}s)")
816
+ if "rmse" in ev and "sigma" in ev:
817
+ r, s = float(ev["rmse"]), float(ev["sigma"])
818
+ self.tele_rmse_var.set(f"{r:.6e} (r/σ={r/max(s, 1e-15):.4f})")
819
+ if "temperature" in ev or "cand_floors" in ev:
820
+ t_val = ev.get("temperature", 0.0)
821
+ cf = ev.get("cand_floors", 0)
822
+ ff = ev.get("fac_floors", 0)
823
+ self.tele_floors_var.set(f"T={t_val:.2e} | cand={cf}, fac={ff}")
824
+
825
+ self.status_var.set(f"[{idx}/{total}] {ti.name} — {phase} ({blk_pct:.0f}%)")
826
+ self._update_tree_row_state(ti.name, f"{phase.split(':')[0]} ({blk_pct:.0f}%)", focus=True)
827
+
828
+ is_Copy = phase.startswith("Passthrough Copy")
829
+ phase_key = f"{ti.name}::{phase}"
830
+ if not is_Copy:
831
+ if self.log_level_var.get() == "ultra" or phase_key != self._last_logged_phase:
832
+ self._log(f"[{idx}/{total}] {ti.name} | {phase} | {detail}")
833
+ self._last_logged_phase = phase_key
834
+ elif kind == "tensor_done":
835
+ done, total, name, action, rmse, has_imat = payload # type: ignore[misc]
836
+ self.model_progress_var.set(100.0 * done / max(total, 1))
837
+ self.tensor_progress_var.set(100.0)
838
+ if action == "Q2_K" and rmse is not None:
839
+ self._update_tree_row_state(name, f"Q2_K (RMSE={rmse:.4e})")
840
+ self._log(
841
+ f"[{done}/{total}] FINISHED {name:42s} -> Q2_K RMSE={rmse:.6e} (iMatrix={'yes' if has_imat else 'no'})"
842
+ )
843
+ else:
844
+ self._update_tree_row_state(name, "Copied verbatim")
845
+ if self.log_level_var.get() == "ultra" or done % 25 == 0 or done == total:
846
+ self._log(f"[{done}/{total}] COPIED {name}")
847
+ elif kind == "bench_step":
848
+ idx, total, bkind, msg = payload # type: ignore[misc]
849
+ pct = 100.0 * idx / max(total, 1)
850
+ self.model_progress_var.set(pct)
851
+ self.tensor_progress_var.set(pct)
852
+ self.tele_tensor_var.set(f"Synthetic Benchmark ({bkind})")
853
+ self.tele_phase_var.set(f"Block {idx}/{total}")
854
+ self.tele_blocks_var.set(f"{idx} / {total}")
855
+ self._log(str(msg))
856
  elif kind == "done":
857
+ self.model_progress_var.set(100.0)
858
+ self.tensor_progress_var.set(100.0)
859
+ self.tele_phase_var.set("Complete")
860
  self.status_var.set(str(payload))
861
  self._log(str(payload))
862
  self._reset_buttons()
863
  elif kind in ("cancelled", "error"):
864
+ self.tele_phase_var.set(kind.upper())
865
  self.status_var.set(f"{kind.upper()}: {payload}")
866
  self._log(f"{kind.upper()}: {payload}")
867
  self._reset_buttons()
868
+ self.root.after(80, self._poll_queue)
869
 
870
  def _reset_buttons(self) -> None:
871
  self.run_btn.configure(state=tk.NORMAL)