Skip to content

Commit f2cc1f2

Browse files
kmbandyclaude
andcommitted
feat(dsws): Phase-B debugging machinery + gated rolling-dyn-VGPR scaffolding
Checkpoint of the SUSPECT #2 debugging session (root cause + fix landed separately in the DECODE_STI ti clamp, f013114). Everything here is either offline-only or gated OFF by default and byte-identical when disabled — the CONV=1/DIAG=0 .text is unchanged (490db6e5) with all new symbols at their defaults. occ_kernel_dsws.s (gated scaffolding, all default-off): - DSWS2_ENVELOPE: the rolling dyn-VGPR sum-envelope (per-rowblk reserve/grow/ flush/shrink/release via vgpr_reserved) + PEAK_CONC; reserve_try/BUDGET gate widened to DSWS2_CONV||DSWS2_ENVELOPE so the envelope can run at CONV=0. Isolation-tested green at CONV=0 (the perf lever for the next phase). - DSWS2_GQUIESCE: device-scoped global QUIESCE handshake (a candidate fix that the ti clamp obviated; kept for reference, shelved). - DSWS2_BAILMARK: per-wave bail-epoch localization marks (diagnostic; host readout lives in the still-WIP occ_dispatch.cpp, not committed here). Offline models/tests: - dsws_ctrl_model.cpp + test_dsws_ctrl_model.cpp: reserve_spin/reserve_release + envelope-invariant test. - test_dsws_envelope_race.cpp: forward-progress thread race for the burst reserve (0 stalls, all PEAK_CONC>=1). - test_dsws_straggler.cpp: liveness/straggler model that showed the CONV=1 hang needed a hard-stuck follower, ruling out the ordering hypothesis. Docs: the rolling dyn-VGPR envelope design spec + implementation plan. build_dsws.sh: mk2 passthrough for the new envelope defsyms (default-off). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0132aDSBLwusCJ4KzHQTnvdu
1 parent f013114 commit f2cc1f2

8 files changed

Lines changed: 1300 additions & 4 deletions

File tree

docs/superpowers/plans/2026-07-02-dsws-rolling-dynvgpr-envelope.md

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.

docs/superpowers/specs/2026-07-02-dsws-rolling-dynvgpr-envelope-design.md

Lines changed: 350 additions & 0 deletions
Large diffs are not rendered by default.

ggml/src/ggml-cuda/aiter-integration/rdna4_fp8_gemm/spike/dvgpr_occ/build_dsws.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ mk2() { # $1=NCOMP $2=NAFEED $3=NBFEED (DSWS2 v2 substrate, occ_kernel_dsws.s;
2424
# BUDGET passthrough: default mirrors the in-file .ifndef launch-footprint conservation ceiling
2525
# (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN, with NFV=112/VLEAN=32 at this file's fixed FM=2 FN=4) so an
2626
# unset $BUDGET reproduces the existing default exactly; set $BUDGET to give real per-SIMD headroom.
27-
local budget="${BUDGET:-$(( $1 * 112 + ($2 + $3) * 32 ))}"
27+
# Envelope-mode budget = WAVES*VLEAN + PEAK_CONC*(NFV-VLEAN) = (c+a+b)*32 + PEAK_CONC*80; else static-fat.
28+
local budget
29+
if [ "${DSWS2_ENVELOPE:-0}" = "1" ]; then
30+
budget="${BUDGET:-$(( ($1 + $2 + $3) * 32 + ${PEAK_CONC:-2} * 80 ))}"
31+
else
32+
budget="${BUDGET:-$(( $1 * 112 + ($2 + $3) * 32 ))}"
33+
fi
2834
# DSWS2_FORCE* passthrough (Task 5): defaults mirror the in-file .ifndef values exactly, so an
2935
# unset env leaves every existing mk2 call byte-identical (DSWS2_FORCE=0 emits zero bytes).
3036
nice -19 ionice -c3 "$L/clang" -x assembler -target amdgcn-amd-amdhsa -mcpu=gfx1201 \
@@ -33,6 +39,8 @@ mk2() { # $1=NCOMP $2=NAFEED $3=NBFEED (DSWS2 v2 substrate, occ_kernel_dsws.s;
3339
-Wa,-defsym,NCOMP=$1 -Wa,-defsym,NAFEED=$2 -Wa,-defsym,NBFEED=$3 \
3440
-Wa,-defsym,DSWS2_FORCE=${DSWS2_FORCE:-0} -Wa,-defsym,DSWS2_FORCE_WID=${DSWS2_FORCE_WID:-0} \
3541
-Wa,-defsym,DSWS2_FORCE_DIR=${DSWS2_FORCE_DIR:-0} -Wa,-defsym,DSWS2_FORCE_EPOCH=${DSWS2_FORCE_EPOCH:-1} \
42+
-Wa,-defsym,DSWS2_ENVELOPE=${DSWS2_ENVELOPE:-0} -Wa,-defsym,PEAK_CONC=${PEAK_CONC:-2} \
43+
-Wa,-defsym,DSWS2_STAGGER=${DSWS2_STAGGER:-0} -Wa,-defsym,STAGGER_PERIOD=${STAGGER_PERIOD:-4} \
3644
-c occ_kernel_dsws.s -o "$tag.o" 2>/tmp/dsws2_build.err \
3745
&& { "$L/llvm-objcopy" -O binary --only-section=.text "$tag.o" "$tag.bin"; echo " OK $tag.bin ($(wc -c < "$tag.bin")B)"; } \
3846
|| { echo " FAIL $tag"; sed -n '1,15p' /tmp/dsws2_build.err; fail=1; }

ggml/src/ggml-cuda/aiter-integration/rdna4_fp8_gemm/spike/dvgpr_occ/dsws_ctrl_model.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ static inline bool reserve_grow(std::atomic<uint32_t>& resv, uint32_t delta, uin
5353
return true;
5454
}
5555

56+
// Compute-burst reserve with spin-retry (models .Lcompute_reserve): reserve +delta against the
57+
// sum-envelope; on over-budget, reserve_grow has already undone its add, so back off and retry.
58+
// `spins` accumulates the backoff count (permit-starvation depth). Bounded when >=1 peak fits.
59+
static inline void reserve_spin(std::atomic<uint32_t>& resv, uint32_t delta,
60+
uint32_t budget, uint64_t& spins) {
61+
while (!reserve_grow(resv, delta, budget)) ++spins;
62+
}
63+
// Release a booked burst (models the post-shrink lds_fetch_add VRESV_OFF, -delta). Never fails.
64+
static inline void reserve_release(std::atomic<uint32_t>& resv, uint32_t delta) {
65+
resv.fetch_sub(delta, std::memory_order_acq_rel);
66+
}
67+
5668
struct WgSnap { uint32_t nC, nA, nB; };
5769

5870
static inline WgSnap snapshot_counts(uint32_t nC, uint32_t nA, uint32_t nB) {

ggml/src/ggml-cuda/aiter-integration/rdna4_fp8_gemm/spike/dvgpr_occ/occ_kernel_dsws.s

Lines changed: 173 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,49 @@
149149
.ifndef DSWS2_FORCE_EPOCH
150150
.set DSWS2_FORCE_EPOCH, 1
151151
.endif
152+
// Rolling dyn-VGPR sum-envelope (2026-07-02 spec). ENVELOPE routes the per-rowblk compute burst grow
153+
// through the shared vgpr_reserved counter so at most PEAK_CONC waves hold peak at once (the
154+
// multi-grower collision, ISA 3.3.3.2, becomes unreachable). All default to the byte-identical value:
155+
// ENVELOPE=0/STAGGER=0 emit ZERO new bytes and PEAK_CONC/STAGGER_PERIOD are inert unless their gate is on.
156+
.ifndef DSWS2_ENVELOPE
157+
.set DSWS2_ENVELOPE, 0 // 1 = route the per-rowblk compute burst grow through the vgpr_reserved
158+
.endif // sum-envelope. 0 = HEAD (bare .Lcompute_grow) -> .text byte-identical.
159+
.ifndef PEAK_CONC
160+
.set PEAK_CONC, 2 // concurrent compute peaks the budget admits (R3 sweep). Used iff ENVELOPE=1.
161+
.endif
162+
.ifndef DSWS2_STAGGER
163+
.set DSWS2_STAGGER, 0 // 1 = lock-free phase-token stagger (Task 9). 0 -> emergent envelope stagger.
164+
.endif
165+
.ifndef STAGGER_PERIOD
166+
.set STAGGER_PERIOD, NCOMP // phase slots in the stagger ring (R3 sweep). Used iff STAGGER=1.
167+
.endif
152168
.set SNAP_BASE, (INITFLAG_OFF + 4) // u32[6]: [parity*3 + {0:nC,1:nA,2:nB}] role-mix snapshots
153-
.set QUIESCE_CNT_OFF,(SNAP_BASE + 6*4) // u32 role-agnostic bail counter
169+
.set QUIESCE_CNT_OFF,(SNAP_BASE + 6*4) // u32 role-agnostic bail counter (LDS; DSWS2_GQUIESCE=0)
154170
.set DSWS2_STATE_END,(QUIESCE_CNT_OFF + 4)
171+
// DSWS2_GQUIESCE (2026-07-02 SUSPECT #2 candidate fix): route the QUIESCE handshake through a DEVICE-SCOPED
172+
// GLOBAL atomic in the uncached occ buffer (byte QUIESCE_GOFF), mirroring the GREEN occ[20] claim/occ[0]
173+
// live handshake, instead of the barrier-free LDS counter (whose cross-wave visibility is unguaranteed and
174+
// is the leading SUSPECT #2 hang mechanism). occ buffer = AllocGpu 0x1000 (1024 u32, uncached); host uses
175+
// occ[0..6] + DIAG scratch (<= byte 116); byte 200 (occ[50]) is provably free. Default 0 => LDS path,
176+
// .text byte-identical. Requires DSWS2_CONV (QUIESCE only exists there).
177+
.ifndef DSWS2_GQUIESCE
178+
.set DSWS2_GQUIESCE, 0
179+
.endif
180+
.set QUIESCE_GOFF, 200 // occ[] byte offset for the global QUIESCE counter (occ[50])
181+
// DSWS2_BAILMARK (SUSPECT #2 localization, 2026-07-03): each follower publishes its OWN epoch (s35) to a
182+
// PER-WAVE occ slot (BAIL_BASE + wid*4) at its _quiesce bail. One-shot per super-tile per wave -> minimal
183+
// timing perturbation (NOT the claimer's per-spin DIAG poll stores, which are the heisenbug source and stay
184+
// DIAG-only). After a watchdog abort the host reads occ[BAIL_BASE/4 + wid]: every follower's slot == the
185+
// hung epoch => all reached their bail (=> a QUIESCE visibility/lost-update, gq relevant); ONE slot stale
186+
// at the prior epoch => that exact wave is the STRAGGLER (stuck in _alloc/_init/_follow; gq irrelevant).
187+
// Per-WAVE (not per-role): 4 compute share one role, so a role mark's last-writer-wins would hide a single
188+
// straggler. Default 0 => no bytes, .text byte-identical. Requires DSWS2_CONV.
189+
.ifndef DSWS2_BAILMARK
190+
.set DSWS2_BAILMARK, 0
191+
.endif
192+
.set BAIL_BASE, 160 // occ[] byte offset base for per-wave bail marks: occ[40..47]
193+
// (host prints occ[40..47] as BAIL[w0..w7]; clear of the
194+
// occ[32..36]/occ[39] DSWS sensor+roles slots and occ[50] gq)
155195
.set KSEG_STEPS, (SEGK/16) // K16-steps per split-K segment = SEGK K-elements / 16
156196
// FIX 1(b): NKSEG_SHIFT = log2(KSEG_STEPS), so the prologue can derive n_kseg = KT >> NKSEG_SHIFT instead
157197
// of receiving it as a (now-dropped) kernarg. SEGK is always a power-of-two multiple of 16 in every
@@ -409,7 +449,11 @@
409449
// OOR-poison under dyn-VGPR, SPEC S4): scalars <= s65 only (s60/s61 scratch; callers pass dst in
410450
// [s62,s65]); the only vector temps are inside lds_get, which uses v11/v14 (INTERIOR to the launch
411451
// 16-VGPR block) -- NO >v15 temp is introduced here.
412-
.if DSWS2_CONV
452+
// GATE: DSWS2_CONV || DSWS2_ENVELOPE. reserve_try + the BUDGET default are the pool-economy primitives the
453+
// rolling envelope needs INDEPENDENTLY of role conversion (they touch only VRESV_OFF/lds_fetch_add), so the
454+
// envelope must be able to run at CONV=0 (the isolation config). Everything in this block is macro/.set
455+
// definition (emits ZERO bytes), so widening the gate is byte-identical at CONV=0/ENV=0 and CONV=1.
456+
.if DSWS2_CONV || DSWS2_ENVELOPE
413457
.macro occ_sample dst_a, dst_b // out: \dst_a=occ_A in [0,G], \dst_b=occ_B in [0,FN]; clob s60,s61
414458
lds_get \dst_a, AROW_DONE_OFF // prod_a: A rowblks resident (store-completion)
415459
lds_get \dst_b, BFRAG_DONE_OFF // prod_b: B frags resident (store-completion)
@@ -420,6 +464,46 @@
420464
s_sub_u32 \dst_b, \dst_b, s61 // occ_B = prod_b - cons_b in [0,FN]
421465
.endm
422466

467+
// ---- DSWS2_GQUIESCE: device-scoped GLOBAL QUIESCE handshake (mirrors the green occ[20]/occ[0] pattern).
468+
// All three ops are lane-0-masked (v2==0), exec saved/restored via s49 (the LDS-macro convention -- s49 is
469+
// never live across a macro boundary, so it is provably free at every site these replace an lds_* op).
470+
// vaddr = v4 (the stable occ-base per-lane offset, =0, prologue-set), data/dst = v3/v5 (occ scratch vregs,
471+
// same as the claim/live ops). scope:SCOPE_DEV + uncached occ buffer => device-coherent visibility (the
472+
// fix). s_wait_storecnt/loadcnt drain before proceeding so the poll observes committed bumps.
473+
.macro gq_reset // claimer: occ[QUIESCE_GOFF] = 0 (committed before EPOCH publish)
474+
s_mov_b32 s49, exec_lo
475+
v_cmp_eq_u32 vcc_lo, 0, v2
476+
s_and_b32 exec_lo, exec_lo, vcc_lo
477+
s_cbranch_execz .Lgqr_skip\@
478+
v_mov_b32 v3, 0
479+
global_store_b32 v4, v3, s[0:1] offset:QUIESCE_GOFF scope:SCOPE_DEV
480+
s_wait_storecnt 0x0
481+
.Lgqr_skip\@:
482+
s_mov_b32 exec_lo, s49
483+
.endm
484+
.macro gq_bump // follower: occ[QUIESCE_GOFF] += 1 (one bump/wave/super-tile)
485+
s_mov_b32 s49, exec_lo
486+
v_cmp_eq_u32 vcc_lo, 0, v2
487+
s_and_b32 exec_lo, exec_lo, vcc_lo
488+
s_cbranch_execz .Lgqb_skip\@
489+
v_mov_b32 v3, 1
490+
global_atomic_add_u32 v4, v3, s[0:1] offset:QUIESCE_GOFF scope:SCOPE_DEV
491+
s_wait_storecnt 0x0
492+
.Lgqb_skip\@:
493+
s_mov_b32 exec_lo, s49
494+
.endm
495+
.macro gq_read dst // claimer: \dst = occ[QUIESCE_GOFF] (lane0 load + broadcast)
496+
s_mov_b32 s49, exec_lo
497+
v_cmp_eq_u32 vcc_lo, 0, v2
498+
s_and_b32 exec_lo, exec_lo, vcc_lo
499+
s_cbranch_execz .Lgqrd_skip\@
500+
global_load_b32 v5, v4, s[0:1] offset:QUIESCE_GOFF scope:SCOPE_DEV
501+
s_wait_loadcnt 0x0
502+
.Lgqrd_skip\@:
503+
s_mov_b32 exec_lo, s49
504+
v_readfirstlane_b32 \dst, v5
505+
.endm
506+
423507
// ---- Pool-T7 chunk-2 wedge localization (DIAG-only; DSWS2_CONV=0 emits nothing -> .text byte-identical).
424508
// epoch_mark: lane-0 publishes this role's live epoch (s35) to a host-streamed occ slot so a hung dispatch
425509
// shows how far each role advanced (stream field roles[C/A/B]). v14<=v15 (feeds/compute are lean-32 at the
@@ -437,6 +521,26 @@
437521
.endif
438522
.endm
439523

524+
// bail_mark: PER-WAVE localization mark. Lane-0 writes this wave's epoch (s35) to occ[BAIL_BASE + wid*4]
525+
// (runtime vaddr since the offset depends on wid=s24). s48 scratch, s49 exec-save (macro-local; free at the
526+
// _quiesce bail sites), v13 vaddr, v14 data (both <=v15; the wave is lean-32 at every bail site). One-shot
527+
// per super-tile -> negligible perturbation vs the DIAG per-spin claimer stores. Enabled by DIAG OR BAILMARK.
528+
.macro bail_mark
529+
.if DSWS2_CONV && (DIAG || DSWS2_BAILMARK)
530+
s_mov_b32 s49, exec_lo
531+
v_cmp_eq_u32 vcc_lo, 0, v2
532+
s_and_b32 exec_lo, exec_lo, vcc_lo
533+
s_cbranch_execz .Lbmk_skip\@
534+
s_lshl_b32 s48, s24, 2 // wid*4
535+
s_add_u32 s48, s48, BAIL_BASE // occ byte offset for THIS wave
536+
v_mov_b32 v13, s48 // vaddr = per-wave byte offset (lane0)
537+
v_mov_b32 v14, s35 // data = this wave's current epoch
538+
global_store_b32 v13, v14, s[0:1] scope:SCOPE_DEV
539+
.Lbmk_skip\@:
540+
s_mov_b32 exec_lo, s49
541+
.endif
542+
.endm
543+
440544
// --------------------------------------------------------------------------------------------
441545
// Phase-B controller thresholds + sum-envelope budget (Task 4). EPOCH_SHIFT mirrors coop /
442546
// occ_dispatch (epoch = segcnt >> EPOCH_SHIFT). BUDGET is the per-WG VGPR sum-envelope ceiling
@@ -448,7 +552,11 @@
448552
.set EPOCH_SHIFT, 3 // decision clock: epoch = segcnt >> EPOCH_SHIFT (small = reactive)
449553
.endif
450554
.ifndef BUDGET
451-
.set BUDGET, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) // = VRESV_OFF init (conservation ceiling)
555+
.if DSWS2_ENVELOPE
556+
.set BUDGET, (WAVES*VLEAN + PEAK_CONC*(NFV-VLEAN)) // rolling: lean floor + concurrent-peak headroom
557+
.else
558+
.set BUDGET, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) // = VRESV_OFF init (conservation ceiling)
559+
.endif
452560
.endif
453561

454562
.if DSWS2_CONV
@@ -457,6 +565,12 @@
457565
.error "WAVES*VLEAN exceeds BUDGET — pool cannot stay all-lean (parking is out of scope)"
458566
.endif
459567
.endif
568+
.if DSWS2_ENVELOPE
569+
// forward-progress: the budget must admit at least one concurrent peak or a claimed wave can never grow
570+
.if (WAVES*VLEAN + (NFV-VLEAN)) > BUDGET
571+
.error "ENVELOPE: BUDGET admits < 1 concurrent peak — forward progress impossible"
572+
.endif
573+
.endif
460574

461575
// try_gate: the lock-free single-winner conversion ticket (transcribed VERBATIM from occ_kernel_coop.s,
462576
// which transcribes dsws_ctrl_model.cpp gate_try_win + epoch_of EXACTLY). E = segcnt>>EPOCH_SHIFT.
@@ -750,12 +864,20 @@ occ_kernel:
750864
lds_put (GATE_OFF+4), 0
751865
lds_put (GATE_OFF+8), 0
752866
lds_put (GATE_OFF+12), 0
867+
.if DSWS2_ENVELOPE
868+
lds_put VRESV_OFF, (WAVES*VLEAN) // rolling: everyone lean; counter books peaks
869+
.else
753870
lds_put VRESV_OFF, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN)
871+
.endif
754872
lds_put SEGCNT_OFF, 0
755873
.if DSWS2_CONV
756874
// Phase-B: seed BOTH epoch-parity role-mix snapshots with the launch mix, zero the quiesce counter.
757875
// Gated so DSWS2_CONV=0 emits ZERO new bytes -> byte-identical to the Phase-A green bin.
876+
.if DSWS2_GQUIESCE
877+
gq_reset // global QUIESCE = 0 (device-scoped; committed before INITFLAG)
878+
.else
758879
lds_put QUIESCE_CNT_OFF, 0
880+
.endif
759881
lds_put (SNAP_BASE + 0), NCOMP // parity-0 snapshot = launch mix
760882
lds_put (SNAP_BASE + 4), NAFEED
761883
lds_put (SNAP_BASE + 8), NBFEED
@@ -816,7 +938,11 @@ occ_kernel:
816938
s_add_u32 s46, s46, 4
817939
lds_get s47, NBFEED_SLOT
818940
lds_put_r s46, s47 // snap.nB = live NBFEED_SLOT
941+
.if DSWS2_GQUIESCE
942+
gq_reset // global QUIESCE = 0 (device-scoped; committed before EPOCH publish)
943+
.else
819944
lds_put QUIESCE_CNT_OFF, 0 // reset the per-super-tile bail counter
945+
.endif
820946
.endif
821947
lds_put EPOCH_OFF, s44
822948
BSTAGE // claimer helps stage B for this super-tile (s30,s31)
@@ -894,7 +1020,11 @@ occ_kernel:
8941020
.Lqc_ar_ok:
8951021
// quiesce_done (s51) = QUIESCE_CNT >= WAVES-1 (each of the WAVES-1 non-claimer waves bumped once)
8961022
s_mov_b32 s51, 1
1023+
.if DSWS2_GQUIESCE
1024+
gq_read s44 // device-scoped global read (observes committed follower bumps)
1025+
.else
8971026
lds_get s44, QUIESCE_CNT_OFF
1027+
.endif
8981028
s_cmp_lt_u32 s44, (WAVES-1)
8991029
s_cbranch_scc0 .Lqc_q_ok
9001030
s_mov_b32 s51, 0
@@ -935,6 +1065,16 @@ occ_kernel:
9351065
global_store_b32 v4, v14, s[0:1] offset:84 scope:SCOPE_DEV // occ[21] pub = AROW_NEXT
9361066
v_mov_b32 v14, s62
9371067
global_store_b32 v4, v14, s[0:1] offset:88 scope:SCOPE_DEV // occ[22] rawTi = BFRAG_NEXT
1068+
.if DSWS2_ENVELOPE
1069+
// Envelope telemetry (2026-07-02 spec 4.5): instantaneous vgpr_reserved at the wedge moment +
1070+
// PEAK_CONC config echo. If a dispatch wedges with occ[8]==BUDGET, it is permit-starvation (the
1071+
// envelope is saturated); anything below BUDGET points the wedge elsewhere. DIAG+ENVELOPE gated.
1072+
lds_get s52, VRESV_OFF
1073+
v_mov_b32 v14, s52
1074+
global_store_b32 v4, v14, s[0:1] offset:32 scope:SCOPE_DEV // occ[8] vgpr_reserved (want < BUDGET)
1075+
v_mov_b32 v14, PEAK_CONC
1076+
global_store_b32 v4, v14, s[0:1] offset:56 scope:SCOPE_DEV // occ[14] PEAK_CONC echo (config readback)
1077+
.endif
9381078
.Lqc_diag_skip:
9391079
s_mov_b32 exec_lo, s16
9401080
.endif
@@ -1034,8 +1174,13 @@ occ_kernel:
10341174
s_sub_i32 s66, s66, 1
10351175
.endif
10361176
.Lbfeed_quiesce:
1177+
.if DSWS2_GQUIESCE
1178+
gq_bump // device-scoped global bump (visible to the claimer poll)
1179+
.else
10371180
lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2)
1181+
.endif
10381182
epoch_mark 144 // roles[2] = B-feed last epoch bumped (wedge localize)
1183+
bail_mark // per-wave localization: occ[BAIL_BASE + wid*4] = this wave's epoch
10391184
.endif
10401185
.if DSWS2_CONV
10411186
s_branch .Ldispatch
@@ -1108,8 +1253,13 @@ occ_kernel:
11081253
s_sub_i32 s66, s66, 1
11091254
.endif
11101255
.Lafeed_quiesce:
1256+
.if DSWS2_GQUIESCE
1257+
gq_bump // device-scoped global bump (visible to the claimer poll)
1258+
.else
11111259
lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2)
1260+
.endif
11121261
epoch_mark 140 // roles[1] = A-feed last epoch bumped (wedge localize)
1262+
bail_mark // per-wave localization: occ[BAIL_BASE + wid*4] = this wave's epoch
11131263
.endif
11141264
.if DSWS2_CONV
11151265
s_branch .Ldispatch
@@ -1164,6 +1314,18 @@ occ_kernel:
11641314
.if DYNVGPR
11651315
s_wait_loadcnt 0x0
11661316
s_wait_storecnt 0x0
1317+
.if DSWS2_ENVELOPE
1318+
// Sum-envelope reserve (2026-07-02 spec 4.1): book +Δ against vgpr_reserved before growing so at most
1319+
// PEAK_CONC waves hold peak at once. Over-budget -> reserve_try already undid its add -> back off AT
1320+
// LEAN and retry (wave is still 32 here; every temp <=v15, pre-grow OOR-safe). s54=won, s62/s63 scratch
1321+
// (all dead in the claim loop -- conv only runs at .Lcompute_drained).
1322+
.Lcompute_reserve:
1323+
reserve_try +(NFV-VLEAN), s54
1324+
s_cmp_eq_u32 s54, 0
1325+
s_cbranch_scc0 .Lcompute_grow // won (s54!=0 -> SCC==0) -> grow
1326+
s_sleep SLEEPN
1327+
s_branch .Lcompute_reserve
1328+
.endif
11671329
.Lcompute_grow:
11681330
s_alloc_vgpr NFV // grow (SCC-retry guarded, brick-class rule)
11691331
s_cbranch_scc0 .Lcompute_grow
@@ -1230,6 +1392,9 @@ occ_kernel:
12301392
.Lcompute_shrink:
12311393
s_alloc_vgpr 32 // shrink (SCC-retry guarded)
12321394
s_cbranch_scc0 .Lcompute_shrink
1395+
.if DSWS2_ENVELOPE
1396+
lds_fetch_add s54, VRESV_OFF, -(NFV-VLEAN) // envelope release −Δ (shrink committed; wave lean, v<=v15)
1397+
.endif
12331398
.endif
12341399
lds_inc ROWBLK_DONE_OFF // rowblk r computed + flushed (frees the A7 advance gate)
12351400
s_branch .Lcompute_claim
@@ -1291,8 +1456,13 @@ occ_kernel:
12911456
.Lcmp_quiesce:
12921457
// ORDERING (SPEC 3.4 decision 2): the commit above fully completed (role-slot CAS + reservation +
12931458
// s_alloc_vgpr) BEFORE this QUIESCE_CNT bump -- the bump is the snapshot handshake the claimer reads.
1459+
.if DSWS2_GQUIESCE
1460+
gq_bump // device-scoped global bump (visible to the claimer poll)
1461+
.else
12941462
lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // exactly one bump per non-claimer wave / super-tile
1463+
.endif
12951464
epoch_mark 136 // roles[0] = compute last epoch bumped (wedge localize)
1465+
bail_mark // per-wave localization: occ[BAIL_BASE + wid*4] = this wave's epoch
12961466
.endif
12971467
.if DSWS2_CONV
12981468
s_branch .Ldispatch

ggml/src/ggml-cuda/aiter-integration/rdna4_fp8_gemm/spike/dvgpr_occ/test_dsws_ctrl_model.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ int main() {
118118
}
119119
printf("dsws_ctrl_model: entry-contract (Pool-T7 repro) OK\n");
120120

121+
// ---- envelope invariant: at most PEAK_CONC concurrent peaks fit; release frees a slot ----
122+
{
123+
const uint32_t VLEAN = 32, NFV = 112, D = NFV - VLEAN; // Δ = 80
124+
const uint32_t WAVES = 8, PEAK_CONC = 2;
125+
const uint32_t BUDGET = WAVES * VLEAN + PEAK_CONC * D; // 256 + 160 = 416
126+
std::atomic<uint32_t> resv{WAVES * VLEAN}; // init: everyone lean
127+
uint64_t spins = 0;
128+
reserve_spin(resv, D, BUDGET, spins); assert(spins == 0); // 1st peak fits
129+
reserve_spin(resv, D, BUDGET, spins); assert(spins == 0); // 2nd peak fits (== PEAK_CONC)
130+
assert(reserve_grow(resv, D, BUDGET) == false); // 3rd over-budget -> rejected + undone
131+
assert(resv.load() == WAVES * VLEAN + PEAK_CONC * D); // undo left counter exactly at 2 peaks
132+
reserve_release(resv, D); // free one slot
133+
assert(reserve_grow(resv, D, BUDGET) == true); // now the 3rd fits
134+
reserve_release(resv, D); reserve_release(resv, D);
135+
assert(resv.load() == WAVES * VLEAN); // conservation: back to all-lean
136+
printf("dsws_ctrl_model: envelope invariant OK\n");
137+
}
121138
printf("dsws_ctrl_model: ALL PASS\n");
122139
return 0;
123140
}

0 commit comments

Comments
 (0)