Skip to content

Commit e187744

Browse files
stashuk-olekfacebook-github-bot
authored andcommitted
Optimize Triton TBE forward for small long-bag tables (meta-pytorch#4486)
Summary: So there's a ton of gather there that are quite expensive. An interesting trick would be to try to count elements in rows with histogram and then do single TC dot to restore them. Reviewed By: axeisghost Differential Revision: D114273027
1 parent 4d81744 commit e187744

2 files changed

Lines changed: 205 additions & 16 deletions

File tree

torchrec/distributed/batched_embedding_kernel.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3894,6 +3894,7 @@ def __init__(
38943894
)
38953895
output_dtype = output_dtype_sparse.as_dtype()
38963896
stochastic_rounding = fused_params.get("stochastic_rounding", True)
3897+
bag_size_hints: Optional[List[int]] = fused_params.get("bag_size_hints")
38973898

38983899
# Create Triton TBE module with feature_table_map for correct batch size handling
38993900
self._emb_module: TritonTableBatchedEmbeddingBags = (
@@ -3907,6 +3908,7 @@ def __init__(
39073908
eps=eps,
39083909
optimizer=optimizer,
39093910
device=device,
3911+
bag_size_hints=bag_size_hints,
39103912
)
39113913
)
39123914

torchrec/distributed/triton_tbe/triton_table_batched_embeddings.py

Lines changed: 203 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,10 @@ def table_batched_embedding_bag_forward_weighted_kernel(
185185

186186
col_offsets = tl.arange(0, BLOCK_SIZE)
187187
mask = col_offsets < embedding_dim
188-
bag_output = tl.zeros((BLOCK_SIZE,), dtype=tl.float64)
188+
accumulator_dtype: tl.constexpr = (
189+
tl.float64 if weight_ptr.dtype.element_ty == tl.float32 else tl.float32
190+
)
191+
bag_output = tl.zeros((BLOCK_SIZE,), dtype=accumulator_dtype)
189192

190193
# without type hint the unrolling performance will downgrade
191194
step: tl.constexpr = 4
@@ -270,6 +273,8 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
270273
T: tl.constexpr,
271274
BLOCK_SIZE: tl.constexpr,
272275
vbe: tl.constexpr = False,
276+
FEATURE_START: tl.constexpr = 0,
277+
FEATURE_END: tl.constexpr = -1,
273278
FUSED_BOUNDS_CHECK: tl.constexpr = False,
274279
) -> None:
275280

@@ -281,7 +286,8 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
281286
else:
282287
output_row_base = output_ptr + b * total_embedding_dim
283288

284-
for t in range(T):
289+
feature_end: tl.constexpr = T if FEATURE_END < 0 else FEATURE_END
290+
for t in range(FEATURE_START, feature_end):
285291
if vbe:
286292
# VBE: check if this batch index is within feature t's batch size
287293
B_start = tl.load(B_offsets_ptr + t).to(tl.int64)
@@ -308,7 +314,10 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
308314
end = tl.load(offsets_ptr + b_t + 1)
309315

310316
mask = col_offsets < embedding_dim
311-
bag_output = tl.zeros((BLOCK_SIZE,), dtype=tl.float64)
317+
accumulator_dtype: tl.constexpr = (
318+
tl.float64 if weight_ptr.dtype.element_ty == tl.float32 else tl.float32
319+
)
320+
bag_output = tl.zeros((BLOCK_SIZE,), dtype=accumulator_dtype)
312321

313322
step: tl.constexpr = 4
314323
ns = (end - start) // step
@@ -401,6 +410,100 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
401410
tl.atomic_add(bounds_check_warning_ptr, warning_count.to(tl.int64))
402411

403412

413+
@triton.jit
414+
def table_batched_embedding_bag_forward_small_table_kernel(
415+
output_ptr,
416+
indices_ptr,
417+
offsets_ptr,
418+
weight_ptr,
419+
table_offsets_ptr,
420+
embedding_dims_ptr,
421+
embedding_offsets_ptr,
422+
feature_table_map_ptr,
423+
bounds_check_warning_ptr,
424+
total_embedding_dim: tl.constexpr,
425+
B,
426+
FEATURE: tl.constexpr,
427+
NUM_ROWS: tl.constexpr,
428+
ROW_BINS: tl.constexpr,
429+
BLOCK_SIZE: tl.constexpr,
430+
FUSED_BOUNDS_CHECK: tl.constexpr = False,
431+
) -> None:
432+
bags_per_program: tl.constexpr = 16
433+
histogram_chunk_size: tl.constexpr = 256
434+
435+
bag_slots = tl.arange(0, bags_per_program)
436+
bags = tl.program_id(0).to(tl.int64) * bags_per_program + bag_slots
437+
bag_mask = bags < B
438+
starts = tl.load(offsets_ptr + FEATURE * B + bags, mask=bag_mask, other=0)
439+
ends = tl.load(offsets_ptr + FEATURE * B + bags + 1, mask=bag_mask, other=0)
440+
lengths = ends - starts
441+
442+
positions = tl.arange(0, histogram_chunk_size)
443+
input_mask = bag_mask[:, None] & (positions[None, :] < lengths[:, None])
444+
row_indices, invalid_indices = _load_checked_index(
445+
indices_ptr,
446+
starts[:, None] + positions[None, :],
447+
NUM_ROWS,
448+
input_mask,
449+
FUSED_BOUNDS_CHECK,
450+
)
451+
row_indices = row_indices.to(tl.int32)
452+
warning_count = tl.sum(invalid_indices.to(tl.int32))
453+
encoded_indices = row_indices + bag_slots[:, None] * ROW_BINS
454+
counts = tl.histogram(
455+
encoded_indices.reshape((bags_per_program * histogram_chunk_size,)),
456+
bags_per_program * ROW_BINS,
457+
mask=input_mask.reshape((bags_per_program * histogram_chunk_size,)),
458+
).reshape((bags_per_program, ROW_BINS))
459+
460+
table_idx = tl.load(feature_table_map_ptr + FEATURE)
461+
table_offset = tl.load(table_offsets_ptr + table_idx)
462+
embedding_dim = tl.load(embedding_dims_ptr + FEATURE)
463+
embedding_offset = tl.load(embedding_offsets_ptr + FEATURE)
464+
rows = tl.arange(0, ROW_BINS)
465+
columns = tl.arange(0, BLOCK_SIZE)
466+
table = tl.load(
467+
weight_ptr + table_offset + rows[:, None] * embedding_dim + columns[None, :],
468+
mask=(rows[:, None] < NUM_ROWS) & (columns[None, :] < embedding_dim),
469+
other=0,
470+
)
471+
bag_output = tl.dot(counts.to(tl.float16), table)
472+
473+
tail_lengths = tl.maximum(lengths - histogram_chunk_size, 0)
474+
for tail in range(0, tl.max(tail_lengths)):
475+
active = bag_mask & (tail < tail_lengths)
476+
row_idx, invalid = _load_checked_index(
477+
indices_ptr,
478+
starts + histogram_chunk_size + tail,
479+
NUM_ROWS,
480+
active,
481+
FUSED_BOUNDS_CHECK,
482+
)
483+
if FUSED_BOUNDS_CHECK:
484+
warning_count += tl.sum(invalid.to(tl.int32))
485+
row = tl.load(
486+
weight_ptr
487+
+ table_offset
488+
+ row_idx[:, None] * embedding_dim
489+
+ columns[None, :],
490+
mask=active[:, None] & (columns[None, :] < embedding_dim),
491+
other=0,
492+
)
493+
bag_output += row.to(tl.float32)
494+
495+
tl.store(
496+
output_ptr
497+
+ bags[:, None] * total_embedding_dim
498+
+ embedding_offset
499+
+ columns[None, :],
500+
bag_output,
501+
mask=bag_mask[:, None] & (columns[None, :] < embedding_dim),
502+
)
503+
if FUSED_BOUNDS_CHECK and warning_count > 0:
504+
tl.atomic_add(bounds_check_warning_ptr, warning_count.to(tl.int64))
505+
506+
404507
@triton.jit
405508
def triton_tbe_backward_short_run_unweighted(
406509
dout_ptr,
@@ -1153,6 +1256,10 @@ def forward(
11531256
precomputed_total_B: int = 0,
11541257
precomputed_max_B: int = 0,
11551258
hoist_transpose_to_forward: bool = True,
1259+
histogram_feature: int = -1,
1260+
histogram_num_rows: int = 0,
1261+
histogram_row_bins: int = 0,
1262+
histogram_block_size: int = 0,
11561263
bounds_check_warning: Optional[torch.Tensor] = None,
11571264
fused_bounds_check: bool = False,
11581265
) -> torch.Tensor:
@@ -1368,8 +1475,16 @@ def forward(
13681475
num_warps=num_warps,
13691476
)
13701477
else:
1371-
if is_amd():
1372-
_amd_fwd_unweighted_kernel[(B,)](
1478+
use_small_table_kernel = (
1479+
histogram_feature >= 0
1480+
and not vbe
1481+
and not is_amd()
1482+
and weight.dtype == torch.float16
1483+
)
1484+
if use_small_table_kernel:
1485+
table_batched_embedding_bag_forward_small_table_kernel[
1486+
(triton.cdiv(B, 16),)
1487+
](
13731488
output,
13741489
indices,
13751490
offsets,
@@ -1378,17 +1493,19 @@ def forward(
13781493
embedding_dims,
13791494
embedding_offsets,
13801495
feature_table_map,
1381-
row_output_offsets_ptr,
1382-
B_offsets_ptr,
1496+
bounds_check_warning_ptr,
13831497
total_embedding_dim,
13841498
B,
1385-
T,
1386-
BLOCK_SIZE=block_size,
1387-
vbe=vbe,
1388-
num_warps=num_warps,
1499+
FEATURE=histogram_feature,
1500+
NUM_ROWS=histogram_num_rows,
1501+
ROW_BINS=histogram_row_bins,
1502+
BLOCK_SIZE=histogram_block_size,
1503+
FUSED_BOUNDS_CHECK=fused_bounds_check,
1504+
num_warps=1,
13891505
)
1390-
else:
1391-
table_batched_embedding_bag_forward_unweighted_kernel[(B,)](
1506+
1507+
if is_amd():
1508+
_amd_fwd_unweighted_kernel[(B,)](
13921509
output,
13931510
indices,
13941511
offsets,
@@ -1397,18 +1514,50 @@ def forward(
13971514
embedding_dims,
13981515
embedding_offsets,
13991516
feature_table_map,
1400-
rows_cumsum,
1401-
bounds_check_warning_ptr,
14021517
row_output_offsets_ptr,
14031518
B_offsets_ptr,
14041519
total_embedding_dim,
14051520
B,
14061521
T,
14071522
BLOCK_SIZE=block_size,
14081523
vbe=vbe,
1409-
FUSED_BOUNDS_CHECK=fused_bounds_check,
14101524
num_warps=num_warps,
14111525
)
1526+
else:
1527+
feature_ranges = (
1528+
[
1529+
(0, histogram_feature),
1530+
(histogram_feature + 1, T),
1531+
]
1532+
if use_small_table_kernel
1533+
else [(0, T)]
1534+
)
1535+
for feature_start, feature_end in feature_ranges:
1536+
if feature_start >= feature_end:
1537+
continue
1538+
table_batched_embedding_bag_forward_unweighted_kernel[(B,)](
1539+
output,
1540+
indices,
1541+
offsets,
1542+
weight,
1543+
table_offsets,
1544+
embedding_dims,
1545+
embedding_offsets,
1546+
feature_table_map,
1547+
rows_cumsum,
1548+
bounds_check_warning_ptr,
1549+
row_output_offsets_ptr,
1550+
B_offsets_ptr,
1551+
total_embedding_dim,
1552+
B,
1553+
T,
1554+
BLOCK_SIZE=block_size,
1555+
vbe=vbe,
1556+
FEATURE_START=feature_start,
1557+
FEATURE_END=feature_end,
1558+
FUSED_BOUNDS_CHECK=fused_bounds_check,
1559+
num_warps=num_warps,
1560+
)
14121561

14131562
# Record a CUDA event to mark forward kernel completion.
14141563
# This is needed for synchronization before NCCL collectives.
@@ -1906,6 +2055,10 @@ def backward(ctx, dout) -> Tuple[None, ...]:
19062055
None, # hoist_transpose_to_forward
19072056
None,
19082057
None,
2058+
None,
2059+
None,
2060+
None,
2061+
None,
19092062
)
19102063

19112064

@@ -1927,6 +2080,7 @@ def __init__(
19272080
optimizer: OptimType = OptimType.EXACT_SGD,
19282081
device: Optional[torch.device] = None,
19292082
hoist_transpose_to_forward: bool = False,
2083+
bag_size_hints: Optional[List[int]] = None,
19302084
fused_bounds_check: bool = False,
19312085
) -> None:
19322086
super().__init__()
@@ -2005,6 +2159,35 @@ def __init__(
20052159
self.output_dtype = (
20062160
output_dtype if output_dtype is not None else weights_precision
20072161
)
2162+
if bag_size_hints is not None and len(bag_size_hints) != self.T:
2163+
raise ValueError(
2164+
f"bag_size_hints must have {self.T} entries, "
2165+
f"got {len(bag_size_hints)}"
2166+
)
2167+
2168+
self._histogram_feature = -1
2169+
self._histogram_num_rows = 0
2170+
self._histogram_row_bins = 0
2171+
self._histogram_block_size = 0
2172+
if (
2173+
bag_size_hints is not None
2174+
and weights_precision == torch.float16
2175+
and self.output_dtype == torch.float32
2176+
):
2177+
candidates = []
2178+
for feature, table in enumerate(feature_table_map):
2179+
num_rows = hash_sizes[table]
2180+
dim = feature_dims[feature]
2181+
bag_size = bag_size_hints[feature]
2182+
if num_rows <= 64 and 64 <= dim <= 128 and bag_size >= 64:
2183+
candidates.append((bag_size * dim, feature, num_rows, dim))
2184+
if candidates:
2185+
_, feature, num_rows, dim = max(candidates)
2186+
self._histogram_feature = feature
2187+
self._histogram_num_rows = num_rows
2188+
self._histogram_row_bins = max(32, triton.next_power_of_2(num_rows))
2189+
self._histogram_block_size = triton.next_power_of_2(dim)
2190+
20082191
self.stochastic_rounding = stochastic_rounding
20092192
self.learning_rate = learning_rate
20102193
self.eps = eps
@@ -2269,6 +2452,10 @@ def forward(
22692452
total_B,
22702453
max_B,
22712454
self.hoist_transpose_to_forward,
2455+
self._histogram_feature,
2456+
self._histogram_num_rows,
2457+
self._histogram_row_bins,
2458+
self._histogram_block_size,
22722459
self.bounds_check_warning,
22732460
use_fused_bounds_check,
22742461
)

0 commit comments

Comments
 (0)