diff --git a/README.md b/README.md index a78fefd..a21aceb 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Current tile-graph status: - the optimizer now has its own deep-dive map in `docs/optimizer-algorithm-map.md`, and the first cut from that map removed the old UV-optimizer name plus the continuous k-means cluster-boundary scout; the continuous path is now a fixed-lattice, source-first discrete chooser guided by real edge evidence - optimizer preparation is now split into a private `_OptimizerPrep` bundle, so the continuous path has a visible seam between "build the map" and "choose the pixels" - refine is now source-first too: the representative portrait still stabilizes snap, but refine and final structure choice no longer consult it, and the pinned badge smoke improved slightly from `0.07535` to `0.07485` +- the next relax cut also paid off: removing the old relax-mode bonus while keeping the core relax stage improved the same pinned badge smoke again from `0.07485` to `0.07451` - after the first pruning pass, tile-graph no longer participates in pipeline phase-rerank probes and no longer carries hybrid geometry priors through its unary cost; the path is now one lattice-conditioned candidate generator plus one local discrete solver - this fixes the core design mismatch that had allowed repeated distant labels to create big same-color patches and opaque black background blocks - on the current `24x24` emblem smoke case, an end-to-end `tile-graph` run dropped from about `2.57s` on CPU to `0.61s` on CUDA on this machine diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 42fa8a2..356bf62 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -141,6 +141,18 @@ The third optimizer cut is behavioral: - the dead refine/structure representative knobs were removed from `SolverHyperParams` and the tuning harness - the pinned cleaned-badge continuous smoke run under `artifacts/optimizer-cut-v3-source-refine-badge-126/` improved slightly: snap stayed `0.08100`, final moved from `0.07535` to `0.07485` +The fourth optimizer cut trims relax rather than removing it: + +- the old `refine_relaxed_mode_weight` bonus is gone, so greedy refine is no longer biased toward the relaxed-mode selection on every candidate score +- the core relax stage is still kept, because the earlier ablation showed that turning relax off entirely made the pinned badge worse +- the pinned cleaned-badge continuous smoke run under `artifacts/optimizer-cut-v4-relax-bonus-badge-126/` improved again: snap stayed `0.08100`, final moved from `0.07485` to `0.07451` + +Next after that: + +- keep putting relax on trial, but now term-by-term instead of as a monolith +- add a focused ablation runner for pinned badge/emblem cases before cutting more behavior +- collapse duplicated adjacency / motif / line voices only after ablation shows which stage owns each idea + The next optimizer simplification pass should use that map as its cutting checklist instead of guessing from scattered helper names. ## High-value regression case diff --git a/docs/optimizer-algorithm-map.md b/docs/optimizer-algorithm-map.md index 413877f..020013b 100644 --- a/docs/optimizer-algorithm-map.md +++ b/docs/optimizer-algorithm-map.md @@ -373,6 +373,11 @@ At the end of relax, the optimizer keeps two things: That is the machine hedging its bets before the next stage hardens everything again. +Recent cut: + +- the old extra `relaxed_mode` bonus in refine is gone +- the machine still compares the best refined state against the relaxed mode state at the end, but it no longer biases every greedy candidate score toward the relaxed mode + ## Stage 8: Greedy discrete refine Function: @@ -476,7 +481,7 @@ These are the places where the optimizer still speaks in an overly complicated o - the machine still maintains two overlapping portraits of the same lattice, even though refine is now source-first and only snap still arbitrates between them - snap, relax, and refine all carry slightly different versions of adjacency, motif, line, and delta agreement; the same idea is being said three times with different accents - the candidate generator is local and the solver is discrete, but the vocabulary around it still sounds like a continuous deformation engine -- the main entry point is now honestly named `optimize_lattice_pixels(...)`, the k-means boundary scout is gone, prep is split from decision-making, and refine no longer consults the representative portrait; those cuts removed four contradictions from the previous map +- the main entry point is now honestly named `optimize_lattice_pixels(...)`, the k-means boundary scout is gone, prep is split from decision-making, refine no longer consults the representative portrait, and the relax-mode bonus is gone; those cuts removed five contradictions from the previous map This is probably the real cutting checklist for the next optimizer pass. @@ -493,7 +498,7 @@ That is much simpler than the surrounding names make it sound. If we keep pruning, the highest-value cuts look like this: 1. Put relax on trial. - The soft relaxation stage may be a useful bridge, or it may be an expensive second solver that mostly repeats refine. + The soft relaxation stage still seems useful, but the relax-mode bonus was dead weight. The next question is which remaining relax terms are actually earning their keep. 2. Collapse duplicate structure voices. Adjacency, motif, and line are important, but they should not need three near-parallel dialects unless they are truly doing different work. 3. Decide whether snap still needs both portraits everywhere. diff --git a/src/repixelizer/continuous.py b/src/repixelizer/continuous.py index 87b6156..2ad81e1 100644 --- a/src/repixelizer/continuous.py +++ b/src/repixelizer/continuous.py @@ -1243,15 +1243,9 @@ def _discrete_refine_output( solver_params, iterations=relax_iterations, ) - relaxed_mode_colors = _select_colors(candidate_colors, relaxed_mode_selected) refine_base_energy = base_energy + ( (candidate_colors - relaxed_context[..., None, :]).abs().mean(dim=-1) * solver_params.relax_handoff_weight ) - if solver_params.refine_relaxed_mode_weight > 0.0: - refine_base_energy = refine_base_energy + ( - (candidate_colors - relaxed_mode_colors[..., None, :]).abs().mean(dim=-1) - * solver_params.refine_relaxed_mode_weight - ) start_candidates = [selected, relaxed_mode_selected] selected = selected.clone() best_selected = selected.clone() diff --git a/src/repixelizer/params.py b/src/repixelizer/params.py index f91dea9..ba86143 100644 --- a/src/repixelizer/params.py +++ b/src/repixelizer/params.py @@ -31,7 +31,6 @@ class SolverHyperParams: refine_diagonal_weight: float = 0.08 refine_motif_weight: float = 0.12 refine_line_weight: float = 0.10 - refine_relaxed_mode_weight: float = 0.14 refine_candidate_extent: float = 0.70 refine_candidate_levels: int = 7 guided_candidate_edge_threshold: float = 0.06