1919import torch
2020import triton # @manual
2121import triton .language as tl # @manual
22+ from fbgemm_gpu .config import FeatureGateName
2223from fbgemm_gpu .split_embedding_configs import EmbOptimType as OptimType , SparseType
2324from fbgemm_gpu .split_table_batched_embeddings_ops_common import (
2425 BoundsCheckMode ,
@@ -77,6 +78,67 @@ def lengths_to_offsets(lengths: List[int], keep_last: bool = False) -> List[int]
7778 return offsets
7879
7980
81+ @triton .jit
82+ def _bounds_check_offsets_kernel (
83+ offsets_ptr ,
84+ warning_ptr ,
85+ num_indices ,
86+ total_B ,
87+ BLOCK_SIZE : tl .constexpr ,
88+ ) -> None :
89+ positions = tl .program_id (0 ) * BLOCK_SIZE + tl .arange (0 , BLOCK_SIZE )
90+ active = positions < total_B
91+ starts = tl .load (offsets_ptr + positions , mask = active , other = 0 )
92+ raw_ends = tl .load (offsets_ptr + positions + 1 , mask = active , other = 0 )
93+ ends = tl .where (positions == total_B - 1 , num_indices , raw_ends )
94+ invalid = active & ((starts < 0 ) | (starts > ends ) | (ends > num_indices ))
95+ if tl .program_id (0 ) == 0 :
96+ last_offset = tl .load (offsets_ptr + total_B )
97+ if last_offset != num_indices :
98+ tl .atomic_add (warning_ptr , 1 )
99+
100+ warning_count = tl .sum (invalid .to (tl .int64 ))
101+ if warning_count > 0 :
102+ tl .atomic_add (warning_ptr , warning_count )
103+
104+
105+ @triton .jit
106+ def _repair_offsets_kernel (
107+ offsets_ptr ,
108+ warning_ptr ,
109+ num_indices ,
110+ total_B ,
111+ ) -> None :
112+ if tl .load (warning_ptr ) > 0 :
113+ current = tl .maximum (0 , tl .minimum (tl .load (offsets_ptr ), num_indices ))
114+ tl .store (offsets_ptr , current )
115+ position = 0
116+ while position < total_B :
117+ raw_end = tl .load (offsets_ptr + position + 1 )
118+ raw_end = tl .where (position == total_B - 1 , num_indices , raw_end )
119+ current = tl .maximum (current , tl .minimum (raw_end , num_indices ))
120+ tl .store (offsets_ptr + position + 1 , current )
121+ position += 1
122+
123+
124+ @triton .jit
125+ def _load_checked_index (
126+ indices_ptr ,
127+ position ,
128+ num_rows ,
129+ mask ,
130+ FUSED_BOUNDS_CHECK : tl .constexpr ,
131+ ):
132+ row_idx = tl .load (indices_ptr + position , mask = mask , other = 0 )
133+ if FUSED_BOUNDS_CHECK :
134+ invalid = mask & (row_idx != - 1 ) & ((row_idx < 0 ) | (row_idx >= num_rows ))
135+ tl .store (indices_ptr + position , 0 , mask = invalid )
136+ row_idx = tl .where (invalid , 0 , row_idx )
137+ else :
138+ invalid = row_idx < row_idx
139+ return row_idx , invalid
140+
141+
80142@triton .jit
81143def table_batched_embedding_bag_forward_weighted_kernel (
82144 output_ptr ,
@@ -196,6 +258,8 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
196258 embedding_dims_ptr ,
197259 embedding_offsets_ptr ,
198260 feature_table_map_ptr ,
261+ rows_cumsum_ptr ,
262+ bounds_check_warning_ptr ,
199263 # VBE-specific pointers (only used when vbe=True)
200264 # pyre-fixme[2]: Parameter must be annotated.
201265 row_output_offsets_ptr ,
@@ -206,10 +270,12 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
206270 T : tl .constexpr ,
207271 BLOCK_SIZE : tl .constexpr ,
208272 vbe : tl .constexpr = False ,
273+ FUSED_BOUNDS_CHECK : tl .constexpr = False ,
209274) -> None :
210275
211276 b = tl .program_id (0 ).to (tl .int64 )
212277 col_offsets = tl .arange (0 , BLOCK_SIZE )
278+ warning_count = 0
213279 if vbe :
214280 output_row_base = output_ptr # unused, VBE uses row_output_offsets
215281 else :
@@ -233,6 +299,10 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
233299 table_offset = tl .load (table_offsets_ptr + table_idx )
234300 # embedding_dim and embedding_offset are indexed by feature
235301 embedding_dim = tl .load (embedding_dims_ptr + t )
302+ if FUSED_BOUNDS_CHECK :
303+ num_rows = tl .load (rows_cumsum_ptr + table_idx + 1 ) - tl .load (
304+ rows_cumsum_ptr + table_idx
305+ )
236306
237307 start = tl .load (offsets_ptr + b_t )
238308 end = tl .load (offsets_ptr + b_t + 1 )
@@ -245,10 +315,41 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
245315 endn = start + step * ns
246316
247317 for idx in range (start , endn , step ):
248- row_idx_0 = tl .load (indices_ptr + idx + 0 )
249- row_idx_1 = tl .load (indices_ptr + idx + 1 )
250- row_idx_2 = tl .load (indices_ptr + idx + 2 )
251- row_idx_3 = tl .load (indices_ptr + idx + 3 )
318+ row_idx_0 , invalid_0 = _load_checked_index (
319+ indices_ptr ,
320+ idx + 0 ,
321+ num_rows if FUSED_BOUNDS_CHECK else 0 ,
322+ True ,
323+ FUSED_BOUNDS_CHECK ,
324+ )
325+ row_idx_1 , invalid_1 = _load_checked_index (
326+ indices_ptr ,
327+ idx + 1 ,
328+ num_rows if FUSED_BOUNDS_CHECK else 0 ,
329+ True ,
330+ FUSED_BOUNDS_CHECK ,
331+ )
332+ row_idx_2 , invalid_2 = _load_checked_index (
333+ indices_ptr ,
334+ idx + 2 ,
335+ num_rows if FUSED_BOUNDS_CHECK else 0 ,
336+ True ,
337+ FUSED_BOUNDS_CHECK ,
338+ )
339+ row_idx_3 , invalid_3 = _load_checked_index (
340+ indices_ptr ,
341+ idx + 3 ,
342+ num_rows if FUSED_BOUNDS_CHECK else 0 ,
343+ True ,
344+ FUSED_BOUNDS_CHECK ,
345+ )
346+ if FUSED_BOUNDS_CHECK :
347+ warning_count += (
348+ invalid_0 .to (tl .int32 )
349+ + invalid_1 .to (tl .int32 )
350+ + invalid_2 .to (tl .int32 )
351+ + invalid_3 .to (tl .int32 )
352+ )
252353
253354 row_start_ptr_0 = weight_ptr + table_offset + row_idx_0 * embedding_dim
254355 row_start_ptr_1 = weight_ptr + table_offset + row_idx_1 * embedding_dim
@@ -273,7 +374,15 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
273374 )
274375
275376 for idx in range (endn , end ):
276- row_idx = tl .load (indices_ptr + idx )
377+ row_idx , invalid = _load_checked_index (
378+ indices_ptr ,
379+ idx ,
380+ num_rows if FUSED_BOUNDS_CHECK else 0 ,
381+ True ,
382+ FUSED_BOUNDS_CHECK ,
383+ )
384+ if FUSED_BOUNDS_CHECK :
385+ warning_count += invalid .to (tl .int32 )
277386 row_start_ptr = weight_ptr + table_offset + row_idx * embedding_dim
278387 row_ptrs = row_start_ptr + col_offsets
279388 row = tl .load (row_ptrs , mask = mask , other = 0 )
@@ -288,6 +397,9 @@ def table_batched_embedding_bag_forward_unweighted_kernel(
288397 bag_output_original = bag_output .to (tl .float32 )
289398 tl .store (output_row_ptrs , bag_output_original , mask = mask )
290399
400+ if FUSED_BOUNDS_CHECK and warning_count > 0 :
401+ tl .atomic_add (bounds_check_warning_ptr , warning_count .to (tl .int64 ))
402+
291403
292404@triton .jit
293405def triton_tbe_backward_short_run_unweighted (
@@ -1041,6 +1153,8 @@ def forward(
10411153 precomputed_total_B : int = 0 ,
10421154 precomputed_max_B : int = 0 ,
10431155 hoist_transpose_to_forward : bool = True ,
1156+ bounds_check_warning : Optional [torch .Tensor ] = None ,
1157+ fused_bounds_check : bool = False ,
10441158 ) -> torch .Tensor :
10451159 # VBE support: use pre-computed metadata if available, otherwise compute
10461160 vbe = batch_size_per_feature_per_rank is not None
@@ -1120,6 +1234,15 @@ def forward(
11201234 (B , total_embedding_dim ), device = weight .device , dtype = output_dtype
11211235 )
11221236
1237+ weighted = per_sample_weights is not None and per_sample_weights .numel () > 0
1238+ if fused_bounds_check and (
1239+ bounds_check_warning is None
1240+ or hoist_transpose_to_forward
1241+ or weighted
1242+ or vbe
1243+ ):
1244+ raise ValueError ("Invalid fused bounds-check configuration" )
1245+
11231246 # For VBE backward, save row_output_offsets, B_offsets, and b_t_map
11241247 if vbe :
11251248 assert vbe_metadata .B_offsets is not None
@@ -1204,8 +1327,9 @@ def forward(
12041327
12051328 num_warps = 1
12061329
1207- weighted = per_sample_weights is not None and per_sample_weights .numel () > 0
1208-
1330+ bounds_check_warning_ptr = (
1331+ bounds_check_warning if bounds_check_warning is not None else indices
1332+ )
12091333 # Prepare VBE pointers (use dummy tensor if not VBE)
12101334 row_output_offsets_ptr = (
12111335 row_output_offsets
@@ -1244,29 +1368,47 @@ def forward(
12441368 num_warps = num_warps ,
12451369 )
12461370 else :
1247- fwd_kernel = (
1248- _amd_fwd_unweighted_kernel
1249- if is_amd ()
1250- else table_batched_embedding_bag_forward_unweighted_kernel
1251- )
1252- fwd_kernel [(B ,)](
1253- output ,
1254- indices ,
1255- offsets ,
1256- weight ,
1257- table_offsets ,
1258- embedding_dims ,
1259- embedding_offsets ,
1260- feature_table_map ,
1261- row_output_offsets_ptr ,
1262- B_offsets_ptr ,
1263- total_embedding_dim ,
1264- B ,
1265- T ,
1266- BLOCK_SIZE = block_size ,
1267- vbe = vbe ,
1268- num_warps = num_warps ,
1269- )
1371+ if is_amd ():
1372+ _amd_fwd_unweighted_kernel [(B ,)](
1373+ output ,
1374+ indices ,
1375+ offsets ,
1376+ weight ,
1377+ table_offsets ,
1378+ embedding_dims ,
1379+ embedding_offsets ,
1380+ feature_table_map ,
1381+ row_output_offsets_ptr ,
1382+ B_offsets_ptr ,
1383+ total_embedding_dim ,
1384+ B ,
1385+ T ,
1386+ BLOCK_SIZE = block_size ,
1387+ vbe = vbe ,
1388+ num_warps = num_warps ,
1389+ )
1390+ else :
1391+ table_batched_embedding_bag_forward_unweighted_kernel [(B ,)](
1392+ output ,
1393+ indices ,
1394+ offsets ,
1395+ weight ,
1396+ table_offsets ,
1397+ embedding_dims ,
1398+ embedding_offsets ,
1399+ feature_table_map ,
1400+ rows_cumsum ,
1401+ bounds_check_warning_ptr ,
1402+ row_output_offsets_ptr ,
1403+ B_offsets_ptr ,
1404+ total_embedding_dim ,
1405+ B ,
1406+ T ,
1407+ BLOCK_SIZE = block_size ,
1408+ vbe = vbe ,
1409+ FUSED_BOUNDS_CHECK = fused_bounds_check ,
1410+ num_warps = num_warps ,
1411+ )
12701412
12711413 # Record a CUDA event to mark forward kernel completion.
12721414 # This is needed for synchronization before NCCL collectives.
@@ -1762,6 +1904,8 @@ def backward(ctx, dout) -> Tuple[None, ...]:
17621904 None , # precomputed_total_B
17631905 None , # precomputed_max_B
17641906 None , # hoist_transpose_to_forward
1907+ None ,
1908+ None ,
17651909 )
17661910
17671911
@@ -1783,6 +1927,7 @@ def __init__(
17831927 optimizer : OptimType = OptimType .EXACT_SGD ,
17841928 device : Optional [torch .device ] = None ,
17851929 hoist_transpose_to_forward : bool = False ,
1930+ fused_bounds_check : bool = False ,
17861931 ) -> None :
17871932 super ().__init__ ()
17881933 logging .info ("TritonTableBatchedEmbeddingBags init args: %s" , locals ())
@@ -1865,6 +2010,7 @@ def __init__(
18652010 self .eps = eps
18662011 self .optimizer = optimizer
18672012 self .hoist_transpose_to_forward = hoist_transpose_to_forward
2013+ self .fused_bounds_check = fused_bounds_check
18682014
18692015 # Initialize optimizer state
18702016 rows = [spec [0 ] for spec in embedding_specs ]
@@ -1892,6 +2038,9 @@ def __init__(
18922038 )
18932039 self .bounds_check_warning = torch .tensor ([0 ], device = device , dtype = torch .int64 )
18942040 self .bounds_check_mode : BoundsCheckMode = BoundsCheckMode .V2_WARNING
2041+ self ._disable_offsets_adjustment = (
2042+ FeatureGateName .DISABLE_OFFSETS_ADJUSTMENT .is_enabled ()
2043+ )
18952044
18962045 def _bounds_check_config (self ) -> Tuple [BoundsCheckMode , int ]:
18972046 is_v2 = self .bounds_check_mode .name .startswith ("V2_" )
@@ -2030,9 +2179,25 @@ def forward(
20302179 offsets , batch_size_per_feature_per_rank
20312180 )
20322181
2033- # Bounds check (VBE-aware)
20342182 bounds_check_mode , bounds_check_version = self ._bounds_check_config ()
2035- if bounds_check_mode != BoundsCheckMode .NONE :
2183+ use_fused_bounds_check = (
2184+ self .fused_bounds_check
2185+ and bounds_check_mode == BoundsCheckMode .WARNING
2186+ and batch_size_per_feature_per_rank is None
2187+ and (per_sample_weights is None or per_sample_weights .numel () == 0 )
2188+ and not self .hoist_transpose_to_forward
2189+ and not is_amd ()
2190+ )
2191+
2192+ if use_fused_bounds_check :
2193+ if indices .dim () != 1 or offsets .dim () != 1 :
2194+ raise RuntimeError ("indices and offsets must be one-dimensional" )
2195+ if offsets .numel () == 0 or (offsets .numel () - 1 ) % self .T != 0 :
2196+ raise RuntimeError ("offsets size must equal B * T + 1" )
2197+ if indices .device != offsets .device or indices .device != self .weight .device :
2198+ raise RuntimeError ("TBE inputs must be on the same device" )
2199+
2200+ if bounds_check_mode != BoundsCheckMode .NONE and not use_fused_bounds_check :
20362201 torch .ops .fbgemm .bounds_check_indices (
20372202 self .rows_per_table ,
20382203 indices ,
@@ -2047,6 +2212,28 @@ def forward(
20472212 info_B_mask = info_B_mask if info_B_mask > 0 else - 1 ,
20482213 bounds_check_version = bounds_check_version ,
20492214 )
2215+ elif use_fused_bounds_check :
2216+ self .bounds_check_warning .zero_ ()
2217+ total_bags = offsets .size (0 ) - 1
2218+ if total_bags > 0 :
2219+ _bounds_check_offsets_kernel [(triton .cdiv (total_bags , 256 ),)](
2220+ offsets ,
2221+ self .bounds_check_warning ,
2222+ indices .numel (),
2223+ total_bags ,
2224+ BLOCK_SIZE = 256 ,
2225+ num_warps = 8 ,
2226+ )
2227+ if self ._disable_offsets_adjustment :
2228+ torch ._assert_async (self .bounds_check_warning == 0 )
2229+ else :
2230+ _repair_offsets_kernel [(1 ,)](
2231+ offsets ,
2232+ self .bounds_check_warning ,
2233+ indices .numel (),
2234+ total_bags ,
2235+ num_warps = 1 ,
2236+ )
20502237
20512238 return TritonTBE .apply (
20522239 indices ,
@@ -2082,6 +2269,8 @@ def forward(
20822269 total_B ,
20832270 max_B ,
20842271 self .hoist_transpose_to_forward ,
2272+ self .bounds_check_warning ,
2273+ use_fused_bounds_check ,
20852274 )
20862275
20872276 def split_embedding_weights (self ) -> List [torch .Tensor ]:
0 commit comments