INT8 Quantization › Smooth Quantization

Michael BrenndoerferJanuary 11, 20268 min read
Language AI HandbookAll chapters
Part 42 of 67Inference Optimization
Chapter 318INT8 Quantization › Smooth Quantization
Reading section 8 of 15 · Smooth Quantization · 8 minSection 8 of 15 · 8 min

Book contents

Section 8 of 15 · 8 min read

INT8 Quantization › Smooth QuantizationOne section · Sections in this chapter8 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The key insight behind smooth quantization is that while activations are hard to quantize (dynamic, contain outliers), weights are easy to quantize (static, relatively uniform). We can mathematically migrate the quantization difficulty from activations to weights through a channel-wise scaling transformation, making the problem tractable.

The intuition is straightforward. If one matrix is "spiky" with outliers and another is "smooth" and well-behaved, we can transfer some of the spikiness from the first to the second. As long as we do this in a mathematically consistent way that preserves the final computation, we have not changed the model's behavior, only redistributed the quantization difficulty to where it is easier to handle. Think of smooth quantization as a seesaw: we load more weight (quantization difficulty) onto the weight side, which can bear it, and unload the activation side, which was struggling under the strain.

The insight that makes this possible is that the product XW\mathbf{XW} is unchanged if we multiply X\mathbf{X} by some diagonal matrix D\mathbf{D} and also multiply W\mathbf{W} by D−1\mathbf{D}^{-1}. These operations are inverses and cancel out. But while they cancel mathematically, they can sharply change the magnitude distributions of both matrices, making each individually much easier to quantize. The question is just how to choose D\mathbf{D} to optimally redistribute the difficulty.

The Mathematical Equivalence

Consider a linear layer computing Y=XW\mathbf{Y} = \mathbf{X}\mathbf{W}, where X∈Rn×din\mathbf{X} \in \mathbb{R}^{n \times d_{in}} is the activation input and W∈Rdin×dout\mathbf{W} \in \mathbb{R}^{d_{in} \times d_{out}} is the weight matrix. We introduce a diagonal matrix S=diag(s1,s2,…,sdin)\mathbf{S} = \text{diag}(s_1, s_2, \ldots, s_{d_{in}}) with positive scaling factors, then insert S−1S=I\mathbf{S}^{-1}\mathbf{S} = \mathbf{I} between the two matrices:

Y=XW=(XS−1)(SW)(insert identity S−1S=I)=X^W^(regroup terms)\begin{aligned} \mathbf{Y} &= \mathbf{X}\mathbf{W} \\ &= (\mathbf{X}\mathbf{S}^{-1})(\mathbf{S}\mathbf{W}) \quad \text{(insert identity } \mathbf{S}^{-1}\mathbf{S} = \mathbf{I} \text{)} \\ &= \hat{\mathbf{X}}\hat{\mathbf{W}} \quad \text{(regroup terms)} \end{aligned}

where:

  • Y\mathbf{Y}: the output of the linear layer, unchanged by this transformation
  • X\mathbf{X}: the input activation tensor with outlier channels
  • W\mathbf{W}: the weight matrix with relatively uniform distribution
  • S\mathbf{S}: the diagonal smoothing matrix containing scaling factors sjs_j for each input dimension
  • X^=XS−1\hat{\mathbf{X}} = \mathbf{X}\mathbf{S}^{-1}: the smoothed activations, where each column jj is divided by sjs_j
  • W^=SW\hat{\mathbf{W}} = \mathbf{S}\mathbf{W}: the adjusted weights, where each row jj is multiplied by sjs_j

This transformation is mathematically exact since S−1S=I\mathbf{S}^{-1}\mathbf{S} = \mathbf{I}. We have not changed the computation, just how we factor it. The key mathematical property we exploit is that multiplying by a matrix and then its inverse produces the identity, so inserting S−1S\mathbf{S}^{-1}\mathbf{S} between X\mathbf{X} and W\mathbf{W} changes nothing about the final result. We are free to group these factors however we like, and we choose to group S−1\mathbf{S}^{-1} with the activations and S\mathbf{S} with the weights.

The transformation affects the data as follows: smoothed activations X^=XS−1\hat{\mathbf{X}} = \mathbf{X}\mathbf{S}^{-1} divide each column jj of X\mathbf{X} by sjs_j, which shrinks outlier channels by choosing large sjs_j values. Meanwhile, adjusted weights W^=SW\hat{\mathbf{W}} = \mathbf{S}\mathbf{W} multiply each row jj of W\mathbf{W} by the same sjs_j, increasing the magnitude of those rows to compensate. If channel jj has outlier activations, we choose a large sjs_j. This shrinks the activations (making them easier to quantize) while enlarging the corresponding weights (which are already easy to quantize). We are transferring the outlier problem to a domain where it is more manageable.

Choosing the Smoothing Factors

How do we pick the sjs_j values? If we make sjs_j too large, we might shrink the activations so much that we lose precision in other ways, or we might inflate the weights to the point where they become hard to quantize. The optimal choice balances these competing concerns, and the SmoothQuant paper proposes a principled way to reach this balance.

The SmoothQuant paper proposes balancing the quantization difficulty between activations and weights using a migration strength parameter α∈[0,1]\alpha \in [0, 1]:

sj=max⁡(∣X:,j∣)αmax⁡(∣Wj,:∣)1−αs_j = \frac{\max(|\mathbf{X}_{:,j}|)^\alpha}{\max(|\mathbf{W}_{j,:}|)^{1-\alpha}}

where:

  • sjs_j: the smoothing factor for the jj-th input channel
  • max⁡(∣X:,j∣)\max(|\mathbf{X}_{:,j}|): the maximum absolute value in the jj-th column of activations, measured across a calibration dataset
  • max⁡(∣Wj,:∣)\max(|\mathbf{W}_{j,:}|): the maximum absolute value in the jj-th row of weights
  • α\alpha: the migration strength hyperparameter controlling how much difficulty shifts from activations to weights

This formula balances two competing goals. When α=1\alpha = 1, we have sj=max⁡(∣X:,j∣)s_j = \max(|\mathbf{X}_{:,j}|), which fully smooths the activations by dividing each channel by its maximum. This makes the activations perfectly balanced but potentially makes weights hard to quantize. When α=0\alpha = 0, we have sj=1/max⁡(∣Wj,:∣)s_j = 1/\max(|\mathbf{W}_{j,:}|), which leaves activations unchanged. The intermediate value α=0.5\alpha = 0.5 gives the geometric mean, which ensures that after transformation, the maximum activation and maximum weight for each channel have roughly equal magnitude, spreading the quantization difficulty evenly.

To understand this formula intuitively, consider what "equal difficulty" means. After applying the transformation, the smoothed activation maximum for channel jj is max⁡(∣X:,j∣)/sj\max(|\mathbf{X}_{:,j}|)/s_j, and the adjusted weight maximum is max⁡(∣Wj,:∣)⋅sj\max(|\mathbf{W}_{j,:}|) \cdot s_j. For these to be equal:

max⁡(∣X:,j∣)sj=max⁡(∣Wj,:∣)⋅sj\frac{\max(|\mathbf{X}_{:,j}|)}{s_j} = \max(|\mathbf{W}_{j,:}|) \cdot s_j

Solving for sjs_j:

sj2=max⁡(∣X:,j∣)max⁡(∣Wj,:∣)  ⟹  sj=max⁡(∣X:,j∣)max⁡(∣Wj,:∣)s_j^2 = \frac{\max(|\mathbf{X}_{:,j}|)}{\max(|\mathbf{W}_{j,:}|)} \implies s_j = \sqrt{\frac{\max(|\mathbf{X}_{:,j}|)}{\max(|\mathbf{W}_{j,:}|)}}

This is exactly the α=0.5\alpha = 0.5 case of the SmoothQuant formula, confirming that α=0.5\alpha = 0.5 is the "equal difficulty" point. Values above 0.5 shift more difficulty to the weights, values below 0.5 leave more with the activations.

In practice, α=0.5\alpha = 0.5 works well for most models, achieving roughly equal quantization ranges for both activations and weights after smoothing. However, some models may benefit from different values. Models with particularly severe activation outliers might use α>0.5\alpha > 0.5 to more aggressively smooth the activations, while models with already-difficult-to-quantize weights might use α<0.5\alpha < 0.5 to avoid making the weights worse.

Out[6]:
Visualization
Line chart showing smoothed activation maximum decreasing and adjusted weight maximum increasing as alpha varies from 0 to 1, with a red dot marking the balanced intersection near alpha equals 0.5.
Effect of the migration strength parameter alpha on channel maximum magnitudes after smoothing. As alpha increases from 0 to 1, activation magnitudes (blue) decrease while weight magnitudes (orange) increase proportionally. The red dot marks the balanced intersection near alpha equals 0.5, where quantization difficulty is evenly distributed between activations and weights.

Offline Computation and LayerNorm Fusion

A necessary practical advantage of smooth quantization is that the transformation can be computed entirely offline during a calibration phase. This means inference has no additional runtime cost compared to standard quantized inference. The calibration procedure works as follows:

First, run a small calibration dataset (typically a few hundred representative samples) through the model to collect activation statistics. During this calibration forward pass, we record the maximum absolute value seen in each channel across all calibration samples. Second, compute max⁡(∣X:,j∣)\max(|\mathbf{X}_{:,j}|) for each channel using these statistics, capturing how large the outliers tend to be. Third, calculate smoothing factors sjs_j for each layer using the formula above. Fourth, apply the weight transformation W^=SW\hat{\mathbf{W}} = \mathbf{S}\mathbf{W} and store the modified weights. Fifth, and most cleverly, fuse S−1\mathbf{S}^{-1} into the preceding layer's weights or normalization parameters.

Step 5 is important and deserves careful explanation. Rather than explicitly dividing activations by S\mathbf{S} at runtime, we absorb this division into the bias of the previous layer or the scale/shift parameters of the preceding LayerNorm. This means inference has zero additional cost compared to standard quantized inference. The activation values arrive at the quantization step already "smoothed" because the previous layer was modified to produce smoothed outputs.

The fusion works because transformer architectures typically have a LayerNorm immediately before each linear layer. LayerNorm applies a learned scale (γ\gamma) and shift (β\beta) to each channel. We can incorporate the smoothing factor directly into these existing parameters. If the original LayerNorm scale for channel jj was γj\gamma_j and shift was βj\beta_j, we replace them with γj/sj\gamma_j/s_j and βj/sj\beta_j/s_j. The activations coming out of the modified LayerNorm are already "smoothed," and we never perform the division at runtime. This is mathematically equivalent to explicitly dividing at every forward pass but requires zero additional computation.

The offline nature of smooth quantization also means it is compatible with any existing INT8 quantization infrastructure. You do not need to modify the inference engine or write new kernels. Simply transform the weights and LayerNorm parameters before quantizing, then proceed with standard absmax or per-channel quantization. The resulting model can be deployed on any hardware that supports INT8 matrix multiplication.