Skip to content

Commit 0f5414f

Browse files
q10meta-codesync[bot]
authored andcommitted
Fix the grid problem with sparse permute 2d (V2: threshold-guarded) (#5898)
Summary: Pull Request resolved: #5898 X-link: https://github.com/facebookresearch/FBGEMM/pull/2817 Migrates the four host-side cap sites in `sparse_permute_2d.cu` from the unconditional ROCm cap pattern (V1) to the new `fbgemm_gpu::utils::cuda::determine_grid_blocks` helper (introduced in D106267802) with the default `BlockCapPolicy::OverflowOnly`. Net behaviour change vs V1: - ROCm small/medium grid (`blocks * threads_per_block <= UINT32_MAX`): uncapped grid restored. Was: capped to `MAX_THREAD_BLOCKS_FACTOR * #SMs` unconditionally. Now: passes through unchanged (matches pre-D104903707 behaviour). - ROCm large grid (`blocks * threads_per_block > UINT32_MAX`): cap still applied. The kernel grid-strides over `b_t` so capping is correctness-preserving, and this is the regime where the HIP `2^32` thread-per-launch limit would otherwise fire. - NVIDIA: bit-identical to V1 (the threshold check lives entirely under `#ifdef USE_ROCM` inside the helper). The four sites: - `permute_2D_sparse_data_cuda` `blocks_1` (line ~223), `threads_1=256`. - `permute_2D_sparse_data_cuda` `blocks_2` (line ~262), block size `dim3(32, BT_blocks=32)` so threads_per_block = `32 * BT_blocks = 1024`. - `permute_sparse_features_cuda` `blocks_1` (line ~439), `threads_1=256`. - `permute_sparse_features_cuda` `blocks_2` (line ~486), block size same as above (1024). Drive-by: switches `cuda_calc_block_count` (y/z-dim 65535 cap) to `determine_grid_blocks` (which uses `cuda_calc_xblock_count` internally with the 2^31-1 x-dim cap). These launches are 1-D x-dim launches so `cuda_calc_xblock_count` is the correct primitive; the kernels grid-stride so any change in grid size is correctness-preserving. Reviewed By: spcyppt Differential Revision: D104937969 fbshipit-source-id: e7c89c0942c8665595772b58ca72c0bf88177a0b
1 parent 57f3a8b commit 0f5414f

2 files changed

Lines changed: 179 additions & 6 deletions

File tree

fbgemm_gpu/src/sparse_ops/sparse_permute_2d.cu

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,12 @@ permute_2D_sparse_data_cuda(
221221
permuted_lengths = at::empty({T, B}, lengths.options());
222222

223223
constexpr int32_t threads_1 = 256;
224-
const auto blocks_1 = cuda_calc_block_count(B * T, threads_1);
224+
// HIP enforces a hard limit of 2^32 total threads per launch (unlike CUDA,
225+
// which silently wraps). permute_2D_lengths_kernel uses CUDA_KERNEL_LOOP,
226+
// which already grid-strides, so capping is correctness-preserving.
227+
// See: https://github.com/ROCm/hip/issues/2253
228+
const auto blocks_1 = utils::cuda::cap_grid_dim_x_from_workload(
229+
B * T, threads_1, at::cuda::getCurrentCUDAStream());
225230
AT_DISPATCH_INDEX_TYPES(
226231
lengths.scalar_type(), "permute_2D_lengths_kernel", [&] {
227232
FBGEMM_LAUNCH_KERNEL(
@@ -250,7 +255,15 @@ permute_2D_sparse_data_cuda(
250255

251256
constexpr int32_t BT_blocks = 32;
252257
dim3 threads_2(32, BT_blocks);
253-
const auto blocks_2 = cuda_calc_block_count(B * T, BT_blocks);
258+
// HIP enforces a hard limit of 2^32 total threads per launch (unlike CUDA,
259+
// which silently wraps). Both permute_2D_data_kernel and
260+
// permute_2D_data_kernel_vec grid-stride over b_t, so capping is
261+
// correctness-preserving.
262+
// See: https://github.com/ROCm/hip/issues/2253
263+
const auto blocks_2 = utils::cuda::cap_grid_dim_x(
264+
cuda_calc_xblock_count(B * T, BT_blocks),
265+
BT_blocks * 32,
266+
at::cuda::getCurrentCUDAStream());
254267
permuted_indices = at::empty(permuted_indices_size, indices.options());
255268

256269
AT_DISPATCH_INDEX_TYPES(
@@ -415,8 +428,12 @@ permute_sparse_features_cuda(
415428
permuted_lengths = at::empty({num_output_features, B}, lengths.options());
416429

417430
constexpr int32_t threads_1 = 256;
418-
const auto blocks_1 =
419-
cuda_calc_block_count(B * num_output_features, threads_1);
431+
// HIP enforces a hard limit of 2^32 total threads per launch (unlike CUDA,
432+
// which silently wraps). permute_2D_lengths_kernel uses CUDA_KERNEL_LOOP,
433+
// which already grid-strides, so capping is correctness-preserving.
434+
// See: https://github.com/ROCm/hip/issues/2253
435+
const auto blocks_1 = utils::cuda::cap_grid_dim_x_from_workload(
436+
B * num_output_features, threads_1, at::cuda::getCurrentCUDAStream());
420437
AT_DISPATCH_INDEX_TYPES(
421438
lengths.scalar_type(), "permute_2D_lengths_kernel", [&] {
422439
FBGEMM_LAUNCH_KERNEL(
@@ -452,8 +469,14 @@ permute_sparse_features_cuda(
452469

453470
constexpr int32_t BT_blocks = 32;
454471
dim3 threads_2(32, BT_blocks);
455-
const auto blocks_2 =
456-
cuda_calc_block_count(B * num_output_features, BT_blocks);
472+
// HIP enforces a hard limit of 2^32 total threads per launch (unlike CUDA,
473+
// which silently wraps). permute_indices_weights_kernel grid-strides over
474+
// b_t, so capping is correctness-preserving.
475+
// See: https://github.com/ROCm/hip/issues/2253
476+
const auto blocks_2 = utils::cuda::cap_grid_dim_x(
477+
cuda_calc_xblock_count(B * num_output_features, BT_blocks),
478+
BT_blocks * 32,
479+
at::cuda::getCurrentCUDAStream());
457480
permuted_indices = at::empty(permuted_lengths_sum, indices.options());
458481
if (weights.has_value()) {
459482
const Tensor weights_value = weights.value();

fbgemm_gpu/test/sparse/permute_indices_test.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,156 @@ def test_permute_1D_sparse_data_large_grid(self) -> None:
866866
torch.testing.assert_close(permuted_indices_gpu.cpu(), permuted_indices_cpu)
867867
self.assertIsNone(permuted_weights_gpu)
868868

869+
@unittest.skipIf(*gpu_unavailable)
870+
# Skip on GPUs with insufficient HBM (need ~512 MB for the int32
871+
# lengths tensor at the chosen B).
872+
@unittest.skipIf(*gpu_memory_lt_gb(4))
873+
def test_permute_2D_sparse_data_large_grid(self) -> None:
874+
"""
875+
Reproduces the HIP grid-overflow bug in permute_2D_sparse_data_cuda
876+
and verifies output correctness at the same scale.
877+
878+
With BT_blocks=32 and dim3(32, 32) (block size 1024), the
879+
permute_2D_data_kernel_vec launch grid is
880+
cuda_calc_block_count(B*T, 32). For B*T > 2**27, total threads
881+
exceed the HIP 2**32 limit, causing FBGEMM_LAUNCH_KERNEL ->
882+
KernelLauncher::checkThreadCountNotExceeded to TORCH_CHECK-fail on
883+
ROCm pre-fix. With the production fix in place, this test
884+
additionally validates output correctness against the CPU dispatch
885+
of the same op — the GPU output must match the CPU reference
886+
element-for-element.
887+
888+
Uses ``T=2, B=2**26+1`` so ``B*T = 2**27 + 2`` strictly trips the
889+
threshold. ``lengths`` is sparse: all zero except for four known
890+
non-zero positions (one per row, plus one mid-row), so HBM usage
891+
stays bounded (~537 MB int32) while the permutation logic is
892+
still exercised. ``permute = [1, 0]`` is a deterministic row swap
893+
on the T axis with ``perm[i] != i`` for every i, so any
894+
"kernel computed identity instead of permutation" or wrong-``b_t``
895+
decoding bug surfaces in the assertion below.
896+
"""
897+
898+
# Choose B*T so that total threads strictly exceeds 2**32:
899+
# cuda_calc_block_count(B*T, 32) * 1024 ~= B*T * 32; need B*T > 2**27.
900+
T = 2
901+
B = (1 << 26) + 1
902+
903+
device = torch.device(torch.accelerator.current_accelerator() or "cuda")
904+
905+
# Deterministic non-identity permute: row swap on the T axis.
906+
# perm[0] == 1 and perm[1] == 0, so perm[i] != i for every i.
907+
perm_cpu = torch.tensor([1, 0], dtype=torch.int32)
908+
permute = perm_cpu.to(device)
909+
910+
# Sparse non-zero lengths at four known positions. Total = 11.
911+
lengths_cpu = torch.zeros((T, B), dtype=torch.int32)
912+
lengths_cpu[0, 0] = 3
913+
lengths_cpu[0, B // 2] = 5
914+
lengths_cpu[1, 0] = 2
915+
lengths_cpu[1, B - 1] = 1
916+
lengths = lengths_cpu.to(device)
917+
918+
# Distinct indices per segment so the permutation is fully observable.
919+
indices_cpu = torch.arange(11, dtype=torch.int32)
920+
indices = indices_cpu.to(device)
921+
922+
# CPU reference oracle — same op, different dispatch.
923+
(
924+
permuted_lengths_cpu,
925+
permuted_indices_cpu,
926+
_permuted_weights_cpu,
927+
) = torch.ops.fbgemm.permute_2D_sparse_data(
928+
perm_cpu, lengths_cpu, indices_cpu, None, None
929+
)
930+
931+
# GPU op under test. Pre-fix, this launch trips
932+
# KernelLauncher::checkThreadCountNotExceeded on ROCm.
933+
(
934+
permuted_lengths_gpu,
935+
permuted_indices_gpu,
936+
permuted_weights_gpu,
937+
) = torch.ops.fbgemm.permute_2D_sparse_data(
938+
permute, lengths, indices, None, None
939+
)
940+
941+
torch.testing.assert_close(permuted_lengths_gpu.cpu(), permuted_lengths_cpu)
942+
torch.testing.assert_close(permuted_indices_gpu.cpu(), permuted_indices_cpu)
943+
self.assertIsNone(permuted_weights_gpu)
944+
945+
@unittest.skipIf(*gpu_unavailable)
946+
# Skip on GPUs with insufficient HBM (need ~512 MB for the int32
947+
# lengths tensor at the chosen B).
948+
@unittest.skipIf(*gpu_memory_lt_gb(4))
949+
def test_permute_sparse_features_large_grid(self) -> None:
950+
"""
951+
Reproduces the HIP grid-overflow bug in permute_sparse_features_cuda
952+
and verifies output correctness at the same scale.
953+
954+
With BT_blocks=32 and dim3(32, 32) (block size 1024), the
955+
permute_indices_weights_kernel launch grid is
956+
cuda_calc_block_count(B*T, 32). For B*T > 2**27, total threads
957+
exceed the HIP 2**32 limit, causing FBGEMM_LAUNCH_KERNEL ->
958+
KernelLauncher::checkThreadCountNotExceeded to TORCH_CHECK-fail on
959+
ROCm pre-fix. With the production fix in place, this test
960+
additionally validates output correctness against the CPU dispatch
961+
of the same op — the GPU output must match the CPU reference
962+
element-for-element.
963+
964+
Uses ``T=2, B=2**26+1`` so ``B*T = 2**27 + 2`` strictly trips the
965+
threshold. ``lengths`` is sparse: all zero except for four known
966+
non-zero positions (one per row, plus one mid-row), so HBM usage
967+
stays bounded (~537 MB int32) while the permutation logic is
968+
still exercised. ``permute = [1, 0]`` is a deterministic row swap
969+
on the T axis with ``perm[i] != i`` for every i, so any
970+
"kernel computed identity instead of permutation" or wrong-``b_t``
971+
decoding bug surfaces in the assertion below.
972+
"""
973+
974+
# Choose B*T so that total threads strictly exceeds 2**32:
975+
# cuda_calc_block_count(B*T, 32) * 1024 ~= B*T * 32; need B*T > 2**27.
976+
T = 2
977+
B = (1 << 26) + 1
978+
979+
device = torch.device(torch.accelerator.current_accelerator() or "cuda")
980+
981+
# Deterministic non-identity permute: row swap on the T axis.
982+
# perm[0] == 1 and perm[1] == 0, so perm[i] != i for every i.
983+
perm_cpu = torch.tensor([1, 0], dtype=torch.int32)
984+
permute = perm_cpu.to(device)
985+
986+
# Sparse non-zero lengths at four known positions. Total = 11.
987+
lengths_cpu = torch.zeros((T, B), dtype=torch.int32)
988+
lengths_cpu[0, 0] = 3
989+
lengths_cpu[0, B // 2] = 5
990+
lengths_cpu[1, 0] = 2
991+
lengths_cpu[1, B - 1] = 1
992+
lengths = lengths_cpu.to(device)
993+
994+
# Distinct indices per segment so the permutation is fully observable.
995+
indices_cpu = torch.arange(11, dtype=torch.int32)
996+
indices = indices_cpu.to(device)
997+
998+
# CPU reference oracle — same op, different dispatch.
999+
(
1000+
permuted_lengths_cpu,
1001+
permuted_indices_cpu,
1002+
_permuted_weights_cpu,
1003+
) = torch.ops.fbgemm.permute_sparse_features(
1004+
perm_cpu, lengths_cpu, indices_cpu, None
1005+
)
1006+
1007+
# GPU op under test. Pre-fix, this launch trips
1008+
# KernelLauncher::checkThreadCountNotExceeded on ROCm.
1009+
(
1010+
permuted_lengths_gpu,
1011+
permuted_indices_gpu,
1012+
permuted_weights_gpu,
1013+
) = torch.ops.fbgemm.permute_sparse_features(permute, lengths, indices, None)
1014+
1015+
torch.testing.assert_close(permuted_lengths_gpu.cpu(), permuted_lengths_cpu)
1016+
torch.testing.assert_close(permuted_indices_gpu.cpu(), permuted_indices_cpu)
1017+
self.assertIsNone(permuted_weights_gpu)
1018+
8691019

8701020
extend_test_class(PermuteIndicesTest)
8711021

0 commit comments

Comments
 (0)