Skip to content

Commit e06a599

Browse files
kmbandyclaude
andcommitted
DSWS: emergent wave economy + count-to-WAVES exit barrier + per-chunk diag
Snapshot the DSWS flow-economy work in progress (MAD-305, gfx1201/RDNA4): - occ_kernel_dsws_flow.s: emergent wave economy (baked NCOMP/NAFEED/NBFEED mix deleted; compute-biased seed; host-derived W_launch; VBUDGET physical budget) + RETBARRIER count-to-WAVES collective exit (all waves check in at .Lflow_dead and s_endpgm together so the EOP registers a clean completion; fixes the W=16 fence-never-fires stall: single-shot 25s-hang -> 0.81s clean). - occ_dispatch.cpp: run_dsws2 flow path (Wlaunch derivation, kernel-done gate, per-chunk EOP-on-last, periodic compositor yield) + ML8_CHUNK_DIAG per-chunk wall + STAGINSTR delta diagnostic (host-only, opt-in). - build_flow.sh: WAVES/VBUDGET/G/TRACE defsyms for the flow bin. - Supporting: coop/dsws/ring/grind kernels, oracle, and design/plan docs. WIP on feat/dsws-phaseb-conversion; open item = W=16 re-launched-chunk 2s straggler (compositor VGPR-contention hypothesis, under diagnosis). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0132aDSBLwusCJ4KzHQTnvdu
1 parent f2cc1f2 commit e06a599

20 files changed

Lines changed: 7640 additions & 13 deletions
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
 (0)