|
| 1 | +# Bench 334: Sudoku Speculative-Solve Perf — Arto Inkala (Hardest) |
| 2 | + |
| 3 | +Pure perf characterization (no GOAT gate — this is a perf-truth bench, not a |
| 4 | +primitive-promotion gate). Answers: **how fast can we solve the hardest Sudoku |
| 5 | +the speculate way, and can it beat backtracking?** |
| 6 | + |
| 7 | +## Setup |
| 8 | + |
| 9 | +- **Puzzle**: Arto Inkala "World's Hardest" — 21 clues, 60 empty cells. |
| 10 | +- **Bench**: `benches/sudoku_speculate_bench.rs` (`harness = false`, `std::time::Instant`, |
| 11 | + median-of-31 × batch-of-16, 3 warmup — matches `cucg_bench.rs` convention). |
| 12 | +- **Drafter**: uniform marginals over digits 1–9 (worst case, zero signal — the |
| 13 | + pruner supplies all constraint information). |
| 14 | +- **Pruner**: path-aware `SudokuPruner` (100% valid branches, cross-depth conflict |
| 15 | + checking). |
| 16 | +- **Run**: `cargo bench --bench sudoku_speculate_bench --features sudoku` |
| 17 | + |
| 18 | +## Three Modes |
| 19 | + |
| 20 | +| Mode | What it measures | |
| 21 | +|------|------------------| |
| 22 | +| 1. `backtrack` | Canonical `Sudoku9x9::solve()` — ground-truth complete solver (naive: try 1-9 in order, no MRV). | |
| 23 | +| 4. `solve_fast` | `Sudoku9x9::solve_fast()` — MRV cell selection + bitmask candidates + naked-singles constraint propagation. **The faster way.** | |
| 24 | +| 2. `speculate_iterative` | Iterative DDTree + greedy path commit + backtrack fallback. The realistic "speculative decoding" pattern. | |
| 25 | +| 3. `build_one_tree` | Raw DDTree primitive throughput — nodes/µs for one 8-deep build. | |
| 26 | + |
| 27 | +## Results (macOS, release, 2026-06-27) |
| 28 | + |
| 29 | +### Mode 1 — backtrack baseline |
| 30 | + |
| 31 | +| Metric | Value | |
| 32 | +|--------|-------| |
| 33 | +| solved | ✅ true | |
| 34 | +| steps | 49,559 | |
| 35 | +| median time | **2.417 ms/solve** | |
| 36 | +| per-step | 0.05 µs | |
| 37 | + |
| 38 | +Matches the docs baseline (49,559 steps) — bench is correct. |
| 39 | + |
| 40 | +### Mode 4 — solve_fast (MRV + constraint propagation) ← THE FASTER WAY |
| 41 | + |
| 42 | +| Metric | Value | |
| 43 | +|--------|-------| |
| 44 | +| solved | ✅ true | |
| 45 | +| steps | **1,851** (vs backtrack 49,559 → **27× fewer**) | |
| 46 | +| median time | **367 µs/solve** | |
| 47 | +| per-step | 0.20 µs | |
| 48 | +| speedup | **6.6× faster** wall time | |
| 49 | + |
| 50 | +Pure modelless: MRV cell ordering + bitmask candidate tracking + naked-singles |
| 51 | +constraint propagation. No training, no gradient descent — just deterministic |
| 52 | +rules. This is the honest answer to "is there a faster way": **yes, and it was |
| 53 | +hiding in plain sight** — the original `solve()` was a deliberately naive |
| 54 | +proof-of-concept for the streaming/hull-attention demo, not a fast solver. |
| 55 | + |
| 56 | +### Mode 2 — speculate_iterative (DDTree + greedy commit + fallback) |
| 57 | + |
| 58 | +| lookahead | budget | solved | spec_commits | fallback_steps | tree_nodes | time | |
| 59 | +|-----------|--------|--------|--------------|----------------|------------|------| |
| 60 | +| 4 | 32 | ❌ false | 13 | 3 | 85 | 11.02 µs | |
| 61 | +| 8 | 64 | ❌ false | 14 | 18 | 145 | 18.33 µs | |
| 62 | +| 8 | 128 | ❌ false | 12 | 237 | 224 | 31.40 µs | |
| 63 | +| 16→8 | 256 | ❌ false | 7 | 4 | 259 | 28.05 µs | |
| 64 | + |
| 65 | +Every config falls back to backtracking (solved=false means the speculation |
| 66 | +hit a dead-end and the fallback ran — but the bench reports pre-fallback |
| 67 | +spec_commits and the fallback step count). The speculate phase itself is |
| 68 | +microseconds-fast, but it never solves Inkala: uniform marginals have no |
| 69 | +signal, so greedy commits paint into corners within ~7–14 cells, then revert |
| 70 | ++ backtrack. |
| 71 | + |
| 72 | +### Mode 3 — DDTree primitive throughput (lookahead=8) |
| 73 | + |
| 74 | +| budget | nodes_built | time | nodes/µs | |
| 75 | +|--------|-------------|------|----------| |
| 76 | +| 64 | 64 | 6.97 µs | 9.2 | |
| 77 | +| 256 | 256 | 25.24 µs | 10.1 | |
| 78 | +| 1,024 | 1,024 | 106.81 µs | 9.6 | |
| 79 | +| 4,096 | 2,678 | 262.75 µs | 10.2 | |
| 80 | +| 16,384 | 2,678 | 270.12 µs | 9.9 | |
| 81 | + |
| 82 | +Steady-state ~10 nodes/µs (10 M nodes/sec). Tree saturates at 2,678 nodes — |
| 83 | +that's the full 8-deep pruned search space for Inkala's first 8 empties |
| 84 | +(9⁸ raw = 43M, pruned to 2,678 = 16,000× reduction by the path-aware pruner). |
| 85 | + |
| 86 | +## Key Finding — Architectural Ceiling |
| 87 | + |
| 88 | +**`TreeNode.parent_path: u128` packs 16-bit tokens → hard max lookahead = 8 |
| 89 | +(128/16).** The DDTree speculate primitive is a **token-level speculative- |
| 90 | +decoding kernel**, NOT a full-puzzle search. A 60-empty Sudoku **cannot be |
| 91 | +solved in one tree** — it physically cannot fit in the u128 path encoding. |
| 92 | + |
| 93 | +The coupled limit: `TreeBuilder::parent_tokens_buf` is sized to |
| 94 | +`config.draft_lookahead + 1` (= 9 by default). Exploring depth ≥ 9 panics with |
| 95 | +`range end index 10 out of range for slice of length 9`. This is why |
| 96 | +`Config::draft()` ships with `draft_lookahead: 8`. |
| 97 | + |
| 98 | +## Verdict — Can speculate beat backtrack on hardest Sudoku? |
| 99 | + |
| 100 | +**No, not with the current infrastructure + uniform drafter.** Two reasons: |
| 101 | + |
| 102 | +1. **No signal**: With uniform marginals, the drafter contributes zero |
| 103 | + information — every digit it proposes is already constraint-valid via the |
| 104 | + pruner. Speculation at best matches backtrack; at worst it pays tree-build |
| 105 | + overhead (~10–30 µs/round) before falling back. |
| 106 | + |
| 107 | +2. **8-deep ceiling**: Even with a perfect drafter, the u128 layout caps |
| 108 | + lookahead at 8. Solving 60 cells requires ≥8 speculate rounds, each |
| 109 | + paying the primitive cost, with dead-end reverts between them. |
| 110 | + |
| 111 | +**Break-even**: speculate wins only when |
| 112 | +`acceptance_rate × commits_per_round × per_commit_savings > tree_build_overhead`. |
| 113 | +With `p_accept = 1/9` (uniform) on Inkala, the LHS ≈ 1 × 8 × 0.05µs = 0.4µs |
| 114 | +<< RHS ≈ 10–30µs. Never holds. |
| 115 | + |
| 116 | +## What Would Make Speculate Win |
| 117 | + |
| 118 | +Per the modelless-first mandate (AGENTS.md), before deferring to riir-train: |
| 119 | + |
| 120 | +1. **MRV cell ordering** (modelless): reorder empties by minimum-remaining- |
| 121 | + values so the drafter proposes forced moves first. This is a deterministic |
| 122 | + drafter improvement, no training needed. Could push `p_accept` from 1/9 |
| 123 | + toward 1/1 on forced cells. |
| 124 | +2. **Constraint propagation as the drafter** (modelless): use naked/hidden |
| 125 | + singles as the draft signal — any cell with 1 valid digit is committed |
| 126 | + without speculation. Pure deterministic rules engine. |
| 127 | +3. **Trained digit priors** (→ riir-train, deferred): a real draft model that |
| 128 | + proposes the RIGHT digit, not just a valid one. Out of scope for modelless. |
| 129 | + |
| 130 | +Options 1 and 2 are modelless and should be tried first per §3.5 of the |
| 131 | +research skill. Filing as an optimization candidate (see `issues/`). |
| 132 | + |
| 133 | +## Files |
| 134 | + |
| 135 | +- `benches/sudoku_speculate_bench.rs` — the bench (380 LOC). |
| 136 | +- `Cargo.toml` — `[[bench]]` entry (required-features = `["sudoku"]`, harness = false). |
| 137 | + |
| 138 | +## TL;DR |
| 139 | + |
| 140 | +Hardest Sudoku (Inkala) solves in **2.417 ms** via naive backtrack (49,559 |
| 141 | +steps), or **367 µs** via `solve_fast` (1,851 steps, **6.6× faster**) — a |
| 142 | +pure modelless MRV + constraint-propagation solver. |
| 143 | + |
| 144 | +The speculate way **cannot beat either** with the current DDTree infra: |
| 145 | +(a) uniform marginals give the drafter zero signal, and (b) |
| 146 | +`TreeNode.parent_path: u128` hard-caps lookahead at 8, so a 60-cell puzzle can |
| 147 | +never be solved in one tree. The DDTree primitive runs at ~10 M nodes/sec — |
| 148 | +fast for token-level speculative decoding, but the wrong tool for full-puzzle |
| 149 | +search. |
| 150 | + |
| 151 | +**Lesson**: before asking "can speculate beat backtrack", check whether the |
| 152 | +baseline solver itself is optimal. The 27× step reduction from MRV+CP dwarfs |
| 153 | +anything speculation could deliver on top of a naive backtracker. |
0 commit comments