Repository navigation
Conversation
…p16 casts as convert A zero-valued constant_pad_nd on the two spatial dims whose only user is a non-transposed convolution is now folded into that convolution's padding (FusePadIntoConvPass, run last in the pass manager). XNNPACK takes top/right/bottom/left padding independently, so asymmetric pads such as F.pad(x, (2, 1, 2, 1)) before a 4x4 filter fold too. The graph is left intact: the pad is tagged and its visitor aliases its output to its input, the convolution is tagged with the combined padding that its visitor serializes. Pads that InsertPadQDQPass routed through q/dq and pads with a non-zero value are left alone. _to_dim_order_copy / _to_copy that only changes dtype between fp32 and fp16 (default dim order) is now partitioned and serialized as XNNConvert. The channels-last pass treats such a cast as layout-agnostic, and RemoveRedundantCopyPass neither removes it nor treats it as one half of an NCHW/NHWC copy pair, while still removing a same-layout copy that follows it. This removes the padded activation copies before the blur filters of MI-GAN and its last portable ops (the fp32 I/O casts of the fp16 graph). Prepared with Claude Code.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24112
Note: Links to docs will display an error until the docs builds have been completed. ❌ 2 Unclassified FailuresAs of commit ce27dca with merge base 422cc39 ( UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@pytorchbot label "release notes: xnnpack" |
| @@ -0,0 +1,101 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
There was a problem hiding this comment.
Add software mansion copyright, not Meta. we will add if an eng modifies too
| ) -> None: | ||
| input_node = get_input_node(node, 0) | ||
| if node.meta["val"].dtype != input_node.meta["val"].dtype: | ||
| # fp32 <-> fp16 cast (the partitioner admits no other dtype |
There was a problem hiding this comment.
couple comments that codify this fp32 <-> fp16 cast, I would prefer not codifying this into comments as they will become stale for example if/when we add fp32 <-> bf16
| @@ -0,0 +1,79 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
| # the op does not change the dim order at the same time. | ||
| float_pair = {input_dtype, output_dtype} <= {torch.float32, torch.float16} | ||
| dim_order = node.kwargs.get("dim_order", None) | ||
| default_order = dim_order is None or list(dim_order) == list( |
There was a problem hiding this comment.
this compares the output dim_order to the identity order, not to the input's dim order
we can check it with an explicit x.to(torch.float16, memory_format=torch.contiguous_format) on a channels_last input, we would expect it to not be delegated, while just x.to(torch.float16) should be
Summary
Two XNNPACK lowering improvements found while profiling the fp16 MI-GAN inpainting model. A zero
constant_pad_ndbefore a convolution costs a full extra pass over the activation, which is noticeably slow in NHWC. In MI-GAN these pads, one before every 4x4 blur filter, took 13-22% of the forward. The fp32 I/O casts of an fp16 graph were also the only ops left outside the delegate.1. Fold zero pads into convolutions (
FusePadIntoConvPass).A zero-valued
constant_pad_ndon the two spatial dims whose only user is a non-transposed convolution is folded into that convolution's padding. XNNPACK takes top/right/bottom/left padding independently, so asymmetric pads such asF.pad(x, (2, 1, 2, 1))before a 4x4 filter fold too. The graph and its shape metadata stay as they are: the pass only tags nodes. The pad visitor aliases the pad output to its input, and the conv visitor serializes the combined padding.The pass runs last, after
ChannelsLastTaggedReshapePassandRemoveRedundantCopyPass. If a layout copy sits between the pad and the conv, the conv is no longer the pad's only user and nothing folds. Quantized pads are left alone.InsertPadQDQPassruns earlier and puts q/dq around pads fed by a dequantize, and the fold skips any pad whose input is a dequantize or whose user is a quantize. A pad in a quantized context therefore still becomes a quantized static pad, as before. Non-zero pad values and transposed convolutions are also not folded.2. Lower fp32 <-> fp16 casts as
XNNConvert.ToDimOrderCopyConfignow accepts a_to_dim_order_copythat only changes dtype between fp32 and fp16 with the default dim order. Other dtype conversions, and a dtype cast combined with a dim order change, are still rejected.op_to_copy.pyserializes such a cast asXNNConvert.ChannelsLastTaggedReshapePasstreats the cast as layout-agnostic, so it inherits its input's layout.RemoveRedundantCopyPassno longer removes the cast as a "same-layout copy" and never pairs it with a layout copy as the first half of an NCHW/NHWC round trip. It still removes a same-layout copy that follows the cast.No runtime change is needed:
XNNConvertand TRBL conv padding already exist in the runtime.Results measured on an ExecuTorch 1.4.1-based branch. They include the
upsample_nearest2dlowering from #24113, without which a layout copy sits between every MI-GAN pad and its conv and nothing folds. Timings areMethod::executefor MI-GAN 256 / 512 / faces_256 fp16, 10 timed runs after 2 warm-ups, output bit-identical:The host gain is larger because XNNPACK's
Constant Padis unusually slow on the M4 Pro. On the device, the casts asConvertnodes alone are within noise. Their benefit is that 100% of the forward now runs inside the delegate.#24113 (
upsample_nearest2dlowering) adds a static method at the same spot inchannels_last_tagged_reshape_pass.py. Whichever lands second gets a trivial conflict there.Review order:
fuse_pad_into_conv_pass.py,op_static_constant_pad.pyandop_conv2d.py(the fold).generic_node_configs.py,op_to_copy.py,channels_last_tagged_reshape_pass.pyandremove_redundant_copy_pass.py(the casts).Test plan
New tests:
backends/xnnpack/test/passes/test_fuse_pad_into_conv_pass.py: conv-pad-conv in fp32 and fp16 and with a depthwise conv. Each asserts through the pass stage that the pad is tagged as folded and that the conv has the expected combined TRBL padding (existing conv padding included), then lowers, runs and compares outputs. Pads that must stay unfused (non-zero value, transposed conv, pad on the graph input, qs8 graph withInsertPadQDQPassin the pipeline) are checked the same way and must stay correct.backends/xnnpack/test/passes/test_remove_redundant_copy_pass.py: fp32 I/O around an fp16 conv and a bare fp16/fp32 round trip keep their casts. A channels-last copy after an fp16 cast is still removed.backends/xnnpack/test/ops/test_to_copy_dtype.py: fp32 I/O around an fp16 conv and a cast-only graph are fully delegated with no cast left outside. An int cast and a dtype plus dim order cast are not partitioned.Run on macOS (Apple Silicon, Python 3.13) against the prebuilt pybindings.
lintrunneron the changed files reports no issues.cc @nil-is-all @GregoryComer @digantdesai @cbilgin @JakeStevens