(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor | None,
stride: Sequence[int],
padding: Sequence[int],
dilation: Sequence[int],
input_offset: int,
output_offset: int,
requantize_multipliers: torch.Tensor,
requantize_shifts: torch.Tensor,
activation_min: int,
activation_max: int,
)
| 750 | |
| 751 | @impl(lib, "quantized_conv2d", "CompositeExplicitAutograd") # type: ignore[misc] |
| 752 | def quantized_conv2d_impl( |
| 753 | input: torch.Tensor, |
| 754 | weight: torch.Tensor, |
| 755 | bias: torch.Tensor | None, |
| 756 | stride: Sequence[int], |
| 757 | padding: Sequence[int], |
| 758 | dilation: Sequence[int], |
| 759 | input_offset: int, |
| 760 | output_offset: int, |
| 761 | requantize_multipliers: torch.Tensor, |
| 762 | requantize_shifts: torch.Tensor, |
| 763 | activation_min: int, |
| 764 | activation_max: int, |
| 765 | ) -> torch.Tensor: |
| 766 | if input.dim() != 4 or weight.dim() != 4: |
| 767 | raise RuntimeError("quantized_conv2d expects 4D input and weight tensors") |
| 768 | # Convert to int32 for accumulation and apply offsets |
| 769 | input_int32 = input.to(torch.int32) + int(input_offset) |
| 770 | weight_int32 = weight.to(torch.int32) |
| 771 | |
| 772 | if bias is None: |
| 773 | bias_int32 = torch.zeros( |
| 774 | weight.shape[0], dtype=torch.int32, device=input.device |
| 775 | ) |
| 776 | else: |
| 777 | bias_int32 = bias.to(torch.int32) |
| 778 | |
| 779 | input_channels = input.shape[1] |
| 780 | kernel_input_channels = weight.shape[3] |
| 781 | groups = input_channels // kernel_input_channels |
| 782 | |
| 783 | # Convert weights back to OIHW layout expected by torch.nn.functional.conv2d |
| 784 | weight_oi_hw = weight_int32.permute(0, 3, 1, 2).contiguous() |
| 785 | |
| 786 | conv_acc = F.conv2d( |
| 787 | input_int32, |
| 788 | weight_oi_hw, |
| 789 | bias_int32, |
| 790 | stride=tuple(stride), |
| 791 | padding=tuple(padding), |
| 792 | dilation=tuple(dilation), |
| 793 | groups=groups, |
| 794 | ) |
| 795 | |
| 796 | result_channels = [] |
| 797 | for output_channel_i in range(conv_acc.shape[1]): |
| 798 | result_channel = requantize_cmsis( |
| 799 | conv_acc[:, output_channel_i, :, :], |
| 800 | int(requantize_multipliers[output_channel_i]), |
| 801 | int(requantize_shifts[output_channel_i]), |
| 802 | ) |
| 803 | result_channels.append(result_channel) |
| 804 | |
| 805 | result = torch.stack(result_channels, dim=1) |
| 806 | |
| 807 | result += output_offset |
| 808 | result = torch.clamp(result, activation_min, activation_max) |
| 809 |
nothing calls this directly
no test coverage detected