Sweep the quantization bit width
Lesson: Quantization: Making Models FitCheat sheetTrack: LLM inferenceTrack: Experiment labs
Problem
Quantization stores each weight as a small integer and one float scale. Fewer bits save memory, but each bit removed doubles the rounding step. One large outlier makes it worse, because the outlier sets the scale. In this lab you sweep the bit width from 2 to 8 on a weight matrix with outliers. You measure the error with one scale for the whole matrix and with one scale for each row.
support.py gives make_weights(seed). It returns a (64, 128) matrix of normal weights with standard deviation 0.02, and six outliers near \pm 0.25 in six different rows. It also gives the grid BITS and the error limit THRESHOLD = 0.05. The starter gives qrange(num_bits), which returns the signed range (q_{min}, q_{max}) = (-2^{b-1}, 2^{b-1} - 1).
Task
Write six helper functions and answers().
quantize_symmetric(tensor, num_bits)returns(quantized, scale). The scale is s = \max \lvert W \rvert / q_{max}. If \max \lvert W \rvert = 0, use s = 1.0. The codes areround(W / s)clipped to [q_{min}, q_{max}], as integers. Usenp.round, which rounds a halfway value to the even integer.quantize_per_row(tensor, num_bits)does the same with one scale for each row. It returns the codes and a float array of shape(rows,). A row of zeros gets the scale 1.0.dequantize(quantized, scale)returns the float arrayquantized * scale. Ifscaleis a 1D array, row i usesscale[i].relative_error(original, reconstructed)returns \lVert W - \hat W \rVert_F / \lVert W \rVert_F.bit_sweep(tensor, bits, per_row)returns a float array with one relative error for each bit width inbits. It usesquantize_per_rowwhenper_rowisTrue, elsequantize_symmetric.smallest_bits(bits, errors, threshold)returns the smallest bit width whose error is less than or equal tothreshold. If no bit width passes, it returnsNone.
Then do the experiment in answers().
- Make the weights with
make_weights(seed=0). - Change the bit width over
BITS. Measure the relative error with one scale for the matrix. - Do step 2 again with one scale for each row.
- Report the smallest bit width under
THRESHOLDfor each method assmallest_bits_per_tensorandsmallest_bits_per_row. - Report the 4 bit errors as
error_4bit_per_tensoranderror_4bit_per_row. Report their ratio, per tensor divided by per row, asper_row_gain_4bit.
Input and output
| Name | Type | Shape | Meaning |
|---|---|---|---|
tensor | float array | (rows, cols) | the weight matrix |
num_bits | int | scalar | the bit width b, from 2 to 8 |
quantized | int array | (rows, cols) | the integer codes |
scale | float or float array | scalar or (rows,) | one scale, or one scale for each row |
bits | list of ints | (k,) | the bit widths of the sweep, in increasing order |
errors | list or float array | (k,) | one error for each bit width |
quantize_symmetricreturns an int array and a Pythonfloat.quantize_per_rowreturns an int array and a float array of shape(rows,).dequantizereturns a float array of shape(rows, cols).relative_errorreturns a float.bit_sweepreturns a float array of shape(k,).answers()returns a dict with the five keys above. Each value is a Pythonfloat.
Constraints
1 <= rows, cols <= 256. The original matrix inrelative_erroris not all zeros.- Do not change the input arrays.
- Do not round the answers. The checks accept a relative difference of
1e-6from the reference run. - Use numpy and the standard library only.
Examples
pythonW = np.array([[0.5, -1.0, 0.25], [0.0, 0.0, 0.0]])
quantize_symmetric(W, 4)text(array([[ 4, -7, 2],
[ 0, 0, 0]], dtype=int32), 0.14285714285714285)The value 0.5 / s = 3.5 is halfway, so it rounds to the even integer 4.
pythonquantize_per_row(W, 4)[1]text[0.14285714 1. ]pythonrelative_error(np.array([3.0, 4.0]), np.array([3.0, 3.0]))text0.2pythonbit_sweep(make_weights(0), [4, 8], per_row=True)text[0.18284473 0.0099937 ]Provided code: support.py
Your code and the checks import this module with from support import .... You do not change it.
import numpy as np
BITS = [2, 3, 4, 5, 6, 7, 8]
THRESHOLD = 0.05
def make_weights(seed=0, rows=64, cols=128, n_outliers=6, outlier=0.25):
rng = np.random.default_rng(seed)
weights = rng.normal(0.0, 0.02, size=(rows, cols))
outlier_rows = rng.choice(rows, size=n_outliers, replace=False)
outlier_cols = rng.integers(0, cols, size=n_outliers)
signs = np.where(rng.random(n_outliers) < 0.5, -1.0, 1.0)
weights[outlier_rows, outlier_cols] = signs * outlier * rng.uniform(0.8, 1.2, size=n_outliers)
return weights
Hints
4 hintsSolution and notes
LockedPass every check with Submit to open the reference solution and the notes.
Run it on your computer
Clone the repository, install numpy and run the same checks in a terminal:
python3 scripts/practice/run.py lab-quantization-bits my_answer.py
python3 scripts/practice/run.py lab-quantization-bits my_answer.py --examplesStart my_answer.py from starter.py.
Your code is saved in this browser as you type. On a phone you can read and edit the code, but the checks are easier to use on a desktop.
Keyboard shortcuts
- Ctrl Enter
- Run the examples
- Ctrl Shift Enter
- Submit
- Tab / Shift Tab
- Indent or outdent the lines
- Esc then Tab
- Move the focus out of the editor
- Alt H
- Show the next hint
On a Mac, use Cmd in place of Ctrl.
Select Run to check the examples, or Submit to run every check.