Skip to content

perf(EPv2): size the gfx1250 combine pull tile against the LDS budget - #608

Open
zhangfei829 wants to merge 2 commits into
mainfrom
perf/ep-combine-tile-lds-budget
Open

perf(EPv2): size the gfx1250 combine pull tile against the LDS budget#608
zhangfei829 wants to merge 2 commits into
mainfrom
perf/ep-combine-tile-lds-budget

Conversation

@zhangfei829

Copy link
Copy Markdown
Collaborator

The TDM pull path took its tile from a fixed chunk count, so the LDS it needed was warpNum * srcMax * (hidden / MORI_COMB_TDM) * sizeof, where srcMax is how many distinct ranks a token landed on. At world 4 srcMax is 4 and that product is 230 KB, inside the 320 KB budget. At world 8 with topk 6 it is 6, the product is 345 KB, _cPullOk goes false, and combine drops to the per-element fallback that reads peer memory with the fabric latency exposed.

Measured on two gfx1250 nodes, 8 ranks over scale-up, against this commit's parent, ct=512, hidden 7168, comb 64x8, ITERS=50, median of 3 alternating rounds, CHECK on:

world 8 topk 6 combine 619.2 us / 53.0 GB/s -> 81.1 us / 403.5 GB/s
world 8 topk 8 combine 89.8 us / 423.7 GB/s -> 89.5 us / 422.0 GB/s
world 4 topk 6 combine 60.9 us / 395.4 GB/s -> 60.7 us / 396.4 GB/s
world 4 topk 8 combine 63.2 us / 414.9 GB/s -> 63.1 us / 416.4 GB/s

topk 8 at world 8 was never affected: srcMax == npes makes the QUAD gate true and that path sizes its tiles separately, so it never reads this budget. The broken band was topk 6, 7 and topk >= 9, with 5 and 8 fine on either side of it, which is why it went unnoticed.

Solving for the tile leaves every shape that already fit bit-identical -- world 4 still resolves to 2 chunks and the same 230144 bytes, hence the unchanged rows above -- and takes world size out of the inequality, because srcMax saturates at topk once npes >= topk. Bounding srcMax by min(npes, topk) instead of topk also stops over-reserving when topk > npes.

World 16/32/64 are covered by a host-side replica of the expression only (3 chunks, 312320 bytes at topk 8); no machine above 8 ranks was available.

Two adjacent changes were measured and left out, each 3/3 rounds slower at world 8 topk 6: letting the QUAD gate take topk < npes (+0.4 us, since a group then has more warps than the token has sources), and dropping the npes <= 4 guard on this path's compaction (+1.4 us, the ballot costs more than the reduce iterations it saves, which _CROW_DEAD already skipped).

The TDM pull path took its tile from a fixed chunk count, so the LDS it
needed was warpNum * srcMax * (hidden / MORI_COMB_TDM) * sizeof, where
srcMax is how many distinct ranks a token landed on. At world 4 srcMax is
4 and that product is 230 KB, inside the 320 KB budget. At world 8 with
topk 6 it is 6, the product is 345 KB, _cPullOk goes false, and combine
drops to the per-element fallback that reads peer memory with the fabric
latency exposed.

Measured on two gfx1250 nodes, 8 ranks over scale-up, against this commit's
parent, ct=512, hidden 7168, comb 64x8, ITERS=50, median of 3 alternating
rounds, CHECK on:

  world 8 topk 6   combine  619.2 us /  53.0 GB/s -> 81.1 us / 403.5 GB/s
  world 8 topk 8   combine   89.8 us / 423.7 GB/s -> 89.5 us / 422.0 GB/s
  world 4 topk 6   combine   60.9 us / 395.4 GB/s -> 60.7 us / 396.4 GB/s
  world 4 topk 8   combine   63.2 us / 414.9 GB/s -> 63.1 us / 416.4 GB/s

topk 8 at world 8 was never affected: srcMax == npes makes the QUAD gate
true and that path sizes its tiles separately, so it never reads this
budget. The broken band was topk 6, 7 and topk >= 9, with 5 and 8 fine on
either side of it, which is why it went unnoticed.

Solving for the tile leaves every shape that already fit bit-identical --
world 4 still resolves to 2 chunks and the same 230144 bytes, hence the
unchanged rows above -- and takes world size out of the inequality, because
srcMax saturates at topk once npes >= topk. Bounding srcMax by
min(npes, topk) instead of topk also stops over-reserving when topk > npes.

World 16/32/64 are covered by a host-side replica of the expression only
(3 chunks, 312320 bytes at topk 8); no machine above 8 ranks was available.

Two adjacent changes were measured and left out, each 3/3 rounds slower at
world 8 topk 6: letting the QUAD gate take topk < npes (+0.4 us, since a
group then has more warps than the token has sources), and dropping the
npes <= 4 guard on this path's compaction (+1.4 us, the ballot costs more
than the reduce iterations it saves, which _CROW_DEAD already skipped).
Two ternaries in the previous commit were wrapped by hand where the pinned
clang-format (mirrors-clang-format v20.1.8, --style=file) breaks them
differently. Whitespace only, and no token is added or removed, so the
measurements in the previous commit still describe this code.
@zhangfei829
zhangfei829 requested a review from jhchouuu August 27, 2026 07:42
@jhchouuu

Copy link
Copy Markdown
Collaborator

Verified the diagnosis independently and it reproduces exactly. Recomputing the LDS need under main's expression:

              srcMax  QUAD gate   PULL LDS
 npes=8 topk=5   5     declines     287360   fits
 npes=8 topk=6   6     declines     344832   over -> per-element fallback
 npes=8 topk=7   7     declines     402304   over -> fallback
 npes=8 topk=8   8     accepts     (459776)  QUAD takes it, PULL never runs
 npes=8 topk=9   9     declines     517248   over -> fallback

That is the odd band the description calls out, and the reason topk 8 escapes is exactly that _cPullSrcMax == npes lets the QUAD gate through before PULL is reached.

The three numbers in the description reproduce exactly too: world 4 topk 6 → 2 chunks / 230144 (unchanged), world 8 topk 6 → 3 chunks / 234240 (now fits), world 16 topk 8 → 3 chunks / 312320.

Two things I checked because the new expression introduces a division the old one did not have:

  • No division by zero. EpCfgIsValid already requires numExpertPerToken > 0, worldSize > 0 and warpPerBlock > 0, so warpNum * _cPullSrcMax >= 1.
  • Degradation stays graceful. If the pointer arrays alone exceed the budget, or _cPullTileCap lands below one row, _cPullFitChunks falls back to MORI_COMB_TDM, _cPullOk then goes false, and combine takes the per-element path. Working the worst case EpCfgIsValid allows (wave64: topk ≤ 63, npes ≤ 64, warpPerBlock ≤ 16; wave32: 31/32/32), _cPullTileCap still comes out at 128, so that branch is unreachable in practice, as the comment claims.

Three notes, none of them correctness:

1. The _cPullSrcMax change also moves npes > 4 && topk > npes onto QUAD. The QUAD gate tests _cPullSrcMax == _qSize (i.e. npes). At npes 8, topk 16: before, srcMax = topk = 16 != 8 so the gate declined, and PULL needed 919552 bytes so it declined too, leaving WarpAccumLF. After, srcMax = min(8, 16) = 8 == npes and QUAD accepts. That is very likely a second fix — QUAD is the fast path and _qSetup dedups to at most npes sources on its own, so it is correct there — but it is a path change, it is not in the table, and the description frames that line as purely an over-reservation fix. Worth a sentence, if only so someone bisecting a topk > npes shape later knows this commit moved it.

2. The world-4 rows are a no-regression control, not a test of the changed path. At world 4 with topk 6 or 8, _cPullSrcMax == npes, the QUAD gate passes, and PULL never runs. Labelling the rows would help: world 8 topk 6 is the fix, world 8 topk 8 is the QUAD control, world 4 is the collateral-damage control.

3. validAccumCount is only compacted when npes <= 4, so above that it stays topk and the runtime admission validAccumCount <= _cPullSrcMax reduces to topk <= npes. Self-consistent with the new bound, and exactly the band this fixes — but the coupling between the compaction guard and the srcMax bound is now load-bearing and undocumented. A line at the admission site would save the next reader the derivation. (You clearly already probed this, since dropping the npes <= 4 guard is one of the two rejected experiments.)

LGTM. The fix takes world size out of the inequality, leaves every shape that already fit bit-identical, and the description is unusually complete: measurements, why the band looked arbitrary, the two adjacent changes tried and rejected, and an honest note that above 8 ranks the coverage is arithmetic rather than measured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants