Spaces:
Runtime error
Runtime error
| import copy | |
| import torch | |
| import torch.nn as nn | |
| import torch.ao.quantization as tq | |
| class CNN_PSK_QATWrapper(nn.Module): | |
| def __init__(self, base_model: nn.Module): | |
| super().__init__() | |
| self.m = base_model | |
| self.quant_in_conv3 = tq.QuantStub() | |
| self.dequant_after_conv3 = tq.DeQuantStub() | |
| self.quant_in_fc = tq.QuantStub() | |
| self.dequant_after_fc = tq.DeQuantStub() | |
| def forward(self, x): | |
| x = self.m.conv1(x) # float | |
| x = self.m.conv2(x) # float | |
| x = self.quant_in_conv3(x) # int8-sim | |
| x = self.m.conv3(x) # quantized block | |
| x = self.dequant_after_conv3(x) | |
| x = self.m.ssrp_ms(x) # float | |
| x = self.m.flatten(x) | |
| x = self.quant_in_fc(x) # int8-sim | |
| x = self.m.fc(x) # quantized linear | |
| x = self.dequant_after_fc(x) | |
| x = self.m.kan(x) # float | |
| return x | |
| def _get_available_backend(preferred: str = "fbgemm") -> str: | |
| supported = torch.backends.quantized.supported_engines | |
| if preferred in supported: | |
| return preferred | |
| for fallback in ("qnnpack", "fbgemm", "onednn"): | |
| if fallback in supported: | |
| return fallback | |
| raise RuntimeError( | |
| f"No supported quantization backend found. Available: {supported}" | |
| ) | |
| def _set_backend(backend: str): | |
| backend = _get_available_backend(backend) | |
| torch.backends.quantized.engine = backend | |
| return backend | |
| def _fuse_conv_bn_relu_for_qat(base: nn.Module): | |
| if not (hasattr(base, "conv3") and isinstance(base.conv3, nn.Sequential)): | |
| return | |
| if len(base.conv3) < 3: | |
| return | |
| was_training = base.training | |
| base.eval() | |
| tq.fuse_modules(base.conv3, [["0", "1", "2"]], inplace=True) | |
| if was_training: | |
| base.train() | |
| def get_qat_qconfig_compatible(backend: str = "fbgemm"): | |
| """ | |
| QAT config compatible with eager-mode convert() for Conv/Linear: | |
| - Activations: quint8 per-tensor affine | |
| - Weights: qint8 per-channel symmetric (supported by quantized conv/linear) | |
| """ | |
| backend = _get_available_backend(backend) | |
| act_fq = tq.FusedMovingAvgObsFakeQuantize.with_args( | |
| observer=tq.MovingAverageMinMaxObserver, | |
| dtype=torch.quint8, | |
| qscheme=torch.per_tensor_affine, | |
| quant_min=0, | |
| quant_max=255, | |
| reduce_range=False, | |
| ) | |
| weight_fq = tq.FusedMovingAvgObsFakeQuantize.with_args( | |
| observer=tq.MovingAveragePerChannelMinMaxObserver, | |
| dtype=torch.qint8, | |
| qscheme=torch.per_channel_symmetric, # key change | |
| quant_min=-128, | |
| quant_max=127, | |
| reduce_range=False, | |
| ch_axis=0, # Conv2d out_channels axis, Linear out_features axis | |
| ) | |
| return tq.QConfig(activation=act_fq, weight=weight_fq) | |
| def prepare_qat_model( | |
| model_fp32: nn.Module, | |
| backend: str = "fbgemm", | |
| inplace: bool = False, | |
| ) -> nn.Module: | |
| backend = _set_backend(backend) | |
| base = model_fp32 if inplace else copy.deepcopy(model_fp32) | |
| # 1) Fuse conv3 safely | |
| _fuse_conv_bn_relu_for_qat(base) | |
| # 2) Wrap | |
| qat_wrapped = CNN_PSK_QATWrapper(base) | |
| # 3) Attach QAT qconfig | |
| qat_wrapped.qconfig = tq.get_default_qat_qconfig(backend) # backend already resolved | |
| # Keep custom / unsupported parts in float | |
| qat_wrapped.m.conv1.qconfig = None | |
| qat_wrapped.m.conv2.qconfig = None | |
| qat_wrapped.m.ssrp_ms.qconfig = None | |
| qat_wrapped.m.flatten.qconfig = None | |
| qat_wrapped.m.kan.qconfig = None | |
| # Keep PCAw_Pool float (inside conv1) | |
| for mod in qat_wrapped.m.conv1.modules(): | |
| if mod.__class__.__name__ == "PCAw_Pool": | |
| mod.qconfig = None | |
| # 4) Prepare for QAT (inserts fake quant modules) | |
| qat_wrapped.train() | |
| tq.prepare_qat(qat_wrapped, inplace=True) | |
| return qat_wrapped | |
| def convert_qat_model( | |
| qat_model: nn.Module, | |
| backend: str = "fbgemm", | |
| inplace: bool = False, | |
| ) -> nn.Module: | |
| _set_backend(backend) # will auto-resolve to supported backend | |
| m = qat_model if inplace else copy.deepcopy(qat_model) | |
| m.eval() | |
| m = m.cpu() | |
| return tq.convert(m, inplace=True) | |