Skip to content

Commit 1c31046

Browse files
stashuk-olekfacebook-github-bot
authored andcommitted
Optimize Triton TBE forward for small long-bag tables (#4486)
Summary: Convert long bags over small tables into per-bag row histograms followed by a tensor-core dot. Generic row, dimension, and bag-size predicates select the route, and checked loads preserve fused bounds behavior. Reviewed By: axeisghost Differential Revision: D114273027
1 parent 11e6c4b commit 1c31046

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)