PasanSarathchandra's picture
add files of the application
320e2b9 verified
Raw
History Blame Contribute Delete
4.18 kB
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)