-
Notifications
You must be signed in to change notification settings - Fork 797
Add support for fused Q Up-Proj GEMM/RoPE/Quant. #3303
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
Merged
Merged
Changes from 14 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
c19f0df
Add support for fused Q Up-Proj GEMM/RoPE/Quant.
chaseblock cd730aa
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] d0e3915
Remove unused function from fused mla q uproj, add error handling
chaseblock 9854af3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 5f90db7
Adjust comment SBHD/BSHD
chaseblock b993fde
Merge branch 'main' into qrope_fusion
sraman-rgb e2ebeac
Add cudnn_frontend version check; eliminate code dup in
chaseblock 417d233
Add missing docstrings
chaseblock dc89f01
Add unit test for q up proj fusion
chaseblock 24e0b03
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] e20f595
Add test for bwd path of fused gemm+rope+quant.
chaseblock 9770829
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] a14039a
Consolidate fused q proj tests and add to unit test list
chaseblock d6785bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 11c0f1b
Remove backward test from fused mla q uproj.
chaseblock 8c01e02
Merge branch 'main' into qrope_fusion
sudhakarsingh27 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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| """Unit tests for FusedMLAQUpProjRopeQuant. | ||
|
|
||
| Run: | ||
| pytest tests/pytorch/attention/test_fused_mla_q_uproj.py -v | ||
| """ | ||
|
|
||
| import pytest | ||
| import torch | ||
|
|
||
| import transformer_engine.pytorch # registers transformer_engine_torch | ||
| import transformer_engine_torch as tex | ||
| from transformer_engine.pytorch.attention import FusedMLAQUpProjRopeQuant | ||
| from transformer_engine.pytorch.cpp_extensions import general_gemm | ||
| from transformer_engine.pytorch.tensor.mxfp8_tensor import MXFP8Quantizer, MXFP8Tensor | ||
|
|
||
| # DSv3 671B MLA dims | ||
| NUM_HEADS = 128 | ||
| HEAD_DIM_NOPE = 128 | ||
| HEAD_DIM_ROPE = 64 | ||
| HEAD_DIM = HEAD_DIM_NOPE + HEAD_DIM_ROPE # 192 | ||
| Q_LORA_RANK = 1536 | ||
| PROJ_DIM = NUM_HEADS * HEAD_DIM # 24576 | ||
|
|
||
| SEED = 42 | ||
|
|
||
| fused_supported, reason_not_supported = ( | ||
| (True, "") | ||
| if FusedMLAQUpProjRopeQuant.is_supported() | ||
| else ( | ||
| False, | ||
| ( | ||
| "FusedMLAQUpProjRopeQuant.is_supported() returned False " | ||
| "(SM100+, cudnn-frontend >= 1.27.0, and NVTE_FUSED_MLA_Q_UPROJ=1 required)" | ||
| ), | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def _dequantize_fused_output(query: MXFP8Tensor, s: int, b: int) -> torch.Tensor: | ||
| """Dequantize the rowwise fused output to bf16 [s, b, nh, head_dim]. | ||
|
|
||
| TE's C++ dequantize kernel requires 2D layout, so reshape before calling dequantize(). | ||
| """ | ||
| tokens = s * b | ||
| q_2d = MXFP8Tensor( | ||
| shape=(tokens, PROJ_DIM), | ||
| dtype=torch.bfloat16, | ||
| rowwise_data=query._rowwise_data.view(tokens, PROJ_DIM), | ||
| rowwise_scale_inv=query._rowwise_scale_inv.view(tokens, PROJ_DIM // 32), | ||
| columnwise_data=None, | ||
| columnwise_scale_inv=None, | ||
| quantizer=query._quantizer, | ||
| requires_grad=False, | ||
| fp8_dtype=query._fp8_dtype, | ||
| with_gemm_swizzled_scales=False, | ||
| ) | ||
| return q_2d.dequantize().to(torch.bfloat16).view(s, b, NUM_HEADS, HEAD_DIM) | ||
|
|
||
|
|
||
| def _reference_q_uproj( | ||
| x: torch.Tensor, | ||
| w_mxfp8: MXFP8Tensor, | ||
| cos: torch.Tensor, | ||
| sin: torch.Tensor, | ||
| s: int, | ||
| b: int, | ||
| ) -> torch.Tensor: | ||
| """Unfused bf16 reference: dequantize-then-GEMM + RoPE. Returns [s, b, nh, head_dim] bf16.""" | ||
| x_dq = ( | ||
| MXFP8Quantizer(fp8_dtype=tex.DType.kFloat8E4M3, rowwise=True, columnwise=False)(x) | ||
| .dequantize() | ||
| .to(torch.bfloat16) | ||
| ) | ||
| w_dq = w_mxfp8.dequantize().to(torch.bfloat16) | ||
| out = (x_dq @ w_dq.t()).view(s, b, NUM_HEADS, HEAD_DIM) | ||
|
|
||
| q_nope = out[..., :HEAD_DIM_NOPE] | ||
| q_rope = out[..., HEAD_DIM_NOPE:] | ||
| cos_ = cos[:, None, None, :].to(q_rope.dtype) | ||
| sin_ = sin[:, None, None, :].to(q_rope.dtype) | ||
| half = HEAD_DIM_ROPE // 2 | ||
| x1, x2 = q_rope[..., 0::2], q_rope[..., 1::2] | ||
| q_rope_out = torch.cat( | ||
| [ | ||
| x1 * cos_[..., :half] - x2 * sin_[..., :half], | ||
| x2 * cos_[..., half:] + x1 * sin_[..., half:], | ||
| ], | ||
| dim=-1, | ||
| ) | ||
| return torch.cat([q_nope, q_rope_out], dim=-1) | ||
|
|
||
|
|
||
| def _build_rope_tables(tokens: int, device: torch.device) -> tuple[torch.Tensor, torch.Tensor]: | ||
| inv_freq = 1.0 / ( | ||
| 10000 | ||
| ** (torch.arange(0, HEAD_DIM_ROPE, 2, dtype=torch.float32, device=device) / HEAD_DIM_ROPE) | ||
| ) | ||
| freqs = torch.cat( | ||
| [torch.outer(torch.arange(tokens, device=device, dtype=torch.float32), inv_freq)] * 2, | ||
| dim=-1, | ||
| ) | ||
| return freqs.cos().to(torch.bfloat16), freqs.sin().to(torch.bfloat16) | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not fused_supported, reason=reason_not_supported) | ||
| @pytest.mark.parametrize("tokens", [256]) | ||
| def test_fused_mla_q_uproj(tokens: int) -> None: | ||
| """Forward numerics + x_saved properties + backward dgrad/wgrad numerics. | ||
|
|
||
| Forward is checked first so a broken kernel is caught before the backward assertions. | ||
| """ | ||
| s, b = tokens, 1 | ||
| device = torch.device("cuda") | ||
| torch.manual_seed(SEED) | ||
| torch.cuda.manual_seed(SEED) | ||
|
|
||
| x = torch.randn(tokens, Q_LORA_RANK, dtype=torch.bfloat16, device=device) | ||
| # Backward needs columnwise data on w for the dgrad GEMM (general_gemm layout="NN" | ||
| # unwraps A via the columnwise direction). | ||
| w = MXFP8Quantizer(fp8_dtype=tex.DType.kFloat8E4M3, rowwise=True, columnwise=True)( | ||
| torch.randn(PROJ_DIM, Q_LORA_RANK, dtype=torch.bfloat16, device=device) | ||
| ) | ||
| cos, sin = _build_rope_tables(tokens, device) | ||
|
|
||
| query, x_saved = FusedMLAQUpProjRopeQuant.run(x, w, cos, sin, s, b) | ||
|
|
||
| # --- Forward numerics --- | ||
| fused_dq = _dequantize_fused_output(query, s, b) | ||
| ref_dq = _reference_q_uproj(x, w, cos, sin, s, b) | ||
| torch.testing.assert_close(fused_dq, ref_dq, atol=0.5, rtol=0.1) | ||
|
|
||
| # --- x_saved properties --- | ||
| assert isinstance(x_saved, MXFP8Tensor) | ||
| assert x_saved._columnwise_data is not None, "x_saved must retain columnwise data for wgrad" | ||
| assert x_saved._rowwise_data is None, "x_saved rowwise data should be dropped after forward" | ||
|
|
||
| # --- Backward: dgrad + wgrad --- | ||
| grad_output = torch.randn(tokens, PROJ_DIM, dtype=torch.bfloat16, device=device) | ||
| grad_output_quantizer = MXFP8Quantizer( | ||
| fp8_dtype=tex.DType.kFloat8E4M3, rowwise=True, columnwise=True | ||
| ) | ||
| grad_output_quantizer.optimize_for_gemm = True | ||
| gy = grad_output_quantizer(grad_output) | ||
|
|
||
| grad_x = general_gemm( | ||
| w, gy, layout="NN", grad=True, out_dtype=torch.bfloat16, use_split_accumulator=True | ||
| )[0] | ||
| grad_w = general_gemm( | ||
| x_saved, gy, layout="NT", grad=True, out_dtype=torch.bfloat16, use_split_accumulator=True | ||
| )[0] | ||
|
|
||
| x_dq = x_saved.dequantize().to(torch.bfloat16) | ||
| w_dq = w.dequantize().to(torch.bfloat16) | ||
| gy_dq = gy.dequantize().to(torch.bfloat16) | ||
|
|
||
| torch.testing.assert_close(grad_x, gy_dq @ w_dq, atol=0.5, rtol=0.1) | ||
| torch.testing.assert_close(grad_w, gy_dq.t() @ x_dq, atol=0.5, rtol=0.1) | ||
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
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
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.