fix(ck): route scattered page_size=1 paged-KV batch-prefill to GLOBAL… - #10180
Conversation
…_LOAD_LDS Additive follow-up to ROCm#9214. ROCm#9214 routes the FMHA batch-prefill paged-KV gather to GLOBAL_LOAD_LDS only when base_lo32 + pool_bytes exceeds INT32_MAX, using num_total_pages * batch_stride * element_bytes as the max SRD voffset. That bound is invalid for a scattered 1D page table (page_size=1 LINEAR + SGLANG_PAGE_TABLE_1D, e.g. MiniMax-M3 under causal chunked prefill with a prefix cache, q_len != kv_len): the page table holds arbitrary physical-page indices into the whole KV pool, so the per-page signed-int32 voffset (physical_page * stride_page_block + within_page) can wrap on its own, independent of the pool's contiguous size. BUFFER_LOAD then reads a wrapped address and the kernel takes a GPU memory-access fault. Keep ROCm#9214's address-overflow fast path unchanged and add one guard: when page_block_size == 1, always use GLOBAL_LOAD_LDS (full 64-bit address via tile_scatter_gather). The BUFFER_LOAD fast path for 1 < page_block_size < kN0 with in-bounds addresses is preserved. Validated on MiniMax-M3-MXFP4, TP=4, MI35x (gfx950), sglang random benchmark (3500-in / 2048-prefix / 1024-out, 128 concurrency, 3000 prompts): latest CK develop (with ROCm#9214) faults at ~2106/3000; with this change the run completes 3000/3000 with zero faults.
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
Add FmhaBatchPrefillKvLoadMode.SinglePageAlwaysGlobalLoad to test_fmha_fwd.cpp: page_block_size == 1 must route to GLOBAL_LOAD_LDS even for a low-base, in-bounds pool where the ROCm#9214 address-overflow check alone would pick BUFFER_LOAD, while a >1 sub-tile page with the same address still takes the BUFFER_LOAD fast path.
|
Thanks for the fix! Quick question on Could you help confirm whether, in the SGLang integration, k/v is always the full pool ( |
|
@Jeff-Huang upon probing more in depth, I found your assumption to be true On
So CK does not receive a smaller view than the index space, and The real cause: even so, this MiniMax-M3 Forcing that arm to Net: the single-token-page |
|
Thanks for the detailed investigation. One thing we still can't explain is why the BUFFER_LOAD arm faults while GLOBAL_LOAD_LDS does not, given the addresses are provably in range. With voffset ~64 KB and lo32+pool = 1.121 GiB, both paths should resolve to the same physical addresses, so the fault mechanism isn't clear to us yet. Without it, we're a little concerned this may be routing around a defect rather than fixing it, and that the same defect could still be reachable through other configurations. Could you establish the root cause before this is merged? The natural starting points:
|
|
@Jeff-Huang Faulting VA: 0x7f8937388000 (node-6 / dev0). Page-id range for the dispatch: [256, 359939], all < num_total_pages = 3,613,312 → indices are valid; the bad address is from offset arithmetic, not an out-of-range page. The dispatch is a causal chunked prefill with #cached-token = 2048 (prefix cache), q_len != kv_len. So BUFFER_LOAD and GLOBAL_LOAD_LDS do not resolve to the same address here — the BUFFER_LOAD path computes a negative V offset. GLOBAL_LOAD_LDS forms the correct per-element 64-bit address from the page table and stays in bounds, which is why routing to it fixes the fault. This is fixing a real defect, not masking one. Notably the underflow is exactly 2048 − 128 pages = prefix_len − page_size, which strongly implicates the V-gather deriving its KV base from the query offset (only valid when q_len == kv_len); with a prefix cache it underflows by the prefix length. |
|
@mohbasit What's worth chasing is why the Would it be possible to dump the actual One more question: was the patched run validated for numerical correctness against a reference, or only for absence of faults? Both paths share the same |
|
@Jeff-Huang As for the first query, physical_page — not negative/garbage. Neither the within_page — is 0 for page_size=1 (kInPageOffsetMask == 0) So the negative/OOB term is not in either page-index computation. It's formed inside tile_scatter_gather's address assembly — the SRD base/voffset construction from physical_pages_ + page_stride_elements_ + coord, i.e., the coord/SRD-base handling in include/ck_tile/core/tensor/tile_scatter_gather.hpp, not the page lookup I am not sure how to exactly pin point to the instruction which is causing this though. The OOB is sensitive to register allocation / instruction scheduling / SRD-register placement, that is why it seems latent in production. |
|
@mohbasit Would it be possible to run a numerical correctness check on the case that used to crash? Our concern is just this: we still don't have a confirmed root cause for the negative offset in the So at minimum it would be good to confirm that the |
Jeff-Huang
left a comment
There was a problem hiding this comment.
Approving as a workaround for the production crash. Root cause of the negative offset in the BUFFER_LOAD path is still open — tracking separately.
|
Gardener triage: this failure is infra, not your change. The only real failure is Every RPM 404s while the repo metadata downloads fine, so it is a URL problem, not missing Your change only touches Tracking issue: ROCm/TheRock#7161 Happy to override-merge on that basis whenever you are ready — just ping me once the other CI you |
|
@AmosLewis, thank you, our CI passed, could you override-merge this PR? |
fix(ck): route scattered page_size=1 paged-KV batch-prefill to GLOBAL_LOAD_LDS page_size=1 paged-KV batch-prefill faults on gfx950 with the BUFFER_LOAD gather even for in-bounds addresses. Route page_block_size == 1 to the 64-bit-safe GLOBAL_LOAD_LDS path; #9214's fast path for 1 < page_block_size < kN0 is unchanged. Validated on MiniMax-M3-MXFP4 TP=4 (gfx950): 3000/3000 requests, zero faults. Root cause of the negative BUFFER_LOAD offset is still open, tracked separately. Override rationale: the only red check is the sles16 RPM install lane, which is broken repo-wide and unrelated to this change. Tracking: ROCm/TheRock#7161.
fix(ck): route scattered page_size=1 paged-KV batch-prefill to GLOBAL_LOAD_LDS
ISSUE ID : ROCm/aiter#3824
Motivation
#9214 fixed a 32-bit SRD address overflow in the ck_tile FMHA
mha_batch_prefillpaged-KV gather by switching toGLOBAL_LOAD_LDSwhenbase_lo32 + pool_bytesexceedsINT32_MAX. That fix removes the high-base-VA fault, but a second, distinct fault in the samepage_block_size < kN0BUFFER_LOADarm remains for a scattered 1D paged KV layout (page_size=1LINEAR +SGLANG_PAGE_TABLE_1D), as used by MiniMax-M3 under causal chunked prefill with a prefix cache (q_len != kv_len).In that layout the per-page SRD
voffsetisphysical_page * stride_page_block + within_page, wherephysical_pageis read from a page table and indexes into the entire KV pool.#9214's overflow check estimates the maximumvoffsetasnum_total_pages * batch_stride * element_bytes, but for a scattered page tablenum_total_pages(this dispatch's page count) does not bound the physical page indices — they can point anywhere in the global pool. So the signed-int32voffsetcan wrap on its own, independent of the pool's contiguous byte size and of the base address.#9214's check evaluates to "no overflow", keepsBUFFER_LOAD, the SRD reads a wrapped address, and the kernel takes a GPUmemory-access fault (
Memory access fault by GPU node-N ... Reason: Unknown→ coredump →Fatal Python error: Aborted).Technical Details
Keep
#9214's fast path and address-overflow check exactly as-is, and add a single guard for the scattered single-token-page case:GLOBAL_LOAD_LDSforms the full 64-bit address viatile_scatter_gatherand is always safe for the scattered case.BUFFER_LOADfast path for1 < page_block_size < kN0with in-bounds addresses is preserved — this change is strictly additive to fix: FMHA batch-prefill paged-KV 32-bit VA overflow at high GPU base addresses #9214 (+9 lines, no behavior change for any case fix: FMHA batch-prefill paged-KV 32-bit VA overflow at high GPU base addresses #9214 already handled).Changed files:
example/ck_tile/01_fmha/fmha_fwd.hpp— one extrapage_block_size == 1early return infmha_batch_prefill_select_kv_load_mode.test/ck_tile/fmha/test_fmha_fwd.cpp— host-only unit testFmhaBatchPrefillKvLoadMode.SinglePageAlwaysGlobalLoadcovering the new guard (extends the selector tests fix: FMHA batch-prefill paged-KV 32-bit VA overflow at high GPU base addresses #9214 added).Test Plan
Full end-to-end serving repro on MI35x (gfx950), 4 GPUs, sglang with the aiter attention/MoE backends. Model:
amd/MiniMax-M3-MXFP4, TP=4.Server (image built with CK at
developHEAD incl. #9214, then this patch):Load (drives large mixed prefills with 2048-token shared prefix → prefix-cache hits at 128 concurrency, the exact conditions that manufacture the faulting batch-prefill dispatch):
Test Result
develop(with #9214), unpatchedMemory access faultat ~2106/3000 requestsdevelop+ this changeRight before the fault (unpatched) the batch state is
#running-req: 128,#cached-token: 2048(prefix-cache hits) with large mixed prefills — all four TP ranks (KFD nodes) fault. With this change the identical run completes cleanly at ~22.1k tok/s total throughput and theBUFFER_LOADfast path is retained for thepage_block_size >= kN0and1 < page_block_size < kN0in-bounds cases.Submission Checklist
https://github.com/ROCm/ROCm/blob/develop/CONTRIBUTING.md#pull-requests