Skip to content

feat(tensilelite): factored 2-D StreamK cluster mode for gfx1250#9612

Draft
jaopaulolc wants to merge 19 commits into
developfrom
users/jolabega/streamk-factored-cluster-mode
Draft

feat(tensilelite): factored 2-D StreamK cluster mode for gfx1250#9612
jaopaulolc wants to merge 19 commits into
developfrom
users/jolabega/streamk-factored-cluster-mode

Conversation

@jaopaulolc

@jaopaulolc jaopaulolc commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

JIRA ID : AIHPBLAS-3929

Motivation

The gfx1250 StreamK spatial B-multicast (#9603) and K-split cluster reduction (#9611) are two ways to use one hardware workgroup cluster, and on their own PRs they are mutually exclusive. This PR unifies them into a single factored 2-D cluster mode so one kernel can do both. The cluster is described entirely by its shape ClusterDim = [Cs, Ck] (C = Cs * Ck): Cs spatial B-multicast peers × Ck K-split reduction peers.

Cumulative stacked PR (base develop): for context it also contains #9599, #9603 and #9611; the work new to this PR is the factored mode. The earlier three are reviewed via their own PRs.

Technical Details

Fully ClusterDim-driven, param-free. The StreamKClusterKSplit and StreamKClusterReduction user/benchmark parameters are removed. Cluster behavior is derived solely from the shape:

  • Cs = ClusterDim[0], Ck = ClusterDim[1], C = Cs*Ck; StreamKMulticast = (Cs > 1) and StreamKClusterReduction = (Ck > 1) are derived-only internal flags. The three canonical expressions:
  • Decode/mask: the within-cluster rank decodes "k fastest" (k = StreamKIdx & (Ck-1), s = (StreamKIdx & (C-1)) >> log2(Ck)) after the 2-D StreamKIdx fold. The factored B-multicast mask is maskB_base << k (Cs bits at stride Ck) with a self-only fallback for a partial / M-unaligned cluster. The reduction reuses the whole-cluster cluster-scope -3 split barrier unchanged, with the handshake fences escalated to SCOPE_SYS; owner/peer accounting falls out of skSplit = Ck.
  • Host (ContractionSolution.cpp/.hpp): getSKGridImpl unified to skGrid = roundUp(Ck*tiles, C); genuine 2-D launch grid [skGrid/Ck, Ck, 1] with Ck taken from clusterDim.y; ClusterDimCheck aligns on Cs (value[3]) and pins the N-tile divisor value[4]=1 for a 2-D cluster; ClusterReductionIterCheck balances Ck (value=[DepthU, Ck]). The streamKClusterKSplit SizeMapping field + its serialization are removed.
  • Validation: a single _validateStreamKClusterShape enforces Cs and Ck each a power of two with C = Cs*Ck ∈ [2,16]; the per-axis multicast/reduction validators compose (the historic mutual-exclusion xor is converted into orthogonal composable axes, not silently dropped).

Test Plan

  • Param-free CPU unit tests (derivation, validation matrix, predicate values, clean 2-D emission) across test_streamk_factored_cluster.py, test_streamk_cluster_reduction.py, test_streamk_multicast.py.
  • Verify shipped multicast / coop-load / PGR2 gfx1250 _codegen char tests stay byte-identical (run without --snapshot-update); regenerate reduction + factored goldens intentionally.
  • Enumerate migrated client configs (err==0, 0 DID_NOT_SATISFY_ASSERTS).
  • Real-HW / FFM validation of the new ClusterDim expressions (see below).

Test Result

  • 77 CPU unit tests pass across the three StreamK test files.
  • Multicast char snapshots byte-identical (multicast + coop-load + PGR2, 5 tests / 2 snapshots pass without --snapshot-update). Reduction + factored goldens regenerated intentionally: the delta is purely the basename solution-hash re-hash from the config expression change ([C,1]+params → [1,C]/[2,2]); every node keeps err: 0, node counts unchanged, no nodes added/removed.
  • Config enumeration clean: the 4 migrated gfx1250 client configs derive 120 solutions total (multicast [2,1]/[4,1]/[8,1] → Cs>1,Ck==1; reduction [1,2]/[1,4]/[1,8] → Cs==1,Ck>1), all err==0, correct derived flags, 0 rejections / 0 DID_NOT_SATISFY_ASSERTS.
  • SCOPE_SYS cluster-reduction handshake fence preserved (unchanged from feat(tensilelite): add StreamK WG-cluster partial reduction for gfx1250 #9611); the edited host code is clang-format-clean; stinkytofu (rocisa) is untouched.

Config before → after

Mode Before After
Pure multicast ClusterDim=[C,1], StreamKClusterReduction: [0] ClusterDim=[C,1] (derived: Cs>1, Ck==1)
Pure reduction ClusterDim=[C,1], StreamKClusterReduction: [1] ClusterDim=[1,C] (derived: Cs==1, Ck>1)
Factored ClusterDim=[4,1] + StreamKClusterKSplit: [2] ClusterDim=[2,2] (derived: Cs>1, Ck>1)

NEW config expressions requiring USER gfx1250 HW re-validation

The following are new expressions of already-validated mechanics but change the launch/derivation, so they need real-HW confirmation:

  • Pure reduction [1,C] (MXFP4 + MXFP8) — same reduction mechanics as feat(tensilelite): add StreamK WG-cluster partial reduction for gfx1250 #9611 but now expressed as [1,C] with the genuine 2-D launch (previously [C,1]).
  • Factored [Cs,Ck] beyond [2,2][2,2] is covered by CPU unit + codegen characterization; larger factorings (e.g. [2,4], [4,2], [2,8], [8,2]) exercising the true composed Cs×Ck path are not yet HW-validated (no client config sets a factored shape > [2,2]).

Submission Checklist

Risk level

Medium. [C,1] pure multicast is byte-identical to the shipped path; [1,C] pure reduction matches #9611; the genuinely-new composed Cs×Ck factoring (both axes > 1) is HW-validated only at [2,2] and otherwise covered by CPU unit + codegen characterization pending real-HW confirmation.

Update — folded-in symmetric-cluster-barrier reduction fix (HW validation pending)

Same root cause and fix as folded into #9611, applied here to the factored superset (which includes the reduction path). On gfx1250 the [1,C] reduction (and the reduction axis of a factored [Cs,Ck] cluster, Ck > 1) dropped exactly one peer's partial for C >= 4 at the grid tail because the reduction barrier handshake was asymmetric (peers arrived-and-exited; only the owner waited), so the owner could sum a peer slot before that cross-WGP partial was globally visible across the partitioned L2. Fence escalation alone did not fix it — the defect was structural.

Folded in (commits fb2e2c9c3adb038acc195):

  • Symmetric cluster barrier: every member (owner and every peer) both s_barrier_signal -3 (arrive) and s_barrier_wait -3 (wait) on the same cluster barrier (peer-side clusterReduceWait added after clusterReduceSignal); the interim C <= 2 restriction removed so the barrier carries peer→owner sync for all C. Global-flag handshake retained only for a cluster straddling the SK grid boundary; owner/peers gate on the identical predicate (deadlock invariant preserved).
  • 64-bit workspace slot offset via s_mul_hi_u32, scoped to the cluster-reduction/factored path (_streamKClusterReductionEnabled, derived Ck > 1) so the pure-multicast [C,1] and non-cluster StreamK paths keep the original 32-bit codegen byte-for-byte.

Local validation (CPU, this fix)

  • Unit pytest Tensile/Tests/unit (factored + reduction + multicast suites incl. test_streamk_factored_cluster.py): 134 passed.
  • Characterization (run without --snapshot-update): 23 passed / 8 snapshots. Multicast (+ coop-load + PGR2) char snapshots byte-identical; reduction + factored char err == 0.
  • Multicast byte-identity confirmed: emitted multicast asm byte-identical to the shipped baseline (STINKY_TOTAL_INST_BYTES unchanged across all 8 kernels; pre-vs-post diff collapses to the emitter's nondeterministic label/schedule floor).
  • Config zero-skip enumeration: sk_mxf4gemm_cluster_reduction.yaml, sk_mxf8gemm_cluster_reduction.yaml, sk_mxf4gemm_cluster_factored.yaml, sk_mxf8gemm_cluster_factored.yaml → 18/18 solutions each, 0 skipped, 0 DID_NOT_SATISFY_ASSERTS, err == 0.

Functional simulation (FFM) is historically green even when HW fails, so it is treated only as a regression check. Real gfx1250 hardware validation is still pending (owner: reviewer/user).

Update 2 — 64-bit workspace-offset fix now applies to ALL StreamK paths (incl. multicast)

Supersedes the "scoped to the cluster-reduction/factored path" note in the previous update. The 64-bit workspace slot-offset math in the SHARED computeWorkspaceSrd helper (the s_mul_hi_u32 high word folded into SrdWS+1) is now emitted universally — pure-multicast [C,1]/Ck==1, cluster reduction, factored [Cs,Ck], and any non-cluster StreamK that uses this helper — rather than gated behind _streamKClusterReductionEnabled (derived Ck > 1).

Motivation — could the overflow happen on multicast too? Yes.

The overflow depends only on the per-slot tile stride and the StreamK slot count, not on the cluster mode. The partials workspace is sized (host ContractionSolution::partialTileSize) as partialTileSize = MacroTile0*MacroTile1*bpe * skGrid, and computeWorkspaceSrd addresses slot sPartialIdx ∈ [0, skGrid) at byte offset MacroTile0*MacroTile1*bpe * sPartialIdx. The pure-multicast [C,1] path emits and reads this exact workspace (partial-tile write + owner fixup read), so for a large SK grid the 32-bit s_mul_i32 product wraps past 2³² and the peer-write / owner-read SRD aliases the wrong workspace slot — the same silent corruption as the reduction/factored paths. Concrete example: a 256×256 tile with fp32 partials is 256 KiB/slot, so skGrid ≳ 2³²/262144 = 16384 slots makes offBytes*sPartialIdx ≥ 4 GiB and overflows a 32-bit offset (cluster C = Cs*Ck multiplies the effective slot count, which is why big-C multicast on large problems is the realistic trigger).

Technical Details

Un-gated the one hunk (removed the if _streamKClusterReductionEnabled(...) / else split; always emit the 64-bit s_mul_i32 + s_mul_hi_u32 + s_add_u32/s_addc_u32 sequence). This intentionally drops the multicast byte-identity claimed in the previous update (one extra SGPR + one s_mul_hi_u32 per workspace-SRD setup); byte-identity is deliberately no longer a goal.

Test Result (CPU, single-process)

  • Unit + char (streamk / cluster / multicast / reduction / factored suites): 258 passed, 8 snapshots passed. The multicast {basename, err} char goldens are unchanged and still err == 0 (a {basename, err} digest does not move when only instructions are added), so no multicast snapshot was regenerated.
  • Confirmed the emitted multicast [C,1], reduction [1,C], and factored [Cs,Ck] kernels all now carry the 64-bit offset (s_mul_hi_u32 … // … high word … for 64-bit SRD folded into SrdWS+1).
  • Config zero-skip enumeration: factored streamk_factored_cluster.yaml 4/4, reduction streamk_cluster_reduction.yaml 8/8, multicast streamk_cluster_multicast.yaml 4/4 kernels → err == 0, 0 DID_NOT_SATISFY_ASSERTS, 64-bit SRD present in every kernel.

FFM/functional-sim is historically green regardless of HW correctness and is treated as a regression check only. Real gfx1250 hardware validation is still pending (owner: user).

Update 3 — folded in the ForceDPOnly 2-D [2,2] dual-operand multicast cluster (HW-validated on probe; PR re-validation pending)

Folded the HW-validated (gfx1250) ForceDPOnly 2-D dual-operand multicast capability from the probe branch (users/jolabega/streamk-forcedp-2d-probe, commit a99551ebab) onto this factored-cluster head. The probe was originally based on the older #9612 head (3463fc554f); it is applied here onto the current base so it composes with — and preserves — the symmetric cluster-barrier reduction fix and the universal 64-bit workspace slot-offset (Updates 1 & 2) and the factored path.

What it adds

A genuine 2-D HW cluster ClusterDim = [Cs, Ck] = [2, 2] on a StreamK==3 StreamKForceDPOnly (dense data-parallel, no K-split reduction) kernel where both operands are TDM-multicast:

  • Cs = ClusterDim[0] — X-peers on M-adjacent output tiles reuse B (exactly as the shipped 1-D [C,1] ForceDPOnly multicast);
  • Ck = ClusterDim[1] — Y-peers on N-adjacent output tiles reuse A (A is genuinely multicast — new).

Here Ck is an N-tiling / A-multicast axis, NOT a K-split reduction axis. This is orthogonal to the factored [Cs,Ck] K-split cluster and is disambiguated purely structurally (StreamKForceDPOnly + both ClusterDim axes > 1), so no new serialized/derived flag is needed.

  • Common/Utilities.py: new streamKForceDP2DMulticast structural detector.
  • Components/ClusterLoad.py: for ForceDP-2D use the dense A peer count (aPeers = Ck) so maskA multicasts A across the Ck/Y peers (0x5 for [2,2]); maskB unchanged (0x3). [C,1], factored, and reduction paths keep self-only A.
  • Components/StreamK.py: new 2-D DP tile-index fold StreamKIdx = batch*(nWG0*nWG1) + WorkGroup1*nWG0 + WorkGroup0 (added as a leading branch before the preserved factored "k fastest" fold) so skIndexToWG lands X-peers M-adjacent (B) and Y-peers N-adjacent (A); streamKMulticastMaskPredicate short-circuits (keep dense masks; no preLoop overwrite).
  • SolutionStructs/Solution.py: do not derive StreamKClusterReduction for ForceDP-2D (Cs>1 still derives StreamKMulticast).
  • Contractions.py ClusterDimCheck: keep ClusterDim[1] as the N-tile divisor for ForceDP-2D (nWG_y % Ck == 0) instead of pinning to 1.
  • src/ContractionSolution.cpp: full M x N tile launch grid [nWG0, nWG1, batch] and getSKGridImpl skGrid = tiles for ForceDP-2D; the factored/reduction 2-D launch [skGrid/Ck, Ck, 1] + roundup preserved in the else branch.

Tests folded in

  • Tests/unit/test_streamk_forcedp_2d_probe.py (detector + [2,2] mask math).
  • Tests/unit/characterization/_codegen/test_streamk_forcedp_2d_gfx1250_char.py + designed golden _designed/gfx1250/streamk_forcedp_2d.yaml.
  • HW-runnable client config renamed from the probe's _probe_forcedp_2d.yaml to Tests/common/streamk/gfx1250/core/sk_mxf4_force_dp_only_cluster_2d_multicast.yaml (consistent with the sibling sk_* naming; collectible by create_tests.py).

Local validation (CPU, single-process, this fold, on the current base)

  • Unit: 165 passed across the streamk / cluster / factored / multicast / reduction / ForceDPOnly suites incl. the new test_streamk_forcedp_2d_probe.py (10).
  • Characterization (run without --snapshot-update): new ForceDP-2D char passes; the multicast / multicast-PGR2 / coop-load / reduction / factored gfx1250 goldens are byte-identical (8 snapshots, no regeneration) — the ForceDP-2D path is properly gated so it does not perturb the shipped kernels; broader streamk char (r2/r3/r7) 15 passed. No snapshot regeneration was required.
  • Config zero-skip enumeration (gfx1250): reduction streamk_cluster_reduction.yaml 8/8, factored streamk_factored_cluster.yaml 4/4, multicast streamk_cluster_multicast.yaml 4/4, and ForceDP-2D streamk_forcedp_2d.yaml 1/1 → err == 0, 0 DID_NOT_SATISFY_ASSERTS.
  • ForceDP-2D [2,2] emission confirmed on the current base: maskA = 0x5 and maskB = 0x3 both s_lshl_b32-set and OR'd onto the tdmA/tdmB (and MXSA/MXSB) descriptors (A genuinely multicast); the 2-D DP StreamKIdx = batch*(nWG0*nWG1) + N*nWG0 + M fold is present; the cluster split-barrier is balanced (s_barrier_signal -3 ×4 / s_barrier_wait -3 ×5, matching the shipped 1-D ForceDPOnly multicast); the factored K-split decode/shift are absent.

FFM/functional-sim is historically green regardless of HW correctness and is treated as a regression check only. The ForceDPOnly 2-D [2,2] dual-operand multicast was HW-validated on the probe branch; re-validation on this PR base is still pending (owner: user).

@therock-pr-bot

therock-pr-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
🌿 Branch Name ✅ Pass
📝 PR Title/Description ✅ Pass
Forbidden Files ✅ Pass
🧪 Unit Test ✅ Pass
🚫 Draft PR 🔜 To Be Enabled
🚩 Feature Flag 🔜 To Be Enabled
📊 Code Coverage 🔜 To Be Enabled

🎉 All policy checks passed!

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

@therock-pr-bot

therock-pr-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.72512% with 94 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ipblaslt/tensilelite/Tensile/Components/StreamK.py 75.13% 46 Missing and 2 partials ⚠️
...lt/tensilelite/Tensile/SolutionStructs/Solution.py 82.11% 10 Missing and 7 partials ⚠️
.../hipblaslt/tensilelite/src/ContractionSolution.cpp 0.00% 16 Missing and 1 partial ⚠️
...ects/hipblaslt/tensilelite/Tensile/KernelWriter.py 60.00% 2 Missing and 2 partials ⚠️
...blaslt/tensilelite/Tensile/KernelWriterAssembly.py 69.23% 3 Missing and 1 partial ⚠️
...lelite/Tensile/Components/Subtile/SubtileGREmit.py 0.00% 3 Missing ⚠️
...aslt/tensilelite/Tensile/Components/ClusterLoad.py 98.86% 0 Missing and 1 partial ⚠️

❌ Your project check has failed because the head coverage (76.84%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #9612      +/-   ##
===========================================
+ Coverage    64.17%   65.76%   +1.60%     
===========================================
  Files         2763     2765       +2     
  Lines       450335   450958     +623     
  Branches     66254    66367     +113     
===========================================
+ Hits        288962   296560    +7598     
+ Misses      140483   133571    -6912     
+ Partials     20890    20827      -63     
Flag Coverage Δ *Carryforward flag
TensileLite 34.38% <ø> (+0.03%) ⬆️ Carriedforward from c730f1a
TensileLite-CPP 38.29% <0.00%> (+0.02%) ⬆️ Carriedforward from c730f1a
TensileLite-Unit 42.21% <77.73%> (+6.69%) ⬆️ Carriedforward from c730f1a
hipBLAS 90.62% <ø> (ø) Carriedforward from c730f1a
hipBLASLt 34.63% <ø> (ø)
hipCUB 82.68% <ø> (ø) Carriedforward from c730f1a
hipDNN 86.34% <ø> (ø) Carriedforward from c730f1a
hipFFT 48.90% <ø> (ø) Carriedforward from c730f1a
hipRAND 76.12% <ø> (ø) Carriedforward from c730f1a
hipSOLVER 69.18% <ø> (ø) Carriedforward from c730f1a
hipSPARSE 86.27% <ø> (ø) Carriedforward from c730f1a
rocBLAS 47.95% <ø> (ø) Carriedforward from c730f1a
rocFFT 48.51% <ø> (ø) Carriedforward from c730f1a
rocRAND 57.01% <ø> (ø) Carriedforward from c730f1a
rocSOLVER 76.84% <ø> (ø) Carriedforward from c730f1a
rocSPARSE 72.49% <ø> (ø) Carriedforward from c730f1a
rocThrust 91.36% <ø> (ø) Carriedforward from c730f1a

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...slt/tensilelite/Tensile/Common/GlobalParameters.py 92.49% <ø> (+5.14%) ⬆️
.../hipblaslt/tensilelite/Tensile/Common/Utilities.py 100.00% <ø> (+0.39%) ⬆️
...aslt/tensilelite/Tensile/Common/ValidParameters.py 90.20% <ø> (ø)
...rojects/hipblaslt/tensilelite/Tensile/Component.py 61.48% <100.00%> (+7.00%) ⬆️
...pblaslt/tensilelite/Tensile/Components/__init__.py 100.00% <ø> (ø)
...ects/hipblaslt/tensilelite/Tensile/Contractions.py 78.82% <100.00%> (+2.90%) ⬆️
...aslt/tensilelite/Tensile/SolutionStructs/Naming.py 82.58% <100.00%> (+0.41%) ⬆️
...aslt/tensilelite/Tensile/Components/ClusterLoad.py 98.86% <98.86%> (ø)
...lelite/Tensile/Components/Subtile/SubtileGREmit.py 57.38% <0.00%> (-0.10%) ⬇️
...ects/hipblaslt/tensilelite/Tensile/KernelWriter.py 55.21% <60.00%> (+31.45%) ⬆️
... and 4 more

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jaopaulolc jaopaulolc added the rocm:gemm algos Label to identify PRs of the GEMM Algorithms team. label Jul 21, 2026
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/streamk-factored-cluster-mode branch from 45dd3cd to ca47b80 Compare July 22, 2026 15:48
…ulticast

JIRA ID: AIHPBLAS-3929

The TDM multicast ("cluster load") mask machinery was duplicated inline across
KernelWriter, KernelWriterAssembly, and Subtile/SubtileGREmit, and Multicast was
implicitly coupled to ClusterDim != [1, 1]. That coupling made it impossible to
compose barrier-only clustering and cooperative-load clustering independently, or
to reuse the mask attach at a new load site.

- New ClusterLoad component (Components/ClusterLoad.py, registered via
  Component.py + Components/__init__.py). ClusterLoadTDM centralizes the mask
  value computation (computeMasks), the MulticastMask* SGPR declare/undeclare
  (declareSgprs / undeclareSgprs), the topology decision (combined vs split
  A/B masks), and the descriptor attach at each load site (applyToDescriptor).
  It is a behavior-preserving extraction: every method emits byte-identical
  assembly to the previous inline code, and computeMasks receives the exact
  SGPR operands the caller already holds so register indices are unchanged.
  Selection is capability-based (HasTDM + TDMInst == 3), like TensorDataMoverLoad.
- KernelWriter / KernelWriterAssembly / SubtileGREmit now route mask declare,
  undeclare, compute, and descriptor attach through ClusterLoadTDM.find(...).
- Tri-state Multicast parameter (ValidParameters + GlobalParameters default -1):
  -1 = auto (legacy: ClusterDim != [1,1] implies Multicast, minus the StreamK
  cluster paths), 0 = force off, 1 = force on. Default -1 reproduces the historic
  derivation exactly, so every existing YAML (which omits Multicast) derives
  byte-identically.
- Solution.py derives Multicast/ClusterBarrier from the tri-state, keying the
  legacy auto coupling and the ClusterBarrier gate on StreamK == 0.
- Common/Utilities.clusterEnabled() helper for the ClusterDim != [1,1] test.

- Unit: test_cluster_load_component.py (component find/declare/compute/attach),
  test_multicast_tristate.py (-1/0/1 derivation, legacy equivalence).

Low. Behavior-preserving refactor; the tri-state defaults to -1 (legacy auto),
so emitted assembly is unchanged for all existing configs.
…icast)

Add the gfx1250 StreamK DP cooperative B-multicast fast path
(StreamKMulticast), auto-derived from StreamK=3 + ClusterDim on top of the
ClusterLoad component + tri-state Multicast foundation.

- Collapse: StreamK==3 && ClusterDim!=[1,1] auto-enables StreamKMulticast
  (derived-only internal state; no YAML opt-in), forcing Multicast on and
  enabling the cluster-scope barrier handshake (ClusterBarrier).
- StreamK.py: multicast mask predicate (clusterMulticastValid), DP->SK
  boundary mask clear, and a prologue wave-0 s_barrier_signal -3 that pairs
  the InsertClusterBarrierPass first-load wait (balanced signal/wait counts).
- KernelWriter.py: postMainLoopBarrierCheckAndReset preserves cluster-scope
  -3 barriers.
- ClusterLoad.py: re-add the split-mask hooks (usesCombinedMask /
  maskSgprName) for StreamKMulticast.
- _validateStreamKMulticast: SK3 + [C,1] pow2 + gfx1250/HasTDM/TDMInst=3 +
  XCC=0; Multicast==1 force-on hard-guard.
- Host: streamKMulticast field threaded through SizeMapping / C++ structs;
  grid multiple-of-C guard + getSKGridImpl ceil(tiles/C)*C override; SK4/SK5
  + ClusterDim reject.

Mutual-exclusion with the barrier-only cluster reduction is deferred to the
following stacked PR.

JIRA ID : AIHPBLAS-3929
StreamK GEMM tiles whose partial results are reduced across workgroups (WGs)
synchronize that reduction with a cross-CU global-workspace flag spin-wait. On
gfx1250 the WGs that reduce one tile can instead be co-located into a hardware
workgroup cluster and synchronized with an intra-cluster split barrier, removing
the spin-wait from the fast path.

Adds an opt-in solution parameter StreamKClusterReduction. When enabled, a
StreamK tile's fixup peers are placed in a single 1-D workgroup cluster
(ClusterDim = [C, 1]); the HW WG-id remap (WorkGroup0 = cluster_x*C + wg_x) makes
each cluster own the contiguous StreamK index range [c*C, c*C+C), i.e. exactly
one tile's peers.

- Kernel (Components/StreamK.py): clusterReduceSignal / clusterReduceWait /
  clusterReduceIntraCheck helpers emit the wave-0-elected s_barrier_signal /
  s_barrier_wait handshake in the fixup epilogue. A uniform intra-cluster
  predicate selects the barrier fast path or the retained global-flag path so a
  cluster never mixes the two. The WG-id reread is skipped under clustering so
  StreamKIdx stays the global index.
- Host (src/ContractionSolution.cpp, ContractionSolution.hpp + serialization):
  getSKGridImpl reduction override rounds the SK grid to C*tiles (fixed even
  split, SKItersPerWG = itersPerTile/C) and keeps the launch grid a multiple of
  C; the two-tile cluster-reduction kernarg block is emitted only when
  sk.grid == C*tiles; the field is threaded through SizeMapping / Contractions.
- Predicate (ContractionProblemPredicates.hpp + serialization + Contractions.py):
  ClusterReductionIterCheck rejects at selection time any problem where
  itersPerTile is not a multiple of C (the split barrier would otherwise
  over-signal); the global-flag reduction is retained as the runtime fallback.
- Solution.py: _validateStreamKClusterReduction gates the opt-in (SK3, non-atomic,
  non-DP-only, [C,1] power-of-two cluster, gfx1250 with HasClusterBarrier,
  TDMInst != 0). Multicast and the mainloop ClusterBarrier are kept off for a
  StreamK reduction cluster: its peers iterate different K-splits and never march
  the mainloop in lockstep.

Multicast and cluster reduction are mutually exclusive: the StreamKMulticast
auto-derivation is gated on NOT StreamKClusterReduction, and both validators
reject the simultaneous combination with a neutral message. This stacks on the
reusable ClusterLoad component + tri-state Multicast and the StreamK cooperative
multicast; the mutual exclusion is intended to be unified by a later factored
combined-cluster mode.

Validation: CPU asm-string unit tests (test_streamk_cluster_reduction.py),
snapshot codegen characterization (test_streamk_cluster_reduction_gfx1250_char),
the ClusterReductionIterCheck predicate characterization and C++ gtests, plus
opt-in-off client GEMM YAMLs. The feature is off by default and strictly opt-in.
…ticast x K-split reduction) for gfx1250

Factor the 1-D gfx1250 StreamK HW cluster ClusterDim=[C,1] into two ORTHOGONAL
axes, C = Cs * Ck, selected by a new StreamKClusterKSplit (Ck) parameter:

  * Cs = C // Ck spatial B-multicast peers -- process M-adjacent DISTINCT tiles
    sharing the same B N-block over one K-slice (multicast along the s axis);
  * Ck              K-split reduction peers -- split one tile's K range and
    reduce partials through the whole-cluster split barrier (reduction along k).

A single cluster now performs BOTH the spatial multicast and the K-split partial
reduction in one kernel. The within-cluster rank decodes "k fastest"
(k = StreamKIdx & (Ck-1), s = (StreamKIdx & (C-1)) >> log2(Ck)); the factored
B-multicast mask is maskB_base << k (Cs bits at stride Ck), with the self-only
fallback for a partial / M-unaligned cluster. The reduction reuses the existing
cluster-scope -3 barrier unchanged: it releases at the full C-membership arrival
count, which is benign over-synchronization across s-rows and keeps whole-kernel
signal/wait counts balanced (prologue arrive + mainloop lockstep + epilogue
reduce). Reduction owner/peer accounting falls out of skSplit = Ck.

Host: getSKGridImpl is unified to skGrid = roundUp(Ck*tiles, C); the two-tile
cluster-reduction kernarg block uses skSplit = Ck; ClusterDimCheck aligns on Cs
and ClusterReductionIterCheck balances Ck. New sizeMapping.streamKClusterKSplit
threads Ck to the host.

Mutual-exclusion relaxation: the multicast and cluster-reduction fast paths are
no longer mutually exclusive -- they are composable axes of one Cs x Ck
factoring. The collapse derives StreamKMulticast (Cs>1) and
StreamKClusterReduction (Ck>1) from (ClusterDim[0], StreamKClusterKSplit), and
the validators accept the combination; a new factoring validator enforces Ck |
C with Ck and Cs powers of two.

Degenerate collapse: Ck==1 (Cs==C) reproduces the shipped pure-multicast path
byte-for-byte; Ck==C (Cs==1) reproduces the shipped pure-reduction path
byte-for-byte (the legacy StreamKClusterReduction=1 opt-in is its Ck==C
degenerate).

Validation is CPU/snapshot only; real-HW / FFM validation of the combined axes
is deliberately deferred and gated behind multicast cooperative-load stability
at PrefetchGlobalRead>1. Adds factored CPU unit tests (decode, validation
matrix, predicate values, and degenerate byte-identity vs the pure paths) and a
gfx1250 codegen characterization test + designed config; regenerates only the
ValidParameters / SolutionClass roster goldens for the new parameter.
… the zero-iteration skip path

The StreamKMulticast prologue emits a wave-0-elected cluster-scope arrive
before the first cooperative-multicast load. Its only matching cluster-scope
wait was the first-load wait, which sits after the last-iteration guard's long
branch to PrefetchGlobalLastIterEnd. On the zero-full-iteration path (reachable
when K is not a whole multiple of DepthU) that branch skips the first-load wait,
leaving the prologue arrive unmatched and the cluster-scope barrier unbalanced
on that edge.

Add a matching cluster wait on the skip edge (guarded by StreamKMulticast) so
the arrive is consumed on every control-flow path, preserving whole-cluster
barrier symmetry. The wait branches over on scc0 (>=1 full iteration, where the
first-load wait pairs the arrive) and executes an all-waves cluster wait on scc1
(zero iterations); it leaves scc intact so the subsequent long branch is
unaffected.

Update the two gfx1250 cluster char-test balance checks (multicast and coop
load): the skip-edge wait and the first-load wait are mutually exclusive at
runtime but both emitted statically, so the static cluster-wait count is now
exactly one greater than the signal count (one prologue arrive consumed by
exactly one of the two waits).
…multicast

- Option A prologue prefetch handshake bracketed with a cluster-scope
  barrier round (StreamK.py / KernelWriter.py StreamKMulticast plumb).
- Remove the PGR<=1 enumeration guard so PGR2 multicast solutions build.
- InsertClusterBarrierPass: drain the cooperative broadcast tensor group
  with both a pre-round drain and a producer-side drain (after the
  cooperative tensor_load group), gated on StreamKMulticast && PGR>=2.
- PrefetchGlobalRead: [1,2] in the mxf4/mxf8 cluster-multicast configs
  plus a new PGR2 characterization test (+ snapshot + designed yaml).

GPU-validated on gfx1250.

JIRA ID : N/A
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/streamk-factored-cluster-mode branch from ca47b80 to 61b867b Compare July 22, 2026 17:28
…cluster mode

Condense verbose prose that duplicates the design docs (ClusterLoad docstrings,
StreamK cluster helpers, the StreamKMulticast/Reduction/KSplit validators) to
concise summaries with a "See docs/design/..." pointer, repoint stale citations
of the untracked local plan doc to docs/design/streamk-wg-clusters.md, and strip
explanatory comments from the client/characterization test YAMLs (copyright/SPDX
retained; parsed-YAML unchanged). No codegen change: char snapshots and targeted
unit tests (incl degenerate byte-identity) unchanged.
@jaopaulolc
jaopaulolc marked this pull request as ready for review July 22, 2026 18:15
@jaopaulolc
jaopaulolc requested review from a team as code owners July 22, 2026 18:15
@jaopaulolc jaopaulolc changed the title feat(tensilelite): factored 2-D StreamK cluster mode (spatial multicast x K-split reduction) for gfx1250 [DRAFT] feat(tensilelite): factored 2-D StreamK cluster mode (spatial multicast x K-split reduction) for gfx1250 Jul 22, 2026
…orce-DP-only client tests

Extend PrefetchGlobalRead to [1, 2] in the MX-FP4/FP8 cluster-reduction client
configs and sk_mxf4_force_dp_only_cluster_multicast.yaml so both PGR1 and PGR2
are exercised on HW. No validator guard blocks PGR2; solutions enumerate err==0.
…for zero-skip coverage

The mxf4/mxf8 cluster-reduction client YAMLs paired big tiles (MT64x64, MT128x128)
and high cluster factors (C up to 8) with low-M / low-K sizes, so 32 of 96
solution x size runs were rejected at selection time by ClusterDimCheck
(ceil(M/MT0) % C != 0) or ClusterReductionIterCheck (ceil(K/DepthU) % C != 0),
surfacing as DID_NOT_SATISFY_ASSERTS with no real coverage.

Redesign both configs so every (tile x C x size) combo runs:
- Drop MT128x128 (MT128 x C8 needs M a multiple of 1024 and is cluster-resource
  heavy); keep MT32x16 / MT32x32 / MT64x64.
- Sizes with M and N multiples of 512 and K a multiple of 2048 so
  itersPerTile = ceil(K/256) in {8,16} is divisible by C in {2,4,8} and
  ceil(M/MT0) is divisible by C for every kept tile:
  Exact [512,512,1,2048], [1024,512,1,2048], [512,512,1,4096], [512,1024,2,2048].
- Retain C in {2,4,8}, PrefetchGlobalRead [1,2], a multi-iter K (4096) and a
  batched size ([512,1024,2,2048]).

Result: 0 DID_NOT_SATISFY_ASSERTS (72 runs each, was 32/96), verified by
enumerating solutions via the char config harness and evaluating both host
predicates per solution x size, and end-to-end on the FFM client. Kept
byte-identical to the pure cluster-reduction branch.
…r validator reject branches

Add unit coverage for previously-untested branches (codecov gaps): ClusterLoad
computeMasks sparse-metadata + undeclareSgprs metadata; direct reject branches of
_validateStreamKMulticast, _validateStreamKClusterReduction (factored: mutual
exclusion relaxed), and _validateStreamKClusterKSplit (Ck power-of-two/divides-C,
Cs power-of-two) unreachable through config derivation.
… to SCOPE_SYS

The gfx1250 cluster-reduction split-barrier handshake published peer partials
with a SCOPE_DEV global_wb and read them after a SCOPE_DEV global_inv. The split
barrier orders execution but carries no memory semantics, so on gfx1250's
partitioned L2 a SCOPE_DEV writeback was not guaranteed visible to a peer reading
on a different partition -> a rare (~0.1%, one boundary cluster) miscompute at the
largest grid (512,512,1,2048) on PGR1 (PGR2 masked it via prefetch timing).

Escalate only the cluster-reduction release (peer partial writeback) and the
owner's post-barrier acquire invalidate to SCOPE_SYS via a new optional `scope`
arg on releaseFence/acquireFence; the global-flag fallback and all non-cluster
StreamK keep SCOPE_DEV. Factored mode (Ck>1) inherits it through the shared
reduction handshake; the Ck==C degenerate stays byte-identical to pure reduction.
InsertClusterBarrierPass.cpp (blob cf772a0) untouched.

Confirmed on real gfx1250: MXFP4 and MXFP8 cluster reduction pass. Reduction +
factored char and unit tests green (51 passed, incl. degenerate byte-identity).
@jaopaulolc jaopaulolc changed the title feat(tensilelite): factored 2-D StreamK cluster mode (spatial multicast x K-split reduction) for gfx1250 feat(tensilelite): factored 2-D StreamK cluster mode for gfx1250 Jul 22, 2026
@therock-pr-bot

Copy link
Copy Markdown

Pre-commit check failed

pre-commit failed

Please run locally:

  • python -m pip install pre-commit
  • pre-commit install
  • pre-commit run --all-files --show-diff-on-failure

This repo uses .pre-commit-config.yaml.

…rain

Reflow the producerDrainAnchors.push_back ternary to the repo clang-format style
(Google/IndentWidth 4/ColumnLimit 100). Whitespace only; no behavior change.
@jaopaulolc
jaopaulolc marked this pull request as draft July 22, 2026 21:12
…terDim-driven)

Drop the StreamKClusterKSplit and StreamKClusterReduction user/benchmark
parameters: the StreamK workgroup cluster is now described entirely by its
shape ClusterDim = [Cs, Ck] (C = Cs*Ck). StreamKMulticast (Cs = ClusterDim[0] > 1)
and StreamKClusterReduction (Ck = ClusterDim[1] > 1) are derived-only internal
flags. The three canonical expressions:
  * [C,1]   -> pure multicast   (byte-identical to the shipped 1-D path)
  * [1,C]   -> pure reduction    (2-D launch, matches #9611)
  * [Cs,Ck] -> factored (both>1) (genuine 2-D cluster: B-multicast x K-split)

- streamKClusterFactors returns (Cs, Ck, C, is2D) purely from ClusterDim.
- Solution: single _validateStreamKClusterShape (Cs, Ck each pow2, C in [2,16]);
  multicast/reduction validators compose (no longer mutually exclusive).
- Contractions: ClusterDimCheck value[3]=Cs, N-divisor value[4] pinned to 1 for
  genuine 2-D clusters; ClusterReductionIterCheck value=[DepthU, Ck].
- Host (ContractionSolution.cpp/.hpp): 2-D grid [skGrid/Ck, Ck, 1], Ck from
  clusterDim.y; remove streamKClusterKSplit field + serialization.
- ValidParameters/GlobalParameters: remove both user params (derived-only note).
- Migrate factored designed YAML [4,1]+KSplit=2 -> [2,2]; reduction designed +
  client YAMLs [C,1] -> [1,C]; multicast client YAMLs drop StreamKClusterReduction.
- Rewrite unit tests param-free; regenerate reduction/factored char goldens
  (basename rehash only, err==0); multicast char goldens byte-identical.

Preserves the SCOPE_SYS cluster-reduction handshake fence.
…) for HW validation

Client GEMM configs exercising the genuinely-composed factored path
(ClusterDim=[Cs,Ck] with Cs>1 AND Ck>1 => both StreamKMulticast and
StreamKClusterReduction derived), covering [2,2]/[2,4]/[4,2] x PGR[1,2] with
sizes chosen for zero DID_NOT_SATISFY_ASSERTS (ceil(M/MT0)%Cs==0, ceil(K/DepthU)%Ck==0).
Enumerates 18 solutions each, all both-axes, 0 skips, err==0. For real-gfx1250 validation.
…1250

Pure StreamK cluster reduction (ClusterDim [1,C]) produced a flaky,
cluster-aligned column-stripe numerical error on gfx1250 for C>=4 (~8-9%
of fresh runs on 256x512x4096 F8 MT32x16 PGR1 SK3). Capturing wrong
elements showed device ~= (C-1)/C * reference (0.751 measured for C=4):
exactly one peer's partial contribution was dropped -- a peer-contribution
visibility bug, not wrong-slot addressing. Both orientations (256x512 vs
512x256) select identical SK scalars (tiles=256, itersPerTile=16, C=4,
grid=1024, exact even split), so the orientation trigger is purely the
spatial WG->WGP mapping, i.e. whether a cluster straddles WGP boundaries.

Root cause: the intra-cluster split-barrier fast path lets the owner clear
a single s_barrier_signal/wait -3 and then read peer partials. That -3
cluster barrier only orders the owner against HW-cluster-scoped peers; a
C>=4 reduction cluster spans multiple WGPs, so the owner proceeds before a
cross-WGP peer's partial store is globally visible and sums a stale
(missing) partial. A SCOPE_SYS fence around the barrier does not help
because the barrier itself does not wait for the peer store.

Fix: restrict the split-barrier fast path to C<=2 (single WGP, validated
race-free); for C>=4 fall back to the proven per-peer global-flag
handshake, where each peer publishes its partial with a release and raises
its own completion flag and the owner spins on each peer flag with an
acquire before reading that slot -- a genuine cross-peer visibility gate.
The owner's global-flag acquire is escalated to SCOPE_SYS for
cluster-reduction kernels to pair with the peer's existing SCOPE_SYS
release across gfx1250's partitioned L2. Both owner wait and peer signal
gate on the identical compile-time predicate, so the cluster never mixes
barrier signalers with flag setters (deadlock invariant preserved).

Also fix a latent 64-bit workspace slot-offset overflow in
computeWorkspaceSrd: MacroTile0*MacroTile1*bpe * PartialIdx can exceed
2^32 for large SK grids, so the 32-bit product wrapped and aliased a wrong
slot; carry the SMulHIU32 high word into SrdWS+1 instead of only the
lo-add carry.

Validation (gfx1250, GPU3, fresh process per rep):
  before: 256x512 C4 83/1000 fail, 512x512 C4 31/1000, 512x512 C8 16/500
  after (0 fail, 0 hang):
    256x512   C4  520/520
    512x512   C4  520/520
    512x512   C8  520/520
    1024x1024 C4  420/420
    1024x1024 C8  420/420
    hang-check 512x1024 C8 120/120, 1024x1024 C8 120/120 (0 hang)
  suites: sk_mxf8gemm_cluster_reduction 72/72 PASS,
          sk_mxf4gemm_cluster_reduction 72/72 PASS,
          sk_mxf4gemm_cluster_multicast 296/296 PASS (baseline unaffected).

The secondary C=8 oversubscription spin-wait hang did not reproduce at the
tested grids and no new failures were introduced; it remains a pre-existing
persistent-kernel limitation (grid must fit resident capacity) and is left
documented rather than clamped under deadline.

JIRA ID : N/A
…rrier (no flags)

The gfx1250 StreamK [1,C] cluster reduction dropped exactly one peer's
partial for C >= 4 at the grid tail (device ~= (C-1)/C * reference, a
cluster-aligned column stripe confined to the last ~16 tiles / WG
960-1023). Root cause: the reduction cluster barrier handshake was
ASYMMETRIC -- each peer only issued s_barrier_signal -3 (arrive) and then
branched away / exited, while only the owner issued s_barrier_wait -3.
A peer-signal-only arrive is not a genuine cluster synchronization point
on gfx1250's partitioned L2: the owner could observe the barrier
satisfied and sum a peer slot before that cross-WGP peer's partial was
globally visible. Escalating the release/acquire fences to SCOPE_SYS did
not fix it because the defect was structural, not a missing drain.

Fix: mirror the HW-validated multicast handshake and make the reduction
barrier SYMMETRIC -- every cluster member (owner AND every peer) both
arrives (s_barrier_signal -3) AND waits (s_barrier_wait -3) on the same
cluster barrier, so no peer proceeds past the rendezvous until the whole
cluster has arrived and the owner's paired SCOPE_SYS acquire has a real
cross-cluster order to observe. Concretely: add the peer-side
clusterReduceWait after clusterReduceSignal in partialsWriteProcedure,
and remove the C <= 2 restriction in _streamKClusterBarrierFastEnabled so
the barrier path (not a per-peer global-flag fallback) carries
peer->owner synchronization for ALL C. The 64-bit slot-offset fix
(SMulHIU32 in computeWorkspaceSrd) and the SCOPE_SYS release/acquire on
the reduction path are retained. The per-tile global-flag handshake
remains ONLY for a cluster that straddles the SK grid boundary
(clusterReduceIntraCheck == SCC0); both owner and peers gate on that
identical uniform predicate, so a cluster never mixes barrier
participants with flag setters (deadlock invariant preserved).

Validation (gfx1250, physical GPU3), barrier-only kernels confirmed by
disassembly (s_barrier_signal/wait -3 present on owner and peer, peer
skips the flag store on the intra path, for both C=4 |3 and C=8 |7):
  256x512x4096 [1,4]:   1000/1000 pass, 0 fail, 0 hang
  512x512x4096 [1,4]:   1000/1000 pass, 0 fail, 0 hang
  512x512x4096 [1,8]:    500/500  pass, 0 fail, 0 hang
  1024x1024x4096 [1,4]:  500/500  pass, 0 fail, 0 hang
  1024x1024x4096 [1,8]:  500/500  pass, 0 fail, 0 hang
  hang check 512x1024 [1,8], 1024x1024 [1,8] (timeout 30): 0 hang
Full suites (fresh codegen from this source): sk_mxf8gemm_cluster_reduction
72/72 PASS, sk_mxf4gemm_cluster_reduction 72/72 PASS,
sk_mxf4gemm_cluster_multicast 296/296 PASS (multicast unregressed).
CPU unit tests: test_streamk_cluster_reduction 28/28, char emit test pass.

JIRA ID : N/A
…/factored

The ported symmetric-cluster-barrier fix folded a 64-bit workspace slot
offset (SMulHIU32 high word) into the SHARED computeWorkspaceSrd helper,
which the pure-multicast ([C,1]) and non-cluster StreamK paths also emit.
That perturbed the multicast kernel codegen (extra s_mul_hi_u32 + an SGPR
checkout that cascaded into register/schedule changes), so the multicast
asm was no longer byte-identical to the shipped baseline.

Gate the 64-bit high-word path on _streamKClusterReductionEnabled (the
same predicate every other hunk of the fix already uses): the
cluster-reduction / factored (derived Ck>1) path -- which drives the large
SK grids the 64-bit offset guards against, and is the only path this fix
targets -- keeps the HW-validated 64-bit codegen; the multicast and
non-cluster paths take the else branch and emit the original 32-bit offset
byte-for-byte. Verified: multicast STINKY_TOTAL_INST_BYTES unchanged and
the pre-vs-post multicast diff collapses to the emitter's nondeterministic
label/schedule floor (identical to a pre-vs-pre baseline), i.e. the
multicast instruction stream is byte-identical to the shipped path.

JIRA ID : N/A
Un-gate the 64-bit workspace slot-offset computation in the shared
computeWorkspaceSrd helper so the high-word (SMulHIU32) fold into SrdWS+1
is emitted for every StreamK path that addresses the partial-sum
workspace -- pure-multicast ([C,1]/Ck==1), cluster reduction, factored
([Cs,Ck]), and non-cluster StreamK -- not only the cluster-reduction /
factored (derived Ck>1) paths.

The overflow this guards against depends only on the per-slot tile
stride (MacroTile0*MacroTile1*bpe) and the StreamK slot count: the
partials workspace is partialTileSize == tileSize * skGrid regardless of
cluster mode, so the multicast path can silently wrap a 32-bit
slot*stride product on large problems exactly like the factored path,
aliasing the wrong workspace slot. This intentionally changes the
multicast codegen (byte-identity with the prior 32-bit path is
deliberately dropped); the {basename, err} char goldens are unchanged
and still emit err==0. Real-HW validation pending (owner: user).

JIRA ID : N/A
…cluster (gfx1250)

Fold the HW-validated (gfx1250) ForceDPOnly 2-D dual-operand multicast
cluster feature (probe branch users/jolabega/streamk-forcedp-2d-probe,
commit a99551e) onto the current #9612 factored-cluster head. The probe
was originally based on the old #9612 head (3463fc5); this applies its
implementation onto the newer base that already carries the reduction
symmetric-cluster-barrier fix, the UNIVERSAL 64-bit workspace slot-offset,
and the factored 2-D cluster path -- all of which are preserved.

Give StreamK ForceDPOnly (dense data-parallel, no K-split) a genuine 2-D HW
cluster ClusterDim=[Cs,Ck]=[2,2] where BOTH operands are TDM-multicast: Cs (X)
peers on M-adjacent tiles reuse B (as the shipped 1-D [C,1] multicast) and Ck (Y)
peers on N-ADJACENT tiles reuse A. The dense ClusterLoad 2-D masks are reused
verbatim for both operands; the only new codegen is the ForceDPOnly DP tile-index
fold that makes Y-peers N-adjacent.

- Utilities: add streamKForceDP2DMulticast structural detector (ForceDPOnly +
  ClusterDim[0]>1 + ClusterDim[1]>1); the K-split reading of Ck>1 is rejected for
  ForceDPOnly so the shape is unambiguous and needs no extra flag.
- ClusterLoad.computeMasks: for ForceDP-2D use the DENSE A peer count (aPeers=Ck)
  so maskA multicasts A across the Ck/Y peers (0x5 for [2,2]); maskB unchanged
  (0x3). 1-D [C,1], factored, and reduction paths keep self-only A.
- StreamK.preLoop: new 2-D DP fold StreamKIdx = batch*(nWG0*nWG1) + WorkGroup1*nWG0
  + WorkGroup0 (added as a leading branch before the factored 'k fastest' fold,
  which is preserved) so skIndexToWG lands X-peers M-adjacent (B) and Y-peers
  N-adjacent (A). streamKMulticastMaskPredicate short-circuits (keep dense masks).
- Solution: do NOT derive StreamKClusterReduction for ForceDP-2D (Ck is an N-tiling
  axis, not a reduction); Cs>1 still derives StreamKMulticast.
- Contractions.ClusterDimCheck: keep ClusterDim[1] as the N-tile divisor for
  ForceDP-2D (nWG_y % Ck == 0) instead of pinning to 1.
- ContractionSolution: launch the full M x N tile grid [nWG0, nWG1, batch] and set
  skGrid = tiles for ForceDP-2D (not the K-split roundup); factored/reduction
  launch + getSKGridImpl roundup preserved in the else branch.

Tests folded in: focused unit test (detector + [2,2] mask math), a gfx1250
characterization, and the HW-runnable client config renamed from the probe's
_probe_forcedp_2d.yaml to sk_mxf4_force_dp_only_cluster_2d_multicast.yaml to match
the sibling sk_* naming under Tests/common/streamk/gfx1250/core/. Feature is
HW-validated on the probe branch; re-validation on the PR base is pending (owner:
user). CPU-only validation here.

Co-authored-by: Joao P. L. de Carvalho <joao.carvalho@amd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant