Repository navigation
[FlyDSL] FP8 MQA logits improvements for gfx950 - #7412
vpietila-amd wants to merge 6 commits into
Conversation
…computing optimal KV-split values, add new head accumulation method; improve software pipelining; fix empty KV window handling; use async loads for LDS copies.
…mqa-logits-gfx950-improvements
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
One backend per PR: PR title tags & labels: |
NOT READY FOR HUMAN REVIEWPR Self-ReviewRevision Author self-review accepted. Readiness report: Author answers
Show the author's self-review answersBehavior and intentValidation and unverified scopePR-specific questionChanged op testsPR state
Copilot findings
Code and op-test hygiene
Required CI
Shadow mode reports what would block but keeps the status successful. Set |
…t guidelines
Kernel (ISA-identical on all 96 checked gfx950 specializations):
- Use the public fx.fma spelling instead of fx.math.fma.
- Pass DSL values straight to rocdl.readfirstlane, which coerces them;
drop the redundant .ir_value() unwraps.
- Pass tile_skip/dma_guard to the LDS builder directly instead of through
conditional kwargs dicts.
kernels_common strided warp reductions:
- Document why fx.coop (FlyDSL 0.4.1) cannot replace them: all of its
warp collectives, including the ROCm DPP override, fold contiguous lane
groups only, and permlane{16,32}_swap has no typed wrapper.
- Use fx.max for ReductionOp.MAX, matching fx.coop's combine.
op test:
- --shapes-csv rows now sweep the existing --num-heads/--head-dim/--window
axes when a column is absent, replacing the scalar --csv-num-heads and
--csv-window flags.
- -o/--output gets help text, writes every scenario's table (bench no
longer overwrites verify), and reuses the logged speedup line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rse variants LDS usage and do not build variants that exceed devices LDS capacity. Fix documentation for gfx950 routes. Add test for auto-routed head dimensions.
…s. Test improvements: every route is checked against reference.
Summary
This PR improves the current FlyDSL FP8 MQA logits kernel on gfx950. The KV-split (
grid.y) heuristic now uses the occupancy-aware wave-cost model (same as forgfx942), and a tuned route table replaces the hand-written variant pick. The LDS-pipelined builder gains new head-accumulation, head-reduce, and software-pipelining options, and moves to async copy atoms. The empty/past-the-end KV-window handling is also fixed.On MI350X, over the 960-case
--fullsweep, FlyDSL FP8 MQA logits provides a speed-up over the existing Gluon andthe old FlyDSL kernel (geometric mean)
Motivation
cu_oversubtarget that ignores how many blocks of a given kernel instance fit on a CU. Small-M / long-KV shapes were badly under- or over-split._auto_variantfor gfx950 picked from a handful of fixed variants and lost to Gluon on about half the sweep. On main, FlyDSL is 0.91×–1.00× Gluon geomean for H32–H64 at clean_logits=False.seq_len_kv(past_end), or any empty window, stretched the block's union KV window toseq_len_kv. The block scanned the entire KV for nothing.raw_ptr_buffer_load_lds/create_llvm_ptr/buffer_opsaddressing and a hand-coded swizzle.Changes
Host side (
aiter/ops/flydsl/fp8_mqa_logits_kernels.py)_SplitPolicyadopts gfx942's occupancy-aware wave-cost model (block_overhead_tiles=8). A launch that cannot query occupancy keeps main's fixedcu_oversub=4target._variant_builder):mfma<MxNxK>_bkv<B>_r<R>_w<W>[_lds<NB>[_pd<PD>][_swp][_rs][_ls][_ts][_dg]][_h<fma|fma2>][_wpe<N>][_ns<N>]. The gfx942 registry is unchanged._GFX950_ROUTES, keyed by (num_heads class, head_size class) withseq_len/seq_len_kvbounds, replaces the gfx950_auto_variantheuristic._auto_variant/_resolve_variantnow takehead_size, and ahead_sizethe atom'sMFMA_Kdoesn't match is rejected host-side.head_size64 and 128. For a larger head_size,_auto_variantskips any route whose LDS slots exceed the device's LDS (_variant_lds_bytesagainstget_lds_capacity_bytes) and falls back to_GFX950_LARGE_HEAD_FALLBACKif none fits._variant_supportsapplies the same LDS check._wpe<N>sets thewaves_per_eucompile hint per variant.mfma32x32x64_bkv64_r1_w1_lds2_swp_rs_hfma.Kernel (
aiter/ops/flydsl/kernels/mqa_logits/fp8_mqa_logits.py)head_accmodes for the per-lane head sum:add(previous behavior),fma(one non-reassociable FMA chain), andfma2(two packedv_pk_fma_f32chains).rs_head: the64 // MFMA_Nn-tiles of a group are head-reduced together with permlane swaps (warp_reduce_scatter_strided), and all 64 lanes store one contiguous run. Before, each n-tile needed a shuffle butterfly and an MFMA_N-lane store.sw_pipe: a depth-2 software pipeline that issues item k+1's MFMAs before consuming item k's accumulators, withiglp_opt(0)for WPB ≤ 2.lds_scales(kv_scales staged into LDS by the same async DMA),tile_skip(per-wave skip of tiles outside its rows' windows) anddma_guard(no prefetch past the last tile). Also aprefetch_depthtag.fx.rocdl.cdna4.BufferLoadAsyncLDS{128b,32b}copy atoms. The bank-conflict swizzle is aSwizzleTypecomposed layout. It is applied on both the DMA source and the read view, so it must be its own inverse (S >= B). Its width and row-bit position follow the head size, so it also holds for head_size > 256 (verified at 512). The builder asserts this, and asserts that its LDS slots fit the device. A and B fragments are tiled copies derived from the atom's tiled MMA (make_tiled_copy_A/B). This removesbuffer_opsandcreate_llvm_ptrfrom this kernel.cu_ends <= cu_startsafter clamping) takes the window[seq_len_kv, 0), so it no longer stretches the block's union window.Shared helpers (
aiter/ops/flydsl/kernels/kernels_common.py)warp_reduce_stridedandwarp_reduce_scatter_stridedare MFMA-accumulator head reductions withfx.coop-style signatures plusstride.fx.coopcannot express them. All of its warp collectives (warp_reduce,warp_reduce_batched[_to_blocked|_to_striped], and the ROCm DPP override ofwarp_reduce) fold contiguous lane groups (XOR 1..width/2), while the head reduce must fold lane bits at and aboveMFMA_N.permlane{16,32}_swapis also exposed only as the raw ROCDL op. The helpers keepfx.coopsignatures so they can be swapped oncefx.coopgains a lane-stride option.Test (
op_tests/test_flydsl_fp8_mqa_logits.py)verifyalso pins every routed gfx950 variant (and gfx942's auto variants) on a small odd shape across num_heads, head_dim, clean_logits and the causal/past_end windows. These are the production-variant pins.variant.--shapes-csvoption: sweep shapes from a CSV. Per-rownum_heads/head_dim/window/batchcolumns pin those axes for the row; otherwise the row sweeps the CLI axes.-o/--outputoption: also write the summary tables and the speedup line to a markdown file._PRODUCTION_EXTRA_HEAD_DIMS), skipping variants whose atom cannot tile the problem or whose LDS slots do not fit.verify_auto_routed_head_dims(gfx950): at head_dim 512, for 5 shapes on every side of the route bounds and every num_heads,_auto_variantmust pick a route that tiles the problem and fits LDS, and the unforced op must match the reference. Onbfe265224all 15 cases fail grading (calc_diff 0.33-0.43, or an occupancy-query error from LDS overflow); they pass here.Performance
Hardware: AMD Instinct MI350X (gfx950, 256 CUs), ROCm 10.0, torch 2.13.0, Triton 3.8.0, FlyDSL 0.4.1.
Workload:
op_tests/test_flydsl_fp8_mqa_logits.py --scenario bench --fullwith the PR branch's default shape list, run on both trees:Measurement:
bfe265224. Main:720b36ce0.Each table gives the geomean speedup (>1 means the left-hand kernel is faster).
clean_logits=True
clean_logits=False
Notes:
Excluding
past_end, where main hits the union-window bug, PR vs main is 1.21× (clean_logits=True) and 1.23× (clean_logits=False) over 372 cases each.Long-context shapes (s_kv ≥ 32k, 90 cases per value):
Coverage of wins:
Per-window breakdown (geomean):
Testing
Tested on MI350X (gfx950) only; see "Validation and unverified scope".
Documentation
The variant tag grammar, the route table and every new builder option are documented in docstrings and comments next to the code. There is no user-guide page for this op.
Dependencies
Breaking Changes
None for the public op (
flydsl_fp8_mqa_logits). For gfx950 only:mfma32x32x64_bkv64_r1_w2_lds3now resolve through the tag grammar. Any tag in that grammar still builds.KERNEL_VARIANTSlists the routed tags.swizzle=builder argument is removed; the swizzle is always on.PR Self-Review
Reviewed revision
067ae36Behavior and intent
num_splitsis now sized by the occupancy-aware wave-cost model.head_acc,rs_head,sw_pipe,lds_scales,tile_skip,dma_guardandprefetch_depth. They reduce VALU work per head term and the cost of the head reduce, and they overlap the MFMAs with the epilogue.seq_len_kvfor nothing (up to ~100× slower at s_kv = 128k), although the output was still correct.head_accparameter (defaultadd) also touch the gfx942 builder. The gfx942 routes and split policy are unchanged.Known limitation: cold first launch inside graph capture
This carries over the limitation already stated in #5921 for gfx942; it now applies to gfx950 as well.
num_splitscomes from the kernel's resident blocks per CU, read once per launcher with FlyDSL'smax_blocks_per_cu()and cached.hipModuleUnloadduring stream capture (hipErrorStreamCaptureUnsupported), and the error invalidates the capture.cu_oversub=4target (main's gfx950 behavior), and the captured graph keeps that split. Output is unaffected; only the split, and therefore performance, can differ from the tuned one.Validation and unverified scope
Validated on MI350X (gfx950), FlyDSL 0.4.1:
FLYDSL_RUNTIME_ENABLE_CACHE=0 python op_tests/test_flydsl_fp8_mqa_logits.py --scenario verify: all 815 case runs passed. That is 80 reduced-set cases, 720 production-variant pins (including 204 at head_dim 512) and 15 auto-routed head-dim cases.python op_tests/test_flydsl_fp8_mqa_logits.py --scenario bench --full -s <18 shapes> --num-heads N --head-dim Don the PR (bfe265224) and on main720b36ce0: 960 of 960 cases passed grading on both. The timings are in the Performance section.black --checkandruff checkpass on all changed files.Unverified:
head_accparameter (default unchanged). CI's gfx942 op-test lane covers it.mfma32x32x64_bkv64_r1_w1_lds4_pd1_swp_rs_hfma_wpe3at H64 D128, the compiler warns thatwaves_per_eu=3is not reached (occupancy 1). It was still the measured best for its window.PR-specific question
What would each changed op test fail on before this PR, and why is that coverage not already provided?
verify_production_variants):_auto_variantcan route to onto a small odd shape (67×1500: a clamped tail tile and a partial row block). They run across num_heads, head_dim, clean_logits and the causal/past_end windows._rs,_swp,_ls,_ts,_dg,_h*,_wpe*) did not exist. Main has no counterpart.variantcolumn,--shapes-csv,-o: reporting and input plumbing; they do not change coverage.Changed op tests
op_tests/test_flydsl_fp8_mqa_logits.pyremains the single canonical FlyDSL fp8_mqa_logits correctness and perf test:variantcolumn in the bench table.--shapes-csv, to bench production shape dumps through the same grading and timing path instead of a second script.-o/--output, to save the summary tables.