Skip to content

Commit e5bc5bd

Browse files
committed
fix(quantization): export root-module weights in graph CoreML
A weight on the root module (a bare nn.Linear) exports with a dot-less get_attr target, which _get_weight_input_names rejected with "Invalid weight target path". Treat a dot-less target as a root-module parameter (module name ""), which _get_weight_module already resolves.
1 parent 6162da5 commit e5bc5bd

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

src/coreai_opt/quantization/_graph/_prepare_for_export.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,11 @@ def _get_weight_input_names(
142142
143143
Returns:
144144
Tuple of (module_name, param_name)
145-
- module_name: e.g., "conv", "layer1.0"
145+
- module_name: e.g., "conv", "layer1.0", or "" for a root-module parameter
146146
- param_name: e.g., "weight", "bias"
147147
148148
Raises:
149149
ValueError: If node is not a weight quantization node
150-
ValueError: If weight target path is invalid
151150
152151
"""
153152
if not _is_weight_fake_quant(fake_quant_node, module):
@@ -159,13 +158,10 @@ def _get_weight_input_names(
159158
# Extract module and parameter name from target path
160159
# e.g., "conv.weight" -> ("conv", "weight")
161160
# e.g., "layer1.0.weight" -> ("layer1.0", "weight")
161+
# e.g., "weight" -> ("", "weight") for a parameter on the root module
162162
target_path = str(input_node.target)
163163
last_dot_idx = target_path.rfind(".")
164-
if last_dot_idx == -1:
165-
msg = f"Invalid weight target path: {target_path}"
166-
raise ValueError(msg)
167-
168-
module_name = target_path[:last_dot_idx]
164+
module_name = target_path[:last_dot_idx] if last_dot_idx != -1 else ""
169165
param_name = target_path[last_dot_idx + 1 :]
170166

171167
return module_name, param_name

tests/export/test_pt2e_weight_only_mil_export.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,63 @@ def test_simple_model_weight_only_mil_export(
8383
export_backend=backend,
8484
prepared_model_output=prepared_model_output,
8585
)
86+
87+
88+
@pytest.mark.parametrize("dtype", ["int8", "uint8"])
89+
@pytest.mark.parametrize(
90+
"granularity",
91+
[
92+
PerTensorGranularity(),
93+
PerChannelGranularity(axis=0),
94+
PerBlockGranularity(axis=0, block_size=2),
95+
],
96+
)
97+
def test_root_module_weight_only_mil_export(
98+
dtype: str,
99+
granularity: PerTensorGranularity | PerChannelGranularity | PerBlockGranularity,
100+
) -> None:
101+
"""Test weight-only quantization export to CoreML for a root-module weight.
102+
103+
A bare ``nn.Linear`` owns its weight directly, so the fake-quant input target is the
104+
dot-less ``"weight"`` rather than ``"<submodule>.weight"``. The graph CoreML export
105+
path rejected that with ``Invalid weight target path: weight`` instead of resolving it
106+
to the root module.
107+
"""
108+
model = torch.nn.Linear(8, 4)
109+
model.eval()
110+
model_input = torch.randn(2, 8)
111+
112+
config = QuantizerConfig(
113+
global_config=ModuleQuantizerConfig(
114+
op_state_spec={
115+
"weight": QuantizationSpec(
116+
dtype=dtype,
117+
qscheme=QuantizationScheme.SYMMETRIC,
118+
granularity=granularity,
119+
fake_quantize_cls="default",
120+
qparam_calculator_cls="default",
121+
range_calculator_cls="minmax",
122+
),
123+
},
124+
op_input_spec=None,
125+
op_output_spec=None,
126+
),
127+
)
128+
129+
quantizer = Quantizer(model, config)
130+
prepared_model = quantizer.prepare((model_input,))
131+
132+
with torch.no_grad():
133+
prepared_model_output = prepared_model(model_input)
134+
135+
finalized_model = quantizer.finalize(backend=ExportBackend.CoreML)
136+
137+
export_utils.convert_and_verify(
138+
finalized_model=finalized_model,
139+
input_data=model_input,
140+
expected_ops={
141+
"constexpr_blockwise_shift_scale": 1,
142+
},
143+
export_backend=ExportBackend.CoreML,
144+
prepared_model_output=prepared_model_output,
145+
)

0 commit comments

Comments
 (0)