Skip to content

Commit b8fae9c

Browse files
stashuk-olekfacebook-github-bot
authored andcommitted
Optimize Triton TBE forward for small long-bag tables (#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 6ac5661 commit b8fae9c

2 files changed

Lines changed: 209 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: 207 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,11 @@ def __init__(
19272080
optimizer: OptimType = OptimType.EXACT_SGD,
19282081
device: Optional[torch.device] = None,
19292082
hoist_transpose_to_forward: bool = False,
2083+
# Heads up, adding this property will change numerics up to 1e-5 with the
2084+
# benefit of saving ~150 gathers into histogram count and one dot product.
2085+
# We've seen significant performance better with for small bags, but use
2086+
# with caution.
2087+
bag_size_hints: Optional[List[int]] = None,
19302088
fused_bounds_check: bool = False,
19312089
) -> None:
19322090
super().__init__()
@@ -2005,6 +2163,35 @@ def __init__(
20052163
self.output_dtype = (
20062164
output_dtype if output_dtype is not None else weights_precision
20072165
)
2166+
if bag_size_hints is not None and len(bag_size_hints) != self.T:
2167+
raise ValueError(
2168+
f"bag_size_hints must have {self.T} entries, "
2169+
f"got {len(bag_size_hints)}"
2170+
)
2171+
2172+
self._histogram_feature = -1
2173+
self._histogram_num_rows = 0
2174+
self._histogram_row_bins = 0
2175+
self._histogram_block_size = 0
2176+
if (
2177+
bag_size_hints is not None
2178+
and weights_precision == torch.float16
2179+
and self.output_dtype == torch.float32
2180+
):
2181+
candidates = []
2182+
for feature, table in enumerate(feature_table_map):
2183+
num_rows = hash_sizes[table]
2184+
dim = feature_dims[feature]
2185+
bag_size = bag_size_hints[feature]
2186+
if num_rows <= 64 and 64 <= dim <= 128 and bag_size >= 64:
2187+
candidates.append((bag_size * dim, feature, num_rows, dim))
2188+
if candidates:
2189+
_, feature, num_rows, dim = max(candidates)
2190+
self._histogram_feature = feature
2191+
self._histogram_num_rows = num_rows
2192+
self._histogram_row_bins = max(32, triton.next_power_of_2(num_rows))
2193+
self._histogram_block_size = triton.next_power_of_2(dim)
2194+
20082195
self.stochastic_rounding = stochastic_rounding
20092196
self.learning_rate = learning_rate
20102197
self.eps = eps
@@ -2269,6 +2456,10 @@ def forward(
22692456
total_B,
22702457
max_B,
22712458
self.hoist_transpose_to_forward,
2459+
self._histogram_feature,
2460+
self._histogram_num_rows,
2461+
self._histogram_row_bins,
2462+
self._histogram_block_size,
22722463
self.bounds_check_warning,
22732464
use_fused_bounds_check,
22742465
)

0 commit comments

Comments
 (0)