Skip to content

Search graph for quantization nodes #6452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 4, 2024

Conversation

Erik-Lundell
Copy link
Collaborator

@Erik-Lundell Erik-Lundell commented Oct 23, 2024

Generalizes the search for quantization parameters. The idea is to make a graph like this (part of) a valid quantized graph:

dq -> view -> transpose -> some_op
dq ------> expand -----------/^

For a subset of operations 'passable_op' that don't modify the values of the tensors it is is allowed to "pass through" the op when searching for qparams. If multiple qparams are encounterd in one search, they are asserted to be equal.

The reason for this is to unify what a quantized graph looks like, removing the need for some ad hoc exceptions (for example checking if a view is before an addmm etc.) that were in place before and in general cleaning up the handling of quantization in the ArmBackend. In particular, some decompositions in the to_edge step seem to not insert q/ dq ops which breaks the pattern of dq -> op -> q.

With this change, Arm internal passes can also in some cases skip inserting quantize ops, simplifying the passes.

Change-Id: I6dbb82fb39164c246ea74a9642d907dba22ab2c3

Copy link

pytorch-bot bot commented Oct 23, 2024

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/6452

Note: Links to docs will display an error until the docs builds have been completed.

✅ No Failures

As of commit bb1dd12 with merge base 97a4600 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 23, 2024
@Erik-Lundell Erik-Lundell added partner: arm For backend delegation, kernels, demo, etc. from the 3rd-party partner, Arm ciflow/trunk labels Oct 23, 2024
@digantdesai
Copy link
Contributor

Can you keep the PR in Draft stage until the tests are passing? :p

@Erik-Lundell
Copy link
Collaborator Author

Good point! (rushed this a bit before heading to a conference, so sorry for late response)

@Erik-Lundell Erik-Lundell marked this pull request as draft October 28, 2024 15:44
Generalizes the search for quantization parameters.
The idea is to make a graph like this a valid quantized graph:

dq -> view -> transpose -> some_op
			   ^
                          /
dq ------> expand -------/

For a subset of operations 'passable_op' it is is allowed to "pass
through" the op when searching for qparams. If multiple qparams
are encounterd in one search, they are asserted to be equal.

Signed-off-by: Erik Lundell <[email protected]>
Change-Id: I6dbb82fb39164c246ea74a9642d907dba22ab2c3
Signed-off-by: Erik Lundell <[email protected]>
Change-Id: I029d95ecdfa85f5cdc63997ad1eb7515a016bae4
@Erik-Lundell Erik-Lundell marked this pull request as ready for review October 28, 2024 16:43
Copy link
Contributor

@digantdesai digantdesai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting, searching as opposed to hardcoding known patterns around passable and non-passable ops. I like the flexibility of this approach. Let's see if we run into issues in the future with some random graph structures. Thanks.

else:
consumer_qargs: list[QuantArgs] = []
for input in consumer_nodes:
quant_args = search_quant_arg_downstream(input)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a test for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that it's rather well tested through all op and model tests, but it could be a good idea to make an extra tricky graph.

q_op = exir_ops.edge.quantized_decomposed.quantize_per_tensor.default
dq_op = exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default
dq_q_ops = [q_op, dq_op]
dq_q_ops = (q_op, dq_op)
passable_ops = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should these be a decorator for each of these ops?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoiding maintaining a list seems like a good idea, could you go more into detail about how/ where to add such a decorator?

else:
quant_node = input_node
input_zp = get_quant_node_args(quant_node).zp
input_zp = search_quant_arg_upstream(input_node).zp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: though we are checking this for a quant case, but this function can return None. I feel at least we should have some logging in this upstream / downstream search function for easier debugging?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning about (the lack of) error handling here was to match the earlier behavior with the hope to solve some additional cases. Since we have checked is_quant_node = True, we have assumed before that the node is embedded with dq/ q nodes as it should. It's possible that there is some case where we don't have this for some weird reason but that would have failed earlier as well.

@facebook-github-bot
Copy link
Contributor

@digantdesai has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@Erik-Lundell
Copy link
Collaborator Author

This is interesting, searching as opposed to hardcoding known patterns around passable and non-passable ops. I like the flexibility of this approach. Let's see if we run into issues in the future with some random graph structures. Thanks.

Thanks! The hope was to clearly define what a correctly quantized graph should look like, instead of having exceptions for cases such as view before addmm like we had before. We also wanted to make sure that we don't have operators with different qparams at different outputs/ inputs that can't handle it which could cause numerical issues. Such cases should end up as assertion errors instead now.

@facebook-github-bot
Copy link
Contributor

@digantdesai has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@Erik-Lundell Erik-Lundell merged commit 63017e4 into pytorch:main Nov 4, 2024
102 checks passed
digantdesai added a commit that referenced this pull request Nov 5, 2024
@digantdesai
Copy link
Contributor

Found type errors!
executorch/backends/arm/operators/op_addmm.py:73:23 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/operators/op_addmm.py:109:26 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_bmm.py:51:24 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/operators/op_bmm.py:75:16 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_conv2d.py:86:12 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/operators/op_conv2d.py:164:26 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_exp.py:56:35 Incompatible parameter type [6]: In call exp_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_exp.py:56:49 Incompatible parameter type [6]: In call exp_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_full.py:46:43 Incompatible parameter type [6]: In call quantize_value, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_hardtanh.py:45:60 Incompatible parameter type [6]: In call quantize_value, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_hardtanh.py:46:60 Incompatible parameter type [6]: In call quantize_value, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_log.py:57:35 Incompatible parameter type [6]: In call log_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_log.py:57:49 Incompatible parameter type [6]: In call log_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_mm.py:61:24 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/operators/op_mm.py:98:16 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_mul.py:55:16 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/operators/op_mul.py:81:34 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_placeholder.py:83:23 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/operators/op_reciprocal.py:48:39 Incompatible parameter type [6]: In call div_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_reciprocal.py:48:52 Incompatible parameter type [6]: In call div_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_relu.py:42:53 Incompatible parameter type [6]: In call tqutils.quantize_value, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_relu.py:43:64 Incompatible parameter type [6]: In call tqutils.quantize_value, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_rsqrt.py:46:37 Incompatible parameter type [6]: In call rsqrt_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_rsqrt.py:46:51 Incompatible parameter type [6]: In call rsqrt_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_sigmoid.py:57:39 Incompatible parameter type [6]: In call sigmoid_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_sigmoid.py:57:53 Incompatible parameter type [6]: In call sigmoid_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_tanh.py:57:36 Incompatible parameter type [6]: In call tanh_table_8bit, for 1st positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/operators/op_tanh.py:57:50 Incompatible parameter type [6]: In call tanh_table_8bit, for 2nd positional argument, expected QuantArgs but got Optional[QuantArgs].
executorch/backends/arm/tosa_quant_utils.py:80:22 Incompatible parameter type [6]: In call QuantArgs.__init__, for 1st positional argument, expected float but got Union[None, slice[typing.Any, typing.Any, typing.Any], Dict[str, typing.Any], List[typing.Any], bool, complex, float, int, range, str, SymBool, SymFloat, SymInt, device, dtype, layout, memory_format, OpOverload, Tensor, Node, typing.Tuple[typing.Any, ...]].
executorch/backends/arm/tosa_quant_utils.py:80:22 Incompatible parameter type [6]: In call QuantArgs.__init__, for 2nd positional argument, expected int but got Union[None, slice[typing.Any, typing.Any, typing.Any], Dict[str, typing.Any], List[typing.Any], bool, complex, float, int, range, str, SymBool, SymFloat, SymInt, device, dtype, layout, memory_format, OpOverload, Tensor, Node, typing.Tuple[typing.Any, ...]].
executorch/backends/arm/tosa_quant_utils.py:80:22 Incompatible parameter type [6]: In call QuantArgs.__init__, for 3rd positional argument, expected int but got Union[None, slice[typing.Any, typing.Any, typing.Any], Dict[str, typing.Any], List[typing.Any], bool, complex, float, int, range, str, SymBool, SymFloat, SymInt, device, dtype, layout, memory_format, OpOverload, Tensor, Node, typing.Tuple[typing.Any, ...]].
executorch/backends/arm/tosa_quant_utils.py:80:22 Incompatible parameter type [6]: In call QuantArgs.__init__, for 4th positional argument, expected int but got Union[None, slice[typing.Any, typing.Any, typing.Any], Dict[str, typing.Any], List[typing.Any], bool, complex, float, int, range, str, SymBool, SymFloat, SymInt, device, dtype, layout, memory_format, OpOverload, Tensor, Node, typing.Tuple[typing.Any, ...]].
executorch/backends/arm/tosa_quant_utils.py:80:22 Incompatible parameter type [6]: In call QuantArgs.__init__, for 5th positional argument, expected dtype but got Union[None, slice[typing.Any, typing.Any, typing.Any], Dict[str, typing.Any], List[typing.Any], bool, complex, float, int, range, str, SymBool, SymFloat, SymInt, device, dtype, layout, memory_format, OpOverload, Tensor, Node, typing.Tuple[typing.Any, ...]].
executorch/backends/arm/tosa_quant_utils.py:151:15 Uninitialized local [61]: Local variable quant_args is undefined, or not always defined.
executorch/backends/arm/tosa_quant_utils.py:184:15 Uninitialized local [61]: Local variable quant_args is undefined, or not always defined.
executorch/backends/arm/tosa_quant_utils.py:193:54 Undefined attribute [16]: Item typing.Callable of typing.Union[typing.Callable[..., typing.Any], str] has no attribute __name__.
executorch/backends/arm/tosa_quant_utils.py:365:21 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/tosa_quant_utils.py:374:16 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/tosa_quant_utils.py:395:35 Undefined attribute [16]: Optional type has no attribute scale.
executorch/backends/arm/tosa_quant_utils.py:402:8 Undefined attribute [16]: Optional type has no attribute zp.
executorch/backends/arm/tosa_utils.py:241:19 Undefined attribute [16]: Optional type has no attribute zp.

@Erik-Lundell
Copy link
Collaborator Author

Whelp, at least that gave me the motivation to install pyre locally

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ciflow/trunk CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. partner: arm For backend delegation, kernels, demo, etc. from the 3rd-party partner, Arm
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants