Skip to content

Commit b9cad48

Browse files
authored
Fix INT16 TABLE virtual endpoint (pytorch#21629)
Differential Revision: D115076485 Pull Request resolved: pytorch#21629
1 parent 84b2b8a commit b9cad48

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

‎backends/arm/_passes/insert_table_ops.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,22 @@ def generate_16_bit_table_values(
211211
"""
212212

213213
def f(x: torch.Tensor) -> torch.Tensor:
214+
int16_max = torch.iinfo(torch.int16).max
215+
has_full_int16_upper_range = (
216+
in_quantargs.dtype == torch.int16 and in_quantargs.qmax == int16_max
217+
)
214218
x = x.clamp(in_quantargs.qmin, in_quantargs.qmax).to(
215219
dtype=in_quantargs.dtype
216220
)
217221
# Dont use the 7 LSBs.
218222
x = in_quantargs.dequantize_value((x & ~0x7F))
223+
if has_full_int16_upper_range:
224+
# TOSA uses table[512] as the virtual right endpoint when
225+
# interpolating raw INT16 input codes [32640, 32767]. Evaluate
226+
# its real value at affine code 32768 without casting to int16.
227+
x[-1] = (
228+
int16_max + 1 - in_quantargs.get_zp_per_tensor()
229+
) * in_quantargs.get_scale_per_tensor()
219230
x = torch_op(x)
220231
return out_quantargs.quantize_value(x)
221232

‎backends/arm/test/passes/test_insert_table_ops_pass.py‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,126 @@ def test_generate_8bit_table_values_matches_reference_for_qmin_minus_127() -> No
9898
assert torch.equal(lut_values, expected_lut_values)
9999
assert int(lut_values[0]) == int(lut_values[1])
100100
assert int(lut_values[zero_input_index]) == expected_zero_output
101+
102+
103+
def test_generate_16bit_identity_table_is_antisymmetric() -> None:
104+
"""Equal zero-point-zero qparams produce an antisymmetric full-range LUT."""
105+
qargs = QuantArgs(
106+
scale=1.0,
107+
zp=0,
108+
qmin=-32767,
109+
qmax=32767,
110+
dtype=torch.int16,
111+
)
112+
113+
lut_values, lshift = InsertTableOpsPass.generate_16_bit_table_values(
114+
torch.nn.Identity(),
115+
qargs,
116+
qargs,
117+
)
118+
119+
assert lshift == -7
120+
for i in range(len(lut_values) // 2):
121+
neg_value = int(lut_values[i])
122+
pos_value = int(lut_values[~i])
123+
assert neg_value == -pos_value, (
124+
"Expected a zero-point-zero identity LUT to be antisymmetric, but "
125+
f"lut_values[{i}]={neg_value} and "
126+
f"lut_values[{~i}]={pos_value}"
127+
)
128+
129+
130+
def test_generate_16bit_full_range_identity_table_applies_zero_point_delta() -> None:
131+
"""Changing only zero point offsets each LUT value by output_zp - input_zp."""
132+
input_qargs = QuantArgs(
133+
scale=1.0,
134+
zp=1,
135+
qmin=-32767,
136+
qmax=32767,
137+
dtype=torch.int16,
138+
)
139+
output_qargs = QuantArgs(
140+
scale=1.0,
141+
zp=-1,
142+
qmin=-32767,
143+
qmax=32767,
144+
dtype=torch.int16,
145+
)
146+
147+
lut_values, lshift = InsertTableOpsPass.generate_16_bit_table_values(
148+
torch.nn.Identity(),
149+
input_qargs,
150+
output_qargs,
151+
)
152+
153+
table_input_codes = torch.arange(-32768, 32769, 128, dtype=torch.int32)
154+
zero_point_delta = (
155+
output_qargs.get_zp_per_tensor() - input_qargs.get_zp_per_tensor()
156+
)
157+
expected_lut_values = (table_input_codes + zero_point_delta).clamp(
158+
output_qargs.qmin,
159+
output_qargs.qmax,
160+
)
161+
162+
assert lshift == -7
163+
assert torch.equal(lut_values.to(torch.int32), expected_lut_values)
164+
165+
166+
def test_generate_16bit_full_range_identity_table_applies_scale_ratio() -> None:
167+
"""Changing only scale multiplies each LUT value by input / output scale."""
168+
input_qargs = QuantArgs(
169+
scale=0.5,
170+
zp=0,
171+
qmin=-32767,
172+
qmax=32767,
173+
dtype=torch.int16,
174+
)
175+
output_qargs = QuantArgs(
176+
scale=1.0,
177+
zp=0,
178+
qmin=-32767,
179+
qmax=32767,
180+
dtype=torch.int16,
181+
)
182+
183+
lut_values, lshift = InsertTableOpsPass.generate_16_bit_table_values(
184+
torch.nn.Identity(),
185+
input_qargs,
186+
output_qargs,
187+
)
188+
189+
table_input_codes = torch.arange(-32768, 32769, 128, dtype=torch.int32)
190+
scale_ratio = (
191+
input_qargs.get_scale_per_tensor() / output_qargs.get_scale_per_tensor()
192+
)
193+
expected_lut_values = (table_input_codes * scale_ratio).round().to(torch.int32)
194+
195+
assert lshift == -7
196+
assert torch.equal(lut_values.to(torch.int32), expected_lut_values)
197+
198+
199+
def test_generate_16bit_table_clamps_endpoint_for_restricted_range() -> None:
200+
"""A restricted input qmax clamps the final LUT value."""
201+
input_qargs = QuantArgs(
202+
scale=1.0,
203+
zp=0,
204+
qmin=-32767,
205+
qmax=30080,
206+
dtype=torch.int16,
207+
)
208+
output_qargs = QuantArgs(
209+
scale=1.0,
210+
zp=0,
211+
qmin=-32767,
212+
qmax=32767,
213+
dtype=torch.int16,
214+
)
215+
216+
lut_values, lshift = InsertTableOpsPass.generate_16_bit_table_values(
217+
torch.nn.Identity(),
218+
input_qargs,
219+
output_qargs,
220+
)
221+
222+
assert lshift == -7
223+
assert int(lut_values[-1]) == input_qargs.qmax

0 commit comments

Comments
 (0)