Repository navigation
[Common][PyTorch] Add NVFP4 EP dispatch and combine backward #3753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sraman-rgb
wants to merge
7
commits into
NVIDIA:main
Choose a base branch
from
sraman-rgb:sraman/nvfp4-ep-direct-receive
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
f938cee
[Common][PyTorch] Add NVFP4 EP dispatch and combine backward with dir…
sraman-rgb 347e624
[PyTorch] Fix NVFP4 EP device selection and zero-copy capture of the …
sraman-rgb ce53965
Merge remote-tracking branch 'upstream/main' into sraman/nvfp4-ep-dir…
sraman-rgb 2804cc8
[PyTorch] EP combine: option to return the BF16 grad under NVFP4 auto…
sraman-rgb a3c6dea
[PyTorch] EP NVFP4: pass the layer arguments that upstream main now r…
sraman-rgb d5d9557
[PyTorch] EP test: compare the opt-out combine grad with an alignment…
sraman-rgb 6d83d2a
[PyTorch] EP test: keep the alignment-pass decorator on the direct-ca…
sraman-rgb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
transformer_engine/pytorch/csrc/extensions/ep_nvfp4.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /************************************************************************* | ||
| * Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * | ||
| * See LICENSE for license information. | ||
| ************************************************************************/ | ||
|
|
||
| // NVFP4 token rows for the EP dispatch and combine backward. Independent of NCCL EP. | ||
|
|
||
| #include <ATen/cuda/CUDAContext.h> | ||
| #include <c10/cuda/CUDAGuard.h> | ||
|
|
||
| #include <optional> | ||
| #include <vector> | ||
|
|
||
| #include "../common.h" | ||
| #include "../extensions.h" | ||
| #include "../pybind.h" | ||
| #include "common/util/system.h" | ||
| #include "transformer_engine/ep.h" | ||
|
|
||
| namespace transformer_engine::pytorch { | ||
|
|
||
| namespace { | ||
|
|
||
| int64_t nvfp4_row_bytes(int64_t hidden) { | ||
| return static_cast<int64_t>(roundup(static_cast<size_t>(hidden / 2 + hidden / 16 + 4), 16)); | ||
| } | ||
|
|
||
| int use_fast_math() { return transformer_engine::getenv<bool>("NVTE_USE_FAST_MATH") ? 1 : 0; } | ||
|
|
||
| // (seed, offset) from torch's CUDA generator; under graph capture the offset is read on device, | ||
| // so every replay draws fresh bits. | ||
| at::Tensor make_rng_state(size_t elts_per_thread) { | ||
| auto gen = at::get_generator_or_default<at::CUDAGeneratorImpl>( | ||
| std::nullopt, at::cuda::detail::getDefaultCUDAGenerator()); | ||
| at::PhiloxCudaState philox_args = init_philox_state(gen, elts_per_thread); | ||
| auto rng_state = at::empty({2}, at::TensorOptions().dtype(at::kLong).device(at::kCUDA)); | ||
| philox_unpack(philox_args, static_cast<int64_t *>(rng_state.data_ptr())); | ||
| return rng_state; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| at::Tensor ep_nvfp4_pack(const at::Tensor &tokens, bool stochastic) { | ||
| NVTE_CHECK(tokens.is_cuda() && tokens.dim() == 2, "tokens must be a 2D CUDA tensor."); | ||
| // Launch, allocate and draw RNG state on the input's device, not the current one. | ||
| const at::cuda::CUDAGuard device_guard(tokens.device()); | ||
| const auto x = tokens.contiguous(); | ||
| auto packed = at::empty({x.size(0), nvfp4_row_bytes(x.size(1))}, x.options().dtype(at::kByte)); | ||
| std::optional<at::Tensor> rng_state; | ||
| TensorWrapper rng_state_nvte; | ||
| if (stochastic) { | ||
| rng_state = make_rng_state(4); | ||
| rng_state_nvte = makeTransformerEngineTensor(*rng_state); | ||
| } | ||
| auto x_nvte = makeTransformerEngineTensor(x); | ||
| auto packed_nvte = makeTransformerEngineTensor(packed); | ||
| NVTE_SCOPED_GIL_RELEASE({ | ||
| nvte_ep_nvfp4_pack(x_nvte.data(), packed_nvte.data(), rng_state_nvte.data(), use_fast_math(), | ||
| at::cuda::getCurrentCUDAStream()); | ||
| }); | ||
| return packed; | ||
| } | ||
|
|
||
| at::Tensor ep_nvfp4_unpack(const at::Tensor &packed, int64_t hidden, | ||
| std::optional<at::Tensor> counts, std::optional<at::Tensor> out) { | ||
| NVTE_CHECK(packed.is_cuda() && packed.dim() == 2 && packed.scalar_type() == at::kByte, | ||
| "packed must be a 2D uint8 CUDA tensor."); | ||
| const at::cuda::CUDAGuard device_guard(packed.device()); | ||
| const auto p = packed.contiguous(); | ||
| if (!out) { | ||
| out = at::empty({p.size(0), hidden}, p.options().dtype(at::kBFloat16)); | ||
| } | ||
| NVTE_CHECK(out->is_contiguous() && out->numel() == p.size(0) * hidden, | ||
| "out must be a contiguous [", p.size(0), ", ", hidden, "] tensor."); | ||
| auto out_2d = out->view({p.size(0), hidden}); | ||
| TensorWrapper counts_nvte; | ||
| if (counts) { | ||
| counts = counts->contiguous(); | ||
| counts_nvte = makeTransformerEngineTensor(*counts); | ||
| } | ||
| auto p_nvte = makeTransformerEngineTensor(p); | ||
| auto out_nvte = makeTransformerEngineTensor(out_2d); | ||
| NVTE_SCOPED_GIL_RELEASE({ | ||
| nvte_ep_nvfp4_unpack(p_nvte.data(), counts_nvte.data(), out_nvte.data(), | ||
| at::cuda::getCurrentCUDAStream()); | ||
| }); | ||
| return *out; | ||
| } | ||
|
|
||
| std::vector<at::Tensor> ep_nvfp4_direct_receive(const at::Tensor &packed, int64_t hidden, | ||
| const at::Tensor &counts, int64_t sign_mask, | ||
| bool stochastic) { | ||
| NVTE_CHECK(packed.is_cuda() && packed.dim() == 2 && packed.scalar_type() == at::kByte, | ||
| "packed must be a 2D uint8 CUDA tensor."); | ||
| NVTE_CHECK(counts.scalar_type() == at::kLong, "counts must be int64."); | ||
| const at::cuda::CUDAGuard device_guard(packed.device()); | ||
| const auto p = packed.contiguous(); | ||
| const auto c = counts.contiguous(); | ||
| const int64_t capacity = p.size(0); | ||
| const int64_t num_groups = c.numel(); | ||
| const auto u8 = p.options().dtype(at::kByte); | ||
| const auto f32 = p.options().dtype(at::kFloat); | ||
| auto row_data = at::empty({capacity * hidden / 2}, u8); | ||
| auto row_scale = at::empty({capacity * hidden / 16}, u8); | ||
| auto col_data = at::empty({capacity * hidden / 2}, u8); | ||
| auto col_scale = at::empty({capacity * hidden / 16}, u8); | ||
| auto row_amax = at::empty({num_groups}, f32); | ||
| auto col_amax = at::empty({num_groups}, f32); | ||
| auto workspace = at::empty({num_groups}, p.options().dtype(at::kInt)); | ||
| std::optional<at::Tensor> rng_state; | ||
| TensorWrapper rng_state_nvte; | ||
| if (stochastic) { | ||
| rng_state = make_rng_state(32); | ||
| rng_state_nvte = makeTransformerEngineTensor(*rng_state); | ||
| } | ||
| auto p_nvte = makeTransformerEngineTensor(p); | ||
| auto c_nvte = makeTransformerEngineTensor(c); | ||
| auto workspace_nvte = makeTransformerEngineTensor(workspace); | ||
| auto row_data_nvte = makeTransformerEngineTensor(row_data); | ||
| auto row_scale_nvte = makeTransformerEngineTensor(row_scale); | ||
| auto col_data_nvte = makeTransformerEngineTensor(col_data); | ||
| auto col_scale_nvte = makeTransformerEngineTensor(col_scale); | ||
| auto row_amax_nvte = makeTransformerEngineTensor(row_amax); | ||
| auto col_amax_nvte = makeTransformerEngineTensor(col_amax); | ||
| NVTE_SCOPED_GIL_RELEASE({ | ||
| nvte_ep_nvfp4_direct_receive( | ||
| p_nvte.data(), c_nvte.data(), workspace_nvte.data(), static_cast<uint32_t>(sign_mask), | ||
| row_data_nvte.data(), row_scale_nvte.data(), col_data_nvte.data(), col_scale_nvte.data(), | ||
| row_amax_nvte.data(), col_amax_nvte.data(), rng_state_nvte.data(), | ||
| static_cast<size_t>(hidden), use_fast_math(), at::cuda::getCurrentCUDAStream()); | ||
| }); | ||
| return {row_data, row_scale, col_data, col_scale, row_amax, col_amax}; | ||
| } | ||
|
|
||
| } // namespace transformer_engine::pytorch | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.