Skip to content

Commit 1813541

Browse files
authored
Block activation support (#56)
* Block activation support * Update logic for blocking activations * add logic to preserve per-block granularity for shared observers * address review comments * fix failing tests
1 parent 7ad2df3 commit 1813541

15 files changed

Lines changed: 513 additions & 126 deletions

src/coreai_opt/quantization/_eager/_prepare_for_export.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
validate_fp4_export,
2929
)
3030
from coreai_opt.quantization.spec.fake_quantize import FakeQuantizeImplBase
31+
from coreai_opt.quantization.spec.granularity import PerBlockGranularity
3132

3233
logger = logging.getLogger(__name__)
3334

@@ -198,7 +199,11 @@ def _import_coreai_torch_modules():
198199
CompressionTargetTensor.ACTIVATION,
199200
):
200201
if is_float4_dtype(module.dtype):
201-
raise ValueError("FP4 activation quantization is not supported for MLIR export.")
202+
raise ValueError("Core AI export does not support FP4 activation quantization.")
203+
if isinstance(module.granularity, PerBlockGranularity):
204+
raise ValueError(
205+
"Core AI export does not support PerBlockGranularity on activations."
206+
)
202207
modules_to_replace.append((name, module))
203208

204209
# Replace each FakeQuantizeImplBase module

src/coreai_opt/quantization/_graph/_prepare_for_export.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
resolve_attr,
3939
)
4040
from coreai_opt.quantization.spec.fake_quantize import FakeQuantizeImplBase
41+
from coreai_opt.quantization.spec.granularity import PerBlockGranularity
4142

4243
logger = logging.getLogger(__name__)
4344

@@ -332,7 +333,10 @@ def _process_mlir_activation_quantization(
332333
fake_quant_mod: The fake quantization module
333334
"""
334335
if is_float4_dtype(fake_quant_mod.dtype):
335-
raise ValueError("FP4 activation quantization is not supported for MLIR export.")
336+
raise ValueError("Core AI export does not support FP4 activation quantization.")
337+
338+
if isinstance(fake_quant_mod.granularity, PerBlockGranularity):
339+
raise ValueError("Core AI export does not support PerBlockGranularity on activations.")
336340

337341
def _import_coreai_custom_ops():
338342
import coreai_torch._compression.custom_layers # noqa: PLC0415, F401

src/coreai_opt/quantization/_graph/_utils.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from coreai_opt.quantization.spec.fake_quantize import FakeQuantizeImplBase
1313
from coreai_opt.quantization.spec.granularity import (
14+
PerBlockGranularity,
1415
PerChannelGranularity,
1516
PerTensorGranularity,
1617
QuantizationGranularity,
@@ -267,8 +268,8 @@ def _shared_granularity_axis_is_safe(
267268
"""Return True only if it's proven safe to keep ``fake_quant``'s
268269
granularity shared across ``op_node``; unproven cases default to unsafe.
269270
270-
Per-channel activation quantization on a shared observer is assumed
271-
unsafe by default. Each op category below has exactly one condition
271+
Per-channel and per-block activation quantization on a shared observer are
272+
assumed unsafe by default. Each op category below has exactly one condition
272273
under which it stops being safe, checked directly against that
273274
category rather than composing generic checks that apply to every op:
274275
@@ -284,10 +285,6 @@ def _shared_granularity_axis_is_safe(
284285
# No axis to violate — trivially safe.
285286
if isinstance(granularity, PerTensorGranularity):
286287
return True
287-
# Anything other than PerChannelGranularity (e.g. PerBlockGranularity)
288-
# has no condition checked below, so it's not safe.
289-
if not isinstance(granularity, PerChannelGranularity):
290-
return False
291288

292289
output_shape = op_node.meta["val"].shape
293290
input_shape = input_fq_node.all_input_nodes[0].meta["val"].shape
@@ -296,14 +293,19 @@ def _shared_granularity_axis_is_safe(
296293
# index, so there's no condition to check here.
297294
if len(input_shape) != len(output_shape):
298295
return False
299-
axis = QuantizationGranularity._resolve_axis(granularity, len(input_shape))
300-
if axis is None:
301-
return False
296+
297+
if isinstance(granularity, PerBlockGranularity):
298+
axes: tuple[int, ...] = tuple(range(len(input_shape)))
299+
else:
300+
axis = QuantizationGranularity._resolve_axis(granularity, len(input_shape))
301+
if axis is None:
302+
return False
303+
axes = (axis,)
302304

303305
if op_node.target in _AXIS_RESIZING_ATEN_OPS:
304-
return input_shape[axis] == output_shape[axis]
306+
return all(input_shape[a] == output_shape[a] for a in axes)
305307
if op_node.target in _AXIS_REORDERING_ATEN_OPS:
306-
return _op_preserves_axis_identity(op_node, axis)
308+
return all(_op_preserves_axis_identity(op_node, a) for a in axes)
307309
# flatten/reshape/view/unsqueeze, or an unrecognized future op: no known
308310
# single condition to prove safety, so default to unsafe.
309311
return False

src/coreai_opt/quantization/spec/factory.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,25 @@ class QuantizationComponentFactory(CompressionComponentFactoryBase):
3535
"""
3636

3737
@classmethod
38-
def create_range_calculator(cls, spec: QuantizationSpec) -> RangeCalculatorBase:
38+
def create_range_calculator(
39+
cls,
40+
spec: QuantizationSpec,
41+
quantization_target: CompressionTargetTensor = CompressionTargetTensor.WEIGHT,
42+
) -> RangeCalculatorBase:
3943
"""
4044
Create a RangeCalculatorBase instance from a QuantizationSpec.
4145
4246
Args:
4347
spec: QuantizationSpec instance containing configuration
48+
quantization_target: The target tensor for quantization (weight/activation).
4449
4550
Returns:
4651
RangeCalculatorBase instance configured from the spec
4752
"""
4853
# Standard arguments for range calculator
4954
common_args = {
5055
"granularity": spec.granularity,
56+
"quantization_target": quantization_target,
5157
}
5258

5359
# Automatically detect and include any extra arguments
@@ -96,7 +102,7 @@ def create_qparams_calculator(
96102
)
97103

98104
# Create range calculator first
99-
range_calculator = cls.create_range_calculator(spec)
105+
range_calculator = cls.create_range_calculator(spec, quantization_target)
100106

101107
# Standard arguments for qparams calculator
102108
common_args = {
@@ -202,7 +208,6 @@ def create_fake_quantizer(
202208
"quant_min": spec.quant_min,
203209
"quant_max": spec.quant_max,
204210
"qparams_calculator": qparams_calculator,
205-
"quantization_target": quantization_target,
206211
"n_bits": spec.n_bits,
207212
}
208213

@@ -283,7 +288,6 @@ def create_fake_quantizer_partial(
283288
"target_dtype": spec.target_dtype,
284289
"quant_min": spec.quant_min,
285290
"quant_max": spec.quant_max,
286-
"quantization_target": quantization_target,
287291
"n_bits": spec.n_bits,
288292
}
289293

src/coreai_opt/quantization/spec/fake_quantize.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def __init__(
5353
quant_min: int | float,
5454
quant_max: int | float,
5555
qparams_calculator: QParamsCalculatorBase,
56-
quantization_target: CompressionTargetTensor,
5756
n_bits: int | None = None,
5857
**kwargs,
5958
):
@@ -65,7 +64,6 @@ def __init__(
6564
self.quant_min = quant_min
6665
self.quant_max = quant_max
6766
self.qparams_calculator = qparams_calculator
68-
self.quantization_target = quantization_target
6967
self.register_buffer("_disabled", torch.tensor(False))
7068

7169
# Infer n_bits from dtype if not provided
@@ -78,6 +76,11 @@ def qscheme(self) -> QuantizationScheme:
7876
"""The quantization scheme, delegated to the qparams_calculator."""
7977
return self.qparams_calculator.qscheme
8078

79+
@property
80+
def quantization_target(self) -> CompressionTargetTensor:
81+
"""Getter for quantization target."""
82+
return self.qparams_calculator.quantization_target
83+
8184
@property
8285
def granularity(self) -> QuantizationGranularity:
8386
"""Getter for granularity."""
@@ -359,7 +362,7 @@ def _quantize_int(
359362
360363
This function quantizes the values in tensor but keeps the quantized tensor dtype in FP.
361364
"""
362-
block_size = self.granularity.get_block_size(tensor.shape)
365+
block_size = self.granularity.get_block_size(tensor.shape, self.quantization_target)
363366
original_shape, blockwise_shape, reduced_shape = _get_quantization_shapes(
364367
tensor, block_size
365368
)
@@ -384,7 +387,7 @@ def _dequantize_int(
384387
output_dtype: torch.dtype,
385388
) -> torch.Tensor:
386389
"""Integer dequantization. See :func:`_dequantize_int` for the math."""
387-
block_size = self.granularity.get_block_size(tensor.shape)
390+
block_size = self.granularity.get_block_size(tensor.shape, self.quantization_target)
388391
original_shape, blockwise_shape, reduced_shape = _get_quantization_shapes(
389392
tensor, block_size
390393
)
@@ -406,7 +409,7 @@ def _quantize_float(
406409
"""
407410
Floating-point quantization: cast_to_low_precision(clamp(input / scale, min, max))
408411
"""
409-
block_size = self.granularity.get_block_size(tensor.shape)
412+
block_size = self.granularity.get_block_size(tensor.shape, self.quantization_target)
410413
original_shape, blockwise_shape, reduced_shape = _get_quantization_shapes(
411414
tensor, block_size
412415
)
@@ -427,7 +430,7 @@ def _dequantize_float(
427430
output_dtype: torch.dtype,
428431
) -> torch.Tensor:
429432
"""Floating-point dequantization: input * scale"""
430-
block_size = self.granularity.get_block_size(tensor.shape)
433+
block_size = self.granularity.get_block_size(tensor.shape, self.quantization_target)
431434
original_shape, blockwise_shape, reduced_shape = _get_quantization_shapes(
432435
tensor, block_size
433436
)
@@ -449,7 +452,7 @@ def _fused_fake_quant_dequant(
449452
450453
Dispatches to the int or float fused STE class based on self.dtype.
451454
"""
452-
block_size = self.granularity.get_block_size(tensor.shape)
455+
block_size = self.granularity.get_block_size(tensor.shape, self.quantization_target)
453456
original_shape, blockwise_shape, reduced_shape = _get_quantization_shapes(
454457
tensor, block_size
455458
)

0 commit comments

Comments
 (0)