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!
There was a problem hiding this comment.
There was a bug with non-contiguous inputs, that should be fixed now.
|
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.
There was a problem hiding this comment.
I've added some comments, hopefully it clears up what is 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
wangye805
left a comment
There was a problem hiding this comment.
in the PR description, add the ticket or confluence doc link for performance
| n_arr[i] = n_i; | ||
| } | ||
|
|
||
| // Activation data (B), activation scales (SB), and output (D) must be contiguous. |
There was a problem hiding this comment.
For main stream MOE models, permuted tokens will still be guaranteed to be continuous but usually the scales is hard to guarantee to be still continuous, especially after padding.
So what's the motivation of this continuous assumption? Without it, the hipkitten kernel will drop a lot? Does our CK grouped gemm need this assumption?
There was a problem hiding this comment.
This was simply a simplification, but you are right that if data is not 256 aligned and scales are not 16 byte aligned then we are unable to use this method. Since we don't do padding for rocm though, generally I would expect us to be okay for mainstream MoE models like you mention.
A note on performance though, if we guarantee that all of output, activations, scales, and weights are contiguous we see about a 20-25% increase in performance for these grouped GEMM kernels. Currently coming from Megatron, only weights are non-contiguous after quantization. I have some ideas on how we can make that possible, or even better, that I am planning on working on in a followup ticket.
| diff algo_tune.csv algo_tune_check.csv | ||
|
|
||
|
|
||
| Grouped GEMM Backends on ROCm |
There was a problem hiding this comment.
Technically multi-stream based hipblaslt/hipkitten is another backend. Maybe we should say explicitly "single-launch" grouped gemm in the section title
There was a problem hiding this comment.
I think technically speaking multistream is not really a grouped gemm, but a set of gemms... -- I see where you are coming from though and have added that clarification!
|
|
||
| The following environment variables control backend selection: | ||
|
|
||
| * ``NVTE_USE_CUTLASS_GROUPED_GEMM=1`` -- Enable the grouped GEMM path. On gfx950, defaults to HipKittens with CK fallback. |
There was a problem hiding this comment.
Is this env by default 1?
There was a problem hiding this comment.
No, this is unset by default. I have added a note at the top of this subsection.
| """Return 64 MiB for gfx50x, 32 MiB for all other architectures.""" | ||
| if get_device_compute_capability() == (9, 5): | ||
| return 67_108_864 | ||
| return 134_217_728 |
There was a problem hiding this comment.
Emm, this workspace size was originally for hipblaslt
There was a problem hiding this comment.
Sorry, I was doing some experiments and forgot to revert this. Should be back to 64 MB now.
|
|
||
| auto *ws = convertNVTETensorCheck(workspace[0]); | ||
|
|
||
| return kittens_grouped_mxfp8_wgrad( |
There was a problem hiding this comment.
Just curious, does hipkitten wgrad need continuous b and b scale requirement?
There was a problem hiding this comment.
No, as this kernel was designed fully per-expert due to the varying K dimension.
| } | ||
|
|
||
| static size_t align256(size_t x) { | ||
| return (x + 255) & ~(size_t)255; |
There was a problem hiding this comment.
nit: since hipkittens kernels are a standalone lib.so, we can use its own env prefix, like HK_. Or still use NVTE_ but pass it from libte.so into libhipkitten.so
There was a problem hiding this comment.
Sounds good, I have added hk prefix. I am hoping to avoid introducing a dependency on libte to keep cmake/builds clean
|
|
||
| template <bool ACCUMULATE, typename OutGL> | ||
| __global__ __launch_bounds__(NUM_THREADS, 2) | ||
| void mxfp8_wgrad_nt_kernel( |
There was a problem hiding this comment.
Emm, is this wgrad_nt_kernel based on/modified from regular nt_kernel? If so, can you have some comments on how the new one is derived.
If this is a complete new one, maybe have some tiling + pipelining comments or a pointer to our internal doc?
There was a problem hiding this comment.
Done. This is similar to our regular nt kernel but the varying K dimension means that templating was getting messy so I separated it out. Also, a 1D grid performed better for the nature of expert wgrad where k is relatively small to m and n.
Modifies HipKittens MXFP8 GEMM kernels to handled single launch grouped GEMM. Usage is enabled through NVTE_USE_CUTLASS_GROUPED_GEMM, similar to CK FP8