Repository navigation
Per-channel int8 quantized linear (#24397) - #24397
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24397
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit bd2a5a5 with merge base dd38e81 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@pssrawat has exported this pull request. If you are a Meta employee, you can view the originating Diff in D126929702. |
47819b6 to
63e5911
Compare
Summary: Pull Request resolved: pytorch#24397 `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
Summary: `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
63e5911 to
73f94ec
Compare
AdrianLundell
left a comment
There was a problem hiding this comment.
Looks good overall. a few small nits. For easier reviewing I would appreciate to keep different PR:s separate, otherwise I have to sort out non relevant changes myself
| namespace { | ||
| // Runs `fully_connected`, a CMSIS-NN fully connected call bound to its | ||
| // requantization parameters, on the op's tensors. | ||
| template <typename FullyConnected> |
There was a problem hiding this comment.
Templating this adds too much complexity for too little benefit for my taste.
| else: | ||
| output = torch.stack( | ||
| [ | ||
| requantize_cmsis(output_reshaped[..., channel], int(mult), int(shift)) |
There was a problem hiding this comment.
Maybe simpler to add per-channel support to requantize_cmsis?
| ] | ||
| super().__init__(quantizers) | ||
|
|
||
| def set_node_target( |
There was a problem hiding this comment.
This is from another PR
This is happens when internal diff stacks are exported one by one. It needs the earlier diffs so there's a functional stack. @pssrawat - when working with stacks internally, you can use |
Summary: `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
73f94ec to
bd55896
Compare
Summary: `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
bd55896 to
ffc2a1a
Compare
Summary: `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
ffc2a1a to
3fc45c1
Compare
Addressed the nits. The changes from bottom-stack no longer present. |
| cmsis_nn_dims output_dims = {batches, 1, 1, out_feat}; | ||
|
|
||
| arm_cmsis_nn_status status = arm_fully_connected_s8( | ||
| const arm_cmsis_nn_status status = arm_fully_connected_wrapper_s8( |
There was a problem hiding this comment.
I think that this might link both per_channel and per_tensor kernels even if just one is used, could you verify if this is the case and fix that in that case?
There was a problem hiding this comment.
Good catch, confirmed. arm_fully_connected_wrapper_s8 references both arm_fully_connected_s8 and arm_fully_connected_per_channel_s8. Even with --gc-sections, a binary that only uses per-tensor linear still linked the per-channel kernel.
I've removed the wrapper. quantized_linear_impl now takes the kernel as a template argument, and each op passes its own kernel.
Summary: `cortex_m::quantized_linear` supports only per-tensor weights: it calls `arm_fully_connected_s8` with a single requantization multiplier and shift. This adds per-channel linear weights. With `CortexMQuantizer.set_node_target` (previous diff), `set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)` gives per-channel int8 linear weights; without it, linear layers keep `per_tensor_config`, so existing exports are unchanged. `CortexMLinearCheck` rejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature. Per-channel linear weights lower to a new `cortex_m::quantized_linear_per_channel`. It matches `quantized_linear` but takes int32 multiplier and shift tensors, as the convolution ops do, and calls `arm_fully_connected_per_channel_s8`. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion. `int[]` lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call; `quantized_linear` is unchanged. The Python reference requantizes each output feature with its own parameters. This diff was authored with Claude Code. Differential Revision: D126929702
3fc45c1 to
499a468
Compare
| // cppcheck-suppress unusedFunction | ||
| Tensor& quantized_linear_out( | ||
| namespace { | ||
| template <typename QuantParams, typename FullyConnectedFn> |
There was a problem hiding this comment.
For me I would prefer just copying the function twice over templating it but this is fine too.
rascani
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
Summary:
cortex_m::quantized_linearsupports only per-tensor weights: it callsarm_fully_connected_s8with a single requantization multiplier and shift. This adds per-channel linear weights.With
CortexMQuantizer.set_node_target(previous diff),set_node_target(torch.ops.aten.linear.default, INT8_PER_CHANNEL_CONFIG)gives per-channel int8 linear weights; without it, linear layers keepper_tensor_config, so existing exports are unchanged.CortexMLinearCheckrejects per-channel weights that are not quantized per output feature (axis 0, or unset as the convolution check allows), and the lowering checks that it gets one weight scale per output feature, since the kernel indexes its multipliers by output feature.Per-channel linear weights lower to a new
cortex_m::quantized_linear_per_channel. It matchesquantized_linearbut takes int32 multiplier and shift tensors, as the convolution ops do, and callsarm_fully_connected_per_channel_s8. CMSIS-NN takes int32 arrays, so tensors avoid any runtime conversion.int[]lists would also cost about 20 bytes per output feature in the program, where a tensor costs 4. The two C++ entry points share one implementation that takes the CMSIS-NN call;quantized_linearis unchanged. The Python reference requantizes each output feature with its own parameters.This diff was authored with Claude Code.
Differential Revision: D126929702