Skip to content

Commit d5eacf6

Browse files
committed
removed LDS staging while storing C
Signed-off-by: xintin <gaurav.verma@amd.com>
1 parent db23dbb commit d5eacf6

4 files changed

Lines changed: 112 additions & 365 deletions

File tree

wave_lang/kernel/compiler/wave_codegen/read_write.py

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,10 +1149,17 @@ def handle_write(emitter: WaveEmitter, node: fx.Node):
11491149
is_shared = get_custom(memory).type.address_space == SHARED_ADDRESS_SPACE
11501150
is_bf16 = isinstance(element_type, BF16Type)
11511151

1152-
# TODO: Replace _shuffle_xor_pack with a formal Write node attribute.
1153-
if is_shared and is_bf16 and getattr(node, "_shuffle_xor_pack", False):
1154-
_write_shuffle_xor16_b128_to_lds(
1155-
emitter, insert_vector, kb_dest, start_indices_th
1152+
if not is_shared and is_bf16 and getattr(node, "_permlane_pack_global", False):
1153+
_write_permlane_pack_to_global(
1154+
emitter,
1155+
insert_vector,
1156+
kb_dest,
1157+
output_shape,
1158+
start_indices,
1159+
start_indices_wg,
1160+
start_indices_th,
1161+
get_custom(memory),
1162+
index,
11561163
)
11571164
return
11581165

@@ -1177,68 +1184,113 @@ def handle_write(emitter: WaveEmitter, node: fx.Node):
11771184
)
11781185

11791186

1180-
def _write_shuffle_xor16_b128_to_lds(
1187+
def _write_permlane_pack_to_global(
11811188
emitter: WaveEmitter,
11821189
insert_vector: Value,
1183-
mem: Value,
1190+
kb_dest: Value,
1191+
output_shape: tuple,
1192+
start_indices: tuple,
1193+
start_indices_wg: tuple,
11841194
start_indices_th: tuple,
1195+
memory_custom,
1196+
index: dict,
11851197
):
1186-
"""Combine own 4 bf16 with partner lane's 4 bf16 via shuffle XOR 16,
1187-
producing a vector<4xi32> (8 packed bf16) written as ds_write_b128.
1198+
"""Pack two lanes' bf16 values via permlane16_swap for wide global stores.
11881199
11891200
MMA accumulator layout (F32_16x16x128_F8F6F4) gives each thread 4
1190-
consecutive M values. Lanes are grouped by 16: lanes 0-15 own M=0-3,
1191-
lanes 16-31 own M=4-7, etc. Shuffle XOR 16 exchanges between paired
1192-
groups, giving each lane 8 consecutive M values.
1201+
consecutive M values. Lanes are grouped by 16: lanes 0-15 own M=0-3,
1202+
lanes 16-31 own M=4-7, etc. ``v_permlane16_swap_b32`` exchanges data
1203+
between paired groups, giving each lane 8 consecutive M values that
1204+
can be written as a single ``buffer_store_dwordx4`` (128 bits).
1205+
1206+
Both lane halves produce identical data at the same address (benign
1207+
duplicate store):
1208+
1209+
- Lower half (lanes 0-15 in each 32-lane group):
1210+
data = [own, partner], address = thread's original M index.
1211+
- Upper half (lanes 16-31):
1212+
data = [partner, own], address = original M index - 4.
11931213
1194-
Both lane halves write: the lower half uses [own, partner] ordering,
1195-
the upper half uses [partner, own] with an adjusted address, so both
1196-
produce identical data at the same LDS location.
1214+
This dual-write avoids divergent control flow (no scf.if / exec
1215+
masking needed). The buffer descriptor's ``valid_bytes`` handles
1216+
out-of-bounds suppression for dynamic shapes.
1217+
1218+
Precondition: M must be the innermost (last) memory dimension with
1219+
stride 1 (i.e. transpose_output=True, shape [N, M]).
11971220
"""
11981221
bf16_type = BF16Type.get()
11991222
i32_type = IntegerType.get_signless(32)
1223+
idx_type = IndexType.get()
12001224
v2i32_type = VectorType.get([2], i32_type)
12011225
v4i32_type = VectorType.get([4], i32_type)
12021226
v8bf16_type = VectorType.get([8], bf16_type)
1203-
idx_type = IndexType.get()
12041227

1228+
# Bitcast 4 x bf16 -> 2 x i32 so permlane16_swap can operate on dwords.
12051229
i32_vec = vector_d.bitcast(v2i32_type, insert_vector)
1206-
own_0 = vector_d.extract(i32_vec, static_position=[0], dynamic_position=[])
1207-
own_1 = vector_d.extract(i32_vec, static_position=[1], dynamic_position=[])
1230+
own_lo = vector_d.extract(i32_vec, static_position=[0], dynamic_position=[])
1231+
own_hi = vector_d.extract(i32_vec, static_position=[1], dynamic_position=[])
12081232

1209-
swap_result_type = llvm_d.StructType.get_literal([i32_type, i32_type])
1210-
swap_0 = rocdl_d.permlane16_swap(swap_result_type, own_0, own_0, False, False)
1211-
partner_0 = llvm_d.extractvalue(i32_type, swap_0, [0])
1212-
swap_1 = rocdl_d.permlane16_swap(swap_result_type, own_1, own_1, False, False)
1213-
partner_1 = llvm_d.extractvalue(i32_type, swap_1, [0])
1233+
# Exchange dwords with the partner lane 16 positions apart.
1234+
swap_type = llvm_d.StructType.get_literal([i32_type, i32_type])
1235+
partner_lo = llvm_d.extractvalue(
1236+
i32_type, rocdl_d.permlane16_swap(swap_type, own_lo, own_lo, False, False), [0]
1237+
)
1238+
partner_hi = llvm_d.extractvalue(
1239+
i32_type, rocdl_d.permlane16_swap(swap_type, own_hi, own_hi, False, False), [0]
1240+
)
12141241

1215-
lane_id = emitter.thread_ids[0]
1216-
lane_in_wave = arith_d.remui(lane_id, arith_d.constant(idx_type, 64))
1242+
# Classify this lane as lower (0-15) or upper (16-31) within each
1243+
# 32-lane half-wave.
1244+
lane_in_wave = arith_d.remui(emitter.thread_ids[0], arith_d.constant(idx_type, 64))
12171245
half_pos = arith_d.remui(lane_in_wave, arith_d.constant(idx_type, 32))
12181246
is_lower = arith_d.cmpi(
1219-
arith_d.CmpIPredicate.ult,
1220-
half_pos,
1221-
arith_d.constant(idx_type, 16),
1247+
arith_d.CmpIPredicate.ult, half_pos, arith_d.constant(idx_type, 16)
12221248
)
12231249

1224-
v0 = arith_d.select(is_lower, own_0, partner_0)
1225-
v1 = arith_d.select(is_lower, own_1, partner_1)
1226-
v2 = arith_d.select(is_lower, partner_0, own_0)
1227-
v3 = arith_d.select(is_lower, partner_1, own_1)
1250+
# Both halves build identical 8-bf16 vectors, but in complementary
1251+
# order so they land at the same memory address:
1252+
# lower: [own_lo, own_hi, partner_lo, partner_hi] @ M
1253+
# upper: [partner_lo, partner_hi, own_lo, own_hi] @ M - 4
1254+
d0 = arith_d.select(is_lower, own_lo, partner_lo)
1255+
d1 = arith_d.select(is_lower, own_hi, partner_hi)
1256+
d2 = arith_d.select(is_lower, partner_lo, own_lo)
1257+
d3 = arith_d.select(is_lower, partner_hi, own_hi)
12281258

1229-
wide_i32 = vector_d.from_elements(v4i32_type, [v0, v1, v2, v3])
1259+
wide_i32 = vector_d.from_elements(v4i32_type, [d0, d1, d2, d3])
12301260
wide_vec = vector_d.bitcast(v8bf16_type, wide_i32)
12311261

1232-
lds_offset = start_indices_th[0]
1233-
four_elems = arith_d.constant(idx_type, 4)
1234-
adjusted_offset = arith_d.subi(lds_offset, four_elems)
1235-
final_offset = arith_d.select(is_lower, lds_offset, adjusted_offset)
1262+
# Adjust the M (last) dimension index for the upper half so both
1263+
# halves target the same 8-element span starting at the lower half's
1264+
# M base. M is contiguous (stride 1), so subtracting 4 elements
1265+
# from the index subtracts 4 from the linearized element offset.
1266+
four = arith_d.constant(idx_type, 4)
1267+
1268+
adj_th = list(start_indices_th)
1269+
adj_th[-1] = arith_d.select(is_lower, adj_th[-1], arith_d.subi(adj_th[-1], four))
12361270

1237-
# Both lane halves (lower and upper within each 32-lane group) write
1238-
# identical data to the same LDS address, so half the ds_write_b128
1239-
# instructions are redundant.
1240-
# TODO: Mask out the upper half to cut LDS write traffic in half.
1241-
vector_d.store(wide_vec, mem, [final_offset])
1271+
adj_full = list(start_indices)
1272+
adj_full[-1] = arith_d.select(
1273+
is_lower, adj_full[-1], arith_d.subi(adj_full[-1], four)
1274+
)
1275+
1276+
# mask=None: the buffer descriptor encodes valid_bytes for the full
1277+
# output buffer, so OOB stores at tile edges are silently dropped by
1278+
# hardware. The original Write node's mask was sized for 4 elements,
1279+
# incompatible with our 8-element vector.
1280+
_create_vec_read_write(
1281+
emitter,
1282+
output_shape,
1283+
kb_dest,
1284+
wide_vec,
1285+
None,
1286+
tuple(adj_full),
1287+
start_indices_wg,
1288+
tuple(adj_th),
1289+
8,
1290+
memory_custom,
1291+
None,
1292+
node_index=index,
1293+
)
12421294

12431295

12441296
def assume_index_subgroup_uniform(value: Value, element_type: IrType) -> Value:

0 commit comments

Comments
 (0)