Skip to content

fix(tinytorch): constant tensor silently zeroed after quantize/dequantize roundtrip#1444

Merged
profvjreddi merged 1 commit into
harvard-edge:devfrom
Shashank-Tripathi-07:fix/tinytorch-quantize-constant-tensor-zeroed
Apr 22, 2026
Merged

fix(tinytorch): constant tensor silently zeroed after quantize/dequantize roundtrip#1444
profvjreddi merged 1 commit into
harvard-edge:devfrom
Shashank-Tripathi-07:fix/tinytorch-quantize-constant-tensor-zeroed

Conversation

@Shashank-Tripathi-07

Copy link
Copy Markdown
Contributor

What this fixes

quantize_int8() in src/15_quantization/15_quantization.py has a special case for constant tensors (all elements equal, so max == min). The guard exists to avoid division by zero when computing scale. It sets scale=1.0 and zero_point=0, then returns an all-zeros INT8 tensor.

On dequantization the formula is (quantized - zero_point) * scale. With quantized=0, zero_point=0, scale=1.0 that gives 0.0 for every element -- the original constant value is gone.

# Before fix
constant_tensor = Tensor([[5.0, 5.0]])
q, scale, zp = quantize_int8(constant_tensor)  # scale=1.0, zp=0, q=[[0,0]]
restored = (q.data - zp) * scale               # [[0.0, 0.0]]  -- wrong, should be 5.0

Any weight tensor that happens to be uniform (e.g. a bias layer initialised to a constant, or a frozen embedding) is silently zeroed out after quantization. No error is raised.

Root cause

The contributor correctly avoided the division-by-zero when max == min, but forgot that zero_point must encode the constant so dequantization can recover it. The invariant is:

(0 - zero_point) * scale == original_constant
=> zero_point = -original_constant / scale

With scale=1.0 this simplifies to zero_point = round(-min_val), clamped to [-128, 127].

Fix

# After fix
zero_point = int(np.clip(np.round(-min_val), INT8_MIN_VALUE, INT8_MAX_VALUE))

Dequantization now correctly recovers the constant:

(0 - zero_point) * 1.0 == min_val  # holds for any constant in [-128, 127]

Test gap closed

The existing unit test only asserted scale_const == 1.0 and never verified the roundtrip. That is why this survived undetected. The updated test explicitly dequantizes and asserts value recovery for both positive and negative constants.

…al value

quantize_int8() had a special case for tensors where all elements are the
same value (max == min). It set scale=1.0 and zero_point=0 and returned an
all-zeros INT8 tensor. On dequantization: (0 - 0) * 1.0 = 0.0 for every
element, so the reconstructed tensor is all zeros regardless of what the
original constant was. A bias layer initialised to a non-zero constant, or
any weight tensor that happens to be uniform, is silently zeroed out after
a quantize/dequantize roundtrip.

Root cause: the contributor correctly handled the zero-range edge case to
avoid division by zero when computing scale, but forgot that zero_point must
encode the constant so that dequantization can recover it.

Fix: compute zero_point = round(-min_val) (clamped to [-128, 127]) so that
the invariant (0 - zero_point) * scale = min_val holds for constant tensors.

Also tightens the unit test: the old test only asserted scale_const == 1.0
and never checked that the value survived the roundtrip, which is why this
went undetected. New test explicitly dequantizes and asserts recovery of both
positive and negative constants.
@github-actions github-actions Bot added area: tinytorch TinyTorch framework core javascript Pull requests that update javascript code type: bug bug in rendering labels Apr 22, 2026
@profvjreddi profvjreddi merged commit 18428a6 into harvard-edge:dev Apr 22, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tinytorch TinyTorch framework core javascript Pull requests that update javascript code type: bug bug in rendering

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants