latest (dev)
Copy
Latest development documentation · Updated 2026-10-15
Source code for tensorplay.ao.quantization.fake_quantize
"""Stateful simulated quantization with native rounding masks."""
import tensorplay as tp
from tensorplay import nn
from tensorplay.autograd.function import Function
from tensorplay import _C
from .observer import (
ObserverBase, MovingAverageMinMaxObserver, MovingAveragePerChannelMinMaxObserver,
FixedQParamsObserver, HistogramObserver, _with_args, is_per_channel, QUANT_MIN, QUANT_MAX,
default_fixed_qparams_range_neg1to1_observer,
default_fixed_qparams_range_0to1_observer,
)
class _TensorFakeQuant(Function):
@staticmethod
def forward(ctx, x, scale, zero_point, quant_min, quant_max):
output, mask = _C.fake_quantize_per_tensor_affine_cachemask(
x, float(scale), int(zero_point), quant_min, quant_max)
ctx.save_for_backward(mask)
return output
@staticmethod
def backward(ctx, grad, *_rest):
return (_C.fake_quantize_per_tensor_affine_cachemask_backward(
grad, ctx.saved_tensors[0]), None, None, None, None)
class _ChannelFakeQuant(Function):
@staticmethod
def forward(ctx, x, scale, zero_point, axis, quant_min, quant_max):
output, mask = _C.fake_quantize_per_channel_affine_cachemask(
x, scale, zero_point, axis, quant_min, quant_max)
ctx.save_for_backward(mask)
return output
@staticmethod
def backward(ctx, grad, *_rest):
return (_C.fake_quantize_per_channel_affine_cachemask_backward(
grad, ctx.saved_tensors[0]), None, None, None, None, None)
[docs]
def fake_quantize_per_tensor(x, scale, zero_point, quant_min=QUANT_MIN, quant_max=QUANT_MAX):
return _TensorFakeQuant.apply(x, scale, zero_point, quant_min, quant_max)
[docs]
def fake_quantize_per_channel(x, scales, zero_points, axis=0, quant_min=QUANT_MIN, quant_max=QUANT_MAX):
zp = zero_points.float() if zero_points.dtype.is_floating_point else zero_points.to(tp.int32)
return _ChannelFakeQuant.apply(x, scales.float(), zp, axis, quant_min, quant_max)
class FakeQuantizeBase(nn.Module):
def __init__(self):
super().__init__()
self.register_buffer('fake_quant_enabled', tp.tensor([1], dtype=tp.uint8))
self.register_buffer('observer_enabled', tp.tensor([1], dtype=tp.uint8))
def enable_fake_quant(self, enabled=True):
self.fake_quant_enabled.fill_(int(enabled))
def disable_fake_quant(self):
self.enable_fake_quant(False)
def enable_observer(self, enabled=True):
self.observer_enabled.fill_(int(enabled))
def disable_observer(self):
self.enable_observer(False)
with_args = classmethod(_with_args)
[docs]
class FakeQuantize(FakeQuantizeBase):
def __init__(self, observer=MovingAverageMinMaxObserver, quant_min=None,
quant_max=None, is_dynamic=False, scale=None, zero_point=None,
disable_observer=False, **observer_kwargs):
super().__init__()
if observer is None:
observer = MovingAverageMinMaxObserver
if quant_min is not None and quant_max is not None:
observer_kwargs.update(quant_min=quant_min, quant_max=quant_max)
observer_kwargs['is_dynamic'] = is_dynamic
self.activation_post_process = (observer if isinstance(observer, ObserverBase)
else observer(**observer_kwargs))
obs = self.activation_post_process
self.dtype, self.qscheme = obs.dtype, obs.qscheme
self.quant_min, self.quant_max = obs.quant_min, obs.quant_max
self.is_dynamic = obs.is_dynamic
self.ch_axis = getattr(obs, 'ch_axis', -1)
self.is_per_channel = is_per_channel(self.qscheme)
storage_dtype = {tp.qint8: tp.int8, tp.quint8: tp.uint8,
tp.qint32: tp.int32}.get(self.dtype, self.dtype)
limits = tp.iinfo(storage_dtype)
if self.quant_min < limits.min or self.quant_max > limits.max:
raise ValueError('Quantization range exceeds the dtype bounds')
zp_dtype = tp.float32 if self.qscheme == tp.per_channel_affine_float_qparams else tp.int32
device = next(obs.buffers(), tp.tensor(0)).device
self.register_buffer('scale', tp.ones(1, dtype=tp.float32, device=device))
self.register_buffer('zero_point', tp.zeros(1, dtype=zp_dtype, device=device))
if scale is not None:
if zero_point is None:
raise ValueError('Explicit scale requires zero_point')
self._update_qparams(tp.as_tensor(scale, device=device).reshape(-1),
tp.as_tensor(zero_point, device=device).reshape(-1))
if disable_observer or scale is not None:
self.disable_observer()
@property
def observer(self):
return self.activation_post_process
@property
def frozen(self):
return not bool(self.observer_enabled[0])
@property
def scales(self):
return self.scale
@property
def zero_points(self):
return self.zero_point
def _update_qparams(self, scale, zero_point):
self.scale.resize_(scale.shape).copy_(scale.to(self.scale.device))
self.zero_point.resize_(zero_point.shape).copy_(zero_point.to(self.zero_point.device))
def record(self, x):
# Boolean assignment is retained for callers using the earlier flag API.
disabled = self.__dict__.get('disable_observer', False) is True
if bool(self.observer_enabled[0]) and not disabled:
self.activation_post_process(x.detach())
self._update_qparams(*self.calculate_qparams())
return x
def freeze(self):
if bool(self.observer_enabled[0]):
self._update_qparams(*self.calculate_qparams())
self.enable_observer(False)
def calculate_qparams(self):
return self.activation_post_process.calculate_qparams()
def forward(self, x):
self.record(x)
if not bool(self.fake_quant_enabled[0]):
return x
if self.is_per_channel:
return fake_quantize_per_channel(x, self.scale, self.zero_point,
self.ch_axis, self.quant_min, self.quant_max)
return fake_quantize_per_tensor(x, self.scale, self.zero_point,
self.quant_min, self.quant_max)
def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,
missing_keys, unexpected_keys, error_msgs):
for name in ('scale', 'zero_point'):
if prefix + name in state_dict:
getattr(self, name).resize_(state_dict[prefix + name].shape)
super()._load_from_state_dict(state_dict, prefix, local_metadata, strict,
missing_keys, unexpected_keys, error_msgs)
[docs]
class PerChannelFakeQuantize(FakeQuantize):
def __init__(self, ch_axis=0, observer=None, scales=None, zero_points=None, **kwargs):
if observer is None:
observer = MovingAveragePerChannelMinMaxObserver.with_args(
dtype=tp.qint8, qscheme=tp.per_channel_symmetric, ch_axis=ch_axis)
super().__init__(observer=observer, scale=scales, zero_point=zero_points, **kwargs)
[docs]
class FixedQParamsFakeQuantize(FakeQuantize):
def __init__(self, observer):
super().__init__(observer=observer)
if type(self.activation_post_process) is not FixedQParamsObserver:
raise ValueError('A fixed parameter observer is required')
self._update_qparams(*self.activation_post_process.calculate_qparams())
def calculate_qparams(self):
return self.scale, self.zero_point
class _FusedFakeQuant(Function):
@staticmethod
def forward(ctx, x, observer_on, fake_quant_on, running_min, running_max,
scale, zero_point, averaging_const, quant_min, quant_max,
ch_axis, per_channel, symmetric):
output, mask = _C._fused_moving_avg_obs_fq_helper(
x, observer_on, fake_quant_on, running_min, running_max, scale,
zero_point, averaging_const, quant_min, quant_max, ch_axis,
per_channel, symmetric)
ctx.save_for_backward(mask)
return output
@staticmethod
def backward(ctx, grad, *_rest):
return (_C.fake_quantize_per_tensor_affine_cachemask_backward(
grad, ctx.saved_tensors[0]),) + (None,) * 12
[docs]
class FusedMovingAvgObsFakeQuantize(FakeQuantize):
def __init__(self, observer=MovingAverageMinMaxObserver, quant_min=0, quant_max=255, **kwargs):
super().__init__(observer, quant_min, quant_max, **kwargs)
if not isinstance(self.observer, (MovingAverageMinMaxObserver, MovingAveragePerChannelMinMaxObserver)):
raise ValueError('A moving average observer is required')
self.fake_quant_enabled = self.fake_quant_enabled.long()
self.observer_enabled = self.observer_enabled.long()
self.is_symmetric_quant = self.qscheme in (tp.per_tensor_symmetric, tp.per_channel_symmetric)
def forward(self, x):
return _FusedFakeQuant.apply(
x, self.observer_enabled, self.fake_quant_enabled,
self.observer.min_val, self.observer.max_val, self.scale, self.zero_point,
self.observer.averaging_constant, self.quant_min, self.quant_max,
self.ch_axis, self.is_per_channel, self.is_symmetric_quant)
def enable_fake_quant(module):
if isinstance(module, FakeQuantizeBase):
module.enable_fake_quant()
def disable_fake_quant(module):
if isinstance(module, FakeQuantizeBase):
module.disable_fake_quant()
def enable_observer(module):
if isinstance(module, FakeQuantizeBase):
module.enable_observer()
def disable_observer(module):
if isinstance(module, FakeQuantizeBase):
module.disable_observer()
default_fake_quant = FakeQuantize.with_args(quant_min=0, quant_max=255, reduce_range=True)
default_weight_fake_quant = FakeQuantize.with_args(dtype=tp.qint8, qscheme=tp.per_tensor_symmetric, quant_min=-128, quant_max=127)
default_per_channel_weight_fake_quant = FakeQuantize.with_args(observer=MovingAveragePerChannelMinMaxObserver, dtype=tp.qint8, qscheme=tp.per_channel_symmetric, quant_min=-128, quant_max=127, ch_axis=0)
default_dynamic_fake_quant = FakeQuantize.with_args(dtype=tp.quint8, quant_min=0, quant_max=255, is_dynamic=True, averaging_constant=1)
default_fixed_qparams_range_neg1to1_fake_quant = FixedQParamsFakeQuantize.with_args(observer=default_fixed_qparams_range_neg1to1_observer)
default_fixed_qparams_range_0to1_fake_quant = FixedQParamsFakeQuantize.with_args(observer=default_fixed_qparams_range_0to1_observer)
default_symmetric_fixed_qparams_fake_quant = default_fixed_qparams_range_neg1to1_fake_quant
default_affine_fixed_qparams_fake_quant = default_fixed_qparams_range_0to1_fake_quant
default_embedding_fake_quant = FakeQuantize.with_args(observer=MovingAveragePerChannelMinMaxObserver, dtype=tp.quint8, qscheme=tp.per_channel_affine_float_qparams, ch_axis=0, averaging_constant=1)
default_embedding_fake_quant_4bit = FakeQuantize.with_args(observer=MovingAveragePerChannelMinMaxObserver, dtype=tp.quint4x2, qscheme=tp.per_channel_affine_float_qparams, ch_axis=0, averaging_constant=1)
default_histogram_fake_quant = FakeQuantize.with_args(observer=HistogramObserver, quant_min=0, quant_max=255, dtype=tp.quint8, qscheme=tp.per_tensor_affine, reduce_range=True)
__all__ = [name for name in globals() if name.startswith('default_')] + [
'FakeQuantizeBase', 'FakeQuantize', 'PerChannelFakeQuantize',
'FixedQParamsFakeQuantize', 'FusedMovingAvgObsFakeQuantize',
'fake_quantize_per_tensor', 'fake_quantize_per_channel',
'enable_fake_quant', 'disable_fake_quant', 'enable_observer', 'disable_observer',
]
default_fused_act_fake_quant = FusedMovingAvgObsFakeQuantize.with_args(quant_min=0, quant_max=255, reduce_range=True)
default_fused_wt_fake_quant = FusedMovingAvgObsFakeQuantize.with_args(dtype=tp.qint8, qscheme=tp.per_tensor_symmetric, quant_min=-128, quant_max=127)
default_fused_per_channel_wt_fake_quant = FusedMovingAvgObsFakeQuantize.with_args(observer=MovingAveragePerChannelMinMaxObserver, dtype=tp.qint8, qscheme=tp.per_channel_symmetric, quant_min=-128, quant_max=127, ch_axis=0)
fused_wt_fake_quant_range_neg_127_to_127 = FusedMovingAvgObsFakeQuantize.with_args(
observer=MovingAverageMinMaxObserver,
quant_min=-127,
quant_max=127,
dtype=tp.qint8,
qscheme=tp.per_tensor_symmetric,
eps=2**-12,
)
fused_per_channel_wt_fake_quant_range_neg_127_to_127 = FusedMovingAvgObsFakeQuantize.with_args(
observer=MovingAveragePerChannelMinMaxObserver,
quant_min=-127,
quant_max=127,
dtype=tp.qint8,
qscheme=tp.per_channel_symmetric,
eps=2**-12,
)
__all__ += ['default_fused_act_fake_quant', 'default_fused_wt_fake_quant', 'default_fused_per_channel_wt_fake_quant', 'fused_wt_fake_quant_range_neg_127_to_127', 'fused_per_channel_wt_fake_quant_range_neg_127_to_127']Help improve this page
Found an error, an unclear step, or a missing example?
Was this page helpful?

