|
| 1 | +# DSWS Emergent Wave-Economy — Design (Step 1 of the "feed + kill-the-8-wave-limit" work) |
| 2 | + |
| 3 | +**Date:** 2026-07-05 |
| 4 | +**Status:** design, pre-plan. Follows `DSWS_STAGGER_DESIGN.md` / `DSWS_STAGGER_PLAN.md` (write-once + stagger, both oracle-clean to 14 waves/WG). |
| 5 | +**Council:** Fable + Codex consulted; verdict + corrections baked into §5. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. Goal |
| 10 | + |
| 11 | +Stop specifying the wave mix at all. The VGPR-budget accounting we just built should *decide* the |
| 12 | +wave economy at runtime — how many waves are fat compute vs lean feed emerges from the physical VGPR |
| 13 | +file, moment to moment. **No artificial wave-count limit.** Owner's words: *"we built this elegant |
| 14 | +accounting… so why are we even specifying waves? Let the accounting decide; set no artificial limit."* |
| 15 | + |
| 16 | +This is **Step 1 of two**. It removes the baked `NCOMP/NAFEED/NBFEED` mix and lets the economy emerge. |
| 17 | +It is **not** the operand-overlap feed fix (`POOL_N=2` via `SEGK=16`) — that is **Step 2**, a separate, |
| 18 | +separately-measured experiment (§7). The two are orthogonal and must not be conflated (§5, Risk B). |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 2. The mechanical truth (why this is a subtraction, not a rewrite) |
| 23 | + |
| 24 | +Grounded in the current `occ_kernel_dsws_flow.s`: |
| 25 | + |
| 26 | +- **Concurrent-fat already emerges from hardware.** The per-burst grow (`.Lflow_compute`) is a raw |
| 27 | + `s_alloc_vgpr NFV` → `s_cbranch_scc0 .Lflow_growfail`. SCC=0 means the *physical VGPR file* could not |
| 28 | + grant the grow. That is the moat and the true cap. The `BUDGET` / `reserve_try` LDS ledger |
| 29 | + (`VRESV_OFF=52`) is used **only** by `conv_apply` (the old role-conversion envelope), which is |
| 30 | + **dormant** in the flow build. So we do **not** need to build a budget mechanism — the hardware is it. |
| 31 | +- **No runtime role emergence exists today.** The coordinator's sense/nudge is deferred |
| 32 | + (`.Lflow_coord_period`: *"static launch mix for the first flow build"*). A wave reads a **static** |
| 33 | + `ROLE[wid]` each loop and simply *is* that role; the coordinator only ever rewrites `ROLE[wid]` to |
| 34 | + `ROLE_RETIRE` at the end. |
| 35 | +- **The only fungibility is the coast.** A COMPUTE wave with nothing staged, or whose grow fails, |
| 36 | + runs feed code (`.Lflow_coast` → helps whichever of A/B is behind) and returns to `.Lflow_loop`. |
| 37 | + Every path (`.Lflow_feed`, `.Lflow_feed_empty` = `s_sleep;branch`, `.Lflow_stage_adv`, bank-done, |
| 38 | + coast) loops back to the head, which re-reads `ROLE[wid]` and re-dispatches. So a **compute-seeded |
| 39 | + wave already oscillates compute↔feed** by (staged-availability × hardware-grow-success) each |
| 40 | + iteration. This flow is **asymmetric**: compute→feed only. A feed-seeded wave never tries compute. |
| 41 | + |
| 42 | +**Consequence:** the "baked mix" problem is entirely in (a) the *seed* (feed-seeded waves are stuck |
| 43 | +feeding) and (b) the assemble-time coupling of launch count / guards to `NCOMP/NAFEED/NBFEED`. Fix the |
| 44 | +seed to be compute-biased with a minimal feed floor, decouple the launch count, and the economy emerges |
| 45 | +using machinery that already exists and is already oracle-clean. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## 3. Design |
| 50 | + |
| 51 | +### 3.1 Compute-biased seed with a static liveness floor |
| 52 | +Replace the mix-proportional seed (`wid<NBFEED→BFEED`, `wid<NBFEED+NAFEED→AFEED`, else COMPUTE) with: |
| 53 | + |
| 54 | +``` |
| 55 | +wid 0 -> coordinator (runs lean B-feed between ASSIGN duties, as today) |
| 56 | +wid 1 -> ROLE_AFEED (static A-feed floor) |
| 57 | +wid 2 -> ROLE_BFEED (static B-feed floor) [wid0 already B-class, so this is the 2nd B] |
| 58 | +wid 3 .. W-1 -> ROLE_COMPUTE (everything else) |
| 59 | +``` |
| 60 | + |
| 61 | +- The floor (`≥1` dedicated A-feed + `≥1` dedicated B-feed, plus the coordinator's B-class feeding) is |
| 62 | + a **liveness invariant**, not a mix (§5, Risk A). At `POOL_N=1` the pipeline is depth-1 ping-pong; |
| 63 | + liveness *requires* someone always able to fill the operand pool regardless of the compute cohort's |
| 64 | + phase. This guarantees it deterministically, cheaply, with no spin. |
| 65 | +- Everything above the floor is COMPUTE-seeded. Those waves self-distribute: when work is staged and a |
| 66 | + grow succeeds they burst; when the budget is full (`growfail`) or nothing is staged they coast to |
| 67 | + feed and fill the pool; next iteration they try compute again. **Concurrent-fat = whatever the |
| 68 | + physical VGPR file admits** (the `growfail` boundary). No number is imposed. |
| 69 | +- The floor is fixed by `wid`, independent of any `NCOMP/NAFEED/NBFEED`. Those three symbols are |
| 70 | + **deleted** from the kernel. |
| 71 | + |
| 72 | +### 3.2 Decouple the launch count from the mix |
| 73 | +- Introduce a single wave-count defsym (e.g. `WAVES` becomes a standalone `-defsym`, default the |
| 74 | + swept-safe max). It no longer equals `NCOMP+NAFEED+NBFEED`. Everything sized off `WAVES` (the |
| 75 | + 32-wave mailbox region, the retire `.rept WAVES`, guards) keeps working — it already tolerates up |
| 76 | + to 30 (see below). |
| 77 | +- `G` (rowblk-group size = LDS bank count) is **structural**, currently written `.set G, 6 // = NCOMP_MAX`. |
| 78 | + Make `G` its own defsym (default 6), *not* tied to the launch mix. `ACC_N` (banks) stays coupled to `G`. |
| 79 | + |
| 80 | +### 3.3 `BUDGET` becomes a physical constant, decoupled from the mix |
| 81 | +- `BUDGET` today = `NCOMP*NFV + (NAFEED+NBFEED)*VLEAN` (mix-derived). With the mix gone, redefine it as |
| 82 | + a **physical VGPR budget** defsym `VBUDGET` (the VGPR-file credit ceiling), independent of wave count. |
| 83 | +- This does **not** change concurrent-fat behavior (the hardware `s_alloc_vgpr` is the real cap; the |
| 84 | + ledger is dormant). Its only jobs now are (i) keeping the assemble-time sanity guards meaningful and |
| 85 | + (ii) being available if we ever re-arm `conv_apply`. Keep the guards as physical sanity checks: |
| 86 | + - `WAVES*VLEAN ≤ VBUDGET` (all waves fit lean) |
| 87 | + - `WAVES*VLEAN + (NFV-VLEAN) ≤ VBUDGET` (≥1 wave can grow — liveness for compute progress) |
| 88 | + |
| 89 | +### 3.4 Host auto-derives `W_launch` (Option 3 — no wave number anywhere) |
| 90 | +The host picks the launch count as the **min of all real caps**, expecting VGPR to almost never be the |
| 91 | +winner: |
| 92 | + |
| 93 | +``` |
| 94 | +W_launch = min( occupancy_cap, // swept empirically (resident waves/SIMD, ~16 wave32) |
| 95 | + coordinator_cap = 30, // mailbox-tail squat (§3.5); hard structural ceiling |
| 96 | + lean_fit = (VBUDGET - (NFV-VLEAN)) / VLEAN ) // all-lean + one grow of headroom |
| 97 | +``` |
| 98 | + |
| 99 | +- Emergence belongs on the **fat/lean time profile** (the ledger/hardware already deliver it) and on |
| 100 | + the **role mix above the floor** — **not** on resident wave count, which is an |
| 101 | + occupancy-and-coordinator decision the ledger never measured. |
| 102 | +- The host stops reading `DSWS_NCOMP/NAFEED/NBFEED` for the flow path and stops naming the bin per-mix. |
| 103 | + One flow bin, built at `WAVES=W_launch`; the host launches `W_launch*32` threads/WG. |
| 104 | +- `occupancy_cap` is the one unknown → sweep it (§7). Until swept, gate `W_launch` at a conservative |
| 105 | + value and raise it by measurement. |
| 106 | + |
| 107 | +### 3.5 Mailbox / coordinator safety at larger counts |
| 108 | +- The mailbox already reserves a full 32-wave region: `ROLE_BASE=20 … SLOTC_BASE=148`. The coordinator |
| 109 | + state squats at `COORD_KSI_OFF=140` / `COORD_T_OFF=144` = `ROLE[30]` / `ROLE[31]`. Hence **`WAVES ≤ 30` |
| 110 | + is safe today**; `WAVES` of 31–32 would corrupt coord state **silently**. |
| 111 | +- `W_launch` is therefore hard-capped at **30** in §3.4. This is comfortably above the expected |
| 112 | + occupancy cap (~16 wave32), so we do **not** need to relocate coord state for Step 1. (If a future |
| 113 | + step wants 31–32, move `COORD_KSI/T` past the 32-wave region — out of scope here.) |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## 4. What we deliberately do NOT build |
| 118 | + |
| 119 | +- **Option 2 (fully fungible, one uniform wave body): rejected.** Council-unanimous. Dyn-VGPR already |
| 120 | + decouples launch footprint from peak, so fusion doesn't save VGPR; its real cost is that feed would |
| 121 | + become a *residual of compute contention* (you feed only when a grow fails), coupling the bottleneck |
| 122 | + resource to the noisy grow-failure rate the stagger is built to damp. Keeping distinct role bodies |
| 123 | + keeps feed a **controlled variable with a floor**. Also: it's a rewrite of a just-clean kernel. |
| 124 | +- **Re-arming the coordinator sense/nudge / `conv_apply` conversion:** not needed. The compute-biased |
| 125 | + seed + coast + hardware grow-fail deliver emergence without it. `conv_apply` stays dormant. |
| 126 | +- **The `POOL_N=2` / `SEGK=16` feed-overlap fix:** that is Step 2 (§7), not this change. |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## 5. Risks & mitigations (from the council) |
| 131 | + |
| 132 | +- **Risk A — liveness / stampede (both agents).** All-lean-unassigned could stampede all-compute |
| 133 | + (nobody feeds → compute stalls on an empty pool) or all-feed (nothing computes). **Mitigation:** the |
| 134 | + static feed floor in §3.1 is a *correctness invariant*, not tuning. At `POOL_N=1` the single operand |
| 135 | + slot also self-serializes, and `.Lflow_feed_empty` yields safely; deadman covers any stall. |
| 136 | +- **Risk B — this is ONE change or TWO? (both agents): TWO.** Emergent mix governs *who* works; |
| 137 | + `POOL_N` governs *whether feed and compute overlap at all*. Orthogonal. Killing the wave cap alone can |
| 138 | + even **regress** (more waves eligible to grab compute → more pressure on the one slot). The feed win |
| 139 | + is gated on `POOL_N=2` (`SEGK=16`), which is not free: halving SEGK doubles K-segments → doubles |
| 140 | + `ds_add` + `RBDONE` churn **and halves the WMMA burst length**, attacking the stagger's grow/shrink |
| 141 | + amortization. **Mitigation:** ship Step 1, re-confirm oracle + stagger self-maintenance, measure; |
| 142 | + *then* do Step 2 with its own before/after. |
| 143 | +- **Risk C — the VGPR file is NOT the binding cap (Fable; the one I underweighted).** `VBUDGET`/VLEAN |
| 144 | + math lands ~45 waves, but occupancy (~16 wave32) and the coordinator ≤30 bind first. **Mitigation:** |
| 145 | + §3.4 derives `W_launch = min(occupancy, 30, lean_fit)` — VGPR is a sanity ceiling, not the driver. |
| 146 | +- **Risk D — coordinator correctness under dynamic churn (Codex).** Oracle-clean at 14 waves does not |
| 147 | + prove stability near the physical boundary where grow-fails, coasting, pool state, and completer |
| 148 | + election interact. **Mitigation:** strongest oracle + repeats at each new wave count; watch for the |
| 149 | + first sign of a completer/mailbox race and root-cause immediately (do not defer). |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## 6. Acceptance criteria (Step 1 done = all hold) |
| 154 | + |
| 155 | +1. **Correctness:** oracle `bad=0`, `max_rel=0`, `occ[0]=0` at the new (larger) `W_launch`, with repeats |
| 156 | + (Risk D). Re-confirm at the old 4c2a2b-equivalent count too. |
| 157 | +2. **No mix anywhere:** kernel has no `NCOMP/NAFEED/NBFEED`; host does not read `DSWS_NCOMP/AFEED/BFEED` |
| 158 | + for the flow path and does not name the bin per-mix; `W_launch` is host-derived. |
| 159 | +3. **The stagger engages:** `grow-fail` (STAGINSTR `occ[73]`) flips from **0 → >0** — the proof the |
| 160 | + physical VGPR file finally binds and the stagger's coast-repulsion is doing real work. Peak resident |
| 161 | + waves (`occ[1]`) rises meaningfully above the old 8. |
| 162 | +4. **Stagger self-maintains:** no wedge/brick; deadman `occ[0]` stays 0; coast fraction sane (not ~100% |
| 163 | + feed-starve *and* not ~0% — some genuine grow-fail repulsion). |
| 164 | + |
| 165 | +Measurement uses the STAGINSTR counters already in place: `occ[70]=coast`, `[71]=computed`, `[72]=feed`, |
| 166 | +`[73]=growfail`, plus `occ[1]` resident peak and `occ[58]` peak concurrent fat. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## 7. Step 2 (sketch — separate experiment, not part of this plan) |
| 171 | + |
| 172 | +`POOL_N=2` double-buffer so feed and compute overlap instead of serializing on one operand slot. |
| 173 | +Needs `SEGK=16` to halve operand footprint and fit two slots + `ACC_N=6` banks in 64 KB. Costs: 2× K-seg |
| 174 | +count (2× `ds_add`/`RBDONE` churn), halved WMMA burst (stagger amortization pressure), and the "ksi=0 |
| 175 | +write vs add" first-segment handling must become an explicit bank-zero (two ksi in flight otherwise race |
| 176 | +the write/add). Land and measure on its own after Step 1 is clean. |
| 177 | + |
| 178 | +--- |
| 179 | + |
| 180 | +## 8. Open calibration items (resolved during the plan, not before) |
| 181 | + |
| 182 | +- `VBUDGET` exact value (physical VGPR-file credit for R9700 wave32) — sweep/confirm; default generous. |
| 183 | +- `occupancy_cap` — sweep resident waves/SIMD to find the real ceiling below 30. |
| 184 | +- Whether the coordinator (wid0) B-feed duty + `wid1` A-feed + `wid2` B-feed is the right floor, or |
| 185 | + `1×A + 1×B` (drop the redundant 2nd B) suffices — decide by the liveness sweep. |
0 commit comments