Skip to content

Commit aa2f28e

Browse files
gchalumpfacebook-github-bot
authored andcommitted
Add tbe_bwd_indices_preproc op + reference-impl unit test (pytorch#6222)
Summary: X-link: https://github.com/facebookresearch/FBGEMM/pull/3107 Add the standalone `tbe_bwd_indices_preproc` CUDA op that runs the two grad-independent index-preprocessing steps -- transpose_embedding_input (linearize -> radix-sort -> RLE + cumsum) and find_long_segments (segment partition) -- and returns them as a 12-tensor bundle in the driver's preproc_tensors[0..11] unpack order. Folded into embedding_backward_split_grad_template.cu so it shares the split_embedding_backward_codegen_find_long_segments __global__ defined there -- no separate build target/file. CUDA, common path (bagged, non-index-select); max_segment_length_per_cta + use_deterministic_algorithms are derived internally to mirror the inline driver. Op-only base of the preproc-hoist stack: this diff only defines and registers the op. The consume/route wiring (backward driver + PT2 autograd) lands in D113624507 above; the forward-emit in D115771945. Differential Revision: D114645411
1 parent b367d43 commit aa2f28e

4 files changed

Lines changed: 363 additions & 2 deletions

File tree

fbgemm_gpu/codegen/training/backward/embedding_backward_split_grad_template.cu

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
#include "fbgemm_gpu/embedding_backward_template_helpers.cuh"
1111
#include "fbgemm_gpu/utils/tensor_accessor_builder.h"
1212
#include "fbgemm_gpu/split_embeddings_utils.cuh"
13+
#include "fbgemm_gpu/config/feature_gates.h"
14+
#include "fbgemm_gpu/utils/kernel_launcher.cuh"
15+
#include "fbgemm_gpu/utils/ops_utils.h"
16+
#include <ATen/cuda/CUDAContext.h>
17+
#include <torch/library.h>
1318

1419
using Tensor = at::Tensor;
1520

@@ -249,4 +254,167 @@ void grad_mean{{ vdesc }}_kernel
249254

250255
}
251256

257+
{% if not is_index_select %}
258+
// ===========================================================================
259+
// tbe_bwd_indices_preproc: combined index-preprocessing op for the TBE
260+
// backward. Folded into this single, non-optimizer-templated TU so it shares
261+
// the split_embedding_backward_codegen_find_long_segments __global__ defined
262+
// above (embedding_ops namespace) -- no forward-decl, compiled once, NO
263+
// separate build target/file. Wraps the two grad-independent steps
264+
// 1) transpose_embedding_input (linearize -> radix-sort -> RLE + cumsum)
265+
// 2) find_long_segments (segment partition)
266+
// so they can be hoisted OFF the backward critical path. CUDA, common path
267+
// (bagged, non-index-select). max_segment_length_per_cta +
268+
// use_deterministic_algorithms are derived internally, mirroring the driver.
269+
// The 12-tensor output order matches the driver's preproc_tensors[0..11]
270+
// unpack contract in embedding_backward_split_template.cu.
271+
// Design doc:
272+
// docs.google.com/document/d/1Z8_1zI_4WSF-gsaHKVLY3wUNPyAZSYRJLDSbDZfRE2o
273+
// ===========================================================================
274+
namespace fbgemm_gpu {
275+
276+
std::tuple<
277+
Tensor, // linear_indices
278+
Tensor, // linear_indices_sorted
279+
Tensor, // sorted_linear_indices_run
280+
Tensor, // sorted_linear_indices_run_lengths
281+
Tensor, // sorted_linear_indices_num_runs
282+
Tensor, // sorted_linear_indices_cumulative_run_lengths
283+
Tensor, // infos_sorted
284+
Tensor, // long_run_ids
285+
Tensor, // num_long_run_ids
286+
Tensor, // long_run_id_to_really_long_run_ids
287+
Tensor, // num_really_long_run_ids
288+
Tensor> // grad_accum_counter
289+
tbe_bwd_indices_preproc_cuda(
290+
const Tensor& hash_size_cumsum,
291+
const int64_t total_hash_size_bits,
292+
const Tensor& indices,
293+
const Tensor& offsets,
294+
const int64_t info_B_num_bits,
295+
const int64_t info_B_mask,
296+
const int64_t total_unique_indices,
297+
const std::optional<Tensor>& vbe_b_t_map,
298+
const bool nobag,
299+
const bool is_index_select) {
300+
CUDA_DEVICE_GUARD(indices);
301+
302+
// ---- Part A: transpose_embedding_input ----------------------------------
303+
auto
304+
[linear_indices,
305+
linear_indices_sorted,
306+
infos_sorted,
307+
sorted_linear_indices_run,
308+
sorted_linear_indices_run_lengths,
309+
sorted_linear_indices_num_runs,
310+
sorted_linear_indices_cumulative_run_lengths] =
311+
transpose_embedding_input(
312+
hash_size_cumsum,
313+
total_hash_size_bits,
314+
indices,
315+
offsets,
316+
nobag,
317+
vbe_b_t_map,
318+
info_B_num_bits,
319+
info_B_mask,
320+
total_unique_indices,
321+
is_index_select);
322+
323+
// ---- Part B: find_long_segments -----------------------------------------
324+
// Grid bound: when total_unique_indices is unknown at call time (-1, e.g.
325+
// hoisted into the forward before the run count is available), fall back to
326+
// indices.numel() -- a safe upper bound on the number of runs. The kernel
327+
// bounds its real work by the device-side run count, so extra blocks are
328+
// no-ops; this only over-launches, it does not affect correctness.
329+
const auto num_unique =
330+
total_unique_indices >= 0 ? total_unique_indices : indices.numel();
331+
332+
auto long_run_ids =
333+
at::empty({indices.numel()}, sorted_linear_indices_run_lengths.options());
334+
auto num_long_run_ids = at::zeros({1}, indices.options().dtype(at::kInt));
335+
336+
const bool use_deterministic_algorithms =
337+
at::globalContext().deterministicAlgorithms();
338+
339+
// max_segment_length_per_warp is a fixed policy constant (warp/CTA routing
340+
// threshold), not a runtime input -- derived internally to mirror the driver.
341+
#ifdef USE_ROCM
342+
constexpr int32_t max_segment_length_per_warp = 16384;
343+
const int max_segment_length_per_cta =
344+
use_deterministic_algorithms ? INT_MAX : 4096;
345+
#else
346+
constexpr int32_t max_segment_length_per_warp = 32;
347+
const auto device_properties = at::cuda::getCurrentDeviceProperties();
348+
int default_segment_length = 1024;
349+
const bool b200_feature_enabled =
350+
(device_properties->major >= 10) &&
351+
fbgemm_gpu::config::is_feature_enabled(
352+
fbgemm_gpu::config::FeatureGateName::
353+
TBE_USE_TUNED_SEGMENT_LENGTHS_CTA_B200);
354+
if (b200_feature_enabled) {
355+
default_segment_length = 4096;
356+
}
357+
const int max_segment_length_per_cta =
358+
use_deterministic_algorithms ? INT_MAX : default_segment_length;
359+
#endif
360+
361+
Tensor long_run_id_to_really_long_run_ids;
362+
if (use_deterministic_algorithms) {
363+
long_run_id_to_really_long_run_ids =
364+
at::empty(0, sorted_linear_indices_run_lengths.options());
365+
} else {
366+
long_run_id_to_really_long_run_ids = at::empty(
367+
{indices.numel()}, sorted_linear_indices_run_lengths.options());
368+
}
369+
370+
auto num_really_long_run_ids =
371+
at::zeros({1}, indices.options().dtype(at::kInt));
372+
auto grad_accum_counter = at::empty(
373+
use_deterministic_algorithms
374+
? 0
375+
: (indices.numel() / max_segment_length_per_cta),
376+
indices.options().dtype(at::kInt));
377+
378+
constexpr auto fls_ctx = "find_long_segments";
379+
FBGEMM_LAUNCH_KERNEL(
380+
embedding_ops::split_embedding_backward_codegen_find_long_segments,
381+
div_round_up(num_unique, kMaxThreads),
382+
kMaxThreads,
383+
0,
384+
at::cuda::getCurrentCUDAStream(),
385+
PTA_B(sorted_linear_indices_num_runs, int32_t, 1, 32).build(fls_ctx),
386+
PTA_B(sorted_linear_indices_run_lengths, int32_t, 1, 32).build(fls_ctx),
387+
PTA_B(long_run_ids, int32_t, 1, 32).build(fls_ctx),
388+
PTA_B(num_long_run_ids, int32_t, 1, 32).build(fls_ctx),
389+
PTA_B(long_run_id_to_really_long_run_ids, int32_t, 1, 32).build(fls_ctx),
390+
PTA_B(num_really_long_run_ids, int32_t, 1, 32).build(fls_ctx),
391+
PTA_B(grad_accum_counter, int32_t, 1, 32).build(fls_ctx),
392+
max_segment_length_per_warp,
393+
max_segment_length_per_cta,
394+
use_deterministic_algorithms);
395+
396+
return {
397+
linear_indices,
398+
linear_indices_sorted,
399+
sorted_linear_indices_run,
400+
sorted_linear_indices_run_lengths,
401+
sorted_linear_indices_num_runs,
402+
sorted_linear_indices_cumulative_run_lengths,
403+
infos_sorted,
404+
long_run_ids,
405+
num_long_run_ids,
406+
long_run_id_to_really_long_run_ids,
407+
num_really_long_run_ids,
408+
grad_accum_counter};
409+
}
410+
411+
} // namespace fbgemm_gpu
412+
413+
// CUDA dispatch is registered here, co-located with the codegen definition so
414+
// the symbol links; the schema m.def lives in src/split_embeddings_utils/.
415+
TORCH_LIBRARY_FRAGMENT(fbgemm, m) {
416+
DISPATCH_TO_CUDA("tbe_bwd_indices_preproc", tbe_bwd_indices_preproc_cuda);
417+
}
418+
{% endif %}
419+
252420
// clang-format on

fbgemm_gpu/include/fbgemm_gpu/split_embeddings_utils.cuh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,43 @@ transpose_embedding_input(
4343
const int64_t fixed_L_per_warp = 0,
4444
const int64_t num_warps_per_feature = 0);
4545

46+
namespace fbgemm_gpu {
47+
48+
// Combined grad-independent index-preprocessing op for the TBE backward
49+
// (transpose_embedding_input + find_long_segments). Defined AND CUDA-dispatched
50+
// in the generated embedding_backward_split_grad.cu (dispatch must be
51+
// co-located with the definition to link); only the schema m.def lives in
52+
// src/split_embeddings_utils/. Do NOT give these params C++ default arguments:
53+
// the dispatcher always passes all args (Python-facing defaults come from the
54+
// schema string), and defaults baked into the function type break TORCH_FN in
55+
// the CUDA-compiled dispatch TU.
56+
std::tuple<
57+
at::Tensor, // linear_indices
58+
at::Tensor, // linear_indices_sorted
59+
at::Tensor, // sorted_linear_indices_run
60+
at::Tensor, // sorted_linear_indices_run_lengths
61+
at::Tensor, // sorted_linear_indices_num_runs
62+
at::Tensor, // sorted_linear_indices_cumulative_run_lengths
63+
at::Tensor, // infos_sorted
64+
at::Tensor, // long_run_ids
65+
at::Tensor, // num_long_run_ids
66+
at::Tensor, // long_run_id_to_really_long_run_ids
67+
at::Tensor, // num_really_long_run_ids
68+
at::Tensor> // grad_accum_counter
69+
tbe_bwd_indices_preproc_cuda(
70+
const at::Tensor& hash_size_cumsum,
71+
const int64_t total_hash_size_bits,
72+
const at::Tensor& indices,
73+
const at::Tensor& offsets,
74+
const int64_t info_B_num_bits,
75+
const int64_t info_B_mask,
76+
const int64_t total_unique_indices,
77+
const std::optional<at::Tensor>& vbe_b_t_map,
78+
const bool nobag,
79+
const bool is_index_select);
80+
81+
} // namespace fbgemm_gpu
82+
4683
// Use these functions instead of directly calling cub functions
4784
// to reduce code size and compilation time.
4885
// Arguments are the same as cub::DeviceRadixSort::SortPairs

fbgemm_gpu/src/split_embeddings_utils/split_embeddings_utils_cpu.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,20 @@ TORCH_LIBRARY_FRAGMENT(fbgemm, m) {
200200
" int fixed_L_per_warp=0, "
201201
" int num_warps_per_feature=0"
202202
") -> (Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor)");
203+
m.def(
204+
"tbe_bwd_indices_preproc("
205+
" Tensor hash_size_cumsum, "
206+
" int total_hash_size_bits, "
207+
" Tensor indices, "
208+
" Tensor offsets, "
209+
" int info_B_num_bits=26, "
210+
" int info_B_mask=0x2FFFFFF, "
211+
" int total_unique_indices=-1, "
212+
" Tensor? vbe_b_t_map=None, "
213+
" bool nobag=False, "
214+
" bool is_index_select=False"
215+
") -> (Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, "
216+
"Tensor, Tensor, Tensor, Tensor)");
203217
m.def("get_infos_metadata(Tensor unused, int B, int T) -> (int, int)");
204218
m.def(
205219
"generate_vbe_metadata("

0 commit comments

Comments
 (0)