Fold parameter-only subgraphs before XNNPACK partitioning - #22391
Fold parameter-only subgraphs before XNNPACK partitioning#22391john-rocky wants to merge 2 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22391
Note: Links to docs will display an error until the docs builds have been completed.
|
This PR needs a
|
|
a few thoughts: (1) seems there is a bug in the underlying pass for non-determinant ops: (2) I don't really like adding a flag to to_edge_transform_and_lower for a pass. maybe better to enable it only for xnnpack in the pre annotation transform. else there may be a better place to hook this in. thoughts maybe @JacobSzwejbka ? |
constant_prop_pass folds any call_function node whose arguments are all constants. Ops that draw from the RNG, such as aten.rand, take only sizes as arguments, so they qualified and were replaced by a single frozen draw: a model returning x + torch.rand(4) produced the same output on every call once the pass had run. Skip nodes that torch.fx.Node.is_impure() reports as impure. That covers ops tagged nondeterministic_seeded, mutable schemas and side-effectful functions, and is the same test eliminate_dead_code uses to decide what it must keep.
The XNNPACK partitioner configs require a static weight, so a convolution or a linear whose weight is computed from parameters, such as anything under torch.nn.utils.parametrizations.weight_norm, was declined and left to the portable kernels together with the weight computation. On wav2vec2-large's positional convolution that is 3.9 s instead of 4.7 ms for the module alone (pytorch#22078). Override Partitioner.transform_for_pre_decomposition in XnnpackPartitioner to run constant_prop_pass on the ATen program. The skip set mirrors the pass's edge-level default: the factory ops that decompose to aten.full, so a scalar fill does not become a stored tensor, and the quantization primitives, so the Q/DQ chain convert_pt2e leaves on a weight stays in place.
17e2c84 to
b65d16f
Compare
Summary
Fixes #22078.
The XNNPACK partitioner configs require a static weight (
is_param_nodeinpartition/config/gemm_configs.py), so a convolution or a linear whose weight is computed from parameters, which is anything undertorch.nn.utils.parametrizesuch asweight_normandspectral_norm, is declined and left to the portable kernels together with the weight computation. Nothing warns:WhyNoPartitionlogs at DEBUG, and the model simply runs slow.This overrides
Partitioner.transform_for_pre_decompositioninXnnpackPartitionerto runconstant_prop_passon the ATen program, so the fold is XNNPACK-scoped and nothing changes into_edge_transform_and_lower's signature. The skip set mirrors the pass's edge-level default: the factory ops that decompose toaten.full, so a scalar fill does not become a stored tensor, and the quantization primitives, so the Q/DQ chainconvert_pt2eleaves on a weight stays in place.The first commit is #22418 (skip impure ops in
constant_prop_pass); it is included here so thattorch.randstays in the graph once the pass runs on every XNNPACK export. This PR rebases to one commit once that lands.Earlier revision of this PR added a
constant_propflag toto_edge_transform_and_lower; reworked after review.Measurements
wav2vec2-large's positional convolution,
weight_norm(Conv1d(1024, 1024, 128, padding=64, groups=16))at sequence length 499, module alone, synthetic weights, macOS arm64, torch 2.13.0:sum,pow,convolution.ptesize is the same (33.6 MB) in both cases: the folded weight replacesweight_v, it does not join it.Test plan
backends/xnnpack/test/test_xnnpack_partitioner.py:test_parametrized_weight_is_folded_before_partitioning: aweight_normConv1d lowers to a single delegate call with nothing else at the top level, and the runtime output matches eager.test_pre_decomposition_folding_keeps_quantization_primitives: on aconvert_pt2egraph, the set ofquantized_decomposednodes is identical before and after the hook.Also run locally with the hook in place:
backends/xnnpack/test/ops/test_conv1d.py,backends/xnnpack/test/ops/test_linear.py,exir/program/test/test_program.py,exir/tests/test_passes.py -k constant_prop.Local run of
backends/xnnpack/test/ops/test_linear.pyon this machine (main's Python over the 1.4.0 runtime wheel): 58 of 61 pass. The three that fail (qd8_f16/f32_per_channel_int4,qd8_bf16_per_token_weight_per_channel_group_int4) fail identically with the hook removed, so they are the local runtime, not this change.