Skip to content

Commit 17a0604

Browse files
Leon Gaofacebook-github-bot
authored andcommitted
STBE GPU coalescing kernel (#2275)
Summary: Pull Request resolved: #2275 * this is a follow up on GPU coalescing of stbe output, we follow the old idea of sparse feature rebatching to enable this. sequence embedding is afterall jagged tensor in dim D and stbe output is well aligned with input as [sum_l(T, B), D]. * we explictly fork code for avoiding spaming all function into one reorder kernel suite. Reviewed By: jspark1105, 842974287 Differential Revision: D52903658 fbshipit-source-id: cbb5f229c54d4701c6563e90e9a19980dcf781e8
1 parent 5e9722a commit 17a0604

6 files changed

Lines changed: 509 additions & 0 deletions

File tree

fbgemm_gpu/bench/sparse_ops_benchmark.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,102 @@ def reorder_batched_ad_lengths_bench(
533533
)
534534

535535

536+
@cli.command()
537+
@click.option(
538+
"--batch-size", default=32
539+
) # 32 is the representative inference batch size
540+
@click.option("--table-size", default=20)
541+
@click.option("--length", default=512) # long sequence representative case
542+
@click.option("--num-items", default=100)
543+
@click.option("--dim", default=256)
544+
@click.option("--dtype", type=click.Choice(["half", "float"]), default="half")
545+
@click.option("--itype", type=click.Choice(["int", "long"]), default="int")
546+
@click.option("--device", type=str, default="cpu")
547+
def reorder_batched_sequence_embeddings_bench(
548+
batch_size: int,
549+
table_size: int,
550+
length: int,
551+
num_items: int,
552+
dim: int,
553+
dtype: str,
554+
itype: str,
555+
device: str,
556+
) -> None:
557+
assert (
558+
dtype == "float" or dtype == "half"
559+
), "Only 32/16bits floating point number are supported"
560+
data_type = torch.half if dtype == "half" else torch.float
561+
562+
assert itype == "int" or itype == "long", "Only int and long are supported"
563+
index_type = torch.int64 if itype == "long" else torch.int32
564+
565+
cat_sequence_embeddings = torch.random(
566+
size=(batch_size * table_size * num_items * length * dim),
567+
dtype=data_type,
568+
).to(device)
569+
cat_sequence_embeddings_lengths = (
570+
torch.cat(
571+
[
572+
torch.tensor([length for _ in range(table_size * num_items)])
573+
for _ in range(batch_size)
574+
],
575+
0,
576+
)
577+
.to(index_type)
578+
.to(device)
579+
)
580+
581+
batch_offsets = (
582+
(torch.tensor([num_items * b for b in range(batch_size + 1)]).cuda())
583+
.to(index_type)
584+
.to(device)
585+
)
586+
num_items_in_batch = batch_size * num_items
587+
reordered_cat_sequence_embeddings_lengths = (
588+
torch.ops.fbgemm.reorder_batched_ad_lengths(
589+
cat_sequence_embeddings_lengths,
590+
batch_offsets,
591+
num_items_in_batch,
592+
).to(device)
593+
)
594+
595+
cat_sequence_embeddings_offsets = (
596+
torch.ops.fbgemm.asynchronous_complete_cumsum(cat_sequence_embeddings_lengths)
597+
.to(index_type)
598+
.to(device)
599+
)
600+
reordered_cat_sequence_embeddings_offsets = (
601+
torch.ops.fbgemm.asynchronous_complete_cumsum(
602+
reordered_cat_sequence_embeddings_lengths
603+
)
604+
.to(index_type)
605+
.to(device)
606+
)
607+
time, _ = benchmark_torch_function(
608+
torch.ops.fbgemm.reorder_batched_sequence_embeddings,
609+
(
610+
cat_sequence_embeddings_offsets,
611+
cat_sequence_embeddings,
612+
reordered_cat_sequence_embeddings_offsets,
613+
batch_offsets,
614+
num_items_in_batch,
615+
batch_size * table_size * num_items * length,
616+
),
617+
num_warmups=100,
618+
iters=1000,
619+
)
620+
num_bytes = (
621+
batch_size
622+
* table_size
623+
* num_items
624+
* length
625+
* cat_sequence_embeddings.element_size()
626+
)
627+
logging.info(
628+
f"fbgemm_gpu time: {time * 1000:.5f} ms ({num_bytes / time / 1e9:.5f} GB/s)"
629+
)
630+
631+
536632
@cli.command()
537633
@click.option("--num-inputs", default=1024)
538634
@click.option("--rows", default=100)

fbgemm_gpu/include/fbgemm_gpu/sparse_ops.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,14 @@ at::Tensor reorder_batched_ad_indices_gpu(
351351
const bool broadcast_indices = false,
352352
const int64_t num_indices_after_broadcast = -1);
353353

354+
///@ingroup sparse-data-cuda
355+
at::Tensor reorder_batched_sequence_embeddings_gpu(
356+
const at::Tensor& cat_sequence_embeddings_offsets,
357+
const at::Tensor& cat_sequence_embeddings,
358+
const at::Tensor& reordered_cat_sequence_embeddings_offsets,
359+
const at::Tensor& batch_offsets,
360+
const int64_t num_items_in_batch);
361+
354362
///@ingroup sparse-data-cpu
355363
at::Tensor reorder_batched_ad_lengths_cpu(
356364
const at::Tensor& cat_ad_lengths,
@@ -367,6 +375,13 @@ at::Tensor reorder_batched_ad_indices_cpu(
367375
const bool broadcast_indices = false,
368376
const int64_t num_indices_after_broadcast = -1);
369377
///@ingroup sparse-data-cpu
378+
at::Tensor reorder_batched_sequence_embeddings_cpu(
379+
const at::Tensor& cat_sequence_embeddings_offsets,
380+
const at::Tensor& cat_sequence_embeddings,
381+
const at::Tensor& reordered_cat_sequence_embeddings_offsets,
382+
const at::Tensor& batch_offsets,
383+
const int64_t num_items_in_batch);
384+
///@ingroup sparse-data-cpu
370385
at::Tensor cat_reorder_batched_ad_indices_cpu(
371386
const at::Tensor& cat_ad_offsets,
372387
const std::vector<at::Tensor>& cat_ad_indices,

fbgemm_gpu/src/sparse_ops/sparse_ops_cpu.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,107 @@ void cat_reorder_batched_ad_indices_cpu_(
14571457
});
14581458
}
14591459

1460+
template <typename index_t, typename scalar_t>
1461+
void reorder_batched_sequence_embeddings_cpu_(
1462+
const Tensor& cat_sequence_embeddings_offsets,
1463+
const Tensor& cat_sequence_embeddings,
1464+
const Tensor& reordered_cat_sequence_embeddings_offsets,
1465+
const Tensor& batch_offsets,
1466+
const int64_t num_items_in_batch,
1467+
const int32_t dim,
1468+
Tensor& output) {
1469+
const int64_t nB = batch_offsets.numel() - 1;
1470+
const int64_t nT = (reordered_cat_sequence_embeddings_offsets.numel() - 1) /
1471+
num_items_in_batch;
1472+
1473+
const auto* batch_offsets_data = batch_offsets.data_ptr<index_t>();
1474+
const auto* cat_sequence_embeddings_offsets_data =
1475+
cat_sequence_embeddings_offsets.data_ptr<index_t>();
1476+
const auto* reordered_cat_sequence_embeddings_offsets_data =
1477+
reordered_cat_sequence_embeddings_offsets.data_ptr<index_t>();
1478+
const auto* cat_sequence_embeddings_data =
1479+
cat_sequence_embeddings.data_ptr<scalar_t>();
1480+
auto* output_data = output.data_ptr<scalar_t>();
1481+
at::parallel_for(
1482+
0, nB * nT, FALSE_SHARING_PAD, [&](int64_t tb_begin, int64_t tb_end) {
1483+
auto b_begin = tb_begin / nT;
1484+
auto b_end = (tb_end + nT - 1) / nT;
1485+
1486+
for (const auto b : c10::irange(b_begin, b_end)) {
1487+
const auto num_ads_b =
1488+
batch_offsets_data[b + 1] - batch_offsets_data[b];
1489+
int64_t t_begin = (b == b_begin) ? tb_begin % nT : 0;
1490+
int64_t t_end =
1491+
(b == b_end - 1 && tb_end % nT != 0) ? tb_end % nT : nT;
1492+
for (const auto t : c10::irange(t_begin, t_end)) {
1493+
const auto output_segment_offset_start =
1494+
t * num_items_in_batch + batch_offsets_data[b];
1495+
const auto output_segment_start =
1496+
reordered_cat_sequence_embeddings_offsets_data
1497+
[output_segment_offset_start] *
1498+
dim;
1499+
const int32_t input_segment_offset_start =
1500+
nT * batch_offsets_data[b] + t * num_ads_b;
1501+
const int32_t input_segment_offset_end =
1502+
input_segment_offset_start + num_ads_b;
1503+
const auto input_segment_start =
1504+
cat_sequence_embeddings_offsets_data
1505+
[input_segment_offset_start] *
1506+
dim;
1507+
const auto input_segment_end =
1508+
cat_sequence_embeddings_offsets_data[input_segment_offset_end] *
1509+
dim;
1510+
const auto num_elements = (input_segment_end - input_segment_start);
1511+
1512+
for (auto i : c10::irange(num_elements)) {
1513+
// TODO memcpy once this path is heavily used?
1514+
output_data[output_segment_start + i] =
1515+
cat_sequence_embeddings_data[input_segment_start + i];
1516+
}
1517+
}
1518+
}
1519+
});
1520+
}
1521+
1522+
Tensor reorder_batched_sequence_embeddings_cpu(
1523+
const Tensor& cat_sequence_embeddings_offsets,
1524+
const Tensor& cat_sequence_embeddings,
1525+
const Tensor& reordered_cat_sequence_embeddings_offsets,
1526+
const Tensor& batch_offsets,
1527+
const int64_t num_items_in_batch) {
1528+
TENSOR_ON_CPU(cat_sequence_embeddings_offsets);
1529+
TENSOR_ON_CPU(cat_sequence_embeddings);
1530+
TENSOR_ON_CPU(reordered_cat_sequence_embeddings_offsets);
1531+
TENSOR_ON_CPU(batch_offsets);
1532+
TORCH_CHECK(cat_sequence_embeddings.dim() == 2);
1533+
// reorder embeddings from (ragged) [B x T x #num_ads_B_{i} x length_{B_{i},
1534+
// t, a})x D] to [T][B][#num_ads_b][length_{b, t, a}][D], i.e.
1535+
// [sum(length_{B_{i}, t, a}), D]
1536+
Tensor reordered_cat_ad_indices = at::empty_like(
1537+
cat_sequence_embeddings, cat_sequence_embeddings.options());
1538+
1539+
AT_DISPATCH_INDEX_TYPES(
1540+
cat_sequence_embeddings_offsets.scalar_type(),
1541+
"reorder_batched_sequence_embeddings_cpu_kernel_1",
1542+
[&] {
1543+
AT_DISPATCH_ALL_TYPES(
1544+
cat_sequence_embeddings.scalar_type(),
1545+
"reorder_eorder_batched_sequence_embeddings_cpu_kernel_2",
1546+
[&] {
1547+
reorder_batched_sequence_embeddings_cpu_<index_t, scalar_t>(
1548+
cat_sequence_embeddings_offsets,
1549+
cat_sequence_embeddings,
1550+
reordered_cat_sequence_embeddings_offsets,
1551+
batch_offsets,
1552+
num_items_in_batch,
1553+
cat_sequence_embeddings.size(1),
1554+
reordered_cat_ad_indices);
1555+
});
1556+
});
1557+
1558+
return reordered_cat_ad_indices;
1559+
}
1560+
14601561
Tensor reorder_batched_ad_indices_cpu(
14611562
const Tensor& cat_ad_offsets,
14621563
const Tensor& cat_ad_indices,
@@ -2752,6 +2853,8 @@ TORCH_LIBRARY_FRAGMENT(fbgemm, m) {
27522853
m.def(
27532854
"asynchronous_complete_cumsum(Tensor t_in) -> Tensor",
27542855
{PT2_COMPLIANT_TAG});
2856+
m.def(
2857+
"reorder_batched_sequence_embeddings(Tensor cat_sequence_embeddings_offsets, Tensor cat_sequence_embeddings, Tensor reordered_cat_sequence_embeddings_offsets, Tensor batch_offsets, SymInt num_items_in_batch) -> Tensor");
27552858
m.def(
27562859
"reorder_batched_ad_lengths(Tensor cat_ad_lengths, Tensor batch_offsets, SymInt num_ads_in_batch, bool broadcast_lengths=False) -> Tensor");
27572860
m.def(
@@ -2856,6 +2959,9 @@ TORCH_LIBRARY_IMPL(fbgemm, CPU, m) {
28562959
DISPATCH_TO_CPU(
28572960
"cat_reorder_batched_ad_indices",
28582961
fbgemm_gpu::cat_reorder_batched_ad_indices_cpu);
2962+
DISPATCH_TO_CPU(
2963+
"reorder_batched_sequence_embeddings",
2964+
fbgemm_gpu::reorder_batched_sequence_embeddings_cpu);
28592965
DISPATCH_TO_CPU("offsets_range", fbgemm_gpu::offsets_range_cpu);
28602966
DISPATCH_TO_CPU(
28612967
"batched_unary_embeddings",

fbgemm_gpu/src/sparse_ops/sparse_ops_gpu.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,9 @@ TORCH_LIBRARY_IMPL(fbgemm, CUDA, m) {
751751
"reorder_batched_ad_lengths", fbgemm_gpu::reorder_batched_ad_lengths_gpu);
752752
DISPATCH_TO_CUDA(
753753
"reorder_batched_ad_indices", fbgemm_gpu::reorder_batched_ad_indices_gpu);
754+
DISPATCH_TO_CUDA(
755+
"reorder_batched_sequence_embeddings",
756+
fbgemm_gpu::reorder_batched_sequence_embeddings_gpu);
754757
DISPATCH_TO_CUDA(
755758
"batched_unary_embeddings",
756759
fbgemm_gpu::lookup_batched_unary_embedding_function);

fbgemm_gpu/src/sparse_ops/sparse_reorder_batched_ad.cu

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,117 @@ DLL_PUBLIC Tensor reorder_batched_ad_indices_gpu(
273273
return reordered_cat_ad_indices;
274274
}
275275
276+
template <typename Dtype, typename index_t = int32_t>
277+
__global__
278+
__launch_bounds__(kMaxThreads) void reorder_batched_sequence_embeddings_kernel(
279+
// reorder embeddings from (ragged) [B x T x #num_ads_B_{i} x length_{B_{i},
280+
// t, a})x D] to [T][B][#num_ads_b][length_{b, t, a}][D], i.e.
281+
// [sum(length_{B_{i}, t, a}), D]
282+
const at::PackedTensorAccessor32<index_t, 1, at::RestrictPtrTraits>
283+
cat_sequence_embeddings_offsets,
284+
const at::PackedTensorAccessor32<Dtype, 2, at::RestrictPtrTraits>
285+
cat_sequence_embeddings,
286+
const at::PackedTensorAccessor32<index_t, 1, at::RestrictPtrTraits>
287+
reordered_cat_sequence_embeddings_offsets,
288+
at::PackedTensorAccessor32<Dtype, 2, at::RestrictPtrTraits>
289+
reordered_cat_sequence_embeddings,
290+
const at::PackedTensorAccessor32<index_t, 1, at::RestrictPtrTraits>
291+
batch_offsets,
292+
const int32_t T,
293+
const int32_t D) {
294+
const int32_t B = batch_offsets.size(0) - 1;
295+
const int32_t num_items_in_batch = batch_offsets[B];
296+
// warp-per-segment.
297+
const int32_t b_t = blockIdx.x * blockDim.y + threadIdx.y;
298+
const int32_t b = b_t % B;
299+
const int32_t t = b_t / B;
300+
if (t >= T) {
301+
return;
302+
}
303+
304+
const auto num_ads_b = batch_offsets[b + 1] - batch_offsets[b];
305+
const auto output_segment_offset_start =
306+
t * num_items_in_batch + batch_offsets[b];
307+
const auto output_segment_start =
308+
reordered_cat_sequence_embeddings_offsets[output_segment_offset_start];
309+
const int32_t input_segment_offset_start =
310+
T * batch_offsets[b] + t * num_ads_b;
311+
const int32_t input_segment_offset_end =
312+
input_segment_offset_start + num_ads_b;
313+
const auto input_segment_start =
314+
cat_sequence_embeddings_offsets[input_segment_offset_start];
315+
const auto input_segment_end =
316+
cat_sequence_embeddings_offsets[input_segment_offset_end];
317+
const auto num_elements = input_segment_end - input_segment_start;
318+
319+
for (size_t i = 0; i < input_segment_end - input_segment_start; i++) {
320+
const auto output_offset = output_segment_start + i;
321+
const auto input_offset = input_segment_start + i;
322+
for (int32_t d = threadIdx.x; d < D; d += blockDim.x) {
323+
reordered_cat_sequence_embeddings[output_offset][d] =
324+
cat_sequence_embeddings[input_offset][d];
325+
}
326+
}
327+
}
328+
329+
DLL_PUBLIC Tensor reorder_batched_sequence_embeddings_gpu(
330+
const Tensor& cat_sequence_embeddings_offsets,
331+
const Tensor& cat_sequence_embeddings,
332+
const Tensor& reordered_cat_sequence_embeddings_offsets,
333+
const Tensor& batch_offsets,
334+
const int64_t num_items_in_batch) {
335+
TENSORS_ON_SAME_CUDA_GPU_IF_NOT_OPTIONAL(
336+
cat_sequence_embeddings_offsets,
337+
cat_sequence_embeddings,
338+
reordered_cat_sequence_embeddings_offsets,
339+
batch_offsets);
340+
const auto cat_sequence_embeddings_contig =
341+
cat_sequence_embeddings.expect_contiguous();
342+
343+
at::cuda::OptionalCUDAGuard device_guard;
344+
device_guard.set_index(cat_sequence_embeddings_offsets.get_device());
345+
346+
const int64_t B = batch_offsets.numel() - 1;
347+
const int64_t T = (reordered_cat_sequence_embeddings_offsets.numel() - 1) /
348+
num_items_in_batch;
349+
const int64_t D = cat_sequence_embeddings.size(1);
350+
Tensor reordered_cat_sequence_embeddings =
351+
at::empty_like(cat_sequence_embeddings);
352+
353+
const dim3 threads(32, 32);
354+
const dim3 blocks((B * T + 32 - 1) / 32);
355+
356+
AT_DISPATCH_FLOATING_TYPES_AND2(
357+
at::ScalarType::Half,
358+
at::ScalarType::BFloat16,
359+
cat_sequence_embeddings.scalar_type(),
360+
"reorder_batched_sequence_embeddings_gpu_kernel_1",
361+
[&] {
362+
AT_DISPATCH_INDEX_TYPES(
363+
cat_sequence_embeddings_offsets.scalar_type(),
364+
"reorder_batched_sequence_embeddings_gpu_kernel_2",
365+
[&] {
366+
reorder_batched_sequence_embeddings_kernel<scalar_t, index_t><<<
367+
blocks,
368+
threads,
369+
0,
370+
at::cuda::getCurrentCUDAStream()>>>(
371+
cat_sequence_embeddings_offsets
372+
.packed_accessor32<index_t, 1, at::RestrictPtrTraits>(),
373+
cat_sequence_embeddings_contig
374+
->packed_accessor32<scalar_t, 2, at::RestrictPtrTraits>(),
375+
reordered_cat_sequence_embeddings_offsets
376+
.packed_accessor32<index_t, 1, at::RestrictPtrTraits>(),
377+
reordered_cat_sequence_embeddings
378+
.packed_accessor32<scalar_t, 2, at::RestrictPtrTraits>(),
379+
batch_offsets
380+
.packed_accessor32<index_t, 1, at::RestrictPtrTraits>(),
381+
T,
382+
D);
383+
C10_CUDA_KERNEL_LAUNCH_CHECK();
384+
});
385+
});
386+
return reordered_cat_sequence_embeddings;
387+
}
388+
276389
} // namespace fbgemm_gpu

0 commit comments

Comments
 (0)