Handle standalone torchao.dequantize_affine (not fused with linear/embedding). MLX's dequantize always operates along the last axis. When the quantized dimension is not last (e.g. Conv2d with block_size=[1,32,1,1]), we permute the constant weight/scale/zero_point tensors at compile tim
(P: MLXProgramBuilder, n: Node)
| 3768 | |
| 3769 | @REGISTRY.register(target=[torch.ops.torchao.dequantize_affine.default]) |
| 3770 | def _dequantize_affine_handler(P: MLXProgramBuilder, n: Node) -> Slot: |
| 3771 | """Handle standalone torchao.dequantize_affine (not fused with linear/embedding). |
| 3772 | |
| 3773 | MLX's dequantize always operates along the last axis. When the quantized |
| 3774 | dimension is not last (e.g. Conv2d with block_size=[1,32,1,1]), we permute |
| 3775 | the constant weight/scale/zero_point tensors at compile time so the |
| 3776 | quantized dim becomes last, emit the DequantizeNode, then emit a |
| 3777 | TransposeNode with the inverse permutation to restore the original layout. |
| 3778 | """ |
| 3779 | parsed = parse_dequant_node(n) |
| 3780 | if parsed is None: |
| 3781 | raise NotImplementedError( |
| 3782 | f"dequantize_affine: unsupported quantization config at {n}" |
| 3783 | ) |
| 3784 | ( |
| 3785 | qdata_node, |
| 3786 | scale_node, |
| 3787 | zero_point_node, |
| 3788 | group_size, |
| 3789 | bits, |
| 3790 | out_dtype, |
| 3791 | quantized_dim, |
| 3792 | ) = parsed |
| 3793 | |
| 3794 | qdata_target, qdata = P.get_placeholder_target_and_tensor(qdata_node) |
| 3795 | zero_point_target, zero_point = P.get_placeholder_target_and_tensor(zero_point_node) |
| 3796 | scale_target, scale = P.get_placeholder_target_and_tensor(scale_node) |
| 3797 | |
| 3798 | if out_dtype is None: |
| 3799 | out_dtype = scale_node.meta["val"].dtype |
| 3800 | out_scalar_type = torch_dtype_to_scalar_type(out_dtype) |
| 3801 | |
| 3802 | ndim = qdata.ndim |
| 3803 | needs_permute = quantized_dim != ndim - 1 |
| 3804 | |
| 3805 | if needs_permute: |
| 3806 | perm = list(range(ndim)) |
| 3807 | perm.remove(quantized_dim) |
| 3808 | perm.append(quantized_dim) |
| 3809 | qdata = qdata.permute(perm).contiguous() |
| 3810 | scale = scale.permute(perm).contiguous() |
| 3811 | zero_point = zero_point.permute(perm).contiguous() |
| 3812 | |
| 3813 | # to_mlx_qparams expects 2D tensors; flatten N-D to 2D for packing, |
| 3814 | # then restore the (possibly permuted) leading dimensions afterward. |
| 3815 | permuted_shape = qdata.shape |
| 3816 | qdata_2d = qdata.reshape(-1, qdata.shape[-1]) |
| 3817 | scale_2d = scale.reshape(-1, scale.shape[-1]) |
| 3818 | zero_point_2d = zero_point.reshape(-1, zero_point.shape[-1]) |
| 3819 | |
| 3820 | Q, B = to_mlx_qparams(qdata_2d, scale_2d, zero_point_2d, bits) |
| 3821 | |
| 3822 | leading_dims = permuted_shape[:-1] |
| 3823 | Q = Q.reshape(*leading_dims, Q.shape[-1]) |
| 3824 | scale_nd = scale_2d.reshape(*leading_dims, scale_2d.shape[-1]) |
| 3825 | if B is not None: |
| 3826 | B = B.reshape(*leading_dims, B.shape[-1]) |
| 3827 |
nothing calls this directly
no test coverage detected