The useful part, up front

What you’ll take away

  • A quantized code needs its scale and zero point before it identifies a represented value.
  • The original example has no rounding error, yet multiplying raw codes gives the wrong represented dot product.
  • Centering the codes gives an integer accumulator of −4, whose product scale reconstructs −1/2.
  • Changing a code origin together with its codes can preserve the represented values while changing the raw product sum.
  • A later output code has its own scale and zero point; it is a different quantity from the accumulator.
  • A scale can leave a sum only under the appropriate common-factor condition across the summed terms.
01

A stored integer is not automatically the value being multiplied

An AI model can store a value using a small integer while interpreting that integer through a scale and an offset. If you multiply the stored integers as though they were the original numbers, even a perfectly representable example can produce the wrong answer. The problem can appear before any discussion of model accuracy or rounding error.

The ONNX Runtime guide describes an affine relationship between an integer code and its represented value: subtract the zero point, then multiply by the scale [4]. ONNX also documents an integer matrix-product operator with explicit input zero points [1]. Those extra values participate in the meaning of the calculation; they are not optional labels for someone reading a chart.

We will follow three numbers through two equivalent weight encodings. Every chosen input lies exactly on its stated grid. That lets us isolate the arithmetic of offsets and scales, then identify where rounding, output conversion and real implementation limits would enter. The calculations are original teaching examples, checked with exact rational arithmetic, rather than measurements from a deployed model.

The same three inputs have codes and represented values. Original fixture. Activation codes are uint8; weight codes are int8. All listed source values are exactly representable.
Original fixture. Activation codes are uint8; weight codes are int8. All listed source values are exactly representable. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
02

The scale sets the spacing; the zero point names the origin

Imagine integer codes as numbered marks on a ruler. A scale of one half makes neighboring codes represent values half a unit apart. A zero point of 128 says that mark 128 represents zero. Mark 126 then represents −1, and mark 130 represents +1. The integer labels are positive in all three cases, while the represented values are negative, zero and positive.

The ruler analogy describes this example’s evenly spaced values. It does not mean that every quantization format uses the same rule. The selected ONNX linear operators document a scale, a zero point and their associated shapes and types [2][3]. Keep that complete description with the stored codes.

Converting an arbitrary source value onto such a grid may require rounding or limiting it to an endpoint. The documented version-13 quantizer uses nearest-even rounding for the scaled input and saturation to the output type’s range [2]. Our chosen numbers need neither adjustment, so the coming mismatch cannot be blamed on a difficult halfway case or an out-of-range input.

03

Choose a complete three-term example

Take an input list x=(−1, 0, 1) and a weight list w=(1, −1/2, 1/2). A dot product multiplies matching positions and adds the results. Using the original values gives (−1×1)+(0×−1/2)+(1×1/2)=−1/2. This is the reference answer for our small example.

Encode the inputs with scale 1/2 and zero point 128. Their stored codes are (126, 128, 130). Encode the weights with scale 1/4 and zero point 0. Their codes are (4, −2, 2). Reconstructing each list with its own rule returns exactly the values we started with.

The two scales differ, and that difference is intentional. Each input step represents half a unit; each weight step represents a quarter. A product of their centered codes will consequently carry a scale of one eighth. We have supplied every value needed for this calculation, including the sign and origin of each representation. Nothing depends on a hidden calibration set or an unspecified model layer.

04

The raw code product gives a plausible but unrelated number

Multiply the stored codes directly. The first pair contributes 126×4=504, the second contributes 128×−2=−256, and the third contributes 130×2=260. Their sum is 508. These are ordinary, correct integer multiplications.

Multiplying 508 by the combined scale of 1/8 gives 63.5. That is far from the reference answer, −0.5. The multiplication itself did not fail. The calculation used the inputs’ code labels without first accounting for where their zero lies. A large positive code can stand for a small negative value when its origin is 128.

This is why adding the scales to a raw integer result is not enough. Both the spacing and the origin belong to the representation. Our example contains no lost source precision, so increasing the number of stored bits would not fix the particular mistake of omitting an already-declared zero point. The correction is to perform the intended affine calculation with the available metadata.

Adding a scale does not repair a missing zero point. Original numbers from the bias-free dot product. Raw and centered sums are different expressions, even with exact input representation.
Original numbers from the bias-free dot product. Raw and centered sums are different expressions, even with exact input representation. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
05

Recenter each input, accumulate, then restore the product scale

Subtract 128 from the three input codes. They become (−2, 0, 2). The weights already use zero point 0, so their centered codes remain (4, −2, 2). Matching products are now −8, 0 and 4, which add to −4.

Restore the combined scale: −4×1/8=−1/2. That agrees exactly with the original dot product. Each part has a specific job. Subtracting the origins gives the correct signed displacement, multiplication combines those displacements, addition accumulates them, and the product of the scales supplies the result’s real-value spacing.

The chosen operator reference makes this distinction visible by accepting input zero points and specifying an int32 output [1]. Our arithmetic demonstrates the relationship without claiming that a particular runtime follows this sequence instruction by instruction. Implementations may organize equivalent work differently. Whatever their organization, an intermediate named as the centered accumulator must represent the same mathematical quantity if it is to support this reconstruction.

06

An all-zero input supplies a useful interpretation check

Replace the real input list with (0, 0, 0), keeping its scale and zero point unchanged. The correct stored codes are now (128, 128, 128). Subtracting the origin gives three zeros, so every centered product and their sum is zero.

The raw code calculation instead gives 128×4+128×−2+128×2=512. Multiplying by 1/8 produces 64. A calculation that returns 64 for this declared zero input has not preserved the intended meaning of the representation. The example reveals the same missing-origin term without needing a complicated reference result.

The integer code 0 is also available, but under this input rule it represents (0−128)×1/2=−64. Filling a code array with integer zeros would therefore not represent the real zero list used here. The ONNX Runtime guide highlights the role of a representable zero in operations such as zero padding [4]. Our small check illustrates that meaning; it is not a test of a particular network’s padding implementation.

07

Move the weight-code origin without changing the weights

Keep the same real weights and their quarter-unit scale, but change their zero point from 0 to −3. The new codes are (1, −5, −1). Subtracting −3 from each code gives (4, −2, 2), exactly the centered weight list we used before.

The raw integer dot product changes. Using the original input codes, it becomes 126×1+128×−5+130×−1=−644. A different raw number is expected because we changed the weight-code labels. The represented weights have not changed, so the correctly interpreted real dot product must remain −1/2.

This is a useful consistency question: does a calculation depend on the represented values or accidentally on the arbitrary code origin? Our two encodings both stay within their stated int8 range. For these selected values, moving the code labels and their zero point together preserves the represented weights. The statement is conditional on preserving valid codes; it does not say that any origin shift will fit every possible weight into the same finite range.

Changing the weight origin changes four expanded terms. Original alternate weight codes (1, −5, −1), weight zero −3, input zero 128 and three terms. The represented weights remain (1, −1/2, 1/2).
Original alternate weight codes (1, −5, −1), weight zero −3, input zero 128 and three terms. The represented weights remain (1, −1/2, 1/2). · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
08

The expanded formula shows which terms depend on the input

You can obtain the centered sum by expanding the same multiplication. Start with the raw products, subtract the input origin times the sum of weight codes, subtract the weight origin times the sum of input codes, then add back the number of terms times both origins. The last addition is needed because multiplying two subtractions includes a product of their origins.

For the shifted-weight example, the four contributions are −644, +640, +1152 and −1152. They add to −4, the same centered accumulator found by subtracting before every multiplication. This route is a rearrangement of the calculation, not a new approximation.

With weight zero point 0, the last two contributions disappear. In our original example, only 508−512 remains. That can simplify the expression, but an algebraic simplification does not by itself establish a faster runtime. The actual work depends on how an implementation stores inputs, reuses sums, handles shapes and executes the operator.

09

The accumulator and the next output code are different numbers

The centered accumulator is −4, while its reconstructed result is −1/2. Suppose the next stored output uses scale 1/4 and zero point 10. The code for −1/2 is then 8, because (8−10)×1/4=−1/2.

We now have three useful numbers: −4 for the centered accumulator, −0.5 for its represented real result, and 8 for a newly encoded output. None can be substituted for another without its interpretation rule. In particular, writing the accumulator directly into the output-code field would skip a change of scale and origin.

This output happens to lie exactly on the new grid and within the chosen unsigned range. A general output conversion can require rounding and saturation under its own documented rules [2]. The extra conversion is separate from the integer accumulation described by MatMulInteger [1]. Naming the destination representation makes it possible to decide whether a mismatch comes from the dot product, the reconstruction or the later encoding.

An accumulator and a destination code use different metadata. Original exact conversion. Destination is uint8 with scale 1/4 and zero point 10. No rounding or clipping occurs for the chosen value.
Original exact conversion. Destination is uint8 with scale 1/4 and zero point 10. No rounding or clipping occurs for the chosen value. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
10

A common scale can leave the sum only when it is common to its terms

The earlier factor of 1/8 could be applied after adding because every product used the same input scale and the same weight scale. If the scale changes between terms being added, each term may carry a different real-value weight.

Consider a separate two-term construction. Both centered integer products equal 1, but the first carries scale 1/8 and the second scale 1/4. Their real sum is 1/8+1/4=3/8. Adding the integer products first and applying only the first scale gives 2×1/8=1/4, which is a different result.

A statement such as “per-axis scales” therefore needs the axis and the operation. Scales can vary between separate output channels while remaining common across the terms inside each individual dot product. The selected ONNX linear operators document per-tensor and per-axis forms [2][3]. Our two-term construction explains why scale placement matters mathematically; it does not assert that every such arrangement is supported by the integer operator or a particular execution provider.

11

Check representation semantics before measuring approximation error

Our reference inputs survived encoding exactly. A correct centered calculation therefore recovered their dot product exactly. Real quantization can change values through rounding or saturation, so the reconstructed inputs may differ from the originals even when their codes are interpreted correctly [2][4].

These are different checks. First, verify that the arithmetic agrees with the represented inputs and their metadata. Then compare that represented-value result with the original-value reference. A failure in the first check points to interpretation or computation; a difference in the second can reflect the approximation introduced by the chosen representation.

For a complete model, even matching one local calculation does not establish unchanged outputs or task performance. The ONNX Runtime guide describes comparing corresponding weights and activations when investigating quantization differences [4]. Keep the reference, tensor identity and processing stage aligned. A local arithmetic example explains what to compare, but it cannot replace an evaluation of the actual model and the inputs that matter to its use.

12

Read a quantized operation as a complete record

A useful record names the input codes, their integer types, their scales and zero points, the dimension being summed, the accumulator’s meaning and any output conversion. For our example, that record explains why 508, −4, −0.5 and 8 appear at different stages without treating them as interchangeable answers.

A graph’s representation also matters. ONNX Runtime distinguishes quantized operators from a pattern that inserts quantize and dequantize operations around tensors [4]. Seeing an 8-bit tensor somewhere in a graph does not reveal every intermediate dtype or show that all operations happen directly on raw 8-bit codes. Read the relevant operator contract and the surrounding conversions.

The record also needs to identify where its scales came from. The Runtime guide distinguishes parameters obtained using calibration inputs from parameters calculated dynamically [4]. For this article, the scales are declared constants, chosen to make the arithmetic visible. In another system, a code array and a scale copied from a different calculation may not describe the same represented values. Keep the matching metadata with the data.

The result of this article is a reproducible interpretation, not a speed or accuracy rating. Subtract the appropriate origins, preserve each term’s scale, identify the accumulated quantity and name the next representation. Those steps let a reader ask a sharper question about a real system: which precise quantity does this number represent, and which evidence shows that the implementation computed it?

Keep the scale attached to the dimension it describes. Separate original two-term construction. Centered products are both 1, but their scale factors differ. No backend support or timing result is implied.
Separate original two-term construction. Centered products are both 1, but their scale factors differ. No backend support or timing result is implied. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
Follow the evidence

Sources & further reading

Explore the references behind this guide. External pages may change after our review.

  1. MatMulInteger - ONNX 1.24.0 documentation
    ONNX · Accessed 7 Nov 2026
  2. QuantizeLinear - ONNX 1.24.0 documentation
    ONNX · Accessed 7 Nov 2026
  3. DequantizeLinear - ONNX 1.24.0 documentation
    ONNX · Accessed 7 Nov 2026
  4. Quantize ONNX models | onnxruntime
    ONNX Runtime · Accessed 7 Nov 2026

Article updated 7 Nov 2026.

Found something we should revisit?

Good explanations get better with thoughtful feedback.

Suggest a correction