feat: add FlyDSL batched preshuffle GEMM for FP8 rowwise scaling (WP-G2) - #444
feat: add FlyDSL batched preshuffle GEMM for FP8 rowwise scaling (WP-G2)#444kudomcho wants to merge 6 commits into
Conversation
cthi
left a comment
There was a problem hiding this comment.
Thanks, please also check the CI lint as it is failing.
Left some comments, also would be good to nail down the code structure for the new FlyDSL gemms. For exmaple do you plan to have flydsl/_kernels/[...] as building blocks for FlyDSL? if so why do we put the kernel in mslk/gemm/flydsl/_kernels/preshuffle_gemm.py ?
ae3d13e to
81e35b4
Compare
|
Largely looks good. Please resolve the remaining module imports. |
7c59584 to
3b6632d
Compare
| def supported(self) -> bool: | ||
| if not super().supported: | ||
| return False | ||
| from mslk.utils.flydsl import is_flydsl_available |
There was a problem hiding this comment.
Moved to top-level, guarded by if is_flydsl_available().
| def supported(self) -> bool: | ||
| if get_current_accelerator() not in self.supported_accelerators: | ||
| return False | ||
| from mslk.utils.flydsl import is_flydsl_available |
There was a problem hiding this comment.
Same, top-level now.
| @classmethod | ||
| def setUpClass(cls): | ||
| cls.device = torch.accelerator.current_accelerator() | ||
| from mslk.utils.flydsl import is_flydsl_available |
There was a problem hiding this comment.
Done, moved to the top-level import block.
|
Seeing illegal memory writes in unit test from both runs: |
3f248d0 to
3d52b6b
Compare
|
memory access fault resolved. Test logs are now provided on PR info |
9e95d95 to
7d4bc9d
Compare
Add FlyDSL JIT backend for FP8 rowwise preshuffle GEMM on gfx950 (MI350). Provides single and batched FP8 preshuffle GEMM via FlyDSL, with optional HIP graph acceleration for the batched path. Also registers as the ROCm implementation of the mslk rowwise FP8 ops on gfx950. New module: mslk.gemm.flydsl - flydsl_preshuffle(src) — weight shuffle into FlyDSL layout - flydsl_preshuffle_gemm() — JIT-compile and run the kernel - _configs.py — default tile configs for gfx950 heuristic selection - _kernels/ — ported FlyDSL kernel compiler Shared kernel infra: mslk.flydsl.kernels.mma - mfma_epilogues.py, mfma_preshuffle_pipeline.py Op wiring (gfx950, gated on is_flydsl_available()): - torch.ops.mslk.f8f8bf16_rowwise → FlyDSL (bf16) - torch.ops.mslk.f8f8f16_rowwise → FlyDSL (fp16) - torch.ops.mslk.f8f8bf16_rowwise_out → FlyDSL (out-tensor) - torch.ops.mslk.f8f8bf16_rowwise_batched → FlyDSL batched Benchmark: FP8RowwisePreshuffleFlyDSL in bench/gemm/gemm_ops.py Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
… B batches
Use gpu.block_id("z") to index into batch dimension, offsetting all
buffer resource addresses by batch stride. Eliminates 16 separate
kernel launches in favor of one launch with grid=(gx, gy, B).
3ec250c to
cf78e5b
Compare
|
@cthi has imported this pull request. If you are a Meta employee, you can view this in D114744187. |
Remove the old FP8RowwisePreshuffleFlyDSL definition that used is_flydsl_version_at_least() with module-level imports, keeping the newer version with lazy imports. Remove unused is_flydsl_available import from gemm_test.py.
| _batched_preshuffle_cache: dict = {} | ||
|
|
||
| def _get_batched_preshuffled(WQ: Tensor) -> Tensor: | ||
| key = WQ.data_ptr() |
There was a problem hiding this comment.
I think I missed this in a prior PR, but basically caching stuff based on the data_ptr is not really valid PyTorch, CCA could re-use these pointer for something else. Can we avoid doing this? Why do we need to cache the preshuffled W? User can simply pass it pre-shuffled properly.
There was a problem hiding this comment.
Removed the data_ptr cache entirely. The batched dispatch now expects pre-shuffled weights from the caller.
| WQ: Tensor, | ||
| x_scale: Tensor, | ||
| w_scale: Tensor, | ||
| bias: Optional[Tensor] = None, |
There was a problem hiding this comment.
As bias is not supported, raise an exception if it is present.
There was a problem hiding this comment.
Added, raises NotImplementedError if bias is not None.
| use_fast_accum: bool = True, | ||
| output: Optional[Tensor] = None, | ||
| ) -> Tensor: | ||
| WQ_shuf = _get_batched_preshuffled(WQ) |
There was a problem hiding this comment.
I commented about this above, but we should let user explicitly preshuffle as needed instead of trying to cache it.
There was a problem hiding this comment.
Removed the cache. Caller is responsible for preshuffling now.
- Empty __init__.py re-exports, follow triton convention (direct imports) - Move all method-level FlyDSL imports to top-level in bench and test - Remove data_ptr-based preshuffle cache (invalid under CCA pointer reuse) - Batched dispatch now expects caller to pass pre-shuffled weights - Raise NotImplementedError if bias is passed to batched op
Motivation
Add batched FP8 preshuffle GEMM via FlyDSL for gfx950 (MI350). Derivative of WP-G1 — builds on the non-batched preshuffle kernel from PR #434.
Depends on WP-G1 PR #434. Rebased on main after PR #447 merge.
Technical Details
New API:
mslk.gemm.flydsl.preshuffle_gemmflydsl_preshuffle_batched_gemm(XQ, WQ, x_scale, w_scale, ...)— batched GEMM with Grid-Z batching (single kernel launch for all B batches)data_ptr— preshuffle once, reuse on subsequent callstorch.library.impl("mslk::f8f8bf16_rowwise_batched", "CUDA")on gfx950Performance optimizations:
Grid-Z batching — uses
gpu.block_id("z")to index into the batch dimension, launching all B batches in a single kernel withgrid=(gx, gy, B). Each batch's buffer resource addresses are offset bybz * batch_stride_bytes. Eliminates Python dispatch overhead entirely (was ~258 us for 16run_compiledcalls).XCD swizzle + waves_per_eu tuning — full parameter sweep across tile configs ×
xcd_swizzle(0,1,2,4) ×waves_per_eu(0,1,2) identified optimal settings per config.xcd_swizzle=1improves L2 cache reuse across chiplets;waves_per_eu=2improves scheduling on large shapes.Profile-guided shape overrides —
_SHAPE_OVERRIDES_GFX950lookup table maps(m_range, N, K)to sweep-optimal configs for N=1280 and N=8192 shapes where the heuristic picks suboptimal tiles.Batch-aware occupancy heuristic —
select_default_config(batch=B)factors Grid-Z parallelism into the occupancy threshold (m_tiles * n_tiles * B >= 64), enabling larger tile configs that were previously rejected.Benchmark:
FP8RowwiseBatchedPreshuffleFlyDSL— new benchmark class inbench/gemm/gemm_ops.pytargetingAMD_GFX950, gated onis_flydsl_available().Test Plan
# Correctness test pytest test/gemm/gemm_test.py -k FlyDSLPreshuffleBatchedGemmTestReproducer: Host-side end-to-end (CUDA events, separate processes)
Test Result
Correctness: 8/8 shapes pass
Host-side end-to-end: FlyDSL vs CK (B=16, gfx950 MI350, CUDA events, 200 iters)
FlyDSL uses Grid-Z batching (single kernel launch). CK uses a single C++ batched kernel. Speedup > 1.0 means FlyDSL is faster.
FlyDSL beats CK on 8 of 11 shapes (up to 1.23x faster). Remaining 3 shapes are within 4–9% of CK.
GPU kernel time vs host time breakdown (rocprof + CUDA events)
Confirms the speedups are from genuine GPU kernel improvements, not host-side artifacts. GPU time measured via
rocprof --stats, host time via CUDA events (separate runs, no rocprof overhead).FlyDSL GPU kernel is faster on 7 of 9 shapes (up to 1.42x at M=1024, K=1024). The xcd_swizzle + waves_per_eu tuning drives the kernel-level gains. CK's (128,8192,1024) host overhead (22%) is notably higher than FlyDSL's (4%), explaining the larger host-level speedup on that shape.
Optimization impact summary
Submission Checklist
flydsl_preshuffle_batched_gemm()FP8RowwiseBatchedPreshuffleFlyDSLf8f8bf16_rowwise_batchedon gfx950is_flydsl_available()— graceful fallback