Skip to content

[PyTorch]Validate grouped split alignment on device - #3745

Merged
vthumbe1503 merged 12 commits into
NVIDIA:mainfrom
vthumbe1503:validate-grouped-split-alignment
Oct 29, 2026
Merged

vthumbe1503 merged 12 commits into
NVIDIA:mainfrom
vthumbe1503:validate-grouped-split-alignment

Conversation

@vthumbe1503

@vthumbe1503 vthumbe1503 commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Validate grouped split sizes inside the existing device-side multi-output offset scan. GroupedLinear requests 128-row alignment; the generic grouped MLP offset path requests 256-row alignment. The check runs on every CUDA graph replay without a host read or an added kernel launch.

nvte_splits_to_offsets_multi_v2(split_sizes, split_alignment, outputs, ...) exposes the check. The original C API symbol remains and forwards with alignment 1. The PyTorch private binding accepts an optional split_alignment.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Changes

  • Validate every device-resident split in the prefix-sum kernel, including later 256-element scan chunks. NVTE_DEVICE_ERROR prints and asserts; __trap() keeps the failure effective in Release builds where NDEBUG removes the assert. NVTE_BUILD_DEBUG=1 enables the assert when building, not at runtime.
  • Use a bitmask for power-of-two alignments and run the existing scan with 128 threads for up to 128 splits, retaining 256 threads for larger inputs.
  • Pass alignment from GroupedLinear and grouped MLP into the multi-output scan.
  • Keep the device-trap failure case out of the automated tests because it invalidates the test process's CUDA context. Existing grouped tensor tests pass; this PR has no negative alignment regression test.
  • Document the device-side alignment contract.

Offset-scan GPU time on GB200

Measured the one- and four-output scan with 8 and 16 experts, 256 rows per expert, and split_alignment=128 on the PR. The four-output call uses strides [1,1,7168,4096], matching a grouped linear FC1 shape. Inputs are CUDA int64 splits; the first output is int32 and the remaining outputs are int64. Four-output calls use bulk allocation.

Experts Outputs Base µs/call PR µs/call PR − Base µs
8 1 2.884 2.913 +0.029
16 1 2.883 2.912 +0.028
8 4 3.651 3.648 −0.003
16 4 3.651 3.652 +0.001

GPU times are medians of seven samples, each timing 40 replays of a CUDA graph containing 64 scan calls. Baseline e32e1238 and PR 06228625 used the same GB200 and container, with their respective built extensions.

Checklist

  • I have read and followed the contributing guidelines
  • The functionality is complete for paths that run the multi-output scan
  • I have commented the device assertion and barrier behavior
  • I have made corresponding changes to the API documentation
  • My changes generate no new warnings
  • I have added tests that prove the behavior
  • The focused existing tests pass locally

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
@vthumbe1503
vthumbe1503 marked this pull request as ready for review October 26, 2026 21:48
@vthumbe1503

Copy link
Copy Markdown
Collaborator Author

/te-ci L0

@vthumbe1503 vthumbe1503 changed the title Validate grouped split alignment on device [PyTorch]Validate grouped split alignment on device Oct 26, 2026
@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with no new actionable issues found.

Summary

Adds device-side split alignment checks to the existing offset scan. The original C API keeps its behavior, while PyTorch callers can request alignment.

  • Grouped offset scans check each split against its requested alignment.

Reviews (6) · Last reviewed commit: "Apply suggestion from @timmoon10" · Reviewed by Greptile

Comment thread tests/pytorch/test_grouped_tensor.py Resolved
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
timmoon10
timmoon10 previously approved these changes Oct 29, 2026

@timmoon10 timmoon10 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, with minor suggestions.

Comment on lines +90 to 112
// Validate before the scan. All threads leave together so no thread waits at a barrier
// after another thread has trapped on invalid split metadata.
const int64_t split =
idx < args.num_splits ? load_split_size(args.split_sizes, args.split_sizes_dtype, idx) : 0;
if (args.split_alignment > 1) {
const size_t alignment_mask = args.split_alignment - 1;
bool misaligned;
if ((args.split_alignment & alignment_mask) == 0) {
misaligned = (static_cast<size_t>(split) & alignment_mask) != 0;
} else {
misaligned = static_cast<size_t>(split) % args.split_alignment != 0;
}
const bool invalid = idx < args.num_splits && (split < 0 || misaligned);
if (__syncthreads_or(invalid)) {
if (tid == 0) {
NVTE_DEVICE_ERROR("Grouped split size is not divisible by split_alignment.");
__trap(); // NVTE_DEVICE_ERROR's assert is compiled out of release builds.
}
return;
}
}
block_scan[tid] = split;
__syncthreads();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but bitmasking seems overkill for a kernel that's unlikely to be compute-bound. The following is a little simpler.

Suggested change
// Validate before the scan. All threads leave together so no thread waits at a barrier
// after another thread has trapped on invalid split metadata.
const int64_t split =
idx < args.num_splits ? load_split_size(args.split_sizes, args.split_sizes_dtype, idx) : 0;
if (args.split_alignment > 1) {
const size_t alignment_mask = args.split_alignment - 1;
bool misaligned;
if ((args.split_alignment & alignment_mask) == 0) {
misaligned = (static_cast<size_t>(split) & alignment_mask) != 0;
} else {
misaligned = static_cast<size_t>(split) % args.split_alignment != 0;
}
const bool invalid = idx < args.num_splits && (split < 0 || misaligned);
if (__syncthreads_or(invalid)) {
if (tid == 0) {
NVTE_DEVICE_ERROR("Grouped split size is not divisible by split_alignment.");
__trap(); // NVTE_DEVICE_ERROR's assert is compiled out of release builds.
}
return;
}
}
block_scan[tid] = split;
__syncthreads();
// Load input from global memory into shared memory
if (idx < args.num_splits) {
block_scan[tid] = load_split_size(args.split_sizes, args.split_sizes_dtype, idx);
} else {
block_scan[tid] = 0;
}
if (args.split_alignment > 1) {
// Check that splits have required alignment
if (__syncthreads_or(block_scan[tid] % args.split_alignment)) {
if (tid == 0) {
NVTE_DEVICE_ERROR("Grouped split size is not divisible by split_alignment.");
__trap(); // NVTE_DEVICE_ERROR's assert is compiled out of release builds.
}
return;
}
} else {
__syncthreads();
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I evaluated this change. And not doing the bit masking costs us 0.07us consistently for the kernel.

Comment on lines +209 to +213
if (num_splits <= s2o::kSmallThreadsPerBlock) {
s2o::kernel<s2o::kSmallThreadsPerBlock><<<1, s2o::kSmallThreadsPerBlock, 0, stream>>>(args);
} else {
s2o::kernel<s2o::kThreadsPerBlock><<<1, s2o::kThreadsPerBlock, 0, stream>>>(args);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there reason to support separate kernels for these two cases? This kernel seems launch-bound, so the smaller variant doesn't give us much perf benefit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The improves perf by about 0.2 us. I would assume this is purely warp scheduling overhead (4 vs 8 warps). But at the scale of 2-3 us for the kernel that is 10% perf benefit. And so I kept this change. But you can see that we are loosing the perf by about 0.2 us due to the introduction of alginment check.

Co-authored-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Signed-off-by: vthumbe1503 <vthumbe@nvidia.com>
Co-authored-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Signed-off-by: vthumbe1503 <vthumbe@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants