Skip to content

[PyTorch] Make the Triton mask-map permute respect num_out_tokens - #3347

Merged
denera merged 2 commits into
NVIDIA:mainfrom
truong-v:fix/triton-permute-respect-num-out-tokens
Oct 22, 2026
Merged

denera merged 2 commits into
NVIDIA:mainfrom
truong-v:fix/triton-permute-respect-num-out-tokens

Conversation

@truong-v

Copy link
Copy Markdown
Contributor

Description

Fixes #3346.

The Triton mask-map path of moe_permute sizes its output at exactly num_out_tokens rows, but _permute_kernel receives num_out_tokens marked unused and the row-id map assigns destination rows as an unbounded cumsum — so a routing map with more routed entries than num_out_tokens writes the excess rows past the end of the allocation (48 invalid global writes under compute-sanitizer in the linked issue's repro, ending in an unspecified launch failure; 0 errors after this change). The CUDA index-map path behind the same public API implements the capacity limit by marking over-capacity entries -1 in its row map (moe_permute_row_map's idx >= num_out_tokens branch).

This PR gives the Triton path the same semantics at the same place:

  • _row_id_map_pass_2_kernel takes num_out_tokens and marks destinations at or past it as -1 — pass 3 then excludes them from n_routed, and every consumer of the map (permute, unpermute, and their backwards) already skips dropped entries via the existing -1 handling. No compute kernel changes.
  • The PyTorch make_row_id_map and its caller thread num_out_tokens through.
  • The JAX lowering passes an explicit no-drop sentinel (2**31 - 1), keeping JAX behavior unchanged; wiring token_dispatch's num_out_tokens into it can follow separately if desired.
  • New test_permutation_mask_map_capacity_drop (fp32/fp16/bf16): capacity 17 below routing_map.sum(), checking the kept rows against an expert-major reference permutation, the row map's n_routed accounting, and the unpermute of the dropped map. All three fail without the fix and pass with it; the rest of the mask-map suite is unchanged — 151 passed / 113 skipped / 16 failed, with the same 16 failing ids, both with and without the change (those 16 are FP8 DelayedScaling cases that already fail at this commit in my prebuilt-wheel setup, on an NVRTC/CUDA-header version mismatch that has nothing to do with permutation).

Checklist

  • The documentation is up to date with these changes (docstring of make_row_id_map).
  • The tests are up to date with these changes.

Environment

  • Base commit: 07e281f (main); prebuilt transformer-engine-cu12 2.17.0 + source checkout for Python/Triton
  • NVIDIA L40S (sm_89), driver CUDA 12.4
  • torch 2.13.0+cu126, triton 3.7.1, Python 3.12

@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Aug 11, 2026
@greptile-apps

greptile-apps Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

The PyTorch Triton mask-map path now drops destinations at or past num_out_tokens before the map reaches permute and unpermute.

  • PyTorch mask-map permutations drop routed copies beyond output capacity.

No actionable issues were found. Tests were not run during this review.

Reviews (2) · Last reviewed commit: "[PyTorch] Harden the mask-map capacity-d..." · Reviewed by Greptile

@denera denera self-assigned this Oct 2, 2026
@denera
denera self-requested a review October 2, 2026 16:09

@denera denera left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the fix! Everything looks good aside from two particular nits about hardening the unit test for coverage and tolerance. We can launch CI once you make those changes and merge If it comes back clean.

Comment thread tests/pytorch/test_permutation.py Outdated
def test_permutation_mask_map_capacity_drop(torch_dtype):
"""num_out_tokens smaller than routing_map.sum(): over-capacity entries must be
dropped (as the CUDA index-map path does), not written past the output buffer."""
num_tokens, num_expert, hidden_size = 64, 8, 128

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

num_tokens=64 here stays under one 1024-token block so the cross-block prefix is never exercised. Can you parameterize this with an additional num_tokens > 1024 case as well?

Comment thread tests/pytorch/test_permutation.py Outdated
kept_flat[routed_flat_idx[:capacity]] = True
kept_map = kept_flat.view(num_expert, num_tokens).T.contiguous()
ref_unperm = (inp.float().unsqueeze(1) * kept_map.unsqueeze(-1).float()).sum(1)
torch.testing.assert_close(unperm.float(), ref_unperm, atol=1e-2, rtol=1e-2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

atol=rtol=1e-2 seems very relaxed for the FP32 case. We have an assert_close() implementation in the test utils that scales tolerances with dtype. I would recommend using that, or justifying why these tolerances should be kept.

truong-v and others added 2 commits October 19, 2026 23:06
The Triton _permute_kernel received num_out_tokens explicitly marked
unused, and nothing clamped destination rows against it, while the host
sizes the output at exactly num_out_tokens rows: a routing map that
routes more tokens than num_out_tokens wrote the excess rows past the
end of the allocation. The CUDA index-map path behind the same public
API drops over-capacity entries by marking them -1 in the row map
(moe_permute_row_map); do the same in row-id-map pass 2, so every
consumer of the map (permute, unpermute, backward) skips them via the
existing -1 handling. The JAX lowering passes an explicit no-drop
sentinel, keeping its current behavior unchanged.

Signed-off-by: Truong Vu <truongvu0911nd@gmail.com>
Add a 4096-token case so the row-map kernels span several 1024-token
blocks, and compare the unpermuted output with the test-utils
assert_close, which scales tolerances with dtype.

Signed-off-by: Alp Dener <adener@nvidia.com>
@denera
denera force-pushed the fix/triton-permute-respect-num-out-tokens branch from 20ae222 to d5efe56 Compare October 19, 2026 23:19
@denera

denera commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

/te-ci pytorch

@denera denera left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving pending clean CI.

Edit: Delayed due to infra issues, ignore reported CI failures for now.

@denera
denera merged commit c9c9225 into NVIDIA:main Oct 22, 2026
16 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Triton mask-map moe_permute ignores num_out_tokens

3 participants