Skip to content

Commit a97c1e4

Browse files
committed
feat(ck_tile/fmha): add gfx1100 batch_prefill_gfx11 pipeline policy
gfx1100 had no batch_prefill pipeline: the arch supported qr, qr_pagedkv, qr_nwarp_sshuffle, appendkv and bwd only. qr_async cannot be reused because gfx11 has no parent async dwordx2 K copy, and the fallback gemm1 (ARegBSmem) cannot consume a gfx11 WMMA P tile. Add BlockFmhaBatchPrefillPipelineQRKSVSAsyncGfx11Policy: - synchronous K staging (kUseSyncKLoad) instead of async copy - vec4 K packing on N0=32 / K0=32 linear tiles - Independent-V gemm1 (ARegBReg plus a tiled V LDS layout) - P permuted from WMMA C layout to A layout before gemm1 Enabled surface is deliberately narrow: block size 256, tile 128x32x32, hdim 128, linear page KV and no dropout. All other tiles, head dims, dtypes and architectures stay on the existing pipeline, unchanged. Codegen emits exactly that set (192 instances), validation rejects anything outside it with a named error, and the pipeline carries a device-side static_assert so the two gates cannot drift apart. The tile engine harness needed three fixes to exercise the path: the ctypes runner zeroed the paged K/V buffers instead of copying the host pages, it hardcoded window_size_right=-1 so causal masks did not mask, and the Python side passed dense BHSD without the linear page pack or the Q/O transpose. fmha_benchmark.py now also exits non-zero when kernels fail to build or verification fails; previously both printed the failure and exited 0.
1 parent f585988 commit a97c1e4

13 files changed

Lines changed: 753 additions & 43 deletions

File tree

projects/composablekernel/dispatcher/bindings/ctypes/fmha_ctypes_lib.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,8 +1552,16 @@ int fmha_dispatcher_run_batch_prefill(const void* q_host,
15521552
}
15531553

15541554
HIP_CHECK(hipMemcpy(q_dev, q_host, q_bytes, hipMemcpyHostToDevice));
1555-
HIP_CHECK(hipMemset(k_dev, 0, kv_page_bytes));
1556-
HIP_CHECK(hipMemset(v_dev, 0, kv_page_bytes));
1555+
{
1556+
const int64_t k_src_bytes =
1557+
static_cast<int64_t>(total_pages) * nhead_k * page_block_size * hdim_q * in_bytes;
1558+
const int64_t v_src_bytes =
1559+
static_cast<int64_t>(total_pages) * nhead_k * page_block_size * hdim_v * in_bytes;
1560+
HIP_CHECK(hipMemset(k_dev, 0, kv_page_bytes));
1561+
HIP_CHECK(hipMemset(v_dev, 0, kv_page_bytes));
1562+
HIP_CHECK(hipMemcpy(k_dev, k_host, k_src_bytes, hipMemcpyHostToDevice));
1563+
HIP_CHECK(hipMemcpy(v_dev, v_host, v_src_bytes, hipMemcpyHostToDevice));
1564+
}
15571565
HIP_CHECK(hipMemset(o_dev, 0, o_bytes));
15581566

15591567
args.q_ptr = q_dev;
@@ -1613,14 +1621,15 @@ int fmha_dispatcher_run_batch_prefill(const void* q_host,
16131621
args.batch_stride_randval = 0;
16141622
args.batch_stride_lse = static_cast<int64_t>(nhead_q) * seqlen_q;
16151623
args.batch_stride_o = 0;
1616-
args.window_size_left = -1;
1617-
args.window_size_right = -1;
1618-
args.sink_size = 0;
1619-
args.mask_type = mask_type_int;
1620-
args.p_drop = has_dropout ? 0.2f : 0.0f;
1621-
args.s_randval = false;
1622-
args.drop_seed_offset = has_dropout ? std::make_pair(uint64_t(1), uint64_t(0))
1623-
: std::make_pair(uint64_t(0), uint64_t(0));
1624+
// right=-1 is a full row, so causal (top_left=1 / bottom_right=2) needs right=0.
1625+
args.window_size_left = -1;
1626+
args.window_size_right = (mask_type_int == 1 || mask_type_int == 2) ? 0 : -1;
1627+
args.sink_size = 0;
1628+
args.mask_type = mask_type_int;
1629+
args.p_drop = has_dropout ? 0.2f : 0.0f;
1630+
args.s_randval = false;
1631+
args.drop_seed_offset = has_dropout ? std::make_pair(uint64_t(1), uint64_t(0))
1632+
: std::make_pair(uint64_t(0), uint64_t(0));
16241633

16251634
try
16261635
{

projects/composablekernel/dispatcher/codegen/fmha/codegen.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,16 @@ def _batch_prefill_kernel_body(name: str, config: dict) -> str:
753753
warp = alg["warp"]
754754
pad = alg["padding"]
755755
ns = f"ns_{name}"
756+
pipeline_policy = (
757+
", ck_tile::BlockFmhaBatchPrefillPipelineQRKSVSAsyncGfx11Policy"
758+
if alg["pipeline"] == "batch_prefill_gfx11"
759+
else ""
760+
)
761+
pipeline_enum_key = (
762+
"batch_prefill_gfx11"
763+
if alg["pipeline"] == "batch_prefill_gfx11"
764+
else "batch_prefill_async"
765+
)
756766
return f"""// SPDX-License-Identifier: MIT
757767
#pragma once
758768
@@ -808,7 +818,7 @@ def _batch_prefill_kernel_body(name: str, config: dict) -> str:
808818
false,
809819
{sig["page_size"]},
810820
fmha_trait>;
811-
using fmha_pipeline = ck_tile::BlockFmhaBatchPrefillPipelineQRKSVSAsync<fmha_pipeline_problem>;
821+
using fmha_pipeline = ck_tile::BlockFmhaBatchPrefillPipelineQRKSVSAsync<fmha_pipeline_problem{pipeline_policy}>;
812822
using fmha_epilogue = ck_tile::Default2DEpilogue<
813823
ck_tile::Default2DEpilogueProblem<typename FmhaFwdTypeConfig<fmha_dtype>::OaccDataType,
814824
typename FmhaFwdTypeConfig<fmha_dtype>::ODataType,
@@ -821,7 +831,7 @@ def _batch_prefill_kernel_body(name: str, config: dict) -> str:
821831
{mode_cpp},
822832
{tile[0]}, {tile[1]}, {tile[2]}, {tile[3]}, {tile[4]}, {tile[5]},
823833
{vlayout_cpp},
824-
{PIPELINE_ENUM_TO_CPP["batch_prefill_async"]},
834+
{PIPELINE_ENUM_TO_CPP[pipeline_enum_key]},
825835
{_bool_cpp(sig["logits"])},
826836
fmha_mask,
827837
{_bias_cpp(sig["bias"])},

projects/composablekernel/dispatcher/codegen/fmha/fmha_arch_specs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"family": "rdna3",
3232
"arch_tag": "ck_tile::gfx11_t",
3333
"supported_dtypes": ["fp16", "bf16"],
34-
"supported_pipelines": ["qr", "qr_pagedkv", "qr_nwarp_sshuffle", "appendkv", "bwd"],
34+
"supported_pipelines": ["qr", "qr_pagedkv", "qr_nwarp_sshuffle", "appendkv", "batch_prefill_gfx11", "bwd"],
3535
"supports_trload": false,
3636
"supports_v3": false
3737
},

projects/composablekernel/dispatcher/codegen/fmha/instance_gen.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,8 @@ def bwd_dq_wave_warp(tile, hq, trload=False):
14571457
_MASK_MAP = {"no": "no", "causal": "top_left", "generic": "generic"}
14581458
_BIAS_MAP = {"no": "no", "bias": "bias", "alibi": "alibi"}
14591459

1460+
GFX11_WARP_SIZE = 32
1461+
14601462

14611463
def _pad_val(s: str) -> int:
14621464
if s == "f":
@@ -2392,17 +2394,32 @@ def _bp_bk1(bm0, bn0, bk0, hq):
23922394
if restrict_hdims is not None:
23932395
hdims = [hv for hv in hdims if hv in restrict_hdims]
23942396
for hq, hv in hdims:
2397+
# gfx1100 must stay inside Gfx11Policy::UseIndependentVBuffer; the
2398+
# fallback gemm1 does not compile against gfx11 WMMA.
2399+
if arch == "gfx1100" and (hq, hv) != (128, 128):
2400+
continue
23952401
tiles = generate_splitkv_tiles(arch, dtype, hq, hv)
23962402
bp_specs = get_batch_prefill_pipelines(dtype, hq, receipt)
23972403
for tc in tiles:
23982404
bk1 = _bp_bk1(tc.bm0, tc.bn0, tc.bk0, hq)
2405+
if arch == "gfx1100" and (
2406+
tc.rm0 * GFX11_WARP_SIZE != 256
2407+
or tc.bn0 != 32
2408+
or tc.bn1 != 128
2409+
or bk1 != 32
2410+
):
2411+
continue
23992412

24002413
# qr_async stages K into LDS through a bk1-major descriptor while the gemm0
24012414
# loop reads bk0 chunks, therefore the pipeline requires bk0 == bk1
24022415
if tc.bk0 != bk1:
24032416
continue
24042417

24052418
for spec in bp_specs:
2419+
if arch == "gfx1100" and (
2420+
spec.kv_memory_layout != "linear" or spec.dropout == "t"
2421+
):
2422+
continue
24062423
mm = _MASK_MAP.get(spec.mask, spec.mask)
24072424
mb = _BIAS_MAP.get(spec.bias, spec.bias)
24082425
if allowed_masks is not None and mm not in allowed_masks:
@@ -2421,7 +2438,9 @@ def _bp_bk1(bm0, bn0, bk0, hq):
24212438
mode="group",
24222439
hdim_q=hq,
24232440
hdim_v=hv,
2424-
pipeline="qr_async",
2441+
pipeline="batch_prefill_gfx11"
2442+
if arch == "gfx1100"
2443+
else "qr_async",
24252444
tile_m0=tc.bm0,
24262445
tile_n0=tc.bn0,
24272446
tile_k0=tc.bk0,

projects/composablekernel/dispatcher/codegen/fmha/symbol_map.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
"qr_nwarp_sshuffle": "ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS",
241241
"appendkv": "ck_tile::BlockFmhaFwdAppendKVPipeline",
242242
"batch_prefill_async": "ck_tile::BlockFmhaBatchPrefillPipelineQRKSVSAsync",
243+
"batch_prefill_gfx11": "ck_tile::BlockFmhaBatchPrefillPipelineQRKSVSAsync",
243244
}
244245

245246
# Pipeline tag → C++ pipeline enum value.
@@ -254,6 +255,7 @@
254255
"qr_pagedkv": "ck_tile::BlockFmhaPipelineEnum::QRKSVS",
255256
"qr_nwarp_sshuffle": "ck_tile::BlockFmhaPipelineEnum::QRKSVS",
256257
"batch_prefill_async": "ck_tile::BlockFmhaPipelineEnum::QRKSVS_ASYNC",
258+
"batch_prefill_gfx11": "ck_tile::BlockFmhaPipelineEnum::QRKSVS_ASYNC",
257259
}
258260

259261
BOOL_MAP = {

projects/composablekernel/dispatcher/codegen/fmha/validation.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,8 +881,34 @@ def validate_config(
881881
result.add_error("batch_prefill page_size must be a positive power of two")
882882
if sig.get("mode", "batch") != "group":
883883
result.add_error("batch_prefill requires group mode")
884-
if pipeline != "qr_async":
885-
result.add_error("batch_prefill currently uses qr_async pipeline")
884+
if pipeline not in {"qr_async", "batch_prefill_gfx11"}:
885+
result.add_error(
886+
"batch_prefill currently uses qr_async or batch_prefill_gfx11 pipeline"
887+
)
888+
if arch == "gfx1100" and pipeline != "batch_prefill_gfx11":
889+
result.add_error(
890+
"gfx1100 batch_prefill must use batch_prefill_gfx11 policy"
891+
)
892+
if pipeline == "batch_prefill_gfx11":
893+
# Mirror of Gfx11Policy::UseIndependentVBuffer. Outside this
894+
# predicate gemm1 falls back to a shape gfx11 WMMA cannot consume.
895+
if arch != "gfx1100":
896+
result.add_error("batch_prefill_gfx11 is only supported on gfx1100")
897+
if sig.get("kv_memory_layout") != "linear":
898+
result.add_error(
899+
"batch_prefill_gfx11 currently supports only linear KV layout"
900+
)
901+
if (hdim_q, hdim_v) != (128, 128):
902+
result.add_error("batch_prefill_gfx11 currently supports only hdim 128")
903+
if sig.get("dropout", False):
904+
result.add_error("batch_prefill_gfx11 does not support dropout")
905+
wave = alg["wave"]
906+
if len(wave) >= 3 and wave[0] * wave[1] * wave[2] * 32 != 256:
907+
result.add_error("batch_prefill_gfx11 requires block size 256")
908+
if len(tile) >= 5 and (tile[1], tile[3], tile[4]) != (32, 128, 32):
909+
result.add_error(
910+
"batch_prefill_gfx11 requires tile N0=32, N1=128, K1=32"
911+
)
886912

887913
if family == "fwd_appendkv":
888914
if sig.get("mode", "batch") != "batch":

projects/composablekernel/dispatcher/python/fmha_utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,79 @@ def run(
840840
Q_c = np.ascontiguousarray(Q.astype(in_dt))
841841
K_c = np.ascontiguousarray(K.astype(in_dt))
842842
V_c = np.ascontiguousarray(V.astype(in_dt))
843+
if api_family == "batch_prefill":
844+
page_size = kwargs.get("page_size", 64)
845+
kv_layout = kwargs.get("kv_layout", 0)
846+
847+
def _pack_linear_paged(x):
848+
bsz, heads, seqlen, hdim = x.shape
849+
pages_per_seq = (seqlen + page_size - 1) // page_size
850+
packed = np.zeros(
851+
(bsz * pages_per_seq, heads, page_size, hdim), dtype=x.dtype
852+
)
853+
for batch_id in range(bsz):
854+
for page_id in range(pages_per_seq):
855+
begin = page_id * page_size
856+
end = min(begin + page_size, seqlen)
857+
packed[
858+
batch_id * pages_per_seq + page_id, :, : end - begin, :
859+
] = x[batch_id, :, begin:end, :]
860+
return np.ascontiguousarray(packed.reshape(-1))
861+
862+
if kv_layout != 1:
863+
return FmhaResult(
864+
success=False,
865+
error="batch_prefill runner packs linear KV only",
866+
)
867+
K_c = _pack_linear_paged(K_c)
868+
V_c = _pack_linear_paged(V_c)
869+
Q_api = np.ascontiguousarray(np.transpose(Q_c, (0, 2, 1, 3)))
870+
O_api = np.zeros(
871+
(prob.batch, prob.seqlen_q, prob.nhead_q, prob.hdim_v), dtype=out_dt
872+
)
873+
time_ms = ctypes.c_float(0.0)
874+
rc = self._lib._lib.fmha_dispatcher_run_batch_prefill(
875+
ctypes.c_void_p(Q_api.ctypes.data),
876+
ctypes.c_void_p(K_c.ctypes.data),
877+
ctypes.c_void_p(V_c.ctypes.data),
878+
ctypes.c_void_p(O_api.ctypes.data),
879+
prob.batch,
880+
prob.nhead_q,
881+
prob.nhead_k,
882+
prob.seqlen_q,
883+
prob.seqlen_k,
884+
prob.hdim_q,
885+
prob.hdim_v,
886+
ctypes.c_float(prob.scale),
887+
mask_type,
888+
bias_type,
889+
page_size,
890+
kv_layout,
891+
kwargs.get("kv_lookup", 0),
892+
kwargs.get("is_v_rowmajor", 1),
893+
data_type.encode(),
894+
has_lse,
895+
has_dropout,
896+
has_logits,
897+
has_sink,
898+
has_skip,
899+
ctypes.byref(time_ms),
900+
)
901+
if rc != 0:
902+
return FmhaResult(success=False, error=f"Kernel failed (rc={rc})")
903+
ops = prob.num_ops
904+
tflops = (
905+
ops / (time_ms.value * 1e-3) / 1e12
906+
if time_ms.value > 0 and ops > 0
907+
else 0.0
908+
)
909+
O_c = np.ascontiguousarray(np.transpose(O_api, (0, 2, 1, 3)))
910+
if data_type == "bf16":
911+
O_c = _bf16_to_float32(O_c)
912+
return FmhaResult(
913+
success=True, output=O_c, time_ms=time_ms.value, tflops=tflops
914+
)
915+
843916
O_c = np.zeros(prob.o_shape(), dtype=out_dt)
844917

845918
d_q, d_k, d_v, d_o = (ctypes.c_void_p() for _ in range(4))

projects/composablekernel/dispatcher/tests/test_fmha_codegen.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,52 @@ def test_batch_prefill_requires_group_mode(self):
158158
self.assertFalse(result.valid)
159159
self.assertTrue(any("group mode" in error for error in result.errors))
160160

161+
def test_gfx1100_batch_prefill_codegen_emits_gfx11_policy(self):
162+
config = sample_config(
163+
arch="gfx1100",
164+
signature={
165+
"family": "batch_prefill",
166+
"mode": "group",
167+
"paged_kv": True,
168+
"page_size": 16,
169+
"kv_memory_layout": "linear",
170+
"kv_lookup_table": "vllm",
171+
},
172+
algorithm={
173+
"pipeline": "batch_prefill_gfx11",
174+
"tile": [128, 32, 32, 128, 32, 128],
175+
"wave": [8, 1, 1, 8, 1, 1, 1, 1, 1],
176+
"warp": [16, 16, 16, 16, 16, 16, 16, 16, 16],
177+
},
178+
)
179+
result = validate_config(config)
180+
self.assertTrue(result.valid, result.errors)
181+
with tempfile.TemporaryDirectory() as tmpdir:
182+
cmd = [
183+
sys.executable,
184+
str(CODEGEN),
185+
"--output-dir",
186+
tmpdir,
187+
"--gpu-target",
188+
"gfx1100",
189+
"--config-json",
190+
json.dumps(config),
191+
]
192+
proc = subprocess.run(
193+
cmd, capture_output=True, text=True, cwd=str(ROOT / "codegen")
194+
)
195+
self.assertEqual(proc.returncode, 0, msg=proc.stderr or proc.stdout)
196+
generated = list(Path(tmpdir).glob("fmha_*.hpp"))
197+
self.assertEqual(len(generated), 1)
198+
text = generated[0].read_text()
199+
self.assertIn("BlockFmhaBatchPrefillPipelineQRKSVSAsyncGfx11Policy", text)
200+
self.assertNotIn("QRKSVSAsyncDefaultPolicy", text)
201+
# kHasSink occupies the bool slot immediately before kPageBlockSize.
202+
self.assertRegex(
203+
text,
204+
r"TileFmhaBatchPrefillTraits<[\s\S]*false,\s*16,",
205+
)
206+
161207
def _batch_prefill_traits_args(self, sink=False):
162208
config = sample_config(
163209
arch="gfx950",

projects/composablekernel/dispatcher/tests/test_fmha_dispatcher.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,52 @@ TEST(FmhaDispatcherTest, PlansSingleStageFwd)
159159
EXPECT_EQ(plan.stages[0].family, FmhaKernelFamily::Fwd);
160160
}
161161

162+
TEST(FmhaDispatcherTest, PlansSingleStageBatchPrefillOnGfx1100)
163+
{
164+
FmhaRegistry registry;
165+
registry.register_kernel(std::make_shared<MockFmhaKernel>(
166+
make_key(FmhaKernelFamily::BatchPrefill, "prefill"), "prefill"));
167+
168+
FmhaDispatcher dispatcher(&registry);
169+
170+
fmha_batch_prefill_traits traits{};
171+
traits.hdim_q = 128;
172+
traits.hdim_v = 128;
173+
traits.data_type = "fp16";
174+
traits.is_group_mode = true;
175+
traits.is_v_rowmajor = true;
176+
traits.mask_type = mask_enum::no_mask;
177+
traits.bias_type = bias_enum::no_bias;
178+
traits.has_lse = true;
179+
traits.kv_memory_layout = ck_tile::BlockAttentionKVCacheMemoryLayoutEnum::LINEAR_LAYOUT;
180+
traits.kv_lookup_table = ck_tile::BlockAttentionKVCacheLookupTableEnum::VLLM_BLOCK_TABLE_2D;
181+
traits.page_size = 16;
182+
183+
fmha_batch_prefill_args args{};
184+
args.batch = 1;
185+
args.seqlen_q = 128;
186+
args.seqlen_k = 1024;
187+
args.max_seqlen_q = 128;
188+
args.hdim_q = 128;
189+
args.hdim_v = 128;
190+
args.nhead_q = 8;
191+
args.nhead_k = 8;
192+
args.num_total_pages = 1024;
193+
args.page_block_size = 16;
194+
args.kv_memory_layout = ck_tile::BlockAttentionKVCacheMemoryLayoutEnum::LINEAR_LAYOUT;
195+
args.kv_lookup_table = ck_tile::BlockAttentionKVCacheLookupTableEnum::VLLM_BLOCK_TABLE_2D;
196+
args.kv_indptr = reinterpret_cast<void*>(0x1);
197+
args.kv_page_indices = reinterpret_cast<void*>(0x1);
198+
args.kv_last_page_lens = reinterpret_cast<void*>(0x1);
199+
args.seqstart_q_ptr = reinterpret_cast<void*>(0x1);
200+
201+
auto problem = FmhaProblem::from_invocation(FmhaInvocation::make(traits, args), "gfx1100");
202+
auto plan = dispatcher.plan(problem);
203+
ASSERT_TRUE(plan.is_valid());
204+
ASSERT_EQ(plan.stages.size(), 1u);
205+
EXPECT_EQ(plan.stages[0].family, FmhaKernelFamily::BatchPrefill);
206+
}
207+
162208
TEST(FmhaDispatcherTest, PlansSingleStagePagedKv)
163209
{
164210
FmhaRegistry registry;

0 commit comments

Comments
 (0)