Fold DyT affine maps into following convolutions - #21953
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21953
Note: Links to docs will display an error until the docs builds have been completed. ❌ 3 New Failures, 1 Unrelated FailureAs of commit 05c0192 with merge base 7c3cdbb ( 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. |
|
|
@cgreenberg has exported this pull request. If you are a Meta employee, you can view the originating Diff in D116573000. |
d5c698a to
7a77626
Compare
|
|
Summary: Dynamic Tanh (DyT) normalization computes `tanh(alpha * x)` with a learned scalar alpha. Quantized, that lowers to a full-tensor integer Mul followed by a tanh TABLE. On Ethos-U the TABLE is free but the Mul is a real per-element cost, so the Mul is pure overhead. This adds `FoldDyTAlphaIntoLUTPass` in `backends/arm/_passes/`, which folds the alpha multiply into the tanh lookup table and deletes the Mul and its surrounding rescales. Folding alpha in floating point before quantization would change rounding, so the pass does it in the integer domain instead: it replays the exact TOSA SINGLE_ROUND RESCALE and Mul arithmetic over all 256 int8 input codes, feeds the results through the existing tanh quantization mapping, and materializes the result as one 256-entry TABLE. The rewrite is therefore byte-exact rather than approximate. It fails closed on anything it cannot prove: non-scalar alpha, activation-side rank views, and narrowed tanh ranges are all handled explicitly. Also adds `register_pass_factories_before()` to the Arm pass manager, a small hook for inserting a pass that needs access to the `ExportedProgram` ahead of a named target pass. `FoldDyTAlphaIntoLUTPass` needs it to read constant tensors. Note on one import: the pass imports `register_pass_factories_before` inside `register_fold_dyt_alpha_into_lut_pass()` rather than at module scope. `_passes/__init__.py` imports `arm_pass_manager` last and `arm_pass_manager` imports back from the package, so a module-scope import from a pass module that `__init__.py` re-exports is circular. Only the register helper needs it. The pass is inert until a model registers it, so this diff changes no behaviour on its own. Differential Revision: D116560649
Summary: Second half of the Dynamic Tanh (DyT) lowering cost, and a companion to `FoldDyTAlphaIntoLUTPass`. Once the alpha multiply has been folded into the tanh TABLE, a DyT site still emits a per-channel gamma Mul and a per-channel beta Add between the TABLE and the convolution that consumes it. Both are full-tensor elementwise ops on Ethos-U. This adds `FoldDyTAffineIntoConvPass` in `backends/arm/_passes/`, which folds that affine into the weights and bias of the following convolution, the same algebra as BatchNorm folding: `conv(gamma * x + beta) == conv_with_scaled_weights(x) + conv(beta)`. Doing this in floating point is not safe here, because the intermediate INT8 requantization between the affine and the convolution is nonlinear, so a float fold can change rounding. The pass instead evaluates the site's real TOSA integer path over the materialized 256-entry TABLE and only rewrites when the resulting per-channel map is provably exactly integer-affine. Everything else fails closed: saturating or nonlinear maps, unsupported constant layouts, non-exclusive passthrough edges, rank or shape mismatches, and singleton-channel broadcast are all rejected rather than approximated. Padded convolutions are a special case. Gamma still folds exactly, but beta does not: with constant padding its contribution becomes position dependent at the boundary and cannot be represented by a single conv bias. For those sites the pass folds gamma only when gamma is an exact identity and leaves the beta Add in place. Also exposes a small hook in `insert_table_ops` that the fold needs to locate the materialized TABLE. As with the alpha fold, `register_pass_factories_before` is imported inside `register_fold_dyt_affine_into_conv_pass()` rather than at module scope, to avoid a circular import through `_passes/__init__.py`. The pass is inert until a model registers it, so this diff changes no behaviour on its own. Differential Revision: D116573000
7a77626 to
05c0192
Compare
This PR needs a
|
Summary:
Second half of the Dynamic Tanh (DyT) lowering cost, and a companion to
FoldDyTAlphaIntoLUTPass. Once the alpha multiply has been folded into the tanhTABLE, a DyT site still emits a per-channel gamma Mul and a per-channel beta Add
between the TABLE and the convolution that consumes it. Both are full-tensor
elementwise ops on Ethos-U.
This adds
FoldDyTAffineIntoConvPassinbackends/arm/_passes/, which foldsthat affine into the weights and bias of the following convolution, the same
algebra as BatchNorm folding:
conv(gamma * x + beta) == conv_with_scaled_weights(x) + conv(beta).Doing this in floating point is not safe here, because the intermediate INT8
requantization between the affine and the convolution is nonlinear, so a float
fold can change rounding. The pass instead evaluates the site's real TOSA integer
path over the materialized 256-entry TABLE and only rewrites when the resulting
per-channel map is provably exactly integer-affine. Everything else fails closed:
saturating or nonlinear maps, unsupported constant layouts, non-exclusive
passthrough edges, rank or shape mismatches, and singleton-channel broadcast are
all rejected rather than approximated.
Padded convolutions are a special case. Gamma still folds exactly, but beta does
not: with constant padding its contribution becomes position dependent at the
boundary and cannot be represented by a single conv bias. For those sites the
pass folds gamma only when gamma is an exact identity and leaves the beta Add in
place.
Also exposes a small hook in
insert_table_opsthat the fold needs to locate thematerialized TABLE.
As with the alpha fold,
register_pass_factories_beforeis imported insideregister_fold_dyt_affine_into_conv_pass()rather than at module scope, to avoida circular import through
_passes/__init__.py.The pass is inert until a model registers it, so this diff changes no behaviour
on its own.
Differential Revision: D116573000
cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani