Repository navigation
[FlyDSL] Paged mla indexer #4221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 37 commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
9e2dfbc
initial kernel and tests
amd-yashagar 65cba2b
fix bench to not use random avg context length
amd-yashagar 485f08b
simplify fp8_paged_mqa_logits: operator style for i32 arithmetic, fla…
b0bf341
perf: upgrade paged MQA logits to mfma_scale_f32_32x32x64 (K_STEPS 8→…
48d887e
review: un-hardcode ChunkK / block_kv in paged mqa logits host
fhuizing 1233f4c
Merge remote-tracking branch 'origin/main' into paged_MLA_indexer
fhuizing b5e5122
Merge remote-tracking branch 'upstream/main' into paged_MLA_indexer
fhuizing afa1886
style: apply ruff + black to flydsl mqa-logits files
fhuizing 1e0e972
Merge branch 'main' into paged_MLA_indexer
fhuizing 50bb92d
feat(flydsl): paged mqa-logits KVBlockSize>=1 + correctness-only test
fhuizing 4d70992
feat(flydsl): add Preshuffle support to paged fp8 mqa-logits + isolat…
fhuizing 5452346
Merge remote-tracking branch 'upstream/main' into paged_MLA_indexer
fhuizing 92d162f
refactor(flydsl): move paged mqa-logits kernel into mqa_logits subpac…
fhuizing c3a943e
fix(flydsl): size paged mqa-logits bench KV pool from real block demand
fhuizing e28ac81
perf(flydsl): retune paged mqa-logits launch geometry (9.3% across sh…
fhuizing 2341f1f
refactor(flydsl): use FlyDSL operator sugar in paged mqa-logits
fhuizing e415028
refactor(flydsl): drop dead helpers and tighten mqa-logits docs
fhuizing 3de0f40
refactor(flydsl): drive the paged mqa-logits tile loop with scf.for_
fhuizing 90fcbf2
refactor(flydsl): express the KV preshuffle as a layout, not a code path
fhuizing 7ae5ec5
test(flydsl): curate the paged mqa-logits matrix, 870 cases -> 29
fhuizing 8979a09
test(flydsl): cover the wave-split and ChunkK axes
fhuizing 490156e
test(flydsl): condense the paged mqa-logits test prose and plumbing
fhuizing 9a53327
docs(flydsl): drop benchmark figures from the paged mqa-logits source
fhuizing 2b3ad3f
docs(flydsl): fix stale --variant default in the paged bench help
fhuizing 2ba0e31
Merge remote-tracking branch 'upstream/main' into paged_MLA_indexer
fhuizing 22e4a84
refactor: move the FlyDSL benchmarks out of op_benchmarks/triton
fhuizing c07a978
Merge remote-tracking branch 'origin/paged_MLA_indexer' into paged_ML…
fhuizing b11cd5a
style(flydsl): satisfy the Black and Ruff pre-checks on PR #4221
fhuizing b93e68c
refactor(flydsl): drop dead conversion plumbing from the paged kernel
fhuizing 5eea1a7
Merge upstream/main into paged_MLA_indexer
fhuizing 52afe43
fix(flydsl): pass buffer load/store aux as an i32 SSA operand
fhuizing da1443b
Merge remote-tracking branch 'upstream/main' into paged_MLA_indexer
fhuizing 9a9c7f9
fix(flydsl): align buffer_ops and MFMA helpers with flydsl 0.3.1
fhuizing acf0277
perf(flydsl): software-pipeline block-table and K0 prefetch in paged …
fhuizing 99b0ec3
style(flydsl): fix Black/Ruff on paged MQA logits prefetch helpers
fhuizing b1bc231
Merge branch 'main' into paged_MLA_indexer
fhuizing 0ef86a6
Merge branch 'main' into paged_MLA_indexer
fhuizing ae5904b
Merge upstream/main into paged_MLA_indexer
fhuizing 0725f0d
address comments by Sami
fhuizing c1def93
Merge remote-tracking branch 'origin/paged_MLA_indexer' into paged_ML…
fhuizing f2bc4d1
style(flydsl): fix Black/Ruff on paged MQA logits files
fhuizing f33a573
feat(flydsl): add gfx950 H64 16x16x128 paged FP8 MQA logits kernel
fhuizing a811a1d
feat(flydsl): specialize gfx950 paged MQA logits for H=32 and H=64
fhuizing 8adbf32
feat(flydsl): compile-time next_n for gfx950 paged MQA logits
fhuizing 6c1c496
feat(flydsl): dual-bank page pipeline for gfx950 paged MQA logits
fhuizing 51b689b
Merge upstream/main into paged_MLA_indexer
fhuizing c1ee3ec
feat(flydsl): dispatch gfx950 paged MQA logits as the default decode …
fhuizing 9661da3
refactor(flydsl): make gfx950 the only paged FP8 MQA logits path
fhuizing 9a41879
style(flydsl): black-format gfx950 paged MQA logits and tests
fhuizing f5b3903
dynamic next_n scheduling WIP.
fhuizing d1cbe07
cleanup
fhuizing 30bb002
feat(flydsl): accept compact Q[B, next_n, 32, 128] for gfx950 paged M…
fhuizing 0c4b5b7
perf(flydsl): raise wave priority around scaled MFMA
fhuizing 1aaafcd
perf(flydsl): hold s_setprio across both scaled MFMAs per row
fhuizing 2ab0067
perf(flydsl): hint waves_per_eu=6 to match LDS-bound occupancy
fhuizing d63d4bf
perf(flydsl): unroll next_n scoring by two rows
fhuizing d2867b0
perf(flydsl): unroll next_n scoring by four rows
fhuizing 4b2ac42
perf(flydsl): fully unroll next_n scoring over MAX_NN
fhuizing 16923a2
Merge upstream/main into autoresearch/sep15
fhuizing 6d6b90e
perf(flydsl): cache paged MQA logit stores for the following topk
fhuizing 9f5479c
Merge upstream/main into paged_MLA_indexer
fhuizing 43be646
refactor(flydsl): drop dead gfx950 paged-MQA helpers and restore scal…
fhuizing 2182925
Merge upstream/main into paged_MLA_indexer
fhuizing 705c287
test(flydsl): keep paged MQA driver out of pytest collection
fhuizing 6295880
cleanup(flydsl): remove stale paged MQA test paths
fhuizing 3075570
test(flydsl): put paged MQA logits on the aiter op_test template
fhuizing 8098d25
test(flydsl): fold paged MQA pytest coverage into the op_test tables
fhuizing fcfe1d2
Merge remote-tracking branch 'upstream/main' into paged_MLA_indexer
fhuizing 5321501
test(flydsl): drop paged-MQA shims and add the probe_1079 decode table
fhuizing 9d0610d
Merge branch 'main' into paged_MLA_indexer
fhuizing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
aiter/ops/flydsl/kernels/mqa_logits/_mqa_logits_common.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| # SPDX-License-Identifier: MIT | ||
| # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved. | ||
|
|
||
| """FlyDSL building blocks for the paged FP8 MQA-logits kernel:: | ||
|
|
||
| logits[row, n] = sum_h ReLU(<Q[row, h, :], K[n, :]> * kv_scale[n]) * weights[row, h] | ||
|
|
||
| Split out of ``fp8_paged_mqa_logits`` to keep the gather layouts, the MFMA | ||
| head-reduce and the output view separable from the kernel body. The dense | ||
| ``fp8_mqa_logits`` carries its own copies and does not import this. | ||
| """ | ||
|
|
||
| # No `from __future__ import annotations`: FlyDSL arg typing needs real | ||
|
fhuizing marked this conversation as resolved.
Resolved
|
||
| # annotation objects, not PEP 563 strings. | ||
|
|
||
| from functools import lru_cache | ||
|
|
||
| import flydsl.expr as fx | ||
| import torch | ||
| from flydsl.expr import arith, range_constexpr, rocdl | ||
| from flydsl.expr.numeric import ArithValue | ||
|
fhuizing marked this conversation as resolved.
Resolved
|
||
| from flydsl.expr.typing import T | ||
|
|
||
| from ..tensor_shim import GTensor, _to_raw | ||
|
|
||
| Vec = fx.Vector | ||
|
|
||
| # 32x32x64 scaled fp8 MFMA (CDNA4). A/B are vector<8xi32> per lane (32 fp8 | ||
| # bytes), C/D are 16 f32 D-regs. lane_div_N = lane//32, lane_mod_N = lane%32. | ||
| MFMA_M = 32 | ||
| MFMA_N = 32 | ||
| MFMA_K = 64 | ||
|
|
||
|
|
||
| def _mfma_scale_f32_32x32x64_fp8(result_type, operands): | ||
| """operands: [a, b, c] or [a, b, c, cbsz, blgp, opsel_a, scale_a, opsel_b, scale_b].""" | ||
| # Neutral e8m0 scale: 4 packed bytes of 127 => 2^(127-127) = 1.0 each. | ||
| neutral = arith.constant(0x7F7F7F7F, type=T.i32) | ||
| ops = list(operands) | ||
| if len(ops) < 9: | ||
| while len(ops) < 6: | ||
| ops.append(0) | ||
| ops.extend([neutral, 0, neutral]) | ||
| return rocdl.mfma_scale_f32_32x32x64_f8f6f4(result_type, ops) | ||
|
fhuizing marked this conversation as resolved.
Resolved
|
||
|
|
||
|
|
||
| MFMA_FN = _mfma_scale_f32_32x32x64_fp8 | ||
|
|
||
| DEFAULT_COMPILE_HINTS = { | ||
| "waves_per_eu": 2, | ||
| "fast_fp_math": True, | ||
| } | ||
|
|
||
|
|
||
| # Via Uint32 because `//` on a signed Int32 lowers to floordivsi, which expands | ||
| # to several instructions for negative-operand rounding. Everything divided here | ||
| # is non-negative, so this keeps the single-instruction divui/remui. | ||
| def udiv(a, b): | ||
| return fx.Int32(fx.Uint32(a) // fx.Uint32(b)) | ||
|
|
||
|
|
||
| def umod(a, b): | ||
| return fx.Int32(fx.Uint32(a) % fx.Uint32(b)) | ||
|
|
||
|
|
||
| @lru_cache(maxsize=8) | ||
| def device_cu_count(device_index: int) -> int: | ||
| """Compute-unit count for a CUDA/HIP device (cached); 304 if unavailable.""" | ||
| try: | ||
| return torch.cuda.get_device_properties(device_index).multi_processor_count | ||
| except Exception: # noqa: BLE001 | ||
| return 304 | ||
|
fhuizing marked this conversation as resolved.
Resolved
|
||
|
|
||
|
|
||
| def load_pack_v8i32(i32_view, byte_off_i32, lane8): | ||
| """Load 32 fp8 bytes as the vector<8xi32> A/B operand of the 32x32x64 MFMA. | ||
|
|
||
| Each lane holds 4 K-groups of 8 bytes at ``lane8 + kk*16``. Reads go out as | ||
| 2-dword loads because a ``v8i8`` buffer_load fails to lower on gfx942. | ||
|
fhuizing marked this conversation as resolved.
Resolved
|
||
| """ | ||
|
|
||
| def _load_i64(off): | ||
| v2 = i32_view.vec_load((udiv(byte_off_i32 + lane8 + off, 4),), vec_size=2) | ||
| return Vec(v2).bitcast(fx.Int64)[0].ir_value() | ||
|
|
||
| i64_0 = _load_i64(0) # K-group 0: k = lane_div_N*8 + 0..7 | ||
| i64_1 = _load_i64(16) # K-group 1: k = lane_div_N*8 + 16..23 | ||
| i64_2 = _load_i64(32) # K-group 2: k = lane_div_N*8 + 32..39 | ||
| i64_3 = _load_i64(48) # K-group 3: k = lane_div_N*8 + 48..55 | ||
| return Vec.from_elements([i64_0, i64_1, i64_2, i64_3], fx.Int64).bitcast(fx.Int32) | ||
|
|
||
|
|
||
| def make_kv_key_view(kv_cache, head_size, kv_block_size, index_dim, preshuffle): | ||
| """Dword view of the co-packed KV keys over the gather's own axes: | ||
| ``(block, token//16, token%16, K-step, sub-group, lane half)``. | ||
|
|
||
| The two data layouts differ only in this stride vector -- block-flat puts | ||
| token c at ``c*D``, shuffle_weight(16,16) at ``(c//16)*16D + (c%16)*16`` | ||
| with hidden groups every 256B -- so Preshuffle is a layout, not a second | ||
| gather path. index_dim is a multiple of 4, so the block stride divides. | ||
| """ | ||
| D = head_size | ||
| blk = udiv(fx.Int32(kv_block_size) * index_dim, 4) | ||
| stride = ( | ||
| (blk, D * 4, 4, 256, 64, 2) if preshuffle else (blk, D * 4, D // 4, 16, 4, 2) | ||
| ) | ||
| return GTensor(kv_cache, dtype=T.i32, shape=(-1,) * 6, stride=stride) | ||
|
|
||
|
|
||
| def make_kv_scale_view(kv_cache, head_size, kv_block_size, index_dim): | ||
| """f32 ``(block, token)`` view of the co-packed scales. The tail follows the | ||
| KVB*D key bytes and is token-ordered in both layouts, so it folds into base. | ||
| """ | ||
| return GTensor( | ||
| kv_cache, | ||
| dtype=T.f32, | ||
| shape=(-1, kv_block_size), | ||
| stride=(udiv(fx.Int32(kv_block_size) * index_dim, 4), 1), | ||
| base_offset=kv_block_size * head_size // 4, | ||
| ) | ||
|
|
||
|
|
||
| def load_pack_kv(key_view, physical, tok_in_block, kk_step, lane_div_N): | ||
| """vector<8xi32> B operand for one KV token and K-step. Layout-agnostic: | ||
| the view's strides carry the block-flat/preshuffle difference. | ||
| """ | ||
| c_hi, c_lo = udiv(tok_in_block, 16), umod(tok_in_block, 16) | ||
| # Slice everything but `sub` first: that fixes the token's base offset once | ||
| # instead of re-deriving it inside each of the four loads. | ||
| sub_view = key_view[physical, c_hi, c_lo, kk_step, None, lane_div_N] | ||
|
|
||
| def _load_i64(sub): | ||
| v2 = sub_view.vec_load((sub,), vec_size=2) | ||
| return Vec(v2).bitcast(fx.Int64)[0].ir_value() | ||
|
|
||
| return Vec.from_elements( | ||
| [_load_i64(sub) for sub in range_constexpr(4)], fx.Int64 | ||
| ).bitcast(fx.Int32) | ||
|
|
||
|
|
||
| def make_out_row_view(logits, stride_out, row_i32): | ||
| """1-D output GTensor for ``row_i32``, with the row byte base folded into | ||
| the pointer in i64 so the column offset stays i32. A 2-D (row, col) view | ||
| would compute ``row*stride + col`` in i32 and silently overflow past 2^31. | ||
| """ | ||
| stride_i64 = arith.extui(T.i64, _to_raw(stride_out)) | ||
| _ri64 = arith.extui(T.i64, _to_raw(row_i32)) | ||
| _byte = arith.muli(arith.muli(_ri64, stride_i64), arith.constant(4, type=T.i64)) | ||
| _idx = arith.index_cast(T.index, _byte) | ||
| return GTensor(logits, dtype=T.f32, shape=(-1,), static_bytes_offset_i64=_idx) | ||
|
|
||
|
|
||
| def mfma_head_reduce( | ||
| a_row, | ||
| b_col, | ||
| w_row, | ||
| kv_scale, | ||
| *, | ||
| m_tiles, | ||
| k_steps, | ||
| dreg_count=16, | ||
| fm_fast=arith.FastMathFlags.fast, | ||
| mfma_fn=MFMA_FN, | ||
| ): | ||
| """One column's logit: fp8 MFMA over heads, ReLU * weight sum, kv-scale, | ||
| in-wave head reduce. Returns the reduced f32 ``col_sum`` (raw ir value). | ||
|
|
||
| ``kv_scale`` (>= 0) is applied once to the column sum rather than per head: | ||
| ReLU is positively homogeneous, so ReLU(s*x) = s*ReLU(x) and hoisting it out | ||
| of the head sum is exact. | ||
| """ | ||
| res_ty = Vec.make_type(dreg_count, fx.Float32) | ||
| f32_0 = arith.constant(0.0, type=T.f32) | ||
| col_sum = _to_raw(f32_0) | ||
| for mi in range_constexpr(m_tiles): | ||
| acc = Vec.filled(dreg_count, 0.0, fx.Float32) | ||
| for kk in range_constexpr(k_steps): | ||
| acc = mfma_fn(res_ty, [a_row[mi][kk], b_col[kk], acc, 0, 0, 0]) | ||
| for ii in range_constexpr(dreg_count): | ||
| score = Vec(acc)[ii].ir_value() | ||
| relu = arith.maximumf(score, _to_raw(f32_0)) | ||
| wsc = arith.MulFOp(relu, w_row[mi][ii], fastmath=fm_fast).result | ||
| col_sum = arith.AddFOp(col_sum, wsc, fastmath=fm_fast).result | ||
| col_sum = arith.MulFOp(col_sum, kv_scale, fastmath=fm_fast).result | ||
|
|
||
| # For 32x32 tile: lane_div_N = lane // 32 (0 or 1); shuffle_xor(32) sums them. | ||
| # For 16x16 tile: lane_div_N = lane // 16 (0..3); shuffle_xor(16) + xor(32). | ||
| shuffles = (32,) if dreg_count == 16 else (16, 32) | ||
| for sh in shuffles: | ||
| peer = _to_raw(ArithValue(col_sum).shuffle_xor(sh, 64)) | ||
| col_sum = arith.AddFOp(col_sum, peer, fastmath=fm_fast).result | ||
| return col_sum | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.