Skip to content

Commit 6f3e36f

Browse files
kmbandyclaude
andcommitted
feat(dsws): port try_gate ticket + reservation envelope
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0132aDSBLwusCJ4KzHQTnvdu
1 parent c714001 commit 6f3e36f

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@
125125
.ifndef DSWS2_CONV
126126
.set DSWS2_CONV, 0 // 0 = pre-conversion static substrate (Phase A green); 1 = Phase B
127127
.endif
128+
.ifndef DSWS2_TICKET_SELFTEST
129+
.set DSWS2_TICKET_SELFTEST, 0 // DIAG-only try_gate single-winner smoke (Task 4 Step 3); default 0 = no bytes
130+
.endif
128131
.set SNAP_BASE, (INITFLAG_OFF + 4) // u32[6]: [parity*3 + {0:nC,1:nA,2:nB}] role-mix snapshots
129132
.set QUIESCE_CNT_OFF,(SNAP_BASE + 6*4) // u32 role-agnostic bail counter
130133
.set DSWS2_STATE_END,(QUIESCE_CNT_OFF + 4)
@@ -386,6 +389,76 @@
386389
s_min_u32 s61, s60, \dst_b // cons_b = min(clock, prod_b)
387390
s_sub_u32 \dst_b, \dst_b, s61 // occ_B = prod_b - cons_b in [0,FN]
388391
.endm
392+
393+
// --------------------------------------------------------------------------------------------
394+
// Phase-B controller thresholds + sum-envelope budget (Task 4). EPOCH_SHIFT mirrors coop /
395+
// occ_dispatch (epoch = segcnt >> EPOCH_SHIFT). BUDGET is the per-WG VGPR sum-envelope ceiling
396+
// the reservation counter must never exceed; default = the launch reservation, which makes the
397+
// envelope a strict conservation law (a feed->compute grow can only fit if a compute->feed shrink
398+
// already freed the delta). Task 5 may re-tune via `-defsym BUDGET=` if per-SIMD headroom exists.
399+
// --------------------------------------------------------------------------------------------
400+
.ifndef EPOCH_SHIFT
401+
.set EPOCH_SHIFT, 3 // decision clock: epoch = segcnt >> EPOCH_SHIFT (small = reactive)
402+
.endif
403+
.ifndef BUDGET
404+
.set BUDGET, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) // = VRESV_OFF init (conservation ceiling)
405+
.endif
406+
407+
// try_gate: the lock-free single-winner conversion ticket (transcribed VERBATIM from occ_kernel_coop.s,
408+
// which transcribes dsws_ctrl_model.cpp gate_try_win + epoch_of EXACTLY). E = segcnt>>EPOCH_SHIFT.
409+
// gate[dir] holds the last epoch dir fired. Among many waves racing the same (g<E), exactly ONE wins
410+
// per epoch via the LDS compare-swap; the rest see g advanced. \swin <- 1 iff THIS wave won the
411+
// (dir,epoch) ticket, else 0. Read-only on state besides gate[dir]; NO role actuation here (Task 5
412+
// acts on \swin). Scratch: s62..s65 (free at the Task-5 occ_sample->try_gate->reserve_try point --
413+
// occ_sample's s62/s63 result is consumed into `dir` BEFORE this runs), v5/v6/v7 (<=v15: pre-grow /
414+
// lean-safe). CAS operand order (gfx1201, GCN order -- NOT flipped, KG 9ed04f3c):
415+
// ds_cmpstore_rtn_b32 vdst,vaddr,vNEW,vCMP -> MEM=(MEM==vCMP)?vNEW:MEM, vdst<-old. So vsrc0=E (new),
416+
// vsrc1=g (compare). WIN iff returned-old == g. (Swapping them leaves gate stuck so old==g for ALL
417+
// racers -> every racer "wins" -> would-win ~= NCOMP*epochs instead of ~= epochs.)
418+
.macro try_gate dir, swin
419+
lds_get s62, SEGCNT_OFF // E = epoch_of(segcnt, EPOCH_SHIFT)
420+
s_lshr_b32 s62, s62, EPOCH_SHIFT
421+
lds_get s63, (GATE_OFF + (\dir)*4) // g = gate[dir]
422+
s_mov_b32 \swin, 0
423+
s_cmp_ge_u32 s63, s62 // g >= E -> dir already fired this/later epoch -> lose
424+
s_cbranch_scc1 .Ltg_done\@
425+
s_mov_b32 s65, exec_lo // lane0-only CAS (one ticket attempt per WAVE)
426+
v_cmp_eq_u32 vcc_lo, 0, v2
427+
s_and_b32 exec_lo, exec_lo, vcc_lo
428+
s_cbranch_execz .Ltg_restore\@
429+
v_mov_b32 v5, (GATE_OFF + (\dir)*4) // vaddr = &gate[dir]
430+
v_mov_b32 v6, s63 // v6 = g (vsrc1 = CMP/expected)
431+
v_mov_b32 v7, s62 // v7 = E (vsrc0 = NEW value to store)
432+
ds_cmpstore_rtn_b32 v6, v5, v7, v6 // gate[dir] = (gate[dir]==g) ? E : gate[dir]; v6 <- old
433+
s_wait_dscnt 0x0
434+
.Ltg_restore\@:
435+
s_mov_b32 exec_lo, s65
436+
v_readfirstlane_b32 s64, v6 // s64 = old (lane0's CAS result, broadcast)
437+
s_cmp_eq_u32 s64, s63 // WIN iff old == g (we were the swapper)
438+
s_cbranch_scc0 .Ltg_done\@
439+
s_mov_b32 \swin, 1
440+
.Ltg_done\@:
441+
.endm
442+
443+
// reserve_try: the VGPR sum-envelope reservation (transcribes reserve_grow, dsws_ctrl_model.cpp:47).
444+
// Reserve first (atomic add of SIGNED \delta on vgpr_reserved), then validate prev+delta <= BUDGET;
445+
// on over-budget cleanly UNDO (atomic add of -\delta) and reject. The LDS atomic serializes the <=2
446+
// concurrent grows an epoch permits: the second to validate sees the first's reservation and backs off.
447+
// GROW (feed->compute): pass \delta = +(NFV-VLEAN). Over-budget -> undo, \won=0 (stay in role).
448+
// SHRINK(compute->feed): pass \delta = -(NFV-VLEAN). new = prev+delta < prev <= BUDGET, so the
449+
// validate branch is a proven no-op -> \won=1 ALWAYS (shrink never fails).
450+
// One macro, one call site (Task 5 `reserve_try delta, s_ok`); direction is the sign of \delta.
451+
// Scratch: s62/s63 (free at the bail-commit point -- try_gate's s62..s65 are long dead by then).
452+
.macro reserve_try delta, won
453+
lds_fetch_add s62, VRESV_OFF, (\delta) // s62 = prev reserved; vgpr_reserved += delta
454+
s_add_u32 s63, s62, (\delta) // s63 = new reservation = prev + delta
455+
s_mov_b32 \won, 1
456+
s_cmp_le_u32 s63, BUDGET // new <= BUDGET -> commit (win); shrink always passes
457+
s_cbranch_scc1 .Lrt_done\@
458+
lds_fetch_add s62, VRESV_OFF, -(\delta) // over-budget: undo the reservation, reject
459+
s_mov_b32 \won, 0
460+
.Lrt_done\@:
461+
.endm
389462
.endif
390463

391464
// ============================================================================================
@@ -434,6 +507,24 @@ occ_kernel:
434507
v_readfirstlane_b32 s24, v1 // wid (uniform per wave)
435508
s_cmp_eq_u32 s24, 0
436509
s_cbranch_scc1 .Lclaimer
510+
.if DSWS2_CONV && DIAG && DSWS2_TICKET_SELFTEST
511+
// Task 4 Step 3 -- try_gate single-winner SMOKE (assemble-only stub; default off). Every non-claimer
512+
// wave races the (dir=0) ticket ONCE and atomic-adds its win (0/1) into occ[28] (byte offset 112,
513+
// clear of the 0/20/24/104/108 control+probe words). On GPU (Task 6, if enabled) the sum should land
514+
// near #epochs, NOT NCOMP*#epochs -- the harness-side proof the LDS-CAS yields <=1 winner/(dir,epoch).
515+
// v4=0 (set in prologue), v2=lane; try_gate temps v5/v6/v7 are <=v15 (pre-grow safe). wid (s24)
516+
// survives -- try_gate touches only s62..s65 / s16. NOTE: pre-init-rendezvous placement -> a real run
517+
// reads gate/segcnt before the claimer publishes them; fine for an assemble/smoke stub.
518+
try_gate 0, s50
519+
v_cmp_eq_u32 vcc_lo, 0, v2
520+
s_mov_b32 s16, exec_lo
521+
s_and_b32 exec_lo, exec_lo, vcc_lo
522+
s_cbranch_execz .Ltg_selftest_skip
523+
v_mov_b32 v5, s50 // win flag (0/1) for THIS wave
524+
global_atomic_add_u32 v4, v5, s[0:1] offset:112 scope:SCOPE_DEV // occ[28] += win
525+
.Ltg_selftest_skip:
526+
s_mov_b32 exec_lo, s16
527+
.endif
437528
s_cmp_lt_u32 s24, NBFEED
438529
s_cbranch_scc1 .Lbfeed
439530
s_cmp_lt_u32 s24, (NBFEED+NAFEED)

0 commit comments

Comments
 (0)