Repository navigation
CUDA backend: lower rotary embeddings written with complex numbers - #24403
shoumikhin wants to merge 1 commit into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24403
Note: Links to docs will display an error until the docs builds have been completed. ❌ 2 New Failures, 10 Pending, 1 Unrelated FailureAs of commit d90c110 with merge base e2bcb9d ( NEW FAILURES - The following jobs have failed:
BROKEN TRUNK - The following job failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 27m 56s —— View job This PR lowers complex-number rotary embeddings in the CUDA backend and stops repeated AOTI output handles from being deleted twice. Nothing blocks merging, but there are six minor issues: a handle leak in the CUDA-graph capture step, rewritten constants missing the autotune out-of-memory offload, helper code that duplicates Detailed reviewPR Review: #24403SummaryThe PR adds Code Quality
Infrastructure
Testing
RecommendationApprove Nothing here blocks merging. The fixes most worth making before merge are the handle leak in the CUDA-graph capture step and GPU tests for dynamic shapes and for repeated outputs with CUDA graphs. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 24m 19s —— View job Nothing blocks this PR, but Detailed reviewPR Review: #24403SummaryThe PR stores complex tensors as pairs of real numbers, turns fused attention variants back into Code Quality
Testing
RecommendationApprove Nothing blocks merging. The first finding is worth fixing first: it is a one-line change that makes the pass follow its own "same result" rule. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 30m 12s —— View job Nothing blocks this PR, but if it lands before #24326, complex128 programs that fail at export today will export and then fail to load or abort at run time; the other three findings are minor. Detailed reviewPR Review: #24403SummaryThe PR stores complex tensors as pairs of real numbers, turns fused attention back into Code Quality
Testing
RecommendationApprove Nothing blocks merging as long as this lands after #24326, as the description plans. If that order cannot be guaranteed, decline complex128 first. |
Rotary position embeddings are often written with complex numbers:
torch.polar builds the rotations, torch.view_as_complex turns pairs of
values into complex numbers, they are multiplied, and torch.view_as_real
turns the result back. Such a model exports, but the CUDA backend could
not lower it:
RuntimeError: Method forward missing fallback kernels (5 total):
- aoti_torch_cuda_mul_Tensor
- aoti_torch_cuda_polar
- aten::view_as_complex_copy
- aten::view_as_real_copy
- prims::convert_element_type
Inductor generates no code for complex numbers. It calls an ATen kernel
for every op that reads or writes a complex tensor, and this backend
ships none of those kernels. Torch-TensorRT leaves polar and view_as_real
to the CUDA backend, so its programs stop at the same error.
A new pass, ReplaceComplexWithRealPass, keeps each complex tensor as a
real tensor with a last dim of size 2, the layout of view_as_real, and
rewrites the ops on it as real math before Inductor compiles the graph.
polar becomes cosine and sine times the magnitude, a complex product
becomes four real products, and view_as_complex and view_as_real go
away. Views, slices, splits, indexing, repeats, transposes, unbinding,
concatenation, stacking and casts keep their op, with the dims and dtype
of the pair. A cast from real to complex gets zero imaginary parts, and
a complex constant, buffer or parameter is stored as its real view.
A decomposition table cannot do this, because a decomposition returns
the dtype of the op it replaces.
The pass rewrites a program only when it can rewrite every op that
reads or writes a complex tensor in it. Otherwise, for example for a
complex add, a complex program input, complex values inside a torch.cond
branch or complex32, it leaves the program as it is and logs why.
Complex128 math becomes float64 math, which the CUDA runtime supports.
The rotary embeddings of LeRobot's LingBot-VA and Fast-WAM policies are
complex128.
Two view_as_real outputs of one complex tensor become one output in two
slots, and the CUDA runtime deletes such a handle only once.
test_complex_rotary_embedding lowers a rotary embedding with a complex
constant, polar with a magnitude other than one, views, a split, a
concatenation and view_as_real outputs, one of them returned twice, runs
it with the runtime and compares it with eager, in float32 and bfloat16,
and with complex128 math. The tests in
passes/tests/test_replace_complex_with_real.py work on the CPU, with the
program prepared the way the CUDA backend's passes get it. One checks a
rotary embedding with a complex constant and parameter, a gather by
position, views, repeats, splits, joins and squeezes of many kinds and a
dynamic length against eager, after the retrace that the backend runs
when its decomposition table applies. One checks that the programs listed
above, a channels-last copy, a cast to real and a mutated complex buffer
stay as they are. One checks that the pass stays registered. They fail
without this change.
bbe873c to
d90c110
Compare
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 30m 48s —— View job Nothing blocks this PR, but there are three minor findings: the copy nodes the pass creates carry a view's metadata, a mutated-buffer check never decides anything, and no test compiles a dynamic-length program or covers Detailed reviewPR Review: #24403SummaryThe PR adds Code Quality
Testing
RecommendationApprove Nothing blocks merging, and all three findings are minor. The dynamic-length GPU test is the most useful one to add, because that is how rotary embeddings are usually exported. |
|
Thanks for the review. I checked the first and third on an A100, and the second in the code, before deciding. None of them changes a result today, so I will not push again for them now.
|
What is wrong today
Rotary position embeddings are often written with complex numbers.
torch.polarbuilds the rotations,torch.view_as_complexturns pairs of values into complex numbers, they are multiplied, andtorch.view_as_realturns the result back. Such a model exports, but the CUDA backend cannot lower it:Inductor generates no code for complex numbers, so it calls ATen kernels, and this backend ships none of them. Torch-TensorRT leaves these ops to the CUDA backend, so its programs stop at the same error.
What this change does
A new pass,
ReplaceComplexWithRealPass, stores each complex tensor as a real tensor with a last dim of size 2 (the layout ofview_as_real), and rewrites the ops on it as real math:polarbecomes cosine and sine times the magnitude.view_as_complexandview_as_realgo away.A decomposition table cannot do this, because a decomposition must return the dtype of the op it replaces.
The pass rewrites a program only when it can rewrite every op on a complex tensor. Otherwise, for example for a complex add, a complex program input, complex values inside a
torch.condbranch, or complex32, it leaves the program as it is and logs why.Complex128 math becomes float64 math, which the CUDA runtime supports. The rotary embeddings of LeRobot's LingBot-VA and Fast-WAM policies are complex128.
What was tested
With torch 2.14 on an A100:
test_complex_rotary_embeddingruns a lowered rotary embedding with a complex constant,polarwith a magnitude other than one, and aview_as_realoutput returned twice, and compares with eager in float32 and bfloat16, and with complex128 math.test_replace_complex_with_real.pyruns the rewritten program on the CPU against eager, with complex128 math, a dynamic length, repeats and squeezes. It also checks that the programs listed above stay as they are, and that the pass stays registered.They fail without this change. The other CUDA tests in the same CI job pass.