Repository navigation
Qualcomm AI Engine Direct - Keep the dtype of a recomposed RMSNorm - #24428
shoumikhin wants to merge 2 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24428
Note: Links to docs will display an error until the docs builds have been completed. ⏳ 112 Pending, 1 Unrelated FailureAs of commit 22dc132 with merge base cec2e73 ( FLAKY - The following job failed but was likely due to flakiness present on trunk:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 25m 39s —— View job PR Review: #24428No blockers, but in rare mixed-dtype graphs the pass can still build an Detailed reviewSummaryThe PR makes Nothing blocks the PR. The problems are:
All findings come from reading the code. Python can't run in this environment, so none of them were executed. Code Quality
Testing
RecommendationNeeds Discussion Nothing blocks. Item 3 needs an answer on whether QNN's RmsNorm requires one dtype throughout, because that decides how items 2 and 3 should be fixed. Items 1 and 2 are small enough to close in this PR.
|
424b1fc to
ca4b6e5
Compare
|
Thanks for the review. The latest push changes this:
Tests: the dtype cases are now in their own test, |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 36m 11s —— View job PR Review: #24428During quantization capture, the new check between the normalization and the weight stops a norm that casts back with Detailed reviewSummaryThe PR makes
I covered both changed files. Python can't run in this environment, so nothing was executed; every finding comes from reading the code. Code Quality
RecommendationRequest Changes Item 1 makes the quantization flow worse for norms written with
|
RecomposeRmsNorm finds the norm's input by skipping casts, but it only skipped aten.to.dtype. Edge graphs carry the cast as _to_copy or _to_dim_order_copy, so for a Qwen-style norm (upcast to float32, normalize, cast back) the rebuilt rms_norm read the float32 cast and the cast back was lost. Its metadata was copied from the original output, so the graph still claimed the old dtype until a later pass recomputed it. A bfloat16 Qwen norm followed by masked_scatter then failed with "expected self and source to have same dtypes", and when the rebuilt norm stayed on the CPU, to_executorch failed with "Missing out variants: aten::rms_norm". The pass assumes QNN's RmsNorm takes one dtype throughout, so it now rebuilds the norm only when its input and its output share a dtype, and no cast in between rounds below that dtype: - It skips casts that change only the dtype: to.dtype or type_as in captured graphs, _to_copy or _to_dim_order_copy in edge graphs, whose output keeps the input's layout, device and dim order. - It walks back to the nearest tensor in the output dtype, but not past a cast to a narrower dtype or one that changes the dim order. - Only such casts may sit between the normalization and the weight. The "*" in the patterns can match any op, so before, a skipped norm let a later multiply be rebuilt as the norm with the wrong weight. - Outside quantization capture, the dtype also needs a QNN type. - The tensor it scales has to be the tensor it squares, past such casts (T5's norm squares a float32 copy), and the square has to be pow(2) or h * h. Before, any multiply by a root mean square was rebuilt as a norm of the squared tensor, and the scaled tensor was dropped. So a float16 Qwen norm is rebuilt on its float16 input. A norm that rounds to float16 before a float32 weight, and a bfloat16 norm, stay decomposed, unless the bfloat16 norm is about to be quantized. A new test covers these cases, with and without dim order, with a channels-last input and a cast that changes its dim order, a Llama-style norm, a T5 norm, float16 rounding before and inside a float32 norm, a multiply after the norm, and quantization capture, including type_as there. It checks which tensor the norm reads and compares the output with eager, with random weights. Another test checks that a different scaled tensor, a cube and a product of two tensors stay decomposed.
Without dim order, a layout change in an edge graph is a _to_copy with a memory_format argument, and RemoveRedundancy drops every such _to_copy, because QNN does not need layout changes. A cast like x.to(torch.float16, memory_format=torch.contiguous_format) is also a _to_copy with memory_format, so its dtype change was dropped too, and later ops read the old dtype while their metadata said the new one. RemoveRedundancy now drops a _to_copy with memory_format only when it keeps the dtype, as it already does for _to_dim_order_copy. The new test exports such casts without dim order. A layout-only cast is still removed, and a cast to float16 stays, with a float16 output.
ca4b6e5 to
22dc132
Compare
|
Thanks. The latest push changes this:
|
|
@claude /pr-review |
PR Review: #24428
|

What is wrong today
RecomposeRmsNormrebuilds a decomposed RMSNorm into onerms_normnode. To find the norm's input it walks back over casts, but it only knewaten.to.dtype, and edge graphs carry the cast as_to_copyor_to_dim_order_copy. For a Qwen- or Llama-style norm (upcast to float32, normalize, cast back), the rebuiltrms_normread the float32 cast, and the cast back was lost.bfloat16 models then failed later, for example:
What this change does
The pass assumes QNN's RmsNorm takes one dtype throughout. So it now rebuilds the norm only when its input and its output share a dtype, and no cast in between rounds below that dtype:
to.dtypeandtype_asin captured graphs,_to_copyand_to_dim_order_copyin edge graphs): the output keeps the input's layout, device and dim order.pow(2)orh * h. Before, the scaled tensor was not checked and could be dropped.So a float16 Qwen- or Llama-style norm is rebuilt on its float16 input, and a bfloat16 norm stays decomposed on the CPU unless it is about to be quantized. A norm that rounds to float16 before a float32 weight also stays decomposed now. Before, it was rebuilt in float32, which dropped that rounding.
Tests
test_recompose_rms_norm_keeps_the_norm_dtypecovers the Qwen, Llama and T5 forms in float16, float32 and bfloat16, with and without dim order, channels-last inputs, narrowing casts, a multiply after the norm, and quantization capture withto.dtypeandtype_as. Each case checks which tensor the norm reads and compares the output with eager.test_recompose_rms_norm_needs_the_norm_of_one_tensorchecks that a different scaled tensor, a cube and a product of two tensors stay decomposed.A second commit makes
RemoveRedundancy, which runs before this pass, keep a_to_copywithmemory_formatthat also changes the dtype, such asx.to(torch.float16, memory_format=torch.contiguous_format). Without dim order it dropped every such cast. A new test checks both cases.The new tests pass on Linux x86_64 with torch 2.14 and 2.16 nightly, and each fails when its rule is removed. With the QNN SDK (SM8650), a float16 Qwen-style norm is delegated as one
rms_norm, and a bfloat16 norm followed bymasked_scatterand a small bfloat16 Llama-style block now lower. The rest oftest_passes.pypasses, except two tests that fail the same way on main in my environment.cc @cbilgin @psiddh