Conversation
|
Benchmark kernel stats: After: |
There was a problem hiding this comment.
Pull request overview
This PR applies low-level SDMA fast-path micro-optimizations in include/mori/cco/cco.hpp to improve instruction selection and memory coalescing for queue packet publishing on AMDGPU targets.
Changes:
- Introduces an internal
impl::global()helper to force global address space accesses for SDMA queue-related loads/stores and HIP atomics. - Reworks
ccoSdmaUnitwrites to use 16-byte vector stores (Uint4) for better coalescing when writing COPY/ATOMIC packets into the ring.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
jhchouuu
left a comment
There was a problem hiding this comment.
Tested on both arches — gfx950 (MI355X) and gfx1250 — with the baseline at the merge-base 624002c8 and identical build flags for both trees.
Correctness. The seven CCO SDMA tests (put, get, put_mt, block, edge, signal, warp_issue) in fork mode pass on both trees: 8/8 ranks on gfx950, 4/4 on gfx1250.
ISA. Both claims hold, and identically on the two arches (test_sdma_put):
| flat_load | flat_store | flat_atomic | global_load | global_store | |
|---|---|---|---|---|---|
| base | 100 | 84 | 12 | 4 | 0 |
| PR | 6 | 0 | 12 | 102 | 72 |
Packet stores go from three per unit to two. The baseline emits dwordx3@0 + dwordx4@offset:12 + dword@offset:28 — note offset 12 is not 16B-aligned — where this PR emits dwordx4@0 + dwordx4@offset:16. Same shape on gfx1250 with b96/b32/b128.
Perf. 8 B put, thread scope, quiet completion, 2 ranks, runs interleaved to cancel drift:
- gfx950: median 6.076 → 5.970 us (-1.7%), and the PR is faster in 8 of 9 paired runs.
- gfx1250: no measurable difference. Base-first ordering says "PR 0.5% slower", PR-first ordering says "PR 1.5% faster" — the sign follows run order, so an ordering artifact dominates. Pooling both blocks, so each binary ran first 9 times and second 9 times: 8.207 → 8.147 us, well inside the 7.98–8.60 spread.
Bandwidth is unchanged on both: 57.0 GB/s at 8 MB on gfx950, 980 GB/s at 8 MB on gfx1250.
The obvious gfx1250 worry — whether moving packet stores from flat to global could break "packet dwords land before the doorbell" — does not apply. ccoSdmaPublishStores() is a release fence at agent scope there, and s_waitcnt(0) on gfx9; neither relies on flat bumping an extra counter.
Comments inline, nothing blocking.
| // Implicit cast to global memory space. | ||
| template <typename T, typename T2 = typename std::remove_volatile<T>::type> | ||
| __device__ __host__ inline static T2* global(T* ptr) { | ||
| return (T2*)(T2 CCO_GLOBAL_SPACE*)reinterpret_cast<uintptr_t>(ptr); |
There was a problem hiding this comment.
This reinterpret_cast<uintptr_t> looks like it could be simplified to a plain address-space cast. It cannot, and I would suggest a comment saying so — it is the most deletable-looking line in the PR, and deleting it silently undoes the entire change while every test still passes.
I tried it. Replacing the body with
return (T2*)(T2 CCO_GLOBAL_SPACE*)ptr;and regenerating the device assembly for test_sdma_put (gfx950, same flags) puts everything back on flat:
| as written | direct cast | |
|---|---|---|
| flat_load_dwordx2 / x4 | 6 / 0 | 68 / 32 |
| flat_store_dwordx2 / x4 | 0 / 0 | 48 / 24 |
| global_load + global_store | 174 | 4 |
addrspacecast (addrspacecast p to as(1)) to as(0) is an inverse pair that gets folded away immediately, so the address space never survives to InferAddressSpaces. inttoptr cannot be folded, which is precisely why the round trip works. sgpr_count is 44 either way, so nothing is being paid for it.
There was a problem hiding this comment.
yes it is tricky.. atomic_load/store builtin functions do not accept pointers with non-default memory space, that's why this workaround was necessary
| #define CCO_GLOBAL_SPACE __attribute__((address_space(1))) | ||
| // Implicit cast to global memory space. | ||
| template <typename T, typename T2 = typename std::remove_volatile<T>::type> | ||
| __device__ __host__ inline static T2* global(T* ptr) { |
There was a problem hiding this comment.
Three small things on the signature:
remove_volatilesilently dropsvolatile. Nothing inccoSdmaQueueDeviceHandleis volatile today, so this is inert — butrptranddoorbellare hardware-updated, and if anyone ever marks them volatile this helper will quietly discard it. Astatic_assert(!std::is_volatile<T>::value)would keep that honest.__host__:address_space(1)means nothing on the host, and the attribute invites host callers into what is a device-only idiom.T2is a defaulted template parameter, so a caller can override it:impl::global<int, float>(p)compiles and type-puns (verified). Moving it to ausinginside the body removes that.
Also static at namespace scope is redundant for a template.
| do { | ||
| cachedHwReadIndex = __hip_atomic_load(rptr, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_SYSTEM); | ||
| cachedHwReadIndex = | ||
| __hip_atomic_load(impl::global(rptr), __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_SYSTEM); |
There was a problem hiding this comment.
The description says "enforced global memory space for all internal loads / stores", but three sites stay on flat:
- L1252, the
__hip_atomic_fetch_add(cachedWptr, ...)immediately above — one per put, and the hottest single memory op in this path. Both trees still show 12flat_atomic_add_x2intest_sdma_put, on gfx950 and gfx1250 alike. - L1620 / L1628 / L1636,
**(deviceHandles + q)inccoSdmaCommitThread/Warp/Block— these are the 6flat_load_dwordx2still present in the PR build. - L1827, the
waitSignalspin load.
Leaving them may well be deliberate, but the reservation atomic in particular seems worth either converting or saying why not.
There was a problem hiding this comment.
I see, then I guess I have missed these spots. I have just compiled few tests to check for flat load/stores, need to have a look at them once again
| : CCO_SDMA_UNIT * (n + sig); | ||
| } | ||
|
|
||
| using Uint4 = uint32_t __attribute__((ext_vector_type(4))); |
There was a problem hiding this comment.
Uint4 lands in the public mori::cco namespace of a public header, which is a fairly generic name to claim. include/mori/collective/allreduce/twoshot_sdma_kernel.hpp already defines its own Uint4 (function-local, so no clash today).
global() above is correctly tucked into mori::cco::impl — this could go the same way, or be named ccoUint4.
| *ccoSdmaUnitAt(handle, at) = img; | ||
| auto dst = impl::global(ccoSdmaUnitAt(queueBuf, at)); | ||
| dst->vec[0] = Uint4{p[0], p[1], p[2], p[3]}; | ||
| dst->vec[1] = Uint4{p[4], p[5], p[6], 0}; |
There was a problem hiding this comment.
The trailing 0 used to carry its reason:
img.dw[7] = 0; // NOP (op=0), pads COPY out to the unit; engine skips itIt is an opcode the engine skips rather than padding, and that is not recoverable from the bare literal. Worth keeping the comment here — ccoSdmaWriteAtomic just below is a good contrast, since there the eighth dword is real packet content.
| // Where a unit at monotonic byte offset `at` lands. Units are 32B-aligned in a | ||
| // 32B-multiple ring, so this never needs to split a packet. | ||
| inline __device__ ccoSdmaUnit* ccoSdmaUnitAt(ccoSdmaQueueDeviceHandle& handle, uint64_t at) { | ||
| inline __device__ ccoSdmaUnit* ccoSdmaUnitAt(uint32_t* queueBuf, uint64_t at) { |
There was a problem hiding this comment.
Minor, but the alignment requirement changes character here. The old *ccoSdmaUnitAt(...) = img compiled to dwordx3@0 + dwordx4@offset:12 + dword@offset:28 — the compiler was not assuming 16 B alignment. The new Uint4 stores are dwordx4@0 and @offset:16, which do.
It holds today: every at is a multiple of CCO_SDMA_UNIT (32), and queueBuf comes from hsaKmtAllocMemory so it is page-aligned. But the failure mode moved from "slower" to "wrong", so it may be worth pinning down — a __builtin_assume(at % CCO_SDMA_UNIT == 0), or an assert under MORI_CCO_SDMA_DEBUG.
|
Thanks for taking a look! I am going to be away until 17.09, so I guess we can put this PR on hold. |
before:
after: