Skip to content

Commit 28b0092

Browse files
authored
Optimize wna16 MoE main loop: one mma per K tile instead of eight (flagos-ai#5140)
Concatenate the sub-tiles along K instead and issue a single mma over the whole tile, with the activation loaded once as a full tile. _concat_k builds the ordered concatenation from a balanced tl.join tree plus a permute and a reshape.
1 parent 34b0d5e commit 28b0092

1 file changed

Lines changed: 73 additions & 52 deletions

File tree

src/flag_gems/fused/fused_marlin_moe.py

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,25 @@ def _dequant_fp4_bf16_fold(b, cs0, cs1, cs2, cs3):
691691
return x1, x2, x3, x4, x5, x6, x7, x8
692692

693693

694+
@triton.jit
695+
def _concat_k(bs, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr, SWAP_AB: tl.constexpr):
696+
# Concat the 8 dequant outputs along K; permute puts the sub-tile index ahead
697+
# of the in-tile K coord so the reshape flattens to k = K_PACK * j + kp.
698+
j0 = tl.join(bs[0], bs[1])
699+
j1 = tl.join(bs[2], bs[3])
700+
j2 = tl.join(bs[4], bs[5])
701+
j3 = tl.join(bs[6], bs[7])
702+
p0 = tl.join(j0, j1)
703+
p1 = tl.join(j2, j3)
704+
q = tl.join(p0, p1)
705+
if SWAP_AB:
706+
# bs[j] is (BLOCK_N, K_PACK) -> (BLOCK_N, 2, 2, 2, K_PACK) -> (BLOCK_N, BLOCK_K)
707+
return tl.reshape(tl.permute(q, (0, 4, 3, 2, 1)), (BLOCK_N, BLOCK_K))
708+
else:
709+
# bs[j] is (K_PACK, BLOCK_N) -> (2, 2, 2, K_PACK, BLOCK_N) -> (BLOCK_K, BLOCK_N)
710+
return tl.reshape(tl.permute(q, (4, 3, 2, 0, 1)), (BLOCK_K, BLOCK_N))
711+
712+
694713
@triton.jit
695714
def _dequant_fp4_fp16(b, s0, s1, s2, s3):
696715
x1, x2, x3, x4, x5, x6, x7, x8 = tl.inline_asm_elementwise(
@@ -904,7 +923,6 @@ def _w4a16_moe_gemm_kernel(
904923
return
905924

906925
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
907-
offs_ak_pack = tl.arange(0, BLOCK_SIZE_K_PACK)
908926
offs_bk = tl.arange(0, BLOCK_SIZE_K_PACK)
909927

910928
if SWAP_AB:
@@ -939,20 +957,18 @@ def _w4a16_moe_gemm_kernel(
939957
bs = _dequant_int4_bf16(b_packed, scale_bc)
940958

941959
k_logical_base = k * BLOCK_SIZE_K
942-
for j in tl.static_range(8):
943-
k_off = k_logical_base + j * BLOCK_SIZE_K_PACK
944-
if SWAP_AB:
945-
a_j_ptrs = a_base + (k_off + offs_ak_pack[:, None]) * stride_ak
946-
a_j = tl.load(
947-
a_j_ptrs, mask=token_mask[None, :], other=0.0
948-
) # (K_PACK, M)
949-
accumulator = tl.dot(bs[j], a_j, acc=accumulator) # (N, M)
950-
else:
951-
a_j_ptrs = a_base + (k_off + offs_ak_pack[None, :]) * stride_ak
952-
a_j = tl.load(
953-
a_j_ptrs, mask=token_mask[:, None], other=0.0
954-
) # (M, K_PACK)
955-
accumulator = tl.dot(a_j, bs[j], acc=accumulator) # (M, N)
960+
# One mma over the whole K tile; the activation is loaded once as a
961+
# full tile instead of once per sub-tile.
962+
bs_full = _concat_k(bs, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
963+
offs_ak_full = tl.arange(0, BLOCK_SIZE_K)
964+
if SWAP_AB:
965+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[:, None]) * stride_ak
966+
a_full = tl.load(a_full_ptrs, mask=token_mask[None, :], other=0.0) # (K, M)
967+
accumulator = tl.dot(bs_full, a_full, acc=accumulator) # (N, M)
968+
else:
969+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[None, :]) * stride_ak
970+
a_full = tl.load(a_full_ptrs, mask=token_mask[:, None], other=0.0) # (M, K)
971+
accumulator = tl.dot(a_full, bs_full, acc=accumulator) # (M, N)
956972

957973
b_ptrs += BLOCK_SIZE_K_PACK * stride_bk
958974

@@ -1080,7 +1096,6 @@ def _w4a16_moe_gemm_silu_kernel(
10801096

10811097
offs_bn_gate = offs_cn % N
10821098
offs_bn_up = offs_bn_gate + N
1083-
offs_ak_pack = tl.arange(0, BLOCK_SIZE_K_PACK)
10841099
offs_bk = tl.arange(0, BLOCK_SIZE_K_PACK)
10851100

10861101
if SWAP_AB:
@@ -1136,18 +1151,21 @@ def _w4a16_moe_gemm_silu_kernel(
11361151
bs_up = _dequant_int4_bf16(b_packed_up, scale_up_bc)
11371152

11381153
k_logical_base = k * BLOCK_SIZE_K
1139-
for j in tl.static_range(8):
1140-
k_off = k_logical_base + j * BLOCK_SIZE_K_PACK
1141-
if SWAP_AB:
1142-
a_j_ptrs = a_base + (k_off + offs_ak_pack[:, None]) * stride_ak
1143-
a_j = tl.load(a_j_ptrs, mask=token_mask[None, :], other=0.0)
1144-
acc_gate = tl.dot(bs_gate[j], a_j, acc=acc_gate)
1145-
acc_up = tl.dot(bs_up[j], a_j, acc=acc_up)
1146-
else:
1147-
a_j_ptrs = a_base + (k_off + offs_ak_pack[None, :]) * stride_ak
1148-
a_j = tl.load(a_j_ptrs, mask=token_mask[:, None], other=0.0)
1149-
acc_gate = tl.dot(a_j, bs_gate[j], acc=acc_gate)
1150-
acc_up = tl.dot(a_j, bs_up[j], acc=acc_up)
1154+
# gate and up each get one mma; the activation is loaded once and
1155+
# shared by both.
1156+
bs_gate_full = _concat_k(bs_gate, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
1157+
bs_up_full = _concat_k(bs_up, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
1158+
offs_ak_full = tl.arange(0, BLOCK_SIZE_K)
1159+
if SWAP_AB:
1160+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[:, None]) * stride_ak
1161+
a_full = tl.load(a_full_ptrs, mask=token_mask[None, :], other=0.0)
1162+
acc_gate = tl.dot(bs_gate_full, a_full, acc=acc_gate)
1163+
acc_up = tl.dot(bs_up_full, a_full, acc=acc_up)
1164+
else:
1165+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[None, :]) * stride_ak
1166+
a_full = tl.load(a_full_ptrs, mask=token_mask[:, None], other=0.0)
1167+
acc_gate = tl.dot(a_full, bs_gate_full, acc=acc_gate)
1168+
acc_up = tl.dot(a_full, bs_up_full, acc=acc_up)
11511169

11521170
b_ptrs_gate += BLOCK_SIZE_K_PACK * stride_bk
11531171
b_ptrs_up += BLOCK_SIZE_K_PACK * stride_bk
@@ -1560,7 +1578,6 @@ def _mxfp4_moe_gemm_kernel(
15601578
return
15611579

15621580
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
1563-
offs_ak_pack = tl.arange(0, BLOCK_SIZE_K_PACK)
15641581
offs_bk = tl.arange(0, BLOCK_SIZE_K_PACK)
15651582

15661583
if SWAP_AB:
@@ -1605,16 +1622,18 @@ def _mxfp4_moe_gemm_kernel(
16051622
bs = _dequant_fp4_bf16(b_packed, s0, s1, s2, s3)
16061623

16071624
k_logical_base = k * BLOCK_SIZE_K
1608-
for j in tl.static_range(8):
1609-
k_off = k_logical_base + j * BLOCK_SIZE_K_PACK
1610-
if SWAP_AB:
1611-
a_j_ptrs = a_base + (k_off + offs_ak_pack[:, None]) * stride_ak
1612-
a_j = tl.load(a_j_ptrs, mask=token_mask[None, :], other=0.0)
1613-
accumulator = tl.dot(bs[j], a_j, acc=accumulator)
1614-
else:
1615-
a_j_ptrs = a_base + (k_off + offs_ak_pack[None, :]) * stride_ak
1616-
a_j = tl.load(a_j_ptrs, mask=token_mask[:, None], other=0.0)
1617-
accumulator = tl.dot(a_j, bs[j], acc=accumulator)
1625+
# One mma over the whole K tile; the activation is loaded once as a
1626+
# full tile instead of once per sub-tile.
1627+
bs_full = _concat_k(bs, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
1628+
offs_ak_full = tl.arange(0, BLOCK_SIZE_K)
1629+
if SWAP_AB:
1630+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[:, None]) * stride_ak
1631+
a_full = tl.load(a_full_ptrs, mask=token_mask[None, :], other=0.0) # (K, M)
1632+
accumulator = tl.dot(bs_full, a_full, acc=accumulator) # (N, M)
1633+
else:
1634+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[None, :]) * stride_ak
1635+
a_full = tl.load(a_full_ptrs, mask=token_mask[:, None], other=0.0) # (M, K)
1636+
accumulator = tl.dot(a_full, bs_full, acc=accumulator) # (M, N)
16181637

16191638
b_ptrs += BLOCK_SIZE_K_PACK * stride_bk
16201639

@@ -1719,7 +1738,6 @@ def _mxfp4_moe_gemm_silu_kernel(
17191738

17201739
offs_bn_gate = offs_cn % N
17211740
offs_bn_up = offs_bn_gate + N
1722-
offs_ak_pack = tl.arange(0, BLOCK_SIZE_K_PACK)
17231741
offs_bk = tl.arange(0, BLOCK_SIZE_K_PACK)
17241742

17251743
if SWAP_AB:
@@ -1808,18 +1826,21 @@ def _mxfp4_moe_gemm_silu_kernel(
18081826
bs_up = _dequant_fp4_bf16(b_packed_up, su0, su1, su2, su3)
18091827

18101828
k_logical_base = k * BLOCK_SIZE_K
1811-
for j in tl.static_range(8):
1812-
k_off = k_logical_base + j * BLOCK_SIZE_K_PACK
1813-
if SWAP_AB:
1814-
a_j_ptrs = a_base + (k_off + offs_ak_pack[:, None]) * stride_ak
1815-
a_j = tl.load(a_j_ptrs, mask=token_mask[None, :], other=0.0)
1816-
acc_gate = tl.dot(bs_gate[j], a_j, acc=acc_gate)
1817-
acc_up = tl.dot(bs_up[j], a_j, acc=acc_up)
1818-
else:
1819-
a_j_ptrs = a_base + (k_off + offs_ak_pack[None, :]) * stride_ak
1820-
a_j = tl.load(a_j_ptrs, mask=token_mask[:, None], other=0.0)
1821-
acc_gate = tl.dot(a_j, bs_gate[j], acc=acc_gate)
1822-
acc_up = tl.dot(a_j, bs_up[j], acc=acc_up)
1829+
# gate and up each get one mma; the activation is loaded once and
1830+
# shared by both.
1831+
bs_gate_full = _concat_k(bs_gate, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
1832+
bs_up_full = _concat_k(bs_up, BLOCK_SIZE_N, BLOCK_SIZE_K, SWAP_AB)
1833+
offs_ak_full = tl.arange(0, BLOCK_SIZE_K)
1834+
if SWAP_AB:
1835+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[:, None]) * stride_ak
1836+
a_full = tl.load(a_full_ptrs, mask=token_mask[None, :], other=0.0)
1837+
acc_gate = tl.dot(bs_gate_full, a_full, acc=acc_gate)
1838+
acc_up = tl.dot(bs_up_full, a_full, acc=acc_up)
1839+
else:
1840+
a_full_ptrs = a_base + (k_logical_base + offs_ak_full[None, :]) * stride_ak
1841+
a_full = tl.load(a_full_ptrs, mask=token_mask[:, None], other=0.0)
1842+
acc_gate = tl.dot(a_full, bs_gate_full, acc=acc_gate)
1843+
acc_up = tl.dot(a_full, bs_up_full, acc=acc_up)
18231844

18241845
b_ptrs_gate += BLOCK_SIZE_K_PACK * stride_bk
18251846
b_ptrs_up += BLOCK_SIZE_K_PACK * stride_bk

0 commit comments

Comments
 (0)