Fix H20 packed-K GEMM correctness#45
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces several fixes and enhancements to the humming library's epilogue and tuning logic. Key changes include adding a TMA store commit and stream-K wait group logic in gmem_writer.cuh, introducing tma_fence_async_shared to prevent shared memory reuse issues in pipeline.cuh, and adding bounds checks in smem_reducer.cuh to avoid out-of-bounds accesses during reduction. Additionally, the tuning heuristics in sm90_h20.py were updated to enforce a minimum warp shape N of 32 for packed K layouts, and comprehensive tests were added to validate these behaviors. There are no review comments, and I have no additional feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
c479cf1 to
12f2539
Compare
H20 packed-K dense kernels could return corrupted output or select an invalid pipeline shape under the configurations exercised by GLM-5.2. The packed-K S2R pipeline double-buffers N fragments and requires at least two warp-N iterations, but the H20 heuristic could select warp-N 16. Clamp packed-K configurations to warp-N 32 while preserving the existing choice for non-packed layouts. The shared-memory K-warp reducer partitioned the flattened accumulator register array with floor division. Whenever the register count was not evenly divisible by floor(warp-M / 16), trailing accumulator vectors were never reduced. The observed warp-M 56 and warp-N 32 configuration has 14 int4 vectors: three iterations of four vectors processed only 12, leaving two vectors that represent the final 8 output rows. Use ceiling division for both dimensions and guard the partial tail while moving and reducing accumulator vectors. The TMA-C epilogue publishes its tile through generic shared-memory writes and then reads it through the TMA async proxy. A thread barrier does not establish visibility between those proxies. Issue fence.proxy.async.shared::cta from every writer before synchronization, commit each TMA store group, and wait for outstanding stores before the shared-memory buffer is reused. Validation covers H20 heuristic selection, a representative partial accumulator tail, TMA-C buffer reuse, and the exact M=160 stream-K dense configuration. The server-level regression changed from 0/9 GSM8K before the proxy fence to 1275/1319 on the full baseline-comparable dataset after the fix.
12f2539 to
7e0aca8
Compare
Summary
This PR fixes three correctness issues in the H20 packed-K GEMM path used by dense W4AFP8/FP8 workloads:
The changes are limited to Humming kernel selection, reduction, and epilogue synchronization. They do not add or modify checkpoint quantization schemas.
Root cause
Packed-K warp-N selection
Packed-K maps each warp iteration to a 16-column N fragment. The S2R pipeline alternates between two buffers, so it requires at least two N iterations. The H20 heuristic could select
warp_shape_n=16, leaving only one iteration and violating that pipeline invariant.Packed-K configurations now clamp warp-N to 32. Non-packed layouts keep their previous heuristic result.
Partial accumulator tail in K-warp reduction
The shared-memory K-warp reducer partitioned the flattened accumulator register array using floor division for both the reduction iteration count and the vectors handled per iteration. Whenever the register count was not evenly divisible by
floor(warp_shape_m / 16), the trailing vectors were never moved through shared memory and therefore never accumulated across K warps.Warp-M 56 is the concrete H20 reproducer, not a special-cased shape. With warp-N 32 and FP32 accumulation, the register array contains 14
int4vectors per thread. The old calculation used three iterations of four vectors and processed only 12. The two omitted vectors collectively represent the final 8 rows of the 56x128 output tile. Other shapes can exhibit the same truncation whenever their flattened register count leaves a remainder under the old partitioning.The reducer now uses ceiling division for the iteration count and per-iteration width, with bounds checks for the partial final chunk. This handles the general remainder case without a block-M-specific branch.
TMA-C shared-memory proxy ordering
The epilogue writes an output tile to shared memory through the generic proxy, then TMA reads the same tile through the async proxy. A thread barrier coordinates the writers but does not make generic-proxy writes visible to the TMA async proxy.
Every math writer now issues
fence.proxy.async.shared::ctabefore the existing synchronization. TMA output operations are also committed as store groups, and outstanding stores are waited on where required before the shared-memory source buffer is reused.Without this ordering, the affected server configuration produced corrupted logits and repetitive output even though the individual kernel configuration could appear correct in isolated runs.
Validation
gsm8k_train8_test1319.jsonlvalidation after the fix: 1275/1319 correct, or 0.966641 accuracy. The matching baseline was 1263/1319, or 0.957544.The proxy-ordering failure is timing-sensitive: the focused GPU tests exercise the affected path, but removing only the proxy fence does not currently make them fail deterministically. The server-level accuracy regression is therefore the effective fail-before/pass-after validation for that part of the fix.