Grouped MXFP8 GEMMs with HipKittens#661
Conversation
Claude reviewReviewed the 5-file diff ( Verdict: approach is sound and the CUDA path is untouched (
Copyright headers: OK — all five files updated to 2026 AMD end-year, NVIDIA years unchanged. New AMD-only files ( |
This probably needs an update in the Readme, hipkittens is essentially "overriding" the CK grouped Gemm (which is also used for bf16 I believe). |
| }; | ||
|
|
||
| template <typename T> | ||
| __device__ __forceinline__ int rocm_upper_bound(const T *arr, int n, T val) { |
There was a problem hiding this comment.
Isn't this function in rocm_device_utils.cuh?
There was a problem hiding this comment.
Yes, however the kittens folder is built as a separate shared library that doesn't reference TransformerEngine, so we need a copy of this function here.
| void mxfp8_gemm_tn_kernel(const gl_fp8_rt A, const gl_fp8_rt B, const OutGL C, const AuxGLType AuxGL, | ||
| const gl_scale_rt scale_A_gl, const gl_scale_rt scale_B_gl, | ||
| [[maybe_unused]] const void *__restrict__ bias, [[maybe_unused]] int bias_dtype, | ||
| [[maybe_unused]] TileOffsets tile_offsets, [[maybe_unused]] int num_experts, |
There was a problem hiding this comment.
tile_offsets is passed by value into each launch, including the non-grouped launches via dummy.
Not sure if it is an issue, but could we maybe template this argument out, or pass a pointer instead?
There was a problem hiding this comment.
Addressed this with the changes to how tileOffsets is used.
| if (transa && transb) return false; | ||
| if (N % BLOCK_COL != 0 || K % BLOCK_K != 0 || K < 256) return false; | ||
| if (num_experts <= 0 || num_experts > MAX_EXPERTS) return false; |
There was a problem hiding this comment.
Would it be useful to optionally emit warning messages on the reason for fallback, similarly to the CK setup?
There was a problem hiding this comment.
I see you use the NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK flag to enable these warnings, so I have added them similarly.
| int total_M = 0; | ||
| int total_m_tiles = 0; | ||
| for (int g = 0; g < num_experts; g++) { | ||
| if (M_array[g] % BLOCK_ROW != 0) return false; |
There was a problem hiding this comment.
Same comment here regarding warning emission. Given that the CK grouped GEMM has padding support:
would it make sense to have that for HK as well? Otherwise my question would be how frequently does HK grouped MXFP8 fallback for Qwen or DS runs?
There was a problem hiding this comment.
In practice, I don't think we would ever fall back for Qwen and DS as expert weights are all 256 aligned. Activations are padded already by Megatron, so no issue there either.
|
|
||
| constexpr int MAX_EXPERTS = 512; | ||
|
|
||
| struct TileOffsets { |
There was a problem hiding this comment.
Would it make sense to carve tile_offsets out of the workspace?
There was a problem hiding this comment.
Good idea. I have done that and combined it into other variables passed to the kernel itself.
There was a problem hiding this comment.
numerics tests for the HK grouped GEMM?
There was a problem hiding this comment.
Numeric tests are covered by test_grouped_linear_accuracy_cutlass and other grouped tests
There was a problem hiding this comment.
Can you double check test_grouped_linear_accuracy_cutlass? I ran it on gfx950 with:
NVTE_ROCM_ENABLE_MXFP8=1 NVTE_USE_HIPKITTENS_GROUPED_GEMM=1 NVTE_USE_CUTLASS_GROUPED_GEMM=0 NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK=1 pytest tests/pytorch/test_numerics.py::test_grouped_linear_accuracy_cutlass -vv -s
and am seeing assertion failures from the numerical correctness checks when comparing HK outputs against the reference implementation.
e.g.
Mismatched elements: 1559808 / 1572864 (99.2%)
Greatest absolute difference: 2.58984375 at index (1792, 0, 751) (up to 0.001 allowed)
Greatest relative difference: 9792.0 at index (0, 0, 148) (up to 0.008 allowed)
FAILED tests/pytorch/test_numerics.py::test_grouped_linear_accuracy_cutlass[hipkittens-True-True-recipe0-True-126m-1-6-torch.bfloat16] - AssertionError: Tensor-likes are not close!
|
Do we have any performance numbers? I know there isn't a CK MXFP8 group GEMM for gfx950, but do we have hipBlaslt or triton baselines? |
| G::load(As[tic][1], A, {0, 0, a_half1, 0}, sw_A, a_srd, a_base, a_lds[tic][1]); | ||
|
|
||
| if (warp_m == 1) __builtin_amdgcn_s_barrier(); | ||
| asm volatile("s_waitcnt vmcnt(4)"); |
There was a problem hiding this comment.
Consider replacing the magic vmcnt values with named constants. It's currently difficult to tell why the hard-coded values were chosen.
There was a problem hiding this comment.
I don't think this is the right approach, as the number is not so much a magic number, but depends on how many G::load calls are in flight at each point. Named constants would not hold if the kernel changes.
There was a problem hiding this comment.
Right, but I don't think it's strictly the number of G::load calls themselves. The required vmcnt value is really tied to the underlying VMEM operations generated by those loads, which can vary depending on how the backend lowers them (e.g., vector load width per thread).
That gets back to my readability concern. I'm not necessarily advocating for named constants, but it might be helpful to add a brief comment describing what each non-zero vmcnt is waiting on at the pipeline level and why newer loads are allowed to remain in flight.
|
|
||
| G::load(Bs[tic][1], B, {0, 0, block_col * 2 + 1, k + 2}, sw_B, b_srd, b_base, b_lds[tic][1]); | ||
| G::load(Bs[tic][1], B, {0, 0, b_half1, k + 2}, sw_B, b_srd, b_base, b_lds[tic][1]); | ||
| asm volatile("s_waitcnt vmcnt(6)"); |
There was a problem hiding this comment.
For example here, I think the vmcnt(6) here means you're retiring k+1 scales and As[toc][1] k+1, but using named constants here could confirm
There was a problem hiding this comment.
or just describing in a comment as I said above
Modifies HipKittens MXFP8 GEMM kernels to handled single launch grouped GEMM. Usage is enabled through NVTE_USE_CUTLASS_GROUPED_GEMM, similar to CK FP8