1313
1414import logging
1515from collections import defaultdict
16+ from dataclasses import dataclass
1617
1718import torch
1819import torch .nn as nn
1920import torch .nn .utils .parametrize as P
2021from torch .fx import GraphModule
2122
23+ from coreai_opt ._utils .torch_utils import (
24+ ATEN_OP_TO_MODULE_TYPE as _ATEN_OP_TO_MODULE_TYPE ,
25+ )
2226from coreai_opt .config .spec import CompressionTargetTensor
2327from coreai_opt .quantization .spec .fake_quantize import FakeQuantizeImplBase
2428from coreai_opt .quantization .spec .granularity import (
3539_WeightFQMap = defaultdict [FakeQuantizeImplBase , list [_ConsumerInfo ]]
3640
3741
38- # Per-channel defaults: quantize along the OUTPUT channel axis.
39- # Conv weights are [out_ch, in_ch, ...]; ConvTranspose weights are [in_ch, out_ch, ...].
40- # Embedding weights are [num_embeddings, embedding_dim].
41- _PER_CHANNEL_WEIGHT_AXIS_DEFAULTS : dict [type [nn .Module ], int ] = {
42- nn .Conv1d : 0 ,
43- nn .Conv2d : 0 ,
44- nn .Conv3d : 0 ,
45- nn .ConvTranspose1d : 1 ,
46- nn .ConvTranspose2d : 1 ,
47- nn .ConvTranspose3d : 1 ,
48- nn .Linear : 0 ,
49- nn .Embedding : 0 ,
50- }
42+ @dataclass (frozen = True )
43+ class _WeightAxisSpec :
44+ """Default axis indices for a single ``nn.Module`` type.
5145
52- # Per-block defaults: quantize along the INPUT channel axis (reduction dimension)
53- # Embedding weights are [num_embeddings, embedding_dim].
54- _PER_BLOCK_WEIGHT_AXIS_DEFAULTS : dict [type [nn .Module ], int ] = {
55- nn .Conv1d : 1 ,
56- nn .Conv2d : 1 ,
57- nn .Conv3d : 1 ,
58- nn .ConvTranspose1d : 0 ,
59- nn .ConvTranspose2d : 0 ,
60- nn .ConvTranspose3d : 0 ,
61- nn .Linear : 1 ,
62- nn .Embedding : 1 ,
63- }
46+ Attributes:
47+ per_channel_axis (int): Output-channel axis for per-channel granularity.
48+ per_block_axis (int): Input-channel (reduction) axis for per-block granularity.
49+ """
50+
51+ per_channel_axis : int
52+ per_block_axis : int
6453
65- # Maps aten OpOverload -> nn.Module type for graph-mode op to module type resolution.
66- _ATEN_OP_TO_MODULE_TYPE : dict [torch ._ops .OpOverload , type [nn .Module ]] = {
67- torch .ops .aten .conv1d .default : nn .Conv1d ,
68- torch .ops .aten .conv2d .default : nn .Conv2d ,
69- torch .ops .aten .conv3d .default : nn .Conv3d ,
70- torch .ops .aten .conv_transpose1d .default : nn .ConvTranspose1d ,
71- torch .ops .aten .conv_transpose2d .input : nn .ConvTranspose2d ,
72- torch .ops .aten .conv_transpose3d .input : nn .ConvTranspose3d ,
73- torch .ops .aten .linear .default : nn .Linear ,
74- torch .ops .aten .embedding .default : nn .Embedding ,
54+ def default_axis_for (self , granularity : QuantizationGranularity ) -> int | None :
55+ """Return the default axis for the given granularity, or ``None`` if not applicable."""
56+ if isinstance (granularity , PerChannelGranularity ):
57+ return self .per_channel_axis
58+ if isinstance (granularity , PerBlockGranularity ):
59+ return self .per_block_axis
60+ return None
61+
62+
63+ # Conv weights are [out_ch, in_ch, ...]; ConvTranspose weights are [in_ch, out_ch, ...].
64+ # Embedding weights are [num_embeddings, embedding_dim].
65+ _WEIGHT_AXIS_SPECS : dict [type [nn .Module ], _WeightAxisSpec ] = {
66+ nn .Conv1d : _WeightAxisSpec (0 , 1 ),
67+ nn .Conv2d : _WeightAxisSpec (0 , 1 ),
68+ nn .Conv3d : _WeightAxisSpec (0 , 1 ),
69+ nn .ConvTranspose1d : _WeightAxisSpec (1 , 0 ),
70+ nn .ConvTranspose2d : _WeightAxisSpec (1 , 0 ),
71+ nn .ConvTranspose3d : _WeightAxisSpec (1 , 0 ),
72+ nn .Linear : _WeightAxisSpec (0 , 1 ),
73+ nn .Embedding : _WeightAxisSpec (0 , 1 ),
7574}
7675
7776
@@ -220,12 +219,11 @@ def _collect_weight_fq_entries_eager(model: nn.Module) -> _WeightFQMap:
220219def _apply_defaults (fq_map : _WeightFQMap ) -> None :
221220 """Apply default weight axes and raise on unresolved or conflicting entries.
222221
223- For each FQ in the map, selects the per-channel or per-block defaults
224- table based on the granularity type. Resolves each consumer's identifier
225- to a module type (aten ``OpOverload`` entries from graph-mode are mapped via
226- ``_ATEN_OP_TO_MODULE_TYPE``), then looks up the default axis. When a
227- single FQ has multiple consumers (shared weight), all consumers must
228- agree on the same default axis.
222+ For each FQ in the map, resolves each consumer's identifier to a module
223+ type (aten ``OpOverload`` entries from graph-mode are mapped via
224+ ``_ATEN_OP_TO_MODULE_TYPE``), then looks up the default axis from
225+ ``_WEIGHT_AXIS_SPECS``. When a single FQ has multiple consumers (shared
226+ weight), all consumers must agree on the same default axis.
229227
230228 Args:
231229 fq_map (_WeightFQMap): Map from fake-quantize instance to its
@@ -243,14 +241,6 @@ def _apply_defaults(fq_map: _WeightFQMap) -> None:
243241 if not _granularity_needs_axis_default (fq .granularity ):
244242 continue
245243
246- # get appropriate defaults table
247- if isinstance (fq .granularity , PerChannelGranularity ):
248- axis_defaults = _PER_CHANNEL_WEIGHT_AXIS_DEFAULTS
249- elif isinstance (fq .granularity , PerBlockGranularity ):
250- axis_defaults = _PER_BLOCK_WEIGHT_AXIS_DEFAULTS
251- else :
252- continue
253-
254244 # Resolve each consumer to a module type and look up its default axis
255245 resolved_axes : set [int ] = set ()
256246 all_consumer_names : list [str ] = []
@@ -261,8 +251,8 @@ def _apply_defaults(fq_map: _WeightFQMap) -> None:
261251 else :
262252 module_type = module_type_or_op
263253
264- # pick a default based on this consumer
265- default_axis = axis_defaults . get ( module_type ) if module_type is not None else None
254+ spec = _WEIGHT_AXIS_SPECS . get ( module_type ) if module_type is not None else None
255+ default_axis = spec . default_axis_for ( fq . granularity ) if spec is not None else None
266256 if default_axis is not None :
267257 resolved_axes .add (default_axis )
268258
0 commit comments