Skip to content

Commit de658f0

Browse files
committed
[converter] Fix negative axis in quantize/dequantize lowering
The quantize/dequantize lowering normalized a negative axis as axis + rank - 1, off by one from the eager op, which resolves it as axis + rank. A per-channel axis=-1 was applied one dimension early, so the converted model used the wrong channel; when the channel and a neighbor dim share a size this is silent, with no shape error. Normalize a negative axis as axis + rank, matching the eager op. Add per-channel negative-axis numerical tests for both ops. They use a (2, 4, 4) shape with equal middle and channel dims so a wrong axis surfaces as a numerical mismatch rather than a reshape error.
1 parent ea728d6 commit de658f0

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

coreai_torch/_custom_to_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,11 @@ def _replace_quantize_or_dequantize(
295295
quant_elem_type = input_type.element_type # dequantize: quant→float
296296
float_elem_type = result_elem_type
297297

298-
# Extract axis; normalize negative axis the same way the C++ lowering does.
298+
# Extract axis; normalize a negative axis the same way the eager op does.
299299
axis_val = _get_optional_int_arg(node, axis_idx, default=0)
300300
input_rank = len(input_type.shape)
301301
if axis_val < 0:
302-
axis_val = axis_val + input_rank - 1
302+
axis_val = axis_val + input_rank
303303

304304
axis = coreai.constant(np.array(axis_val, dtype=np.int32), loc=loc)
305305

tests/ops/test_custom_ops.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,28 @@ def forward(self, x: Tensor) -> Tensor:
411411
prepare_program=inject_subbyte_tensors,
412412
)
413413

414+
async def test_per_channel_negative_axis_numerical(self) -> None:
415+
"""quantize with a per-channel scale on a negative axis matches eager."""
416+
417+
class Model(nn.Module):
418+
def __init__(self) -> None:
419+
super().__init__()
420+
self.register_buffer(
421+
"scale", torch.tensor([0.1, 0.2, 0.3, 0.4], dtype=torch.float32)
422+
)
423+
self.register_buffer("zero_point", torch.zeros(4, dtype=torch.int8))
424+
425+
def forward(self, x: Tensor) -> Tensor:
426+
return torch.ops.coreai.quantize(
427+
x, self.scale, torch.int8, zero_point=self.zero_point, axis=-1
428+
)
429+
430+
model = Model()
431+
x = torch.randn(2, 4, 4)
432+
await validate_numerical_output(
433+
model=model, x=x, prepare_program=inject_subbyte_tensors
434+
)
435+
414436

415437
# ---------------------------------------------------------------------------
416438
# dequantize → coreai.dequantize
@@ -540,6 +562,28 @@ def forward(self, x: Tensor) -> Tensor:
540562
prepare_program=inject_subbyte_tensors,
541563
)
542564

565+
async def test_per_channel_negative_axis_numerical(self) -> None:
566+
"""dequantize with a per-channel scale on a negative axis matches eager."""
567+
568+
class Model(nn.Module):
569+
def __init__(self) -> None:
570+
super().__init__()
571+
self.register_buffer(
572+
"scale", torch.tensor([0.1, 0.2, 0.3, 0.4], dtype=torch.float32)
573+
)
574+
self.register_buffer("zero_point", torch.zeros(4, dtype=torch.int8))
575+
576+
def forward(self, x: Tensor) -> Tensor:
577+
return torch.ops.coreai.dequantize(
578+
x, self.scale, zero_point=self.zero_point, axis=-1
579+
)
580+
581+
model = Model()
582+
x = torch.randint(-128, 127, (2, 4, 4), dtype=torch.int8)
583+
await validate_numerical_output(
584+
model=model, x=x, prepare_program=inject_subbyte_tensors
585+
)
586+
543587

544588
# ---------------------------------------------------------------------------
545589
# sparse_to_dense → coreai.build_sparse_with_bitmask + coreai.sparse_with_bitmask_to_dense

0 commit comments

Comments
 (0)