|
149 | 149 | .ifndef DSWS2_FORCE_EPOCH |
150 | 150 | .set DSWS2_FORCE_EPOCH, 1 |
151 | 151 | .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 |
152 | 168 | .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) |
154 | 170 | .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) |
155 | 195 | .set KSEG_STEPS, (SEGK/16) // K16-steps per split-K segment = SEGK K-elements / 16 |
156 | 196 | // FIX 1(b): NKSEG_SHIFT = log2(KSEG_STEPS), so the prologue can derive n_kseg = KT >> NKSEG_SHIFT instead |
157 | 197 | // of receiving it as a (now-dropped) kernarg. SEGK is always a power-of-two multiple of 16 in every |
|
409 | 449 | // OOR-poison under dyn-VGPR, SPEC S4): scalars <= s65 only (s60/s61 scratch; callers pass dst in |
410 | 450 | // [s62,s65]); the only vector temps are inside lds_get, which uses v11/v14 (INTERIOR to the launch |
411 | 451 | // 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 |
413 | 457 | .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 |
414 | 458 | lds_get \dst_a, AROW_DONE_OFF // prod_a: A rowblks resident (store-completion) |
415 | 459 | lds_get \dst_b, BFRAG_DONE_OFF // prod_b: B frags resident (store-completion) |
|
420 | 464 | s_sub_u32 \dst_b, \dst_b, s61 // occ_B = prod_b - cons_b in [0,FN] |
421 | 465 | .endm |
422 | 466 |
|
| 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 | + |
423 | 507 | // ---- Pool-T7 chunk-2 wedge localization (DIAG-only; DSWS2_CONV=0 emits nothing -> .text byte-identical). |
424 | 508 | // epoch_mark: lane-0 publishes this role's live epoch (s35) to a host-streamed occ slot so a hung dispatch |
425 | 509 | // shows how far each role advanced (stream field roles[C/A/B]). v14<=v15 (feeds/compute are lean-32 at the |
|
437 | 521 | .endif |
438 | 522 | .endm |
439 | 523 |
|
| 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 | + |
440 | 544 | // -------------------------------------------------------------------------------------------- |
441 | 545 | // Phase-B controller thresholds + sum-envelope budget (Task 4). EPOCH_SHIFT mirrors coop / |
442 | 546 | // occ_dispatch (epoch = segcnt >> EPOCH_SHIFT). BUDGET is the per-WG VGPR sum-envelope ceiling |
|
448 | 552 | .set EPOCH_SHIFT, 3 // decision clock: epoch = segcnt >> EPOCH_SHIFT (small = reactive) |
449 | 553 | .endif |
450 | 554 | .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 |
452 | 560 | .endif |
453 | 561 |
|
454 | 562 | .if DSWS2_CONV |
|
457 | 565 | .error "WAVES*VLEAN exceeds BUDGET — pool cannot stay all-lean (parking is out of scope)" |
458 | 566 | .endif |
459 | 567 | .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 |
460 | 574 |
|
461 | 575 | // try_gate: the lock-free single-winner conversion ticket (transcribed VERBATIM from occ_kernel_coop.s, |
462 | 576 | // which transcribes dsws_ctrl_model.cpp gate_try_win + epoch_of EXACTLY). E = segcnt>>EPOCH_SHIFT. |
@@ -750,12 +864,20 @@ occ_kernel: |
750 | 864 | lds_put (GATE_OFF+4), 0 |
751 | 865 | lds_put (GATE_OFF+8), 0 |
752 | 866 | 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 |
753 | 870 | lds_put VRESV_OFF, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) |
| 871 | +.endif |
754 | 872 | lds_put SEGCNT_OFF, 0 |
755 | 873 | .if DSWS2_CONV |
756 | 874 | // Phase-B: seed BOTH epoch-parity role-mix snapshots with the launch mix, zero the quiesce counter. |
757 | 875 | // 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 |
758 | 879 | lds_put QUIESCE_CNT_OFF, 0 |
| 880 | +.endif |
759 | 881 | lds_put (SNAP_BASE + 0), NCOMP // parity-0 snapshot = launch mix |
760 | 882 | lds_put (SNAP_BASE + 4), NAFEED |
761 | 883 | lds_put (SNAP_BASE + 8), NBFEED |
@@ -816,7 +938,11 @@ occ_kernel: |
816 | 938 | s_add_u32 s46, s46, 4 |
817 | 939 | lds_get s47, NBFEED_SLOT |
818 | 940 | 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 |
819 | 944 | lds_put QUIESCE_CNT_OFF, 0 // reset the per-super-tile bail counter |
| 945 | +.endif |
820 | 946 | .endif |
821 | 947 | lds_put EPOCH_OFF, s44 |
822 | 948 | BSTAGE // claimer helps stage B for this super-tile (s30,s31) |
@@ -894,7 +1020,11 @@ occ_kernel: |
894 | 1020 | .Lqc_ar_ok: |
895 | 1021 | // quiesce_done (s51) = QUIESCE_CNT >= WAVES-1 (each of the WAVES-1 non-claimer waves bumped once) |
896 | 1022 | s_mov_b32 s51, 1 |
| 1023 | +.if DSWS2_GQUIESCE |
| 1024 | + gq_read s44 // device-scoped global read (observes committed follower bumps) |
| 1025 | +.else |
897 | 1026 | lds_get s44, QUIESCE_CNT_OFF |
| 1027 | +.endif |
898 | 1028 | s_cmp_lt_u32 s44, (WAVES-1) |
899 | 1029 | s_cbranch_scc0 .Lqc_q_ok |
900 | 1030 | s_mov_b32 s51, 0 |
@@ -935,6 +1065,16 @@ occ_kernel: |
935 | 1065 | global_store_b32 v4, v14, s[0:1] offset:84 scope:SCOPE_DEV // occ[21] pub = AROW_NEXT |
936 | 1066 | v_mov_b32 v14, s62 |
937 | 1067 | 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 |
938 | 1078 | .Lqc_diag_skip: |
939 | 1079 | s_mov_b32 exec_lo, s16 |
940 | 1080 | .endif |
@@ -1034,8 +1174,13 @@ occ_kernel: |
1034 | 1174 | s_sub_i32 s66, s66, 1 |
1035 | 1175 | .endif |
1036 | 1176 | .Lbfeed_quiesce: |
| 1177 | +.if DSWS2_GQUIESCE |
| 1178 | + gq_bump // device-scoped global bump (visible to the claimer poll) |
| 1179 | +.else |
1037 | 1180 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2) |
| 1181 | +.endif |
1038 | 1182 | 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 |
1039 | 1184 | .endif |
1040 | 1185 | .if DSWS2_CONV |
1041 | 1186 | s_branch .Ldispatch |
@@ -1108,8 +1253,13 @@ occ_kernel: |
1108 | 1253 | s_sub_i32 s66, s66, 1 |
1109 | 1254 | .endif |
1110 | 1255 | .Lafeed_quiesce: |
| 1256 | +.if DSWS2_GQUIESCE |
| 1257 | + gq_bump // device-scoped global bump (visible to the claimer poll) |
| 1258 | +.else |
1111 | 1259 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2) |
| 1260 | +.endif |
1112 | 1261 | 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 |
1113 | 1263 | .endif |
1114 | 1264 | .if DSWS2_CONV |
1115 | 1265 | s_branch .Ldispatch |
@@ -1164,6 +1314,18 @@ occ_kernel: |
1164 | 1314 | .if DYNVGPR |
1165 | 1315 | s_wait_loadcnt 0x0 |
1166 | 1316 | 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 |
1167 | 1329 | .Lcompute_grow: |
1168 | 1330 | s_alloc_vgpr NFV // grow (SCC-retry guarded, brick-class rule) |
1169 | 1331 | s_cbranch_scc0 .Lcompute_grow |
@@ -1230,6 +1392,9 @@ occ_kernel: |
1230 | 1392 | .Lcompute_shrink: |
1231 | 1393 | s_alloc_vgpr 32 // shrink (SCC-retry guarded) |
1232 | 1394 | 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 |
1233 | 1398 | .endif |
1234 | 1399 | lds_inc ROWBLK_DONE_OFF // rowblk r computed + flushed (frees the A7 advance gate) |
1235 | 1400 | s_branch .Lcompute_claim |
@@ -1291,8 +1456,13 @@ occ_kernel: |
1291 | 1456 | .Lcmp_quiesce: |
1292 | 1457 | // ORDERING (SPEC 3.4 decision 2): the commit above fully completed (role-slot CAS + reservation + |
1293 | 1458 | // 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 |
1294 | 1462 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // exactly one bump per non-claimer wave / super-tile |
| 1463 | +.endif |
1295 | 1464 | 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 |
1296 | 1466 | .endif |
1297 | 1467 | .if DSWS2_CONV |
1298 | 1468 | s_branch .Ldispatch |
|
0 commit comments