Skip to content

Commit 17c9f54

Browse files
Refactor: Move transpose check to backend and use standard slicing
1 parent 80ef438 commit 17c9f54

10 files changed

Lines changed: 137 additions & 93 deletions

File tree

src/nncf/quantization/algorithms/weight_compression/awq.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
from nncf.quantization.algorithms.weight_compression.activation_stats import process_stats
3030
from nncf.quantization.algorithms.weight_compression.backend import WeightCompressionAlgoBackend
3131
from nncf.quantization.algorithms.weight_compression.config import WeightCompressionParameters
32-
from nncf.quantization.algorithms.weight_compression.tensor_slicing import get_weight_slice
33-
from nncf.quantization.algorithms.weight_compression.tensor_slicing import set_weight_slice
3432
from nncf.quantization.algorithms.weight_compression.weight_lowering import float_quantize_dequantize_weight
3533
from nncf.quantization.algorithms.weight_compression.weight_lowering import integer_quantize_dequantize_weight
3634
from nncf.quantization.passes import transform_to_inference_graph
@@ -183,7 +181,9 @@ def apply(
183181
prev_weight = self._backend_entity.get_weight(merge_node, prev_weight_port_id, model, graph)
184182

185183
prev_statistics = statistics[merge_node.node_name]
186-
scale = self._data_aware_step(wp, weight, statistics[k], prev_weight, prev_statistics, weight_port_id)
184+
scale = self._data_aware_step(
185+
wp, weight, statistics[k], prev_weight, prev_statistics, weight_port_id, graph
186+
)
187187

188188
w_scale = fns.unsqueeze(scale, 1 - wp.reduction_axes[0])
189189
a_scale = fns.unsqueeze(1.0 / scale, wp.reduction_axes[0])
@@ -212,7 +212,9 @@ def apply(
212212

213213
return transformed_model
214214

215-
def _data_aware_step(self, wp, weight, statistics, prev_weight=None, prev_statistics=None, weight_port_id=None):
215+
def _data_aware_step(
216+
self, wp, weight, statistics, prev_weight=None, prev_statistics=None, weight_port_id=None, graph=None
217+
):
216218
alpha_step = (self._alpha_max - self._alpha_min) / self._steps
217219
config = wp.compression_config
218220
s, X = process_stats(statistics, self._subset_size)
@@ -222,8 +224,8 @@ def _data_aware_step(self, wp, weight, statistics, prev_weight=None, prev_statis
222224
assert isinstance(wp.reduction_axes, tuple) and len(wp.reduction_axes) == 1
223225
reduction_axis = wp.reduction_axes[0]
224226

225-
# Get transpose_b value to handle weight shape correctly
226-
transpose_b = wp.node_with_weight.layer_attributes.constant_attributes[weight_port_id]["transpose"]
227+
# Get transpose_b value from backend to handle weight shape correctly in a backend-agnostic way
228+
transpose_b = self._backend_entity.get_weight_transpose_b(wp.node_with_weight, weight_port_id, graph)
227229

228230
prev_s, prev_w = None, None
229231
if prev_statistics is not None and prev_weight is not None:
@@ -244,7 +246,7 @@ def _data_aware_step(self, wp, weight, statistics, prev_weight=None, prev_statis
244246

245247
groups_to_correct = list(groups_to_correct)
246248

247-
# Remove the old transpose logic - we'll use get_weight_slice instead
249+
# Remove the old transpose logic - we now rely on explicit transpose_b handling
248250
# if reduction_axis == 0:
249251
# weight = fns.transpose(weight)
250252
# reduction_axis = 1
@@ -263,8 +265,11 @@ def _data_aware_step(self, wp, weight, statistics, prev_weight=None, prev_statis
263265
a_max = 1e2
264266
gscale = fns.clip(gscale, a_min=a_min, a_max=a_max)
265267

266-
# Use get_weight_slice instead of hardcoded slicing
267-
gweight = get_weight_slice(weight, slice(offset, offset + group_size), transpose_b)
268+
# Slice weight block along the input-channel dimension taking transpose_b into account
269+
if transpose_b:
270+
gweight = weight[:, offset : offset + group_size]
271+
else:
272+
gweight = weight[offset : offset + group_size, :]
268273
gacts = X[offset : offset + group_size, :]
269274

270275
fp32_out = fns.matmul(gweight, gacts)
@@ -310,8 +315,8 @@ def _data_aware_step(self, wp, weight, statistics, prev_weight=None, prev_statis
310315
alpha += alpha_step
311316

312317
if best_scale is not None:
313-
# Use set_weight_slice for assignment
314-
set_weight_slice(scale, slice(offset, offset + group_size), best_scale, transpose_b)
318+
# Assign best_scale to the corresponding slice of the scale vector
319+
scale[offset : offset + group_size] = best_scale
315320

316321
return scale
317322

src/nncf/quantization/algorithms/weight_compression/backend.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ def get_reduction_axes(node_with_weight: NNCFNode, weight_port_id: int, graph: N
8888
:return: Reduction shape in tuple format or None if not applicable.
8989
"""
9090

91+
@staticmethod
92+
@abstractmethod
93+
def get_weight_transpose_b(node_with_weight: NNCFNode, weight_port_id: int, graph: NNCFGraph) -> bool:
94+
"""
95+
Returns whether the weight input of the given node is treated as transposed (transpose_b=True).
96+
97+
This is backend-specific and abstracts away how the underlying framework stores MatMul/Gemm attributes.
98+
99+
:param node_with_weight: The node with weight.
100+
:param weight_port_id: The input port ID that corresponds to the weight.
101+
:param graph: The model graph.
102+
:return: True if the backend treats the weight as transposed (e.g., [Out, In]), False otherwise.
103+
"""
104+
91105
@staticmethod
92106
@abstractmethod
93107
def get_weight_names_and_port_ids(node: NNCFNode, graph: NNCFGraph) -> list[tuple[str, int]]:

src/nncf/quantization/algorithms/weight_compression/gptq.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
from nncf.quantization.algorithms.weight_compression.config import WeightCompressionParameters
2828
from nncf.quantization.algorithms.weight_compression.parameters import CompressedWeight
2929
from nncf.quantization.algorithms.weight_compression.scale_estimation import ScaleEstimation
30-
from nncf.quantization.algorithms.weight_compression.tensor_slicing import get_weight_slice
31-
from nncf.quantization.algorithms.weight_compression.tensor_slicing import set_weight_slice
3230
from nncf.quantization.algorithms.weight_compression.weight_lowering import calculate_float_quantization_params
3331
from nncf.quantization.algorithms.weight_compression.weight_lowering import calculate_integer_quantization_params
3432
from nncf.quantization.algorithms.weight_compression.weight_lowering import float_quantize_dequantize_weight
@@ -220,16 +218,19 @@ def _quantize_weights(
220218
)
221219
weight_tensor = fns.astype(weight_tensor, TensorDataType.float32)
222220

223-
# Get transpose_b value to handle weight shape correctly
224-
transpose_b = wc_params.node_with_weight.layer_attributes.constant_attributes[wc_params.weight_port_id][
225-
"transpose"
226-
]
221+
# Get transpose_b value via backend to handle weight shape correctly in a backend-agnostic way
222+
transpose_b = self._backend_entity.get_weight_transpose_b(
223+
wc_params.node_with_weight, wc_params.weight_port_id, graph
224+
)
227225

228226
dead_indices = fns.diag(hessian) == 0
229227
hessian[dead_indices, dead_indices] = 1
230228

231-
# Zero out dead indices using utility helper
232-
set_weight_slice(weight_tensor, dead_indices, 0, transpose_b)
229+
# Zero out dead indices along the input-channel dimension
230+
if transpose_b:
231+
weight_tensor[:, dead_indices] = 0
232+
else:
233+
weight_tensor[dead_indices, :] = 0
233234

234235
scales = []
235236
zero_points = []
@@ -260,26 +261,38 @@ def _quantize_weights(
260261
i2 = min(i1 + self._block_size, columns)
261262
count = i2 - i1
262263

263-
# Extract weight block using utility helper
264-
weight_block = get_weight_slice(weight_tensor, slice(i1, i2), transpose_b).clone()
264+
# Extract weight block along the input-channel dimension
265+
if transpose_b:
266+
weight_block = weight_tensor[:, i1:i2].clone()
267+
else:
268+
weight_block = weight_tensor[i1:i2, :].clone()
265269
quantized_block = fns.zeros_like(weight_block)
266270
error_block = fns.zeros_like(weight_block)
267271
loss_block = fns.zeros_like(weight_block)
268272
hessian_inv_block = hessian_inv[i1:i2, i1:i2]
269273

270274
for i in range(count):
271-
weight_col = get_weight_slice(weight_block, i, transpose_b)
275+
if transpose_b:
276+
weight_col = weight_block[:, i]
277+
else:
278+
weight_col = weight_block[i, :]
272279
hessian_diag_val = hessian_inv_block[i, i]
273280

274281
if (i1 + i) % group_size == 0:
275282
if not block_compression_config.is_integer:
276-
weight_slice = get_weight_slice(weight_tensor, slice(i1 + i, i1 + i + group_size), transpose_b)
283+
if transpose_b:
284+
weight_slice = weight_tensor[:, i1 + i : i1 + i + group_size]
285+
else:
286+
weight_slice = weight_tensor[i1 + i : i1 + i + group_size, :]
277287
scale = calculate_float_quantization_params(
278288
weight_slice, reduction_axes, block_compression_config
279289
)
280290
scales.append(scale)
281291
else:
282-
weight_slice = get_weight_slice(weight_tensor, slice(i1 + i, i1 + i + group_size), transpose_b)
292+
if transpose_b:
293+
weight_slice = weight_tensor[:, i1 + i : i1 + i + group_size]
294+
else:
295+
weight_slice = weight_tensor[i1 + i : i1 + i + group_size, :]
283296
if self._scale_estimation and block_compression_config.num_bits == 4:
284297
activations = [inp[..., (i1 + i) : (i1 + i + group_size)] for inp in inputs]
285298
wc_statistics = ScaleEstimation.activations_to_wc_statistics(activations)
@@ -312,24 +325,34 @@ def _quantize_weights(
312325
precomputed_zero_point=zero_points[-1],
313326
)
314327
quantized_col = fns.flatten(quantized_col)
315-
set_weight_slice(quantized_block, i, quantized_col, transpose_b)
328+
if transpose_b:
329+
quantized_block[:, i] = quantized_col
330+
else:
331+
quantized_block[i, :] = quantized_col
316332
loss_col = (weight_col - quantized_col) ** 2 / hessian_diag_val**2
317-
set_weight_slice(loss_block, i, loss_col, transpose_b)
333+
if transpose_b:
334+
loss_block[:, i] = loss_col
335+
else:
336+
loss_block[i, :] = loss_col
318337

319338
error_col = (weight_col - quantized_col) / hessian_diag_val
320339
if transpose_b:
321340
weight_block[:, i:] -= fns.matmul(
322341
fns.unsqueeze(error_col, 1), fns.unsqueeze(hessian_inv_block[i, i:], 0)
323342
)
324-
set_weight_slice(error_block, i, error_col, transpose_b)
343+
error_block[:, i] = error_col
325344
else:
326345
weight_block[i:, :] -= fns.matmul(
327346
fns.unsqueeze(error_col, 0), fns.unsqueeze(hessian_inv_block[i:, i], 1)
328347
)
329-
set_weight_slice(error_block, i, error_col, transpose_b)
348+
error_block[i, :] = error_col
330349

331-
set_weight_slice(quantized_tensor, slice(i1, i2), quantized_block, transpose_b)
332-
set_weight_slice(losses, slice(i1, i2), loss_block / 2, transpose_b)
350+
if transpose_b:
351+
quantized_tensor[:, i1:i2] = quantized_block
352+
losses[:, i1:i2] = loss_block / 2
353+
else:
354+
quantized_tensor[i1:i2, :] = quantized_block
355+
losses[i1:i2, :] = loss_block / 2
333356

334357
# Update remaining weights with error propagation
335358
if transpose_b:

src/nncf/quantization/algorithms/weight_compression/lora_correction.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import pandas as pd
1515

1616
import nncf
17+
from nncf.common.graph import NNCFGraph
1718
from nncf.common.logging import nncf_logger
1819
from nncf.common.utils.debug import DEBUG_LOG_DIR
1920
from nncf.common.utils.debug import is_debug
@@ -108,24 +109,29 @@ def is_applicable(self, wc_params: WeightCompressionParameters):
108109
return wc_params.compression_config.num_bits == 4
109110

110111
def calculate_adapters(
111-
self, weight: Tensor, compressed_weight: CompressedWeight, wc_params: WeightCompressionParameters
112+
self,
113+
weight: Tensor,
114+
compressed_weight: CompressedWeight,
115+
wc_params: WeightCompressionParameters,
116+
graph: NNCFGraph,
112117
) -> tuple[Tensor, Tensor, list[float]]:
113118
"""
114119
Calculates low rank matrices for a given original and compressed weights.
115120
116121
:param weight: original floating-point weight matrix.
117122
:param compressed_weight: compressed weight matrix.
118123
:param wc_params: parameters of weight compression.
124+
:param graph: The model graph.
119125
:return: two low rank matrices in the order of execution of corresponding linear layers.
120126
"""
121127
layer_name = wc_params.node_with_weight.node_name
122128
layer_statistics = self._statistics[layer_name]
123129
is_debug = self._debug_interface is not None
124130

125-
# Get transpose_b value to handle weight shape correctly
126-
transpose_b = wc_params.node_with_weight.layer_attributes.constant_attributes[wc_params.weight_port_id][
127-
"transpose"
128-
]
131+
# Get transpose_b value via backend to handle weight shape correctly in a backend-agnostic way
132+
transpose_b = self._backend_entity.get_weight_transpose_b(
133+
wc_params.node_with_weight, wc_params.weight_port_id, graph
134+
)
129135

130136
lora_A, lora_B, mean_noises = self.calculate_low_rank_matrices(
131137
weight,

src/nncf/quantization/algorithms/weight_compression/onnx_backend.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,26 @@ def get_reduction_axes(node_with_weight: NNCFNode, weight_port_id: int, graph: N
149149
channel_axes = (0,) + channel_axes
150150
return get_reduction_axes(channel_axes, const_shape)
151151

152+
@staticmethod
153+
def get_weight_transpose_b(node_with_weight: NNCFNode, weight_port_id: int, graph: NNCFGraph) -> bool:
154+
"""
155+
Returns the equivalent of transpose_b for ONNX MatMul/Gemm nodes.
156+
157+
For MatMul the initializer layout already follows the expected [K, N] contract of the op,
158+
so we treat weights as transposed (transpose_b=True) by default. For Gemm we respect the
159+
transB attribute when the corresponding input port matches the B input.
160+
"""
161+
# Gemm-specific handling: attribute name and semantics follow ONNX Gemm spec.
162+
if node_with_weight.metatype is onnx_metatypes.ONNXGemmMetatype and weight_port_id == 1:
163+
node_attrs = node_with_weight.layer_attributes.node_attrs
164+
# In Gemm, transB=1 means the B input is transposed.
165+
trans_b_attr = node_attrs.get("transB", 0)
166+
return bool(trans_b_attr)
167+
168+
# For MatMul and other ops, rely on the fact that the stored initializer already matches
169+
# the expected layout for the backend kernels and treat it as transpose_b=True.
170+
return True
171+
152172
@staticmethod
153173
def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> ONNXTargetPoint:
154174
return ONNXTargetPoint(target_type, target_node_name, port_id)

src/nncf/quantization/algorithms/weight_compression/openvino_backend.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ def get_reduction_axes(node_with_weight: NNCFNode, weight_port_id: int, graph: N
103103
const_shape = node_with_weight.layer_attributes.constant_attributes[weight_port_id]["shape"]
104104
return get_reduction_axes(channel_axes, const_shape)
105105

106+
@staticmethod
107+
def get_weight_transpose_b(node_with_weight: NNCFNode, weight_port_id: int, graph: NNCFGraph) -> bool:
108+
"""
109+
Returns the value of the transpose_b attribute for the given weight input.
110+
111+
OpenVINO stores this information in the constant_attributes of the node layer attributes.
112+
Default to True for backward compatibility with older models that do not expose the flag.
113+
"""
114+
const_attrs = node_with_weight.layer_attributes.constant_attributes[weight_port_id]
115+
return const_attrs.get("transpose", True)
116+
106117
@staticmethod
107118
def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> OVTargetPoint:
108119
return OVTargetPoint(target_type, target_node_name, port_id)
@@ -381,7 +392,7 @@ def transform_model(
381392
compressed_weight.tensor = compressed_weight.tensor.as_numpy_tensor()
382393
if compressed_weight.zero_point is not None:
383394
compressed_weight.zero_point = compressed_weight.zero_point.as_numpy_tensor()
384-
adapters = lora_correction_algo.calculate_adapters(weight, compressed_weight, wc_params)
395+
adapters = lora_correction_algo.calculate_adapters(weight, compressed_weight, wc_params, graph)
385396
self.insert_adapters(wc_params, *adapters, int8_lora=lora_correction_algo.use_int8_adapters)
386397
self.name_to_node_mapping = None
387398

src/nncf/quantization/algorithms/weight_compression/scale_estimation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ def apply(
141141

142142
weight = self._backend_entity.get_weight(wp.node_with_weight, weight_port_id, model, graph)
143143

144-
# Get transpose_b value to handle weight shape correctly
145-
transpose_b = wp.node_with_weight.layer_attributes.constant_attributes[weight_port_id]["transpose"]
144+
# Get transpose_b value via backend to handle weight shape correctly in a backend-agnostic way
145+
transpose_b = self._backend_entity.get_weight_transpose_b(wp.node_with_weight, weight_port_id, graph)
146146

147147
scale, zero_point = self.calculate_quantization_params(
148148
stats,

src/nncf/quantization/algorithms/weight_compression/tensor_slicing.py

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)